From f2e43b84bedf7542b68c9b87668ab1372b9ab233 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 17 Dec 2016 22:03:12 +0200 Subject: [PATCH 001/355] Implement color hotkeys --- client/js/lounge.js | 110 +++++++++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 81275fbd..0d2a08ec 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1284,45 +1284,81 @@ $(function() { forms.find(".username").val(nick); }); - Mousetrap.bind([ - "command+up", - "command+down", - "ctrl+up", - "ctrl+down" - ], function(e, keys) { - var channels = sidebar.find(".chan"); - var index = channels.index(channels.filter(".active")); - var direction = keys.split("+").pop(); - switch (direction) { - case "up": - // Loop - var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length; - channels.eq(upTarget).click(); - break; + (function HotkeysScope() { + Mousetrap.bind([ + "command+up", + "command+down", + "ctrl+up", + "ctrl+down" + ], function(e, keys) { + var channels = sidebar.find(".chan"); + var index = channels.index(channels.filter(".active")); + var direction = keys.split("+").pop(); + switch (direction) { + case "up": + // Loop + var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length; + channels.eq(upTarget).click(); + break; - case "down": - // Loop - var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length; - channels.eq(downTarget).click(); - break; + case "down": + // Loop + var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length; + channels.eq(downTarget).click(); + break; + } + }); + + Mousetrap.bind([ + "command+shift+l", + "ctrl+shift+l" + ], function(e) { + if (e.target === input[0]) { + clear(); + e.preventDefault(); + } + }); + + Mousetrap.bind([ + "escape" + ], function() { + contextMenuContainer.hide(); + }); + + var colorsHotkeys = { + k: "\x03", + b: "\x02", + u: "\x1F", + i: "\x1D", + o: "\x0F", + }; + + for (var hotkey in colorsHotkeys) { + Mousetrap.bind([ + "command+" + hotkey, + "ctrl+" + hotkey + ], function(e) { + e.preventDefault(); + + const cursorPosStart = input.prop("selectionStart"); + const cursorPosEnd = input.prop("selectionEnd"); + const value = input.val(); + let newValue = value.substring(0, cursorPosStart) + colorsHotkeys[e.key]; + + if (cursorPosStart === cursorPosEnd) { + // If no text is selected, insert at cursor + newValue += value.substring(cursorPosEnd, value.length); + } else { + // If text is selected, insert formatting character at start and the end + newValue += value.substring(cursorPosStart, cursorPosEnd) + colorsHotkeys[e.key] + value.substring(cursorPosEnd, value.length); + } + + input + .val(newValue) + .get(0).setSelectionRange(cursorPosStart + 1, cursorPosEnd + 1); + }); } - }); - - Mousetrap.bind([ - "command+k", - "ctrl+shift+l" - ], function(e) { - if (e.target === input[0]) { - clear(); - e.preventDefault(); - } - }); - - Mousetrap.bind([ - "escape" - ], function() { - contextMenuContainer.hide(); - }); + }()); setInterval(function() { chat.find(".chan:not(.active)").each(function() { From 65a218d9de5624e48679828cc47d7ce5bbd055f0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 13 Mar 2017 02:38:54 +0000 Subject: [PATCH 002/355] chore(package): update babel-core to version 6.24.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9134fbd..513445ed 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "spdy": "3.4.4" }, "devDependencies": { - "babel-core": "6.23.1", + "babel-core": "6.24.0", "babel-loader": "6.4.0", "babel-preset-es2015": "6.22.0", "chai": "3.5.0", From 8cd8ba61015bf586e407dfead1729d12e1c1b1b4 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 13 Mar 2017 03:54:02 +0000 Subject: [PATCH 003/355] chore(package): update babel-preset-es2015 to version 6.24.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a9134fbd..d81f5e60 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "devDependencies": { "babel-core": "6.23.1", "babel-loader": "6.4.0", - "babel-preset-es2015": "6.22.0", + "babel-preset-es2015": "6.24.0", "chai": "3.5.0", "eslint": "3.17.1", "font-awesome": "4.7.0", From 8ef99d7ad831beedb8136edaf5f83626d76208b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 13 Mar 2017 01:58:39 -0400 Subject: [PATCH 004/355] Add shortcuts for new formatting in help window --- client/index.html | 116 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/client/index.html b/client/index.html index 0ec349a6..192a4085 100644 --- a/client/index.html +++ b/client/index.html @@ -390,6 +390,62 @@ +
+
+ Ctrl + K +
+
+

+ Mark any text typed after this shortcut to be colored. After + hitting this shortcut, enter an integer in the + 0—15 range to select the desired color. +

+

+ A color reference can be found + here. +

+
+
+ +
+
+ Ctrl + B +
+
+

Mark all text typed after this shortcut as bold.

+
+
+ +
+
+ Ctrl + U +
+
+

Mark all text typed after this shortcut as underlined.

+
+
+ +
+
+ Ctrl + I +
+
+

Mark all text typed after this shortcut as italics.

+
+
+ +
+
+ Ctrl + O +
+
+

+ Mark all text typed after this shortcut to be reset to its + original formatting. +

+
+
+

On macOS

@@ -403,13 +459,69 @@
- + K + + + L

Clear the current screen

+
+
+ + K +
+
+

+ Mark any text typed after this shortcut to be colored. After + hitting this shortcut, enter an integer in the + 0—15 range to select the desired color. +

+

+ A color reference can be found + here. +

+
+
+ +
+
+ + B +
+
+

Mark all text typed after this shortcut as bold.

+
+
+ +
+
+ + U +
+
+

Mark all text typed after this shortcut as underlined.

+
+
+ +
+
+ + I +
+
+

Mark all text typed after this shortcut as italics.

+
+
+ +
+
+ + O +
+
+

+ Mark all text typed after this shortcut to be reset to its + original formatting. +

+
+
+

Commands

@@ -461,7 +573,7 @@

Send a CTCP request. Read more about this on - the dedicated Wikipedia article. + the dedicated Wikipedia article.

From ae5f768dd1d3da76d170151bd0aa400decc73132 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 16 Mar 2017 21:35:11 +0000 Subject: [PATCH 005/355] chore(package): update jquery to version 3.2.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8a9b98..72aa1a2e 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "font-awesome": "4.7.0", "handlebars": "4.0.6", "handlebars-loader": "1.4.0", - "jquery": "3.1.1", + "jquery": "3.2.0", "jquery-ui": "1.12.1", "mocha": "3.2.0", "mousetrap": "1.6.0", From aac6980eb50da833f72e3c97e3ba6e1f0c57f94c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 17 Mar 2017 22:13:47 +0000 Subject: [PATCH 006/355] chore(package): update eslint to version 3.18.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8a9b98..bc7ca844 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "babel-loader": "6.4.0", "babel-preset-es2015": "6.22.0", "chai": "3.5.0", - "eslint": "3.17.1", + "eslint": "3.18.0", "font-awesome": "4.7.0", "handlebars": "4.0.6", "handlebars-loader": "1.4.0", From dcefcd19cba4d30b21bdce88d811d24908dffee7 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 18 Mar 2017 11:21:18 +0200 Subject: [PATCH 007/355] Use require() instead of import in client code Closes #895 --- client/.eslintrc.yml | 3 --- client/js/libs/handlebars/colorClass.js | 2 ++ client/js/libs/handlebars/diff.js | 2 ++ client/js/libs/handlebars/equal.js | 2 ++ client/js/libs/handlebars/localedate.js | 2 ++ client/js/libs/handlebars/localetime.js | 2 ++ client/js/libs/handlebars/modes.js | 2 ++ client/js/libs/handlebars/parse.js | 6 +++-- client/js/libs/handlebars/roundBadgeNumber.js | 2 ++ client/js/libs/handlebars/tojson.js | 2 ++ client/js/libs/handlebars/tz.js | 2 ++ client/js/libs/handlebars/users.js | 2 ++ client/js/libs/slideout.js | 6 +++-- client/js/lounge.js | 26 ++++++++++--------- client/views/index.js | 2 ++ 15 files changed, 44 insertions(+), 19 deletions(-) delete mode 100644 client/.eslintrc.yml diff --git a/client/.eslintrc.yml b/client/.eslintrc.yml deleted file mode 100644 index cb4e55ff..00000000 --- a/client/.eslintrc.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -parserOptions: - sourceType: module diff --git a/client/js/libs/handlebars/colorClass.js b/client/js/libs/handlebars/colorClass.js index 53bfc0d4..e7b8d8e4 100644 --- a/client/js/libs/handlebars/colorClass.js +++ b/client/js/libs/handlebars/colorClass.js @@ -1,3 +1,5 @@ +"use strict"; + // Generates a string from "color-1" to "color-32" based on an input string module.exports = function(str) { var hash = 0; diff --git a/client/js/libs/handlebars/diff.js b/client/js/libs/handlebars/diff.js index 3b2116bd..198c8882 100644 --- a/client/js/libs/handlebars/diff.js +++ b/client/js/libs/handlebars/diff.js @@ -1,3 +1,5 @@ +"use strict"; + var diff; module.exports = function(a, opt) { diff --git a/client/js/libs/handlebars/equal.js b/client/js/libs/handlebars/equal.js index 1d7a4393..6a8033ca 100644 --- a/client/js/libs/handlebars/equal.js +++ b/client/js/libs/handlebars/equal.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(a, b, opt) { a = a.toString(); b = b.toString(); diff --git a/client/js/libs/handlebars/localedate.js b/client/js/libs/handlebars/localedate.js index 0f85a5f2..9c800ab0 100644 --- a/client/js/libs/handlebars/localedate.js +++ b/client/js/libs/handlebars/localedate.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(time) { return new Date(time).toLocaleDateString(); }; diff --git a/client/js/libs/handlebars/localetime.js b/client/js/libs/handlebars/localetime.js index 5318a022..59e1dbf8 100644 --- a/client/js/libs/handlebars/localetime.js +++ b/client/js/libs/handlebars/localetime.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(time) { return new Date(time).toLocaleString(); }; diff --git a/client/js/libs/handlebars/modes.js b/client/js/libs/handlebars/modes.js index 6416515f..ff3d555e 100644 --- a/client/js/libs/handlebars/modes.js +++ b/client/js/libs/handlebars/modes.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(mode) { var modes = { "~": "owner", diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index cd61ff7e..9d276f49 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -1,5 +1,7 @@ -import Handlebars from "handlebars/runtime"; -import URI from "urijs"; +"use strict"; + +const Handlebars = require("handlebars/runtime"); +const URI = require("urijs"); module.exports = function(text) { text = Handlebars.Utils.escapeExpression(text); diff --git a/client/js/libs/handlebars/roundBadgeNumber.js b/client/js/libs/handlebars/roundBadgeNumber.js index a7750de0..97c50afa 100644 --- a/client/js/libs/handlebars/roundBadgeNumber.js +++ b/client/js/libs/handlebars/roundBadgeNumber.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(count) { if (count < 1000) { return count; diff --git a/client/js/libs/handlebars/tojson.js b/client/js/libs/handlebars/tojson.js index fcd0fde1..418ac8c4 100644 --- a/client/js/libs/handlebars/tojson.js +++ b/client/js/libs/handlebars/tojson.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(context) { return window.JSON.stringify(context); }; diff --git a/client/js/libs/handlebars/tz.js b/client/js/libs/handlebars/tz.js index 14f05e5e..e62bf285 100644 --- a/client/js/libs/handlebars/tz.js +++ b/client/js/libs/handlebars/tz.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(time) { time = new Date(time); var h = time.getHours(); diff --git a/client/js/libs/handlebars/users.js b/client/js/libs/handlebars/users.js index e52e7526..d962423c 100644 --- a/client/js/libs/handlebars/users.js +++ b/client/js/libs/handlebars/users.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function(count) { return count + " " + (count === 1 ? "user" : "users"); }; diff --git a/client/js/libs/slideout.js b/client/js/libs/slideout.js index 522cc448..34faa9a8 100644 --- a/client/js/libs/slideout.js +++ b/client/js/libs/slideout.js @@ -1,7 +1,9 @@ +"use strict"; + /** * Simple slideout menu implementation. */ -export default function slideoutMenu(viewport, menu) { +module.exports = function slideoutMenu(viewport, menu) { var touchStartPos = null; var touchCurPos = null; var touchStartTime = 0; @@ -98,4 +100,4 @@ export default function slideoutMenu(viewport, menu) { return menuIsOpen; } }; -} +}; diff --git a/client/js/lounge.js b/client/js/lounge.js index 81275fbd..c26f653b 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1,18 +1,20 @@ +"use strict"; + // vendor libraries -import "jquery-ui/ui/widgets/sortable"; -import $ from "jquery"; -import io from "socket.io-client"; -import Mousetrap from "mousetrap"; -import URI from "urijs"; +require("jquery-ui/ui/widgets/sortable"); +const $ = require("jquery"); +const io = require("socket.io-client"); +const Mousetrap = require("mousetrap"); +const URI = require("urijs"); // our libraries -import "./libs/jquery/inputhistory"; -import "./libs/jquery/stickyscroll"; -import "./libs/jquery/tabcomplete"; -import helpers_parse from "./libs/handlebars/parse"; -import helpers_roundBadgeNumber from "./libs/handlebars/roundBadgeNumber"; -import slideoutMenu from "./libs/slideout"; -import templates from "../views"; +require("./libs/jquery/inputhistory"); +require("./libs/jquery/stickyscroll"); +require("./libs/jquery/tabcomplete"); +const helpers_parse = require("./libs/handlebars/parse"); +const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber"); +const slideoutMenu = require("./libs/slideout"); +const templates = require("../views"); $(function() { var path = window.location.pathname + "socket.io/"; diff --git a/client/views/index.js b/client/views/index.js index 2890dea9..0b92ab8e 100644 --- a/client/views/index.js +++ b/client/views/index.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = { actions: { action: require("./actions/action.tpl"), From 777f4771351864fa52db83b02de687379a08ea90 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 15 Mar 2017 12:39:35 +0000 Subject: [PATCH 008/355] fix(package): update fs-extra to version 2.1.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8a9b98..d0821c2a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "commander": "2.9.0", "event-stream": "3.3.4", "express": "4.15.2", - "fs-extra": "2.0.0", + "fs-extra": "2.1.2", "irc-framework": "2.6.1", "ldapjs": "1.0.1", "lodash": "4.17.4", From d9358d555fb7bf9c4640ec49e0ac42348dc25670 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 18 Mar 2017 16:09:43 +0200 Subject: [PATCH 009/355] Generate sourcemap in production build Fixes #974 --- webpack.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack.config.js b/webpack.config.js index 09c36561..8d66b1a9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -71,6 +71,7 @@ let config = { if (process.env.NODE_ENV === "production") { config.plugins.push(new webpack.optimize.UglifyJsPlugin({ + sourceMap: true, comments: false })); } else { From 3318acd16beb5a5b1f82fda5291318623dfda30d Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 9 Jan 2017 16:24:04 +0000 Subject: [PATCH 010/355] fix filling in nickname overriding username (in add network tab) --- client/js/lounge.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 774aeddd..754ab1a4 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1283,9 +1283,27 @@ $(function() { ); }); + forms.on("focusin", ".nick", function() { + // Need to set the first "lastvalue", so it can be used in the below function + var nick = $(this); + nick.data("lastvalue", nick.val()); + }); + forms.on("input", ".nick", function() { var nick = $(this).val(); - forms.find(".username").val(nick); + var usernameInput = forms.find(".username"); + + // Because this gets called /after/ it has already changed, we need use the previous value + var lastValue = $(this).data("lastvalue"); + + // They were the same before the change, so update the username field + if (usernameInput.val() === lastValue) { + usernameInput.val(nick); + } + + // Store the "previous" value, for next time + $(this).data("lastvalue", nick); + }); Mousetrap.bind([ From 3b2e3fc08c8155256f817396a24ce99ca6b97591 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 18 Mar 2017 21:40:39 +0200 Subject: [PATCH 011/355] Enforce more space and new line rules --- .eslintrc.yml | 3 +++ client/js/libs/handlebars/parse.js | 1 - client/js/lounge.js | 3 --- src/clientManager.js | 1 - test/client/js/libs/handlebars/localetimeTest.js | 2 -- test/models/network.js | 1 - 6 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index d6b6a59e..353706cc 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -27,6 +27,7 @@ rules: no-else-return: 2 no-implicit-globals: 2 no-multi-spaces: 2 + no-multiple-empty-lines: [2, { "max": 1 }] no-shadow: 2 no-template-curly-in-string: 2 no-trailing-spaces: 2 @@ -34,11 +35,13 @@ rules: no-useless-escape: 2 no-useless-return: 2 object-curly-spacing: [2, never] + padded-blocks: [2, never] quote-props: [2, as-needed] quotes: [2, double, avoid-escape] semi: [2, always] space-before-blocks: 2 space-before-function-paren: [2, never] + space-in-parens: [2, never] space-infix-ops: 2 spaced-comment: [2, always] strict: 2 diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index cd61ff7e..325b049f 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -49,7 +49,6 @@ var styleCheck_Re = /[\x00-\x1F]/, // breaks all open styles ^O (\x0F) styleBreak = "\x0F"; - function styleTemplate(settings) { return "" + settings.text + ""; } diff --git a/client/js/lounge.js b/client/js/lounge.js index 81275fbd..a51b3fec 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -364,7 +364,6 @@ $(function() { lastDate = msgDate; }); } - } function renderChannelUsers(data) { @@ -498,7 +497,6 @@ $(function() { lastDate = msgDate; }); - }); socket.on("network", function(data) { @@ -1168,7 +1166,6 @@ $(function() { } catch (exception) { // `new Notification(...)` is not supported and should be silenced. } - } } } diff --git a/src/clientManager.js b/src/clientManager.js index 578972c3..a0f94f0f 100644 --- a/src/clientManager.js +++ b/src/clientManager.js @@ -96,7 +96,6 @@ ClientManager.prototype.addUser = function(name, password, enableLog) { return false; } try { - if (require("path").basename(name) !== name) { throw new Error(name + " is an invalid username."); } diff --git a/test/client/js/libs/handlebars/localetimeTest.js b/test/client/js/libs/handlebars/localetimeTest.js index 75debd8e..bccf3649 100644 --- a/test/client/js/libs/handlebars/localetimeTest.js +++ b/test/client/js/libs/handlebars/localetimeTest.js @@ -4,7 +4,6 @@ const expect = require("chai").expect; const localetime = require("../../../../../client/js/libs/handlebars/localetime"); describe("localetime Handlebars helper", () => { - it("should render a human-readable date", () => { // 12PM in UTC time const date = new Date("2014-05-22T12:00:00"); @@ -17,5 +16,4 @@ describe("localetime Handlebars helper", () => { expect(localetime(time)).to.equal("5/22/2014, 12:00:00 PM"); }); - }); diff --git a/test/models/network.js b/test/models/network.js index fe56157d..438c6251 100644 --- a/test/models/network.js +++ b/test/models/network.js @@ -8,7 +8,6 @@ var Network = require("../../src/models/network"); describe("Network", function() { describe("#export()", function() { - it("should produce an valid object", function() { var network = new Network({ name: "networkName", From 7175dc1706c84362d3c56e8f86ad663c3a988418 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 18 Mar 2017 21:27:01 +0000 Subject: [PATCH 012/355] fix(package): update moment to version 2.18.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8a9b98..04805315 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "irc-framework": "2.6.1", "ldapjs": "1.0.1", "lodash": "4.17.4", - "moment": "2.17.1", + "moment": "2.18.0", "read": "1.0.7", "request": "2.81.0", "semver": "5.3.0", From c6ed95e55575a4850ee394f211538809f86123b8 Mon Sep 17 00:00:00 2001 From: William Boman Date: Mon, 20 Mar 2017 23:08:28 +0100 Subject: [PATCH 013/355] views/msg: set data-from attribute to allow styling messages from specific user(s) --- client/views/msg.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/views/msg.tpl b/client/views/msg.tpl index 72811106..36dc4c27 100644 --- a/client/views/msg.tpl +++ b/client/views/msg.tpl @@ -1,4 +1,4 @@ -
+
{{tz time}} From 03fe53e87f3ca9bb681ada5e9db4e9eacdde7a26 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 16 Mar 2017 10:54:48 +0000 Subject: [PATCH 014/355] chore(package): update babel-loader to version 6.4.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fdc2e77..e0f0f00f 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ }, "devDependencies": { "babel-core": "6.24.0", - "babel-loader": "6.4.0", + "babel-loader": "6.4.1", "babel-preset-es2015": "6.24.0", "chai": "3.5.0", "eslint": "3.18.0", From 1f01da21ff6a05fcaed063283056dbdc89b4c4fe Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 28 Mar 2017 05:16:34 +0000 Subject: [PATCH 015/355] chore(package): update nyc to version 10.2.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e0f0f00f..6f750e11 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "mocha": "3.2.0", "mousetrap": "1.6.0", "npm-run-all": "4.0.2", - "nyc": "10.1.2", + "nyc": "10.2.0", "socket.io-client": "1.7.3", "stylelint": "7.9.0", "urijs": "1.18.9", From 2bcbb62af0af93db08c32ecfdd7f8db70dd1584c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 28 Mar 2017 19:09:40 -0400 Subject: [PATCH 016/355] Remove unnecessary coverage dir from excluded nyc files This is possible since https://github.com/istanbuljs/nyc/commit/50adde41969eb067aefc787b5ddb5cb463fd0bb3. --- .nycrc | 1 - 1 file changed, 1 deletion(-) diff --git a/.nycrc b/.nycrc index 998e5bac..ae33aa14 100644 --- a/.nycrc +++ b/.nycrc @@ -3,7 +3,6 @@ "exclude": [ "client/js/bundle.js", "client/js/bundle.vendor.js", - "coverage/", "test/" ], "reporter": [ From d287dede499acebffd924c66479813e35d165598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Wed, 29 Mar 2017 00:05:28 -0400 Subject: [PATCH 017/355] Setup ESLint to make sure an EOF feed is always present --- .eslintrc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.yml b/.eslintrc.yml index 353706cc..937aef1f 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -15,6 +15,7 @@ rules: comma-dangle: 0 curly: [2, all] dot-notation: 2 + eol-last: 2 eqeqeq: 2 handle-callback-err: 2 indent: [2, tab, { "MemberExpression": 1 }] From 4a68b78fd512f60a0c76275e81b7e8f2a90bac4c Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 18 Dec 2016 11:24:50 +0200 Subject: [PATCH 018/355] Implement away message restoration on reconnections and auto away --- src/client.js | 24 ++++++++++++++++++++++++ src/models/network.js | 3 +++ src/plugins/inputs/away.js | 14 ++++++-------- src/plugins/irc-events/connection.js | 9 +++++++++ test/models/network.js | 2 ++ 5 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/client.js b/src/client.js index b7e1b3ef..00f3eb35 100644 --- a/src/client.js +++ b/src/client.js @@ -65,6 +65,7 @@ function Client(manager, name, config) { config = {}; } _.merge(this, { + awayMessage: "", lastActiveChannel: -1, attachedClients: {}, config: config, @@ -485,6 +486,16 @@ Client.prototype.clientAttach = function(socketId) { client.attachedClients[socketId] = client.lastActiveChannel; + if (client.awayMessage && _.size(client.attachedClients) === 0) { + client.networks.forEach(function(network) { + // Only remove away on client attachment if + // there is no away message on this network + if (!network.awayMessage) { + network.irc.raw("AWAY"); + } + }); + } + // Update old networks to store ip and hostmask client.networks.forEach(network => { if (!network.ip) { @@ -508,7 +519,19 @@ Client.prototype.clientAttach = function(socketId) { }; Client.prototype.clientDetach = function(socketId) { + const client = this; + delete this.attachedClients[socketId]; + + if (client.awayMessage && _.size(client.attachedClients) === 0) { + client.networks.forEach(function(network) { + // Only set away on client deattachment if + // there is no away message on this network + if (!network.awayMessage) { + network.irc.raw("AWAY", client.awayMessage); + } + }); + } }; Client.prototype.save = _.debounce(function SaveClient() { @@ -518,6 +541,7 @@ Client.prototype.save = _.debounce(function SaveClient() { const client = this; let json = {}; + json.awayMessage = client.awayMessage; json.networks = this.networks.map(n => n.export()); client.manager.updateUser(client.name, json); }, 1000, {maxWait: 10000}); diff --git a/src/models/network.js b/src/models/network.js index 0ddd3530..4484ed96 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -14,6 +14,7 @@ function Network(attr) { port: 6667, tls: false, password: "", + awayMessage: "", commands: [], username: "", realname: "", @@ -56,6 +57,7 @@ Network.prototype.setNick = function(nick) { Network.prototype.toJSON = function() { return _.omit(this, [ + "awayMessage", "chanCache", "highlightRegex", "irc", @@ -65,6 +67,7 @@ Network.prototype.toJSON = function() { Network.prototype.export = function() { var network = _.pick(this, [ + "awayMessage", "nick", "name", "host", diff --git a/src/plugins/inputs/away.js b/src/plugins/inputs/away.js index 201559fe..34c58596 100644 --- a/src/plugins/inputs/away.js +++ b/src/plugins/inputs/away.js @@ -3,17 +3,15 @@ exports.commands = ["away", "back"]; exports.input = function(network, chan, cmd, args) { - if (cmd === "away") { - let reason = " "; + let reason = ""; - if (args.length > 0) { - reason = args.join(" "); - } + if (cmd === "away") { + reason = args.length > 0 ? args.join(" ") : " "; network.irc.raw("AWAY", reason); - - return; + } else { // back command + network.irc.raw("AWAY"); } - network.irc.raw("AWAY"); + network.awayMessage = reason; }; diff --git a/src/plugins/irc-events/connection.js b/src/plugins/irc-events/connection.js index a8ecef54..4e34a154 100644 --- a/src/plugins/irc-events/connection.js +++ b/src/plugins/irc-events/connection.js @@ -1,5 +1,6 @@ "use strict"; +var _ = require("lodash"); var Msg = require("../../models/msg"); var Chan = require("../../models/chan"); var Helper = require("../../helper"); @@ -18,6 +19,14 @@ module.exports = function(irc, network) { }), true); } + // Always restore away message for this network + if (network.awayMessage) { + irc.raw("AWAY", network.awayMessage); + // Only set generic away message if there are no clients attached + } else if (client.awayMessage && _.size(client.attachedClients) === 0) { + irc.raw("AWAY", client.awayMessage); + } + var delay = 1000; var commands = network.commands; if (Array.isArray(commands)) { diff --git a/test/models/network.js b/test/models/network.js index 438c6251..a5ebb5e4 100644 --- a/test/models/network.js +++ b/test/models/network.js @@ -10,6 +10,7 @@ describe("Network", function() { describe("#export()", function() { it("should produce an valid object", function() { var network = new Network({ + awayMessage: "I am away", name: "networkName", channels: [ new Chan({name: "#thelounge"}), @@ -21,6 +22,7 @@ describe("Network", function() { network.setNick("chillin`"); expect(network.export()).to.deep.equal({ + awayMessage: "I am away", name: "networkName", host: "", port: 6667, From 3f031ba6ffe6f1e9d8ff5b6e651ec16787243965 Mon Sep 17 00:00:00 2001 From: Michael van Tricht Date: Wed, 29 Mar 2017 10:11:12 +0200 Subject: [PATCH 019/355] Help page: commands can be autocompleted. --- client/index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/index.html b/client/index.html index 0ec349a6..f34280e2 100644 --- a/client/index.html +++ b/client/index.html @@ -411,6 +411,8 @@

Commands

+ +

All commands can be autocompleted with tab.

From 9bf1e6e0d52e9e4ffa52c5d0b8e32a9209e60154 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 19 Mar 2017 10:02:39 +0200 Subject: [PATCH 020/355] Do not build json3 module of Webpack --- package.json | 2 +- webpack.config.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ce8a9b98..53086a42 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "start-dev": "npm-run-all --parallel watch start", "build": "npm-run-all build:*", "build:font-awesome": "node scripts/build-fontawesome.js", - "build:webpack": "webpack", + "build:webpack": "webpack --progress", "watch": "webpack --watch", "test": "npm-run-all -c test:* lint", "test:mocha": "mocha", diff --git a/webpack.config.js b/webpack.config.js index 09c36561..edd58dd5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -60,6 +60,9 @@ let config = { }, ] }, + externals: { + json3: "JSON", // socket.io uses json3.js, but we do not target any browsers that need it + }, plugins: [ new webpack.optimize.CommonsChunkPlugin("js/bundle.vendor.js") ] From da0a52e3cb5c9ab00764d8e53722f8063d778cdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Thu, 30 Mar 2017 02:19:26 -0400 Subject: [PATCH 021/355] Fix wrong font size in help center labels This only concerns plain texts, not `` or ``. --- client/css/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/css/style.css b/client/css/style.css index 6c5ac484..3287eba4 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1327,6 +1327,7 @@ kbd { #help .help-item .subject, #help .help-item .description { display: table-cell; + font-size: 14px; padding-bottom: 15px; } @@ -1337,7 +1338,6 @@ kbd { } #help .help-item .description p { - font-size: 14px; margin-bottom: 0; } From 92349976cb06b3d1bae7386a9d25ec1db5a124b0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 30 Mar 2017 09:10:14 +0000 Subject: [PATCH 022/355] chore(package): update urijs to version 1.18.10 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2d01131a..14086ee4 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "nyc": "10.2.0", "socket.io-client": "1.7.3", "stylelint": "7.9.0", - "urijs": "1.18.9", + "urijs": "1.18.10", "webpack": "2.2.1" } } From 35b6b47de33700946261604d7aeba5bad1a67fcc Mon Sep 17 00:00:00 2001 From: Maxime Poulin Date: Sat, 3 Sep 2016 21:29:48 -0400 Subject: [PATCH 023/355] Remove table layout for chat messages (and fix layout issues yet again) --- client/css/style.css | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 3287eba4..14603d2b 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -832,15 +832,15 @@ kbd { } #chat .messages { - display: table; - table-layout: fixed; - width: 100%; padding: 10px 0; } #chat .msg { word-wrap: break-word; word-break: break-word; /* Webkit-specific */ + display: flex; + overflow: hidden; + position: relative; } #chat .unread-marker { @@ -912,16 +912,15 @@ kbd { #chat .time, #chat .from, #chat .text { - display: table-cell; + display: block; padding: 2px 0; - vertical-align: top; + flex: 0 0 auto; } #chat .time { color: #ddd; text-align: right; - max-width: 46px; - min-width: 46px; + width: 46px; } #chat .from { @@ -929,8 +928,14 @@ kbd { color: #b1c3ce; padding-right: 10px; text-align: right; - max-width: 134px; - min-width: 134px; + width: 134px; + align-self: stretch; +} + +#chat .text { + margin: auto; + overflow: hidden; + flex: 1 1 auto; } #loading a, From fb672ab57ff2a6d5ae8a54b2acef64d4e4354513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Thu, 30 Mar 2017 19:50:36 -0400 Subject: [PATCH 024/355] Improvements to the new flex layout for messages --- client/css/style.css | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 14603d2b..fc111802 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -929,12 +929,9 @@ kbd { padding-right: 10px; text-align: right; width: 134px; - align-self: stretch; } #chat .text { - margin: auto; - overflow: hidden; flex: 1 1 auto; } @@ -1001,14 +998,6 @@ kbd { color: #999; } -#chat .msg.motd .text, -#chat .msg.message .text, -#chat .msg.action .action-text, -#chat .msg.notice .text { - white-space: pre-wrap; - overflow: hidden; -} - #chat .msg.channel_list_loading .text { color: #999; font-style: italic; From e62da5b1ea4c5b1d96bd7b0739674f52f53fa9fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Fri, 31 Mar 2017 01:26:37 -0400 Subject: [PATCH 025/355] Remove extra newline to please ESLint See https://travis-ci.org/thelounge/lounge/jobs/217041734#L1200 --- client/js/lounge.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index a706b6d9..52873305 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1296,7 +1296,6 @@ $(function() { // Store the "previous" value, for next time $(this).data("lastvalue", nick); - }); Mousetrap.bind([ From 6b641059c10c0ef1504a5ba23929973a289850ae Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 21 Mar 2017 22:57:55 +0000 Subject: [PATCH 026/355] chore(package): update webpack to version 2.3.2 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14086ee4..313ecaf1 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "socket.io-client": "1.7.3", "stylelint": "7.9.0", "urijs": "1.18.10", - "webpack": "2.2.1" + "webpack": "2.3.2" } } From 0a06874a97ebc5c94b4571ba0c133ee5600e01f6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 20 Mar 2017 19:07:33 +0000 Subject: [PATCH 027/355] chore(package): update jquery to version 3.2.1 Closes #969 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 313ecaf1..8c652fa5 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "font-awesome": "4.7.0", "handlebars": "4.0.6", "handlebars-loader": "1.4.0", - "jquery": "3.2.0", + "jquery": "3.2.1", "jquery-ui": "1.12.1", "mocha": "3.2.0", "mousetrap": "1.6.0", From 5ce8d934100d97bfa3f7b9ddc3d0f7548edf4d5d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 22 Mar 2017 00:11:25 +0000 Subject: [PATCH 028/355] fix(package): update moment to version 2.18.1 Closes #976 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 313ecaf1..89b521b5 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "irc-framework": "2.6.1", "ldapjs": "1.0.1", "lodash": "4.17.4", - "moment": "2.18.0", + "moment": "2.18.1", "read": "1.0.7", "request": "2.81.0", "semver": "5.3.0", From 5923e48dda7538ee94f94a033b362baffc6f4f89 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 31 Mar 2017 20:50:18 +0000 Subject: [PATCH 029/355] chore(package): update eslint to version 3.19.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 313ecaf1..ba6e6340 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "babel-loader": "6.4.1", "babel-preset-es2015": "6.24.0", "chai": "3.5.0", - "eslint": "3.18.0", + "eslint": "3.19.0", "font-awesome": "4.7.0", "handlebars": "4.0.6", "handlebars-loader": "1.4.0", From 001f96035b37bf0369eb047e034e79e6e49ff41e Mon Sep 17 00:00:00 2001 From: S Date: Thu, 23 Mar 2017 08:47:51 +0100 Subject: [PATCH 030/355] Switch to bcryptjs and make password comparison async - PasswordCompareAsync prevents timeouts on resource constraint devices - All password.compare calls are now async - Updated tests to accept async functions --- package.json | 2 +- src/helper.js | 4 +-- src/server.js | 69 +++++++++++++++++++++++------------------ test/tests/passwords.js | 17 ++++++++-- 4 files changed, 57 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index 5615941c..63f68dce 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "node": ">=4.2.0" }, "dependencies": { - "bcrypt-nodejs": "0.0.3", + "bcryptjs": "2.4.3", "cheerio": "0.22.0", "colors": "1.1.2", "commander": "2.9.0", diff --git a/src/helper.js b/src/helper.js index c6e971c1..830b91c9 100644 --- a/src/helper.js +++ b/src/helper.js @@ -6,7 +6,7 @@ var path = require("path"); var os = require("os"); var fs = require("fs"); var net = require("net"); -var bcrypt = require("bcrypt-nodejs"); +var bcrypt = require("bcryptjs"); var Helper = { config: null, @@ -125,5 +125,5 @@ function passwordHash(password) { } function passwordCompare(password, expected) { - return bcrypt.compareSync(password, expected); + return bcrypt.compare(password, expected); } diff --git a/src/server.js b/src/server.js index 636b5ab6..28b35436 100644 --- a/src/server.js +++ b/src/server.js @@ -192,27 +192,33 @@ function init(socket, client) { }); return; } - if (!Helper.password.compare(old || "", client.config.password)) { - socket.emit("change-password", { - error: "The current password field does not match your account password" + + Helper.password + .compare(old || "", client.config.password) + .then(matching => { + if (!matching) { + socket.emit("change-password", { + error: "The current password field does not match your account password" + }); + return; + } + const hash = Helper.password.hash(p1); + + client.setPassword(hash, success => { + const obj = {}; + + if (success) { + obj.success = "Successfully updated your password, all your other sessions were logged out"; + obj.token = client.config.token; + } else { + obj.error = "Failed to update your password"; + } + + socket.emit("change-password", obj); + }); + }).catch(error => { + log.error(`Error while checking users password. Error: ${error}`); }); - return; - } - - var hash = Helper.password.hash(p1); - - client.setPassword(hash, function(success) { - var obj = {}; - - if (success) { - obj.success = "Successfully updated your password, all your other sessions were logged out"; - obj.token = client.config.token; - } else { - obj.error = "Failed to update your password"; - } - - socket.emit("change-password", obj); - }); } ); } @@ -267,19 +273,22 @@ function localAuth(client, user, password, callback) { return callback(false); } - var result = Helper.password.compare(password, client.config.password); + Helper.password + .compare(password, client.config.password) + .then(matching => { + if (Helper.password.requiresUpdate(client.config.password)) { + const hash = Helper.password.hash(password); - if (result && Helper.password.requiresUpdate(client.config.password)) { - var hash = Helper.password.hash(password); - - client.setPassword(hash, function(success) { - if (success) { - log.info(`User ${colors.bold(client.name)} logged in and their hashed password has been updated to match new security requirements`); + client.setPassword(hash, success => { + if (success) { + log.info(`User ${colors.bold(client.name)} logged in and their hashed password has been updated to match new security requirements`); + } + }); } + callback(matching); + }).catch(error => { + log.error(`Error while checking users password. Error: ${error}`); }); - } - - return callback(result); } function ldapAuth(client, user, password, callback) { diff --git a/test/tests/passwords.js b/test/tests/passwords.js index d074477d..48d45c9d 100644 --- a/test/tests/passwords.js +++ b/test/tests/passwords.js @@ -10,14 +10,27 @@ describe("Client passwords", function() { // Generated with third party tool to test implementation let comparedPassword = Helper.password.compare(inputPassword, "$2a$11$zrPPcfZ091WNfs6QrRHtQeUitlgrJcecfZhxOFiQs0FWw7TN3Q1oS"); - expect(comparedPassword).to.be.true; + return comparedPassword.then(result => { + expect(result).to.be.true; + }); + }); + + it("wrong hashed password should not match", function() { + // Compare against a fake hash + let comparedPassword = Helper.password.compare(inputPassword, "$2a$11$zrPPcfZ091WRONGPASSWORDitlgrJcecfZhxOFiQs0FWw7TN3Q1oS"); + + return comparedPassword.then(result => { + expect(result).to.be.false; + }); }); it("freshly hashed password should match", function() { let hashedPassword = Helper.password.hash(inputPassword); let comparedPassword = Helper.password.compare(inputPassword, hashedPassword); - expect(comparedPassword).to.be.true; + return comparedPassword.then((result) => { + expect(result).to.be.true; + }); }); it("shout passwords should be marked as old", function() { From 110c0f0c87cba3e4d44a62dc54ce1d55e69fab01 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 1 Apr 2017 11:06:01 +0300 Subject: [PATCH 031/355] Correctly append date marker when receiving a message --- client/js/lounge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 43775dc2..fa3ec21d 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -432,7 +432,7 @@ $(function() { } if (prevMsgTime.toDateString() !== msgTime.toDateString()) { - prevMsg.append(templates.date_marker({msgDate: msgTime})); + prevMsg.after(templates.date_marker({msgDate: msgTime})); } // Add message to the container From 4d592a6a4094f1df82c67980f0490b43b93ac364 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 1 Apr 2017 16:14:00 +0000 Subject: [PATCH 032/355] chore(package): update stylelint to version 7.10.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63f68dce..5b5b9d20 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "npm-run-all": "4.0.2", "nyc": "10.2.0", "socket.io-client": "1.7.3", - "stylelint": "7.9.0", + "stylelint": "7.10.0", "urijs": "1.18.10", "webpack": "2.3.2" } From c0e364e1c2213b668381f06c67ad474d9d7ed1e1 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 1 Apr 2017 11:33:17 +0300 Subject: [PATCH 033/355] Store channel keys --- src/client.js | 3 +- src/models/chan.js | 1 + src/models/network.js | 3 +- src/plugins/irc-events/connection.js | 2 +- src/plugins/irc-events/join.js | 3 ++ src/plugins/irc-events/mode.js | 70 ++++++++++++++++++++-------- test/models/network.js | 12 +++-- 7 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/client.js b/src/client.js index 00f3eb35..27387778 100644 --- a/src/client.js +++ b/src/client.js @@ -159,7 +159,8 @@ Client.prototype.connect = function(args) { } channels.push(new Chan({ - name: chan.name + name: chan.name, + key: chan.key || "", })); }); diff --git a/src/models/chan.js b/src/models/chan.js index 69e582ab..1448be06 100644 --- a/src/models/chan.js +++ b/src/models/chan.js @@ -19,6 +19,7 @@ function Chan(attr) { id: id++, messages: [], name: "", + key: "", topic: "", type: Chan.Type.CHANNEL, firstUnread: 0, diff --git a/src/models/network.js b/src/models/network.js index 4484ed96..f5576133 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -87,7 +87,8 @@ Network.prototype.export = function() { }) .map(function(chan) { return _.pick(chan, [ - "name" + "name", + "key", ]); }); diff --git a/src/plugins/irc-events/connection.js b/src/plugins/irc-events/connection.js index 4e34a154..47cb331c 100644 --- a/src/plugins/irc-events/connection.js +++ b/src/plugins/irc-events/connection.js @@ -47,7 +47,7 @@ module.exports = function(irc, network) { } setTimeout(function() { - network.irc.join(chan.name); + network.irc.join(chan.name, chan.key); }, delay); delay += 1000; }); diff --git a/src/plugins/irc-events/join.js b/src/plugins/irc-events/join.js index c7f91ec9..1ad88e6f 100644 --- a/src/plugins/irc-events/join.js +++ b/src/plugins/irc-events/join.js @@ -18,6 +18,9 @@ module.exports = function(irc, network) { network: network.id, chan: chan }); + + // Request channels' modes + network.irc.raw("MODE", chan.name); } chan.users.push(new User({nick: data.nick})); chan.sortUsers(irc); diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.js index b47985c9..44db8a73 100644 --- a/src/plugins/irc-events/mode.js +++ b/src/plugins/irc-events/mode.js @@ -1,13 +1,40 @@ "use strict"; -var _ = require("lodash"); -var Chan = require("../../models/chan"); -var Msg = require("../../models/msg"); +const _ = require("lodash"); +const Chan = require("../../models/chan"); +const Msg = require("../../models/msg"); module.exports = function(irc, network) { - var client = this; + const client = this; + + // The following saves the channel key based on channel mode instead of + // extracting it from `/join #channel key`. This lets us not have to + // temporarily store the key until successful join, but also saves the key + // if a key is set or changed while being on the channel. + irc.on("channel info", function(data) { + if (!data.modes) { + return; + } + + const targetChan = network.getChannel(data.channel); + if (typeof targetChan === "undefined") { + return; + } + + data.modes.forEach(mode => { + const text = mode.mode; + const add = text[0] === "+"; + const char = text[1]; + + if (char === "k") { + targetChan.key = add ? mode.param : ""; + client.save(); + } + }); + }); + irc.on("mode", function(data) { - var targetChan; + let targetChan; if (data.target === irc.user.nick) { targetChan = network.channels[0]; @@ -18,23 +45,29 @@ module.exports = function(irc, network) { } } - var usersUpdated; - var supportsMultiPrefix = network.irc.network.cap.isEnabled("multi-prefix"); - var userModeSortPriority = {}; + let usersUpdated; + let userModeSortPriority = {}; + const supportsMultiPrefix = network.irc.network.cap.isEnabled("multi-prefix"); irc.network.options.PREFIX.forEach((prefix, index) => { userModeSortPriority[prefix.symbol] = index; }); - for (var i = 0; i < data.modes.length; i++) { - var mode = data.modes[i]; - var text = mode.mode; + data.modes.forEach(mode => { + let text = mode.mode; + const add = text[0] === "+"; + const char = text[1]; + + if (char === "k") { + targetChan.key = add ? mode.param : ""; + client.save(); + } if (mode.param) { text += " " + mode.param; } - var msg = new Msg({ + const msg = new Msg({ time: data.time, type: Msg.Type.MODE, mode: (targetChan.type !== Chan.Type.LOBBY && targetChan.getMode(data.nick)) || "", @@ -45,22 +78,21 @@ module.exports = function(irc, network) { targetChan.pushMessage(client, msg); if (!mode.param) { - continue; + return; } - var user = _.find(targetChan.users, {name: mode.param}); + const user = _.find(targetChan.users, {name: mode.param}); if (!user) { - continue; + return; } usersUpdated = true; if (!supportsMultiPrefix) { - continue; + return; } - var add = mode.mode[0] === "+"; - var changedMode = network.prefixLookup[mode.mode[1]]; + const changedMode = network.prefixLookup[char]; if (!add) { _.pull(user.modes, changedMode); @@ -73,7 +105,7 @@ module.exports = function(irc, network) { // TODO: remove in future user.mode = (user.modes && user.modes[0]) || ""; - } + }); if (!usersUpdated) { return; diff --git a/test/models/network.js b/test/models/network.js index a5ebb5e4..1727580c 100644 --- a/test/models/network.js +++ b/test/models/network.js @@ -13,8 +13,10 @@ describe("Network", function() { awayMessage: "I am away", name: "networkName", channels: [ - new Chan({name: "#thelounge"}), - new Chan({name: "&foobar"}), + new Chan({name: "#thelounge", key: ""}), + new Chan({name: "&foobar", key: ""}), + new Chan({name: "#secret", key: "foo"}), + new Chan({name: "&secure", key: "bar"}), new Chan({name: "Channel List", type: Chan.Type.SPECIAL}), new Chan({name: "PrivateChat", type: Chan.Type.QUERY}), ] @@ -35,8 +37,10 @@ describe("Network", function() { ip: null, hostname: null, channels: [ - {name: "#thelounge"}, - {name: "&foobar"}, + {name: "#thelounge", key: ""}, + {name: "&foobar", key: ""}, + {name: "#secret", key: "foo"}, + {name: "&secure", key: "bar"}, ] }); }); From 2d9aa35c063e6c5a4e8241eee3630b82f86f026a Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 11 Mar 2017 12:41:46 +0200 Subject: [PATCH 034/355] Implement pgup/pgdown keys --- client/js/lounge.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/client/js/lounge.js b/client/js/lounge.js index fa3ec21d..a7a67796 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1301,6 +1301,33 @@ $(function() { }); (function HotkeysScope() { + Mousetrap.bind([ + "pageup", + "pagedown" + ], function(e, key) { + let container = windows.find(".window.active"); + + // Chat windows scroll message container + if (container.attr("id") === "chat-container") { + container = container.find(".chan.active .chat"); + } + + const offset = container.get(0).clientHeight * 0.94; + let scrollTop = container.scrollTop(); + + if (key === "pageup") { + scrollTop -= offset; + } else { + scrollTop += offset; + } + + container.stop().animate({ + scrollTop: scrollTop + }, 200); + + return false; + }); + Mousetrap.bind([ "command+up", "command+down", From 332047c0dc4cdcb26532b23c43f17421a3f2eeb9 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 2 Apr 2017 10:54:48 +0000 Subject: [PATCH 035/355] chore(package): update stylelint to version 7.10.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b5b9d20..251c4aa4 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "npm-run-all": "4.0.2", "nyc": "10.2.0", "socket.io-client": "1.7.3", - "stylelint": "7.10.0", + "stylelint": "7.10.1", "urijs": "1.18.10", "webpack": "2.3.2" } From 855092aa44c46c3a9306971003d538032c797150 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 2 Apr 2017 19:46:29 +0000 Subject: [PATCH 036/355] chore(package): update mousetrap to version 1.6.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 251c4aa4..6990a606 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "jquery": "3.2.1", "jquery-ui": "1.12.1", "mocha": "3.2.0", - "mousetrap": "1.6.0", + "mousetrap": "1.6.1", "npm-run-all": "4.0.2", "nyc": "10.2.0", "socket.io-client": "1.7.3", From 09eaf80f8c824f16999afd3a88f10cf0baa37189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Sun, 2 Apr 2017 21:03:01 -0400 Subject: [PATCH 037/355] Fix page scroll glitch --- client/js/lounge.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index a7a67796..00ab4f02 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -808,7 +808,7 @@ $(function() { var input = $("#input") .history() - .on("input keyup", function() { + .on("input", function() { var style = window.getComputedStyle(this); // Start by resetting height before computing as scrollHeight does not @@ -1312,13 +1312,13 @@ $(function() { container = container.find(".chan.active .chat"); } - const offset = container.get(0).clientHeight * 0.94; + const offset = container.get(0).clientHeight * 0.9; let scrollTop = container.scrollTop(); if (key === "pageup") { - scrollTop -= offset; + scrollTop = Math.floor(scrollTop - offset); } else { - scrollTop += offset; + scrollTop = Math.ceil(scrollTop + offset); } container.stop().animate({ From e1ff04174f7ec129118e858583ab2ae27d6a1df5 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 3 Apr 2017 08:03:32 +0000 Subject: [PATCH 038/355] chore(package): update webpack to version 2.3.3 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6990a606..e16a0b8a 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "socket.io-client": "1.7.3", "stylelint": "7.10.1", "urijs": "1.18.10", - "webpack": "2.3.2" + "webpack": "2.3.3" } } From 6a273d825aed3b4fdc7dbb9461c92ed7479ad672 Mon Sep 17 00:00:00 2001 From: Maxime Poulin Date: Fri, 22 Jul 2016 23:01:55 -0400 Subject: [PATCH 039/355] Improve inline previews for links and images --- client/css/style.css | 21 +++++++++++++++++---- client/views/toggle.tpl | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index fc111802..7f46637c 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1105,6 +1105,7 @@ kbd { max-width: 100%; padding: 6px 8px; margin-top: 2px; + overflow: hidden; } #chat .toggle-content a { @@ -1113,18 +1114,23 @@ kbd { #chat .toggle-content img { max-width: 100%; - max-height: 250px; + max-height: 128px; display: block; margin: 2px 0; } #chat .toggle-content .thumb { - max-height: 110px; - max-width: 210px; + float: left; + margin-right: 10px; + max-width: 48px; + max-height: 36px; } #chat .toggle-content .head { font-weight: bold; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; } #chat .toggle-content .body { @@ -1132,10 +1138,17 @@ kbd { max-width: 460px; word-break: normal; word-wrap: break-word; + overflow: hidden; + max-height: 30px; + text-overflow: ellipsis; } #chat .toggle-content.show { - display: inline-block !important; + display: block; +} + +#chat .toggle-content.toggle-type-image.show { + display: inline; } #chat .count { diff --git a/client/views/toggle.tpl b/client/views/toggle.tpl index 1c51c5f5..2c8f84eb 100644 --- a/client/views/toggle.tpl +++ b/client/views/toggle.tpl @@ -1,5 +1,5 @@ {{#toggle}} -
+
{{#equal type "image"}} From d842517c4ec2f11e157feb77c3feb855ebda695d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 4 Apr 2017 02:09:53 -0400 Subject: [PATCH 040/355] Fix image preview talking full width Bootstrap was taking over these declarations because they use `!important`. --- client/css/style.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 7f46637c..acbcd8df 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1144,11 +1144,7 @@ kbd { } #chat .toggle-content.show { - display: block; -} - -#chat .toggle-content.toggle-type-image.show { - display: inline; + display: inline-block !important; } #chat .count { From dce42df05031185f0b7d13fac2cb4f9dc520c08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 4 Apr 2017 02:30:16 -0400 Subject: [PATCH 041/355] Fix link preview title going underneath the user list Also fix the preview description not respecting the ellipsis, and update the image size and margin to nicely align with text. --- client/css/style.css | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index acbcd8df..f32261e4 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -933,6 +933,7 @@ kbd { #chat .text { flex: 1 1 auto; + overflow: hidden; } #loading a, @@ -1103,7 +1104,7 @@ kbd { color: #222; font-size: 12px; max-width: 100%; - padding: 6px 8px; + padding: 6px; margin-top: 2px; overflow: hidden; } @@ -1116,31 +1117,28 @@ kbd { max-width: 100%; max-height: 128px; display: block; - margin: 2px 0; } #chat .toggle-content .thumb { float: left; - margin-right: 10px; + margin-right: 6px; max-width: 48px; - max-height: 36px; + max-height: 32px; } -#chat .toggle-content .head { - font-weight: bold; +#chat .toggle-content .head, +#chat .toggle-content .body { white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } +#chat .toggle-content .head { + font-weight: bold; +} + #chat .toggle-content .body { color: #999; - max-width: 460px; - word-break: normal; - word-wrap: break-word; - overflow: hidden; - max-height: 30px; - text-overflow: ellipsis; } #chat .toggle-content.show { From c066f25b171eb7fe2e6a02b045c968e3095f70af Mon Sep 17 00:00:00 2001 From: Awal Garg Date: Thu, 6 Apr 2017 00:45:28 +0530 Subject: [PATCH 042/355] fix: count only message items for show-more the `messages` div contains a `date-marker` div and an `unread-marker` div. this causes the `count` variable to be 2 more than the expected value, which makes the show-more button skip two messages when loading history. this change filters the counted elements to fix this issue. --- client/js/lounge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index fa3ec21d..3edd49dc 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1189,7 +1189,7 @@ $(function() { chat.on("click", ".show-more-button", function() { var self = $(this); - var count = self.parent().next(".messages").children().length; + var count = self.parent().next(".messages").children(".msg").length; socket.emit("more", { target: self.data("id"), count: count From fe7c570cc9f8ac283618e8f5743c0d72dad6325b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Thu, 6 Apr 2017 02:25:43 -0400 Subject: [PATCH 043/355] Use Referrer-Policy header instead of CSP referrer According to MDN: > referrer > Used to specify information in the referer (sic) header for links away from a page. > Use the Referrer-Policy header instead. See: - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/referrer - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy --- src/server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server.js b/src/server.js index 28b35436..846ff78b 100644 --- a/src/server.js +++ b/src/server.js @@ -131,7 +131,8 @@ function index(req, res, next) { return css.slice(0, -4); }); var template = _.template(file); - res.setHeader("Content-Security-Policy", "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'self'; object-src 'none'; form-action 'none'; referrer no-referrer;"); + res.setHeader("Content-Security-Policy", "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'self'; object-src 'none'; form-action 'none';"); + res.setHeader("Referrer-Policy", "no-referrer"); res.setHeader("Content-Type", "text/html"); res.writeHead(200); res.end(template(data)); From 8744d754ff17db520e61d37915cd8bfb57d6ec59 Mon Sep 17 00:00:00 2001 From: Michael van Tricht Date: Thu, 6 Apr 2017 16:45:01 +0200 Subject: [PATCH 044/355] Fix Zenburn and Morning channel list font color. --- client/themes/morning.css | 4 ++++ client/themes/zenburn.css | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/client/themes/morning.css b/client/themes/morning.css index 49750ecd..9698330c 100644 --- a/client/themes/morning.css +++ b/client/themes/morning.css @@ -212,6 +212,10 @@ body { color: #84ce88 !important; } +#chat table.channel-list td { + color: #ccc; +} + /* Embeds */ #chat .toggle-content, #chat .toggle-button { diff --git a/client/themes/zenburn.css b/client/themes/zenburn.css index a5969370..2c075412 100644 --- a/client/themes/zenburn.css +++ b/client/themes/zenburn.css @@ -238,6 +238,10 @@ body { color: #8cd0d3 !important; } +#chat table.channel-list td { + color: #ccc; +} + /* Embeds */ #chat .toggle-content, #chat .toggle-button { From ba2aa7a852d696b8ca4a86c636ff3f1111156e01 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 7 Apr 2017 15:55:35 +0000 Subject: [PATCH 045/355] chore(package): update babel-preset-es2015 to version 6.24.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e16a0b8a..57e10cca 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "devDependencies": { "babel-core": "6.24.0", "babel-loader": "6.4.1", - "babel-preset-es2015": "6.24.0", + "babel-preset-es2015": "6.24.1", "chai": "3.5.0", "eslint": "3.19.0", "font-awesome": "4.7.0", From 7c5f63131944f8c633f402fff75fea34cd52bd25 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 7 Apr 2017 17:11:52 +0000 Subject: [PATCH 046/355] chore(package): update babel-core to version 6.24.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e16a0b8a..c0ed6045 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "spdy": "3.4.4" }, "devDependencies": { - "babel-core": "6.24.0", + "babel-core": "6.24.1", "babel-loader": "6.4.1", "babel-preset-es2015": "6.24.0", "chai": "3.5.0", From bcbd29cd22efa98d7b31bea8040ba39afafc12dd Mon Sep 17 00:00:00 2001 From: Michael van Tricht Date: Thu, 6 Apr 2017 13:09:55 +0200 Subject: [PATCH 047/355] Unread marker takes hidden messages into account. --- client/js/lounge.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index fcc06179..3bd2e50f 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -443,7 +443,8 @@ $(function() { data ]); - if (data.msg.self) { + if (data.msg.self + || container.find("div:visible").last().hasClass("unread-marker")) { container .find(".unread-marker") .appendTo(container); From c0a7ae9d92050d80f8a8575bc015af62c426372e Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 19 Dec 2016 16:59:06 +0200 Subject: [PATCH 048/355] Use css tooltips on time elements --- client/css/style.css | 1 - client/views/msg.tpl | 2 +- client/views/msg_action.tpl | 2 +- client/views/msg_unhandled.tpl | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index f32261e4..f96771ed 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -839,7 +839,6 @@ kbd { word-wrap: break-word; word-break: break-word; /* Webkit-specific */ display: flex; - overflow: hidden; position: relative; } diff --git a/client/views/msg.tpl b/client/views/msg.tpl index 36dc4c27..2fa5f930 100644 --- a/client/views/msg.tpl +++ b/client/views/msg.tpl @@ -1,5 +1,5 @@
- + {{tz time}} diff --git a/client/views/msg_action.tpl b/client/views/msg_action.tpl index c2e9657c..02187a3f 100644 --- a/client/views/msg_action.tpl +++ b/client/views/msg_action.tpl @@ -1,5 +1,5 @@
- + {{tz time}} diff --git a/client/views/msg_unhandled.tpl b/client/views/msg_unhandled.tpl index 9c30f3d3..1a6fcfd7 100644 --- a/client/views/msg_unhandled.tpl +++ b/client/views/msg_unhandled.tpl @@ -1,5 +1,5 @@
- + {{tz time}} [{{command}}] From 5d36b29aa8949b632ceacdac86a6eb1f06bfd277 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Mon, 19 Dec 2016 16:59:19 +0200 Subject: [PATCH 049/355] Only disable touch tooltips on certain buttons --- client/css/style.css | 8 ++------ client/index.html | 16 ++++++++-------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index f96771ed..a7894265 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1763,12 +1763,8 @@ kbd { * - https://www.w3.org/TR/mediaqueries-4/ * - https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover */ - .tooltipped:hover:before, - .tooltipped:hover:after, - .tooltipped:active:before, - .tooltipped:active:after, - .tooltipped:focus:before, - .tooltipped:focus:after { + .tooltipped-no-touch:hover:before, + .tooltipped-no-touch:hover:after { visibility: hidden; opacity: 0; } diff --git a/client/index.html b/client/index.html index f1e3641a..9f6d64d6 100644 --- a/client/index.html +++ b/client/index.html @@ -34,11 +34,11 @@
- - - - - + + + + +
@@ -68,10 +68,10 @@ --> - + - +
@@ -523,7 +523,7 @@

Commands

- +

All commands can be autocompleted with tab.

From 5b721c1b9997e4db87de8999bd781316a8317e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 4 Apr 2017 01:27:13 -0400 Subject: [PATCH 050/355] Update Primer tooltips to latest v0.5.3 This: - Makes tooltips appear after timer instead of instantly, necessary for timestamp tooltips (see https://github.com/thelounge/lounge/pull/824#pullrequestreview-13676231) - Uses Primer default animation (not sure if .2s transition was ours or theirs but here it is) - Goes closer to default tooltips which will help to bump future versions and/or to streamline this in build process --- client/css/style.css | 61 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index a7894265..89a90bfc 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1519,9 +1519,10 @@ kbd { } /** - * Tooltips + * Tooltips v0.5.3 * See http://primercss.io/tooltips/ */ + .tooltipped { position: relative; } @@ -1529,12 +1530,11 @@ kbd { .tooltipped:after { position: absolute; z-index: 1000000; - display: inline-block; - visibility: hidden; - opacity: 0; + display: none; padding: 5px 8px; font: 12px Lato; line-height: 1.2; + -webkit-font-smoothing: subpixel-antialiased; color: #fff; text-align: center; text-decoration: none; @@ -1547,23 +1547,40 @@ kbd { content: attr(aria-label); background: #222; border-radius: 3px; - -webkit-font-smoothing: subpixel-antialiased; - transition: .2s; + opacity: 0; } .tooltipped:before { position: absolute; z-index: 1000001; - display: inline-block; - visibility: hidden; - opacity: 0; + display: none; width: 0; height: 0; color: #fff; pointer-events: none; content: ""; border: 5px solid transparent; - transition: .2s; + opacity: 0; +} + +@-webkit-keyframes tooltip-appear { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes tooltip-appear { + from { + opacity: 0; + } + + to { + opacity: 1; + } } .tooltipped:hover:before, @@ -1572,9 +1589,18 @@ kbd { .tooltipped:active:after, .tooltipped:focus:before, .tooltipped:focus:after { - visibility: visible; - opacity: 1; + display: inline-block; text-decoration: none; + -webkit-animation-name: tooltip-appear; + animation-name: tooltip-appear; + -webkit-animation-duration: .1s; + animation-duration: .1s; + -webkit-animation-fill-mode: forwards; + animation-fill-mode: forwards; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + -webkit-animation-delay: .4s; + animation-delay: .4s; } .tooltipped-s:after, @@ -1671,6 +1697,17 @@ kbd { border-right-color: #222; } +@media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (min--moz-device-pixel-ratio: 2), + only screen and (-moz-min-device-pixel-ratio: 2), + only screen and (min-device-pixel-ratio: 2), + only screen and (min-resolution: 192dpi), + only screen and (min-resolution: 2dppx) { + .tooltipped-w:after { + margin-right: 4.5px; + } +} + /* End tooltips */ /** From b7d353b6200f7f9841779da5ba4b1bd20c3e2cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Thu, 13 Apr 2017 02:30:36 -0400 Subject: [PATCH 051/355] Remove invalid CSS perspective properties These are not valid without units per the CSS validator, which is confirmed in the Chrome dev tools. I could not trigger any consequences by removing these. --- client/css/style.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 89a90bfc..64da0489 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -310,8 +310,6 @@ kbd { transition: transform 160ms, -webkit-transform 160ms; -webkit-transform: translateZ(0); transform: translateZ(0); - -webkit-perspective: 1000; - perspective: 1000; } #viewport.menu-open { @@ -791,8 +789,6 @@ kbd { transition: all .4s; -webkit-transform: translateZ(0); transform: translateZ(0); - -webkit-perspective: 1000; - perspective: 1000; } #chat .lobby .chat, From f645c32cb944fb30c22e9e69fc9000ee625139b9 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 14 Apr 2017 00:05:28 +0300 Subject: [PATCH 052/355] Use local variables to check length --- src/server.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server.js b/src/server.js index 846ff78b..42c3461a 100644 --- a/src/server.js +++ b/src/server.js @@ -42,17 +42,20 @@ module.exports = function() { server = require("http"); server = server.createServer(app); } else { - server = require("spdy"); const keyPath = Helper.expandHome(config.https.key); const certPath = Helper.expandHome(config.https.certificate); - if (!config.https.key.length || !fs.existsSync(keyPath)) { + + if (!keyPath.length || !fs.existsSync(keyPath)) { log.error("Path to SSL key is invalid. Stopping server..."); process.exit(); } - if (!config.https.certificate.length || !fs.existsSync(certPath)) { + + if (!certPath.length || !fs.existsSync(certPath)) { log.error("Path to SSL certificate is invalid. Stopping server..."); process.exit(); } + + server = require("spdy"); server = server.createServer({ key: fs.readFileSync(keyPath), cert: fs.readFileSync(certPath) From fce3d11e7497a39cf9ce63879ed1b3ad0e16c34e Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 14 Apr 2017 21:29:04 +0300 Subject: [PATCH 053/355] Stick to bottom when opening user list Fixes #1031 --- client/js/lounge.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index fcc06179..83d7441b 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -729,6 +729,7 @@ $(function() { var self = $(this); viewport.toggleClass(self.attr("class")); e.stopPropagation(); + chat.find(".chan.active .chat").trigger("msg.sticky"); }); function positionContextMenu(that, e) { @@ -822,7 +823,7 @@ $(function() { + Math.round(parseFloat(style.borderBottomWidth) || 0) ) + "px"; - $("#chat .chan.active .chat").trigger("msg.sticky"); // fix growing + chat.find(".chan.active .chat").trigger("msg.sticky"); // fix growing }) .tab(complete, {hint: false}); From 507bf05d2410802acdedcc1834e278ef1cfb41da Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 14 Apr 2017 21:10:05 +0300 Subject: [PATCH 054/355] Remove referrer meta tag, we send Referrer-Policy header --- client/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/client/index.html b/client/index.html index 9f6d64d6..967bef53 100644 --- a/client/index.html +++ b/client/index.html @@ -8,7 +8,6 @@ - The Lounge From 8020c3c81713e93a8d16fb4b24c772de9024e383 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 14 Apr 2017 21:10:17 +0300 Subject: [PATCH 055/355] Preload scripts as soon as possible --- client/index.html | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/client/index.html b/client/index.html index 967bef53..9d4e58a2 100644 --- a/client/index.html +++ b/client/index.html @@ -4,23 +4,26 @@ - - - - - - - The Lounge + + + The Lounge + + + + + + + "> From 1e2e8a82dbaff79c26865728a286dda451d4941b Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 14 Apr 2017 21:36:41 +0300 Subject: [PATCH 056/355] Add rel noopener to URLs in index.html --- client/index.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/index.html b/client/index.html index 9f6d64d6..5502e712 100644 --- a/client/index.html +++ b/client/index.html @@ -49,7 +49,7 @@

The Lounge is loading…

-

Loading the app… Make sure to have JavaScript enabled.

+

Loading the app… Make sure to have JavaScript enabled.

This is taking longer than it should, there might be connectivity issues.

@@ -402,7 +402,7 @@

A color reference can be found - here. + here.

@@ -478,7 +478,7 @@

A color reference can be found - here. + here.

@@ -575,7 +575,7 @@

Send a CTCP request. Read more about this on - the dedicated Wikipedia article. + the dedicated Wikipedia article.

@@ -809,15 +809,15 @@

<% if (gitCommit) { %> The Lounge is running from source - (<%= gitCommit %>).
+ (<%= gitCommit %>).
<% } else { %> The Lounge is in version <%= version %> - (See release notes).
+ (See release notes).
<% } %> - Website
- Documentation
- Report a bug + Website
+ Documentation
+ Report a bug

From d093a7f4c2955eb47bf02aeaab8f701753e34984 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 15 Apr 2017 18:40:19 +0300 Subject: [PATCH 057/355] Reset notification markers on document focus Fixes #837 --- client/js/lounge.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 83d7441b..ecbe493d 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1595,14 +1595,11 @@ $(function() { $("#viewport .lt").toggleClass("notified", newState); } - document.addEventListener( - "visibilitychange", - function() { - if (sidebar.find(".highlight").length === 0) { - toggleNotificationMarkers(false); - } + $(document).on("visibilitychange focus", () => { + if (sidebar.find(".highlight").length === 0) { + toggleNotificationMarkers(false); } - ); + }); // Only start opening socket.io connection after all events have been registered socket.open(); From fa51a2c281d60464f1642c1f6e05ee921f069e08 Mon Sep 17 00:00:00 2001 From: Metsjeesus Date: Mon, 10 Apr 2017 18:49:58 +0000 Subject: [PATCH 058/355] Add CA bundle option in SSL --- defaults/config.js | 11 ++++++++++- src/server.js | 9 ++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/defaults/config.js b/defaults/config.js index 98f4876c..30a66bd9 100644 --- a/defaults/config.js +++ b/defaults/config.js @@ -287,7 +287,16 @@ module.exports = { // @example "sslcert/key-cert.pem" // @default "" // - certificate: "" + certificate: "", + + // + // Path to the CA bundle. + // + // @type string + // @example "sslcert/bundle.pem" + // @default "" + // + ca: "" }, // diff --git a/src/server.js b/src/server.js index 42c3461a..0ff97b76 100644 --- a/src/server.js +++ b/src/server.js @@ -44,6 +44,7 @@ module.exports = function() { } else { const keyPath = Helper.expandHome(config.https.key); const certPath = Helper.expandHome(config.https.certificate); + const caPath = Helper.expandHome(config.https.ca); if (!keyPath.length || !fs.existsSync(keyPath)) { log.error("Path to SSL key is invalid. Stopping server..."); @@ -55,10 +56,16 @@ module.exports = function() { process.exit(); } + if (caPath.length && !fs.existsSync(caPath)) { + log.error("Path to SSL ca bundle is invalid. Stopping server..."); + process.exit(); + } + server = require("spdy"); server = server.createServer({ key: fs.readFileSync(keyPath), - cert: fs.readFileSync(certPath) + cert: fs.readFileSync(certPath), + ca: caPath ? fs.readFileSync(caPath) : undefined }, app); } From cc0962ba12ece0454dc8a768f74143f2136003e1 Mon Sep 17 00:00:00 2001 From: William Boman Date: Sat, 28 Jan 2017 18:48:34 +0100 Subject: [PATCH 059/355] client: implement states using the History Web API --- client/js/lounge.js | 55 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index ecbe493d..e10c9d73 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -155,7 +155,9 @@ $(function() { return; } sidebar.find(".sign-in") - .click() + .trigger("click", { + pushState: false, + }) .end() .find(".networks") .html("") @@ -197,7 +199,9 @@ $(function() { $("#loading-page-message").text("Rendering…"); if (data.networks.length === 0) { - $("#footer").find(".connect").trigger("click"); + $("#footer").find(".connect").trigger("click", { + pushState: false, + }); } else { renderNetworks(data); } @@ -219,7 +223,9 @@ $(function() { .eq(0) .trigger("click"); if (first.length === 0) { - $("#footer").find(".connect").trigger("click"); + $("#footer").find(".connect").trigger("click", { + pushState: false, + }); } } }); @@ -986,6 +992,33 @@ $(function() { }); }); + sidebar.on("click", ".chan, button", function(e, data) { + // Pushes states to history web API when clicking elements with a data-target attribute. + // States are very trivial and only contain a single `clickTarget` property which + // contains a CSS selector that targets elements which takes the user to a different view + // when clicked. The `popstate` event listener will trigger synthetic click events using that + // selector and thus take the user to a different view/state. + if (data && data.pushState === false) { + return; + } + const self = $(this); + const target = self.data("target"); + if (!target) { + return; + } + const state = {}; + + if (self.hasClass("chan")) { + state.clickTarget = `.chan[data-id="${self.data("id")}"]`; + } else { + state.clickTarget = `#footer button[data-target="${target}"]`; + } + + if (history && history.pushState) { + history.pushState(state, null, null); + } + }); + sidebar.on("click", ".chan, button", function() { var self = $(this); var target = self.data("target"); @@ -1603,4 +1636,20 @@ $(function() { // Only start opening socket.io connection after all events have been registered socket.open(); + + window.addEventListener( + "popstate", + (e) => { + const {state} = e; + if (!state) { + return; + } + const {clickTarget} = state; + if (clickTarget) { + $(clickTarget).trigger("click", { + pushState: false + }); + } + } + ); }); From 5c336d3789d9228758fde9ce1936d2263de3d4ca Mon Sep 17 00:00:00 2001 From: Daniel Oaks Date: Sun, 16 Apr 2017 12:31:32 +0300 Subject: [PATCH 060/355] Add slug with command to unhandled messages --- client/js/libs/handlebars/slugify.js | 5 +++++ client/views/chan.tpl | 2 +- client/views/msg_unhandled.tpl | 2 +- client/views/network.tpl | 2 +- src/models/network.js | 1 + src/plugins/irc-events/connection.js | 3 ++- 6 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 client/js/libs/handlebars/slugify.js diff --git a/client/js/libs/handlebars/slugify.js b/client/js/libs/handlebars/slugify.js new file mode 100644 index 00000000..a8b385e8 --- /dev/null +++ b/client/js/libs/handlebars/slugify.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function(orig) { + return orig.toLowerCase().replace(/[^a-z0-9]/, "-"); +}; diff --git a/client/views/chan.tpl b/client/views/chan.tpl index 626eb228..d9e9d005 100644 --- a/client/views/chan.tpl +++ b/client/views/chan.tpl @@ -1,5 +1,5 @@ {{#each channels}} -
+
{{#if unread}}{{roundBadgeNumber unread}}{{/if}} {{name}} diff --git a/client/views/msg_unhandled.tpl b/client/views/msg_unhandled.tpl index 1a6fcfd7..69ad3fe5 100644 --- a/client/views/msg_unhandled.tpl +++ b/client/views/msg_unhandled.tpl @@ -1,4 +1,4 @@ -
+
{{tz time}} diff --git a/client/views/network.tpl b/client/views/network.tpl index 9da98019..d525dff2 100644 --- a/client/views/network.tpl +++ b/client/views/network.tpl @@ -1,5 +1,5 @@ {{#each networks}} -
+
{{> chan}}
{{/each}} diff --git a/src/models/network.js b/src/models/network.js index f5576133..4a63d355 100644 --- a/src/models/network.js +++ b/src/models/network.js @@ -25,6 +25,7 @@ function Network(attr) { irc: null, serverOptions: { PREFIX: [], + NETWORK: "", }, chanCache: [], }); diff --git a/src/plugins/irc-events/connection.js b/src/plugins/irc-events/connection.js index 47cb331c..e44b7676 100644 --- a/src/plugins/irc-events/connection.js +++ b/src/plugins/irc-events/connection.js @@ -120,7 +120,7 @@ module.exports = function(irc, network) { }); irc.on("server options", function(data) { - if (network.serverOptions.PREFIX === data.options.PREFIX) { + if (network.serverOptions.PREFIX === data.options.PREFIX && network.serverOptions.NETWORK === data.options.NETWORK) { return; } @@ -131,6 +131,7 @@ module.exports = function(irc, network) { }); network.serverOptions.PREFIX = data.options.PREFIX; + network.serverOptions.NETWORK = data.options.NETWORK; client.emit("network_changed", { network: network.id, From 955aada1cff89cc0cef6fe68501fb6fc722db644 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 14 Apr 2017 12:00:08 +0000 Subject: [PATCH 061/355] chore(package): update webpack to version 2.4.1 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 977d56b9..30154e29 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,6 @@ "socket.io-client": "1.7.3", "stylelint": "7.10.1", "urijs": "1.18.10", - "webpack": "2.3.3" + "webpack": "2.4.1" } } From 4938878d10041c4105793dfd484a0b83a7d8bd98 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 17 Apr 2017 10:35:27 +0100 Subject: [PATCH 062/355] Disable show more button when loading messages --- client/js/lounge.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/js/lounge.js b/client/js/lounge.js index e10c9d73..7fcf5d25 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -505,6 +505,8 @@ $(function() { lastDate = msgDate; }); + + scrollable.find(".show-more").prop("disabled", false); }); socket.on("network", function(data) { @@ -1224,6 +1226,7 @@ $(function() { chat.on("click", ".show-more-button", function() { var self = $(this); var count = self.parent().next(".messages").children(".msg").length; + self.prop("disabled", true); socket.emit("more", { target: self.data("id"), count: count From b750da3f9da42922936b5775acc79be532c06b6d Mon Sep 17 00:00:00 2001 From: Metsjeesus Date: Mon, 17 Apr 2017 22:48:28 +0300 Subject: [PATCH 063/355] Fix to helper.expandhome to correctly resolve "" and undefined --- src/helper.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helper.js b/src/helper.js index 830b91c9..552b11ad 100644 --- a/src/helper.js +++ b/src/helper.js @@ -101,6 +101,9 @@ function ip2hex(address) { } function expandHome(shortenedPath) { + if (!shortenedPath) { + return ""; + } var home; if (os.homedir) { @@ -112,7 +115,6 @@ function expandHome(shortenedPath) { } home = home.replace("$", "$$$$"); - return path.resolve(shortenedPath.replace(/^~($|\/|\\)/, home + "$1")); } From a900abc2a4ead565300bbdfa57c0f29bb1bf3a33 Mon Sep 17 00:00:00 2001 From: Kyle Terrien Date: Tue, 18 Apr 2017 19:48:14 -0700 Subject: [PATCH 064/355] Issue 1019: Show MOTD by default --- client/js/lounge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index e10c9d73..ddc3ad3a 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -605,7 +605,7 @@ $(function() { join: true, links: true, mode: true, - motd: false, + motd: true, nick: true, notification: true, notifyAllMessages: false, From 8aa6f9c500b6e5f1b6fd08fd3f4c5bb08c5aecfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Wed, 19 Apr 2017 01:19:11 -0400 Subject: [PATCH 065/355] Exclude Webpack config from coverage report --- .nycrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.nycrc b/.nycrc index ae33aa14..99b69780 100644 --- a/.nycrc +++ b/.nycrc @@ -3,7 +3,8 @@ "exclude": [ "client/js/bundle.js", "client/js/bundle.vendor.js", - "test/" + "test/", + "webpack.config.js" ], "reporter": [ "lcov", From 764ac831d4cac52bd2cc636f4fd72a5a65f3a9e1 Mon Sep 17 00:00:00 2001 From: Michael van Tricht Date: Thu, 6 Apr 2017 18:22:56 +0200 Subject: [PATCH 066/355] Improve channels list. - Set fixed width to channel and users column. - Sort by number of users in channel. - Executing /list multiple times wont show multiple tables. - Channel list is not stickied to the bottom. - Limit channels to 500. Scrolling through 1k is very slow on my system. --- client/css/style.css | 5 +++++ client/js/lounge.js | 6 +++++- src/plugins/irc-events/list.js | 6 ++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index f32261e4..ee76a268 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1022,6 +1022,10 @@ kbd { border-bottom: #eee 1px solid; } +#chat table.channel-list .channel { + width: 80px; +} + #chat table.channel-list .channel, #chat table.channel-list .topic { text-align: left; @@ -1029,6 +1033,7 @@ kbd { #chat table.channel-list .users { text-align: center; + width: 50px; } #chat table.channel-list td.channel .inline-channel { diff --git a/client/js/lounge.js b/client/js/lounge.js index fcc06179..5439d277 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -421,6 +421,10 @@ $(function() { var target = "#chan-" + data.chan; var container = chat.find(target + " .messages"); + if (data.msg.type === "channel_list") { + $(container).empty(); + } + // Check if date changed var prevMsg = $(container.find(".msg")).last(); var prevMsgTime = new Date(prevMsg.attr("data-time")); @@ -1050,7 +1054,7 @@ $(function() { } var chanChat = chan.find(".chat"); - if (chanChat.length > 0) { + if (chanChat.length > 0 && chan.data("type") !== "special") { chanChat.sticky(); } diff --git a/src/plugins/irc-events/list.js b/src/plugins/irc-events/list.js index 0dbf2881..848f912d 100644 --- a/src/plugins/irc-events/list.js +++ b/src/plugins/irc-events/list.js @@ -5,7 +5,7 @@ var Msg = require("../../models/msg"); module.exports = function(irc, network) { var client = this; - var MAX_CHANS = 1000; + var MAX_CHANS = 500; irc.on("channel list start", function() { network.chanCache = []; @@ -23,7 +23,9 @@ module.exports = function(irc, network) { irc.on("channel list end", function() { updateListStatus(new Msg({ type: "channel_list", - channels: network.chanCache.slice(0, MAX_CHANS) + channels: network.chanCache.sort(function(a, b) { + return b.num_users - a.num_users; + }).slice(0, MAX_CHANS) })); if (network.chanCache.length > MAX_CHANS) { From 5bab511c42be718e2683f69d4bcac8b4c96a18fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 17 Apr 2017 18:29:52 -0400 Subject: [PATCH 067/355] Use HTTPS version to the IRC color guide --- client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/index.html b/client/index.html index 348c5f11..83ac96ad 100644 --- a/client/index.html +++ b/client/index.html @@ -404,7 +404,7 @@

A color reference can be found - here. + here.

@@ -480,7 +480,7 @@

A color reference can be found - here. + here.

From f9de811df169d71342816e5f932b2df5de0af321 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 21 Apr 2017 03:48:20 +0000 Subject: [PATCH 068/355] chore(package): update handlebars-loader to version 1.5.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30154e29..15ed8db8 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "eslint": "3.19.0", "font-awesome": "4.7.0", "handlebars": "4.0.6", - "handlebars-loader": "1.4.0", + "handlebars-loader": "1.5.0", "jquery": "3.2.1", "jquery-ui": "1.12.1", "mocha": "3.2.0", From cc85b2143c197ea033d4c5ff9ac409c5584cadc5 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Thu, 20 Apr 2017 11:17:25 +0100 Subject: [PATCH 069/355] Change index.html to be rendered using handlebars --- client/index.html | 68 +++++++++++-------- .../libs/handlebars/firstLetterUppercase.js | 5 ++ client/js/libs/handlebars/index.js | 18 +++++ client/js/libs/handlebars/ternary.js | 5 ++ package.json | 1 + src/server.js | 40 +++++------ 6 files changed, 85 insertions(+), 52 deletions(-) create mode 100644 client/js/libs/handlebars/firstLetterUppercase.js create mode 100644 client/js/libs/handlebars/index.js create mode 100644 client/js/libs/handlebars/ternary.js diff --git a/client/index.html b/client/index.html index 83ac96ad..0bd055f5 100644 --- a/client/index.html +++ b/client/index.html @@ -9,7 +9,7 @@ - + The Lounge @@ -25,7 +25,7 @@ - "> +
@@ -122,12 +122,17 @@

- <%= public ? "The Lounge - " : "" %> + {{ternary public "The Lounge " " " }} Connect - <%= !displayNetwork && lockNetwork ? "to " + defaults.name : "" %> + {{#unless displayNetwork}} + {{#if lockNetwork}} + to {{defaults.name}} + {{/if}} + {{/unless}}

-
> + {{#unless displayNetwork}} +

Network settings

@@ -135,17 +140,17 @@
- +
- > +
- > +
@@ -153,16 +158,17 @@
- +
+ {{/unless}}

User preferences

@@ -170,27 +176,27 @@
- +
- <% if (!useHexIp) { %> + {{#unless useHexIp}}
- +
- <% } %> + {{/unless}}
- +
- +
@@ -261,14 +267,14 @@
- <% if (typeof prefetch === "undefined" || prefetch !== false) { %> + {{#unless prefetch}}

Links and URLs

@@ -284,7 +290,7 @@ Auto-expand links
- <% } %> + {{/unless}}

Notifications

@@ -328,7 +334,8 @@
- <% if (!public && !ldap.enable) { %> + {{#unless public}} + {{#unless ldap.enable}}
@@ -352,7 +359,8 @@
- <% } %> + {{/unless}} + {{/unless}}

Custom Stylesheet

@@ -809,13 +817,13 @@

About The Lounge

- <% if (gitCommit) { %> + {{#if gitCommit}} The Lounge is running from source - (<%= gitCommit %>).
- <% } else { %> - The Lounge is in version <%= version %> - (See release notes).
- <% } %> + ({{ gitCommit }}).
+ {{else}} + The Lounge is in version {{version}} + (See release notes).
+ {{/if}} Website
Documentation
diff --git a/client/js/libs/handlebars/firstLetterUppercase.js b/client/js/libs/handlebars/firstLetterUppercase.js new file mode 100644 index 00000000..d36bff54 --- /dev/null +++ b/client/js/libs/handlebars/firstLetterUppercase.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +}; diff --git a/client/js/libs/handlebars/index.js b/client/js/libs/handlebars/index.js new file mode 100644 index 00000000..fe485b47 --- /dev/null +++ b/client/js/libs/handlebars/index.js @@ -0,0 +1,18 @@ +"use strict"; + +module.exports = { + colorClass: require("./colorClass"), + diff: require("./diff"), + equal: require("./equal"), + firstLetterUppercase: require("./firstLetterUppercase"), + localedate: require("./localedate"), + localetime: require("./localetime"), + modes: require("./modes"), + parse: require("./parse"), + roundBadgeNumber: require("./roundBadgeNumber"), + slugify: require("./slugify"), + ternary: require("./ternary"), + tojson: require("./tojson"), + tz: require("./tz"), + users: require("./users"), +}; diff --git a/client/js/libs/handlebars/ternary.js b/client/js/libs/handlebars/ternary.js new file mode 100644 index 00000000..4dfa2995 --- /dev/null +++ b/client/js/libs/handlebars/ternary.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = function(test, yes, no) { + return test ? yes : no; +}; diff --git a/package.json b/package.json index 30154e29..3628e2b3 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "commander": "2.9.0", "event-stream": "3.3.4", "express": "4.15.2", + "express-handlebars": "^3.0.0", "fs-extra": "2.1.2", "irc-framework": "2.6.1", "ldapjs": "1.0.1", diff --git a/src/server.js b/src/server.js index 0ff97b76..082ef98f 100644 --- a/src/server.js +++ b/src/server.js @@ -5,7 +5,9 @@ var pkg = require("../package.json"); var Client = require("./client"); var ClientManager = require("./clientManager"); var express = require("express"); +var expressHandlebars = require("express-handlebars"); var fs = require("fs"); +var path = require("path"); var io = require("socket.io"); var dns = require("dns"); var Helper = require("./helper"); @@ -29,7 +31,10 @@ module.exports = function() { var app = express() .use(allRequests) .use(index) - .use(express.static("client")); + .use(express.static("client")) + .engine("html", expressHandlebars({extname: ".html", helpers: require("../client/js/libs/handlebars")})) + .set("view engine", "html") + .set("views", path.join(__dirname, "..", "client")); var config = Helper.config; var server = null; @@ -125,28 +130,19 @@ function index(req, res, next) { return next(); } - return fs.readFile("client/index.html", "utf-8", function(err, file) { - if (err) { - throw err; - } - - var data = _.merge( - pkg, - Helper.config - ); - data.gitCommit = Helper.getGitCommit(); - data.themes = fs.readdirSync("client/themes/").filter(function(themeFile) { - return themeFile.endsWith(".css"); - }).map(function(css) { - return css.slice(0, -4); - }); - var template = _.template(file); - res.setHeader("Content-Security-Policy", "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'self'; object-src 'none'; form-action 'none';"); - res.setHeader("Referrer-Policy", "no-referrer"); - res.setHeader("Content-Type", "text/html"); - res.writeHead(200); - res.end(template(data)); + var data = _.merge( + pkg, + Helper.config + ); + data.gitCommit = Helper.getGitCommit(); + data.themes = fs.readdirSync("client/themes/").filter(function(themeFile) { + return themeFile.endsWith(".css"); + }).map(function(css) { + return css.slice(0, -4); }); + res.setHeader("Content-Security-Policy", "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'self'; object-src 'none'; form-action 'none';"); + res.setHeader("Referrer-Policy", "no-referrer"); + res.render("index", data); } function init(socket, client) { From b4310dbc03734fd23f3d6ae72adee33d85949939 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Fri, 21 Apr 2017 09:26:02 +0100 Subject: [PATCH 070/355] Review changes (Should be squashed before merge) --- client/index.html | 14 +++++++------- .../js/libs/handlebars/firstLetterUppercase.js | 5 ----- client/js/libs/handlebars/index.js | 18 ------------------ client/js/libs/handlebars/ternary.js | 5 ----- package.json | 2 +- src/server.js | 8 ++++++-- 6 files changed, 14 insertions(+), 38 deletions(-) delete mode 100644 client/js/libs/handlebars/firstLetterUppercase.js delete mode 100644 client/js/libs/handlebars/index.js delete mode 100644 client/js/libs/handlebars/ternary.js diff --git a/client/index.html b/client/index.html index 0bd055f5..7d751664 100644 --- a/client/index.html +++ b/client/index.html @@ -25,7 +25,7 @@ - +

@@ -122,7 +122,7 @@

- {{ternary public "The Lounge " " " }} + {{#if public}}The Lounge - {{/if}} Connect {{#unless displayNetwork}} {{#if lockNetwork}} @@ -146,11 +146,11 @@

- +
- +
@@ -162,7 +162,7 @@
@@ -268,8 +268,8 @@ diff --git a/client/js/libs/handlebars/firstLetterUppercase.js b/client/js/libs/handlebars/firstLetterUppercase.js deleted file mode 100644 index d36bff54..00000000 --- a/client/js/libs/handlebars/firstLetterUppercase.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -module.exports = function(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -}; diff --git a/client/js/libs/handlebars/index.js b/client/js/libs/handlebars/index.js deleted file mode 100644 index fe485b47..00000000 --- a/client/js/libs/handlebars/index.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; - -module.exports = { - colorClass: require("./colorClass"), - diff: require("./diff"), - equal: require("./equal"), - firstLetterUppercase: require("./firstLetterUppercase"), - localedate: require("./localedate"), - localetime: require("./localetime"), - modes: require("./modes"), - parse: require("./parse"), - roundBadgeNumber: require("./roundBadgeNumber"), - slugify: require("./slugify"), - ternary: require("./ternary"), - tojson: require("./tojson"), - tz: require("./tz"), - users: require("./users"), -}; diff --git a/client/js/libs/handlebars/ternary.js b/client/js/libs/handlebars/ternary.js deleted file mode 100644 index 4dfa2995..00000000 --- a/client/js/libs/handlebars/ternary.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -module.exports = function(test, yes, no) { - return test ? yes : no; -}; diff --git a/package.json b/package.json index 3628e2b3..98754da7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "commander": "2.9.0", "event-stream": "3.3.4", "express": "4.15.2", - "express-handlebars": "^3.0.0", + "express-handlebars": "3.0.0", "fs-extra": "2.1.2", "irc-framework": "2.6.1", "ldapjs": "1.0.1", diff --git a/src/server.js b/src/server.js index 082ef98f..db92efe7 100644 --- a/src/server.js +++ b/src/server.js @@ -32,7 +32,7 @@ module.exports = function() { .use(allRequests) .use(index) .use(express.static("client")) - .engine("html", expressHandlebars({extname: ".html", helpers: require("../client/js/libs/handlebars")})) + .engine("html", expressHandlebars({extname: ".html"})) .set("view engine", "html") .set("views", path.join(__dirname, "..", "client")); @@ -138,7 +138,11 @@ function index(req, res, next) { data.themes = fs.readdirSync("client/themes/").filter(function(themeFile) { return themeFile.endsWith(".css"); }).map(function(css) { - return css.slice(0, -4); + const filename = css.slice(0, -4); + return { + name: filename.charAt(0).toUpperCase() + filename.slice(1), + filename: filename + }; }); res.setHeader("Content-Security-Policy", "default-src *; connect-src 'self' ws: wss:; style-src * 'unsafe-inline'; script-src 'self'; child-src 'self'; object-src 'none'; form-action 'none';"); res.setHeader("Referrer-Policy", "no-referrer"); From adfd99c92c93aa387a3578d960707cfc118e8573 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 17 Apr 2017 08:25:21 +0100 Subject: [PATCH 071/355] Add fix for undefined name being slugified --- client/views/network.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/views/network.tpl b/client/views/network.tpl index d525dff2..05885d47 100644 --- a/client/views/network.tpl +++ b/client/views/network.tpl @@ -1,5 +1,5 @@ {{#each networks}} -
+
{{> chan}}
{{/each}} From 05d363d9a5b556a86a025a789b865dfd9419c60a Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 18 Apr 2017 08:31:46 +0100 Subject: [PATCH 072/355] Create socket module --- client/js/lounge.js | 50 +---------------------------------------- client/js/socket.js | 54 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 client/js/socket.js diff --git a/client/js/lounge.js b/client/js/lounge.js index 0e65b5d3..41505797 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -3,7 +3,6 @@ // vendor libraries require("jquery-ui/ui/widgets/sortable"); const $ = require("jquery"); -const io = require("socket.io-client"); const Mousetrap = require("mousetrap"); const URI = require("urijs"); @@ -15,14 +14,9 @@ const helpers_parse = require("./libs/handlebars/parse"); const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber"); const slideoutMenu = require("./libs/slideout"); const templates = require("../views"); +const socket = require("./socket"); $(function() { - var path = window.location.pathname + "socket.io/"; - var socket = io({ - path: path, - autoConnect: false, - reconnection: false - }); var commands = [ "/away", "/back", @@ -84,48 +78,6 @@ $(function() { // available. See http://stackoverflow.com/q/14555347/1935861. } } - - [ - "connect_error", - "connect_failed", - "disconnect", - "error", - ].forEach(function(e) { - socket.on(e, function(data) { - $("#loading-page-message").text("Connection failed: " + data); - $("#connection-error").addClass("shown").one("click", function() { - window.onbeforeunload = null; - window.location.reload(); - }); - - // Disables sending a message by pressing Enter. `off` is necessary to - // cancel `inputhistory`, which overrides hitting Enter. `on` is then - // necessary to avoid creating new lines when hitting Enter without Shift. - // This is fairly hacky but this solution is not permanent. - $("#input").off("keydown").on("keydown", function(event) { - if (event.which === 13 && !event.shiftKey) { - event.preventDefault(); - } - }); - // Hides the "Send Message" button - $("#submit").remove(); - - console.error(data); - }); - }); - - socket.on("connecting", function() { - $("#loading-page-message").text("Connecting…"); - }); - - socket.on("connect", function() { - $("#loading-page-message").text("Finalizing connection…"); - }); - - socket.on("authorized", function() { - $("#loading-page-message").text("Authorized, loading messages…"); - }); - socket.on("auth", function(data) { var login = $("#sign-in"); var token; diff --git a/client/js/socket.js b/client/js/socket.js new file mode 100644 index 00000000..278802d7 --- /dev/null +++ b/client/js/socket.js @@ -0,0 +1,54 @@ +"use strict"; + +const $ = require("jquery"); +const io = require("socket.io-client"); +const path = window.location.pathname + "socket.io/"; + +const socket = io({ + path: path, + autoConnect: false, + reconnection: false +}); + +[ + "connect_error", + "connect_failed", + "disconnect", + "error", +].forEach(function(e) { + socket.on(e, function(data) { + $("#loading-page-message").text("Connection failed: " + data); + $("#connection-error").addClass("shown").one("click", function() { + window.onbeforeunload = null; + window.location.reload(); + }); + + // Disables sending a message by pressing Enter. `off` is necessary to + // cancel `inputhistory`, which overrides hitting Enter. `on` is then + // necessary to avoid creating new lines when hitting Enter without Shift. + // This is fairly hacky but this solution is not permanent. + $("#input").off("keydown").on("keydown", function(event) { + if (event.which === 13 && !event.shiftKey) { + event.preventDefault(); + } + }); + // Hides the "Send Message" button + $("#submit").remove(); + + console.error(data); + }); +}); + +socket.on("connecting", function() { + $("#loading-page-message").text("Connecting…"); +}); + +socket.on("connect", function() { + $("#loading-page-message").text("Finalizing connection…"); +}); + +socket.on("authorized", function() { + $("#loading-page-message").text("Authorized, loading messages…"); +}); + +module.exports = socket; From 0b85ded53fa66a1ac1474194052416662d9c9533 Mon Sep 17 00:00:00 2001 From: Bonuspunkt Date: Sat, 18 Mar 2017 10:35:17 +0200 Subject: [PATCH 073/355] Add bonuspunkt's parser Fixes #15. Fixes #199. Fixes #583. Fixes #654. Fixes #928. Fixes #1001. --- .../ircmessageparser/anyIntersection.js | 10 + .../libs/handlebars/ircmessageparser/fill.js | 29 +++ .../ircmessageparser/findChannels.js | 32 +++ .../libs/handlebars/ircmessageparser/merge.js | 47 ++++ .../handlebars/ircmessageparser/parseStyle.js | 131 +++++++++++ client/js/libs/handlebars/parse.js | 210 +++++++++--------- 6 files changed, 348 insertions(+), 111 deletions(-) create mode 100644 client/js/libs/handlebars/ircmessageparser/anyIntersection.js create mode 100644 client/js/libs/handlebars/ircmessageparser/fill.js create mode 100644 client/js/libs/handlebars/ircmessageparser/findChannels.js create mode 100644 client/js/libs/handlebars/ircmessageparser/merge.js create mode 100644 client/js/libs/handlebars/ircmessageparser/parseStyle.js diff --git a/client/js/libs/handlebars/ircmessageparser/anyIntersection.js b/client/js/libs/handlebars/ircmessageparser/anyIntersection.js new file mode 100644 index 00000000..4fd0d239 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/anyIntersection.js @@ -0,0 +1,10 @@ +"use strict"; + +function anyIntersection(a, b) { + return a.start <= b.start && b.start < a.end || + a.start < b.end && b.end <= a.end || + b.start <= a.start && a.start < b.end || + b.start < a.end && a.end <= b.end; +} + +module.exports = anyIntersection; diff --git a/client/js/libs/handlebars/ircmessageparser/fill.js b/client/js/libs/handlebars/ircmessageparser/fill.js new file mode 100644 index 00000000..2cc9f705 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/fill.js @@ -0,0 +1,29 @@ +"use strict"; + +function fill(existingEntries, text) { + let position = 0; + const result = []; + + for (let i = 0; i < existingEntries.length; i++) { + const textSegment = existingEntries[i]; + + if (textSegment.start > position) { + result.push({ + start: position, + end: textSegment.start + }); + } + position = textSegment.end; + } + + if (position < text.length) { + result.push({ + start: position, + end: text.length + }); + } + + return result; +} + +module.exports = fill; diff --git a/client/js/libs/handlebars/ircmessageparser/findChannels.js b/client/js/libs/handlebars/ircmessageparser/findChannels.js new file mode 100644 index 00000000..b613415c --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/findChannels.js @@ -0,0 +1,32 @@ +"use strict"; + +const escapeRegExp = require("lodash/escapeRegExp"); + +// NOTE: channel prefixes should be RPL_ISUPPORT.CHANTYPES +// NOTE: userModes should be RPL_ISUPPORT.PREFIX +function findChannels(text, channelPrefixes, userModes) { + const userModePattern = userModes.map(escapeRegExp).join(""); + const channelPrefixPattern = channelPrefixes.map(escapeRegExp).join(""); + + const channelPattern = `(?:^|\\s)[${ userModePattern }]*([${ channelPrefixPattern }][^ \u0007]+)`; + const channelRegExp = new RegExp(channelPattern, "g"); + + const result = []; + let match; + + do { + match = channelRegExp.exec(text); + + if (match) { + result.push({ + start: match.index + match[0].length - match[1].length, + end: match.index + match[0].length, + channel: match[1] + }); + } + } while (match); + + return result; +} + +module.exports = findChannels; diff --git a/client/js/libs/handlebars/ircmessageparser/merge.js b/client/js/libs/handlebars/ircmessageparser/merge.js new file mode 100644 index 00000000..3da520e8 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/merge.js @@ -0,0 +1,47 @@ +"use strict"; + +const anyIntersection = require("./anyIntersection"); +const fill = require("./fill"); + +let Object_assign = Object.assign; + +if (typeof Object_assign !== "function") { + Object_assign = function(target) { + Array.prototype.slice.call(arguments, 1).forEach(function(obj) { + Object.keys(obj).forEach(function(key) { + target[key] = obj[key]; + }); + }); + return target; + }; +} + +function assign(textPart, fragment) { + const fragStart = fragment.start; + const start = Math.max(fragment.start, textPart.start); + const end = Math.min(fragment.end, textPart.end); + + return Object_assign({}, fragment, { + start: start, + end: end, + text: fragment.text.slice(start - fragStart, end - fragStart) + }); +} + +function merge(textParts, styleFragments) { + const cleanText = styleFragments.map(fragment => fragment.text).join(""); + + const allParts = textParts + .concat(fill(textParts, cleanText)) + .sort((a, b) => a.start - b.start); + + return allParts.map(textPart => { + textPart.fragments = styleFragments + .filter(fragment => anyIntersection(textPart, fragment)) + .map(fragment => assign(textPart, fragment)); + + return textPart; + }); +} + +module.exports = merge; diff --git a/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/client/js/libs/handlebars/ircmessageparser/parseStyle.js new file mode 100644 index 00000000..54e1c191 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -0,0 +1,131 @@ +"use strict"; + +const BOLD = "\x02"; +const COLOR = "\x03"; +const RESET = "\x0f"; +const REVERSE = "\x16"; +const ITALIC = "\x1d"; +const UNDERLINE = "\x1f"; + +const colorRx = /^(\d{1,2})(?:,(\d{1,2}))?/; +const controlCodesRx = /[\u0000-\u001F]/g; + +function parseStyle(text) { + const result = []; + let start = 0; + let position = 0; + + let colorCodes, bold, textColor, bgColor, reverse, italic, underline; + + const resetStyle = () => { + bold = false; + textColor = undefined; + bgColor = undefined; + reverse = false; + italic = false; + underline = false; + }; + resetStyle(); + + const emitFragment = () => { + const textPart = text.slice(start, position); + start = position + 1; + + const processedText = textPart.replace(controlCodesRx, ""); + + if (!processedText.length) { + return; + } + + result.push({ + bold, + textColor, + bgColor, + reverse, + italic, + underline, + text: processedText + }); + }; + + while (position < text.length) { + switch (text[position]) { + + case RESET: + emitFragment(); + resetStyle(); + break; + + case BOLD: + emitFragment(); + bold = !bold; + break; + + case COLOR: + emitFragment(); + + colorCodes = text.slice(position + 1).match(colorRx); + + if (colorCodes) { + textColor = Number(colorCodes[1]); + bgColor = Number(colorCodes[2]); + if (Number.isNaN(bgColor)) { + bgColor = undefined; + } + position += colorCodes[0].length; + } else { + textColor = undefined; + bgColor = undefined; + } + start = position + 1; + break; + + case REVERSE: + emitFragment(); + reverse = !reverse; + break; + + case ITALIC: + emitFragment(); + italic = !italic; + break; + + case UNDERLINE: + emitFragment(); + underline = !underline; + break; + } + position += 1; + } + + emitFragment(); + + return result; +} + +const properties = ["bold", "textColor", "bgColor", "italic", "underline", "reverse"]; + +function prepare(text) { + return parseStyle(text) + .filter(fragment => fragment.text.length) + .reduce((prev, curr, i) => { + if (i === 0) { + return prev.concat([curr]); + } + + const lastEntry = prev[prev.length - 1]; + if (properties.some(key => curr[key] !== lastEntry[key])) { + return prev.concat([curr]); + } + + lastEntry.text += curr.text; + return prev; + }, []) + .map((fragment, i, array) => { + fragment.start = i === 0 ? 0 : array[i - 1].end; + fragment.end = fragment.start + fragment.text.length; + return fragment; + }); +} + +module.exports = prepare; diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 45d5c8d2..8c6ae432 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -2,125 +2,113 @@ const Handlebars = require("handlebars/runtime"); const URI = require("urijs"); +const parseStyle = require("./ircmessageparser/parseStyle"); +const findChannels = require("./ircmessageparser/findChannels"); +const merge = require("./ircmessageparser/merge"); -module.exports = function(text) { - text = Handlebars.Utils.escapeExpression(text); - text = colors(text); - text = channels(text); - text = uri(text); - return text; -}; +const commonSchemes = [ + "http", "https", + "ftp", "sftp", + "smb", "file", + "irc", "ircs", + "svn", "git", + "steam", "mumble", "ts3server", + "svn+ssh", "ssh", +]; -function uri(text) { - return URI.withinString(text, function(url) { - if (url.indexOf("javascript:") === 0) { - return url; - } - var split = url.split("<"); - url = "" + split[0] + ""; - if (split.length > 1) { - url += "<" + split.slice(1).join("<"); - } - return url; - }); -} +function findLinks(text) { + let result = []; + let lastPosition = 0; -/** - * Channels names are strings of length up to fifty (50) characters. - * The only restriction on a channel name is that it SHALL NOT contain - * any spaces (' '), a control G (^G or ASCII 7), a comma (','). - * Channel prefix '&' is handled as '&' because this parser is executed - * after entities in the message have been escaped. This prevents a couple of bugs. - */ -function channels(text) { - return text.replace( - /(^|\s|\x07|,)((?:#|&)[^\x07\s,]{1,49})/g, - '$1$2' - ); -} - -/** - * MIRC compliant colour and style parser - * Unfortuanately this is a non trivial operation - * See this branch for source and tests - * https://github.com/megawac/irc-style-parser/tree/shout - */ -var styleCheck_Re = /[\x00-\x1F]/, - back_re = /^([0-9]{1,2})(,([0-9]{1,2}))?/, - colourKey = "\x03", - // breaks all open styles ^O (\x0F) - styleBreak = "\x0F"; - -function styleTemplate(settings) { - return "" + settings.text + ""; -} - -var styles = [ - ["normal", "\x00", ""], ["underline", "\x1F"], - ["bold", "\x02"], ["italic", "\x1D"] -].map(function(style) { - var escaped = encodeURI(style[1]).replace("%", "\\x"); - return { - name: style[0], - style: style[2] ? style[2] : "irc-" + style[0], - key: style[1], - keyregex: new RegExp(escaped + "(.*?)(" + escaped + "|$)") - }; -}); - -function colors(line) { - // http://www.mirc.com/colors.html - // http://www.aviran.org/stripremove-irc-client-control-characters/ - // https://github.com/perl6/mu/blob/master/examples/rules/Grammar-IRC.pm - // regexs are cruel to parse this thing - - // already done? - if (!styleCheck_Re.test(line)) { - return line; - } - - // split up by the irc style break character ^O - if (line.indexOf(styleBreak) >= 0) { - return line.split(styleBreak).map(colors).join(""); - } - - var result = line; - var parseArr = result.split(colourKey); - var text, match, colour, background = ""; - for (var i = 0; i < parseArr.length; i++) { - text = parseArr[i]; - match = text.match(back_re); - if (!match) { - // ^C (no colour) ending. Escape current colour and carry on - background = ""; - continue; - } - colour = "irc-fg" + +match[1]; - // set the background colour - if (match[3]) { - background = " irc-bg" + +match[3]; - } - // update the parsed text result - result = result.replace(colourKey + text, styleTemplate({ - style: colour + background, - text: text.slice(match[0].length) - })); - } - - // Matching styles (italics/bold/underline) - // if only colours were this easy... - styles.forEach(function(style) { - if (result.indexOf(style.key) < 0) { + URI.withinString(text, function(url, start, end) { + // v-- fix: url was modified and does not match input string -> cant be mapped + if (text.indexOf(url, lastPosition) < 0) { return; } + // ^-- /fix: url was modified and does not match input string -> cant be mapped - result = result.replace(style.keyregex, function(matchedTrash, matchedText) { - return styleTemplate({ - style: style.style, - text: matchedText - }); + // v-- fix: use prefered scheme + const parsed = URI(url); + const parsedScheme = parsed.scheme().toLowerCase(); + const matchedScheme = commonSchemes.find(scheme => parsedScheme.endsWith(scheme)); + + if (matchedScheme) { + const prefix = parsedScheme.length - matchedScheme.length; + start += prefix; + url = url.slice(prefix); + } + // ^-- /fix: use prefered scheme + + // URL matched, but does not start with a protocol, add it + if (!parsedScheme.length) { + url = "http://" + url; + } + + result.push({ + start: start, + end: end, + link: url }); }); return result; } + +function createFragment(fragment) { + let className = ""; + if (fragment.bold) { + className += " irc-bold"; + } + if (fragment.textColor !== undefined) { + className += " irc-fg" + fragment.textColor; + } + if (fragment.bgColor !== undefined) { + className += " irc-bg" + fragment.bgColor; + } + if (fragment.italic) { + className += " irc-italic"; + } + if (fragment.underline) { + className += " irc-underline"; + } + const escapedText = Handlebars.Utils.escapeExpression(fragment.text); + if (className) { + return "" + escapedText + ""; + } + return escapedText; +} + +module.exports = function parse(text) { + const styleFragments = parseStyle(text); + const cleanText = styleFragments.map(fragment => fragment.text).join(""); + + const channelPrefixes = ["#", "&"]; // RPL_ISUPPORT.CHANTYPES + const userModes = ["!", "@", "%", "+"]; // RPL_ISUPPORT.PREFIX + const channelParts = findChannels(cleanText, channelPrefixes, userModes); + + const linkParts = findLinks(cleanText); + + const parts = channelParts + .concat(linkParts) + .sort((a, b) => a.start - b.start); + + return merge(parts, styleFragments).map(textPart => { + const fragments = textPart.fragments.map(createFragment).join(""); + + if (textPart.link) { + const escapedLink = Handlebars.Utils.escapeExpression(textPart.link); + return ( + "" + + fragments + + ""); + } else if (textPart.channel) { + const escapedChannel = Handlebars.Utils.escapeExpression(textPart.channel); + return ( + "" + + fragments + + ""); + } + + return fragments; + }).join(""); +}; From eb1360c3af3c2f02cc3facbd843e7e34af3091e9 Mon Sep 17 00:00:00 2001 From: Bonuspunkt Date: Sat, 18 Mar 2017 10:18:47 +0200 Subject: [PATCH 074/355] Add message parser tests --- .../ircmessageparser/anyIntersection.js | 30 ++ .../ircmessageparser/findChannels.js | 123 +++++++ .../libs/handlebars/ircmessageparser/merge.js | 63 ++++ .../handlebars/ircmessageparser/parseStyle.js | 274 ++++++++++++++ test/client/js/libs/handlebars/parse.js | 336 ++++++++++++++++++ 5 files changed, 826 insertions(+) create mode 100644 test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js create mode 100644 test/client/js/libs/handlebars/ircmessageparser/findChannels.js create mode 100644 test/client/js/libs/handlebars/ircmessageparser/merge.js create mode 100644 test/client/js/libs/handlebars/ircmessageparser/parseStyle.js create mode 100644 test/client/js/libs/handlebars/parse.js diff --git a/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js b/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js new file mode 100644 index 00000000..b80a44ed --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/anyIntersection.js @@ -0,0 +1,30 @@ +"use strict"; + +const expect = require("chai").expect; +const anyIntersection = require("../../../../../../client/js/libs/handlebars/ircmessageparser/anyIntersection"); + +describe("anyIntersection", () => { + it("should not intersect on edges", () => { + const a = {start: 1, end: 2}; + const b = {start: 2, end: 3}; + + expect(anyIntersection(a, b)).to.equal(false); + expect(anyIntersection(b, a)).to.equal(false); + }); + + it("should intersect on overlapping", () => { + const a = {start: 0, end: 3}; + const b = {start: 1, end: 2}; + + expect(anyIntersection(a, b)).to.equal(true); + expect(anyIntersection(b, a)).to.equal(true); + }); + + it("should not intersect", () => { + const a = {start: 0, end: 1}; + const b = {start: 2, end: 3}; + + expect(anyIntersection(a, b)).to.equal(false); + expect(anyIntersection(b, a)).to.equal(false); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/findChannels.js b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js new file mode 100644 index 00000000..93c119ee --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js @@ -0,0 +1,123 @@ +"use strict"; + +const expect = require("chai").expect; +const analyseText = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findChannels"); + +describe("findChannels", () => { + it("should find single letter channel", () => { + const input = "#a"; + const expected = [{ + channel: "#a", + start: 0, + end: 2 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should find utf8 channels", () => { + const input = "#äöü"; + const expected = [{ + channel: "#äöü", + start: 0, + end: 4 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should find inline channel", () => { + const input = "inline #channel text"; + const expected = [{ + channel: "#channel", + start: 7, + end: 15 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should stop at \\0x07", () => { + const input = "#chan\x07nel"; + const expected = [{ + channel: "#chan", + start: 0, + end: 5 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should allow classics pranks", () => { + const input = "#1,000"; + const expected = [{ + channel: "#1,000", + start: 0, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with whois reponses", () => { + const input = "@#a"; + const expected = [{ + channel: "#a", + start: 1, + end: 3 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with IRCv3.1 multi-prefix", () => { + const input = "!@%+#a"; + const expected = [{ + channel: "#a", + start: 4, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["!", "@", "%", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should work with custom channelPrefixes", () => { + const input = "@a"; + const expected = [{ + channel: "@a", + start: 0, + end: 2 + }]; + + const actual = analyseText(input, ["@"], ["#", "+"]); + + expect(actual).to.deep.equal(expected); + }); + + it("should handle multiple channelPrefix correctly", () => { + const input = "##test"; + const expected = [{ + channel: "##test", + start: 0, + end: 6 + }]; + + const actual = analyseText(input, ["#"], ["@", "+"]); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/merge.js b/test/client/js/libs/handlebars/ircmessageparser/merge.js new file mode 100644 index 00000000..d55ac1a2 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/merge.js @@ -0,0 +1,63 @@ +"use strict"; + +const expect = require("chai").expect; +const merge = require("../../../../../../client/js/libs/handlebars/ircmessageparser/merge"); + +describe("merge", () => { + it("should split style information", () => { + const textParts = [{ + start: 0, + end: 10, + flag1: true + }, { + start: 10, + end: 20, + flag2: true + }]; + const styleFragments = [{ + start: 0, + end: 5, + text: "01234" + }, { + start: 5, + end: 15, + text: "5678901234" + }, { + start: 15, + end: 20, + text: "56789" + }]; + + const expected = [{ + start: 0, + end: 10, + flag1: true, + fragments: [{ + start: 0, + end: 5, + text: "01234" + }, { + start: 5, + end: 10, + text: "56789" + }] + }, { + start: 10, + end: 20, + flag2: true, + fragments: [{ + start: 10, + end: 15, + text: "01234" + }, { + start: 15, + end: 20, + text: "56789" + }] + }]; + + const actual = merge(textParts, styleFragments); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js new file mode 100644 index 00000000..6af289c4 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -0,0 +1,274 @@ +"use strict"; + +const expect = require("chai").expect; +const parseStyle = require("../../../../../../client/js/libs/handlebars/ircmessageparser/parseStyle"); + +describe("parseStyle", () => { + it("should skip control codes", () => { + const input = "text\x01with\x04control\x05codes"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "textwithcontrolcodes", + + start: 0, + end: 20 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse bold", () => { + const input = "\x02bold"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse textColor", () => { + const input = "\x038yellowText"; + const expected = [{ + bold: false, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "yellowText", + + start: 0, + end: 10 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse textColor and background", () => { + const input = "\x034,8yellowBG redText"; + const expected = [{ + textColor: 4, + bgColor: 8, + bold: false, + reverse: false, + italic: false, + underline: false, + text: "yellowBG redText", + + start: 0, + end: 16 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should parse italic", () => { + const input = "\x1ditalic"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: true, + underline: false, + text: "italic", + + start: 0, + end: 6 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should carry state corretly forward", () => { + const input = "\x02bold\x038yellow\x02nonBold\x03default"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }, { + bold: true, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "yellow", + + start: 4, + end: 10 + }, { + bold: false, + textColor: 8, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "nonBold", + + start: 10, + end: 17 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "default", + + start: 17, + end: 24 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should toggle bold correctly", () => { + const input = "\x02bold\x02 \x02bold\x02"; + const expected = [{ + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 0, + end: 4 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: " ", + + start: 4, + end: 5 + }, { + bold: true, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "bold", + + start: 5, + end: 9 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should reset all styles", () => { + const input = "\x02\x034\x16\x1d\x1ffull\x0fnone"; + const expected = [{ + bold: true, + textColor: 4, + bgColor: undefined, + reverse: true, + italic: true, + underline: true, + text: "full", + + start: 0, + end: 4 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "none", + + start: 4, + end: 8 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should not emit empty fragments", () => { + const input = "\x031\x031,2\x031\x031,2\x031\x031,2\x03a"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "a", + + start: 0, + end: 1 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should optimize fragments", () => { + const rawString = "oh hi test text"; + const colorCode = "\x0312"; + const input = colorCode + rawString.split("").join(colorCode); + const expected = [{ + bold: false, + textColor: 12, + bgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: rawString, + + start: 0, + end: rawString.length + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); +}); diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js new file mode 100644 index 00000000..7d1e025a --- /dev/null +++ b/test/client/js/libs/handlebars/parse.js @@ -0,0 +1,336 @@ +"use strict"; + +const expect = require("chai").expect; +const parse = require("../../../../../client/js/libs/handlebars/parse"); + +describe("parse Handlebars helper", () => { + it("should not introduce xss", () => { + const testCases = [{ + input: "", + expected: "<img onerror='location.href="//youtube.com"'>" + }, { + input: "#&\">bug", + expected: "#&">bug" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should skip control codes", () => { + const testCases = [{ + input: "text\x01with\x04control\x05codes", + expected: "textwithcontrolcodes" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls", () => { + const testCases = [{ + input: "irc://freenode.net/thelounge", + expected: + "" + + "irc://freenode.net/thelounge" + + "" + }, { + input: "www.nooooooooooooooo.com", + expected: + "" + + "www.nooooooooooooooo.com" + + "" + }, { + input: "look at https://thelounge.github.io/ for more information", + expected: + "look at " + + "" + + "https://thelounge.github.io/" + + "" + + " for more information", + }, { + input: "use www.duckduckgo.com for privacy reasons", + expected: + "use " + + "" + + "www.duckduckgo.com" + + "" + + " for privacy reasons" + }, { + input: "svn+ssh://example.org", + expected: + "" + + "svn+ssh://example.org" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("url with a dot parsed correctly", () => { + const input = + "bonuspunkt: your URL parser misparses this URL: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx"; + const correctResult = + "bonuspunkt: your URL parser misparses this URL: " + + "" + + "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx" + + ""; + + const actual = parse(input); + + expect(actual).to.deep.equal(correctResult); + }); + + it("should balance brackets", () => { + const testCases = [{ + input: "", + expected: + "<" + + "" + + "https://theos.kyriasis.com/~kyrias/stats/archlinux.html" + + "" + + ">" + }, { + input: "abc (www.example.com)", + expected: + "abc (" + + "" + + "www.example.com" + + "" + + ")" + }, { + input: "http://example.com/Test_(Page)", + expected: + "" + + "http://example.com/Test_(Page)" + + "" + }, { + input: "www.example.com/Test_(Page)", + expected: + "" + + "www.example.com/Test_(Page)" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find urls", () => { + const testCases = [{ + input: "text www. text", + expected: "text www. text" + }, { + input: "http://.", + expected: "http://." + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should find channels", () => { + const testCases = [{ + input: "#a", + expected: + "" + + "#a" + + "" + }, { + input: "#test", + expected: + "" + + "#test" + + "" + }, { + input: "#äöü", + expected: + "" + + "#äöü" + + "" + }, { + input: "inline #channel text", + expected: + "inline " + + "" + + "#channel" + + "" + + " text" + }, { + input: "#1,000", + expected: + "" + + "#1,000" + + "" + }, { + input: "@#a", + expected: + "@" + + "" + + "#a" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find channels", () => { + const testCases = [{ + input: "hi#test", + expected: "hi#test" + }, { + input: "#", + expected: "#" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should style like mirc", () => { + const testCases = [{ + input: "\x02bold", + expected: "bold" + }, { + input: "\x038yellowText", + expected: "yellowText" + }, { + input: "\x030,0white,white", + expected: "white,white" + }, { + input: "\x034,8yellowBGredText", + expected: "yellowBGredText" + }, { + input: "\x1ditalic", + expected: "italic" + }, { + input: "\x1funderline", + expected: "underline" + }, { + input: "\x02bold\x038yellow\x02nonBold\x03default", + expected: + "bold" + + "yellow" + + "nonBold" + + "default" + }, { + input: "\x02bold\x02 \x02bold\x02", + expected: + "bold" + + " " + + "bold" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should go bonkers like mirc", () => { + const testCases = [{ + input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge", + expected: + "" + + "irc" + + "://" + + "freenode.net" + + "/" + + "thelounge" + + "" + }, { + input: "\x02#\x038,9thelounge", + expected: + "" + + "#" + + "thelounge" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should optimize generated html", () => { + const testCases = [{ + input: "test \x0312#\x0312\x0312\"te\x0312st\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312a", + expected: + "test " + + "" + + "#"testa" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should trim commom protocols", () => { + const testCases = [{ + input: "like..http://example.com", + expected: + "like.." + + "" + + "http://example.com" + + "" + }, { + input: "like..HTTP://example.com", + expected: + "like.." + + "" + + "HTTP://example.com" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find channel in fragment", () => { + const testCases = [{ + input: "http://example.com/#hash", + expected: + "" + + "" + + "http://example.com/#hash" + + "" + }]; + + const actual = testCases.map(testCase => parse(testCase.input)); + const expected = testCases.map(testCase => testCase.expected); + + expect(actual).to.deep.equal(expected); + }); + + it("should not overlap parts", () => { + const input = "Url: http://example.com/path Channel: ##channel"; + const actual = parse(input); + + expect(actual).to.equal( + "Url: http://example.com/path " + + "Channel: ##channel" + ); + }); +}); From 5b4c00d8ca595a1c392f70893d0ca9830ce4028c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 17 Apr 2017 23:28:35 -0400 Subject: [PATCH 075/355] Extract findLinks into its own file and add tests Tests were taken from https://github.com/Bonuspunkt/ircmessageparser/blob/5a249c30b1e0379e5df62f10734a3dc7ce02306b/test/findLinks.js. The underlying code is different but the tests are the same. --- .../handlebars/ircmessageparser/findLinks.js | 53 +++++++++ client/js/libs/handlebars/parse.js | 50 +-------- .../handlebars/ircmessageparser/findLinks.js | 106 ++++++++++++++++++ 3 files changed, 160 insertions(+), 49 deletions(-) create mode 100644 client/js/libs/handlebars/ircmessageparser/findLinks.js create mode 100644 test/client/js/libs/handlebars/ircmessageparser/findLinks.js diff --git a/client/js/libs/handlebars/ircmessageparser/findLinks.js b/client/js/libs/handlebars/ircmessageparser/findLinks.js new file mode 100644 index 00000000..031afa17 --- /dev/null +++ b/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -0,0 +1,53 @@ +"use strict"; + +const URI = require("urijs"); + +const commonSchemes = [ + "http", "https", + "ftp", "sftp", + "smb", "file", + "irc", "ircs", + "svn", "git", + "steam", "mumble", "ts3server", + "svn+ssh", "ssh", +]; + +function findLinks(text) { + let result = []; + let lastPosition = 0; + + URI.withinString(text, function(url, start, end) { + // v-- fix: url was modified and does not match input string -> cant be mapped + if (text.indexOf(url, lastPosition) < 0) { + return; + } + // ^-- /fix: url was modified and does not match input string -> cant be mapped + + // v-- fix: use prefered scheme + const parsed = URI(url); + const parsedScheme = parsed.scheme().toLowerCase(); + const matchedScheme = commonSchemes.find(scheme => parsedScheme.endsWith(scheme)); + + if (matchedScheme) { + const prefix = parsedScheme.length - matchedScheme.length; + start += prefix; + url = url.slice(prefix); + } + // ^-- /fix: use prefered scheme + + // URL matched, but does not start with a protocol, add it + if (!parsedScheme.length) { + url = "http://" + url; + } + + result.push({ + start: start, + end: end, + link: url + }); + }); + + return result; +} + +module.exports = findLinks; diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 8c6ae432..fa21b898 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -1,59 +1,11 @@ "use strict"; const Handlebars = require("handlebars/runtime"); -const URI = require("urijs"); const parseStyle = require("./ircmessageparser/parseStyle"); const findChannels = require("./ircmessageparser/findChannels"); +const findLinks = require("./ircmessageparser/findLinks"); const merge = require("./ircmessageparser/merge"); -const commonSchemes = [ - "http", "https", - "ftp", "sftp", - "smb", "file", - "irc", "ircs", - "svn", "git", - "steam", "mumble", "ts3server", - "svn+ssh", "ssh", -]; - -function findLinks(text) { - let result = []; - let lastPosition = 0; - - URI.withinString(text, function(url, start, end) { - // v-- fix: url was modified and does not match input string -> cant be mapped - if (text.indexOf(url, lastPosition) < 0) { - return; - } - // ^-- /fix: url was modified and does not match input string -> cant be mapped - - // v-- fix: use prefered scheme - const parsed = URI(url); - const parsedScheme = parsed.scheme().toLowerCase(); - const matchedScheme = commonSchemes.find(scheme => parsedScheme.endsWith(scheme)); - - if (matchedScheme) { - const prefix = parsedScheme.length - matchedScheme.length; - start += prefix; - url = url.slice(prefix); - } - // ^-- /fix: use prefered scheme - - // URL matched, but does not start with a protocol, add it - if (!parsedScheme.length) { - url = "http://" + url; - } - - result.push({ - start: start, - end: end, - link: url - }); - }); - - return result; -} - function createFragment(fragment) { let className = ""; if (fragment.bold) { diff --git a/test/client/js/libs/handlebars/ircmessageparser/findLinks.js b/test/client/js/libs/handlebars/ircmessageparser/findLinks.js new file mode 100644 index 00000000..f3f228f2 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -0,0 +1,106 @@ +"use strict"; + +const expect = require("chai").expect; +const findLinks = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findLinks"); + +describe("findLinks", () => { + it("should find url", () => { + const input = "irc://freenode.net/thelounge"; + const expected = [{ + start: 0, + end: 28, + link: "irc://freenode.net/thelounge", + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls with www", () => { + const input = "www.nooooooooooooooo.com"; + const expected = [{ + start: 0, + end: 24, + link: "http://www.nooooooooooooooo.com" + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls in strings", () => { + const input = "look at https://thelounge.github.io/ for more information"; + const expected = [{ + link: "https://thelounge.github.io/", + start: 8, + end: 36 + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls in strings starting with www", () => { + const input = "use www.duckduckgo.com for privacy reasons"; + const expected = [{ + link: "http://www.duckduckgo.com", + start: 4, + end: 22 + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls with odd surroundings", () => { + const input = ""; + const expected = [{ + link: "https://theos.kyriasis.com/~kyrias/stats/archlinux.html", + start: 1, + end: 56 + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should find urls with starting with www. and odd surroundings", () => { + const input = ".:www.github.com:."; + const expected = [{ + link: "http://www.github.com", + start: 2, + end: 16 + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should not find urls", () => { + const input = "text www. text"; + const expected = []; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); + + it("should handle multiple www. correctly", () => { + const input = "www.www.test.com"; + const expected = [{ + link: "http://www.www.test.com", + start: 0, + end: 16 + }]; + + const actual = findLinks(input); + + expect(actual).to.deep.equal(expected); + }); +}); From 90f4a94bb246bd91a0c2d2e2e54f717d16f8ba32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 18 Apr 2017 00:53:12 -0400 Subject: [PATCH 076/355] Use template literals in parse Also make it output double quotes for consistency with web stuff. --- client/js/libs/handlebars/parse.js | 26 +++----- test/client/js/libs/handlebars/parse.js | 84 ++++++++++++------------- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index fa21b898..7e9aebb8 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -7,25 +7,25 @@ const findLinks = require("./ircmessageparser/findLinks"); const merge = require("./ircmessageparser/merge"); function createFragment(fragment) { - let className = ""; + let classes = []; if (fragment.bold) { - className += " irc-bold"; + classes.push("irc-bold"); } if (fragment.textColor !== undefined) { - className += " irc-fg" + fragment.textColor; + classes.push("irc-fg" + fragment.textColor); } if (fragment.bgColor !== undefined) { - className += " irc-bg" + fragment.bgColor; + classes.push("irc-bg" + fragment.bgColor); } if (fragment.italic) { - className += " irc-italic"; + classes.push("irc-italic"); } if (fragment.underline) { - className += " irc-underline"; + classes.push("irc-underline"); } const escapedText = Handlebars.Utils.escapeExpression(fragment.text); - if (className) { - return "" + escapedText + ""; + if (classes.length) { + return `${escapedText}`; } return escapedText; } @@ -49,16 +49,10 @@ module.exports = function parse(text) { if (textPart.link) { const escapedLink = Handlebars.Utils.escapeExpression(textPart.link); - return ( - "" + - fragments + - ""); + return `${fragments}`; } else if (textPart.channel) { const escapedChannel = Handlebars.Utils.escapeExpression(textPart.channel); - return ( - "" + - fragments + - ""); + return `${fragments}`; } return fragments; diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js index 7d1e025a..d3737e98 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -10,7 +10,7 @@ describe("parse Handlebars helper", () => { expected: "<img onerror='location.href="//youtube.com"'>" }, { input: "#&\">bug", - expected: "#&">bug" + expected: "#&">bug" }]; const actual = testCases.map(testCase => parse(testCase.input)); @@ -35,20 +35,20 @@ describe("parse Handlebars helper", () => { const testCases = [{ input: "irc://freenode.net/thelounge", expected: - "" + + "" + "irc://freenode.net/thelounge" + "" }, { input: "www.nooooooooooooooo.com", expected: - "" + + "" + "www.nooooooooooooooo.com" + "" }, { input: "look at https://thelounge.github.io/ for more information", expected: "look at " + - "" + + "" + "https://thelounge.github.io/" + "" + " for more information", @@ -56,14 +56,14 @@ describe("parse Handlebars helper", () => { input: "use www.duckduckgo.com for privacy reasons", expected: "use " + - "" + + "" + "www.duckduckgo.com" + "" + " for privacy reasons" }, { input: "svn+ssh://example.org", expected: - "" + + "" + "svn+ssh://example.org" + "" }]; @@ -79,7 +79,7 @@ describe("parse Handlebars helper", () => { "bonuspunkt: your URL parser misparses this URL: https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx"; const correctResult = "bonuspunkt: your URL parser misparses this URL: " + - "" + + "" + "https://msdn.microsoft.com/en-us/library/windows/desktop/ms644989(v=vs.85).aspx" + ""; @@ -93,7 +93,7 @@ describe("parse Handlebars helper", () => { input: "", expected: "<" + - "" + + "" + "https://theos.kyriasis.com/~kyrias/stats/archlinux.html" + "" + ">" @@ -101,20 +101,20 @@ describe("parse Handlebars helper", () => { input: "abc (www.example.com)", expected: "abc (" + - "" + + "" + "www.example.com" + "" + ")" }, { input: "http://example.com/Test_(Page)", expected: - "" + + "" + "http://example.com/Test_(Page)" + "" }, { input: "www.example.com/Test_(Page)", expected: - "" + + "" + "www.example.com/Test_(Page)" + "" }]; @@ -144,40 +144,40 @@ describe("parse Handlebars helper", () => { const testCases = [{ input: "#a", expected: - "" + + "" + "#a" + "" }, { input: "#test", expected: - "" + + "" + "#test" + "" }, { input: "#äöü", expected: - "" + + "" + "#äöü" + "" }, { input: "inline #channel text", expected: "inline " + - "" + + "" + "#channel" + "" + " text" }, { input: "#1,000", expected: - "" + + "" + "#1,000" + "" }, { input: "@#a", expected: "@" + - "" + + "" + "#a" + "" }]; @@ -206,35 +206,35 @@ describe("parse Handlebars helper", () => { it("should style like mirc", () => { const testCases = [{ input: "\x02bold", - expected: "bold" + expected: "bold" }, { input: "\x038yellowText", - expected: "yellowText" + expected: "yellowText" }, { input: "\x030,0white,white", - expected: "white,white" + expected: "white,white" }, { input: "\x034,8yellowBGredText", - expected: "yellowBGredText" + expected: "yellowBGredText" }, { input: "\x1ditalic", - expected: "italic" + expected: "italic" }, { input: "\x1funderline", - expected: "underline" + expected: "underline" }, { input: "\x02bold\x038yellow\x02nonBold\x03default", expected: - "bold" + - "yellow" + - "nonBold" + + "bold" + + "yellow" + + "nonBold" + "default" }, { input: "\x02bold\x02 \x02bold\x02", expected: - "bold" + + "bold" + " " + - "bold" + "bold" }]; const actual = testCases.map(testCase => parse(testCase.input)); @@ -247,19 +247,19 @@ describe("parse Handlebars helper", () => { const testCases = [{ input: "\x02irc\x0f://\x1dfreenode.net\x0f/\x034,8thelounge", expected: - "" + - "irc" + + "" + + "irc" + "://" + - "freenode.net" + + "freenode.net" + "/" + - "thelounge" + + "thelounge" + "" }, { input: "\x02#\x038,9thelounge", expected: - "" + - "#" + - "thelounge" + + "" + + "#" + + "thelounge" + "" }]; @@ -274,8 +274,8 @@ describe("parse Handlebars helper", () => { input: "test \x0312#\x0312\x0312\"te\x0312st\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312\x0312a", expected: "test " + - "" + - "#"testa" + + "" + + "#"testa" + "" }]; @@ -290,14 +290,14 @@ describe("parse Handlebars helper", () => { input: "like..http://example.com", expected: "like.." + - "" + + "" + "http://example.com" + "" }, { input: "like..HTTP://example.com", expected: "like.." + - "" + + "" + "HTTP://example.com" + "" }]; @@ -313,7 +313,7 @@ describe("parse Handlebars helper", () => { input: "http://example.com/#hash", expected: "" + - "" + + "" + "http://example.com/#hash" + "" }]; @@ -329,8 +329,8 @@ describe("parse Handlebars helper", () => { const actual = parse(input); expect(actual).to.equal( - "Url: http://example.com/path " + - "Channel: ##channel" + "Url: http://example.com/path " + + "Channel: ##channel" ); }); }); From 03e3444a352bc5045ff8f4669bc3ed565b369bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 4 Apr 2017 00:36:03 -0400 Subject: [PATCH 077/355] Explain the modules of the message parser and add tests - Add comments and descriptions to: - `findChannels.js` - `parseStyle` - `findLinks` - `fill` - `anyIntersection` - `merge` - `parse` - Minor optimizations to `parseStyle` - Add tests for `fill` --- .../ircmessageparser/anyIntersection.js | 2 + .../libs/handlebars/ircmessageparser/fill.js | 17 ++-- .../ircmessageparser/findChannels.js | 19 +++- .../handlebars/ircmessageparser/findLinks.js | 20 +++- .../libs/handlebars/ircmessageparser/merge.js | 15 ++- .../handlebars/ircmessageparser/parseStyle.js | 98 ++++++++++++------- client/js/libs/handlebars/parse.js | 17 +++- .../libs/handlebars/ircmessageparser/fill.js | 50 ++++++++++ .../ircmessageparser/findChannels.js | 20 ++-- 9 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 test/client/js/libs/handlebars/ircmessageparser/fill.js diff --git a/client/js/libs/handlebars/ircmessageparser/anyIntersection.js b/client/js/libs/handlebars/ircmessageparser/anyIntersection.js index 4fd0d239..a77e031d 100644 --- a/client/js/libs/handlebars/ircmessageparser/anyIntersection.js +++ b/client/js/libs/handlebars/ircmessageparser/anyIntersection.js @@ -1,5 +1,7 @@ "use strict"; +// Return true if any section of "a" or "b" parts (defined by their start/end +// markers) intersect each other, false otherwise. function anyIntersection(a, b) { return a.start <= b.start && b.start < a.end || a.start < b.end && b.end <= a.end || diff --git a/client/js/libs/handlebars/ircmessageparser/fill.js b/client/js/libs/handlebars/ircmessageparser/fill.js index 2cc9f705..7d90a96c 100644 --- a/client/js/libs/handlebars/ircmessageparser/fill.js +++ b/client/js/libs/handlebars/ircmessageparser/fill.js @@ -1,21 +1,26 @@ "use strict"; +// Create plain text entries corresponding to areas of the text that match no +// existing entries. Returns an empty array if all parts of the text have been +// parsed into recognizable entries already. function fill(existingEntries, text) { let position = 0; - const result = []; - - for (let i = 0; i < existingEntries.length; i++) { - const textSegment = existingEntries[i]; + // Fill inner parts of the text. For example, if text is `foobarbaz` and both + // `foo` and `baz` have matched into an entry, this will return a dummy entry + // corresponding to `bar`. + const result = existingEntries.reduce((acc, textSegment) => { if (textSegment.start > position) { - result.push({ + acc.push({ start: position, end: textSegment.start }); } position = textSegment.end; - } + return acc; + }, []); + // Complete the unmatched end of the text with a dummy entry if (position < text.length) { result.push({ start: position, diff --git a/client/js/libs/handlebars/ircmessageparser/findChannels.js b/client/js/libs/handlebars/ircmessageparser/findChannels.js index b613415c..6edd5dad 100644 --- a/client/js/libs/handlebars/ircmessageparser/findChannels.js +++ b/client/js/libs/handlebars/ircmessageparser/findChannels.js @@ -1,20 +1,31 @@ "use strict"; +// Escapes the RegExp special characters "^", "$", "", ".", "*", "+", "?", "(", +// ")", "[", "]", "{", "}", and "|" in string. +// See https://lodash.com/docs/#escapeRegExp const escapeRegExp = require("lodash/escapeRegExp"); -// NOTE: channel prefixes should be RPL_ISUPPORT.CHANTYPES -// NOTE: userModes should be RPL_ISUPPORT.PREFIX +// Given an array of channel prefixes (such as "#" and "&") and an array of user +// modes (such as "@" and "+"), this function extracts channels and nicks from a +// text. +// It returns an array of objects for each channel found with their start index, +// end index and channel name. function findChannels(text, channelPrefixes, userModes) { + // `userModePattern` is necessary to ignore user modes in /whois responses. + // For example, a voiced user in #thelounge will have a /whois response of: + // > foo is on the following channels: +#thelounge + // We need to explicitly ignore user modes to parse such channels correctly. const userModePattern = userModes.map(escapeRegExp).join(""); const channelPrefixPattern = channelPrefixes.map(escapeRegExp).join(""); - - const channelPattern = `(?:^|\\s)[${ userModePattern }]*([${ channelPrefixPattern }][^ \u0007]+)`; + const channelPattern = `(?:^|\\s)[${userModePattern}]*([${channelPrefixPattern}][^ \u0007]+)`; const channelRegExp = new RegExp(channelPattern, "g"); const result = []; let match; do { + // With global ("g") regexes, calling `exec` multiple times will find + // successive matches in the same string. match = channelRegExp.exec(text); if (match) { diff --git a/client/js/libs/handlebars/ircmessageparser/findLinks.js b/client/js/libs/handlebars/ircmessageparser/findLinks.js index 031afa17..9596a5a0 100644 --- a/client/js/libs/handlebars/ircmessageparser/findLinks.js +++ b/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -2,6 +2,9 @@ const URI = require("urijs"); +// Known schemes to detect in a text. If a text contains `foo...bar://foo.com`, +// the parsed scheme should be `foo...bar` but if it contains +// `foo...http://foo.com`, we assume the scheme to extract will be `http`. const commonSchemes = [ "http", "https", "ftp", "sftp", @@ -16,6 +19,10 @@ function findLinks(text) { let result = []; let lastPosition = 0; + // URI.withinString() identifies URIs within text, e.g. to translate them to + // -Tags. + // See https://medialize.github.io/URI.js/docs.html#static-withinString + // In our case, we store each URI encountered in a result array. URI.withinString(text, function(url, start, end) { // v-- fix: url was modified and does not match input string -> cant be mapped if (text.indexOf(url, lastPosition) < 0) { @@ -23,19 +30,22 @@ function findLinks(text) { } // ^-- /fix: url was modified and does not match input string -> cant be mapped - // v-- fix: use prefered scheme - const parsed = URI(url); - const parsedScheme = parsed.scheme().toLowerCase(); + // Extract the scheme of the URL detected, if there is one + const parsedScheme = URI(url).scheme().toLowerCase(); + + // Check if the scheme of the detected URL matches a common one above. + // In a URL like `foo..http://example.com`, the scheme would be `foo..http`, + // so we need to clean up the end of the scheme and filter out the rest. const matchedScheme = commonSchemes.find(scheme => parsedScheme.endsWith(scheme)); + // A known scheme was found, extract the unknown part from the URL if (matchedScheme) { const prefix = parsedScheme.length - matchedScheme.length; start += prefix; url = url.slice(prefix); } - // ^-- /fix: use prefered scheme - // URL matched, but does not start with a protocol, add it + // The URL matched but does not start with a scheme (`www.foo.com`), add it if (!parsedScheme.length) { url = "http://" + url; } diff --git a/client/js/libs/handlebars/ircmessageparser/merge.js b/client/js/libs/handlebars/ircmessageparser/merge.js index 3da520e8..893997cc 100644 --- a/client/js/libs/handlebars/ircmessageparser/merge.js +++ b/client/js/libs/handlebars/ircmessageparser/merge.js @@ -16,6 +16,7 @@ if (typeof Object_assign !== "function") { }; } +// Merge text part information within a styling fragment function assign(textPart, fragment) { const fragStart = fragment.start; const start = Math.max(fragment.start, textPart.start); @@ -28,13 +29,25 @@ function assign(textPart, fragment) { }); } +// Merge the style fragments withing the text parts, taking into account +// boundaries and text sections that have not matched to links or channels. +// For example, given a string "foobar" where "foo" and "bar" have been +// identified as parts (channels, links, etc.) and "fo", "ob" and "ar" have 3 +// different styles, the first resulting part will contain fragments "fo" and +// "o", and the second resulting part will contain "b" and "ar". "o" and "b" +// fragments will contain duplicate styling attributes. function merge(textParts, styleFragments) { - const cleanText = styleFragments.map(fragment => fragment.text).join(""); + // Re-build the overall text (without control codes) from the style fragments + const cleanText = styleFragments.reduce((acc, frag) => acc + frag.text, ""); + // Every section of the original text that has not been captured in a "part" + // is filled with "text" parts, dummy objects with start/end but no extra + // metadata. const allParts = textParts .concat(fill(textParts, cleanText)) .sort((a, b) => a.start - b.start); + // Distribute the style fragments within the text parts return allParts.map(textPart => { textPart.fragments = styleFragments .filter(fragment => anyIntersection(textPart, fragment)) diff --git a/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/client/js/libs/handlebars/ircmessageparser/parseStyle.js index 54e1c191..d23d5bd6 100644 --- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -1,5 +1,6 @@ "use strict"; +// Styling control codes const BOLD = "\x02"; const COLOR = "\x03"; const RESET = "\x0f"; @@ -7,14 +8,24 @@ const REVERSE = "\x16"; const ITALIC = "\x1d"; const UNDERLINE = "\x1f"; +// Color code matcher, with format `XX,YY` where both `XX` and `YY` are +// integers, `XX` is the text color and `YY` is an optional background color. const colorRx = /^(\d{1,2})(?:,(\d{1,2}))?/; + +// Represents all other control codes that to be ignored/filtered from the text const controlCodesRx = /[\u0000-\u001F]/g; +// Converts a given text into an array of objects, each of them representing a +// similarly styled section of the text. Each object carries the `text`, style +// information (`bold`, `textColor`, `bgcolor`, `reverse`, `italic`, +// `underline`), and `start`/`end` cursors. function parseStyle(text) { const result = []; let start = 0; let position = 0; + // At any given time, these carry style information since last time a styling + // control code was met. let colorCodes, bold, textColor, bgColor, reverse, italic, underline; const resetStyle = () => { @@ -27,27 +38,42 @@ function parseStyle(text) { }; resetStyle(); + // When called, this "closes" the current fragment by adding an entry to the + // `result` array using the styling information set last time a control code + // was met. const emitFragment = () => { + // Uses the text fragment starting from the last control code position up to + // the current position const textPart = text.slice(start, position); - start = position + 1; + // Filters out all non-style related control codes present in this text const processedText = textPart.replace(controlCodesRx, ""); - if (!processedText.length) { - return; + if (processedText.length) { + // Current fragment starts where the previous one ends, or at 0 if none + const fragmentStart = result.length ? result[result.length - 1].end : 0; + + result.push({ + bold, + textColor, + bgColor, + reverse, + italic, + underline, + text: processedText, + start: fragmentStart, + end: fragmentStart + processedText.length + }); } - result.push({ - bold, - textColor, - bgColor, - reverse, - italic, - underline, - text: processedText - }); + // Now that a fragment has been "closed", the next one will start after that + start = position + 1; }; + // This loop goes through each character of the given text one by one by + // bumping the `position` cursor. Every time a new special "styling" character + // is met, an object gets created (with `emitFragment()`)information on text + // encountered since the previous styling character. while (position < text.length) { switch (text[position]) { @@ -56,6 +82,10 @@ function parseStyle(text) { resetStyle(); break; + // Meeting a BOLD character means that the ongoing text is either going to + // be in bold or that the previous one was in bold and the following one + // must be reset. + // This same behavior applies to COLOR, REVERSE, ITALIC, and UNDERLINE. case BOLD: emitFragment(); bold = !bold; @@ -64,20 +94,23 @@ function parseStyle(text) { case COLOR: emitFragment(); + // Go one step further to find the corresponding color colorCodes = text.slice(position + 1).match(colorRx); if (colorCodes) { textColor = Number(colorCodes[1]); - bgColor = Number(colorCodes[2]); - if (Number.isNaN(bgColor)) { - bgColor = undefined; + if (colorCodes[2]) { + bgColor = Number(colorCodes[2]); } + // Color code length is > 1, so bump the current position cursor by as + // much (and reset the start cursor for the current text block as well) position += colorCodes[0].length; + start = position + 1; } else { + // If no color codes were found, toggles back to no colors (like BOLD). textColor = undefined; bgColor = undefined; } - start = position + 1; break; case REVERSE: @@ -95,9 +128,12 @@ function parseStyle(text) { underline = !underline; break; } + + // Evaluate the next character at the next iteration position += 1; } + // The entire text has been parsed, so we finalize the current text fragment. emitFragment(); return result; @@ -107,25 +143,19 @@ const properties = ["bold", "textColor", "bgColor", "italic", "underline", "reve function prepare(text) { return parseStyle(text) - .filter(fragment => fragment.text.length) - .reduce((prev, curr, i) => { - if (i === 0) { - return prev.concat([curr]); + // This optimizes fragments by combining them together when all their values + // for the properties defined above are equal. + .reduce((prev, curr) => { + if (prev.length) { + const lastEntry = prev[prev.length - 1]; + if (properties.every(key => curr[key] === lastEntry[key])) { + lastEntry.text += curr.text; + lastEntry.end += curr.text.length; + return prev; + } } - - const lastEntry = prev[prev.length - 1]; - if (properties.some(key => curr[key] !== lastEntry[key])) { - return prev.concat([curr]); - } - - lastEntry.text += curr.text; - return prev; - }, []) - .map((fragment, i, array) => { - fragment.start = i === 0 ? 0 : array[i - 1].end; - fragment.end = fragment.start + fragment.text.length; - return fragment; - }); + return prev.concat([curr]); + }, []); } module.exports = prepare; diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 7e9aebb8..915a432c 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -6,6 +6,7 @@ const findChannels = require("./ircmessageparser/findChannels"); const findLinks = require("./ircmessageparser/findLinks"); const merge = require("./ircmessageparser/merge"); +// Create an HTML `span` with styling information for a given fragment function createFragment(fragment) { let classes = []; if (fragment.bold) { @@ -30,23 +31,33 @@ function createFragment(fragment) { return escapedText; } +// Transform an IRC message potentially filled with styling control codes, URLs +// and channels into a string of HTML elements to display on the client. module.exports = function parse(text) { + // Extract the styling information and get the plain text version from it const styleFragments = parseStyle(text); const cleanText = styleFragments.map(fragment => fragment.text).join(""); - const channelPrefixes = ["#", "&"]; // RPL_ISUPPORT.CHANTYPES - const userModes = ["!", "@", "%", "+"]; // RPL_ISUPPORT.PREFIX + // On the plain text, find channels and URLs, returned as "parts". Parts are + // arrays of objects containing start and end markers, as well as metadata + // depending on what was found (channel or link). + const channelPrefixes = ["#", "&"]; // TODO Channel prefixes should be RPL_ISUPPORT.CHANTYPES + const userModes = ["!", "@", "%", "+"]; // TODO User modes should be RPL_ISUPPORT.PREFIX const channelParts = findChannels(cleanText, channelPrefixes, userModes); - const linkParts = findLinks(cleanText); + // Sort all parts identified based on their position in the original text const parts = channelParts .concat(linkParts) .sort((a, b) => a.start - b.start); + // Merge the styling information with the channels / URLs / text objects and + // generate HTML strings with the resulting fragments return merge(parts, styleFragments).map(textPart => { + // Create HTML strings with styling information const fragments = textPart.fragments.map(createFragment).join(""); + // Wrap these potentially styled fragments with links and channel buttons if (textPart.link) { const escapedLink = Handlebars.Utils.escapeExpression(textPart.link); return `${fragments}`; diff --git a/test/client/js/libs/handlebars/ircmessageparser/fill.js b/test/client/js/libs/handlebars/ircmessageparser/fill.js new file mode 100644 index 00000000..8723ad52 --- /dev/null +++ b/test/client/js/libs/handlebars/ircmessageparser/fill.js @@ -0,0 +1,50 @@ +"use strict"; + +const expect = require("chai").expect; +const fill = require("../../../../../../client/js/libs/handlebars/ircmessageparser/fill"); + +describe("fill", () => { + const text = "01234567890123456789"; + + it("should return an entry for the unmatched end of string", () => { + const existingEntries = [ + {start: 0, end: 10}, + {start: 5, end: 15}, + ]; + + const expected = [ + {start: 15, end: 20}, + ]; + + const actual = fill(existingEntries, text); + + expect(actual).to.deep.equal(expected); + }); + + it("should return an entry per unmatched areas of the text", () => { + const existingEntries = [ + {start: 0, end: 5}, + {start: 10, end: 15}, + ]; + + const expected = [ + {start: 5, end: 10}, + {start: 15, end: 20}, + ]; + + const actual = fill(existingEntries, text); + + expect(actual).to.deep.equal(expected); + }); + + it("should not return anything when entries match all text", () => { + const existingEntries = [ + {start: 0, end: 10}, + {start: 10, end: 20}, + ]; + + const actual = fill(existingEntries, text); + + expect(actual).to.be.empty; + }); +}); diff --git a/test/client/js/libs/handlebars/ircmessageparser/findChannels.js b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js index 93c119ee..4c676e57 100644 --- a/test/client/js/libs/handlebars/ircmessageparser/findChannels.js +++ b/test/client/js/libs/handlebars/ircmessageparser/findChannels.js @@ -1,7 +1,7 @@ "use strict"; const expect = require("chai").expect; -const analyseText = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findChannels"); +const findChannels = require("../../../../../../client/js/libs/handlebars/ircmessageparser/findChannels"); describe("findChannels", () => { it("should find single letter channel", () => { @@ -12,7 +12,7 @@ describe("findChannels", () => { end: 2 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -25,7 +25,7 @@ describe("findChannels", () => { end: 4 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -38,7 +38,7 @@ describe("findChannels", () => { end: 15 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -51,7 +51,7 @@ describe("findChannels", () => { end: 5 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -64,7 +64,7 @@ describe("findChannels", () => { end: 6 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -77,7 +77,7 @@ describe("findChannels", () => { end: 3 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); @@ -90,7 +90,7 @@ describe("findChannels", () => { end: 6 }]; - const actual = analyseText(input, ["#"], ["!", "@", "%", "+"]); + const actual = findChannels(input, ["#"], ["!", "@", "%", "+"]); expect(actual).to.deep.equal(expected); }); @@ -103,7 +103,7 @@ describe("findChannels", () => { end: 2 }]; - const actual = analyseText(input, ["@"], ["#", "+"]); + const actual = findChannels(input, ["@"], ["#", "+"]); expect(actual).to.deep.equal(expected); }); @@ -116,7 +116,7 @@ describe("findChannels", () => { end: 6 }]; - const actual = analyseText(input, ["#"], ["@", "+"]); + const actual = findChannels(input, ["#"], ["@", "+"]); expect(actual).to.deep.equal(expected); }); From fa1aecdd9e9e0cab27e2f5f92f485e99fe69a376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Thu, 20 Apr 2017 01:37:10 -0400 Subject: [PATCH 078/355] Remove URI.js monkey-patch as fix landed in v1.18.5 See https://github.com/medialize/URI.js/issues/325 --- client/js/libs/handlebars/ircmessageparser/findLinks.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/js/libs/handlebars/ircmessageparser/findLinks.js b/client/js/libs/handlebars/ircmessageparser/findLinks.js index 9596a5a0..1bd989b2 100644 --- a/client/js/libs/handlebars/ircmessageparser/findLinks.js +++ b/client/js/libs/handlebars/ircmessageparser/findLinks.js @@ -17,19 +17,12 @@ const commonSchemes = [ function findLinks(text) { let result = []; - let lastPosition = 0; // URI.withinString() identifies URIs within text, e.g. to translate them to // -Tags. // See https://medialize.github.io/URI.js/docs.html#static-withinString // In our case, we store each URI encountered in a result array. URI.withinString(text, function(url, start, end) { - // v-- fix: url was modified and does not match input string -> cant be mapped - if (text.indexOf(url, lastPosition) < 0) { - return; - } - // ^-- /fix: url was modified and does not match input string -> cant be mapped - // Extract the scheme of the URL detected, if there is one const parsedScheme = URI(url).scheme().toLowerCase(); From 999e419636caf37b8b46e4ffdbc5c4aee0ef1081 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 21 Apr 2017 21:00:57 +0300 Subject: [PATCH 079/355] Remove cycle nicks button Reverts #708. Fixes #869. Fixes #1023. --- client/css/style.css | 11 +---------- client/index.html | 3 --- client/js/lounge.js | 6 ------ 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index d74baf61..c34216e7 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -169,7 +169,6 @@ kbd { #footer .icon, #chat .count:before, #settings #play:before, -#form #cycle-nicks:before, #form #submit:before, #chat .invite .from:before, #chat .join .from:before, @@ -216,8 +215,6 @@ kbd { #footer .help:before { content: "\f059"; /* http://fontawesome.io/icon/question/ */ } #footer .sign-out:before { content: "\f011"; /* http://fontawesome.io/icon/power-off/ */ } -#form #cycle-nicks:before { content: "\f007"; /* http://fontawesome.io/icon/user/ */ } - #form #submit:before { content: "\f1d8"; /* http://fontawesome.io/icon/paper-plane/ */ } #chat .invite .from:before { @@ -1451,22 +1448,16 @@ kbd { align-self: center; } -#form #cycle-nicks, #form #submit { color: #9ca5b4; font-size: 14px; height: 32px; transition: opacity .2s; - width: 24px; + width: 32px; -webkit-flex: 0 0 auto; flex: 0 0 auto; } -#form #submit { - margin-right: 4px; -} - -#form #cycle-nicks:hover, #form #submit:hover { opacity: .6; } diff --git a/client/index.html b/client/index.html index 83ac96ad..f3073fa3 100644 --- a/client/index.html +++ b/client/index.html @@ -70,9 +70,6 @@ --> - - - diff --git a/client/js/lounge.js b/client/js/lounge.js index 0e65b5d3..6b664e56 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -870,12 +870,6 @@ $(function() { input.trigger("click").focus(); }; - // Cycle through nicks for the current word, just like hitting "Tab" - $("#cycle-nicks").on("click", function() { - input.triggerHandler($.Event("keydown.tabcomplete", {which: 9})); - forceFocus(); - }); - $("#form").on("submit", function(e) { e.preventDefault(); forceFocus(); From 751d6690ffd3867737b71dfb849d38af9773b76a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 21 Apr 2017 20:25:48 +0000 Subject: [PATCH 080/355] chore(package): update babel-loader to version 7.0.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30154e29..2ab0302c 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ }, "devDependencies": { "babel-core": "6.24.1", - "babel-loader": "6.4.1", + "babel-loader": "7.0.0", "babel-preset-es2015": "6.24.1", "chai": "3.5.0", "eslint": "3.19.0", From 648cfd12db53ab7d4d0a31815df3147b84679bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Wed, 19 Apr 2017 02:33:05 -0400 Subject: [PATCH 081/355] Use moment on the client to display friendly dates Also, unread and date markers are now half-transparent based on their colors and not parent opacity. This is necessary to display a non-translucide tooltip. --- client/css/style.css | 12 ++++------ client/js/libs/handlebars/friendlydate.js | 13 ++++++++++ client/themes/morning.css | 5 ---- client/themes/zenburn.css | 5 ---- client/views/date-marker.tpl | 6 +++-- .../js/libs/handlebars/friendlydateTest.js | 24 +++++++++++++++++++ webpack.config.js | 1 + 7 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 client/js/libs/handlebars/friendlydate.js create mode 100644 test/client/js/libs/handlebars/friendlydateTest.js diff --git a/client/css/style.css b/client/css/style.css index d74baf61..23e2e5a5 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -841,7 +841,6 @@ kbd { #chat .unread-marker { position: relative; text-align: center; - opacity: .5; margin: 0 10px; z-index: 0; font-weight: bold; @@ -855,13 +854,13 @@ kbd { left: 0; right: 0; top: 50%; - border-top: 1px solid #e74c3c; + border-top: 1px solid rgba(231, 76, 60, .5); } #chat .unread-marker-text:before { content: "New messages"; background-color: white; - color: #e74c3c; + color: rgba(231, 76, 60, .5); padding: 0 10px; } @@ -872,7 +871,6 @@ kbd { #chat .date-marker { position: relative; text-align: center; - opacity: .5; margin: 0 10px; z-index: 0; font-weight: bold; @@ -886,13 +884,13 @@ kbd { left: 0; right: 0; top: 50%; - border-top: 1px solid #006b3b; + border-top: 1px solid rgba(0, 107, 59, .5); } #chat .date-marker-text:before { - content: attr(data-date); + content: attr(data-label); background-color: white; - color: #006b3b; + color: rgba(0, 107, 59, .5); padding: 0 10px; } diff --git a/client/js/libs/handlebars/friendlydate.js b/client/js/libs/handlebars/friendlydate.js new file mode 100644 index 00000000..92e81080 --- /dev/null +++ b/client/js/libs/handlebars/friendlydate.js @@ -0,0 +1,13 @@ +"use strict"; + +const moment = require("moment"); + +module.exports = function(time) { + // See http://momentjs.com/docs/#/displaying/calendar-time/ + return moment(new Date(time)).calendar(null, { + sameDay: "[Today]", + lastDay: "[Yesterday]", + lastWeek: "L", // Locale + sameElse: "L" + }); +}; diff --git a/client/themes/morning.css b/client/themes/morning.css index 9698330c..d5ca62e9 100644 --- a/client/themes/morning.css +++ b/client/themes/morning.css @@ -155,11 +155,6 @@ body { border-color: #333c4a; } -#chat .unread-marker, -#chat .date-marker { - opacity: 1; -} - #chat .unread-marker-text:before { background-color: #333c4a; } diff --git a/client/themes/zenburn.css b/client/themes/zenburn.css index 2c075412..b4abd4ed 100644 --- a/client/themes/zenburn.css +++ b/client/themes/zenburn.css @@ -181,11 +181,6 @@ body { border-color: #3f3f3f; } -#chat .unread-marker, -.date-marker { - opacity: 1; -} - #chat .unread-marker-text:before { background-color: #3f3f3f; } diff --git a/client/views/date-marker.tpl b/client/views/date-marker.tpl index bf9d89c7..3e5ec60d 100644 --- a/client/views/date-marker.tpl +++ b/client/views/date-marker.tpl @@ -1,3 +1,5 @@ -
- +
+
+ +
diff --git a/test/client/js/libs/handlebars/friendlydateTest.js b/test/client/js/libs/handlebars/friendlydateTest.js new file mode 100644 index 00000000..16f335c3 --- /dev/null +++ b/test/client/js/libs/handlebars/friendlydateTest.js @@ -0,0 +1,24 @@ +"use strict"; + +const expect = require("chai").expect; +const moment = require("moment"); +const friendlydate = require("../../../../../client/js/libs/handlebars/friendlydate"); + +describe("friendlydate Handlebars helper", () => { + it("should render 'Today' as a human-friendly date", () => { + const time = new Date().getTime(); + expect(friendlydate(time)).to.equal("Today"); + }); + + it("should render 'Yesterday' as a human-friendly date", () => { + const time = new Date().getTime() - 24 * 3600 * 1000; + expect(friendlydate(time)).to.equal("Yesterday"); + }); + + it("should not render any friendly dates prior to the day before", () => { + [2, 7, 30, 365, 1000].forEach(day => { + const time = new Date().getTime() - 24 * 3600 * 1000 * day; + expect(friendlydate(time)).to.equal(moment(time).format("L")); + }); + }); +}); diff --git a/webpack.config.js b/webpack.config.js index c71979f4..93514dca 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,6 +14,7 @@ let config = { "handlebars/runtime", "jquery", "jquery-ui/ui/widgets/sortable", + "moment", "mousetrap", "socket.io-client", "urijs", From 5fabf2f61ac1a2c6fc4c56d03bce0a4a167ed4c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Sat, 22 Apr 2017 00:06:07 -0400 Subject: [PATCH 082/355] Make sure friendly date markers are reset at midnight --- client/js/lounge.js | 20 ++++++++++++++++++++ client/views/date-marker.tpl | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 41505797..ec18b3f1 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -3,6 +3,7 @@ // vendor libraries require("jquery-ui/ui/widgets/sortable"); const $ = require("jquery"); +const moment = require("moment"); const Mousetrap = require("mousetrap"); const URI = require("urijs"); @@ -1593,6 +1594,25 @@ $(function() { } }); + // Compute how many milliseconds are remaining until the next day starts + function msUntilNextDay() { + return moment().add(1, "day").startOf("day") - moment(); + } + + // Go through all Today/Yesterday date markers in the DOM and recompute their + // labels. When done, restart the timer for the next day. + function updateDateMarkers() { + $(".date-marker-text[data-label='Today'], .date-marker-text[data-label='Yesterday']") + .closest(".date-marker-container") + .each(function() { + $(this).replaceWith(templates.date_marker({msgDate: $(this).data("timestamp")})); + }); + + // This should always be 24h later but re-computing exact value just in case + setTimeout(updateDateMarkers, msUntilNextDay()); + } + setTimeout(updateDateMarkers, msUntilNextDay()); + // Only start opening socket.io connection after all events have been registered socket.open(); diff --git a/client/views/date-marker.tpl b/client/views/date-marker.tpl index 3e5ec60d..9e67f09f 100644 --- a/client/views/date-marker.tpl +++ b/client/views/date-marker.tpl @@ -1,4 +1,4 @@ -
+
From a1bdd6f740aa339f8ada5264c5eb989a8925461b Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 13:25:36 +0300 Subject: [PATCH 083/355] Rewrite server code of channel sorting Fixes server crash, fixes losing channels --- src/client.js | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/client.js b/src/client.js index 27387778..d7145acd 100644 --- a/src/client.js +++ b/src/client.js @@ -412,44 +412,40 @@ Client.prototype.open = function(socketId, target) { }; Client.prototype.sort = function(data) { - var self = this; + const order = data.order; - var type = data.type; - var order = data.order || []; - var sorted = []; + if (!_.isArray(order)) { + return; + } - switch (type) { + switch (data.type) { case "networks": - order.forEach(i => { - var find = _.find(self.networks, {id: i}); - if (find) { - sorted.push(find); - } + this.networks.sort((a, b) => { + return order.indexOf(a.id) > order.indexOf(b.id); }); - self.networks = sorted; + + // Sync order to connected clients + this.emit("sync_sort", {order: this.networks.map(obj => obj.id), type: data.type, target: data.target}); + break; case "channels": - var target = data.target; - var network = _.find(self.networks, {id: target}); + var network = _.find(this.networks, {id: data.target}); if (!network) { return; } - order.forEach(i => { - var find = _.find(network.channels, {id: i}); - if (find) { - sorted.push(find); - } + + network.channels.sort((a, b) => { + return order.indexOf(a.id) > order.indexOf(b.id); }); - network.channels = sorted; + + // Sync order to connected clients + this.emit("sync_sort", {order: network.channels.map(obj => obj.id), type: data.type, target: data.target}); + break; } - self.save(); - - // Sync order to connected clients - const syncOrder = sorted.map(obj => obj.id); - self.emit("sync_sort", {order: syncOrder, type: type, target: data.target}); + this.save(); }; Client.prototype.names = function(data) { From 1e504f438333eea92a02f4862ba0d0d90bb4d7ea Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Sat, 22 Apr 2017 13:51:21 +0100 Subject: [PATCH 084/355] Add support for banlist messages --- client/css/style.css | 12 ++++++-- client/index.html | 9 ++++++ client/js/lounge.js | 4 ++- client/views/actions/ban_list.tpl | 18 ++++++++++++ client/views/index.js | 1 + src/client.js | 1 + src/models/msg.js | 3 +- src/plugins/inputs/mode.js | 13 +++++++-- src/plugins/irc-events/banlist.js | 48 +++++++++++++++++++++++++++++++ 9 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 client/views/actions/ban_list.tpl create mode 100644 src/plugins/irc-events/banlist.js diff --git a/client/css/style.css b/client/css/style.css index d74baf61..b483f0a1 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1005,13 +1005,16 @@ kbd { padding-left: 20px; } -#chat table.channel-list { +#chat table.channel-list, +#chat table.ban-list { margin: 5px 10px; width: calc(100% - 30px); } #chat table.channel-list th, -#chat table.channel-list td { +#chat table.ban-list th, +#chat table.channel-list td, +#chat table.ban-list td { padding: 5px; vertical-align: top; border-bottom: #eee 1px solid; @@ -1022,7 +1025,10 @@ kbd { } #chat table.channel-list .channel, -#chat table.channel-list .topic { +#chat table.channel-list .topic, +#chat table.ban-list .hostmask, +#chat table.ban-list .banned_by, +#chat table.ban-list .banned_at { text-align: left; } diff --git a/client/index.html b/client/index.html index 7d751664..58884e6f 100644 --- a/client/index.html +++ b/client/index.html @@ -554,6 +554,15 @@
+
+
+ /banlist +
+
+

Load the banlist for the current channel.

+
+
+
/clear diff --git a/client/js/lounge.js b/client/js/lounge.js index 41505797..ab5713dc 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -20,6 +20,7 @@ $(function() { var commands = [ "/away", "/back", + "/banlist", "/close", "/connect", "/deop", @@ -247,6 +248,7 @@ $(function() { "whois", "ctcp", "channel_list", + "ban_list", ].indexOf(type) !== -1) { template = "msg_action"; } else if (type === "unhandled") { @@ -379,7 +381,7 @@ $(function() { var target = "#chan-" + data.chan; var container = chat.find(target + " .messages"); - if (data.msg.type === "channel_list") { + if (data.msg.type === "channel_list" || data.msg.type === "ban_list") { $(container).empty(); } diff --git a/client/views/actions/ban_list.tpl b/client/views/actions/ban_list.tpl new file mode 100644 index 00000000..c73ba7dc --- /dev/null +++ b/client/views/actions/ban_list.tpl @@ -0,0 +1,18 @@ + + + + + + + + + + {{#each bans}} + + + + + + {{/each}} + +
BannedBanned ByBanned At
{{hostmask}}{{{parse banned_by}}}{{{localetime banned_at}}}
diff --git a/client/views/index.js b/client/views/index.js index 0b92ab8e..fa1916ee 100644 --- a/client/views/index.js +++ b/client/views/index.js @@ -3,6 +3,7 @@ module.exports = { actions: { action: require("./actions/action.tpl"), + ban_list: require("./actions/ban_list.tpl"), channel_list: require("./actions/channel_list.tpl"), ctcp: require("./actions/ctcp.tpl"), invite: require("./actions/invite.tpl"), diff --git a/src/client.js b/src/client.js index 27387778..c7c57efb 100644 --- a/src/client.js +++ b/src/client.js @@ -17,6 +17,7 @@ var id = 0; var events = [ "connection", "unhandled", + "banlist", "ctcp", "error", "invite", diff --git a/src/models/msg.js b/src/models/msg.js index a3ce4bc8..3b36256e 100644 --- a/src/models/msg.js +++ b/src/models/msg.js @@ -20,7 +20,8 @@ Msg.Type = { CTCP: "ctcp", TOPIC: "topic", TOPIC_SET_BY: "topic_set_by", - WHOIS: "whois" + WHOIS: "whois", + BANLIST: "ban_list" }; module.exports = Msg; diff --git a/src/plugins/inputs/mode.js b/src/plugins/inputs/mode.js index b6ee5d80..8395da15 100644 --- a/src/plugins/inputs/mode.js +++ b/src/plugins/inputs/mode.js @@ -1,11 +1,10 @@ "use strict"; -exports.commands = ["mode", "op", "voice", "deop", "devoice"]; - var Chan = require("../../models/chan"); var Msg = require("../../models/msg"); exports.commands = [ + "banlist", "mode", "op", "deop", @@ -15,6 +14,10 @@ exports.commands = [ "devoice", ]; +const chanCommands = [ + "banlist" +]; + exports.input = function(network, chan, cmd, args) { if (cmd !== "mode") { if (chan.type !== Chan.Type.CHANNEL) { @@ -26,7 +29,7 @@ exports.input = function(network, chan, cmd, args) { return; } - if (args.length === 0) { + if (args.length === 0 && chanCommands.indexOf(cmd) === -1) { chan.pushMessage(this, new Msg({ type: Msg.Type.ERROR, text: `Usage: /${cmd} [...nick]` @@ -36,6 +39,7 @@ exports.input = function(network, chan, cmd, args) { } const mode = { + banlist: "+b", op: "+o", hop: "+h", voice: "+v", @@ -44,6 +48,9 @@ exports.input = function(network, chan, cmd, args) { devoice: "-v" }[cmd]; + if (chanCommands.indexOf(cmd) > -1 && args.length === 0) { + network.irc.raw("MODE", chan.name, mode); + } args.forEach(function(target) { network.irc.raw("MODE", chan.name, mode, target); }); diff --git a/src/plugins/irc-events/banlist.js b/src/plugins/irc-events/banlist.js new file mode 100644 index 00000000..d45d81cc --- /dev/null +++ b/src/plugins/irc-events/banlist.js @@ -0,0 +1,48 @@ +"use strict"; + +const Chan = require("../../models/chan"); +const Msg = require("../../models/msg"); + +module.exports = function(irc, network) { + const client = this; + + irc.on("banlist", function(banlist) { + const channel = banlist.channel; + const bans = banlist.bans; + if (!bans) { + const msg = new Msg({ + time: Date.now(), + type: Msg.Type.ERROR, + text: "Banlist empty" + }); + network.getChannel(channel).pushMessage(client, msg, true); + return; + } + + const chanName = `Banlist for ${channel}`; + let chan = network.getChannel(chanName); + if (typeof chan === "undefined") { + chan = new Chan({ + type: Chan.Type.SPECIAL, + name: chanName + }); + network.channels.push(chan); + client.emit("join", { + network: network.id, + chan: chan + }); + } + + chan.pushMessage(client, + new Msg({ + type: Msg.Type.BANLIST, + bans: bans.map((data) => ({ + hostmask: data.banned, + banned_by: data.banned_by, + banned_at: data.banned_at * 1000 + })) + }), + true + ); + }); +}; From 934400f5ee094e61c62dd0304cb55ea9f9666078 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 16:04:18 +0300 Subject: [PATCH 085/355] Do not build feature branch with open pull requests on AppVeyor Ref: https://github.com/appveyor/ci/issues/882 --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index e36f70f2..25eba312 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,6 +7,9 @@ version: "{build}" # Do not build on tags (GitHub only) skip_tags: true +# Do not build feature branch with open pull requests +skip_branch_with_pr: true + environment: nodejs_version: '4' From 7522847eccdfd70a6baeed0e12af08b367e90eef Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 19:04:46 +0300 Subject: [PATCH 086/355] Enable show more button correctly --- client/js/lounge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index ab5713dc..0de7a10e 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -464,7 +464,7 @@ $(function() { lastDate = msgDate; }); - scrollable.find(".show-more").prop("disabled", false); + scrollable.find(".show-more-button").prop("disabled", false); }); socket.on("network", function(data) { From d1ecdb6b523ae12c8dab6540b4480f94d5b95044 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 19:05:58 +0300 Subject: [PATCH 087/355] Fix displayNetwork to work correctly --- client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/index.html b/client/index.html index 58884e6f..81f63d04 100644 --- a/client/index.html +++ b/client/index.html @@ -131,7 +131,7 @@ {{/unless}}
- {{#unless displayNetwork}} + {{#if displayNetwork}}

Network settings

@@ -168,7 +168,7 @@
- {{/unless}} + {{/if}}

User preferences

From 2e286849fc890164c687ab9dd54eec0217709031 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 18 Apr 2017 08:42:26 +0100 Subject: [PATCH 088/355] Move commands into constants module --- client/js/constants.js | 37 +++++++++++++++++++++++++++++++++++++ client/js/lounge.js | 35 ++--------------------------------- 2 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 client/js/constants.js diff --git a/client/js/constants.js b/client/js/constants.js new file mode 100644 index 00000000..546637fd --- /dev/null +++ b/client/js/constants.js @@ -0,0 +1,37 @@ +"use strict"; + +const commands = [ + "/away", + "/back", + "/banlist", + "/close", + "/connect", + "/deop", + "/devoice", + "/disconnect", + "/invite", + "/join", + "/kick", + "/leave", + "/me", + "/mode", + "/msg", + "/nick", + "/notice", + "/op", + "/part", + "/query", + "/quit", + "/raw", + "/say", + "/send", + "/server", + "/slap", + "/topic", + "/voice", + "/whois" +]; + +module.exports = { + commands: commands +}; diff --git a/client/js/lounge.js b/client/js/lounge.js index 0de7a10e..9c8c7b1e 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -15,40 +15,9 @@ const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber"); const slideoutMenu = require("./libs/slideout"); const templates = require("../views"); const socket = require("./socket"); +const constants = require("./constants"); $(function() { - var commands = [ - "/away", - "/back", - "/banlist", - "/close", - "/connect", - "/deop", - "/devoice", - "/disconnect", - "/invite", - "/join", - "/kick", - "/leave", - "/me", - "/mode", - "/msg", - "/nick", - "/notice", - "/op", - "/part", - "/query", - "/quit", - "/raw", - "/say", - "/send", - "/server", - "/slap", - "/topic", - "/voice", - "/whois" - ]; - var sidebar = $("#sidebar, #footer"); var chat = $("#chat"); @@ -1422,7 +1391,7 @@ $(function() { } function complete(word) { - var words = commands.slice(); + var words = constants.commands.slice(); var users = chat.find(".active").find(".users"); var nicks = users.data("nicks"); From 184dd177a6b49edc121db46f04cc997807e4f84c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 23 Apr 2017 02:13:17 +0000 Subject: [PATCH 089/355] fix(package): update irc-framework to version 2.7.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c2edd45..c98022dd 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "express": "4.15.2", "express-handlebars": "3.0.0", "fs-extra": "2.1.2", - "irc-framework": "2.6.1", + "irc-framework": "2.7.0", "ldapjs": "1.0.1", "lodash": "4.17.4", "moment": "2.18.1", From 57acf0f5ce197c9a1150dedfce7d55d285bbf0f1 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 23 Apr 2017 09:42:16 +0300 Subject: [PATCH 090/355] Revert "Disable (temporarily) client ping timeouts" This reverts commit ffa0740b50977704cba026869f99cdc72af8e16c. --- src/client.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/client.js b/src/client.js index c7c57efb..f9eaa3cd 100644 --- a/src/client.js +++ b/src/client.js @@ -264,7 +264,6 @@ Client.prototype.connect = function(args) { auto_reconnect: true, auto_reconnect_wait: 10000 + Math.floor(Math.random() * 1000), // If multiple users are connected to the same network, randomize their reconnections a little auto_reconnect_max_retries: 360, // At least one hour (plus timeouts) worth of reconnections - ping_interval: 0, // Disable client ping timeouts due to buggy implementation webirc: webirc, }); From 2cfc9119cb8749ec92046b1c5c61e49062f2f3c3 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 00:27:18 +0300 Subject: [PATCH 091/355] Use babel-preset-env --- package.json | 2 +- webpack.config.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4783cf1e..254aae0a 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "devDependencies": { "babel-core": "6.24.1", "babel-loader": "7.0.0", - "babel-preset-es2015": "6.24.1", + "babel-preset-env": "1.4.0", "chai": "3.5.0", "eslint": "3.19.0", "font-awesome": "4.7.0", diff --git a/webpack.config.js b/webpack.config.js index 93514dca..b54015cb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -37,7 +37,11 @@ let config = { loader: "babel-loader", options: { presets: [ - "es2015" + ["env", { + targets: { + browsers: "last 2 versions" + } + }] ] } } From 36a7cf4007e4c317538ac92fe68f9ec478bae81a Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sun, 23 Apr 2017 18:13:49 +0000 Subject: [PATCH 092/355] fix(package): update irc-framework to version 2.8.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a23c0366..f7739fd8 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "express": "4.15.2", "express-handlebars": "3.0.0", "fs-extra": "2.1.2", - "irc-framework": "2.7.0", + "irc-framework": "2.8.0", "ldapjs": "1.0.1", "lodash": "4.17.4", "moment": "2.18.1", From 6a26014b81004416f32a23ead7d770e96f6622f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Fri, 30 Dec 2016 00:32:27 -0500 Subject: [PATCH 093/355] Implement fuzzy-matching for the user list --- client/js/lounge.js | 31 +++++++++++++++++++++---------- package.json | 1 + webpack.config.js | 1 + 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 1857339e..4090b898 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -6,6 +6,7 @@ const $ = require("jquery"); const moment = require("moment"); const Mousetrap = require("mousetrap"); const URI = require("urijs"); +const fuzzy = require("fuzzy"); // our libraries require("./libs/jquery/inputhistory"); @@ -1067,16 +1068,26 @@ $(function() { }); chat.on("input", ".search", function() { - var value = $(this).val().toLowerCase(); - var names = $(this).closest(".users").find(".names"); - names.find(".user").each(function() { - var btn = $(this); - var name = btn.text().toLowerCase().replace(/[+%@~]/, ""); - if (name.indexOf(value) > -1) { - btn.show(); - } else { - btn.hide(); - } + const value = $(this).val().toLowerCase(); + const names = $(this).closest(".users").find(".names"); + + names.find(".user").each((i, el) => { + $(el).text($(el).text().replace(/<\/?b>;/, "")).hide(); + }); + + const fuzzyOptions = { + pre: "", + post: "", + extract: el => $(el).text().toLowerCase().replace(/[+%@~]/, "") + }; + + fuzzy.filter( + value, + names.find(".user").toArray(), + fuzzyOptions + ).forEach(el => { + const firstChar = $(el.original).text()[0].replace(/[^+%@~]/, ""); + $(el.original).html(firstChar + el.string).show(); }); }); diff --git a/package.json b/package.json index a23c0366..d2d88b0d 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "chai": "3.5.0", "eslint": "3.19.0", "font-awesome": "4.7.0", + "fuzzy": "0.1.3", "handlebars": "4.0.6", "handlebars-loader": "1.5.0", "jquery": "3.2.1", diff --git a/webpack.config.js b/webpack.config.js index b54015cb..cf2e9990 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -18,6 +18,7 @@ let config = { "mousetrap", "socket.io-client", "urijs", + "fuzzy", ], }, devtool: "source-map", From cfa9da17a728ea0e2b8799c2cf6df7caca018b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Sun, 1 Jan 2017 21:20:48 -0500 Subject: [PATCH 094/355] Rely on fuzzy's case insensitivity, do not trim mode --- client/js/lounge.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 4090b898..5fd07abe 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1068,7 +1068,7 @@ $(function() { }); chat.on("input", ".search", function() { - const value = $(this).val().toLowerCase(); + const value = $(this).val(); const names = $(this).closest(".users").find(".names"); names.find(".user").each((i, el) => { @@ -1078,7 +1078,7 @@ $(function() { const fuzzyOptions = { pre: "", post: "", - extract: el => $(el).text().toLowerCase().replace(/[+%@~]/, "") + extract: el => $(el).text() }; fuzzy.filter( @@ -1086,8 +1086,7 @@ $(function() { names.find(".user").toArray(), fuzzyOptions ).forEach(el => { - const firstChar = $(el.original).text()[0].replace(/[^+%@~]/, ""); - $(el.original).html(firstChar + el.string).show(); + $(el.original).html(el.string).show(); }); }); From b1e9a7ffda5bdca68b29c9229d8f954b2d9b0cb0 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 28 Jan 2017 19:37:26 +0200 Subject: [PATCH 095/355] Use separate container for search results --- client/css/style.css | 4 ++++ client/js/lounge.js | 21 +++++++++++++-------- client/views/index.js | 1 + client/views/user.tpl | 3 ++- client/views/user_filtered.tpl | 5 +++++ 5 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 client/views/user_filtered.tpl diff --git a/client/css/style.css b/client/css/style.css index 7fbd6492..1327f271 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1219,6 +1219,10 @@ kbd { content: "Users"; } +#chat .user-mode-search:before { + content: "Search Results"; +} + #loading { font-size: 14px; z-index: 1; diff --git a/client/js/lounge.js b/client/js/lounge.js index 5fd07abe..3d27ec68 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1069,11 +1069,15 @@ $(function() { chat.on("input", ".search", function() { const value = $(this).val(); - const names = $(this).closest(".users").find(".names"); + const parent = $(this).closest(".users"); + const names = parent.find(".names-original"); + const container = parent.find(".names-filtered"); - names.find(".user").each((i, el) => { - $(el).text($(el).text().replace(/<\/?b>;/, "")).hide(); - }); + if (!value.length) { + container.hide(); + names.show(); + return; + } const fuzzyOptions = { pre: "", @@ -1081,13 +1085,14 @@ $(function() { extract: el => $(el).text() }; - fuzzy.filter( + const result = fuzzy.filter( value, names.find(".user").toArray(), fuzzyOptions - ).forEach(el => { - $(el.original).html(el.string).show(); - }); + ); + + names.hide(); + container.html(templates.user_filtered({matches: result})).show(); }); chat.on("msg", ".messages", function(e, target, msg) { diff --git a/client/views/index.js b/client/views/index.js index fa1916ee..201690ee 100644 --- a/client/views/index.js +++ b/client/views/index.js @@ -30,4 +30,5 @@ module.exports = { toggle: require("./toggle.tpl"), unread_marker: require("./unread_marker.tpl"), user: require("./user.tpl"), + user_filtered: require("./user_filtered.tpl"), }; diff --git a/client/views/user.tpl b/client/views/user.tpl index 23a3a9f0..29a6872c 100644 --- a/client/views/user.tpl +++ b/client/views/user.tpl @@ -2,8 +2,9 @@
+
{{/if}} -
+
{{#diff "reset"}}{{/diff}} {{#each users}} {{#diff mode}} diff --git a/client/views/user_filtered.tpl b/client/views/user_filtered.tpl new file mode 100644 index 00000000..1b86f99d --- /dev/null +++ b/client/views/user_filtered.tpl @@ -0,0 +1,5 @@ + From c583d6edf9d01276e6be03e372146bd44dae452c Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sat, 22 Apr 2017 15:49:01 +0300 Subject: [PATCH 096/355] Correctly update user list and search filtering on user updates --- client/css/style.css | 19 +++++++------------ client/js/libs/handlebars/users.js | 5 ----- client/js/lounge.js | 19 +++++++++++++++++-- client/views/chat.tpl | 10 +++++++++- client/views/user.tpl | 28 ++++++++++------------------ 5 files changed, 43 insertions(+), 38 deletions(-) delete mode 100644 client/js/libs/handlebars/users.js diff --git a/client/css/style.css b/client/css/style.css index 1327f271..89b01c94 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -768,6 +768,9 @@ kbd { overflow: auto; -webkit-overflow-scrolling: touch; position: absolute; +} + +#chat .channel .chat { right: 180px; } @@ -791,18 +794,6 @@ kbd { transform: translateZ(0); } -#chat .lobby .chat, -#chat .special .chat, -#chat .query .chat { - right: 0; -} - -#chat .lobby .sidebar, -#chat .special .sidebar, -#chat .query .sidebar { - display: none; -} - #chat .show-more { display: none; padding: 10px; @@ -1180,6 +1171,10 @@ kbd { width: 100%; } +#chat .names-filtered { + display: none; +} + #chat .names .user { display: block; line-height: 1.6; diff --git a/client/js/libs/handlebars/users.js b/client/js/libs/handlebars/users.js deleted file mode 100644 index d962423c..00000000 --- a/client/js/libs/handlebars/users.js +++ /dev/null @@ -1,5 +0,0 @@ -"use strict"; - -module.exports = function(count) { - return count + " " + (count === 1 ? "user" : "users"); -}; diff --git a/client/js/lounge.js b/client/js/lounge.js index 3d27ec68..a0e52e60 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -258,7 +258,10 @@ $(function() { function renderChannel(data) { renderChannelMessages(data); - renderChannelUsers(data); + + if (data.type === "channel") { + renderChannelUsers(data); + } } function renderChannelMessages(data) { @@ -318,7 +321,19 @@ $(function() { return (oldSortOrder[a] || Number.MAX_VALUE) - (oldSortOrder[b] || Number.MAX_VALUE); }); - users.html(templates.user(data)).data("nicks", nicks); + const search = users + .find(".search") + .attr("placeholder", nicks.length + " " + (nicks.length === 1 ? "user" : "users")); + + users + .find(".names-original") + .html(templates.user(data)) + .data("nicks", nicks); + + // Refresh user search + if (search.val().length) { + search.trigger("input"); + } } function renderNetworks(data) { diff --git a/client/views/chat.tpl b/client/views/chat.tpl index 9ccb6391..898dd75b 100644 --- a/client/views/chat.tpl +++ b/client/views/chat.tpl @@ -19,8 +19,16 @@
+ {{#equal type "channel"}} + {{/equal}}
{{/each}} diff --git a/client/views/user.tpl b/client/views/user.tpl index 29a6872c..b0af2a0c 100644 --- a/client/views/user.tpl +++ b/client/views/user.tpl @@ -1,19 +1,11 @@ -{{#if users.length}} -
- -
-
-{{/if}} -
- {{#diff "reset"}}{{/diff}} - {{#each users}} - {{#diff mode}} - {{#unless @first}} -
- {{/unless}} -
- {{/diff}} - {{mode}}{{name}} - {{/each}} -
+{{#diff "reset"}}{{/diff}} +{{#each users}} + {{#diff mode}} + {{#unless @first}} +
+ {{/unless}} +
+ {{/diff}} + {{mode}}{{name}} +{{/each}}
From 326f1ac47633c71452752f53850a18630411eadb Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Sat, 22 Apr 2017 14:03:00 +0100 Subject: [PATCH 097/355] Create options module --- client/js/localStorage.js | 19 +++++ client/js/lounge.js | 149 +++----------------------------------- client/js/options.js | 124 +++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 137 deletions(-) create mode 100644 client/js/localStorage.js create mode 100644 client/js/options.js diff --git a/client/js/localStorage.js b/client/js/localStorage.js new file mode 100644 index 00000000..3b08b3bd --- /dev/null +++ b/client/js/localStorage.js @@ -0,0 +1,19 @@ +"use strict"; + +module.exports = { + set: function(key, value) { + try { + window.localStorage.setItem(key, value); + } catch (e) { + // Do nothing. If we end up here, web storage quota exceeded, or user is + // in Safari's private browsing where localStorage's setItem is not + // available. See http://stackoverflow.com/q/14555347/1935861. + } + }, + get: function(key) { + return window.localStorage.getItem(key); + }, + remove: function(key, value) { + window.localStorage.removeItem(key, value); + } +}; diff --git a/client/js/lounge.js b/client/js/lounge.js index 1857339e..40a52b34 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -17,6 +17,7 @@ const slideoutMenu = require("./libs/slideout"); const templates = require("../views"); const socket = require("./socket"); const constants = require("./constants"); +const storage = require("./localStorage"); $(function() { var sidebar = $("#sidebar, #footer"); @@ -40,15 +41,6 @@ $(function() { var favicon = $("#favicon"); - function setLocalStorageItem(key, value) { - try { - window.localStorage.setItem(key, value); - } catch (e) { - // Do nothing. If we end up here, web storage quota exceeded, or user is - // in Safari's private browsing where localStorage's setItem is not - // available. See http://stackoverflow.com/q/14555347/1935861. - } - } socket.on("auth", function(data) { var login = $("#sign-in"); var token; @@ -56,14 +48,14 @@ $(function() { login.find(".btn").prop("disabled", false); if (!data.success) { - window.localStorage.removeItem("token"); + storage.remove("token"); var error = login.find(".error"); error.show().closest("form").one("submit", function() { error.hide(); }); } else { - token = window.localStorage.getItem("token"); + token = storage.get("token"); if (token) { $("#loading-page-message").text("Authorizing…"); socket.emit("auth", {token: token}); @@ -72,7 +64,7 @@ $(function() { var input = login.find("input[name='user']"); if (input.val() === "") { - input.val(window.localStorage.getItem("user") || ""); + input.val(storage.get("user") || ""); } if (token) { return; @@ -106,8 +98,8 @@ $(function() { }); } - if (data.token && window.localStorage.getItem("token") !== null) { - setLocalStorageItem("token", data.token); + if (data.token && storage.get("token") !== null) { + storage.set("token", data.token); } passwordForm @@ -130,9 +122,9 @@ $(function() { } if (data.token && $("#sign-in-remember").is(":checked")) { - setLocalStorageItem("token", data.token); + storage.set("token", data.token); } else { - window.localStorage.removeItem("token"); + storage.remove("token"); } $("body").removeClass("signed-out"); @@ -198,7 +190,7 @@ $(function() { var chan = chat.find(target); var template = "msg"; - if (!data.msg.highlight && !data.msg.self && (type === "message" || type === "notice") && highlights.some(function(h) { + if (!data.msg.highlight && !data.msg.self && (type === "message" || type === "notice") && options.highlights.some(function(h) { return data.msg.text.toLocaleLowerCase().indexOf(h.toLocaleLowerCase()) > -1; })) { data.msg.highlight = true; @@ -527,127 +519,10 @@ $(function() { socket.on("names", renderChannelUsers); - var userStyles = $("#user-specified-css"); - var highlights = []; - var options = $.extend({ - coloredNicks: true, - desktopNotifications: false, - join: true, - links: true, - mode: true, - motd: true, - nick: true, - notification: true, - notifyAllMessages: false, - part: true, - quit: true, - theme: $("#theme").attr("href").replace(/^themes\/(.*).css$/, "$1"), // Extracts default theme name, set on the server configuration - thumbnails: true, - userStyles: userStyles.text(), - }, JSON.parse(window.localStorage.getItem("settings"))); + var options = require("./options"); var windows = $("#windows"); - (function SettingsScope() { - var settings = $("#settings"); - - for (var i in options) { - if (i === "userStyles") { - if (!/[?&]nocss/.test(window.location.search)) { - $(document.head).find("#user-specified-css").html(options[i]); - } - settings.find("#user-specified-css-input").val(options[i]); - } else if (i === "highlights") { - settings.find("input[name=" + i + "]").val(options[i]); - } else if (i === "theme") { - $("#theme").attr("href", "themes/" + options[i] + ".css"); - settings.find("select[name=" + i + "]").val(options[i]); - } else if (options[i]) { - settings.find("input[name=" + i + "]").prop("checked", true); - } - } - - settings.on("change", "input, select, textarea", function() { - var self = $(this); - var name = self.attr("name"); - - if (self.attr("type") === "checkbox") { - options[name] = self.prop("checked"); - } else { - options[name] = self.val(); - } - - setLocalStorageItem("settings", JSON.stringify(options)); - - if ([ - "join", - "mode", - "motd", - "nick", - "part", - "quit", - "notifyAllMessages", - ].indexOf(name) !== -1) { - chat.toggleClass("hide-" + name, !self.prop("checked")); - } else if (name === "coloredNicks") { - chat.toggleClass("colored-nicks", self.prop("checked")); - } else if (name === "theme") { - $("#theme").attr("href", "themes/" + options[name] + ".css"); - } else if (name === "userStyles") { - userStyles.html(options[name]); - } else if (name === "highlights") { - var highlightString = options[name]; - highlights = highlightString.split(",").map(function(h) { - return h.trim(); - }).filter(function(h) { - // Ensure we don't have empty string in the list of highlights - // otherwise, users get notifications for everything - return h !== ""; - }); - } - }).find("input") - .trigger("change"); - - $("#desktopNotifications").on("change", function() { - if ($(this).prop("checked") && Notification.permission !== "granted") { - Notification.requestPermission(updateDesktopNotificationStatus); - } - }); - - // Updates the checkbox and warning in settings when the Settings page is - // opened or when the checkbox state is changed. - // When notifications are not supported, this is never called (because - // checkbox state can not be changed). - var updateDesktopNotificationStatus = function() { - if (Notification.permission === "denied") { - desktopNotificationsCheckbox.attr("disabled", true); - desktopNotificationsCheckbox.attr("checked", false); - warningBlocked.show(); - } else { - if (Notification.permission === "default" && desktopNotificationsCheckbox.prop("checked")) { - desktopNotificationsCheckbox.attr("checked", false); - } - desktopNotificationsCheckbox.attr("disabled", false); - warningBlocked.hide(); - } - }; - - // If browser does not support notifications, override existing settings and - // display proper message in settings. - var desktopNotificationsCheckbox = $("#desktopNotifications"); - var warningUnsupported = $("#warnUnsupportedDesktopNotifications"); - var warningBlocked = $("#warnBlockedDesktopNotifications"); - warningBlocked.hide(); - if (("Notification" in window)) { - warningUnsupported.hide(); - windows.on("show", "#settings", updateDesktopNotificationStatus); - } else { - options.desktopNotifications = false; - desktopNotificationsCheckbox.attr("disabled", true); - desktopNotificationsCheckbox.attr("checked", false); - } - }()); - var viewport = $("#viewport"); var sidebarSlide = slideoutMenu(viewport[0], sidebar[0]); var contextMenuContainer = $("#context-menu-container"); @@ -1027,7 +902,7 @@ $(function() { }); sidebar.on("click", "#sign-out", function() { - window.localStorage.removeItem("token"); + storage.remove("token"); location.reload(); }); @@ -1236,7 +1111,7 @@ $(function() { } }); if (values.user) { - setLocalStorageItem("user", values.user); + storage.set("user", values.user); } socket.emit( event, values diff --git a/client/js/options.js b/client/js/options.js new file mode 100644 index 00000000..24265a56 --- /dev/null +++ b/client/js/options.js @@ -0,0 +1,124 @@ +"use strict"; +const $ = require("jquery"); +const settings = $("#settings"); +const userStyles = $("#user-specified-css"); +const storage = require("./localStorage"); + +const windows = $("#windows"); +const chat = $("#chat"); + +const options = $.extend({ + coloredNicks: true, + desktopNotifications: false, + join: true, + links: true, + mode: true, + motd: false, + nick: true, + notification: true, + notifyAllMessages: false, + part: true, + quit: true, + theme: $("#theme").attr("href").replace(/^themes\/(.*).css$/, "$1"), // Extracts default theme name, set on the server configuration + thumbnails: true, + userStyles: userStyles.text(), + highlights: [] +}, JSON.parse(storage.get("settings"))); + +module.exports = options; + +for (var i in options) { + if (i === "userStyles") { + if (!/[?&]nocss/.test(window.location.search)) { + $(document.head).find("#user-specified-css").html(options[i]); + } + settings.find("#user-specified-css-input").val(options[i]); + } else if (i === "highlights") { + settings.find("input[name=" + i + "]").val(options[i]); + } else if (i === "theme") { + $("#theme").attr("href", "themes/" + options[i] + ".css"); + settings.find("select[name=" + i + "]").val(options[i]); + } else if (options[i]) { + settings.find("input[name=" + i + "]").prop("checked", true); + } +} + +settings.on("change", "input, select, textarea", function() { + var self = $(this); + var name = self.attr("name"); + + if (self.attr("type") === "checkbox") { + options[name] = self.prop("checked"); + } else { + options[name] = self.val(); + } + + storage.set("settings", JSON.stringify(options)); + + if ([ + "join", + "mode", + "motd", + "nick", + "part", + "quit", + "notifyAllMessages", + ].indexOf(name) !== -1) { + chat.toggleClass("hide-" + name, !self.prop("checked")); + } else if (name === "coloredNicks") { + chat.toggleClass("colored-nicks", self.prop("checked")); + } else if (name === "theme") { + $("#theme").attr("href", "themes/" + options[name] + ".css"); + } else if (name === "userStyles") { + userStyles.html(options[name]); + } else if (name === "highlights") { + var highlightString = options[name]; + options.highlights = highlightString.split(",").map(function(h) { + return h.trim(); + }).filter(function(h) { + // Ensure we don't have empty string in the list of highlights + // otherwise, users get notifications for everything + return h !== ""; + }); + } +}).find("input") + .trigger("change"); + +$("#desktopNotifications").on("change", function() { + if ($(this).prop("checked") && Notification.permission !== "granted") { + Notification.requestPermission(updateDesktopNotificationStatus); + } +}); + +// Updates the checkbox and warning in settings when the Settings page is +// opened or when the checkbox state is changed. +// When notifications are not supported, this is never called (because +// checkbox state can not be changed). +var updateDesktopNotificationStatus = function() { + if (Notification.permission === "denied") { + desktopNotificationsCheckbox.attr("disabled", true); + desktopNotificationsCheckbox.attr("checked", false); + warningBlocked.show(); + } else { + if (Notification.permission === "default" && desktopNotificationsCheckbox.prop("checked")) { + desktopNotificationsCheckbox.attr("checked", false); + } + desktopNotificationsCheckbox.attr("disabled", false); + warningBlocked.hide(); + } +}; + +// If browser does not support notifications, override existing settings and +// display proper message in settings. +var desktopNotificationsCheckbox = $("#desktopNotifications"); +var warningUnsupported = $("#warnUnsupportedDesktopNotifications"); +var warningBlocked = $("#warnBlockedDesktopNotifications"); +warningBlocked.hide(); +if (("Notification" in window)) { + warningUnsupported.hide(); + windows.on("show", "#settings", updateDesktopNotificationStatus); +} else { + options.desktopNotifications = false; + desktopNotificationsCheckbox.attr("disabled", true); + desktopNotificationsCheckbox.attr("checked", false); +} From 413ab234d698c28e3ae8704bb3f9e7049152a1db Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 24 Apr 2017 09:45:02 +0000 Subject: [PATCH 098/355] chore(package): update mocha to version 3.3.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a23c0366..4dcc5edd 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "handlebars-loader": "1.5.0", "jquery": "3.2.1", "jquery-ui": "1.12.1", - "mocha": "3.2.0", + "mocha": "3.3.0", "mousetrap": "1.6.1", "npm-run-all": "4.0.2", "nyc": "10.2.0", From fe07bf66373d39ef5f3a4f616a04fa33ffc78ce6 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 24 Apr 2017 12:01:24 +0100 Subject: [PATCH 099/355] Add fix for slow scrolling when holding pg-up/pg-dn Fixes #1022 --- client/js/lounge.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/js/lounge.js b/client/js/lounge.js index 1857339e..1043465f 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1271,6 +1271,9 @@ $(function() { "pagedown" ], function(e, key) { let container = windows.find(".window.active"); + if (container.is(":animated")) { + return; + } // Chat windows scroll message container if (container.attr("id") === "chat-container") { From e45cfbf02ca6f30750e5b268eb241593ffec6fbd Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Wed, 19 Apr 2017 12:27:26 -0700 Subject: [PATCH 100/355] Use irc-framework setTopic() for topic command --- src/plugins/inputs/topic.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/plugins/inputs/topic.js b/src/plugins/inputs/topic.js index 8e2327cc..ef7a7cb4 100644 --- a/src/plugins/inputs/topic.js +++ b/src/plugins/inputs/topic.js @@ -14,9 +14,6 @@ exports.input = function(network, chan, cmd, args) { return; } - - var irc = network.irc; - irc.raw("TOPIC", chan.name, args.join(" ")); - + network.irc.setTopic(chan.name, args.join(" ")); return true; }; From b69ba5e4b11233977a8e48c1f21060c9a8e5a92b Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Tue, 25 Apr 2017 10:26:52 +0100 Subject: [PATCH 101/355] Fix showing prefetch options --- client/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/index.html b/client/index.html index 81f63d04..362688ba 100644 --- a/client/index.html +++ b/client/index.html @@ -274,7 +274,7 @@ {{/each}}
- {{#unless prefetch}} + {{#if prefetch}}

Links and URLs

@@ -290,7 +290,7 @@ Auto-expand links
- {{/unless}} + {{/if}}

Notifications

From d0987719ce76ac8d8174d81cc93463aee7a6f394 Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 24 Apr 2017 12:20:48 +0100 Subject: [PATCH 102/355] Replace the state on init rather than adding a new entry Fixes #1042 --- client/js/lounge.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 40a52b34..e9b78f2f 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -132,7 +132,9 @@ $(function() { $("#sign-in").remove(); var id = data.active; - var target = sidebar.find("[data-id='" + id + "']").trigger("click"); + var target = sidebar.find("[data-id='" + id + "']").trigger("click", { + replaceHistory: true + }); if (target.length === 0) { var first = sidebar.find(".chan") .eq(0) @@ -820,7 +822,11 @@ $(function() { } if (history && history.pushState) { - history.pushState(state, null, null); + if (data && data.replaceHistory && history.replaceState) { + history.replaceState(state, null, null); + } else { + history.pushState(state, null, null); + } } }); From b03d01b6eb83fe7f5519706f0ad4c428a39672bb Mon Sep 17 00:00:00 2001 From: Alistair McKinlay Date: Mon, 24 Apr 2017 11:40:53 +0100 Subject: [PATCH 103/355] Add ban/unban command Fixes #1073 --- client/index.html | 20 ++++++++++++++ client/js/constants.js | 2 ++ src/client.js | 1 + src/plugins/inputs/ban.js | 44 +++++++++++++++++++++++++++++++ src/plugins/inputs/mode.js | 11 +------- src/plugins/irc-events/banlist.js | 2 +- 6 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/plugins/inputs/ban.js diff --git a/client/index.html b/client/index.html index 362688ba..1b40bfce 100644 --- a/client/index.html +++ b/client/index.html @@ -554,6 +554,16 @@ +
+
+ /ban nick +
+
+

Ban (+b) a user from the current channel. + This can be a nickname or a hostmask.

+
+
+
/banlist @@ -799,6 +809,16 @@
+
+
+ /unban nick +
+
+

Unban (-b) a user from the current channel.

+ This can be a nickname or a hostmask.

+
+
+
/voice nick [...nick] diff --git a/client/js/constants.js b/client/js/constants.js index 546637fd..c9587709 100644 --- a/client/js/constants.js +++ b/client/js/constants.js @@ -3,6 +3,7 @@ const commands = [ "/away", "/back", + "/ban", "/banlist", "/close", "/connect", @@ -28,6 +29,7 @@ const commands = [ "/server", "/slap", "/topic", + "/unban", "/voice", "/whois" ]; diff --git a/src/client.js b/src/client.js index 0d3caffa..aa3b7045 100644 --- a/src/client.js +++ b/src/client.js @@ -36,6 +36,7 @@ var events = [ "whois" ]; var inputs = [ + "ban", "ctcp", "msg", "part", diff --git a/src/plugins/inputs/ban.js b/src/plugins/inputs/ban.js new file mode 100644 index 00000000..b6745652 --- /dev/null +++ b/src/plugins/inputs/ban.js @@ -0,0 +1,44 @@ +"use strict"; + +var Chan = require("../../models/chan"); +var Msg = require("../../models/msg"); + +exports.commands = [ + "ban", + "unban", + "banlist" +]; + +exports.input = function(network, chan, cmd, args) { + if (chan.type !== Chan.Type.CHANNEL) { + chan.pushMessage(this, new Msg({ + type: Msg.Type.ERROR, + text: `${cmd} command can only be used in channels.` + })); + + return; + } + + if (cmd !== "banlist" && args.length === 0) { + if (args.length === 0) { + chan.pushMessage(this, new Msg({ + type: Msg.Type.ERROR, + text: `Usage: /${cmd} ` + })); + + return; + } + } + + switch (cmd) { + case "ban": + network.irc.ban(chan.name, args[0]); + break; + case "unban": + network.irc.unban(chan.name, args[0]); + break; + case "banlist": + network.irc.banlist(chan.name); + break; + } +}; diff --git a/src/plugins/inputs/mode.js b/src/plugins/inputs/mode.js index 8395da15..4e1227ad 100644 --- a/src/plugins/inputs/mode.js +++ b/src/plugins/inputs/mode.js @@ -4,7 +4,6 @@ var Chan = require("../../models/chan"); var Msg = require("../../models/msg"); exports.commands = [ - "banlist", "mode", "op", "deop", @@ -14,10 +13,6 @@ exports.commands = [ "devoice", ]; -const chanCommands = [ - "banlist" -]; - exports.input = function(network, chan, cmd, args) { if (cmd !== "mode") { if (chan.type !== Chan.Type.CHANNEL) { @@ -29,7 +24,7 @@ exports.input = function(network, chan, cmd, args) { return; } - if (args.length === 0 && chanCommands.indexOf(cmd) === -1) { + if (args.length === 0) { chan.pushMessage(this, new Msg({ type: Msg.Type.ERROR, text: `Usage: /${cmd} [...nick]` @@ -39,7 +34,6 @@ exports.input = function(network, chan, cmd, args) { } const mode = { - banlist: "+b", op: "+o", hop: "+h", voice: "+v", @@ -48,9 +42,6 @@ exports.input = function(network, chan, cmd, args) { devoice: "-v" }[cmd]; - if (chanCommands.indexOf(cmd) > -1 && args.length === 0) { - network.irc.raw("MODE", chan.name, mode); - } args.forEach(function(target) { network.irc.raw("MODE", chan.name, mode, target); }); diff --git a/src/plugins/irc-events/banlist.js b/src/plugins/irc-events/banlist.js index d45d81cc..fa5993ea 100644 --- a/src/plugins/irc-events/banlist.js +++ b/src/plugins/irc-events/banlist.js @@ -9,7 +9,7 @@ module.exports = function(irc, network) { irc.on("banlist", function(banlist) { const channel = banlist.channel; const bans = banlist.bans; - if (!bans) { + if (!bans || bans.length === 0) { const msg = new Msg({ time: Date.now(), type: Msg.Type.ERROR, From 785842cde558db47a36aefd2753dbff2ceb21634 Mon Sep 17 00:00:00 2001 From: Yash Srivastav Date: Sun, 11 Dec 2016 05:43:26 +0530 Subject: [PATCH 104/355] Add emoji/nick/commands/chan autocomplete --- client/css/style.css | 33 +- client/js/libs/jquery/inputhistory.js | 2 +- client/js/libs/simplemap.json | 1434 +++++++++++++++++++++++++ client/js/lounge.js | 118 +- package.json | 2 + webpack.config.js | 5 + 6 files changed, 1582 insertions(+), 12 deletions(-) create mode 100644 client/js/libs/simplemap.json diff --git a/client/css/style.css b/client/css/style.css index 7fbd6492..522b319d 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1486,7 +1486,8 @@ kbd { background: transparent; } -#context-menu { +#context-menu, +.textcomplete-menu { position: absolute; list-style: none; margin: 0; @@ -1505,7 +1506,8 @@ kbd { background-color: rgba(0, 0, 0, .1); } -.context-menu-item { +.context-menu-item, +.textcomplete-item { cursor: pointer; display: block; padding: 4px 8px; @@ -1514,15 +1516,38 @@ kbd { margin-bottom: 6px; } -.context-menu-item:hover { +.context-menu-item:hover, +.textcomplete-item:hover, +.textcomplete-menu .active { background-color: #f6f6f6; } -.context-menu-item:before { +.context-menu-item:before, +.textcomplete-item:before { width: 20px; display: inline-block; } +.textcomplete-item { + border-bottom-color: rgba(0, 0, 0, .1); + border-bottom-style: solid; + border-bottom-width: 1px; + margin-top: 0; + margin-bottom: 0; + padding: 10px 8px; +} + +.textcomplete-item a { + color: #333; +} + +.textcomplete-item .emoji { + margin-right: 8px; + width: 16px; + text-align: center; + display: inline-block; +} + /** * Tooltips v0.5.3 * See http://primercss.io/tooltips/ diff --git a/client/js/libs/jquery/inputhistory.js b/client/js/libs/jquery/inputhistory.js index 1f5b7bf8..778fa696 100644 --- a/client/js/libs/jquery/inputhistory.js +++ b/client/js/libs/jquery/inputhistory.js @@ -34,7 +34,7 @@ import jQuery from "jquery"; var key = e.which; switch (key) { case 13: // Enter - if (e.shiftKey) { + if (e.shiftKey || self.data("autocompleting")) { return; // multiline input } diff --git a/client/js/libs/simplemap.json b/client/js/libs/simplemap.json new file mode 100644 index 00000000..2f3f0567 --- /dev/null +++ b/client/js/libs/simplemap.json @@ -0,0 +1,1434 @@ +{ + "100": "💯", + "1234": "🔢", + "grinning": "😀", + "grimacing": "😬", + "grin": "😁", + "joy": "😂", + "rofl": "🤣", + "smiley": "😃", + "smile": "😄", + "sweat_smile": "😅", + "laughing": "😆", + "innocent": "😇", + "wink": "😉", + "blush": "😊", + "slightly_smiling_face": "🙂", + "upside_down_face": "🙃", + "relaxed": "☺️", + "yum": "😋", + "relieved": "😌", + "heart_eyes": "😍", + "kissing_heart": "😘", + "kissing": "😗", + "kissing_smiling_eyes": "😙", + "kissing_closed_eyes": "😚", + "stuck_out_tongue_winking_eye": "😜", + "stuck_out_tongue_closed_eyes": "😝", + "stuck_out_tongue": "😛", + "money_mouth_face": "🤑", + "nerd_face": "🤓", + "sunglasses": "😎", + "clown_face": "🤡", + "cowboy_hat_face": "🤠", + "hugs": "🤗", + "smirk": "😏", + "no_mouth": "😶", + "neutral_face": "😐", + "expressionless": "😑", + "unamused": "😒", + "roll_eyes": "🙄", + "thinking": "🤔", + "lying_face": "🤥", + "flushed": "😳", + "disappointed": "😞", + "worried": "😟", + "angry": "😠", + "rage": "😡", + "pensive": "😔", + "confused": "😕", + "slightly_frowning_face": "🙁", + "frowning_face": "☹", + "persevere": "😣", + "confounded": "😖", + "tired_face": "😫", + "weary": "😩", + "triumph": "😤", + "open_mouth": "😮", + "scream": "😱", + "fearful": "😨", + "cold_sweat": "😰", + "hushed": "😯", + "frowning": "😦", + "anguished": "😧", + "cry": "😢", + "disappointed_relieved": "😥", + "drooling_face": "🤤", + "sleepy": "😪", + "sweat": "😓", + "sob": "😭", + "dizzy_face": "😵", + "astonished": "😲", + "zipper_mouth_face": "🤐", + "nauseated_face": "🤢", + "sneezing_face": "🤧", + "mask": "😷", + "face_with_thermometer": "🤒", + "face_with_head_bandage": "🤕", + "sleeping": "😴", + "zzz": "💤", + "poop": "💩", + "smiling_imp": "😈", + "imp": "👿", + "japanese_ogre": "👹", + "japanese_goblin": "👺", + "skull": "💀", + "ghost": "👻", + "alien": "👽", + "robot": "🤖", + "smiley_cat": "😺", + "smile_cat": "😸", + "joy_cat": "😹", + "heart_eyes_cat": "😻", + "smirk_cat": "😼", + "kissing_cat": "😽", + "scream_cat": "🙀", + "crying_cat_face": "😿", + "pouting_cat": "😾", + "raised_hands": "🙌", + "clap": "👏", + "wave": "👋", + "call_me_hand": "🤙", + "+1": "👍", + "-1": "👎", + "facepunch": "👊", + "fist": "✊", + "fist_left": "🤛", + "fist_right": "🤜", + "v": "✌", + "ok_hand": "👌", + "raised_hand": "✋", + "raised_back_of_hand": "🤚", + "open_hands": "👐", + "muscle": "💪", + "pray": "🙏", + "handshake": "🤝", + "point_up": "☝", + "point_up_2": "👆", + "point_down": "👇", + "point_left": "👈", + "point_right": "👉", + "fu": "🖕", + "raised_hand_with_fingers_splayed": "🖐", + "metal": "🤘", + "crossed_fingers": "🤞", + "vulcan_salute": "🖖", + "writing_hand": "✍", + "selfie": "🤳", + "nail_care": "💅", + "lips": "👄", + "tongue": "👅", + "ear": "👂", + "nose": "👃", + "eye": "👁", + "eyes": "👀", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "speaking_head": "🗣", + "baby": "👶", + "boy": "👦", + "girl": "👧", + "man": "👨", + "woman": "👩", + "blonde_woman": "👱‍♀️", + "blonde_man": "👱", + "older_man": "👴", + "older_woman": "👵", + "man_with_gua_pi_mao": "👲", + "woman_with_turban": "👳‍♀️", + "man_with_turban": "👳", + "policewoman": "👮‍♀️", + "policeman": "👮", + "construction_worker_woman": "👷‍♀️", + "construction_worker_man": "👷", + "guardswoman": "💂‍♀️", + "guardsman": "💂", + "female_detective": "🕵️‍♀️", + "male_detective": "🕵", + "woman_health_worker": "👩‍⚕️", + "man_health_worker": "👨‍⚕️", + "woman_farmer": "👩‍🌾", + "man_farmer": "👨‍🌾", + "woman_cook": "👩‍🍳", + "man_cook": "👨‍🍳", + "woman_student": "👩‍🎓", + "man_student": "👨‍🎓", + "woman_singer": "👩‍🎤", + "man_singer": "👨‍🎤", + "woman_teacher": "👩‍🏫", + "man_teacher": "👨‍🏫", + "woman_factory_worker": "👩‍🏭", + "man_factory_worker": "👨‍🏭", + "woman_technologist": "👩‍💻", + "man_technologist": "👨‍💻", + "woman_office_worker": "👩‍💼", + "man_office_worker": "👨‍💼", + "woman_mechanic": "👩‍🔧", + "man_mechanic": "👨‍🔧", + "woman_scientist": "👩‍🔬", + "man_scientist": "👨‍🔬", + "woman_artist": "👩‍🎨", + "man_artist": "👨‍🎨", + "woman_firefighter": "👩‍🚒", + "man_firefighter": "👨‍🚒", + "woman_pilot": "👩‍✈️", + "man_pilot": "👨‍✈️", + "woman_astronaut": "👩‍🚀", + "man_astronaut": "👨‍🚀", + "woman_judge": "👩‍⚖️", + "man_judge": "👨‍⚖️", + "mrs_claus": "🤶", + "santa": "🎅", + "angel": "👼", + "pregnant_woman": "🤰", + "princess": "👸", + "prince": "🤴", + "bride_with_veil": "👰", + "man_in_tuxedo": "🤵", + "running_woman": "🏃‍♀️", + "running_man": "🏃", + "walking_woman": "🚶‍♀️", + "walking_man": "🚶", + "dancer": "💃", + "man_dancing": "🕺", + "dancing_women": "👯", + "dancing_men": "👯‍♂️", + "couple": "👫", + "two_men_holding_hands": "👬", + "two_women_holding_hands": "👭", + "bowing_woman": "🙇‍♀️", + "bowing_man": "🙇", + "man_facepalming": "🤦", + "woman_facepalming": "🤦‍♀️", + "woman_shrugging": "🤷", + "man_shrugging": "🤷‍♂️", + "tipping_hand_woman": "💁", + "tipping_hand_man": "💁‍♂️", + "no_good_woman": "🙅", + "no_good_man": "🙅‍♂️", + "ok_woman": "🙆", + "ok_man": "🙆‍♂️", + "raising_hand_woman": "🙋", + "raising_hand_man": "🙋‍♂️", + "pouting_woman": "🙎", + "pouting_man": "🙎‍♂️", + "frowning_woman": "🙍", + "frowning_man": "🙍‍♂️", + "haircut_woman": "💇", + "haircut_man": "💇‍♂️", + "massage_woman": "💆", + "massage_man": "💆‍♂️", + "couple_with_heart_woman_man": "💑", + "couple_with_heart_woman_woman": "👩‍❤️‍👩", + "couple_with_heart_man_man": "👨‍❤️‍👨", + "couplekiss_man_woman": "💏", + "couplekiss_woman_woman": "👩‍❤️‍💋‍👩", + "couplekiss_man_man": "👨‍❤️‍💋‍👨", + "family_man_woman_boy": "👪", + "family_man_woman_girl": "👨‍👩‍👧", + "family_man_woman_girl_boy": "👨‍👩‍👧‍👦", + "family_man_woman_boy_boy": "👨‍👩‍👦‍👦", + "family_man_woman_girl_girl": "👨‍👩‍👧‍👧", + "family_woman_woman_boy": "👩‍👩‍👦", + "family_woman_woman_girl": "👩‍👩‍👧", + "family_woman_woman_girl_boy": "👩‍👩‍👧‍👦", + "family_woman_woman_boy_boy": "👩‍👩‍👦‍👦", + "family_woman_woman_girl_girl": "👩‍👩‍👧‍👧", + "family_man_man_boy": "👨‍👨‍👦", + "family_man_man_girl": "👨‍👨‍👧", + "family_man_man_girl_boy": "👨‍👨‍👧‍👦", + "family_man_man_boy_boy": "👨‍👨‍👦‍👦", + "family_man_man_girl_girl": "👨‍👨‍👧‍👧", + "family_woman_boy": "👩‍👦", + "family_woman_girl": "👩‍👧", + "family_woman_girl_boy": "👩‍👧‍👦", + "family_woman_boy_boy": "👩‍👦‍👦", + "family_woman_girl_girl": "👩‍👧‍👧", + "family_man_boy": "👨‍👦", + "family_man_girl": "👨‍👧", + "family_man_girl_boy": "👨‍👧‍👦", + "family_man_boy_boy": "👨‍👦‍👦", + "family_man_girl_girl": "👨‍👧‍👧", + "womans_clothes": "👚", + "tshirt": "👕", + "jeans": "👖", + "necktie": "👔", + "dress": "👗", + "bikini": "👙", + "kimono": "👘", + "lipstick": "💄", + "kiss": "💋", + "footprints": "👣", + "high_heel": "👠", + "sandal": "👡", + "boot": "👢", + "mans_shoe": "👞", + "athletic_shoe": "👟", + "womans_hat": "👒", + "tophat": "🎩", + "rescue_worker_helmet": "⛑", + "mortar_board": "🎓", + "crown": "👑", + "school_satchel": "🎒", + "pouch": "👝", + "purse": "👛", + "handbag": "👜", + "briefcase": "💼", + "eyeglasses": "👓", + "dark_sunglasses": "🕶", + "ring": "💍", + "closed_umbrella": "🌂", + "dog": "🐶", + "cat": "🐱", + "mouse": "🐭", + "hamster": "🐹", + "rabbit": "🐰", + "fox_face": "🦊", + "bear": "🐻", + "panda_face": "🐼", + "koala": "🐨", + "tiger": "🐯", + "lion": "🦁", + "cow": "🐮", + "pig": "🐷", + "pig_nose": "🐽", + "frog": "🐸", + "squid": "🦑", + "octopus": "🐙", + "shrimp": "🦐", + "monkey_face": "🐵", + "gorilla": "🦍", + "see_no_evil": "🙈", + "hear_no_evil": "🙉", + "speak_no_evil": "🙊", + "monkey": "🐒", + "chicken": "🐔", + "penguin": "🐧", + "bird": "🐦", + "baby_chick": "🐤", + "hatching_chick": "🐣", + "hatched_chick": "🐥", + "duck": "🦆", + "eagle": "🦅", + "owl": "🦉", + "bat": "🦇", + "wolf": "🐺", + "boar": "🐗", + "horse": "🐴", + "unicorn": "🦄", + "honeybee": "🐝", + "bug": "🐛", + "butterfly": "🦋", + "snail": "🐌", + "beetle": "🐞", + "ant": "🐜", + "spider": "🕷", + "scorpion": "🦂", + "crab": "🦀", + "snake": "🐍", + "lizard": "🦎", + "turtle": "🐢", + "tropical_fish": "🐠", + "fish": "🐟", + "blowfish": "🐡", + "dolphin": "🐬", + "shark": "🦈", + "whale": "🐳", + "whale2": "🐋", + "crocodile": "🐊", + "leopard": "🐆", + "tiger2": "🐅", + "water_buffalo": "🐃", + "ox": "🐂", + "cow2": "🐄", + "deer": "🦌", + "dromedary_camel": "🐪", + "camel": "🐫", + "elephant": "🐘", + "rhinoceros": "🦏", + "goat": "🐐", + "ram": "🐏", + "sheep": "🐑", + "racehorse": "🐎", + "pig2": "🐖", + "rat": "🐀", + "mouse2": "🐁", + "rooster": "🐓", + "turkey": "🦃", + "dove": "🕊", + "dog2": "🐕", + "poodle": "🐩", + "cat2": "🐈", + "rabbit2": "🐇", + "chipmunk": "🐿", + "paw_prints": "🐾", + "dragon": "🐉", + "dragon_face": "🐲", + "cactus": "🌵", + "christmas_tree": "🎄", + "evergreen_tree": "🌲", + "deciduous_tree": "🌳", + "palm_tree": "🌴", + "seedling": "🌱", + "herb": "🌿", + "shamrock": "☘", + "four_leaf_clover": "🍀", + "bamboo": "🎍", + "tanabata_tree": "🎋", + "leaves": "🍃", + "fallen_leaf": "🍂", + "maple_leaf": "🍁", + "ear_of_rice": "🌾", + "hibiscus": "🌺", + "sunflower": "🌻", + "rose": "🌹", + "wilted_flower": "🥀", + "tulip": "🌷", + "blossom": "🌼", + "cherry_blossom": "🌸", + "bouquet": "💐", + "mushroom": "🍄", + "chestnut": "🌰", + "jack_o_lantern": "🎃", + "shell": "🐚", + "spider_web": "🕸", + "earth_americas": "🌎", + "earth_africa": "🌍", + "earth_asia": "🌏", + "full_moon": "🌕", + "waning_gibbous_moon": "🌖", + "last_quarter_moon": "🌗", + "waning_crescent_moon": "🌘", + "new_moon": "🌑", + "waxing_crescent_moon": "🌒", + "first_quarter_moon": "🌓", + "waxing_gibbous_moon": "🌔", + "new_moon_with_face": "🌚", + "full_moon_with_face": "🌝", + "first_quarter_moon_with_face": "🌛", + "last_quarter_moon_with_face": "🌜", + "sun_with_face": "🌞", + "crescent_moon": "🌙", + "star": "⭐", + "star2": "🌟", + "dizzy": "💫", + "sparkles": "✨", + "comet": "☄", + "sunny": "☀️", + "sun_behind_small_cloud": "🌤", + "partly_sunny": "⛅", + "sun_behind_large_cloud": "🌥", + "sun_behind_rain_cloud": "🌦", + "cloud": "☁️", + "cloud_with_rain": "🌧", + "cloud_with_lightning_and_rain": "⛈", + "cloud_with_lightning": "🌩", + "zap": "⚡", + "fire": "🔥", + "boom": "💥", + "snowflake": "❄️", + "cloud_with_snow": "🌨", + "snowman": "⛄", + "snowman_with_snow": "☃", + "wind_face": "🌬", + "dash": "💨", + "tornado": "🌪", + "fog": "🌫", + "open_umbrella": "☂", + "umbrella": "☔", + "droplet": "💧", + "sweat_drops": "💦", + "ocean": "🌊", + "green_apple": "🍏", + "apple": "🍎", + "pear": "🍐", + "tangerine": "🍊", + "lemon": "🍋", + "banana": "🍌", + "watermelon": "🍉", + "grapes": "🍇", + "strawberry": "🍓", + "melon": "🍈", + "cherries": "🍒", + "peach": "🍑", + "pineapple": "🍍", + "kiwi_fruit": "🥝", + "avocado": "🥑", + "tomato": "🍅", + "eggplant": "🍆", + "cucumber": "🥒", + "carrot": "🥕", + "hot_pepper": "🌶", + "potato": "🥔", + "corn": "🌽", + "sweet_potato": "🍠", + "peanuts": "🥜", + "honey_pot": "🍯", + "croissant": "🥐", + "bread": "🍞", + "baguette_bread": "🥖", + "cheese": "🧀", + "egg": "🥚", + "bacon": "🥓", + "pancakes": "🥞", + "poultry_leg": "🍗", + "meat_on_bone": "🍖", + "fried_shrimp": "🍤", + "fried_egg": "🍳", + "hamburger": "🍔", + "fries": "🍟", + "stuffed_flatbread": "🥙", + "hotdog": "🌭", + "pizza": "🍕", + "spaghetti": "🍝", + "taco": "🌮", + "burrito": "🌯", + "green_salad": "🥗", + "shallow_pan_of_food": "🥘", + "ramen": "🍜", + "stew": "🍲", + "fish_cake": "🍥", + "sushi": "🍣", + "bento": "🍱", + "curry": "🍛", + "rice_ball": "🍙", + "rice": "🍚", + "rice_cracker": "🍘", + "oden": "🍢", + "dango": "🍡", + "shaved_ice": "🍧", + "ice_cream": "🍨", + "icecream": "🍦", + "cake": "🍰", + "birthday": "🎂", + "custard": "🍮", + "candy": "🍬", + "lollipop": "🍭", + "chocolate_bar": "🍫", + "popcorn": "🍿", + "doughnut": "🍩", + "cookie": "🍪", + "milk_glass": "🥛", + "beer": "🍺", + "beers": "🍻", + "clinking_glasses": "🥂", + "wine_glass": "🍷", + "tumbler_glass": "🥃", + "cocktail": "🍸", + "tropical_drink": "🍹", + "champagne": "🍾", + "sake": "🍶", + "tea": "🍵", + "coffee": "☕", + "baby_bottle": "🍼", + "spoon": "🥄", + "fork_and_knife": "🍴", + "plate_with_cutlery": "🍽", + "soccer": "⚽", + "basketball": "🏀", + "football": "🏈", + "baseball": "⚾", + "tennis": "🎾", + "volleyball": "🏐", + "rugby_football": "🏉", + "8ball": "🎱", + "golf": "⛳", + "golfing_woman": "🏌️‍♀️", + "golfing_man": "🏌", + "ping_pong": "🏓", + "badminton": "🏸", + "goal_net": "🥅", + "ice_hockey": "🏒", + "field_hockey": "🏑", + "cricket": "🏏", + "ski": "🎿", + "skier": "⛷", + "snowboarder": "🏂", + "person_fencing": "🤺", + "women_wrestling": "🤼‍♀️", + "men_wrestling": "🤼‍♂️", + "woman_cartwheeling": "🤸‍♀️", + "man_cartwheeling": "🤸‍♂️", + "woman_playing_handball": "🤾‍♀️", + "man_playing_handball": "🤾‍♂️", + "ice_skate": "⛸", + "bow_and_arrow": "🏹", + "fishing_pole_and_fish": "🎣", + "boxing_glove": "🥊", + "martial_arts_uniform": "🥋", + "rowing_woman": "🚣‍♀️", + "rowing_man": "🚣", + "swimming_woman": "🏊‍♀️", + "swimming_man": "🏊", + "woman_playing_water_polo": "🤽‍♀️", + "man_playing_water_polo": "🤽‍♂️", + "surfing_woman": "🏄‍♀️", + "surfing_man": "🏄", + "bath": "🛀", + "basketball_woman": "⛹️‍♀️", + "basketball_man": "⛹", + "weight_lifting_woman": "🏋️‍♀️", + "weight_lifting_man": "🏋", + "biking_woman": "🚴‍♀️", + "biking_man": "🚴", + "mountain_biking_woman": "🚵‍♀️", + "mountain_biking_man": "🚵", + "horse_racing": "🏇", + "business_suit_levitating": "🕴", + "trophy": "🏆", + "running_shirt_with_sash": "🎽", + "medal_sports": "🏅", + "medal_military": "🎖", + "1st_place_medal": "🥇", + "2nd_place_medal": "🥈", + "3rd_place_medal": "🥉", + "reminder_ribbon": "🎗", + "rosette": "🏵", + "ticket": "🎫", + "tickets": "🎟", + "performing_arts": "🎭", + "art": "🎨", + "circus_tent": "🎪", + "woman_juggling": "🤹‍♀️", + "man_juggling": "🤹‍♂️", + "microphone": "🎤", + "headphones": "🎧", + "musical_score": "🎼", + "musical_keyboard": "🎹", + "drum": "🥁", + "saxophone": "🎷", + "trumpet": "🎺", + "guitar": "🎸", + "violin": "🎻", + "clapper": "🎬", + "video_game": "🎮", + "space_invader": "👾", + "dart": "🎯", + "game_die": "🎲", + "slot_machine": "🎰", + "bowling": "🎳", + "red_car": "🚗", + "taxi": "🚕", + "blue_car": "🚙", + "bus": "🚌", + "trolleybus": "🚎", + "racing_car": "🏎", + "police_car": "🚓", + "ambulance": "🚑", + "fire_engine": "🚒", + "minibus": "🚐", + "truck": "🚚", + "articulated_lorry": "🚛", + "tractor": "🚜", + "kick_scooter": "🛴", + "motorcycle": "🏍", + "bike": "🚲", + "motor_scooter": "🛵", + "rotating_light": "🚨", + "oncoming_police_car": "🚔", + "oncoming_bus": "🚍", + "oncoming_automobile": "🚘", + "oncoming_taxi": "🚖", + "aerial_tramway": "🚡", + "mountain_cableway": "🚠", + "suspension_railway": "🚟", + "railway_car": "🚃", + "train": "🚋", + "monorail": "🚝", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "light_rail": "🚈", + "mountain_railway": "🚞", + "steam_locomotive": "🚂", + "train2": "🚆", + "metro": "🚇", + "tram": "🚊", + "station": "🚉", + "helicopter": "🚁", + "small_airplane": "🛩", + "airplane": "✈️", + "flight_departure": "🛫", + "flight_arrival": "🛬", + "sailboat": "⛵", + "motor_boat": "🛥", + "speedboat": "🚤", + "ferry": "⛴", + "passenger_ship": "🛳", + "rocket": "🚀", + "artificial_satellite": "🛰", + "seat": "💺", + "canoe": "🛶", + "anchor": "⚓", + "construction": "🚧", + "fuelpump": "⛽", + "busstop": "🚏", + "vertical_traffic_light": "🚦", + "traffic_light": "🚥", + "checkered_flag": "🏁", + "ship": "🚢", + "ferris_wheel": "🎡", + "roller_coaster": "🎢", + "carousel_horse": "🎠", + "building_construction": "🏗", + "foggy": "🌁", + "tokyo_tower": "🗼", + "factory": "🏭", + "fountain": "⛲", + "rice_scene": "🎑", + "mountain": "⛰", + "mountain_snow": "🏔", + "mount_fuji": "🗻", + "volcano": "🌋", + "japan": "🗾", + "camping": "🏕", + "tent": "⛺", + "national_park": "🏞", + "motorway": "🛣", + "railway_track": "🛤", + "sunrise": "🌅", + "sunrise_over_mountains": "🌄", + "desert": "🏜", + "beach_umbrella": "🏖", + "desert_island": "🏝", + "city_sunrise": "🌇", + "city_sunset": "🌆", + "cityscape": "🏙", + "night_with_stars": "🌃", + "bridge_at_night": "🌉", + "milky_way": "🌌", + "stars": "🌠", + "sparkler": "🎇", + "fireworks": "🎆", + "rainbow": "🌈", + "houses": "🏘", + "european_castle": "🏰", + "japanese_castle": "🏯", + "stadium": "🏟", + "statue_of_liberty": "🗽", + "house": "🏠", + "house_with_garden": "🏡", + "derelict_house": "🏚", + "office": "🏢", + "department_store": "🏬", + "post_office": "🏣", + "european_post_office": "🏤", + "hospital": "🏥", + "bank": "🏦", + "hotel": "🏨", + "convenience_store": "🏪", + "school": "🏫", + "love_hotel": "🏩", + "wedding": "💒", + "classical_building": "🏛", + "church": "⛪", + "mosque": "🕌", + "synagogue": "🕍", + "kaaba": "🕋", + "shinto_shrine": "⛩", + "watch": "⌚", + "iphone": "📱", + "calling": "📲", + "computer": "💻", + "keyboard": "⌨", + "desktop_computer": "🖥", + "printer": "🖨", + "computer_mouse": "🖱", + "trackball": "🖲", + "joystick": "🕹", + "clamp": "🗜", + "minidisc": "💽", + "floppy_disk": "💾", + "cd": "💿", + "dvd": "📀", + "vhs": "📼", + "camera": "📷", + "camera_flash": "📸", + "video_camera": "📹", + "movie_camera": "🎥", + "film_projector": "📽", + "film_strip": "🎞", + "telephone_receiver": "📞", + "phone": "☎️", + "pager": "📟", + "fax": "📠", + "tv": "📺", + "radio": "📻", + "studio_microphone": "🎙", + "level_slider": "🎚", + "control_knobs": "🎛", + "stopwatch": "⏱", + "timer_clock": "⏲", + "alarm_clock": "⏰", + "mantelpiece_clock": "🕰", + "hourglass_flowing_sand": "⏳", + "hourglass": "⌛", + "satellite": "📡", + "battery": "🔋", + "electric_plug": "🔌", + "bulb": "💡", + "flashlight": "🔦", + "candle": "🕯", + "wastebasket": "🗑", + "oil_drum": "🛢", + "money_with_wings": "💸", + "dollar": "💵", + "yen": "💴", + "euro": "💶", + "pound": "💷", + "moneybag": "💰", + "credit_card": "💳", + "gem": "💎", + "balance_scale": "⚖", + "wrench": "🔧", + "hammer": "🔨", + "hammer_and_pick": "⚒", + "hammer_and_wrench": "🛠", + "pick": "⛏", + "nut_and_bolt": "🔩", + "gear": "⚙", + "chains": "⛓", + "gun": "🔫", + "bomb": "💣", + "hocho": "🔪", + "dagger": "🗡", + "crossed_swords": "⚔", + "shield": "🛡", + "smoking": "🚬", + "skull_and_crossbones": "☠", + "coffin": "⚰", + "funeral_urn": "⚱", + "amphora": "🏺", + "crystal_ball": "🔮", + "prayer_beads": "📿", + "barber": "💈", + "alembic": "⚗", + "telescope": "🔭", + "microscope": "🔬", + "hole": "🕳", + "pill": "💊", + "syringe": "💉", + "thermometer": "🌡", + "label": "🏷", + "bookmark": "🔖", + "toilet": "🚽", + "shower": "🚿", + "bathtub": "🛁", + "key": "🔑", + "old_key": "🗝", + "couch_and_lamp": "🛋", + "sleeping_bed": "🛌", + "bed": "🛏", + "door": "🚪", + "bellhop_bell": "🛎", + "framed_picture": "🖼", + "world_map": "🗺", + "parasol_on_ground": "⛱", + "moyai": "🗿", + "shopping": "🛍", + "shopping_cart": "🛒", + "balloon": "🎈", + "flags": "🎏", + "ribbon": "🎀", + "gift": "🎁", + "confetti_ball": "🎊", + "tada": "🎉", + "dolls": "🎎", + "wind_chime": "🎐", + "crossed_flags": "🎌", + "izakaya_lantern": "🏮", + "email": "✉️", + "envelope_with_arrow": "📩", + "incoming_envelope": "📨", + "e-mail": "📧", + "love_letter": "💌", + "postbox": "📮", + "mailbox_closed": "📪", + "mailbox": "📫", + "mailbox_with_mail": "📬", + "mailbox_with_no_mail": "📭", + "package": "📦", + "postal_horn": "📯", + "inbox_tray": "📥", + "outbox_tray": "📤", + "scroll": "📜", + "page_with_curl": "📃", + "bookmark_tabs": "📑", + "bar_chart": "📊", + "chart_with_upwards_trend": "📈", + "chart_with_downwards_trend": "📉", + "page_facing_up": "📄", + "date": "📅", + "calendar": "📆", + "spiral_calendar": "🗓", + "card_index": "📇", + "card_file_box": "🗃", + "ballot_box": "🗳", + "file_cabinet": "🗄", + "clipboard": "📋", + "spiral_notepad": "🗒", + "file_folder": "📁", + "open_file_folder": "📂", + "card_index_dividers": "🗂", + "newspaper_roll": "🗞", + "newspaper": "📰", + "notebook": "📓", + "closed_book": "📕", + "green_book": "📗", + "blue_book": "📘", + "orange_book": "📙", + "notebook_with_decorative_cover": "📔", + "ledger": "📒", + "books": "📚", + "open_book": "📖", + "link": "🔗", + "paperclip": "📎", + "paperclips": "🖇", + "scissors": "✂️", + "triangular_ruler": "📐", + "straight_ruler": "📏", + "pushpin": "📌", + "round_pushpin": "📍", + "triangular_flag_on_post": "🚩", + "white_flag": "🏳", + "black_flag": "🏴", + "rainbow_flag": "🏳️‍🌈", + "closed_lock_with_key": "🔐", + "lock": "🔒", + "unlock": "🔓", + "lock_with_ink_pen": "🔏", + "pen": "🖊", + "fountain_pen": "🖋", + "black_nib": "✒️", + "memo": "📝", + "pencil2": "✏️", + "crayon": "🖍", + "paintbrush": "🖌", + "mag": "🔍", + "mag_right": "🔎", + "heart": "❤️", + "yellow_heart": "💛", + "green_heart": "💚", + "blue_heart": "💙", + "purple_heart": "💜", + "black_heart": "🖤", + "broken_heart": "💔", + "heavy_heart_exclamation": "❣", + "two_hearts": "💕", + "revolving_hearts": "💞", + "heartbeat": "💓", + "heartpulse": "💗", + "sparkling_heart": "💖", + "cupid": "💘", + "gift_heart": "💝", + "heart_decoration": "💟", + "peace_symbol": "☮", + "latin_cross": "✝", + "star_and_crescent": "☪", + "om": "🕉", + "wheel_of_dharma": "☸", + "star_of_david": "✡", + "six_pointed_star": "🔯", + "menorah": "🕎", + "yin_yang": "☯", + "orthodox_cross": "☦", + "place_of_worship": "🛐", + "ophiuchus": "⛎", + "aries": "♈", + "taurus": "♉", + "gemini": "♊", + "cancer": "♋", + "leo": "♌", + "virgo": "♍", + "libra": "♎", + "scorpius": "♏", + "sagittarius": "♐", + "capricorn": "♑", + "aquarius": "♒", + "pisces": "♓", + "id": "🆔", + "atom_symbol": "⚛", + "u7a7a": "🈳", + "u5272": "🈹", + "radioactive": "☢", + "biohazard": "☣", + "mobile_phone_off": "📴", + "vibration_mode": "📳", + "u6709": "🈶", + "u7121": "🈚", + "u7533": "🈸", + "u55b6": "🈺", + "u6708": "🈷️", + "eight_pointed_black_star": "✴️", + "vs": "🆚", + "accept": "🉑", + "white_flower": "💮", + "ideograph_advantage": "🉐", + "secret": "㊙️", + "congratulations": "㊗️", + "u5408": "🈴", + "u6e80": "🈵", + "u7981": "🈲", + "a": "🅰️", + "b": "🅱️", + "ab": "🆎", + "cl": "🆑", + "o2": "🅾️", + "sos": "🆘", + "no_entry": "⛔", + "name_badge": "📛", + "no_entry_sign": "🚫", + "x": "❌", + "o": "⭕", + "stop_sign": "🛑", + "anger": "💢", + "hotsprings": "♨️", + "no_pedestrians": "🚷", + "do_not_litter": "🚯", + "no_bicycles": "🚳", + "non-potable_water": "🚱", + "underage": "🔞", + "no_mobile_phones": "📵", + "exclamation": "❗", + "grey_exclamation": "❕", + "question": "❓", + "grey_question": "❔", + "bangbang": "‼️", + "interrobang": "⁉️", + "low_brightness": "🔅", + "high_brightness": "🔆", + "trident": "🔱", + "fleur_de_lis": "⚜", + "part_alternation_mark": "〽️", + "warning": "⚠️", + "children_crossing": "🚸", + "beginner": "🔰", + "recycle": "♻️", + "u6307": "🈯", + "chart": "💹", + "sparkle": "❇️", + "eight_spoked_asterisk": "✳️", + "negative_squared_cross_mark": "❎", + "white_check_mark": "✅", + "diamond_shape_with_a_dot_inside": "💠", + "cyclone": "🌀", + "loop": "➿", + "globe_with_meridians": "🌐", + "m": "Ⓜ️", + "atm": "🏧", + "sa": "🈂️", + "passport_control": "🛂", + "customs": "🛃", + "baggage_claim": "🛄", + "left_luggage": "🛅", + "wheelchair": "♿", + "no_smoking": "🚭", + "wc": "🚾", + "parking": "🅿️", + "potable_water": "🚰", + "mens": "🚹", + "womens": "🚺", + "baby_symbol": "🚼", + "restroom": "🚻", + "put_litter_in_its_place": "🚮", + "cinema": "🎦", + "signal_strength": "📶", + "koko": "🈁", + "ng": "🆖", + "ok": "🆗", + "up": "🆙", + "cool": "🆒", + "new": "🆕", + "free": "🆓", + "zero": "0️⃣", + "one": "1️⃣", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "keycap_ten": "🔟", + "asterisk": "*⃣", + "arrow_forward": "▶️", + "pause_button": "⏸", + "next_track_button": "⏭", + "stop_button": "⏹", + "record_button": "⏺", + "play_or_pause_button": "⏯", + "previous_track_button": "⏮", + "fast_forward": "⏩", + "rewind": "⏪", + "twisted_rightwards_arrows": "🔀", + "repeat": "🔁", + "repeat_one": "🔂", + "arrow_backward": "◀️", + "arrow_up_small": "🔼", + "arrow_down_small": "🔽", + "arrow_double_up": "⏫", + "arrow_double_down": "⏬", + "arrow_right": "➡️", + "arrow_left": "⬅️", + "arrow_up": "⬆️", + "arrow_down": "⬇️", + "arrow_upper_right": "↗️", + "arrow_lower_right": "↘️", + "arrow_lower_left": "↙️", + "arrow_upper_left": "↖️", + "arrow_up_down": "↕️", + "left_right_arrow": "↔️", + "arrows_counterclockwise": "🔄", + "arrow_right_hook": "↪️", + "leftwards_arrow_with_hook": "↩️", + "arrow_heading_up": "⤴️", + "arrow_heading_down": "⤵️", + "hash": "#️⃣", + "information_source": "ℹ️", + "abc": "🔤", + "abcd": "🔡", + "capital_abcd": "🔠", + "symbols": "🔣", + "musical_note": "🎵", + "notes": "🎶", + "wavy_dash": "〰️", + "curly_loop": "➰", + "heavy_check_mark": "✔️", + "arrows_clockwise": "🔃", + "heavy_plus_sign": "➕", + "heavy_minus_sign": "➖", + "heavy_division_sign": "➗", + "heavy_multiplication_x": "✖️", + "heavy_dollar_sign": "💲", + "currency_exchange": "💱", + "copyright": "©️", + "registered": "®️", + "tm": "™️", + "end": "🔚", + "back": "🔙", + "on": "🔛", + "top": "🔝", + "soon": "🔜", + "ballot_box_with_check": "☑️", + "radio_button": "🔘", + "white_circle": "⚪", + "black_circle": "⚫", + "red_circle": "🔴", + "large_blue_circle": "🔵", + "small_orange_diamond": "🔸", + "small_blue_diamond": "🔹", + "large_orange_diamond": "🔶", + "large_blue_diamond": "🔷", + "small_red_triangle": "🔺", + "black_small_square": "▪️", + "white_small_square": "▫️", + "black_large_square": "⬛", + "white_large_square": "⬜", + "small_red_triangle_down": "🔻", + "black_medium_square": "◼️", + "white_medium_square": "◻️", + "black_medium_small_square": "◾", + "white_medium_small_square": "◽", + "black_square_button": "🔲", + "white_square_button": "🔳", + "speaker": "🔈", + "sound": "🔉", + "loud_sound": "🔊", + "mute": "🔇", + "mega": "📣", + "loudspeaker": "📢", + "bell": "🔔", + "no_bell": "🔕", + "black_joker": "🃏", + "mahjong": "🀄", + "spades": "♠️", + "clubs": "♣️", + "hearts": "♥️", + "diamonds": "♦️", + "flower_playing_cards": "🎴", + "thought_balloon": "💭", + "right_anger_bubble": "🗯", + "speech_balloon": "💬", + "left_speech_bubble": "🗨", + "clock1": "🕐", + "clock2": "🕑", + "clock3": "🕒", + "clock4": "🕓", + "clock5": "🕔", + "clock6": "🕕", + "clock7": "🕖", + "clock8": "🕗", + "clock9": "🕘", + "clock10": "🕙", + "clock11": "🕚", + "clock12": "🕛", + "clock130": "🕜", + "clock230": "🕝", + "clock330": "🕞", + "clock430": "🕟", + "clock530": "🕠", + "clock630": "🕡", + "clock730": "🕢", + "clock830": "🕣", + "clock930": "🕤", + "clock1030": "🕥", + "clock1130": "🕦", + "clock1230": "🕧", + "afghanistan": "🇦🇫", + "aland_islands": "🇦🇽", + "albania": "🇦🇱", + "algeria": "🇩🇿", + "american_samoa": "🇦🇸", + "andorra": "🇦🇩", + "angola": "🇦🇴", + "anguilla": "🇦🇮", + "antarctica": "🇦🇶", + "antigua_barbuda": "🇦🇬", + "argentina": "🇦🇷", + "armenia": "🇦🇲", + "aruba": "🇦🇼", + "australia": "🇦🇺", + "austria": "🇦🇹", + "azerbaijan": "🇦🇿", + "bahamas": "🇧🇸", + "bahrain": "🇧🇭", + "bangladesh": "🇧🇩", + "barbados": "🇧🇧", + "belarus": "🇧🇾", + "belgium": "🇧🇪", + "belize": "🇧🇿", + "benin": "🇧🇯", + "bermuda": "🇧🇲", + "bhutan": "🇧🇹", + "bolivia": "🇧🇴", + "caribbean_netherlands": "🇧🇶", + "bosnia_herzegovina": "🇧🇦", + "botswana": "🇧🇼", + "brazil": "🇧🇷", + "british_indian_ocean_territory": "🇮🇴", + "british_virgin_islands": "🇻🇬", + "brunei": "🇧🇳", + "bulgaria": "🇧🇬", + "burkina_faso": "🇧🇫", + "burundi": "🇧🇮", + "cape_verde": "🇨🇻", + "cambodia": "🇰🇭", + "cameroon": "🇨🇲", + "canada": "🇨🇦", + "canary_islands": "🇮🇨", + "cayman_islands": "🇰🇾", + "central_african_republic": "🇨🇫", + "chad": "🇹🇩", + "chile": "🇨🇱", + "cn": "🇨🇳", + "christmas_island": "🇨🇽", + "cocos_islands": "🇨🇨", + "colombia": "🇨🇴", + "comoros": "🇰🇲", + "congo_brazzaville": "🇨🇬", + "congo_kinshasa": "🇨🇩", + "cook_islands": "🇨🇰", + "costa_rica": "🇨🇷", + "croatia": "🇭🇷", + "cuba": "🇨🇺", + "curacao": "🇨🇼", + "cyprus": "🇨🇾", + "czech_republic": "🇨🇿", + "denmark": "🇩🇰", + "djibouti": "🇩🇯", + "dominica": "🇩🇲", + "dominican_republic": "🇩🇴", + "ecuador": "🇪🇨", + "egypt": "🇪🇬", + "el_salvador": "🇸🇻", + "equatorial_guinea": "🇬🇶", + "eritrea": "🇪🇷", + "estonia": "🇪🇪", + "ethiopia": "🇪🇹", + "eu": "🇪🇺", + "falkland_islands": "🇫🇰", + "faroe_islands": "🇫🇴", + "fiji": "🇫🇯", + "finland": "🇫🇮", + "fr": "🇫🇷", + "french_guiana": "🇬🇫", + "french_polynesia": "🇵🇫", + "french_southern_territories": "🇹🇫", + "gabon": "🇬🇦", + "gambia": "🇬🇲", + "georgia": "🇬🇪", + "de": "🇩🇪", + "ghana": "🇬🇭", + "gibraltar": "🇬🇮", + "greece": "🇬🇷", + "greenland": "🇬🇱", + "grenada": "🇬🇩", + "guadeloupe": "🇬🇵", + "guam": "🇬🇺", + "guatemala": "🇬🇹", + "guernsey": "🇬🇬", + "guinea": "🇬🇳", + "guinea_bissau": "🇬🇼", + "guyana": "🇬🇾", + "haiti": "🇭🇹", + "honduras": "🇭🇳", + "hong_kong": "🇭🇰", + "hungary": "🇭🇺", + "iceland": "🇮🇸", + "india": "🇮🇳", + "indonesia": "🇮🇩", + "iran": "🇮🇷", + "iraq": "🇮🇶", + "ireland": "🇮🇪", + "isle_of_man": "🇮🇲", + "israel": "🇮🇱", + "it": "🇮🇹", + "cote_divoire": "🇨🇮", + "jamaica": "🇯🇲", + "jp": "🇯🇵", + "jersey": "🇯🇪", + "jordan": "🇯🇴", + "kazakhstan": "🇰🇿", + "kenya": "🇰🇪", + "kiribati": "🇰🇮", + "kosovo": "🇽🇰", + "kuwait": "🇰🇼", + "kyrgyzstan": "🇰🇬", + "laos": "🇱🇦", + "latvia": "🇱🇻", + "lebanon": "🇱🇧", + "lesotho": "🇱🇸", + "liberia": "🇱🇷", + "libya": "🇱🇾", + "liechtenstein": "🇱🇮", + "lithuania": "🇱🇹", + "luxembourg": "🇱🇺", + "macau": "🇲🇴", + "macedonia": "🇲🇰", + "madagascar": "🇲🇬", + "malawi": "🇲🇼", + "malaysia": "🇲🇾", + "maldives": "🇲🇻", + "mali": "🇲🇱", + "malta": "🇲🇹", + "marshall_islands": "🇲🇭", + "martinique": "🇲🇶", + "mauritania": "🇲🇷", + "mauritius": "🇲🇺", + "mayotte": "🇾🇹", + "mexico": "🇲🇽", + "micronesia": "🇫🇲", + "moldova": "🇲🇩", + "monaco": "🇲🇨", + "mongolia": "🇲🇳", + "montenegro": "🇲🇪", + "montserrat": "🇲🇸", + "morocco": "🇲🇦", + "mozambique": "🇲🇿", + "myanmar": "🇲🇲", + "namibia": "🇳🇦", + "nauru": "🇳🇷", + "nepal": "🇳🇵", + "netherlands": "🇳🇱", + "new_caledonia": "🇳🇨", + "new_zealand": "🇳🇿", + "nicaragua": "🇳🇮", + "niger": "🇳🇪", + "nigeria": "🇳🇬", + "niue": "🇳🇺", + "norfolk_island": "🇳🇫", + "northern_mariana_islands": "🇲🇵", + "north_korea": "🇰🇵", + "norway": "🇳🇴", + "oman": "🇴🇲", + "pakistan": "🇵🇰", + "palau": "🇵🇼", + "palestinian_territories": "🇵🇸", + "panama": "🇵🇦", + "papua_new_guinea": "🇵🇬", + "paraguay": "🇵🇾", + "peru": "🇵🇪", + "philippines": "🇵🇭", + "pitcairn_islands": "🇵🇳", + "poland": "🇵🇱", + "portugal": "🇵🇹", + "puerto_rico": "🇵🇷", + "qatar": "🇶🇦", + "reunion": "🇷🇪", + "romania": "🇷🇴", + "ru": "🇷🇺", + "rwanda": "🇷🇼", + "st_barthelemy": "🇧🇱", + "st_helena": "🇸🇭", + "st_kitts_nevis": "🇰🇳", + "st_lucia": "🇱🇨", + "st_pierre_miquelon": "🇵🇲", + "st_vincent_grenadines": "🇻🇨", + "samoa": "🇼🇸", + "san_marino": "🇸🇲", + "sao_tome_principe": "🇸🇹", + "saudi_arabia": "🇸🇦", + "senegal": "🇸🇳", + "serbia": "🇷🇸", + "seychelles": "🇸🇨", + "sierra_leone": "🇸🇱", + "singapore": "🇸🇬", + "sint_maarten": "🇸🇽", + "slovakia": "🇸🇰", + "slovenia": "🇸🇮", + "solomon_islands": "🇸🇧", + "somalia": "🇸🇴", + "south_africa": "🇿🇦", + "south_georgia_south_sandwich_islands": "🇬🇸", + "kr": "🇰🇷", + "south_sudan": "🇸🇸", + "es": "🇪🇸", + "sri_lanka": "🇱🇰", + "sudan": "🇸🇩", + "suriname": "🇸🇷", + "swaziland": "🇸🇿", + "sweden": "🇸🇪", + "switzerland": "🇨🇭", + "syria": "🇸🇾", + "taiwan": "🇹🇼", + "tajikistan": "🇹🇯", + "tanzania": "🇹🇿", + "thailand": "🇹🇭", + "timor_leste": "🇹🇱", + "togo": "🇹🇬", + "tokelau": "🇹🇰", + "tonga": "🇹🇴", + "trinidad_tobago": "🇹🇹", + "tunisia": "🇹🇳", + "tr": "🇹🇷", + "turkmenistan": "🇹🇲", + "turks_caicos_islands": "🇹🇨", + "tuvalu": "🇹🇻", + "uganda": "🇺🇬", + "ukraine": "🇺🇦", + "united_arab_emirates": "🇦🇪", + "uk": "🇬🇧", + "us": "🇺🇸", + "us_virgin_islands": "🇻🇮", + "uruguay": "🇺🇾", + "uzbekistan": "🇺🇿", + "vanuatu": "🇻🇺", + "vatican_city": "🇻🇦", + "venezuela": "🇻🇪", + "vietnam": "🇻🇳", + "wallis_futuna": "🇼🇫", + "western_sahara": "🇪🇭", + "yemen": "🇾🇪", + "zambia": "🇿🇲", + "zimbabwe": "🇿🇼" +} \ No newline at end of file diff --git a/client/js/lounge.js b/client/js/lounge.js index e9b78f2f..5c44ba3a 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -2,12 +2,14 @@ // vendor libraries require("jquery-ui/ui/widgets/sortable"); +require("jquery-textcomplete"); const $ = require("jquery"); const moment = require("moment"); const Mousetrap = require("mousetrap"); const URI = require("urijs"); // our libraries +const emojiMap = require("./libs/simplemap.json"); require("./libs/jquery/inputhistory"); require("./libs/jquery/stickyscroll"); require("./libs/jquery/tabcomplete"); @@ -41,6 +43,80 @@ $(function() { var favicon = $("#favicon"); + // Autocompletion Strategies + + var emojiStrategy = { + id: "emoji", + match: /\B:([-+\w]*)$/, + search: function(term, callback) { + callback($.map(Object.keys(emojiMap), function(e) { + return e.indexOf(term) === 0 ? e : null; + })); + }, + template: function(value) { + return `${emojiMap[value]} ${value}`; + }, + replace: function(value) { + return emojiMap[value]; + }, + index: 1 + }; + + var nicksStrategy = { + id: "nicks", + match: /\B(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/, + search: function(term, callback) { + term = term.slice(1); + if (term[0] === "@") { + callback(completeNicks(term.slice(1)).map(function(val) { + return "@" + val; + })); + } else { + callback(completeNicks(term)); + } + }, + template: function(value) { + if (value[0] === "@") { + return value; + } + return "@" + value; + }, + replace: function(value) { + return value; + }, + index: 1 + }; + + var chanStrategy = { + id: "chans", + match: /\B((#|\+|&|![A-Z0-9]{5})([^\x00\x0A\x0D\x20\x2C\x3A]+(:[^\x00\x0A\x0D\x20\x2C\x3A]*)?)?)$/, + search: function(term, callback, match) { + callback(completeChans(match[0])); + }, + template: function(value) { + return value; + }, + replace: function(value) { + return value; + }, + index: 1 + }; + + var commandStrategy = { + id: "commands", + match: /^\/(\w*)$/, + search: function(term, callback) { + callback(completeCommands("/" + term)); + }, + template: function(value) { + return value; + }, + replace: function(value) { + return value; + }, + index: 1 + }; + socket.on("auth", function(data) { var login = $("#sign-in"); var token; @@ -638,7 +714,18 @@ $(function() { chat.find(".chan.active .chat").trigger("msg.sticky"); // fix growing }) - .tab(complete, {hint: false}); + .tab(completeNicks, {hint: false}) + .textcomplete([emojiStrategy, nicksStrategy, chanStrategy, commandStrategy], { + dropdownClassName: "textcomplete-menu", + placement: "top" + }).on({ + "textComplete:show": function() { + $(this).data("autocompleting", true); + }, + "textComplete:hide": function() { + $(this).data("autocompleting", false); + } + }); var focus = $.noop; if (!("ontouchstart" in window || navigator.maxTouchPoints > 0)) { @@ -1272,14 +1359,31 @@ $(function() { .find(".messages .msg, .date-marker").remove(); } - function complete(word) { - var words = constants.commands.slice(); + function completeNicks(word) { var users = chat.find(".active").find(".users"); - var nicks = users.data("nicks"); + var words = users.data("nicks"); - for (var i in nicks) { - words.push(nicks[i]); - } + return $.grep( + words, + function(w) { + return !w.toLowerCase().indexOf(word.toLowerCase()); + } + ); + } + + function completeCommands(word) { + var words = constants.commands.slice(); + + return $.grep( + words, + function(w) { + return !w.toLowerCase().indexOf(word.toLowerCase()); + } + ); + } + + function completeChans(word) { + var words = []; sidebar.find(".chan") .each(function() { diff --git a/package.json b/package.json index 447d0e43..6f31a1c6 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "express-handlebars": "3.0.0", "fs-extra": "2.1.2", "irc-framework": "2.8.0", + "json-loader": "0.5.4", "ldapjs": "1.0.1", "lodash": "4.17.4", "moment": "2.18.1", @@ -68,6 +69,7 @@ "handlebars": "4.0.6", "handlebars-loader": "1.5.0", "jquery": "3.2.1", + "jquery-textcomplete": "1.8.0", "jquery-ui": "1.12.1", "mocha": "3.3.0", "mousetrap": "1.6.1", diff --git a/webpack.config.js b/webpack.config.js index b54015cb..fb9e2fbf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,6 +13,7 @@ let config = { "js/bundle.vendor.js": [ "handlebars/runtime", "jquery", + "jquery-textcomplete", "jquery-ui/ui/widgets/sortable", "moment", "mousetrap", @@ -46,6 +47,10 @@ let config = { } } }, + { + test: /\.json$/, + loader: "json-loader" + }, { test: /\.tpl$/, include: [ From 7229e0dda4d3565e78dd55eef4e9aa4de293d270 Mon Sep 17 00:00:00 2001 From: Yash Srivastav Date: Mon, 12 Dec 2016 05:12:33 +0530 Subject: [PATCH 105/355] Disable history completion during emoji completion --- client/js/libs/jquery/inputhistory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/libs/jquery/inputhistory.js b/client/js/libs/jquery/inputhistory.js index 778fa696..d6f98360 100644 --- a/client/js/libs/jquery/inputhistory.js +++ b/client/js/libs/jquery/inputhistory.js @@ -56,7 +56,7 @@ import jQuery from "jquery"; case 38: // Up case 40: // Down // NOTICE: This is specific to The Lounge. - if (e.ctrlKey || e.metaKey) { + if (e.ctrlKey || e.metaKey || self.data("autocompleting")) { break; } From 29d8bc9d3da29bac49f1d978eb8768744a1ab43e Mon Sep 17 00:00:00 2001 From: Yash Srivastav Date: Wed, 29 Mar 2017 11:05:40 +0530 Subject: [PATCH 106/355] Add Help for autocompletion --- client/index.html | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/client/index.html b/client/index.html index 1b40bfce..af4d3bcd 100644 --- a/client/index.html +++ b/client/index.html @@ -532,6 +532,48 @@
+

Autocompletion

+ +

Start typing the following characters followed by any letter to + trigger the autocompletion dropdown:

+ +
+
+ @ +
+
+

Nickname

+
+
+ +
+
+ # +
+
+

Chan

+
+
+ +
+
+ / +
+
+

Commands (see list of commands below)

+
+
+ +
+
+ : +
+
+

Emoji

+
+
+ +

Commands

All commands can be autocompleted with tab.

From e000ba45dfb594dfdf8dada5155ab6af30be21b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 25 Apr 2017 01:01:24 +0200 Subject: [PATCH 107/355] Improve details of emoji/chan/nick/command autocompletion - Make dropdown items match context menu items - Disable transparency on dropdown item links - Clean up help page additions - Better align help page autocompletion characters - Use ES6 features (`const`, arrow functions, method definition shorthands) - Use `Array#filter` instead of `$.map` - Do not display `@` in nick completion *when* only one `@` is used (to be less confusing and more consistent) --- client/css/style.css | 16 ++++------- client/index.html | 15 +++++----- client/js/lounge.js | 67 ++++++++++++++++++-------------------------- 3 files changed, 40 insertions(+), 58 deletions(-) diff --git a/client/css/style.css b/client/css/style.css index 522b319d..29323197 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1338,8 +1338,7 @@ kbd { #help .help-item .subject { white-space: nowrap; - padding-right: 10px; - min-width: 150px; + padding-right: 15px; } #help .help-item .description p { @@ -1528,19 +1527,14 @@ kbd { display: inline-block; } -.textcomplete-item { - border-bottom-color: rgba(0, 0, 0, .1); - border-bottom-style: solid; - border-bottom-width: 1px; - margin-top: 0; - margin-bottom: 0; - padding: 10px 8px; -} - .textcomplete-item a { color: #333; } +.textcomplete-item a:hover { + opacity: 1; +} + .textcomplete-item .emoji { margin-right: 8px; width: 16px; diff --git a/client/index.html b/client/index.html index af4d3bcd..5103e0ba 100644 --- a/client/index.html +++ b/client/index.html @@ -534,15 +534,17 @@

Autocompletion

-

Start typing the following characters followed by any letter to - trigger the autocompletion dropdown:

+

+ Start typing the following characters followed by any letter to + trigger the autocompletion dropdown: +

@
-

Nickname

+

Nickname

@@ -551,7 +553,7 @@ #
-

Chan

+

Channel

@@ -560,7 +562,7 @@ /
-

Commands (see list of commands below)

+

Commands (see list of commands below)

@@ -569,11 +571,10 @@ :
-

Emoji

+

Emoji

-

Commands

All commands can be autocompleted with tab.

diff --git a/client/js/lounge.js b/client/js/lounge.js index 5c44ba3a..327808dd 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -45,73 +45,66 @@ $(function() { // Autocompletion Strategies - var emojiStrategy = { + const emojiStrategy = { id: "emoji", match: /\B:([-+\w]*)$/, - search: function(term, callback) { - callback($.map(Object.keys(emojiMap), function(e) { - return e.indexOf(term) === 0 ? e : null; - })); + search(term, callback) { + callback(Object.keys(emojiMap).filter(name => name.indexOf(term) === 0)); }, - template: function(value) { + template(value) { return `${emojiMap[value]} ${value}`; }, - replace: function(value) { + replace(value) { return emojiMap[value]; }, index: 1 }; - var nicksStrategy = { + const nicksStrategy = { id: "nicks", match: /\B(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/, - search: function(term, callback) { + search(term, callback) { term = term.slice(1); if (term[0] === "@") { - callback(completeNicks(term.slice(1)).map(function(val) { - return "@" + val; - })); + callback(completeNicks(term.slice(1)).map(val => "@" + val)); } else { callback(completeNicks(term)); } }, - template: function(value) { - if (value[0] === "@") { - return value; - } - return "@" + value; + template(value) { + return value; }, - replace: function(value) { + replace(value) { return value; }, index: 1 }; - var chanStrategy = { + const chanStrategy = { id: "chans", match: /\B((#|\+|&|![A-Z0-9]{5})([^\x00\x0A\x0D\x20\x2C\x3A]+(:[^\x00\x0A\x0D\x20\x2C\x3A]*)?)?)$/, - search: function(term, callback, match) { + search(term, callback, match) { callback(completeChans(match[0])); }, - template: function(value) { + template(value) { return value; }, - replace: function(value) { + replace(value) { return value; }, index: 1 }; - var commandStrategy = { + const commandStrategy = { id: "commands", match: /^\/(\w*)$/, - search: function(term, callback) { + search(term, callback) { callback(completeCommands("/" + term)); }, - template: function(value) { + template(value) { return value; }, - replace: function(value) { + replace(value) { return value; }, index: 1 @@ -1360,34 +1353,30 @@ $(function() { } function completeNicks(word) { - var users = chat.find(".active").find(".users"); - var words = users.data("nicks"); + const users = chat.find(".active").find(".users"); + const words = users.data("nicks"); return $.grep( words, - function(w) { - return !w.toLowerCase().indexOf(word.toLowerCase()); - } + w => !w.toLowerCase().indexOf(word.toLowerCase()) ); } function completeCommands(word) { - var words = constants.commands.slice(); + const words = constants.commands.slice(); return $.grep( words, - function(w) { - return !w.toLowerCase().indexOf(word.toLowerCase()); - } + w => !w.toLowerCase().indexOf(word.toLowerCase()) ); } function completeChans(word) { - var words = []; + const words = []; sidebar.find(".chan") .each(function() { - var self = $(this); + const self = $(this); if (!self.hasClass("lobby")) { words.push(self.data("title")); } @@ -1395,9 +1384,7 @@ $(function() { return $.grep( words, - function(w) { - return !w.toLowerCase().indexOf(word.toLowerCase()); - } + w => !w.toLowerCase().indexOf(word.toLowerCase()) ); } From 5c3e15e17c0dc4a608f1a7f98bb8ff6dcbef7c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Tue, 25 Apr 2017 22:31:37 +0200 Subject: [PATCH 108/355] Remove json-loader, unnecessary with Webpack v2 See these notes: - https://webpack.js.org/guides/migrating/#json-loader-is-not-required-anymore - https://github.com/webpack-contrib/json-loader#json-loader --- package.json | 1 - webpack.config.js | 4 ---- 2 files changed, 5 deletions(-) diff --git a/package.json b/package.json index 6f31a1c6..fc8693d7 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "express-handlebars": "3.0.0", "fs-extra": "2.1.2", "irc-framework": "2.8.0", - "json-loader": "0.5.4", "ldapjs": "1.0.1", "lodash": "4.17.4", "moment": "2.18.1", diff --git a/webpack.config.js b/webpack.config.js index fb9e2fbf..0cdd6833 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -47,10 +47,6 @@ let config = { } } }, - { - test: /\.json$/, - loader: "json-loader" - }, { test: /\.tpl$/, include: [ From 81a5615c9a58f2d91962f053f5eb0b38320e553b Mon Sep 17 00:00:00 2001 From: PolarizedIons Date: Wed, 26 Apr 2017 19:34:31 +0200 Subject: [PATCH 109/355] Fix nick autocomplete --- client/js/lounge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 539eb975..2d9eac34 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -1380,7 +1380,7 @@ $(function() { } function completeNicks(word) { - const users = chat.find(".active").find(".users"); + const users = chat.find(".active").find(".names-original"); const words = users.data("nicks"); return $.grep( From 927c40739e7bb82aa2066b2f008abb287e93afc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Wed, 26 Apr 2017 23:30:51 +0200 Subject: [PATCH 110/355] Fix network layout displaying the scrollbar incorrectly This was introduced by https://github.com/thelounge/lounge/pull/856/files#diff-97db1f70168fb5f12457b238ff6052b5R773 (and L794-798): a right position got introduced for all channel containers, but default position for other types of containers was absent before this script. --- client/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/client/css/style.css b/client/css/style.css index 490439ce..8f079476 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -762,6 +762,7 @@ kbd { #chat .chat { bottom: 0; left: 0; + right: 0; overflow: auto; -webkit-overflow-scrolling: touch; position: absolute; From b9ead20fc7bef1149370e8f15e318bda65a48ba2 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 27 Apr 2017 19:39:32 +0000 Subject: [PATCH 111/355] fix(package): update fs-extra to version 3.0.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7c54e1e..00ab14c3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "event-stream": "3.3.4", "express": "4.15.2", "express-handlebars": "3.0.0", - "fs-extra": "2.1.2", + "fs-extra": "3.0.0", "irc-framework": "2.8.0", "ldapjs": "1.0.1", "lodash": "4.17.4", From d6d7df62fe02f0933ea187767f2c8284ce4f4b40 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 28 Apr 2017 18:58:14 +0300 Subject: [PATCH 112/355] Fix away message disappearing Closes #1102 --- src/client.js | 8 ++++---- src/clientManager.js | 1 + src/plugins/inputs/away.js | 2 ++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/client.js b/src/client.js index aa3b7045..554f2b6c 100644 --- a/src/client.js +++ b/src/client.js @@ -66,8 +66,9 @@ function Client(manager, name, config) { if (typeof config !== "object") { config = {}; } + _.merge(this, { - awayMessage: "", + awayMessage: config.awayMessage || "", lastActiveChannel: -1, attachedClients: {}, config: config, @@ -482,8 +483,6 @@ Client.prototype.clientAttach = function(socketId) { var client = this; var save = false; - client.attachedClients[socketId] = client.lastActiveChannel; - if (client.awayMessage && _.size(client.attachedClients) === 0) { client.networks.forEach(function(network) { // Only remove away on client attachment if @@ -494,6 +493,8 @@ Client.prototype.clientAttach = function(socketId) { }); } + client.attachedClients[socketId] = client.lastActiveChannel; + // Update old networks to store ip and hostmask client.networks.forEach(network => { if (!network.ip) { @@ -539,7 +540,6 @@ Client.prototype.save = _.debounce(function SaveClient() { const client = this; let json = {}; - json.awayMessage = client.awayMessage; json.networks = this.networks.map(n => n.export()); client.manager.updateUser(client.name, json); }, 1000, {maxWait: 10000}); diff --git a/src/clientManager.js b/src/clientManager.js index a0f94f0f..6e73af19 100644 --- a/src/clientManager.js +++ b/src/clientManager.js @@ -104,6 +104,7 @@ ClientManager.prototype.addUser = function(name, password, enableLog) { user: name, password: password || "", log: enableLog, + awayMessage: "", networks: [] }; fs.writeFileSync( diff --git a/src/plugins/inputs/away.js b/src/plugins/inputs/away.js index 34c58596..aa4d604b 100644 --- a/src/plugins/inputs/away.js +++ b/src/plugins/inputs/away.js @@ -14,4 +14,6 @@ exports.input = function(network, chan, cmd, args) { } network.awayMessage = reason; + + this.save(); }; From a3810dea06f1559aca2f155dc95e3f08b9afcae5 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 28 Apr 2017 21:58:00 +0300 Subject: [PATCH 113/355] Fix chat layout on small devices when users list is hidden Fixes #1092 --- client/css/style.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/css/style.css b/client/css/style.css index 8f079476..af9b5605 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1856,6 +1856,10 @@ kbd { display: block; } + #chat .channel .chat { + right: 0; + } + #chat .sidebar { right: -180px; } From 70655120cb2a51756c240c46ac0206aa28c81631 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Fri, 28 Apr 2017 14:45:18 -0700 Subject: [PATCH 114/355] Add ctcp to constants, adds to auto-complete --- client/js/constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/js/constants.js b/client/js/constants.js index c9587709..e1990158 100644 --- a/client/js/constants.js +++ b/client/js/constants.js @@ -7,6 +7,7 @@ const commands = [ "/banlist", "/close", "/connect", + "/ctcp", "/deop", "/devoice", "/disconnect", From 0b645d54c68d719a0ca67133a70653da099a4ead Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Fri, 28 Apr 2017 08:28:45 +0300 Subject: [PATCH 115/355] Add support for 0x04 hex colors Ref: https://modern.ircdocs.horse/formatting.html#hex-color --- .../handlebars/ircmessageparser/parseStyle.js | 34 ++++- client/js/libs/handlebars/parse.js | 18 ++- .../handlebars/ircmessageparser/parseStyle.js | 119 ++++++++++++++++++ 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/client/js/libs/handlebars/ircmessageparser/parseStyle.js index d23d5bd6..a0f3fd08 100644 --- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -3,6 +3,7 @@ // Styling control codes const BOLD = "\x02"; const COLOR = "\x03"; +const HEX_COLOR = "\x04"; const RESET = "\x0f"; const REVERSE = "\x16"; const ITALIC = "\x1d"; @@ -12,6 +13,9 @@ const UNDERLINE = "\x1f"; // integers, `XX` is the text color and `YY` is an optional background color. const colorRx = /^(\d{1,2})(?:,(\d{1,2}))?/; +// 6-char Hex color code matcher +const hexColorRx = /^([0-9a-f]{6})(?:,([0-9a-f]{6}))?/i; + // Represents all other control codes that to be ignored/filtered from the text const controlCodesRx = /[\u0000-\u001F]/g; @@ -26,12 +30,14 @@ function parseStyle(text) { // At any given time, these carry style information since last time a styling // control code was met. - let colorCodes, bold, textColor, bgColor, reverse, italic, underline; + let colorCodes, bold, textColor, bgColor, hexColor, hexBgColor, reverse, italic, underline; const resetStyle = () => { bold = false; textColor = undefined; bgColor = undefined; + hexColor = undefined; + hexBgColor = undefined; reverse = false; italic = false; underline = false; @@ -57,6 +63,8 @@ function parseStyle(text) { bold, textColor, bgColor, + hexColor, + hexBgColor, reverse, italic, underline, @@ -113,6 +121,28 @@ function parseStyle(text) { } break; + case HEX_COLOR: + emitFragment(); + + colorCodes = text.slice(position + 1).match(hexColorRx); + + if (colorCodes) { + hexColor = colorCodes[1].toUpperCase(); + if (colorCodes[2]) { + hexBgColor = colorCodes[2].toUpperCase(); + } + // Color code length is > 1, so bump the current position cursor by as + // much (and reset the start cursor for the current text block as well) + position += colorCodes[0].length; + start = position + 1; + } else { + // If no color codes were found, toggles back to no colors (like BOLD). + hexColor = undefined; + hexBgColor = undefined; + } + + break; + case REVERSE: emitFragment(); reverse = !reverse; @@ -139,7 +169,7 @@ function parseStyle(text) { return result; } -const properties = ["bold", "textColor", "bgColor", "italic", "underline", "reverse"]; +const properties = ["bold", "textColor", "bgColor", "hexColor", "hexBgColor", "italic", "underline", "reverse"]; function prepare(text) { return parseStyle(text) diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index 915a432c..426cbe4f 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -24,10 +24,24 @@ function createFragment(fragment) { if (fragment.underline) { classes.push("irc-underline"); } + + let attributes = classes.length ? ` class="${classes.join(" ")}"` : ""; const escapedText = Handlebars.Utils.escapeExpression(fragment.text); - if (classes.length) { - return `${escapedText}`; + + if (fragment.hexColor) { + attributes += ` style="color:#${fragment.hexColor}`; + + if (fragment.hexBgColor) { + attributes += `;background-color:#${fragment.hexBgColor}`; + } + + attributes += "\""; } + + if (attributes.length) { + return `${escapedText}`; + } + return escapedText; } diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js index 6af289c4..e978cbcc 100644 --- a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -10,6 +10,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -30,6 +32,8 @@ describe("parseStyle", () => { bold: true, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -50,6 +54,8 @@ describe("parseStyle", () => { bold: false, textColor: 8, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -69,6 +75,8 @@ describe("parseStyle", () => { const expected = [{ textColor: 4, bgColor: 8, + hexColor: undefined, + hexBgColor: undefined, bold: false, reverse: false, italic: false, @@ -90,6 +98,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: true, underline: false, @@ -104,12 +114,101 @@ describe("parseStyle", () => { expect(actual).to.deep.equal(expected); }); + it("should parse hex colors", () => { + const input = "test \x04FFFFFFnice \x02\x04RES006 \x0303,04\x04ff00FFcolored\x04eeeaFF,001122 background\x04\x03\x02?"; + const expected = [{ + bold: false, + textColor: undefined, + bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "test ", + + start: 0, + end: 5 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + hexColor: "FFFFFF", + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "nice ", + + start: 5, + end: 10 + }, { + bold: true, + textColor: undefined, + bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "RES006 ", + + start: 10, + end: 17 + }, { + bold: true, + textColor: 3, + bgColor: 4, + hexColor: "FF00FF", + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "colored", + + start: 17, + end: 24 + }, { + bold: true, + textColor: 3, + bgColor: 4, + hexColor: "EEEAFF", + hexBgColor: "001122", + reverse: false, + italic: false, + underline: false, + text: " background", + + start: 24, + end: 35 + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + text: "?", + + start: 35, + end: 36 + }]; + + const actual = parseStyle(input); + + expect(actual).to.deep.equal(expected); + }); + it("should carry state corretly forward", () => { const input = "\x02bold\x038yellow\x02nonBold\x03default"; const expected = [{ bold: true, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -121,6 +220,8 @@ describe("parseStyle", () => { bold: true, textColor: 8, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -132,6 +233,8 @@ describe("parseStyle", () => { bold: false, textColor: 8, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -143,6 +246,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -163,6 +268,8 @@ describe("parseStyle", () => { bold: true, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -174,6 +281,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -185,6 +294,8 @@ describe("parseStyle", () => { bold: true, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -205,6 +316,8 @@ describe("parseStyle", () => { bold: true, textColor: 4, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: true, italic: true, underline: true, @@ -216,6 +329,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -236,6 +351,8 @@ describe("parseStyle", () => { bold: false, textColor: undefined, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, @@ -258,6 +375,8 @@ describe("parseStyle", () => { bold: false, textColor: 12, bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, reverse: false, italic: false, underline: false, From 14ea84988f84dc66c1f01ba42586ca21d5c35e35 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 29 Apr 2017 07:07:10 +0000 Subject: [PATCH 116/355] chore(package): update nyc to version 10.3.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7c54e1e..ca655e98 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ "mocha": "3.3.0", "mousetrap": "1.6.1", "npm-run-all": "4.0.2", - "nyc": "10.2.0", + "nyc": "10.3.0", "socket.io-client": "1.7.3", "stylelint": "7.10.1", "urijs": "1.18.10", From dd48ba4e87dbcf3d5bbc28dc29b95fd3fe367dae Mon Sep 17 00:00:00 2001 From: PolarizedIons Date: Sat, 29 Apr 2017 13:17:21 +0200 Subject: [PATCH 117/355] Fix channel sorting messing up the order --- src/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client.js b/src/client.js index aa3b7045..8f0d06ba 100644 --- a/src/client.js +++ b/src/client.js @@ -422,7 +422,7 @@ Client.prototype.sort = function(data) { switch (data.type) { case "networks": this.networks.sort((a, b) => { - return order.indexOf(a.id) > order.indexOf(b.id); + return order.indexOf(a.id) - order.indexOf(b.id); }); // Sync order to connected clients @@ -437,7 +437,7 @@ Client.prototype.sort = function(data) { } network.channels.sort((a, b) => { - return order.indexOf(a.id) > order.indexOf(b.id); + return order.indexOf(a.id) - order.indexOf(b.id); }); // Sync order to connected clients From 29cd9b80ef2173e980dbe1ad624c7394e4cc5490 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 29 Apr 2017 20:56:52 +0000 Subject: [PATCH 118/355] chore(package): update handlebars to version 4.0.7 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0560e6d7..1c9d5b87 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "eslint": "3.19.0", "font-awesome": "4.7.0", "fuzzy": "0.1.3", - "handlebars": "4.0.6", + "handlebars": "4.0.7", "handlebars-loader": "1.5.0", "jquery": "3.2.1", "jquery-textcomplete": "1.8.0", From f7b7248ff7b0f606f409106bc09b4b086387a401 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Thu, 27 Apr 2017 21:32:58 +0300 Subject: [PATCH 119/355] Fix nick autocomplete Fixes #1119. --- client/js/lounge.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/client/js/lounge.js b/client/js/lounge.js index 2d9eac34..b11b2fc7 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -300,8 +300,9 @@ $(function() { var nicks = chan.find(".users").data("nicks"); if (nicks) { var find = nicks.indexOf(data.msg.from); - if (find !== -1 && typeof move === "function") { - move(nicks, find, 0); + if (find !== -1) { + nicks.splice(find, 1); + nicks.unshift(data.msg.from); } } } @@ -389,9 +390,9 @@ $(function() { .attr("placeholder", nicks.length + " " + (nicks.length === 1 ? "user" : "users")); users + .data("nicks", nicks) .find(".names-original") - .html(templates.user(data)) - .data("nicks", nicks); + .html(templates.user(data)); // Refresh user search if (search.val().length) { @@ -1380,7 +1381,13 @@ $(function() { } function completeNicks(word) { - const users = chat.find(".active").find(".names-original"); + const users = chat.find(".active .users"); + + // Lobbies and private chats do not have an user list + if (!users.length) { + return []; + } + const words = users.data("nicks"); return $.grep( @@ -1534,17 +1541,6 @@ $(function() { $("#nick-value").text(nick); } - function move(array, old_index, new_index) { - if (new_index >= array.length) { - var k = new_index - array.length; - while ((k--) + 1) { - this.push(undefined); - } - } - array.splice(new_index, 0, array.splice(old_index, 1)[0]); - return array; - } - function toggleNotificationMarkers(newState) { // Toggles the favicon to red when there are unread notifications if (favicon.data("toggled") !== newState) { From 381ea326f4a2d3588854485f17025258e1600ee7 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Sun, 30 Apr 2017 15:07:09 +0300 Subject: [PATCH 120/355] Disable tabindex on userlist search input Fixes #1036. --- client/views/chat.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/views/chat.tpl b/client/views/chat.tpl index 898dd75b..55924b6f 100644 --- a/client/views/chat.tpl +++ b/client/views/chat.tpl @@ -23,7 +23,7 @@