Merge pull request #1800 from thelounge/xpaw/textcomplete
Update textcomplete library and rewrite tabcomplete
This commit is contained in:
commit
b662764caa
@ -2,11 +2,23 @@
|
||||
|
||||
const $ = require("jquery");
|
||||
const fuzzy = require("fuzzy");
|
||||
const Mousetrap = require("mousetrap");
|
||||
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;
|
||||
let textcomplete;
|
||||
|
||||
module.exports = {
|
||||
enable: enableAutocomplete,
|
||||
disable: () => {
|
||||
input.unbind("input.tabcomplete");
|
||||
Mousetrap(input.get(0)).unbind("tab", "keydown");
|
||||
textcomplete.destroy();
|
||||
},
|
||||
};
|
||||
|
||||
const chat = $("#chat");
|
||||
const sidebar = $("#sidebar");
|
||||
@ -138,31 +150,84 @@ const backgroundColorStrategy = {
|
||||
index: 2,
|
||||
};
|
||||
|
||||
const input = $("#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 = [];
|
||||
});
|
||||
|
||||
if (options.autocomplete) {
|
||||
enableAutocomplete();
|
||||
}
|
||||
Mousetrap(input.get(0)).bind("tab", (e) => {
|
||||
if (input.data("autocompleting")) {
|
||||
return;
|
||||
}
|
||||
|
||||
function enableAutocomplete() {
|
||||
input.textcomplete([
|
||||
emojiStrategy, nicksStrategy, chanStrategy, commandStrategy,
|
||||
foregroundColorStrategy, backgroundColorStrategy,
|
||||
], {
|
||||
dropdownClassName: "textcomplete-menu",
|
||||
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",
|
||||
placement: "top",
|
||||
}).on({
|
||||
"textComplete:show": function() {
|
||||
$(this).data("autocompleting", true);
|
||||
},
|
||||
"textComplete:hide": function() {
|
||||
$(this).data("autocompleting", false);
|
||||
},
|
||||
});
|
||||
|
||||
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) {
|
||||
|
258
client/js/libs/jquery/tabcomplete.js
vendored
258
client/js/libs/jquery/tabcomplete.js
vendored
@ -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 <input> and <textarea> elements.
|
||||
var tag = this.prop("tagName");
|
||||
if (tag != "INPUT" && tag != "TEXTAREA") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set default options.
|
||||
options = $.extend(
|
||||
$.tabcomplete.defaultOptions,
|
||||
options
|
||||
);
|
||||
|
||||
// Remove any leftovers.
|
||||
// This allows us to override the plugin if necessary.
|
||||
this.unbind(".tabcomplete");
|
||||
this.prev(".hint").remove();
|
||||
|
||||
var self = this;
|
||||
var backspace = false;
|
||||
var i = -1;
|
||||
var words = [];
|
||||
var last = "";
|
||||
|
||||
var hint = $.noop;
|
||||
|
||||
// Determine what type of hinting to use.
|
||||
switch (options.hint) {
|
||||
case "placeholder":
|
||||
hint = placeholder;
|
||||
break;
|
||||
|
||||
case "select":
|
||||
hint = select;
|
||||
break;
|
||||
}
|
||||
|
||||
this.on("input.tabcomplete", function() {
|
||||
var input = self.val();
|
||||
var word = input.split(/ |\n/).pop();
|
||||
|
||||
// Reset iteration.
|
||||
i = -1;
|
||||
last = "";
|
||||
words = [];
|
||||
|
||||
// Check for matches if the current word is the last word.
|
||||
if (self[0].selectionStart == input.length
|
||||
&& word.length) {
|
||||
if (typeof args === "function") {
|
||||
// If the user supplies a function, invoke it
|
||||
// and keep the result.
|
||||
words = args(word);
|
||||
} else {
|
||||
// Otherwise, call the .match() function.
|
||||
words = match(word, args, options.caseSensitive);
|
||||
}
|
||||
|
||||
// Append 'after' to each word.
|
||||
if (options.after) {
|
||||
words = $.map(words, function(w) { return w + options.after; });
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the number of matching words with the 'match' event.
|
||||
self.trigger("match", words.length);
|
||||
|
||||
if (options.hint) {
|
||||
if (!(options.hint == "select" && backspace) && word.length >= options.minLength) {
|
||||
// Show hint.
|
||||
hint.call(self, words[0]);
|
||||
} else {
|
||||
// Clear hinting.
|
||||
// This call is needed when using backspace.
|
||||
hint.call(self, "");
|
||||
}
|
||||
}
|
||||
|
||||
if (backspace) {
|
||||
backspace = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.on("keydown.tabcomplete", function(e) {
|
||||
var key = e.which;
|
||||
if (key == keys.tab && !e.ctrlKey
|
||||
|| (options.arrowKeys && (key == keys.up || key == keys.down))) {
|
||||
|
||||
// Don't lose focus on tab click.
|
||||
e.preventDefault();
|
||||
|
||||
// Iterate the matches with tab and the up and down keys by incrementing
|
||||
// or decrementing the 'i' variable.
|
||||
if (key != keys.up) {
|
||||
i++;
|
||||
} else {
|
||||
if (i == -1) return;
|
||||
if (i == 0) {
|
||||
// Jump to the last word.
|
||||
i = words.length - 1;
|
||||
} else {
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Get next match.
|
||||
var word = words[i % words.length];
|
||||
if (!word) {
|
||||
return;
|
||||
}
|
||||
|
||||
var value = self.val();
|
||||
last = last || value.split(/ |\n/).pop();
|
||||
|
||||
// Return if the 'minLength' requirement isn't met.
|
||||
if (last.length < options.minLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update element with the completed text.
|
||||
var text = value.substr(0, self[0].selectionStart - last.length) + word;
|
||||
self.val(text);
|
||||
|
||||
// Put the cursor at the end after completion.
|
||||
// This isn't strictly necessary, but solves an issue with
|
||||
// Internet Explorer.
|
||||
if (options.hint == "select") {
|
||||
self[0].selectionStart = text.length;
|
||||
}
|
||||
|
||||
// Remember the word until next time.
|
||||
last = word;
|
||||
|
||||
// Emit event.
|
||||
self.trigger("tabcomplete", last);
|
||||
|
||||
if (options.hint) {
|
||||
// Turn off any additional hinting.
|
||||
hint.call(self, "");
|
||||
}
|
||||
} else if (e.which == keys.backspace) {
|
||||
// Remember that backspace was pressed. This is used
|
||||
// by the 'input' event.
|
||||
backspace = true;
|
||||
|
||||
// Reset iteration.
|
||||
i = -1;
|
||||
last = "";
|
||||
}
|
||||
});
|
||||
|
||||
if (options.hint) {
|
||||
// If enabled, turn on hinting.
|
||||
hint.call(this, "");
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// Simple matching.
|
||||
// Filter the array and return the items that begins with 'word'.
|
||||
function match(word, array, caseSensitive) {
|
||||
return $.grep(
|
||||
array,
|
||||
function(w) {
|
||||
if (caseSensitive) {
|
||||
return !w.indexOf(word);
|
||||
} else {
|
||||
return !w.toLowerCase().indexOf(word.toLowerCase());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Show placeholder text.
|
||||
// This works by creating a copy of the input and placing it behind
|
||||
// the real input.
|
||||
function placeholder(word) {
|
||||
var input = this;
|
||||
var clone = input.prev(".hint");
|
||||
|
||||
input.css({
|
||||
backgroundColor: "transparent",
|
||||
position: "relative",
|
||||
});
|
||||
|
||||
// Lets create a clone of the input if it does
|
||||
// not already exist.
|
||||
if (!clone.length) {
|
||||
input.wrap(
|
||||
$("<div>").css({position: "relative", height: input.css("height")})
|
||||
);
|
||||
clone = input
|
||||
.clone()
|
||||
.attr("tabindex", -1)
|
||||
.removeAttr("id name placeholder")
|
||||
.addClass("hint")
|
||||
.insertBefore(input);
|
||||
clone.css({
|
||||
position: "absolute",
|
||||
});
|
||||
}
|
||||
|
||||
var hint = "";
|
||||
if (typeof word !== "undefined") {
|
||||
var value = input.val();
|
||||
hint = value + word.substr(value.split(/ |\n/).pop().length);
|
||||
}
|
||||
|
||||
clone.val(hint);
|
||||
}
|
||||
|
||||
// Hint by selecting part of the suggested word.
|
||||
function select(word) {
|
||||
var input = this;
|
||||
var value = input.val();
|
||||
if (word) {
|
||||
input.val(
|
||||
value
|
||||
+ word.substr(value.split(/ |\n/).pop().length)
|
||||
);
|
||||
|
||||
// Select hint.
|
||||
input[0].selectionStart = value.length;
|
||||
}
|
||||
}
|
||||
})(jQuery);
|
@ -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");
|
||||
|
@ -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");
|
||||
@ -156,9 +156,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();
|
||||
}
|
||||
} else if (name === "desktopNotifications") {
|
||||
if ($(this).prop("checked") && Notification.permission !== "granted") {
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user