Merge pull request #787 from yashsriv/feat/emoji-autocomplete

Add autocomplete for emoji, users, chans, and commands
This commit is contained in:
Pavel Djundik 2017-04-26 11:39:09 +03:00 committed by GitHub
commit 6c8d1616fd
7 changed files with 1609 additions and 20 deletions

View File

@ -1338,8 +1338,7 @@ kbd {
#help .help-item .subject { #help .help-item .subject {
white-space: nowrap; white-space: nowrap;
padding-right: 10px; padding-right: 15px;
min-width: 150px;
} }
#help .help-item .description p { #help .help-item .description p {
@ -1486,7 +1485,8 @@ kbd {
background: transparent; background: transparent;
} }
#context-menu { #context-menu,
.textcomplete-menu {
position: absolute; position: absolute;
list-style: none; list-style: none;
margin: 0; margin: 0;
@ -1505,7 +1505,8 @@ kbd {
background-color: rgba(0, 0, 0, .1); background-color: rgba(0, 0, 0, .1);
} }
.context-menu-item { .context-menu-item,
.textcomplete-item {
cursor: pointer; cursor: pointer;
display: block; display: block;
padding: 4px 8px; padding: 4px 8px;
@ -1514,15 +1515,33 @@ kbd {
margin-bottom: 6px; margin-bottom: 6px;
} }
.context-menu-item:hover { .context-menu-item:hover,
.textcomplete-item:hover,
.textcomplete-menu .active {
background-color: #f6f6f6; background-color: #f6f6f6;
} }
.context-menu-item:before { .context-menu-item:before,
.textcomplete-item:before {
width: 20px; width: 20px;
display: inline-block; display: inline-block;
} }
.textcomplete-item a {
color: #333;
}
.textcomplete-item a:hover {
opacity: 1;
}
.textcomplete-item .emoji {
margin-right: 8px;
width: 16px;
text-align: center;
display: inline-block;
}
/** /**
* Tooltips v0.5.3 * Tooltips v0.5.3
* See http://primercss.io/tooltips/ * See http://primercss.io/tooltips/

View File

@ -532,6 +532,49 @@
</div> </div>
</div> </div>
<h2>Autocompletion</h2>
<p>
Start typing the following characters followed by any letter to
trigger the autocompletion dropdown:
</p>
<div class="help-item">
<div class="subject">
<code>@</code>
</div>
<div class="description">
<p>Nickname</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>#</code>
</div>
<div class="description">
<p>Channel</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>/</code>
</div>
<div class="description">
<p>Commands (see list of commands below)</p>
</div>
</div>
<div class="help-item">
<div class="subject">
<code>:</code>
</div>
<div class="description">
<p>Emoji</p>
</div>
</div>
<h2>Commands</h2> <h2>Commands</h2>
<p>All commands can be autocompleted with <kbd>tab</kbd>.</p> <p>All commands can be autocompleted with <kbd>tab</kbd>.</p>

View File

@ -34,7 +34,7 @@ import jQuery from "jquery";
var key = e.which; var key = e.which;
switch (key) { switch (key) {
case 13: // Enter case 13: // Enter
if (e.shiftKey) { if (e.shiftKey || self.data("autocompleting")) {
return; // multiline input return; // multiline input
} }
@ -56,7 +56,7 @@ import jQuery from "jquery";
case 38: // Up case 38: // Up
case 40: // Down case 40: // Down
// NOTICE: This is specific to The Lounge. // NOTICE: This is specific to The Lounge.
if (e.ctrlKey || e.metaKey) { if (e.ctrlKey || e.metaKey || self.data("autocompleting")) {
break; break;
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,12 +2,14 @@
// vendor libraries // vendor libraries
require("jquery-ui/ui/widgets/sortable"); require("jquery-ui/ui/widgets/sortable");
require("jquery-textcomplete");
const $ = require("jquery"); const $ = require("jquery");
const moment = require("moment"); const moment = require("moment");
const Mousetrap = require("mousetrap"); const Mousetrap = require("mousetrap");
const URI = require("urijs"); const URI = require("urijs");
// our libraries // our libraries
const emojiMap = require("./libs/simplemap.json");
require("./libs/jquery/inputhistory"); require("./libs/jquery/inputhistory");
require("./libs/jquery/stickyscroll"); require("./libs/jquery/stickyscroll");
require("./libs/jquery/tabcomplete"); require("./libs/jquery/tabcomplete");
@ -41,6 +43,73 @@ $(function() {
var favicon = $("#favicon"); var favicon = $("#favicon");
// Autocompletion Strategies
const emojiStrategy = {
id: "emoji",
match: /\B:([-+\w]*)$/,
search(term, callback) {
callback(Object.keys(emojiMap).filter(name => name.indexOf(term) === 0));
},
template(value) {
return `<span class="emoji">${emojiMap[value]}</span> ${value}`;
},
replace(value) {
return emojiMap[value];
},
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)).map(val => "@" + val));
} else {
callback(completeNicks(term));
}
},
template(value) {
return value;
},
replace(value) {
return value;
},
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(value) {
return value;
},
replace(value) {
return value;
},
index: 1
};
const commandStrategy = {
id: "commands",
match: /^\/(\w*)$/,
search(term, callback) {
callback(completeCommands("/" + term));
},
template(value) {
return value;
},
replace(value) {
return value;
},
index: 1
};
socket.on("auth", function(data) { socket.on("auth", function(data) {
var login = $("#sign-in"); var login = $("#sign-in");
var token; var token;
@ -638,7 +707,18 @@ $(function() {
chat.find(".chan.active .chat").trigger("msg.sticky"); // fix growing 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; var focus = $.noop;
if (!("ontouchstart" in window || navigator.maxTouchPoints > 0)) { if (!("ontouchstart" in window || navigator.maxTouchPoints > 0)) {
@ -1272,18 +1352,31 @@ $(function() {
.find(".messages .msg, .date-marker").remove(); .find(".messages .msg, .date-marker").remove();
} }
function complete(word) { function completeNicks(word) {
var words = constants.commands.slice(); const users = chat.find(".active").find(".users");
var users = chat.find(".active").find(".users"); const words = users.data("nicks");
var nicks = users.data("nicks");
for (var i in nicks) { return $.grep(
words.push(nicks[i]); words,
w => !w.toLowerCase().indexOf(word.toLowerCase())
);
} }
function completeCommands(word) {
const words = constants.commands.slice();
return $.grep(
words,
w => !w.toLowerCase().indexOf(word.toLowerCase())
);
}
function completeChans(word) {
const words = [];
sidebar.find(".chan") sidebar.find(".chan")
.each(function() { .each(function() {
var self = $(this); const self = $(this);
if (!self.hasClass("lobby")) { if (!self.hasClass("lobby")) {
words.push(self.data("title")); words.push(self.data("title"));
} }
@ -1291,9 +1384,7 @@ $(function() {
return $.grep( return $.grep(
words, words,
function(w) { w => !w.toLowerCase().indexOf(word.toLowerCase())
return !w.toLowerCase().indexOf(word.toLowerCase());
}
); );
} }

View File

@ -68,6 +68,7 @@
"handlebars": "4.0.6", "handlebars": "4.0.6",
"handlebars-loader": "1.5.0", "handlebars-loader": "1.5.0",
"jquery": "3.2.1", "jquery": "3.2.1",
"jquery-textcomplete": "1.8.0",
"jquery-ui": "1.12.1", "jquery-ui": "1.12.1",
"mocha": "3.3.0", "mocha": "3.3.0",
"mousetrap": "1.6.1", "mousetrap": "1.6.1",

View File

@ -13,6 +13,7 @@ let config = {
"js/bundle.vendor.js": [ "js/bundle.vendor.js": [
"handlebars/runtime", "handlebars/runtime",
"jquery", "jquery",
"jquery-textcomplete",
"jquery-ui/ui/widgets/sortable", "jquery-ui/ui/widgets/sortable",
"moment", "moment",
"mousetrap", "mousetrap",