diff options
Diffstat (limited to '')
-rw-r--r-- | client/client.js | 1377 |
1 files changed, 713 insertions, 664 deletions
diff --git a/client/client.js b/client/client.js index 3624925..5a9537d 100644 --- a/client/client.js +++ b/client/client.js @@ -5,90 +5,134 @@ * soon. As a result of this, the current code has been deprecated * and will not actively be updated. * -*/ + */ // 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 ''; - } + 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); +var md = new Remarkable("full", markdownOptions); // image handler var allowImages = false; -var imgHostWhitelist = [ - 'i.imgur.com', - 'imgur.com', -]; +var imgHostWhitelist = ["i.imgur.com", "imgur.com"]; function getDomain(link) { - var a = document.createElement('a'); - a.href = link; - return a.hostname; + var a = document.createElement("a"); + a.href = link; + return a.hostname; } function isWhiteListed(link) { - return imgHostWhitelist.indexOf(getDomain(link)) !== -1; + 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 '<a href="' + src + '" target="_blank" rel="noreferrer"><img' + scrollOnload + imgSrc + alt + title + suffix + '></a>'; - } - - return '<a href="' + src + '" target="_blank" rel="noreferrer">' + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(src)) + '</a>'; + 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 ( + '<a href="' + + src + + '" target="_blank" rel="noreferrer"><img' + + scrollOnload + + imgSrc + + alt + + title + + suffix + + "></a>" + ); + } + + return ( + '<a href="' + + src + + '" target="_blank" rel="noreferrer">' + + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(src)) + + "</a>" + ); }; 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 '<a rel="noreferrer" onclick="return verifyLink(this)" href="' + Remarkable.utils.escapeHtml(tokens[idx].href) + '"' + title + target + '>'; + var title = tokens[idx].title + ? ' title="' + + Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(tokens[idx].title)) + + '"' + : ""; + var target = options.linkTarget ? ' target="' + options.linkTarget + '"' : ""; + return ( + '<a rel="noreferrer" onclick="return verifyLink(this)" href="' + + Remarkable.utils.escapeHtml(tokens[idx].href) + + '"' + + title + + target + + ">" + ); }; -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 + '<a href="' + channelLink + '" target="_blank">' + channelLink + '</a>'; - }); - } +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 + '<a href="' + channelLink + '" target="_blank">' + channelLink + "</a>"; + } + ); + } return tokens[idx].content; }; @@ -96,790 +140,795 @@ md.renderer.rules.text = function(tokens, idx) { 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); - } + 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; + return true; } var verifyNickname = function (nick) { - return /^[a-zA-Z0-9_]{1,24}$/.test(nick); -} + return /^[a-zA-Z0-9_]{1,24}$/.test(nick); +}; var frontpage = [ - " _ _ _ _ ", - " | |_ ___ ___| |_ ___| |_ ___| |_ ", - " | |_ || _| '_| | _| |_ || _|", - " |_|_|__/|___|_,_|.|___|_|_|__/|_| ", - "", - "", - "Welcome to hack.chat, a minimal, distraction-free chat application.", - "Channels are created, joined and shared with the url, create your own channel by changing the text after the question mark.", - "If you wanted your channel name to be 'your-channel': https://hack.chat/?your-channel", - "There are no channel lists, so a secret channel name can be used for private discussions.", - "", - "Here are some pre-made channels you can join:", - "?lounge ?meta", - "?math ?physics ?chemistry", - "?technology ?programming", - "?games ?banana", - "And here's a random one generated just for you: ?" + Math.random().toString(36).substr(2, 8), - "", - "Formatting:", - "Whitespace is preserved, so source code can be pasted verbatim.", - "Surround LaTeX with a dollar sign for inline style $\\zeta(2) = \\pi^2/6$, and two dollars for display. $$\\int_0^1 \\int_0^1 \\frac{1}{1-xy} dx dy = \\frac{\\pi^2}{6}$$", - "For syntax highlight, wrap the code like: ```<language> <the code>``` where <language> is any known programming language.", - "", - "Current Github: https://github.com/hack-chat", - "Legacy GitHub: https://github.com/AndrewBelt/hack.chat", - "", - "Bots, Android clients, desktop clients, browser extensions, docker images, programming libraries, server modules and more:", - "https://github.com/hack-chat/3rd-party-software-list", - "", - "Server and web client released under the WTFPL and MIT open source license.", - "No message history is retained on the hack.chat server." + " _ _ _ _ ", + " | |_ ___ ___| |_ ___| |_ ___| |_ ", + " | |_ || _| '_| | _| |_ || _|", + " |_|_|__/|___|_,_|.|___|_|_|__/|_| ", + "", + "", + "Welcome to hack.chat, a minimal, distraction-free chat application.", + "Channels are created, joined and shared with the url, create your own channel by changing the text after the question mark.", + "If you wanted your channel name to be 'your-channel': https://hack.chat/?your-channel", + "There are no channel lists, so a secret channel name can be used for private discussions.", + "", + "Here are some pre-made channels you can join:", + "?lounge ?meta", + "?math ?physics ?chemistry", + "?technology ?programming", + "?games ?banana", + "And here's a random one generated just for you: ?" + Math.random().toString(36).substr(2, 8), + "", + "Formatting:", + "Whitespace is preserved, so source code can be pasted verbatim.", + "Surround LaTeX with a dollar sign for inline style $\\zeta(2) = \\pi^2/6$, and two dollars for display. $$\\int_0^1 \\int_0^1 \\frac{1}{1-xy} dx dy = \\frac{\\pi^2}{6}$$", + "For syntax highlight, wrap the code like: ```<language> <the code>``` where <language> is any known programming language.", + "", + "Current Github: https://github.com/hack-chat", + "Legacy GitHub: https://github.com/AndrewBelt/hack.chat", + "", + "Bots, Android clients, desktop clients, browser extensions, docker images, programming libraries, server modules and more:", + "https://github.com/hack-chat/3rd-party-software-list", + "", + "Server and web client released under the WTFPL and MIT open source license.", + "No message history is retained on the hack.chat server.", ].join("\n"); function $(query) { - return document.querySelector(query); + return document.querySelector(query); } function localStorageGet(key) { - try { - return window.localStorage[key] - } catch (e) { } + try { + return window.localStorage[key]; + } catch (e) {} } function localStorageSet(key, val) { - try { - window.localStorage[key] = val - } catch (e) { } + try { + window.localStorage[key] = val; + } catch (e) {} } var ws; -var myNick = localStorageGet('my-nick') || ''; -var myChannel = window.location.search.replace(/^\?/, ''); +var myNick = localStorageGet("my-nick") || ""; +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") +var notifySwitch = document.getElementById("notify-switch"); +var notifySetting = localStorageGet("notify-api"); var notifyPermissionExplained = 0; // 1 = granted msg shown, -1 = denied message shown // Inital request for notifications permission function RequestNotifyPermission() { - try { - var notifyPromise = Notification.requestPermission(); - if (notifyPromise) { - notifyPromise.then(function (result) { - console.log("Hack.Chat notification permission: " + result); - if (result === "granted") { - if (notifyPermissionExplained === 0) { - pushMessage({ - cmd: "chat", - nick: "*", - text: "Notifications permission granted.", - time: null - }); - notifyPermissionExplained = 1; - } - return false; - } else { - if (notifyPermissionExplained === 0) { - pushMessage({ - cmd: "chat", - nick: "*", - text: "Notifications permission denied, you won't be notified if someone @mentions you.", - time: null - }); - notifyPermissionExplained = -1; - } - return true; - } - }); - } - } catch (error) { - pushMessage({ - cmd: "chat", - nick: "*", - text: "Unable to create a notification.", - time: null - }); - console.error("An error occured trying to request notification permissions. This browser might not support desktop notifications.\nDetails:") - console.error(error) - return false; - } + try { + var notifyPromise = Notification.requestPermission(); + if (notifyPromise) { + notifyPromise.then(function (result) { + console.log("Hack.Chat notification permission: " + result); + if (result === "granted") { + if (notifyPermissionExplained === 0) { + pushMessage({ + cmd: "chat", + nick: "*", + text: "Notifications permission granted.", + time: null, + }); + notifyPermissionExplained = 1; + } + return false; + } else { + if (notifyPermissionExplained === 0) { + pushMessage({ + cmd: "chat", + nick: "*", + text: + "Notifications permission denied, you won't be notified if someone @mentions you.", + time: null, + }); + notifyPermissionExplained = -1; + } + return true; + } + }); + } + } catch (error) { + pushMessage({ + cmd: "chat", + nick: "*", + text: "Unable to create a notification.", + time: null, + }); + console.error( + "An error occured trying to request notification permissions. This browser might not support desktop notifications.\nDetails:" + ); + console.error(error); + return false; + } } // Update localStorage with value of checkbox -notifySwitch.addEventListener('change', (event) => { - if (event.target.checked) { - RequestNotifyPermission(); - } - localStorageSet("notify-api", notifySwitch.checked) -}) +notifySwitch.addEventListener("change", (event) => { + if (event.target.checked) { + RequestNotifyPermission(); + } + localStorageSet("notify-api", notifySwitch.checked); +}); // Check if localStorage value is set, defaults to OFF if (notifySetting === null) { - localStorageSet("notify-api", "false") - notifySwitch.checked = false + localStorageSet("notify-api", "false"); + notifySwitch.checked = false; } // Configure notifySwitch checkbox element if (notifySetting === "true" || notifySetting === true) { - notifySwitch.checked = true + notifySwitch.checked = true; } else if (notifySetting === "false" || notifySetting === false) { - notifySwitch.checked = false + notifySwitch.checked = false; } /** Sound switch and local storage behavior **/ -var soundSwitch = document.getElementById("sound-switch") -var notifySetting = localStorageGet("notify-sound") +var soundSwitch = document.getElementById("sound-switch"); +var notifySetting = localStorageGet("notify-sound"); // Update localStorage with value of checkbox -soundSwitch.addEventListener('change', (event) => { - localStorageSet("notify-sound", soundSwitch.checked) -}) +soundSwitch.addEventListener("change", (event) => { + localStorageSet("notify-sound", soundSwitch.checked); +}); // Check if localStorage value is set, defaults to OFF if (notifySetting === null) { - localStorageSet("notify-sound", "false") - soundSwitch.checked = false + localStorageSet("notify-sound", "false"); + soundSwitch.checked = false; } // Configure soundSwitch checkbox element if (notifySetting === "true" || notifySetting === true) { - soundSwitch.checked = true + soundSwitch.checked = true; } else if (notifySetting === "false" || notifySetting === false) { - soundSwitch.checked = false + 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 - if (!("Notification" in window)) { - console.error("This browser does not support desktop notification"); - } else if (Notification.permission === "granted") { // Check if notification permissions are already given - // If it's okay let's create a notification - var options = { - body: body, - icon: "/favicon-96x96.png" - }; - var n = new Notification(title, options); - } - // Otherwise, we need to ask the user for permission - else if (Notification.permission !== "denied") { - if (RequestNotifyPermission()) { - var options = { - body: body, - icon: "/favicon-96x96.png" - }; - var n = new Notification(title, options); - } - } else if (Notification.permission == "denied") { - // At last, if the user has denied notifications, and you - // want to be respectful, there is no need to bother them any more. - } + // Let's check if the browser supports notifications + if (!("Notification" in window)) { + console.error("This browser does not support desktop notification"); + } else if (Notification.permission === "granted") { + // Check if notification permissions are already given + // If it's okay let's create a notification + var options = { + body: body, + icon: "/favicon-96x96.png", + }; + var n = new Notification(title, options); + } + // Otherwise, we need to ask the user for permission + else if (Notification.permission !== "denied") { + if (RequestNotifyPermission()) { + var options = { + body: body, + icon: "/favicon-96x96.png", + }; + var n = new Notification(title, options); + } + } else if (Notification.permission == "denied") { + // 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) { - spawnNotification("?" + myChannel + " — " + args.nick, args.text) - } - - // Play sound if enabled - if (soundSwitch.checked) { - var soundPromise = document.getElementById("notify-sound").play(); - if (soundPromise) { - soundPromise.catch(function (error) { - console.error("Problem playing sound:\n" + error); - }); - } - } + // Spawn notification if enabled + if (notifySwitch.checked) { + spawnNotification("?" + myChannel + " — " + args.nick, args.text); + } + + // Play sound if enabled + if (soundSwitch.checked) { + var soundPromise = document.getElementById("notify-sound").play(); + if (soundPromise) { + soundPromise.catch(function (error) { + console.error("Problem playing sound:\n" + error); + }); + } + } } function join(channel) { - if (document.domain == 'hack.chat') { - // For https://hack.chat/ - ws = new WebSocket('wss://hack.chat/chat-ws'); - } else { - // for local installs - var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' - // if you changed the port during the server config, change 'wsPath' - // to the new port (example: ':8080') - // if you are reverse proxying, change 'wsPath' to the new location - // (example: '/chat-ws') - var wsPath = ':6060'; - ws = new WebSocket(protocol + '//' + document.domain + wsPath); - } - - var wasConnected = false; - - ws.onopen = function () { - if (!wasConnected) { - if (location.hash) { - myNick = location.hash.substr(1); - } else { - myNick = prompt('Nickname:', myNick); - } - } - - if (myNick) { - localStorageSet('my-nick', myNick); - send({ cmd: 'join', channel: channel, nick: myNick }); - } - - wasConnected = true; - } - - ws.onclose = function () { - if (wasConnected) { - pushMessage({ nick: '!', text: "Server disconnected. Attempting to reconnect. . ." }); - } - - window.setTimeout(function () { - join(channel); - }, 2000); - } - - ws.onmessage = function (message) { - var args = JSON.parse(message.data); - var cmd = args.cmd; - var command = COMMANDS[cmd]; - command.call(null, args); - } + if (document.domain == "hack.chat") { + // For https://hack.chat/ + ws = new WebSocket("wss://hack.chat/chat-ws"); + } else { + // for local installs + var protocol = location.protocol === "https:" ? "wss:" : "ws:"; + // if you changed the port during the server config, change 'wsPath' + // to the new port (example: ':8080') + // if you are reverse proxying, change 'wsPath' to the new location + // (example: '/chat-ws') + var wsPath = "/socket"; + ws = new WebSocket(protocol + "//" + document.domain + wsPath); + } + + var wasConnected = false; + + ws.onopen = function () { + if (!wasConnected) { + if (location.hash) { + myNick = location.hash.substr(1); + } else { + myNick = prompt("Nickname:", myNick); + } + } + + if (myNick) { + localStorageSet("my-nick", myNick); + send({cmd: "join", channel: channel, nick: myNick}); + } + + wasConnected = true; + }; + + ws.onclose = function () { + if (wasConnected) { + pushMessage({nick: "!", text: "Server disconnected. Attempting to reconnect. . ."}); + } + + window.setTimeout(function () { + join(channel); + }, 2000); + }; + + ws.onmessage = function (message) { + var args = JSON.parse(message.data); + var cmd = args.cmd; + var command = COMMANDS[cmd]; + command.call(null, args); + }; } var COMMANDS = { - chat: function (args) { - if (ignoredUsers.indexOf(args.nick) >= 0) { - return; - } - pushMessage(args); - }, + chat: function (args) { + if (ignoredUsers.indexOf(args.nick) >= 0) { + return; + } + pushMessage(args); + }, - info: function (args) { - args.nick = '*'; - pushMessage(args); - }, + info: function (args) { + args.nick = "*"; + pushMessage(args); + }, - warn: function (args) { - args.nick = '!'; - pushMessage(args); - }, + warn: function (args) { + args.nick = "!"; + pushMessage(args); + }, - onlineSet: function (args) { - var nicks = args.nicks; + onlineSet: function (args) { + var nicks = args.nicks; - usersClear(); + usersClear(); - nicks.forEach(function (nick) { - userAdd(nick); - }); + nicks.forEach(function (nick) { + userAdd(nick); + }); - pushMessage({ nick: '*', text: "Users online: " + nicks.join(", ") }) - }, + pushMessage({nick: "*", text: "Users online: " + nicks.join(", ")}); + }, - onlineAdd: function (args) { - var nick = args.nick; + onlineAdd: function (args) { + var nick = args.nick; - userAdd(nick); + userAdd(nick); - if ($('#joined-left').checked) { - pushMessage({ nick: '*', text: nick + " joined" }); - } - }, + if ($("#joined-left").checked) { + pushMessage({nick: "*", text: nick + " joined"}); + } + }, - onlineRemove: function (args) { - var nick = args.nick; + onlineRemove: function (args) { + var nick = args.nick; - userRemove(nick); + userRemove(nick); - if ($('#joined-left').checked) { - pushMessage({ nick: '*', text: nick + " left" }); - } - } -} + if ($("#joined-left").checked) { + pushMessage({nick: "*", text: nick + " left"}); + } + }, +}; function pushMessage(args) { - // Message container - var messageEl = document.createElement('div'); - - if ( - typeof (myNick) === 'string' && ( - args.text.match(new RegExp('@' + myNick.split('#')[0] + '\\b', "gi")) || - ((args.type === "whisper" || args.type === "invite") && args.from) - ) - ) { - notify(args); - } - - messageEl.classList.add('message'); - - if (verifyNickname(myNick) && args.nick == myNick) { - messageEl.classList.add('me'); - } else if (args.nick == '!') { - messageEl.classList.add('warn'); - } else if (args.nick == '*') { - messageEl.classList.add('info'); - } else if (args.admin) { - messageEl.classList.add('admin'); - } else if (args.mod) { - messageEl.classList.add('mod'); - } - - // Nickname - var nickSpanEl = document.createElement('span'); - nickSpanEl.classList.add('nick'); - messageEl.appendChild(nickSpanEl); - - if (args.trip) { - var tripEl = document.createElement('span'); - tripEl.textContent = args.trip + " "; - tripEl.classList.add('trip'); - nickSpanEl.appendChild(tripEl); - } - - if (args.nick) { - var nickLinkEl = document.createElement('a'); - nickLinkEl.textContent = args.nick; - - nickLinkEl.onclick = function () { - insertAtCursor("@" + args.nick + " "); - $('#chatinput').focus(); - } - - var date = new Date(args.time || Date.now()); - nickLinkEl.title = date.toLocaleString(); - nickSpanEl.appendChild(nickLinkEl); - } - - // Text - var textEl = document.createElement('p'); - textEl.classList.add('text'); - textEl.innerHTML = md.render(args.text); - - messageEl.appendChild(textEl); - - // Scroll to bottom - var atBottom = isAtBottom(); - $('#messages').appendChild(messageEl); - if (atBottom) { - window.scrollTo(0, document.body.scrollHeight); - } - - unread += 1; - updateTitle(); + // Message container + var messageEl = document.createElement("div"); + + if ( + typeof myNick === "string" && + (args.text.match(new RegExp("@" + myNick.split("#")[0] + "\\b", "gi")) || + ((args.type === "whisper" || args.type === "invite") && args.from)) + ) { + notify(args); + } + + messageEl.classList.add("message"); + + if (verifyNickname(myNick) && args.nick == myNick) { + messageEl.classList.add("me"); + } else if (args.nick == "!") { + messageEl.classList.add("warn"); + } else if (args.nick == "*") { + messageEl.classList.add("info"); + } else if (args.admin) { + messageEl.classList.add("admin"); + } else if (args.mod) { + messageEl.classList.add("mod"); + } + + // Nickname + var nickSpanEl = document.createElement("span"); + nickSpanEl.classList.add("nick"); + messageEl.appendChild(nickSpanEl); + + if (args.trip) { + var tripEl = document.createElement("span"); + tripEl.textContent = args.trip + " "; + tripEl.classList.add("trip"); + nickSpanEl.appendChild(tripEl); + } + + if (args.nick) { + var nickLinkEl = document.createElement("a"); + nickLinkEl.textContent = args.nick; + + nickLinkEl.onclick = function () { + insertAtCursor("@" + args.nick + " "); + $("#chatinput").focus(); + }; + + var date = new Date(args.time || Date.now()); + nickLinkEl.title = date.toLocaleString(); + nickSpanEl.appendChild(nickLinkEl); + } + + // Text + var textEl = document.createElement("p"); + textEl.classList.add("text"); + textEl.innerHTML = md.render(args.text); + + messageEl.appendChild(textEl); + + // Scroll to bottom + var atBottom = isAtBottom(); + $("#messages").appendChild(messageEl); + if (atBottom) { + window.scrollTo(0, document.body.scrollHeight); + } + + unread += 1; + updateTitle(); } function insertAtCursor(text) { - var input = $('#chatinput'); - var start = input.selectionStart || 0; - var before = input.value.substr(0, start); - var after = input.value.substr(start); + var input = $("#chatinput"); + var start = input.selectionStart || 0; + var before = input.value.substr(0, start); + var after = input.value.substr(start); - before += text; - input.value = before + after; - input.selectionStart = input.selectionEnd = before.length; + before += text; + input.value = before + after; + input.selectionStart = input.selectionEnd = before.length; - updateInputSize(); + updateInputSize(); } function send(data) { - if (ws && ws.readyState == ws.OPEN) { - ws.send(JSON.stringify(data)); - } + if (ws && ws.readyState == ws.OPEN) { + ws.send(JSON.stringify(data)); + } } var windowActive = true; var unread = 0; window.onfocus = function () { - windowActive = true; + windowActive = true; - updateTitle(); -} + updateTitle(); +}; window.onblur = function () { - windowActive = false; -} + windowActive = false; +}; window.onscroll = function () { - if (isAtBottom()) { - updateTitle(); - } -} + if (isAtBottom()) { + updateTitle(); + } +}; function isAtBottom() { - return (window.innerHeight + window.scrollY) >= (document.body.scrollHeight - 1); + return window.innerHeight + window.scrollY >= document.body.scrollHeight - 1; } function updateTitle() { - if (windowActive && isAtBottom()) { - unread = 0; - } - - var title; - if (myChannel) { - title = "?" + myChannel; - } else { - title = "hack.chat"; - } - - if (unread > 0) { - title = '(' + unread + ') ' + title; - } - - document.title = title; -} - -$('#footer').onclick = function () { - $('#chatinput').focus(); -} - -$('#chatinput').onkeydown = function (e) { - if (e.keyCode == 13 /* ENTER */ && !e.shiftKey) { - e.preventDefault(); - - // Submit message - if (e.target.value != '') { - var text = e.target.value; - e.target.value = ''; - - send({ cmd: 'chat', text: text }); - - lastSent[0] = text; - lastSent.unshift(""); - lastSentPos = 0; - - updateInputSize(); - } - } else if (e.keyCode == 38 /* UP */) { - // Restore previous sent messages - if (e.target.selectionStart === 0 && lastSentPos < lastSent.length - 1) { - e.preventDefault(); - - if (lastSentPos == 0) { - lastSent[0] = e.target.value; - } - - lastSentPos += 1; - e.target.value = lastSent[lastSentPos]; - e.target.selectionStart = e.target.selectionEnd = e.target.value.length; - - updateInputSize(); - } - } else if (e.keyCode == 40 /* DOWN */) { - if (e.target.selectionStart === e.target.value.length && lastSentPos > 0) { - e.preventDefault(); - - lastSentPos -= 1; - e.target.value = lastSent[lastSentPos]; - e.target.selectionStart = e.target.selectionEnd = 0; - - updateInputSize(); - } - } else if (e.keyCode == 27 /* ESC */) { - e.preventDefault(); - - // Clear input field - e.target.value = ""; - lastSentPos = 0; - lastSent[lastSentPos] = ""; - - updateInputSize(); - } else if (e.keyCode == 9 /* TAB */) { - // Tab complete nicknames starting with @ - - if (e.ctrlKey) { - // Skip autocompletion and tab insertion if user is pressing ctrl - // ctrl-tab is used by browsers to cycle through tabs - return; - } - e.preventDefault(); - - var pos = e.target.selectionStart || 0; - var text = e.target.value; - var index = text.lastIndexOf('@', pos); - - var autocompletedNick = false; - - if (index >= 0) { - var stub = text.substring(index + 1, pos).toLowerCase(); - // Search for nick beginning with stub - var nicks = onlineUsers.filter(function (nick) { - return nick.toLowerCase().indexOf(stub) == 0 - }); - - if (nicks.length > 0) { - autocompletedNick = true; - if (nicks.length == 1) { - insertAtCursor(nicks[0].substr(stub.length) + " "); - } - } - } - - // Since we did not insert a nick, we insert a tab character - if (!autocompletedNick) { - insertAtCursor('\t'); - } - } + if (windowActive && isAtBottom()) { + unread = 0; + } + + var title; + if (myChannel) { + title = "?" + myChannel; + } else { + title = "hack.chat"; + } + + if (unread > 0) { + title = "(" + unread + ") " + title; + } + + document.title = title; } +$("#footer").onclick = function () { + $("#chatinput").focus(); +}; + +$("#chatinput").onkeydown = function (e) { + if (e.keyCode == 13 /* ENTER */ && !e.shiftKey) { + e.preventDefault(); + + // Submit message + if (e.target.value != "") { + var text = e.target.value; + e.target.value = ""; + + send({cmd: "chat", text: text}); + + lastSent[0] = text; + lastSent.unshift(""); + lastSentPos = 0; + + updateInputSize(); + } + } else if (e.keyCode == 38 /* UP */) { + // Restore previous sent messages + if (e.target.selectionStart === 0 && lastSentPos < lastSent.length - 1) { + e.preventDefault(); + + if (lastSentPos == 0) { + lastSent[0] = e.target.value; + } + + lastSentPos += 1; + e.target.value = lastSent[lastSentPos]; + e.target.selectionStart = e.target.selectionEnd = e.target.value.length; + + updateInputSize(); + } + } else if (e.keyCode == 40 /* DOWN */) { + if (e.target.selectionStart === e.target.value.length && lastSentPos > 0) { + e.preventDefault(); + + lastSentPos -= 1; + e.target.value = lastSent[lastSentPos]; + e.target.selectionStart = e.target.selectionEnd = 0; + + updateInputSize(); + } + } else if (e.keyCode == 27 /* ESC */) { + e.preventDefault(); + + // Clear input field + e.target.value = ""; + lastSentPos = 0; + lastSent[lastSentPos] = ""; + + updateInputSize(); + } else if (e.keyCode == 9 /* TAB */) { + // Tab complete nicknames starting with @ + + if (e.ctrlKey) { + // Skip autocompletion and tab insertion if user is pressing ctrl + // ctrl-tab is used by browsers to cycle through tabs + return; + } + e.preventDefault(); + + var pos = e.target.selectionStart || 0; + var text = e.target.value; + var index = text.lastIndexOf("@", pos); + + var autocompletedNick = false; + + if (index >= 0) { + var stub = text.substring(index + 1, pos).toLowerCase(); + // Search for nick beginning with stub + var nicks = onlineUsers.filter(function (nick) { + return nick.toLowerCase().indexOf(stub) == 0; + }); + + if (nicks.length > 0) { + autocompletedNick = true; + if (nicks.length == 1) { + insertAtCursor(nicks[0].substr(stub.length) + " "); + } + } + } + + // Since we did not insert a nick, we insert a tab character + if (!autocompletedNick) { + insertAtCursor("\t"); + } + } +}; + function updateInputSize() { - var atBottom = isAtBottom(); + var atBottom = isAtBottom(); - var input = $('#chatinput'); - input.style.height = 0; - input.style.height = input.scrollHeight + 'px'; - document.body.style.marginBottom = $('#footer').offsetHeight + 'px'; + var input = $("#chatinput"); + input.style.height = 0; + input.style.height = input.scrollHeight + "px"; + document.body.style.marginBottom = $("#footer").offsetHeight + "px"; - if (atBottom) { - window.scrollTo(0, document.body.scrollHeight); - } + if (atBottom) { + window.scrollTo(0, document.body.scrollHeight); + } } -$('#chatinput').oninput = function () { - updateInputSize(); -} +$("#chatinput").oninput = function () { + updateInputSize(); +}; updateInputSize(); /* sidebar */ -$('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function (e) { - $('#sidebar-content').classList.remove('hidden'); - $('#sidebar').classList.add('expand'); - e.stopPropagation(); -} - -$('#sidebar').onmouseleave = document.ontouchstart = function (event) { - var e = event.toElement || event.relatedTarget; - try { - if (e.parentNode == this || e == this) { - return; - } - } catch (e) { return; } +$("#sidebar").onmouseenter = $("#sidebar").ontouchstart = function (e) { + $("#sidebar-content").classList.remove("hidden"); + $("#sidebar").classList.add("expand"); + e.stopPropagation(); +}; - if (!$('#pin-sidebar').checked) { - $('#sidebar-content').classList.add('hidden'); - $('#sidebar').classList.remove('expand'); - } -} +$("#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"); + } +}; -$('#clear-messages').onclick = function () { - // Delete children elements - var messages = $('#messages'); - messages.innerHTML = ''; -} +$("#clear-messages").onclick = function () { + // Delete children elements + var messages = $("#messages"); + messages.innerHTML = ""; +}; // Restore settings from localStorage -if (localStorageGet('pin-sidebar') == 'true') { - $('#pin-sidebar').checked = true; - $('#sidebar-content').classList.remove('hidden'); +if (localStorageGet("pin-sidebar") == "true") { + $("#pin-sidebar").checked = true; + $("#sidebar-content").classList.remove("hidden"); } -if (localStorageGet('joined-left') == 'false') { - $('#joined-left').checked = false; +if (localStorageGet("joined-left") == "false") { + $("#joined-left").checked = false; } -if (localStorageGet('parse-latex') == 'false') { - $('#parse-latex').checked = false; - md.inline.ruler.disable([ 'katex' ]); - md.block.ruler.disable([ 'katex' ]); +if (localStorageGet("parse-latex") == "false") { + $("#parse-latex").checked = false; + md.inline.ruler.disable(["katex"]); + md.block.ruler.disable(["katex"]); } -$('#pin-sidebar').onchange = function (e) { - localStorageSet('pin-sidebar', !!e.target.checked); -} +$("#pin-sidebar").onchange = function (e) { + localStorageSet("pin-sidebar", !!e.target.checked); +}; -$('#joined-left').onchange = function (e) { - localStorageSet('joined-left', !!e.target.checked); -} +$("#joined-left").onchange = function (e) { + localStorageSet("joined-left", !!e.target.checked); +}; -$('#parse-latex').onchange = function (e) { - 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' ]); - } -} +$("#parse-latex").onchange = function (e) { + 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; +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; -} +$("#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; +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; -} +$("#allow-imgur").onchange = function (e) { + var enabled = !!e.target.checked; + localStorageSet("allow-imgur", enabled); + allowImages = enabled; +}; // User list var onlineUsers = []; var ignoredUsers = []; function userAdd(nick) { - var user = document.createElement('a'); - user.textContent = nick; + var user = document.createElement("a"); + user.textContent = nick; - user.onclick = function (e) { - userInvite(nick) - } + user.onclick = function (e) { + userInvite(nick); + }; - var userLi = document.createElement('li'); - userLi.appendChild(user); - $('#users').appendChild(userLi); - onlineUsers.push(nick); + var userLi = document.createElement("li"); + userLi.appendChild(user); + $("#users").appendChild(userLi); + onlineUsers.push(nick); } function userRemove(nick) { - var users = $('#users'); - var children = users.children; + var users = $("#users"); + var children = users.children; - for (var i = 0; i < children.length; i++) { - var user = children[i]; - if (user.textContent == nick) { - users.removeChild(user); - } - } + for (var i = 0; i < children.length; i++) { + var user = children[i]; + if (user.textContent == nick) { + users.removeChild(user); + } + } - var index = onlineUsers.indexOf(nick); - if (index >= 0) { - onlineUsers.splice(index, 1); - } + var index = onlineUsers.indexOf(nick); + if (index >= 0) { + onlineUsers.splice(index, 1); + } } function usersClear() { - var users = $('#users'); + var users = $("#users"); - while (users.firstChild) { - users.removeChild(users.firstChild); - } + while (users.firstChild) { + users.removeChild(users.firstChild); + } - onlineUsers.length = 0; + onlineUsers.length = 0; } function userInvite(nick) { - send({ cmd: 'invite', nick: nick }); + send({cmd: "invite", nick: nick}); } function userIgnore(nick) { - ignoredUsers.push(nick); + ignoredUsers.push(nick); } /* color scheme switcher */ var schemes = [ - 'android', - 'android-white', - 'atelier-dune', - 'atelier-forest', - 'atelier-heath', - 'atelier-lakeside', - 'atelier-seaside', - 'bright', - 'chalk', - 'default', - 'eighties', - 'fresh-green', - 'greenscreen', - 'mariana', - 'mocha', - 'monokai', - 'nese', - 'ocean', - 'pop', - 'railscasts', - 'solarized', - 'tomorrow' + "android", + "android-white", + "atelier-dune", + "atelier-forest", + "atelier-heath", + "atelier-lakeside", + "atelier-seaside", + "bright", + "chalk", + "default", + "eighties", + "fresh-green", + "greenscreen", + "mariana", + "mocha", + "monokai", + "nese", + "ocean", + "pop", + "railscasts", + "solarized", + "tomorrow", ]; var highlights = [ - 'agate', - 'androidstudio', - 'atom-one-dark', - 'darcula', - 'github', - 'rainbow', - 'tomorrow', - 'xcode', - 'zenburn' -] - -var currentScheme = 'atelier-dune'; -var currentHighlight = 'darcula'; + "agate", + "androidstudio", + "atom-one-dark", + "darcula", + "github", + "rainbow", + "tomorrow", + "xcode", + "zenburn", +]; + +var currentScheme = "atelier-dune"; +var currentHighlight = "darcula"; function setScheme(scheme) { - currentScheme = scheme; - $('#scheme-link').href = "schemes/" + scheme + ".css"; - localStorageSet('scheme', scheme); + currentScheme = scheme; + $("#scheme-link").href = "schemes/" + scheme + ".css"; + localStorageSet("scheme", scheme); } function setHighlight(scheme) { - currentHighlight = scheme; - $('#highlight-link').href = "vendor/hljs/styles/" + scheme + ".min.css"; - localStorageSet('highlight', scheme); + currentHighlight = scheme; + $("#highlight-link").href = "vendor/hljs/styles/" + scheme + ".min.css"; + localStorageSet("highlight", scheme); } // Add scheme options to dropdown selector schemes.forEach(function (scheme) { - var option = document.createElement('option'); - option.textContent = scheme; - option.value = scheme; - $('#scheme-selector').appendChild(option); + var option = document.createElement("option"); + option.textContent = scheme; + option.value = scheme; + $("#scheme-selector").appendChild(option); }); highlights.forEach(function (scheme) { - var option = document.createElement('option'); - option.textContent = scheme; - option.value = scheme; - $('#highlight-selector').appendChild(option); + var option = document.createElement("option"); + option.textContent = scheme; + option.value = scheme; + $("#highlight-selector").appendChild(option); }); -$('#scheme-selector').onchange = function (e) { - setScheme(e.target.value); -} +$("#scheme-selector").onchange = function (e) { + setScheme(e.target.value); +}; -$('#highlight-selector').onchange = function (e) { - setHighlight(e.target.value); -} +$("#highlight-selector").onchange = function (e) { + setHighlight(e.target.value); +}; // Load sidebar configaration values from local storage if available -if (localStorageGet('scheme')) { - setScheme(localStorageGet('scheme')); +if (localStorageGet("scheme")) { + setScheme(localStorageGet("scheme")); } -if (localStorageGet('highlight')) { - setHighlight(localStorageGet('highlight')); +if (localStorageGet("highlight")) { + setHighlight(localStorageGet("highlight")); } -$('#scheme-selector').value = currentScheme; -$('#highlight-selector').value = currentHighlight; +$("#scheme-selector").value = currentScheme; +$("#highlight-selector").value = currentHighlight; /* main */ -if (myChannel == '') { - pushMessage({ text: frontpage }); - $('#footer').classList.add('hidden'); - $('#sidebar').classList.add('hidden'); +if (myChannel == "") { + pushMessage({text: frontpage}); + $("#footer").classList.add("hidden"); + $("#sidebar").classList.add("hidden"); } else { - join(myChannel); + join(myChannel); } |