Skip to content

Instantly share code, notes, and snippets.

@joshrobb
Last active December 25, 2015 20:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joshrobb/7036399 to your computer and use it in GitHub Desktop.
Save joshrobb/7036399 to your computer and use it in GitHub Desktop.
Autocomplete user script for Hipchat - drop this file into an open Chrome extensions window and it will enable autocomplete for hipchat emoticons.
// ==UserScript==
// @name HipchatEmotiAutocomplete
// @description Autocomplete for emoticons in hipchat
// @include https://*.hipchat.com/chat*
// @version 1.0
// ==/UserScript==
var code = function() {
window.emoti_autocomplete = {
input: null,
position_node: null,
autocomplete: null,
search_str: '',
startingchars: {},
choose_selected: function() {
var tag = this.autocomplete.find('div.selected:first').attr('shortcut');
if (tag) {
var selection = this.input.getSelection();
var replacement = (tag.indexOf(' ') == -1 ? tag : (tag + ' '));
var replaced_message = this.input.val().substr(0, selection.end).replace(/\(\w*$/, replacement);
this.input.val(replaced_message + this.input.val().substr(selection.end));
this.input.setSelection(replaced_message.length, replaced_message.length);
}
this.autocomplete.hide();
},
display_autocomplete: function() {
// Initial showing of autocomplete, populate it first
this.populate();
this.autocomplete.show().
scrollTop(0).
width(this.position_node.outerWidth() - 2); // subtract 2 for borders
},
// Filter the autocomplete list based on the current search string
// Also sort the users based on match strength
filter: function(str) {
str = str.toLowerCase();
this.search_str = str;
$("div", this.autocomplete).sort(this.sort).appendTo(this.autocomplete);
this.autocomplete.find('div.member').each(function(idx, elem) {
var elem = $(elem);
elem.removeClass('selected');
// Everything matches at 0
if (str.length == 0) {
elem.show();
return;
}
var matches = false;
var highlighted_string = null;
var shortcut = elem.attr("shortcut")
matches = shortcut.indexOf(str) > -1 || shortcut.indexOf(str.substr(1)) > -1;
if (matches) {
elem.show();
} else {
elem.hide();
}
});
this.autocomplete.find('div.member:visible:first').addClass('selected');
},
handle_input_keydown: function(event) {
if (!this.autocomplete.is(':visible')) {
mention_autocomplete.handle_input_keydown(event);
return;
}
if (event.keyCode == 13 || event.keyCode == 9) { // enter and tab
if (this.autocomplete.is(':visible')) {
event.preventDefault();
event.stopImmediatePropagation();
}
} else if (event.keyCode == 38) { // up arrow
if (this.autocomplete.is(':visible')) {
event.preventDefault();
event.stopImmediatePropagation();
this.select_previous();
}
} else if (event.keyCode == 40) { // down arrow
if (this.autocomplete.is(':visible')) {
event.preventDefault();
event.stopImmediatePropagation();
this.select_next();
}
}
},
handle_input_keyup: function(event) {
var is_cursor_move = false;
if (event.keyCode == 37 || event.keyCode == 38 || // up,down,left,right arrows
event.keyCode == 39 || event.keyCode == 40) {
is_cursor_move = true;
}
var cur_text = this.input.val();
var pos = this.input.getSelection().start;
//var sregex = "[\\"+Object.keys(this.startingchars).join("\\")+"](\w*)$";
//var test = new RegExp("(\\(\w*)$");
var test = /([\(]\w*)$/;
var matches = cur_text.substr(0, pos).match(test);
var colonemoti = pos > 0 && (cur_text[pos - 1] == ":" || cur_text[pos - 2] == ":")
if (!matches || matches.length == 0 || colonemoti) {
this.autocomplete.hide();
mention_autocomplete.handle_input_keyup(event);
return;
}
// Don't show autocomplete on cursor movement, only hide it
if (is_cursor_move) {
return;
}
// Handle enter/tab on key up so we don't end up completing before we've
// filtered and selected the proper user (e.g. if you type @g<tab> really
// fast)
if (event.keyCode == 13 || event.keyCode == 9) { // enter and tab
if (this.autocomplete.is(':visible')) {
this.choose_selected();
return;
}
} else if (event.keyCode == 27) { // esc
// Hide autocomplete on esc
if (this.autocomplete.is(':visible')) {
this.autocomplete.hide();
return;
}
}
if (!this.autocomplete.is(':visible')) {
this.display_autocomplete();
}
this.filter(matches[1]);
// Check to see that we actually have results
if (this.autocomplete.find('div:visible').length == 0) {
this.autocomplete.hide();
return;
}
var self = this;
setTimeout(function() {
var coords = self.position_node.offset();
self.autocomplete.offset({
top: (coords.top - self.autocomplete.outerHeight()),
left: coords.left
});
}, 0);
},
handle_mousedown: function(event) {
var target = $(event.target);
var member = (target.hasClass('member') ? target : target.closest('.member'));
if (member.length > 0) {
this.autocomplete.find('div.member').removeClass('selected');
member.addClass('selected');
this.choose_selected();
}
},
init: function(input, position_node) {
this.input = $(input);
this.position_node = $(position_node);
this.startingchars = {}
for (i in config.emoticons) {
e = config.emoticons[i];
this.startingchars[e.shortcut[0]] = 1
};
var autocomplete_node = $(document.createElement('div'));
autocomplete_node.attr('id', 'emojiautocomplete');
autocomplete_node.hide();
var css = ["body.chat #emojiautocomplete { background-color: #fefefe; border: 1px solid #ccc; position: absolute; max-height: 130px; overflow: auto;}",
"body.chat #emojiautocomplete div.member { padding: 5px; cursor: pointer; color: #333; -moz-user-select: none; -webkit-user-select: none; white-space: nowrap; }",
"body.chat #emojiautocomplete div.member:hover { background-color: #f6f6f6; } ",
"body.chat #emojiautocomplete div.selected, body.chat #emojiautocomplete div.selected:hover { background-color: #669acc; color: #fff; }"
]
var sheet = document.styleSheets[0];
for (var i in css) {
sheet.insertRule(css[i], sheet.rules.length)
}
$('body').append(autocomplete_node);
this.autocomplete = autocomplete_node;
this.autocomplete.mousedown($.proxy(this, 'handle_mousedown'));
this.input.off("keyup");
this.input.off("keydown");
this.input.keyup($.proxy(this, 'handle_input_keyup'))
.keydown($.proxy(this, 'handle_input_keydown'));
//setup the app listeners again
mention_autocomplete.init(input, position_node);
this.input.keydown($.proxy(chat, 'handle_message_keydown'));
this.input.keyup($.proxy(chat, 'handle_message_keyup'));
//next tab
this.input.bind('keydown', 'Ctrl+]', $.proxy(chat, 'show_next_tab'));
this.input.bind('keydown', 'Ctrl+Shift+]', $.proxy(chat, 'show_next_tab'));
// previous tab
this.input.bind('keydown', 'Ctrl+[', $.proxy(chat, 'show_prev_tab'));
this.input.bind('keydown', 'Ctrl+Shift+[', $.proxy(chat, 'show_prev_tab'));
// invite
this.input.bind('keydown', 'Ctrl+i', $.proxy(chat, 'show_room_invite_popup'));
//chat.setup_listeners();
},
// Generate the full autcomplete list
populate: function() {
this.autocomplete.html('');
// Go through each room member and create a label and tag for them
// tag = string to display when autocompleting for that user
// nick = non-default chosen mention name (null if not different from default mention name)
var html_strings = [];
for (var i = 0; i < config.emoticons.length; i++) {
var emoti = config.emoticons[i];
var class_name = (i == 0 ? ' selected' : '');
var label = emoti.shortcut;
var imgpath = emoticons.path_prefix + '/' + emoti.file;
var img = ' <img height="' + emoti.height + '" width="' + emoti.width + '" src="' + imgpath + '" />';
html_strings.push('<div class="member' + class_name + '" shortcut="' + label + '">' + img + label + '</div>');
}
this.autocomplete.append(html_strings.join(''));
},
select_next: function() {
var selected = this.autocomplete.find('.selected');
var next_item = selected.nextAll(':visible:first');
if (next_item.length > 0) {
selected.removeClass('selected');
next_item.addClass('selected');
if (next_item.position().top + next_item.outerHeight() > this.autocomplete.height()) {
this.autocomplete.scrollTop(this.autocomplete.scrollTop() + next_item.outerHeight());
}
}
},
select_previous: function() {
var selected = this.autocomplete.find('.selected');
var prev_item = selected.prevAll(':visible:first');
if (prev_item.length > 0) {
selected.removeClass('selected');
prev_item.addClass('selected');
if (prev_item.position().top < 0) {
this.autocomplete.scrollTop(this.autocomplete.scrollTop() - prev_item.outerHeight());
}
}
},
};
message_input = $('#message_input');
window.emoti_autocomplete.init(message_input, message_input.parent());
};
var execed = false;
function exec(fn) {
if (execed) return;
execed = true;
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = '(' + fn + ')();';
document.body.appendChild(script); // run the script
document.body.removeChild(script); // clean up
}
window.addEventListener("load", function() {
// script injection
exec(code);
}, false);
setTimeout(exec(code), 1000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment