diff --git a/client/js/autocompletion.js b/client/js/autocompletion.js
new file mode 100644
index 00000000..cdd39ec6
--- /dev/null
+++ b/client/js/autocompletion.js
@@ -0,0 +1,217 @@
+"use strict";
+
+const $ = require("jquery");
+const fuzzy = require("fuzzy");
+const emojiMap = require("./libs/simplemap.json");
+const options = require("./options");
+const constants = require("./constants");
+require("jquery-textcomplete");
+require("./libs/jquery/tabcomplete");
+
+const chat = $("#chat");
+const sidebar = $("#sidebar");
+const emojiSearchTerms = Object.keys(emojiMap);
+const emojiStrategy = {
+ id: "emoji",
+ match: /\B:([-+\w:?]{2,}):?$/,
+ search(term, callback) {
+ // Trim colon from the matched term,
+ // as we are unable to get a clean string from match regex
+ term = term.replace(/:$/, ""),
+ callback(fuzzyGrep(term, emojiSearchTerms));
+ },
+ template([string, original]) {
+ return `${emojiMap[original]} ${string}`;
+ },
+ replace([, original]) {
+ return emojiMap[original];
+ },
+ index: 1
+};
+
+const nicksStrategy = {
+ id: "nicks",
+ match: /\B(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/,
+ search(term, callback) {
+ term = term.slice(1);
+ if (term[0] === "@") {
+ callback(completeNicks(term.slice(1), true)
+ .map((val) => ["@" + val[0], "@" + val[1]]));
+ } else {
+ callback(completeNicks(term, true));
+ }
+ },
+ template([string, ]) {
+ return string;
+ },
+ replace([, original]) {
+ return original;
+ },
+ index: 1
+};
+
+const chanStrategy = {
+ id: "chans",
+ match: /\B((#|\+|&|![A-Z0-9]{5})([^\x00\x0A\x0D\x20\x2C\x3A]+(:[^\x00\x0A\x0D\x20\x2C\x3A]*)?)?)$/,
+ search(term, callback, match) {
+ callback(completeChans(match[0]));
+ },
+ template([string,]) {
+ return string;
+ },
+ replace([, original]) {
+ return original;
+ },
+ index: 1
+};
+
+const commandStrategy = {
+ id: "commands",
+ match: /^\/(\w*)$/,
+ search(term, callback) {
+ callback(completeCommands("/" + term));
+ },
+ template([string, ]) {
+ return string;
+ },
+ replace([, original]) {
+ return original;
+ },
+ index: 1
+};
+
+const foregroundColorStrategy = {
+ id: "foreground-colors",
+ match: /\x03(\d{0,2}|[A-Za-z ]{0,10})$/,
+ search(term, callback) {
+ term = term.toLowerCase();
+
+ const matchingColorCodes = constants.colorCodeMap
+ .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
+ .map((i) => {
+ if (fuzzy.test(term, i[1])) {
+ return [i[0], fuzzy.match(term, i[1], {
+ pre: "",
+ post: ""
+ }).rendered];
+ }
+ return i;
+ });
+
+ callback(matchingColorCodes);
+ },
+ template(value) {
+ return `${value[1]}`;
+ },
+ replace(value) {
+ return "\x03" + value[0];
+ },
+ index: 1
+};
+
+const backgroundColorStrategy = {
+ id: "background-colors",
+ match: /\x03(\d{2}),(\d{0,2}|[A-Za-z ]{0,10})$/,
+ search(term, callback, match) {
+ term = term.toLowerCase();
+ const matchingColorCodes = constants.colorCodeMap
+ .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
+ .map((pair) => {
+ if (fuzzy.test(term, pair[1])) {
+ return [pair[0], fuzzy.match(term, pair[1], {
+ pre: "",
+ post: ""
+ }).rendered];
+ }
+ return pair;
+ })
+ .map((pair) => pair.concat(match[1])); // Needed to pass fg color to `template`...
+
+ callback(matchingColorCodes);
+ },
+ template(value) {
+ return `${value[1]}`;
+ },
+ replace(value) {
+ return "\x03$1," + value[0];
+ },
+ index: 2
+};
+
+const input = $("#input")
+ .tab((word) => completeNicks(word, false), {hint: false})
+ .on("autocomplete:on", function() {
+ enableAutocomplete();
+ });
+
+if (options.autocomplete) {
+ enableAutocomplete();
+}
+
+function enableAutocomplete() {
+ input.textcomplete([
+ emojiStrategy, nicksStrategy, chanStrategy, commandStrategy,
+ foregroundColorStrategy, backgroundColorStrategy
+ ], {
+ dropdownClassName: "textcomplete-menu",
+ placement: "top"
+ }).on({
+ "textComplete:show": function() {
+ $(this).data("autocompleting", true);
+ },
+ "textComplete:hide": function() {
+ $(this).data("autocompleting", false);
+ }
+ });
+}
+
+function fuzzyGrep(term, array) {
+ const results = fuzzy.filter(
+ term,
+ array,
+ {
+ pre: "",
+ post: ""
+ }
+ );
+ return results.map((el) => [el.string, el.original]);
+}
+
+function completeNicks(word, isFuzzy) {
+ const users = chat.find(".active .users");
+ word = word.toLowerCase();
+
+ // Lobbies and private chats do not have an user list
+ if (!users.length) {
+ return [];
+ }
+
+ const words = users.data("nicks");
+ if (isFuzzy) {
+ return fuzzyGrep(word, words);
+ }
+ return $.grep(
+ words,
+ (w) => !w.toLowerCase().indexOf(word)
+ );
+}
+
+function completeCommands(word) {
+ const words = constants.commands.slice();
+
+ return fuzzyGrep(word, words);
+}
+
+function completeChans(word) {
+ const words = [];
+
+ sidebar.find(".chan")
+ .each(function() {
+ const self = $(this);
+ if (!self.hasClass("lobby")) {
+ words.push(self.data("title"));
+ }
+ });
+
+ return fuzzyGrep(word, words);
+}
diff --git a/client/js/lounge.js b/client/js/lounge.js
index 9fcf8cb1..99b0b91c 100644
--- a/client/js/lounge.js
+++ b/client/js/lounge.js
@@ -2,7 +2,6 @@
// vendor libraries
require("jquery-ui/ui/widgets/sortable");
-require("jquery-textcomplete");
const $ = require("jquery");
const moment = require("moment");
const Mousetrap = require("mousetrap");
@@ -10,18 +9,17 @@ const URI = require("urijs");
const fuzzy = require("fuzzy");
// our libraries
-const emojiMap = require("./libs/simplemap.json");
require("./libs/jquery/inputhistory");
require("./libs/jquery/stickyscroll");
-require("./libs/jquery/tabcomplete");
const helpers_roundBadgeNumber = require("./libs/handlebars/roundBadgeNumber");
const slideoutMenu = require("./libs/slideout");
const templates = require("../views");
const socket = require("./socket");
require("./socket-events");
-const constants = require("./constants");
const storage = require("./localStorage");
+const options = require("./options");
const utils = require("./utils");
+require("./autocompletion");
require("./webpush");
$(function() {
@@ -44,140 +42,7 @@ $(function() {
pop.play();
});
- // Autocompletion Strategies
-
- const emojiSearchTerms = Object.keys(emojiMap);
- const emojiStrategy = {
- id: "emoji",
- match: /\B:([-+\w:?]{2,}):?$/,
- search(term, callback) {
- // Trim colon from the matched term,
- // as we are unable to get a clean string from match regex
- term = term.replace(/:$/, ""),
- callback(fuzzyGrep(term, emojiSearchTerms));
- },
- template([string, original]) {
- return `${emojiMap[original]} ${string}`;
- },
- replace([, original]) {
- return emojiMap[original];
- },
- index: 1
- };
-
- const nicksStrategy = {
- id: "nicks",
- match: /\B(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/,
- search(term, callback) {
- term = term.slice(1);
- if (term[0] === "@") {
- callback(completeNicks(term.slice(1), true)
- .map((val) => ["@" + val[0], "@" + val[1]]));
- } else {
- callback(completeNicks(term, true));
- }
- },
- template([string, ]) {
- return string;
- },
- replace([, original]) {
- return original;
- },
- index: 1
- };
-
- const chanStrategy = {
- id: "chans",
- match: /\B((#|\+|&|![A-Z0-9]{5})([^\x00\x0A\x0D\x20\x2C\x3A]+(:[^\x00\x0A\x0D\x20\x2C\x3A]*)?)?)$/,
- search(term, callback, match) {
- callback(completeChans(match[0]));
- },
- template([string,]) {
- return string;
- },
- replace([, original]) {
- return original;
- },
- index: 1
- };
-
- const commandStrategy = {
- id: "commands",
- match: /^\/(\w*)$/,
- search(term, callback) {
- callback(completeCommands("/" + term));
- },
- template([string, ]) {
- return string;
- },
- replace([, original]) {
- return original;
- },
- index: 1
- };
-
- const foregroundColorStrategy = {
- id: "foreground-colors",
- match: /\x03(\d{0,2}|[A-Za-z ]{0,10})$/,
- search(term, callback) {
- term = term.toLowerCase();
-
- const matchingColorCodes = constants.colorCodeMap
- .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
- .map((i) => {
- if (fuzzy.test(term, i[1])) {
- return [i[0], fuzzy.match(term, i[1], {
- pre: "",
- post: ""
- }).rendered];
- }
- return i;
- });
-
- callback(matchingColorCodes);
- },
- template(value) {
- return `${value[1]}`;
- },
- replace(value) {
- return "\x03" + value[0];
- },
- index: 1
- };
-
- const backgroundColorStrategy = {
- id: "background-colors",
- match: /\x03(\d{2}),(\d{0,2}|[A-Za-z ]{0,10})$/,
- search(term, callback, match) {
- term = term.toLowerCase();
- const matchingColorCodes = constants.colorCodeMap
- .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1]))
- .map((pair) => {
- if (fuzzy.test(term, pair[1])) {
- return [pair[0], fuzzy.match(term, pair[1], {
- pre: "",
- post: ""
- }).rendered];
- }
- return pair;
- })
- .map((pair) => pair.concat(match[1])); // Needed to pass fg color to `template`...
-
- callback(matchingColorCodes);
- },
- template(value) {
- return `${value[1]}`;
- },
- replace(value) {
- return "\x03$1," + value[0];
- },
- index: 2
- };
-
- var options = require("./options");
-
var windows = $("#windows");
-
var viewport = $("#viewport");
var sidebarSlide = slideoutMenu(viewport[0], sidebar[0]);
var contextMenuContainer = $("#context-menu-container");
@@ -290,33 +155,8 @@ $(function() {
) + "px";
chat.find(".chan.active .chat").trigger("msg.sticky"); // fix growing
- })
- .tab((word) => completeNicks(word, false), {hint: false})
- .on("autocomplete:on", function() {
- enableAutocomplete();
});
- if (options.autocomplete) {
- enableAutocomplete();
- }
-
- function enableAutocomplete() {
- input.textcomplete([
- emojiStrategy, nicksStrategy, chanStrategy, commandStrategy,
- foregroundColorStrategy, backgroundColorStrategy
- ], {
- 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)) {
focus = function() {
@@ -932,57 +772,6 @@ $(function() {
}
}());
- function fuzzyGrep(term, array) {
- const results = fuzzy.filter(
- term,
- array,
- {
- pre: "",
- post: ""
- }
- );
- return results.map((el) => [el.string, el.original]);
- }
-
- function completeNicks(word, isFuzzy) {
- const users = chat.find(".active .users");
- word = word.toLowerCase();
-
- // Lobbies and private chats do not have an user list
- if (!users.length) {
- return [];
- }
-
- const words = users.data("nicks");
- if (isFuzzy) {
- return fuzzyGrep(word, words);
- }
- return $.grep(
- words,
- (w) => !w.toLowerCase().indexOf(word)
- );
- }
-
- function completeCommands(word) {
- const words = constants.commands.slice();
-
- return fuzzyGrep(word, words);
- }
-
- function completeChans(word) {
- const words = [];
-
- sidebar.find(".chan")
- .each(function() {
- const self = $(this);
- if (!self.hasClass("lobby")) {
- words.push(self.data("title"));
- }
- });
-
- return fuzzyGrep(word, words);
- }
-
$(document).on("visibilitychange focus click", () => {
if (sidebar.find(".highlight").length === 0) {
utils.toggleNotificationMarkers(false);