From df703dc73aff2fc64047e3fa110d4a39e93fb716 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 5 Dec 2017 19:44:40 +0200 Subject: [PATCH 1/2] Upgrade to new textcomplete library --- client/js/autocompletion.js | 60 +++++++++++++++++++++++++------------ client/js/lounge.js | 1 - client/js/options.js | 6 ++-- package.json | 2 +- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/client/js/autocompletion.js b/client/js/autocompletion.js index dad11900..87c173f2 100644 --- a/client/js/autocompletion.js +++ b/client/js/autocompletion.js @@ -3,11 +3,20 @@ 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 input = $("#input"); +const Textcomplete = require("textcomplete/lib/textcomplete").default; +const Textarea = require("textcomplete/lib/textarea").default; +const editor = new Textarea(input.get(0)); +let textcomplete; + +module.exports = { + enable: enableAutocomplete, + disable: () => textcomplete.destroy(false), +}; + const chat = $("#chat"); const sidebar = $("#sidebar"); const emojiSearchTerms = Object.keys(emojiMap); @@ -138,31 +147,44 @@ const backgroundColorStrategy = { index: 2, }; -const input = $("#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); + textcomplete = new Textcomplete(editor, { + dropdown: { + className: "textcomplete-menu", + placement: "top", }, }); + + textcomplete.register([ + emojiStrategy, + nicksStrategy, + chanStrategy, + commandStrategy, + foregroundColorStrategy, + backgroundColorStrategy, + ]); + + // Activate the first item by default + // https://github.com/yuku-t/textcomplete/issues/93 + textcomplete.on("rendered", () => { + if (textcomplete.dropdown.items.length > 0) { + textcomplete.dropdown.items[0].activate(); + } + }); + + textcomplete.on("show", () => { + input.data("autocompleting", true); + }); + + textcomplete.on("hidden", () => { + input.data("autocompleting", false); + }); } function fuzzyGrep(term, array) { diff --git a/client/js/lounge.js b/client/js/lounge.js index 3adf2881..0aaa9b76 100644 --- a/client/js/lounge.js +++ b/client/js/lounge.js @@ -17,7 +17,6 @@ const render = require("./render"); require("./socket-events"); const storage = require("./localStorage"); const utils = require("./utils"); -require("./autocompletion"); require("./webpush"); require("./keybinds"); require("./clipboard"); diff --git a/client/js/options.js b/client/js/options.js index 0c62bee1..4b5e2b80 100644 --- a/client/js/options.js +++ b/client/js/options.js @@ -1,11 +1,11 @@ "use strict"; const $ = require("jquery"); -require("jquery-textcomplete"); const escapeRegExp = require("lodash/escapeRegExp"); const userStyles = $("#user-specified-css"); const storage = require("./localStorage"); const tz = require("./libs/handlebars/tz"); +const autocompletion = require("./autocompletion"); const windows = $("#windows"); const chat = $("#chat"); @@ -123,9 +123,9 @@ module.exports.initialize = () => { chat.toggleClass("show-seconds", self.prop("checked")); } else if (name === "autocomplete") { if (self.prop("checked")) { - $("#input").trigger("autocomplete:on"); + autocompletion.enable(); } else { - $("#input").textcomplete("destroy"); + autocompletion.disable(); } } }).find("input") diff --git a/package.json b/package.json index c4521de5..d0d6de5e 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "handlebars-loader": "1.6.0", "intersection-observer": "0.5.0", "jquery": "3.2.1", - "jquery-textcomplete": "1.8.4", "jquery-ui": "1.12.1", "mocha": "4.0.1", "mousetrap": "1.6.1", @@ -85,6 +84,7 @@ "socket.io-client": "2.0.4", "stylelint": "8.3.1", "stylelint-config-standard": "18.0.0", + "textcomplete": "0.14.5", "webpack": "3.10.0" } } From e462ed6270f39abc8ed2dcaf348c7d0604387cb9 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Tue, 5 Dec 2017 21:05:53 +0200 Subject: [PATCH 2/2] Rewrite tabcomplete --- client/js/autocompletion.js | 59 +++++- client/js/libs/jquery/tabcomplete.js | 258 --------------------------- 2 files changed, 51 insertions(+), 266 deletions(-) delete mode 100644 client/js/libs/jquery/tabcomplete.js diff --git a/client/js/autocompletion.js b/client/js/autocompletion.js index 87c173f2..ff1fb259 100644 --- a/client/js/autocompletion.js +++ b/client/js/autocompletion.js @@ -2,19 +2,22 @@ const $ = require("jquery"); const fuzzy = require("fuzzy"); +const Mousetrap = require("mousetrap"); const emojiMap = require("./libs/simplemap.json"); const constants = require("./constants"); -require("./libs/jquery/tabcomplete"); const input = $("#input"); const Textcomplete = require("textcomplete/lib/textcomplete").default; const Textarea = require("textcomplete/lib/textarea").default; -const editor = new Textarea(input.get(0)); let textcomplete; module.exports = { enable: enableAutocomplete, - disable: () => textcomplete.destroy(false), + disable: () => { + input.unbind("input.tabcomplete"); + Mousetrap(input.get(0)).unbind("tab", "keydown"); + textcomplete.destroy(); + }, }; const chat = $("#chat"); @@ -147,13 +150,53 @@ const backgroundColorStrategy = { index: 2, }; -input - .tab((word) => completeNicks(word, false), {hint: false}) - .on("autocomplete:on", function() { - enableAutocomplete(); +function enableAutocomplete() { + let tabCount = 0; + let currentMatches = []; + + input.on("input.tabcomplete", () => { + tabCount = 0; + currentMatches = []; }); -function enableAutocomplete() { + Mousetrap(input.get(0)).bind("tab", (e) => { + if (input.data("autocompleting")) { + return; + } + + e.preventDefault(); + + const text = input.val(); + + if (input.get(0).selectionStart !== text.length) { + return; + } + + let lastWord; + + if (tabCount === 0) { + lastWord = text.split(/\s/).pop(); + + if (lastWord.length === 0) { + return; + } + + currentMatches = completeNicks(lastWord, false); + + if (currentMatches.length === 0) { + return; + } + } else { + lastWord = nicksStrategy.replace([0, currentMatches[(tabCount - 1) % currentMatches.length]]); + } + + const matchedNick = currentMatches[tabCount % currentMatches.length]; + input.val(text.substr(0, input.get(0).selectionStart - lastWord.length) + nicksStrategy.replace([0, matchedNick])); + + tabCount++; + }, "keydown"); + + const editor = new Textarea(input.get(0)); textcomplete = new Textcomplete(editor, { dropdown: { className: "textcomplete-menu", diff --git a/client/js/libs/jquery/tabcomplete.js b/client/js/libs/jquery/tabcomplete.js deleted file mode 100644 index 5d04794c..00000000 --- a/client/js/libs/jquery/tabcomplete.js +++ /dev/null @@ -1,258 +0,0 @@ -import jQuery from "jquery"; - -/*! - * tabcomplete - * http://github.com/erming/tabcomplete - * v1.3.1 - */ -(function($) { - var keys = { - backspace: 8, - tab: 9, - up: 38, - down: 40 - }; - - $.tabcomplete = {}; - $.tabcomplete.defaultOptions = { - after: "", - arrowKeys: false, - caseSensitive: false, - hint: "placeholder", - minLength: 1 - }; - - $.fn.tab = // Alias - $.fn.tabcomplete = function(args, options) { - if (this.length > 1) { - return this.each(function() { - $(this).tabcomplete(args, options); - }); - } - - // Only enable the plugin on and