diff --git a/CHANGELOG.md b/CHANGELOG.md index 99d40e8..27811ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [2.1.91 pre 2.2] - 2019-08-17 +### Added +- (Client) Markdown engine +- (Client) Imgur based image posting (through markdown) + +### Changed +- (Client) Removed cloudflare references making hack.chat is self-hosted again +- (Client) The way messages are pushed, closing an xss vuln in PRs 985dd6f and 9fcb235 +- (Client) Side bar layout +- (Client) Fixed some options not storing +- (Client) Fixed firefox drop down menu bug +- (Client) Updated Katex lib + +### Stretched +- The term "minimal" + ## [2.1.9 pre 2.2] - 2019-03-18 ### Changed - Configuration script setup, making it more portable/sane diff --git a/client/audio/index.html b/client/audio/index.html new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/client/audio/index.html @@ -0,0 +1 @@ + diff --git a/client/client.js b/client/client.js index 06bfc1b..ba5a236 100644 --- a/client/client.js +++ b/client/client.js @@ -7,6 +7,103 @@ * */ +// initialize markdown engine +var markdownOptions = { + html: false, + xhtmlOut: false, + breaks: true, + langPrefix: '', + linkify: true, + linkTarget: '_blank" rel="noreferrer', + typographer: true, + quotes: `""''`, + + doHighlight: true, + highlight: function (str, lang) { + if (!markdownOptions.doHighlight || !window.hljs) { return ''; } + + if (lang && hljs.getLanguage(lang)) { + try { + return hljs.highlight(lang, str).value; + } catch (__) {} + } + + try { + return hljs.highlightAuto(str).value; + } catch (__) {} + + return ''; + } +}; + +var md = new Remarkable('full', markdownOptions); + +// image handler +var allowImages = false; +var imgHostWhitelist = [ + 'i.imgur.com', + 'imgur.com', +]; + +function getDomain(link) { + var a = document.createElement('a'); + a.href = link; + return a.hostname; +} + +function isWhiteListed(link) { + return imgHostWhitelist.indexOf(getDomain(link)) !== -1; +} + +md.renderer.rules.image = function (tokens, idx, options) { + var src = Remarkable.utils.escapeHtml(tokens[idx].src); + + if (isWhiteListed(src) && allowImages) { + var imgSrc = ' src="' + Remarkable.utils.escapeHtml(tokens[idx].src) + '"'; + var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : ''; + var alt = ' alt="' + (tokens[idx].alt ? Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(tokens[idx].alt))) : '') + '"'; + var suffix = options.xhtmlOut ? ' /' : ''; + var scrollOnload = isAtBottom() ? ' onload="window.scrollTo(0, document.body.scrollHeight)"' : ''; + return ''; + } + + return '' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(src)) + ''; +}; + +md.renderer.rules.link_open = function (tokens, idx, options) { + var title = tokens[idx].title ? (' title="' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + '"') : ''; + var target = options.linkTarget ? (' target="' + options.linkTarget + '"') : ''; + return ''; +}; + +md.renderer.rules.text = function(tokens, idx) { + tokens[idx].content = Remarkable.utils.escapeHtml(tokens[idx].content); + + if (tokens[idx].content.indexOf('?') !== -1) { + tokens[idx].content = tokens[idx].content.replace(/(^|\s)(\?)\S+?(?=[,.!?:)]?\s|$)/gm, function(match) { + var channelLink = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(match.trim())); + var whiteSpace = ''; + if (match[0] !== '?') { + whiteSpace = match[0]; + } + return whiteSpace + '' + channelLink + ''; + }); + } + + return tokens[idx].content; +}; + +md.use(remarkableKatex); + +function verifyLink(link) { + var linkHref = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(link.href)); + if (linkHref !== link.innerHTML) { + return confirm('Warning, please verify this is where you want to go: ' + linkHref); + } + + return true; +} + var verifyNickname = function (nick) { return /^[a-zA-Z0-9_]{1,24}$/.test(nick); } @@ -67,7 +164,6 @@ var myChannel = window.location.search.replace(/^\?/, ''); var lastSent = [""]; var lastSentPos = 0; - /** Notification switch and local storage behavior **/ var notifySwitch = document.getElementById("notify-switch") var notifySetting = localStorageGet("notify-api") @@ -118,7 +214,6 @@ function RequestNotifyPermission() { } } - // Update localStorage with value of checkbox notifySwitch.addEventListener('change', (event) => { if (event.target.checked) { @@ -138,7 +233,6 @@ if (notifySetting === "true" || notifySetting === true) { notifySwitch.checked = false } - /** Sound switch and local storage behavior **/ var soundSwitch = document.getElementById("sound-switch") var notifySetting = localStorageGet("notify-sound") @@ -159,8 +253,6 @@ if (notifySetting === "true" || notifySetting === true) { soundSwitch.checked = false } - - // Create a new notification after checking if permission has been granted function spawnNotification(title, body) { // Let's check if the browser supports notifications @@ -184,12 +276,11 @@ function spawnNotification(title, body) { var n = new Notification(title, options); } } else if (Notification.permission == "denied") { - // At last, if the user has denied notifications, and you + // At last, if the user has denied notifications, and you // want to be respectful, there is no need to bother them any more. } } - function notify(args) { // Spawn notification if enabled if (notifySwitch.checked) { @@ -320,7 +411,6 @@ function pushMessage(args) { ((args.type === "whisper" || args.type === "invite") && args.from) ) ) { - messageEl.classList.add('refmessage'); notify(args); } @@ -365,41 +455,10 @@ function pushMessage(args) { } // Text - var textEl = document.createElement('pre'); + var textEl = document.createElement('p'); textEl.classList.add('text'); + textEl.innerHTML = md.render(args.text); - textEl.textContent = args.text || ''; - - if($('#syntax-highlight').checked && textEl.textContent.includes('```')) { - var textParts = textEl.textContent.split('```'); - var ignore = 0; - textEl.innerHTML = ''; - for(var i=0; i< textParts.length; i++) { - if(i==ignore) { - textEl.innerHTML += parseLatex(textParts[i]); - continue; - } - var lang = textParts[i].split(/\s+/g)[0]; - if(lang == '') { - textEl.innerHTML += parseLatex('```' + textParts[i]); - continue; - } - - var codeEl = document.createElement('code'); - codeEl.classList.add(lang); - codeEl.textContent = textParts[i].replace(lang, '').trim(); - hljs.highlightBlock(codeEl); - - textEl.innerHTML += codeEl.outerHTML.toString(); - ignore = i+1; - } - } else { - var parsed = parseLatex(textEl.textContent); - if(parsed != null) - textEl.innerHTML = parsed; - } - textEl.innerHTML = textEl.innerHTML.replace(/(\?|https?:\/\/)\S+?(?=[,.!?:)]?\s|$)/g, parseLinks); - messageEl.appendChild(textEl); // Scroll to bottom @@ -413,27 +472,6 @@ function pushMessage(args) { updateTitle(); } -function parseLatex(str) { - if ($('#parse-latex').checked) { - // Temporary hotfix for \rule spamming, see https://github.com/Khan/KaTeX/issues/109 - str = str.replace(/\\rule|\\\\\s*\[.*?\]/g, ''); - var holEl = document.createElement('p'); - holEl.textContent = str; - try { - renderMathInElement(holEl, { - delimiters: [ - { left: "$$", right: "$$", display: true }, - { left: "$", right: "$", display: false }, - ] - }); - return holEl.innerHTML.toString(); - } catch (e) { - console.warn(e); - } - } - return null; -} - function insertAtCursor(text) { var input = $('#chatinput'); var start = input.selectionStart || 0; @@ -453,19 +491,6 @@ function send(data) { } } -function parseLinks(g0) { - var a = document.createElement('a'); - - a.innerHTML = g0; - - var url = a.textContent; - - a.href = url; - a.target = '_blank'; - - return a.outerHTML; -} - var windowActive = true; var unread = 0; @@ -620,7 +645,6 @@ $('#chatinput').oninput = function () { updateInputSize(); - /* sidebar */ $('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function (e) { @@ -629,7 +653,14 @@ $('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function (e) { e.stopPropagation(); } -$('#sidebar').onmouseleave = document.ontouchstart = function () { +$('#sidebar').onmouseleave = document.ontouchstart = function (event) { + var e = event.toElement || event.relatedTarget; + try { + if (e.parentNode == this || e == this) { + return; + } + } catch (e) { return; } + if (!$('#pin-sidebar').checked) { $('#sidebar-content').classList.add('hidden'); $('#sidebar').classList.remove('expand'); @@ -639,9 +670,7 @@ $('#sidebar').onmouseleave = document.ontouchstart = function () { $('#clear-messages').onclick = function () { // Delete children elements var messages = $('#messages'); - while (messages.firstChild) { - messages.removeChild(messages.firstChild); - } + messages.innerHTML = ''; } // Restore settings from localStorage @@ -657,6 +686,8 @@ if (localStorageGet('joined-left') == 'false') { if (localStorageGet('parse-latex') == 'false') { $('#parse-latex').checked = false; + md.inline.ruler.disable([ 'katex' ]); + md.block.ruler.disable([ 'katex' ]); } $('#pin-sidebar').onchange = function (e) { @@ -668,7 +699,37 @@ $('#joined-left').onchange = function (e) { } $('#parse-latex').onchange = function (e) { - localStorageSet('parse-latex', !!e.target.checked); + var enabled = !!e.target.checked; + localStorageSet('parse-latex', enabled); + if (enabled) { + md.inline.ruler.enable([ 'katex' ]); + md.block.ruler.enable([ 'katex' ]); + } else { + md.inline.ruler.disable([ 'katex' ]); + md.block.ruler.disable([ 'katex' ]); + } +} + +if (localStorageGet('syntax-highlight') == 'false') { + $('#syntax-highlight').checked = false; + markdownOptions.doHighlight = false; +} + +$('#syntax-highlight').onchange = function (e) { + var enabled = !!e.target.checked; + localStorageSet('syntax-highlight', enabled); + markdownOptions.doHighlight = enabled; +} + +if (localStorageGet('allow-imgur') == 'false') { + $('#allow-imgur').checked = false; + allowImages = false; +} + +$('#allow-imgur').onchange = function (e) { + var enabled = !!e.target.checked; + localStorageSet('allow-imgur', enabled); + allowImages = enabled; } // User list @@ -772,7 +833,7 @@ function setScheme(scheme) { function setHighlight(scheme) { currentHighlight = scheme; - $('#highlight-link').href = "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/" + scheme + ".min.css"; + $('#highlight-link').href = "vendor/hljs/styles/" + scheme + ".min.css"; localStorageSet('highlight', scheme); } diff --git a/client/imgs/index.html b/client/imgs/index.html new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/client/imgs/index.html @@ -0,0 +1 @@ + diff --git a/client/index.html b/client/index.html index 6931451..ca623ec 100644 --- a/client/index.html +++ b/client/index.html @@ -5,14 +5,18 @@