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

initial commit

This commit is contained in:
marzavec 2018-03-09 23:47:00 -08:00
commit fde6895720
244 changed files with 13967 additions and 0 deletions

82
README.md Normal file
View File

@ -0,0 +1,82 @@
# Hack.Chat v2
This is a backwards compatible continuation of the work by Andrew Belt [https://github.com/AndrewBelt/hack.chat](https://github.com/AndrewBelt/hack.chat). The server code has been updated to ES6 along with several new features- including new commands and hot-reload of the commands/protocol.
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
### Prerequisites
What things you need to install the software and how to install them
```
node >= 8.10.0
npm >= 5.7.1
```
### Installing
Clone this git and cd into the directory. These steps will get a development env running:
```
npm install
```
Or on a Windows machine with Yarn installed:
```
yarn install
```
Then:
```
node main.js
```
The configuration script will execute the initial server setup by requesting input. Follow the steps until it finishes:
```
Note: npm/yarn run config will re-run this utility.
You will now be asked for the following:
- Admin Name, the initial admin username
- Admin Pass, the initial admin password
- Port, the port for the websocket
- Salt, the salt for username trip
prompt: adminName: admin
prompt: adminPass: ****
prompt: websocketPort: (6060)
prompt: tripSalt: ************
Config generated! You may now start the server normally.
```
You may now begin development or deploy to live system with a node process manager.
## Deployment
After configuration, push everything except the node_modules folder to the live server and re-run:
```
npm install
```
You can now run start the server software with a process manager like PM2.
## Authors
* **Marzavec** - *Initial work* - [https://github.com/marzavec](https://github.com/marzavec)
See also the list of [contributors](https://github.com/hack-chat/main.git/contributors) who participated in this project.
## License
This project is licensed under the WTFPL License - see the [http://www.wtfpl.net/txt/copying/](http://www.wtfpl.net/txt/copying/) file for details
## Acknowledgments
* TODO

View File

@ -0,0 +1,24 @@
//
// Base16 Bright
// Created by Fredrik Broman (http://frebro.com)
// Based on the Android visual guidelines
// http://developer.android.com/design/style/color.html
//
@base00: #212121;
@base01: #333333;
@base02: #626261;
@base03: #858585;
@base04: #dddddd;
@base05: #e0e0e0;
@base06: #f2f2f2;
@base07: #ffffff;
@base08: #e94749;
@base09: #f18618;
@base0A: #fbba37;
@base0B: #99C21D;
@base0C: #33B5E5;
@base0D: #0099CC;
@base0E: #9568AA;
@base0F: #754595;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Dune
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #20201d;
@base01: #292824;
@base02: #6e6b5e;
@base03: #7d7a68;
@base04: #999580;
@base05: #a6a28c;
@base06: #e8e4cf;
@base07: #fefbec;
@base08: #d73737;
@base09: #b65611;
@base0A: #cfb017;
@base0B: #60ac39;
@base0C: #1fad83;
@base0D: #6684e1;
@base0E: #b854d4;
@base0F: #d43552;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Forest
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #1b1918;
@base01: #2c2421;
@base02: #68615e;
@base03: #766e6b;
@base04: #9c9491;
@base05: #a8a19f;
@base06: #e6e2e0;
@base07: #f1efee;
@base08: #f22c40;
@base09: #df5320;
@base0A: #d5911a;
@base0B: #5ab738;
@base0C: #00ad9c;
@base0D: #407ee7;
@base0E: #6666ea;
@base0F: #c33ff3;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Heath
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #1b181b;
@base01: #292329;
@base02: #695d69;
@base03: #776977;
@base04: #9e8f9e;
@base05: #ab9bab;
@base06: #d8cad8;
@base07: #f7f3f7;
@base08: #ca402b;
@base09: #a65926;
@base0A: #bb8a35;
@base0B: #379a37;
@base0C: #159393;
@base0D: #516aec;
@base0E: #7b59c0;
@base0F: #cc33cc;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Lakeside
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #161b1d;
@base01: #1f292e;
@base02: #516d7b;
@base03: #5a7b8c;
@base04: #7195a8;
@base05: #7ea2b4;
@base06: #c1e4f6;
@base07: #ebf8ff;
@base08: #d22d72;
@base09: #935c25;
@base0A: #8a8a0f;
@base0B: #568c3b;
@base0C: #2d8f6f;
@base0D: #257fad;
@base0E: #5d5db1;
@base0F: #b72dd2;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Seaside
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #131513;
@base01: #242924;
@base02: #5e6e5e;
@base03: #687d68;
@base04: #809980;
@base05: #8ca68c;
@base06: #cfe8cf;
@base07: #f0fff0;
@base08: #e6193c;
@base09: #87711d;
@base0A: #c3c322;
@base0B: #29a329;
@base0C: #1999b3;
@base0D: #3d62f5;
@base0E: #ad2bee;
@base0F: #e619c3;

21
client/base16/bright.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Bright
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #000000;
@base01: #303030;
@base02: #505050;
@base03: #b0b0b0;
@base04: #d0d0d0;
@base05: #e0e0e0;
@base06: #f5f5f5;
@base07: #ffffff;
@base08: #fb0120;
@base09: #fc6d24;
@base0A: #fda331;
@base0B: #a1c659;
@base0C: #76c7b7;
@base0D: #6fb3d2;
@base0E: #d381c3;
@base0F: #be643c;

21
client/base16/chalk.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Chalk
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #151515;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #f5f5f5;
@base08: #fb9fb1;
@base09: #eda987;
@base0A: #ddb26f;
@base0B: #acc267;
@base0C: #12cfc0;
@base0D: #6fc2ef;
@base0E: #e1a3ee;
@base0F: #deaf8f;

View File

@ -0,0 +1,21 @@
//
// Base16 Default
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #151515;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #f5f5f5;
@base08: #ac4142;
@base09: #d28445;
@base0A: #f4bf75;
@base0B: #90a959;
@base0C: #75b5aa;
@base0D: #6a9fb5;
@base0E: #aa759f;
@base0F: #8f5536;

View File

@ -0,0 +1,21 @@
//
// Base16 Eighties
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #2d2d2d;
@base01: #393939;
@base02: #515151;
@base03: #747369;
@base04: #a09f93;
@base05: #d3d0c8;
@base06: #e8e6df;
@base07: #f2f0ec;
@base08: #f2777a;
@base09: #f99157;
@base0A: #ffcc66;
@base0B: #99cc99;
@base0C: #66cccc;
@base0D: #6699cc;
@base0E: #cc99cc;
@base0F: #d27b53;

View File

@ -0,0 +1,21 @@
//
// Base16 Greenscreen
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #001100;
@base01: #003300;
@base02: #005500;
@base03: #007700;
@base04: #009900;
@base05: #00bb00;
@base06: #00dd00;
@base07: #00ff00;
@base08: #007700;
@base09: #009900;
@base0A: #007700;
@base0B: #00bb00;
@base0C: #005500;
@base0D: #009900;
@base0E: #00bb00;
@base0F: #005500;

21
client/base16/mocha.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Mocha
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #3B3228;
@base01: #534636;
@base02: #645240;
@base03: #7e705a;
@base04: #b8afad;
@base05: #d0c8c6;
@base06: #e9e1dd;
@base07: #f5eeeb;
@base08: #cb6077;
@base09: #d28b71;
@base0A: #f4bc87;
@base0B: #beb55b;
@base0C: #7bbda4;
@base0D: #8ab3b5;
@base0E: #a89bb9;
@base0F: #bb9584;

View File

@ -0,0 +1,21 @@
//
// Base16 Monokai
// Created by Wimer Hazenberg (http://www.monokai.nl)
//
@base00: #272822;
@base01: #383830;
@base02: #49483e;
@base03: #75715e;
@base04: #a59f85;
@base05: #f8f8f2;
@base06: #f5f4f1;
@base07: #f9f8f5;
@base08: #f92672;
@base09: #fd971f;
@base0A: #f4bf75;
@base0B: #a6e22e;
@base0C: #a1efe4;
@base0D: #66d9ef;
@base0E: #ae81ff;
@base0F: #cc6633;

26
client/base16/nese.less Normal file
View File

@ -0,0 +1,26 @@
//
// Base16 NE.se color system
// Created by Fredrik Broman (http://frebro.com)
//
// Reference on Adobe Kuler:
// https://kuler.adobe.com/NESE-Warm-color-theme-2900819/
// https://kuler.adobe.com/NESE-Cold-color-theme-2900847/
//
@base00: lighten(black, 1%); // #030303
@base01: lighten(black, 5%); // #0c0c0c
@base02: lighten(black, 20%); // #262626
@base03: lighten(black, 45%); // #5f5f5f
@base04: lighten(black, 65%); // #959595
@base05: lighten(black, 80%); // #c1c1c1
@base06: lighten(black, 95%); // #efefef
@base07: lighten(black, 99%); // #fcfcfc
@base08: #f73e30; // Red
@base09: mix(@base08, @base0A, 50%); // Orange = #f97c37
@base0A: #faba3d; // Yellow
@base0B: #1ab857; // Green
@base0C: mix(@base0B, @base0D, 50%); // Cyan = #29a188
@base0D: #388bb8; // Blue
@base0E: mix(@base0D, @base0F, 50%); // Purple = #9c73a7
@base0F: #ff5a96; // Pink

21
client/base16/ocean.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Ocean
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #2b303b;
@base01: #343d46;
@base02: #4f5b66;
@base03: #65737e;
@base04: #a7adba;
@base05: #c0c5ce;
@base06: #dfe1e8;
@base07: #eff1f5;
@base08: #bf616a;
@base09: #d08770;
@base0A: #ebcb8b;
@base0B: #a3be8c;
@base0C: #96b5b4;
@base0D: #8fa1b3;
@base0E: #b48ead;
@base0F: #ab7967;

21
client/base16/pop.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Pop
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #000000;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #ffffff;
@base08: #eb008a;
@base09: #f29333;
@base0A: #f8ca12;
@base0B: #37b349;
@base0C: #00aabb;
@base0D: #0e5a94;
@base0E: #b31e8d;
@base0F: #7a2d00;

View File

@ -0,0 +1,21 @@
//
// Base16 Railscasts
// Created by Ryan Bates (http://railscasts.com)
//
@base00: #2b2b2b;
@base01: #272935;
@base02: #3a4055;
@base03: #5a647e;
@base04: #d4cfc9;
@base05: #e6e1dc;
@base06: #f4f1ed;
@base07: #f9f7f3;
@base08: #da4939;
@base09: #cc7833;
@base0A: #ffc66d;
@base0B: #a5c261;
@base0C: #519f50;
@base0D: #6d9cbe;
@base0E: #b6b3eb;
@base0F: #bc9458;

View File

@ -0,0 +1,21 @@
//
// Base16 Solarized
// Created by Ethan Schoonover (http://ethanschoonover.com/solarized)
//
@base00: #002b36;
@base01: #073642;
@base02: #586e75;
@base03: #657b83;
@base04: #839496;
@base05: #93a1a1;
@base06: #eee8d5;
@base07: #fdf6e3;
@base08: #dc322f;
@base09: #cb4b16;
@base0A: #b58900;
@base0B: #859900;
@base0C: #2aa198;
@base0D: #268bd2;
@base0E: #6c71c4;
@base0F: #d33682;

View File

@ -0,0 +1,21 @@
//
// Base16 Tomorrow
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #1d1f21;
@base01: #282a2e;
@base02: #373b41;
@base03: #969896;
@base04: #b4b7b4;
@base05: #c5c8c6;
@base06: #e0e0e0;
@base07: #ffffff;
@base08: #cc6666;
@base09: #de935f;
@base0A: #f0c674;
@base0B: #b5bd68;
@base0C: #8abeb7;
@base0D: #81a2be;
@base0E: #b294bb;
@base0F: #a3685a;

534
client/client.js Normal file
View File

@ -0,0 +1,534 @@
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}$$",
"",
"GitHub: https://github.com/AndrewBelt/hack.chat",
"Android apps: https://goo.gl/UkbKYy https://goo.gl/qasdSu https://goo.gl/fGQFQN",
"",
"Server and web client released under the MIT open source license.",
"No message history is retained on the hack.chat server.",
"",
"[03/03/2018] Please note that the server is currently undergoing changes, expect random downtime or disconnections!",
"[03/03/2018] Hack.chat is now under new management by the core community; @raf924 @bacon @wwandrew @Rut @_0x17 @M4GNV5 @MinusGix @nanotech",
].join("\n")
function $(query) {return document.querySelector(query)}
function localStorageGet(key) {
try {
return window.localStorage[key]
}
catch(e) {}
}
function localStorageSet(key, val) {
try {
window.localStorage[key] = val
}
catch(e) {}
}
var ws
var myNick = localStorageGet('my-nick')
var myChannel = window.location.search.replace(/^\?/, '')
var lastSent = [""]
var lastSentPos = 0
// Ping server every 50 seconds to retain WebSocket connection
window.setInterval(function() {
send({cmd: 'ping'})
}, 50000)
function join(channel) {
ws = new WebSocket('ws://127.0.0.1:6060')
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)
},
info: function(args) {
args.nick = '*'
pushMessage(args)
},
warn: function(args) {
args.nick = '!'
pushMessage(args)
},
onlineSet: function(args) {
var nicks = args.nicks
usersClear()
nicks.forEach(function(nick) {
userAdd(nick)
})
pushMessage({nick: '*', text: "Users online: " + nicks.join(", ")})
},
onlineAdd: function(args) {
var nick = args.nick
userAdd(nick)
if ($('#joined-left').checked) {
pushMessage({nick: '*', text: nick + " joined"})
}
},
onlineRemove: function(args) {
var nick = args.nick
userRemove(nick)
if ($('#joined-left').checked) {
pushMessage({nick: '*', text: nick + " left"})
}
},
}
function pushMessage(args) {
// Message container
var messageEl = document.createElement('div')
messageEl.classList.add('message')
if (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('pre')
textEl.classList.add('text')
textEl.textContent = args.text || ''
textEl.innerHTML = textEl.innerHTML.replace(/(\?|https?:\/\/)\S+?(?=[,.!?:)]?\s|$)/g, parseLinks)
if ($('#parse-latex').checked) {
// Temporary hotfix for \rule spamming, see https://github.com/Khan/KaTeX/issues/109
textEl.innerHTML = textEl.innerHTML.replace(/\\rule|\\\\\s*\[.*?\]/g, '')
try {
renderMathInElement(textEl, {delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
]})
}
catch (e) {
console.warn(e)
}
}
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)
before += text
input.value = before + after
input.selectionStart = input.selectionEnd = before.length
updateInputSize()
}
function send(data) {
if (ws && ws.readyState == ws.OPEN) {
ws.send(JSON.stringify(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
window.onfocus = function() {
windowActive = true
updateTitle()
}
window.onblur = function() {
windowActive = false
}
window.onscroll = function() {
if (isAtBottom()) {
updateTitle()
}
}
function isAtBottom() {
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 */
$('#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 @
e.preventDefault()
var pos = e.target.selectionStart || 0
var text = e.target.value
var index = text.lastIndexOf('@', pos)
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 == 1) {
insertAtCursor(nicks[0].substr(stub.length) + " ")
}
}
}
}
function updateInputSize() {
var atBottom = isAtBottom()
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)
}
}
$('#chatinput').oninput = function() {
updateInputSize()
}
updateInputSize()
/* sidebar */
$('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function(e) {
$('#sidebar-content').classList.remove('hidden')
e.stopPropagation()
}
$('#sidebar').onmouseleave = document.ontouchstart = function() {
if (!$('#pin-sidebar').checked) {
$('#sidebar-content').classList.add('hidden')
}
}
$('#clear-messages').onclick = function() {
// Delete children elements
var messages = $('#messages')
while (messages.firstChild) {
messages.removeChild(messages.firstChild)
}
}
// Restore settings from localStorage
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('parse-latex') == 'false') {
$('#parse-latex').checked = false
}
$('#pin-sidebar').onchange = function(e) {
localStorageSet('pin-sidebar', !!e.target.checked)
}
$('#joined-left').onchange = function(e) {
localStorageSet('joined-left', !!e.target.checked)
}
$('#parse-latex').onchange = function(e) {
localStorageSet('parse-latex', !!e.target.checked)
}
// User list
var onlineUsers = []
var ignoredUsers = []
function userAdd(nick) {
var user = document.createElement('a')
user.textContent = nick
user.onclick = function(e) {
userInvite(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
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)
}
}
function usersClear() {
var users = $('#users')
while (users.firstChild) {
users.removeChild(users.firstChild)
}
onlineUsers.length = 0
}
function userInvite(nick) {
send({cmd: 'invite', nick: nick})
}
function userIgnore(nick) {
ignoredUsers.push(nick)
}
/* 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',
]
var currentScheme = 'atelier-dune'
function setScheme(scheme) {
currentScheme = scheme
$('#scheme-link').href = "/schemes/" + scheme + ".css"
localStorageSet('scheme', 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)
})
$('#scheme-selector').onchange = function(e) {
setScheme(e.target.value)
}
// Load sidebar configaration values from local storage if available
if (localStorageGet('scheme')) {
setScheme(localStorageGet('scheme'))
}
$('#scheme-selector').value = currentScheme
/* main */
if (myChannel == '') {
pushMessage({text: frontpage})
$('#footer').classList.add('hidden')
$('#sidebar').classList.add('hidden')
}
else {
join(myChannel)
}

68
client/index.html Normal file
View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<title>hack.chat</title>
<meta name="application-name" content="Hack.Chat">
<meta name="author" content="Marzavec, admin@marzavec.com">
<meta name="description" content="A minimal, accountless, anonymous, distraction-free chat application">
<meta name="keywords" content="minimal, accountless, anonymous, distraction-free, chat">
<meta name="robots" content="index, follow">
<meta name="viewport" content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-touch-icon-152x152.png">
<meta name="apple-mobile-web-app-title" content="Rubix">
<link rel="icon" type="image/png" href="/favicons/favicon-196x196.png" sizes="196x196">
<link rel="icon" type="image/png" href="/favicons/favicon-160x160.png" sizes="160x160">
<link rel="icon" type="image/png" href="/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="/favicons/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/favicons/favicon-32x32.png" sizes="32x32">
<meta name="msapplication-TileColor" content="#E76049">
<meta name="msapplication-TileImage" content="/favicons/mstile-144x144.png">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="katex/katex.min.css">
<link id="scheme-link" rel="stylesheet" href="schemes/atelier-dune.css">
<script src="katex/katex.min.js"></script>
<script src="katex/contrib/auto-render.min.js"></script>
</head>
<body>
<article class="container">
<div id="messages" class="messages"></div>
</article>
<footer id="footer">
<div class="container">
<form id="chatform" class="messages"><textarea id="chatinput" type="text" autocomplete="off" autofocus></textarea></form>
</div>
</footer>
<nav id="sidebar">
<div id="sidebar-content" class="hidden">
<p><input id="pin-sidebar" type="checkbox"><label for="pin-sidebar">Pin sidebar</label></p>
<h4>Settings</h4>
<p><input id="joined-left" type="checkbox" checked><label for="joined-left">Join/left notify</label></p>
<p><input id="parse-latex" type="checkbox" checked><label for="parse-latex">Parse LaTeX</label></p>
<p><button id="clear-messages">Clear messages</button></p>
<h4>Color scheme</h4><select id="scheme-selector"></select>
<h4>Users online</h4>
<p>(Click user to invite)</p>
<ul id="users"></ul>
</div>
</nav>
<script src="client.js"></script>
</body>
</html>

0
client/katex/.gitkeep Normal file
View File

219
client/katex/auto-render.js Normal file
View File

@ -0,0 +1,219 @@
(function(e){if("function"==typeof bootstrap)bootstrap("rendermathinelement",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeRenderMathInElement=e}else"undefined"!=typeof window?window.renderMathInElement=e():global.renderMathInElement=e()})(function(){var define,ses,bootstrap,module,exports;
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global katex */
var splitAtDelimiters = require("./splitAtDelimiters");
var splitWithDelimiters = function(text, delimiters) {
var data = [{type: "text", data: text}];
for (var i = 0; i < delimiters.length; i++) {
var delimiter = delimiters[i];
data = splitAtDelimiters(
data, delimiter.left, delimiter.right,
delimiter.display || false);
}
return data;
};
var renderMathInText = function(text, delimiters) {
var data = splitWithDelimiters(text, delimiters);
var fragment = document.createDocumentFragment();
for (var i = 0; i < data.length; i++) {
if (data[i].type === "text") {
fragment.appendChild(document.createTextNode(data[i].data));
} else {
var span = document.createElement("span");
var math = data[i].data;
try {
katex.render(math, span, {
displayMode: data[i].display
});
} catch (e) {
if (!(e instanceof katex.ParseError)) {
throw e;
}
console.error(
"KaTeX auto-render: Failed to parse `" + data[i].data +
"` with ",
e
);
fragment.appendChild(document.createTextNode(data[i].rawData));
continue;
}
fragment.appendChild(span);
}
}
return fragment;
};
var renderElem = function(elem, delimiters, ignoredTags) {
for (var i = 0; i < elem.childNodes.length; i++) {
var childNode = elem.childNodes[i];
if (childNode.nodeType === 3) {
// Text node
var frag = renderMathInText(childNode.textContent, delimiters);
i += frag.childNodes.length - 1;
elem.replaceChild(frag, childNode);
} else if (childNode.nodeType === 1) {
// Element node
var shouldRender = ignoredTags.indexOf(
childNode.nodeName.toLowerCase()) === -1;
if (shouldRender) {
renderElem(childNode, delimiters, ignoredTags);
}
}
// Otherwise, it's something else, and ignore it.
}
};
var defaultOptions = {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\[", right: "\\]", display: true},
{left: "\\(", right: "\\)", display: false}
// LaTeX uses this, but it ruins the display of normal `$` in text:
// {left: "$", right: "$", display: false}
],
ignoredTags: [
"script", "noscript", "style", "textarea", "pre", "code"
]
};
var extend = function(obj) {
// Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
}
}
}
return obj;
};
var renderMathInElement = function(elem, options) {
if (!elem) {
throw new Error("No element provided to render");
}
options = extend({}, defaultOptions, options);
renderElem(elem, options.delimiters, options.ignoredTags);
};
module.exports = renderMathInElement;
},{"./splitAtDelimiters":2}],2:[function(require,module,exports){
var findEndOfMath = function(delimiter, text, startIndex) {
// Adapted from
// https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
var index = startIndex;
var braceLevel = 0;
var delimLength = delimiter.length;
while (index < text.length) {
var character = text[index];
if (braceLevel <= 0 &&
text.slice(index, index + delimLength) === delimiter) {
return index;
} else if (character === "\\") {
index++;
} else if (character === "{") {
braceLevel++;
} else if (character === "}") {
braceLevel--;
}
index++;
}
return -1;
};
var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
var finalData = [];
for (var i = 0; i < startData.length; i++) {
if (startData[i].type === "text") {
var text = startData[i].data;
var lookingForLeft = true;
var currIndex = 0;
var nextIndex;
nextIndex = text.indexOf(leftDelim);
if (nextIndex !== -1) {
currIndex = nextIndex;
finalData.push({
type: "text",
data: text.slice(0, currIndex)
});
lookingForLeft = false;
}
while (true) {
if (lookingForLeft) {
nextIndex = text.indexOf(leftDelim, currIndex);
if (nextIndex === -1) {
break;
}
finalData.push({
type: "text",
data: text.slice(currIndex, nextIndex)
});
currIndex = nextIndex;
} else {
nextIndex = findEndOfMath(
rightDelim,
text,
currIndex + leftDelim.length);
if (nextIndex === -1) {
break;
}
finalData.push({
type: "math",
data: text.slice(
currIndex + leftDelim.length,
nextIndex),
rawData: text.slice(
currIndex,
nextIndex + rightDelim.length),
display: display
});
currIndex = nextIndex + rightDelim.length;
}
lookingForLeft = !lookingForLeft;
}
finalData.push({
type: "text",
data: text.slice(currIndex)
});
} else {
finalData.push(startData[i]);
}
}
return finalData;
};
module.exports = splitAtDelimiters;
},{}]},{},[1])
(1)
});
;

View File

@ -0,0 +1 @@
(function(e){if("function"==typeof bootstrap)bootstrap("rendermathinelement",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeRenderMathInElement=e}else"undefined"!=typeof window?window.renderMathInElement=e():global.renderMathInElement=e()})(function(){var e,t,r,n,a;return function i(e,t,r){function n(o,l){if(!t[o]){if(!e[o]){var f=typeof require=="function"&&require;if(!l&&f)return f(o,!0);if(a)return a(o,!0);throw new Error("Cannot find module '"+o+"'")}var s=t[o]={exports:{}};e[o][0].call(s.exports,function(t){var r=e[o][1][t];return n(r?r:t)},s,s.exports,i,e,t,r)}return t[o].exports}var a=typeof require=="function"&&require;for(var o=0;o<r.length;o++)n(r[o]);return n}({1:[function(e,t,r){var n=e("./splitAtDelimiters");var a=function(e,t){var r=[{type:"text",data:e}];for(var a=0;a<t.length;a++){var i=t[a];r=n(r,i.left,i.right,i.display||false)}return r};var i=function(e,t){var r=a(e,t);var n=document.createDocumentFragment();for(var i=0;i<r.length;i++){if(r[i].type==="text"){n.appendChild(document.createTextNode(r[i].data))}else{var o=document.createElement("span");var l=r[i].data;try{katex.render(l,o,{displayMode:r[i].display})}catch(f){if(!(f instanceof katex.ParseError)){throw f}console.error("KaTeX auto-render: Failed to parse `"+r[i].data+"` with ",f);n.appendChild(document.createTextNode(r[i].rawData));continue}n.appendChild(o)}}return n};var o=function(e,t,r){for(var n=0;n<e.childNodes.length;n++){var a=e.childNodes[n];if(a.nodeType===3){var l=i(a.textContent,t);n+=l.childNodes.length-1;e.replaceChild(l,a)}else if(a.nodeType===1){var f=r.indexOf(a.nodeName.toLowerCase())===-1;if(f){o(a,t,r)}}}};var l={delimiters:[{left:"$$",right:"$$",display:true},{left:"\\[",right:"\\]",display:true},{left:"\\(",right:"\\)",display:false}],ignoredTags:["script","noscript","style","textarea","pre","code"]};var f=function(e){var t,r;for(var n=1,a=arguments.length;n<a;n++){t=arguments[n];for(r in t){if(Object.prototype.hasOwnProperty.call(t,r)){e[r]=t[r]}}}return e};var s=function(e,t){if(!e){throw new Error("No element provided to render")}t=f({},l,t);o(e,t.delimiters,t.ignoredTags)};t.exports=s},{"./splitAtDelimiters":2}],2:[function(e,t,r){var n=function(e,t,r){var n=r;var a=0;var i=e.length;while(n<t.length){var o=t[n];if(a<=0&&t.slice(n,n+i)===e){return n}else if(o==="\\"){n++}else if(o==="{"){a++}else if(o==="}"){a--}n++}return-1};var a=function(e,t,r,a){var i=[];for(var o=0;o<e.length;o++){if(e[o].type==="text"){var l=e[o].data;var f=true;var s=0;var d;d=l.indexOf(t);if(d!==-1){s=d;i.push({type:"text",data:l.slice(0,s)});f=false}while(true){if(f){d=l.indexOf(t,s);if(d===-1){break}i.push({type:"text",data:l.slice(s,d)});s=d}else{d=n(r,l,s+t.length);if(d===-1){break}i.push({type:"math",data:l.slice(s+t.length,d),rawData:l.slice(s,d+r.length),display:a});s=d+r.length}f=!f}i.push({type:"text",data:l.slice(s)})}else{i.push(e[o])}}return i};t.exports=a},{}]},{},[1])(1)});

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More