1
0
mirror of https://github.com/hack-chat/main.git synced 2024-03-22 13:20:33 +08:00
hack-chat-main/client/client.js

610 lines
14 KiB
JavaScript
Raw Normal View History

2018-03-10 15:47:00 +08:00
var frontpage = [
" _ _ _ _ ",
" | |_ ___ ___| |_ ___| |_ ___| |_ ",
" | |_ || _| '_| | _| |_ || _|",
" |_|_|__/|___|_,_|.|___|_|_|__/|_| ",
"",
"",
"Welcome to hack.chat, a minimal, distraction-free chat application.",
"Channels are created and joined by going to 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}$$",
2018-05-17 23:42:16 +08:00
"For syntax highlight, the first line of the code block must begin with #<format> where <format> can be html, js or any known format",
2018-03-10 15:47:00 +08:00
"",
"Current Github: https://github.com/hack-chat includes server and client source along with other resources",
2018-03-10 15:47:00 +08:00
"",
"Legacy GitHub: https://github.com/AndrewBelt/hack.chat",
"Android apps: https://goo.gl/UkbKYy https://goo.gl/qasdSu https://goo.gl/fGQFQN",
"Other Softwares: https://github.com/hack-chat/3rd-party-software-list",
2018-03-10 15:47:00 +08:00
"",
"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");
2018-03-10 15:47:00 +08:00
function $(query) {
return document.querySelector(query);
}
2018-03-10 15:47:00 +08:00
function localStorageGet(key) {
try {
return window.localStorage[key]
2018-05-17 23:42:16 +08:00
} catch (e) { }
2018-03-10 15:47:00 +08:00
}
function localStorageSet(key, val) {
try {
window.localStorage[key] = val
2018-05-17 23:42:16 +08:00
} catch (e) { }
2018-03-10 15:47:00 +08:00
}
var ws;
var myNick = localStorageGet('my-nick');
var myChannel = window.location.search.replace(/^\?/, '');
var lastSent = [""];
var lastSentPos = 0;
2018-03-10 15:47:00 +08:00
function join(channel) {
if (document.domain == 'hack.chat') {
// For https://hack.chat/
ws = new WebSocket('wss://hack.chat/chat-ws');
} else {
// for local installs
ws = new WebSocket('ws://' + document.domain + ':6060');
}
2018-03-10 15:47:00 +08:00
var wasConnected = false;
2018-03-10 15:47:00 +08:00
ws.onopen = function () {
2018-03-10 15:47:00 +08:00
if (!wasConnected) {
if (location.hash) {
myNick = location.hash.substr(1);
} else {
myNick = prompt('Nickname:', myNick);
2018-03-10 15:47:00 +08:00
}
}
2018-03-10 15:47:00 +08:00
if (myNick) {
localStorageSet('my-nick', myNick);
send({ cmd: 'join', channel: channel, nick: myNick });
2018-03-10 15:47:00 +08:00
}
wasConnected = true;
2018-03-10 15:47:00 +08:00
}
ws.onclose = function () {
2018-03-10 15:47:00 +08:00
if (wasConnected) {
pushMessage({ nick: '!', text: "Server disconnected. Attempting to reconnect. . ." });
2018-03-10 15:47:00 +08:00
}
window.setTimeout(function () {
join(channel);
}, 2000);
2018-03-10 15:47:00 +08:00
}
ws.onmessage = function (message) {
var args = JSON.parse(message.data);
var cmd = args.cmd;
var command = COMMANDS[cmd];
command.call(null, args);
2018-03-10 15:47:00 +08:00
}
}
var COMMANDS = {
chat: function (args) {
2018-03-10 15:47:00 +08:00
if (ignoredUsers.indexOf(args.nick) >= 0) {
return;
2018-03-10 15:47:00 +08:00
}
pushMessage(args);
2018-03-10 15:47:00 +08:00
},
info: function (args) {
args.nick = '*';
pushMessage(args);
2018-03-10 15:47:00 +08:00
},
warn: function (args) {
args.nick = '!';
pushMessage(args);
2018-03-10 15:47:00 +08:00
},
onlineSet: function (args) {
var nicks = args.nicks;
usersClear();
nicks.forEach(function (nick) {
userAdd(nick);
});
pushMessage({ nick: '*', text: "Users online: " + nicks.join(", ") })
2018-03-10 15:47:00 +08:00
},
onlineAdd: function (args) {
var nick = args.nick;
userAdd(nick);
2018-03-10 15:47:00 +08:00
if ($('#joined-left').checked) {
pushMessage({ nick: '*', text: nick + " joined" });
2018-03-10 15:47:00 +08:00
}
},
onlineRemove: function (args) {
var nick = args.nick;
userRemove(nick);
2018-03-10 15:47:00 +08:00
if ($('#joined-left').checked) {
pushMessage({ nick: '*', text: nick + " left" });
2018-03-10 15:47:00 +08:00
}
}
2018-03-10 15:47:00 +08:00
}
function pushMessage(args) {
// Message container
var messageEl = document.createElement('div');
messageEl.classList.add('message');
2018-03-10 15:47:00 +08:00
if (args.nick == myNick) {
messageEl.classList.add('me');
2018-05-17 23:42:16 +08:00
} else if (args.nick == '!') {
messageEl.classList.add('warn');
2018-05-17 23:42:16 +08:00
} else if (args.nick == '*') {
messageEl.classList.add('info');
2018-05-17 23:42:16 +08:00
} else if (args.admin) {
messageEl.classList.add('admin');
2018-05-17 23:42:16 +08:00
} else if (args.mod) {
messageEl.classList.add('mod');
2018-03-10 15:47:00 +08:00
}
// Nickname
var nickSpanEl = document.createElement('span');
nickSpanEl.classList.add('nick');
messageEl.appendChild(nickSpanEl);
2018-03-10 15:47:00 +08:00
if (args.trip) {
var tripEl = document.createElement('span');
tripEl.textContent = args.trip + " ";
tripEl.classList.add('trip');
nickSpanEl.appendChild(tripEl);
2018-03-10 15:47:00 +08:00
}
if (args.nick) {
var nickLinkEl = document.createElement('a');
nickLinkEl.textContent = args.nick;
nickLinkEl.onclick = function () {
insertAtCursor("@" + args.nick + " ");
$('#chatinput').focus();
2018-03-10 15:47:00 +08:00
}
var date = new Date(args.time || Date.now());
nickLinkEl.title = date.toLocaleString();
nickSpanEl.appendChild(nickLinkEl);
2018-03-10 15:47:00 +08:00
}
// Text
var textEl = document.createElement('pre');
textEl.classList.add('text');
2018-03-10 15:47:00 +08:00
textEl.textContent = args.text || '';
textEl.innerHTML = textEl.innerHTML.replace(/(\?|https?:\/\/)\S+?(?=[,.!?:)]?\s|$)/g, parseLinks);
2018-03-10 15:47:00 +08:00
2018-05-18 00:34:14 +08:00
if ($('#syntax-highlight').checked && textEl.textContent.indexOf('#') == 0) {
var lang = textEl.textContent.split(/\s+/g)[0].replace('#', '');
var codeEl = document.createElement('code');
codeEl.classList.add(lang);
var content = textEl.textContent.replace('#' + lang, '');
codeEl.textContent = content.trim();
hljs.highlightBlock(codeEl);
textEl.innerHTML = '';
textEl.appendChild(codeEl);
2018-05-17 23:42:16 +08:00
} else if ($('#parse-latex').checked) {
2018-03-10 15:47:00 +08:00
// Temporary hotfix for \rule spamming, see https://github.com/Khan/KaTeX/issues/109
textEl.innerHTML = textEl.innerHTML.replace(/\\rule|\\\\\s*\[.*?\]/g, '');
2018-03-10 15:47:00 +08:00
try {
2018-05-17 23:42:16 +08:00
renderMathInElement(textEl, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
]
})
} catch (e) {
console.warn(e);
2018-03-10 15:47:00 +08:00
}
}
messageEl.appendChild(textEl);
2018-03-10 15:47:00 +08:00
// Scroll to bottom
var atBottom = isAtBottom();
$('#messages').appendChild(messageEl);
2018-03-10 15:47:00 +08:00
if (atBottom) {
window.scrollTo(0, document.body.scrollHeight);
2018-03-10 15:47:00 +08:00
}
unread += 1;
updateTitle();
2018-03-10 15:47:00 +08:00
}
function insertAtCursor(text) {
var input = $('#chatinput');
var start = input.selectionStart || 0;
var before = input.value.substr(0, start);
var after = input.value.substr(start);
2018-03-10 15:47:00 +08:00
before += text;
input.value = before + after;
input.selectionStart = input.selectionEnd = before.length;
updateInputSize();
}
2018-03-10 15:47:00 +08:00
function send(data) {
if (ws && ws.readyState == ws.OPEN) {
ws.send(JSON.stringify(data));
2018-03-10 15:47:00 +08:00
}
}
function parseLinks(g0) {
var a = document.createElement('a');
a.innerHTML = g0;
var url = a.textContent;
a.href = url;
a.target = '_blank';
return a.outerHTML;
2018-03-10 15:47:00 +08:00
}
var windowActive = true;
var unread = 0;
2018-03-10 15:47:00 +08:00
window.onfocus = function () {
windowActive = true;
2018-03-10 15:47:00 +08:00
updateTitle();
2018-03-10 15:47:00 +08:00
}
window.onblur = function () {
windowActive = false;
2018-03-10 15:47:00 +08:00
}
window.onscroll = function () {
2018-03-10 15:47:00 +08:00
if (isAtBottom()) {
updateTitle();
2018-03-10 15:47:00 +08:00
}
}
function isAtBottom() {
return (window.innerHeight + window.scrollY) >= (document.body.scrollHeight - 1);
2018-03-10 15:47:00 +08:00
}
function updateTitle() {
if (windowActive && isAtBottom()) {
unread = 0;
2018-03-10 15:47:00 +08:00
}
var title;
2018-03-10 15:47:00 +08:00
if (myChannel) {
title = "?" + myChannel;
2018-05-17 23:42:16 +08:00
} else {
title = "hack.chat";
2018-03-10 15:47:00 +08:00
}
2018-03-10 15:47:00 +08:00
if (unread > 0) {
title = '(' + unread + ') ' + title;
2018-03-10 15:47:00 +08:00
}
document.title = title;
}
2018-03-10 15:47:00 +08:00
$('#footer').onclick = function () {
$('#chatinput').focus();
2018-03-10 15:47:00 +08:00
}
$('#chatinput').onkeydown = function (e) {
2018-03-10 15:47:00 +08:00
if (e.keyCode == 13 /* ENTER */ && !e.shiftKey) {
e.preventDefault();
2018-03-10 15:47:00 +08:00
// 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();
2018-03-10 15:47:00 +08:00
}
2018-05-17 23:42:16 +08:00
} else if (e.keyCode == 38 /* UP */) {
2018-03-10 15:47:00 +08:00
// Restore previous sent messages
if (e.target.selectionStart === 0 && lastSentPos < lastSent.length - 1) {
e.preventDefault();
2018-03-10 15:47:00 +08:00
if (lastSentPos == 0) {
lastSent[0] = e.target.value;
2018-03-10 15:47:00 +08:00
}
lastSentPos += 1;
e.target.value = lastSent[lastSentPos];
e.target.selectionStart = e.target.selectionEnd = e.target.value.length;
updateInputSize();
2018-03-10 15:47:00 +08:00
}
} else if (e.keyCode == 40 /* DOWN */) {
2018-03-10 15:47:00 +08:00
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();
2018-03-10 15:47:00 +08:00
}
} else if (e.keyCode == 27 /* ESC */) {
e.preventDefault();
2018-03-10 15:47:00 +08:00
// Clear input field
e.target.value = "";
lastSentPos = 0;
lastSent[lastSentPos] = "";
updateInputSize();
2018-05-17 23:42:16 +08:00
} else if (e.keyCode == 9 /* TAB */) {
2018-03-10 15:47:00 +08:00
// Tab complete nicknames starting with @
e.preventDefault();
var pos = e.target.selectionStart || 0;
var text = e.target.value;
var index = text.lastIndexOf('@', pos);
2018-03-10 15:47:00 +08:00
if (index >= 0) {
var stub = text.substring(index + 1, pos).toLowerCase();
2018-03-10 15:47:00 +08:00
// Search for nick beginning with stub
var nicks = onlineUsers.filter(function (nick) {
2018-03-10 15:47:00 +08:00
return nick.toLowerCase().indexOf(stub) == 0
});
2018-03-10 15:47:00 +08:00
if (nicks.length == 1) {
insertAtCursor(nicks[0].substr(stub.length) + " ");
2018-03-10 15:47:00 +08:00
}
}
}
}
function updateInputSize() {
var atBottom = isAtBottom();
2018-03-10 15:47:00 +08:00
var input = $('#chatinput');
input.style.height = 0;
input.style.height = input.scrollHeight + 'px';
document.body.style.marginBottom = $('#footer').offsetHeight + 'px';
2018-03-10 15:47:00 +08:00
if (atBottom) {
window.scrollTo(0, document.body.scrollHeight);
2018-03-10 15:47:00 +08:00
}
}
$('#chatinput').oninput = function () {
updateInputSize();
2018-03-10 15:47:00 +08:00
}
updateInputSize();
2018-03-10 15:47:00 +08:00
/* sidebar */
$('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function (e) {
$('#sidebar-content').classList.remove('hidden');
$('#sidebar').classList.add('expand');
e.stopPropagation();
2018-03-10 15:47:00 +08:00
}
$('#sidebar').onmouseleave = document.ontouchstart = function () {
2018-03-10 15:47:00 +08:00
if (!$('#pin-sidebar').checked) {
$('#sidebar-content').classList.add('hidden');
$('#sidebar').classList.remove('expand');
2018-03-10 15:47:00 +08:00
}
}
$('#clear-messages').onclick = function () {
2018-03-10 15:47:00 +08:00
// Delete children elements
var messages = $('#messages');
2018-03-10 15:47:00 +08:00
while (messages.firstChild) {
messages.removeChild(messages.firstChild);
2018-03-10 15:47:00 +08:00
}
}
// Restore settings from localStorage
if (localStorageGet('pin-sidebar') == 'true') {
$('#pin-sidebar').checked = true;
$('#sidebar-content').classList.remove('hidden');
2018-03-10 15:47:00 +08:00
}
2018-03-10 15:47:00 +08:00
if (localStorageGet('joined-left') == 'false') {
$('#joined-left').checked = false;
2018-03-10 15:47:00 +08:00
}
2018-03-10 15:47:00 +08:00
if (localStorageGet('parse-latex') == 'false') {
$('#parse-latex').checked = false;
2018-03-10 15:47:00 +08:00
}
$('#pin-sidebar').onchange = function (e) {
localStorageSet('pin-sidebar', !!e.target.checked);
2018-03-10 15:47:00 +08:00
}
$('#joined-left').onchange = function (e) {
localStorageSet('joined-left', !!e.target.checked);
2018-03-10 15:47:00 +08:00
}
$('#parse-latex').onchange = function (e) {
localStorageSet('parse-latex', !!e.target.checked);
2018-03-10 15:47:00 +08:00
}
// User list
var onlineUsers = [];
var ignoredUsers = [];
2018-03-10 15:47:00 +08:00
function userAdd(nick) {
var user = document.createElement('a');
user.textContent = nick;
user.onclick = function (e) {
2018-03-10 15:47:00 +08:00
userInvite(nick)
}
var userLi = document.createElement('li');
userLi.appendChild(user);
$('#users').appendChild(userLi);
onlineUsers.push(nick);
2018-03-10 15:47:00 +08:00
}
function userRemove(nick) {
var users = $('#users');
var children = users.children;
2018-03-10 15:47:00 +08:00
for (var i = 0; i < children.length; i++) {
var user = children[i];
2018-03-10 15:47:00 +08:00
if (user.textContent == nick) {
users.removeChild(user);
2018-03-10 15:47:00 +08:00
}
}
var index = onlineUsers.indexOf(nick);
2018-03-10 15:47:00 +08:00
if (index >= 0) {
onlineUsers.splice(index, 1);
2018-03-10 15:47:00 +08:00
}
}
function usersClear() {
var users = $('#users');
2018-03-10 15:47:00 +08:00
while (users.firstChild) {
users.removeChild(users.firstChild);
2018-03-10 15:47:00 +08:00
}
onlineUsers.length = 0;
2018-03-10 15:47:00 +08:00
}
function userInvite(nick) {
send({ cmd: 'invite', nick: nick });
2018-03-10 15:47:00 +08:00
}
function userIgnore(nick) {
ignoredUsers.push(nick);
2018-03-10 15:47:00 +08:00
}
/* color scheme switcher */
var schemes = [
'android',
'atelier-dune',
'atelier-forest',
'atelier-heath',
'atelier-lakeside',
'atelier-seaside',
'bright',
'chalk',
'default',
'eighties',
'greenscreen',
'mocha',
'monokai',
'nese',
'ocean',
'pop',
'railscasts',
'solarized',
'tomorrow'
];
2018-03-10 15:47:00 +08:00
2018-05-17 23:42:16 +08:00
var highlights = [
'agate',
'androidstudio',
'darcula',
'github',
'rainbow',
'tomorrow',
'xcode',
'zenburn'
]
var currentScheme = 'atelier-dune';
2018-05-17 23:42:16 +08:00
var currentHighlight = 'darcula';
2018-03-10 15:47:00 +08:00
function setScheme(scheme) {
currentScheme = scheme;
$('#scheme-link').href = "schemes/" + scheme + ".css";
localStorageSet('scheme', scheme);
2018-03-10 15:47:00 +08:00
}
2018-05-17 23:42:16 +08:00
function setHighlight(scheme) {
currentHighlight = scheme;
$('#highlight-link').href = "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/" + scheme + ".min.css";
localStorageSet('highlight', scheme);
}
2018-03-10 15:47:00 +08:00
// 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);
});
2018-03-10 15:47:00 +08:00
2018-05-17 23:42:16 +08:00
highlights.forEach(function (scheme) {
var option = document.createElement('option');
option.textContent = scheme;
option.value = scheme;
$('#highlight-selector').appendChild(option);
});
$('#scheme-selector').onchange = function (e) {
setScheme(e.target.value);
2018-03-10 15:47:00 +08:00
}
2018-05-17 23:42:16 +08:00
$('#highlight-selector').onchange = function (e) {
setHighlight(e.target.value);
}
2018-03-10 15:47:00 +08:00
// Load sidebar configaration values from local storage if available
if (localStorageGet('scheme')) {
setScheme(localStorageGet('scheme'));
2018-03-10 15:47:00 +08:00
}
2018-05-17 23:42:16 +08:00
if (localStorageGet('highlight')) {
setHighlight(localStorageGet('highlight'));
}
$('#scheme-selector').value = currentScheme;
2018-05-17 23:42:16 +08:00
$('#highlight-selector').value = currentHighlight;
2018-03-10 15:47:00 +08:00
/* main */
if (myChannel == '') {
pushMessage({ text: frontpage });
$('#footer').classList.add('hidden');
$('#sidebar').classList.add('hidden');
} else {
join(myChannel);
}