diff --git a/static/js/composebox_typeahead.js b/static/js/composebox_typeahead.js
index 8ebc4c2e47..8579d07f74 100644
--- a/static/js/composebox_typeahead.js
+++ b/static/js/composebox_typeahead.js
@@ -183,6 +183,127 @@ exports.split_at_cursor = function (query) {
return [query.slice(0, cursor), query.slice(cursor)];
};
+exports.compose_content_begins_typeahead = function (query) {
+ var q = exports.split_at_cursor(query)[0];
+
+ var strings = q.split(/[\s*(){}\[\]]/);
+ if (strings.length < 1) {
+ return false;
+ }
+ var current_token = strings[strings.length-1];
+
+ // Only start the emoji autocompleter if : is directly after one
+ // of the whitespace or punctuation chars we split on.
+ if (current_token[0] === ':') {
+ // We don't want to match non-emoji emoticons such
+ // as :P or :-p
+ // Also, if the user has only typed a colon and nothing after,
+ // no need to match yet.
+ if (/^:-?.?$/.test(current_token)) {
+ return false;
+ }
+ this.completing = 'emoji';
+ this.token = current_token.substring(1);
+ return emoji.emojis;
+ }
+
+ // Don't autocomplete more than this many characters.
+ var max_chars = 30;
+ var last_at = q.lastIndexOf('@');
+ if (last_at === -1 || last_at < q.length-1 - max_chars) {
+ return false; // No '@', or too far back
+ }
+
+ // Only match if the @ follows a space, various punctuation,
+ // or is at the beginning of the string.
+ if (last_at > 0 && "\n\t \"'(){}[]".indexOf(q[last_at-1]) === -1) {
+ return false;
+ }
+
+ current_token = q.substring(last_at + 1);
+ if (current_token.length < 1 || current_token.lastIndexOf('*') !== -1) {
+ return false;
+ }
+
+ this.completing = 'mention';
+ this.token = current_token.substring(current_token.indexOf("@")+1);
+ var all_item = {
+ special_item_text: "all (Notify everyone)",
+ email: "all",
+ // Always sort above, under the assumption that names will
+ // be longer and only contain "all" as a substring.
+ pm_recipient_count: Infinity,
+ full_name: "all"
+ };
+ return page_params.people_list.concat([all_item]);
+};
+
+exports.content_highlighter = function (item) {
+ if (this.completing === 'emoji') {
+ return "
" + item.emoji_name;
+ } else if (this.completing === 'mention') {
+ var item_formatted = typeahead_helper.render_person(item);
+ return typeahead_helper.highlight_with_escaping(this.token, item_formatted);
+ }
+};
+
+exports.content_typeahead_selected = function (item) {
+ var pieces = exports.split_at_cursor(this.query);
+ var beginning = pieces[0];
+ var rest = pieces[1];
+
+ if (this.completing === 'emoji') {
+ //leading and trailing spaces are required for emoji, except if it begins a message.
+ if (beginning.lastIndexOf(":") === 0 || beginning.charAt(beginning.lastIndexOf(":") - 1) === " ") {
+ beginning = beginning.replace(/:\S+$/, "") + ":" + item.emoji_name + ": ";
+ } else {
+ beginning = beginning.replace(/:\S+$/, "") + " :" + item.emoji_name + ": ";
+ }
+ } else if (this.completing === 'mention') {
+ beginning = (beginning.substring(0, beginning.length - this.token.length-1)
+ + '@**' + item.full_name + '** ');
+
+ // We insert a special `all` item to the autocompleter above
+ // Don't consider it a user mention
+ if (item.email !== 'all') {
+ $(document).trigger('usermention_completed.zulip', {mentioned: item});
+ }
+ }
+
+ // Keep the cursor after the newly inserted text, as Bootstrap will call textbox.change() to overwrite the text
+ // in the textbox.
+ setTimeout(function () {
+ $('#new_message_content').caret(beginning.length, beginning.length);
+ }, 0);
+ return beginning + rest;
+};
+
+exports.initialize_compose_typeahead = function (selector) {
+ $(selector).typeahead({
+ items: 5,
+ dropup: true,
+ fixed: true,
+ source: exports.compose_content_begins_typeahead,
+ highlighter: exports.content_highlighter,
+ matcher: function (item) {
+ if (this.completing === 'emoji') {
+ return query_matches_emoji(this.token, item);
+ } else if (this.completing === 'mention') {
+ return query_matches_person(this.token, item);
+ }
+ },
+ sorter: function (matches) {
+ if (this.completing === 'emoji') {
+ return typeahead_helper.sort_emojis(matches, this.token);
+ } else if (this.completing === 'mention') {
+ return typeahead_helper.sort_recipients(matches, this.token);
+ }
+ },
+ updater: exports.content_typeahead_selected,
+ stopAdvance: true // Do not advance to the next field on a tab or enter
+ });
+};
+
exports.initialize = function () {
select_on_focus("stream");
select_on_focus("subject");
@@ -286,118 +407,7 @@ exports.initialize = function () {
stopAdvance: true // Do not advance to the next field on a tab or enter
});
- $( "#new_message_content" ).typeahead({
- source: function (query, process) {
- var q = exports.split_at_cursor(query)[0];
-
- var strings = q.split(/[\s*(){}\[\]]/);
- if (strings.length < 1) {
- return false;
- }
- var current_token = strings[strings.length-1];
-
- // Only start the emoji autocompleter if : is directly after one
- // of the whitespace or punctuation chars we split on.
- if (current_token[0] === ':') {
- // We don't want to match non-emoji emoticons such
- // as :P or :-p
- // Also, if the user has only typed a colon and nothing after,
- // no need to match yet.
- if (/^:-?.?$/.test(current_token)) {
- return false;
- }
- this.completing = 'emoji';
- this.token = current_token.substring(1);
- return emoji.emojis;
- }
-
- // Don't autocomplete more than this many characters.
- var max_chars = 30;
- var last_at = q.lastIndexOf('@');
- if (last_at === -1 || last_at < q.length-1 - max_chars) {
- return false; // No '@', or too far back
- }
-
- // Only match if the @ follows a space, various punctuation,
- // or is at the beginning of the string.
- if (last_at > 0 && "\n\t \"'(){}[]".indexOf(q[last_at-1]) === -1) {
- return false;
- }
-
- current_token = q.substring(last_at + 1);
- if (current_token.length < 1 || current_token.lastIndexOf('*') !== -1) {
- return false;
- }
-
- this.completing = 'mention';
- this.token = current_token.substring(current_token.indexOf("@")+1);
- var all_item = {
- special_item_text: "all (Notify everyone)",
- email: "all",
- // Always sort above, under the assumption that names will
- // be longer and only contain "all" as a substring.
- pm_recipient_count: Infinity,
- full_name: "all"
- };
- return page_params.people_list.concat([all_item]);
- },
- items: 5,
- highlighter: function (item) {
- if (this.completing === 'emoji') {
- return "
" + item.emoji_name;
- } else if (this.completing === 'mention') {
- var item_formatted = typeahead_helper.render_person(item);
- return typeahead_helper.highlight_with_escaping(this.token, item_formatted);
- }
- },
- dropup: true,
- fixed: true,
- matcher: function (item) {
- if (this.completing === 'emoji') {
- return query_matches_emoji(this.token, item);
- } else if (this.completing === 'mention') {
- return query_matches_person(this.token, item);
- }
- },
- sorter: function (matches) {
- if (this.completing === 'emoji') {
- return typeahead_helper.sort_emojis(matches, this.token);
- } else if (this.completing === 'mention') {
- return typeahead_helper.sort_recipients(matches, this.token);
- }
- },
- updater: function (item) {
- var pieces = exports.split_at_cursor(this.query);
- var beginning = pieces[0];
- var rest = pieces[1];
-
- if (this.completing === 'emoji') {
- //leading and trailing spaces are required for emoji, except if it begins a message.
- if (beginning.lastIndexOf(":") === 0 || beginning.charAt(beginning.lastIndexOf(":") - 1) === " ") {
- beginning = beginning.replace(/:\S+$/, "") + ":" + item.emoji_name + ": ";
- } else {
- beginning = beginning.replace(/:\S+$/, "") + " :" + item.emoji_name + ": ";
- }
- } else if (this.completing === 'mention') {
- beginning = (beginning.substring(0, beginning.length - this.token.length-1)
- + '@**' + item.full_name + '** ');
-
- // We insert a special `all` item to the autocompleter above
- // Don't consider it a user mention
- if (item.email !== 'all') {
- $(document).trigger('usermention_completed.zulip', {mentioned: item});
- }
- }
-
- // Keep the cursor after the newly inserted text, as Bootstrap will call textbox.change() to overwrite the text
- // in the textbox.
- setTimeout(function () {
- $('#new_message_content').caret(beginning.length, beginning.length);
- }, 0);
- return beginning + rest;
- },
- stopAdvance: true // Do not advance to the next field on a tab or enter
- });
+ exports.initialize_compose_typeahead("#new_message_content");
$( "#private_message_recipient" ).blur(function (event) {
var val = $(this).val();