2 * Friendica people autocomplete
4 * require jQuery, jquery.textcomplete
6 function contact_search(term, callback, backend_url, type) {
8 // Check if there is a conversation id to include the unkonwn contacts of the conversation
9 var conv_id = document.activeElement.id.match(/\d+$/);
12 // Check if there is a cached result that contains the same information we would get with a full server-side search
13 var bt = backend_url+type;
14 if(!(bt in contact_search.cache)) contact_search.cache[bt] = {};
16 var lterm = term.toLowerCase(); // Ignore case
17 for(var t in contact_search.cache[bt]) {
18 if(lterm.indexOf(t) >= 0) { // A more broad search has been performed already, so use those results
19 // Filter old results locally
20 var matching = contact_search.cache[bt][t].filter(function (x) { return (x.name.toLowerCase().indexOf(lterm) >= 0 || (typeof x.nick !== 'undefined' && x.nick.toLowerCase().indexOf(lterm) >= 0)); }); // Need to check that nick exists because groups don't have one
21 matching.unshift({taggable:false, text: term, replace: term});
22 setTimeout(function() { callback(matching); } , 1); // Use "pseudo-thread" to avoid some problems
35 postdata['conversation'] = conv_id[0];
43 success: function(data){
44 // Cache results if we got them all (more information would not improve results)
45 // data.count represents the maximum number of items
46 if(data.items.length -1 < data.count) {
47 contact_search.cache[bt][lterm] = data.items;
49 var items = data.items.slice(0);
50 items.unshift({taggable:false, text: term, replace: term});
53 }).fail(function () {callback([]); }); // Callback must be invoked even if something went wrong.
55 contact_search.cache = {};
58 function contact_format(item) {
59 // Show contact information if not explicitly told to show something else
60 if(typeof item.text === 'undefined') {
61 var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick);
62 if(typeof desc === 'undefined') desc = '';
63 if(desc) desc = ' ('+desc+')';
64 return "<div class='{0}' title='{4}'><img class='acpopup-img' src='{1}'><span class='acpopup-contactname'>{2}</span><span class='acpopup-sub-text'>{3}</span><div class='clear'></div></div>".format(item.taggable, item.photo, item.name, desc, item.link);
67 return "<div>" + item.text + "</div>";
70 function editor_replace(item) {
71 if(typeof item.replace !== 'undefined') {
72 return '$1$2' + item.replace;
75 // $2 ensures that prefix (@,@!) is preserved
77 // 16 chars of hash should be enough. Full hash could be used if it can be done in a visually appealing way.
78 // 16 chars is also the minimum length in the backend (otherwise it's interpreted as a local id).
80 id = item.id.substring(0,16);
82 return '$1$2' + item.nick.replace(' ', '') + '+' + id + ' ';
85 function basic_replace(item) {
86 if(typeof item.replace !== 'undefined')
87 return '$1'+item.replace;
89 return '$1'+item.name+' ';
92 function trim_replace(item) {
93 if(typeof item.replace !== 'undefined')
94 return '$1'+item.replace;
96 return '$1'+item.name;
100 function submit_form(e) {
101 $(e).parents('form').submit();
105 * jQuery plugin 'editor_autocomplete'
108 $.fn.editor_autocomplete = function(backend_url) {
110 // Autocomplete contacts
112 match: /(^|\s)(@\!*)([^ \n]+)$/,
114 search: function(term, callback) { contact_search(term, callback, backend_url, 'c'); },
115 replace: editor_replace,
116 template: contact_format,
120 match: /(^|\s)(:[a-z]{2,})$/,
122 search: function(term, callback) { $.getJSON('/smilies/json').done(function(data) { callback($.map(data, function(entry) { return entry.text.indexOf(term) === 0 ? entry : null; })); }); },
123 template: function(item) { return item.icon + item.text; },
124 replace: function(item) { return "$1" + item.text + ' '; },
126 this.attr('autocomplete','off');
127 this.textcomplete([contacts,smilies], {className:'acpopup', zIndex:1020});
132 * jQuery plugin 'search_autocomplete'
135 $.fn.search_autocomplete = function(backend_url) {
136 // Autocomplete contacts
138 match: /(^@)([^\n]{2,})$/,
140 search: function(term, callback) { contact_search(term, callback, backend_url, 'x'); },
141 replace: basic_replace,
142 template: contact_format,
144 this.attr('autocomplete', 'off');
145 var a = this.textcomplete([contacts], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'nav'});
146 a.on('textComplete:select', function(e, value, strategy) { submit_form(this); });
151 $.fn.contact_autocomplete = function(backend_url, typ, autosubmit, onselect) {
152 if(typeof typ === 'undefined') typ = '';
153 if(typeof autosubmit === 'undefined') autosubmit = false;
155 // Autocomplete contacts
157 match: /(^)([^\n]+)$/,
159 search: function(term, callback) { contact_search(term, callback, backend_url, typ); },
160 replace: basic_replace,
161 template: contact_format,
164 this.attr('autocomplete','off');
165 var a = this.textcomplete([contacts], {className:'acpopup', zIndex:1020});
168 a.on('textComplete:select', function(e,value,strategy) { submit_form(this); });
170 if(typeof onselect !== 'undefined')
171 a.on('textComplete:select', function(e, value, strategy) { onselect(value); });
177 $.fn.name_autocomplete = function(backend_url, typ, autosubmit, onselect) {
178 if(typeof typ === 'undefined') typ = '';
179 if(typeof autosubmit === 'undefined') autosubmit = false;
181 // Autocomplete contacts
183 match: /(^)([^\n]+)$/,
185 search: function(term, callback) { contact_search(term, callback, backend_url, typ); },
186 replace: trim_replace,
187 template: contact_format,
190 this.attr('autocomplete','off');
191 var a = this.textcomplete([names], {className:'acpopup', zIndex:1020});
194 a.on('textComplete:select', function(e,value,strategy) { submit_form(this); });
196 if(typeof onselect !== 'undefined')
197 a.on('textComplete:select', function(e, value, strategy) { onselect(value); });
203 * Friendica people autocomplete legacy
204 * code which is needed for tinymce
206 * require jQuery, jquery.textareas
209 function ACPopup(elm,backend_url){
214 this.kp_timer = false;
215 this.url = backend_url;
217 this.conversation_id = null;
218 var conv_id = this.element.id.match(/\d+$/);
219 if (conv_id) this.conversation_id = conv_id[0];
220 console.log("ACPopup elm id",this.element.id,"conversation",this.conversation_id);
226 if(tinyMCE.activeEditor == null) {
227 style = $(elm).offset();
232 // I can't find an "official" way to get the element who get all
233 // this fraking thing that is tinyMCE.
234 // This code will broke again at some point...
235 var container = $(tinyMCE.activeEditor.getContainer()).find("table");
236 style = $(container).offset();
237 w = $(container).width();
238 h = $(container).height();
241 style.top=style.top+h;
243 style.position = 'absolute';
244 /* style['max-height'] = '150px';
245 style.border = '1px solid red';
246 style.background = '#cccccc';
248 style.overflow = 'auto';
249 style['z-index'] = '100000';
251 style.display = 'none';
253 this.cont = $("<div class='acpopup-mce'></div>");
254 this.cont.css(style);
256 $("body").append(this.cont);
259 ACPopup.prototype.close = function(){
260 $(this.cont).remove();
263 ACPopup.prototype.search = function(text){
265 this.searchText=text;
266 if (this.kp_timer) clearTimeout(this.kp_timer);
267 this.kp_timer = setTimeout( function(){that._search();}, 500);
270 ACPopup.prototype._search = function(){
271 console.log("_search");
276 search:this.searchText,
278 conversation: this.conversation_id,
286 success:function(data){
290 $(data.items).each(function(){
291 var html = "<img src='{0}' height='16px' width='16px'>{1} ({2})".format(this.photo, this.name, this.nick);
292 var nick = this.nick.replace(' ','');
293 if (this.id!=='') nick += '+' + this.id;
294 that.add(html, nick + ' - ' + this.link);
304 ACPopup.prototype.add = function(label, value){
306 var elm = $("<div class='acpopupitem' title='"+value+"'>"+label+"</div>");
307 elm.click(function(e){
308 t = $(this).attr('title').replace(new RegExp(' \- .*'),'');
309 if(typeof(that.element.container) === "undefined") {
311 sel = el.getSelection();
312 sel.start = sel.start- that.searchText.length;
313 el.setSelection(sel.start,sel.end).replaceSelectedText(t+' ').collapseSelection(false);
317 txt = tinyMCE.activeEditor.getContent();
318 // alert(that.searchText + ':' + t);
319 newtxt = txt.replace('@' + that.searchText,'@' + t +' ');
320 tinyMCE.activeEditor.setContent(newtxt);
321 tinyMCE.activeEditor.focus();
325 $(this.cont).append(elm);
328 ACPopup.prototype.onkey = function(event){
329 if (event.keyCode == '13') {
331 this.cont.children()[this.idsel].click();
332 event.preventDefault();
337 if (event.keyCode == '38') { //cursor up
338 cmax = this.cont.children().size()-1;
340 if (this.idsel<0) this.idsel=cmax;
341 event.preventDefault();
343 if (event.keyCode == '40' || event.keyCode == '9') { //cursor down
344 cmax = this.cont.children().size()-1;
346 if (this.idsel>cmax) this.idsel=0;
347 event.preventDefault();
350 if (event.keyCode == '38' || event.keyCode == '40' || event.keyCode == '9') {
351 this.cont.children().removeClass('selected');
352 $(this.cont.children()[this.idsel]).addClass('selected');
355 if (event.keyCode == '27') { //ESC