]> git.mxchange.org Git - friendica.git/blob - js/autocomplete.js
rework autocomplete: update query-textcomplete
[friendica.git] / js / autocomplete.js
1 /**
2  * Friendica people autocomplete
3  *
4  * require jQuery, jquery.textcomplete
5  */
6 function contact_search(term, callback, backend_url, type) {
7
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+$/);
10
11
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] = {};
15
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
23                         return;
24                 }
25         }
26
27         var postdata = {
28                 start:0,
29                 count:100,
30                 search:term,
31                 type:type,
32         };
33
34         if(conv_id !== null)
35                 postdata['conversation'] = conv_id[0];
36
37
38         $.ajax({
39                 type:'POST',
40                 url: backend_url,
41                 data: postdata,
42                 dataType: 'json',
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;
48                         }
49                         var items = data.items.slice(0);
50                         items.unshift({taggable:false, text: term, replace: term});
51                         callback(items);
52                 },
53         }).fail(function () {callback([]); }); // Callback must be invoked even if something went wrong.
54 }
55 contact_search.cache = {};
56
57
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);
65         }
66         else
67                 return "<div>" + item.text + "</div>";
68 }
69
70 function editor_replace(item) {
71         if(typeof item.replace !== 'undefined') {
72                 return '$1$2' + item.replace;
73         }
74
75         // $2 ensures that prefix (@,@!) is preserved
76         var id = item.id;
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).
79         if(id.length > 16) 
80                 id = item.id.substring(0,16);
81
82         return '$1$2' + item.nick.replace(' ', '') + '+' + id + ' ';
83 }
84
85 function basic_replace(item) {
86         if(typeof item.replace !== 'undefined')
87                 return '$1'+item.replace;
88
89         return '$1'+item.name+' ';
90 }
91
92 function trim_replace(item) {
93         if(typeof item.replace !== 'undefined')
94                 return '$1'+item.replace;
95
96         return '$1'+item.name;
97 }
98
99
100 function submit_form(e) {
101         $(e).parents('form').submit();
102 }
103
104 /**
105  * jQuery plugin 'editor_autocomplete'
106  */
107 (function( $ ) {
108         $.fn.editor_autocomplete = function(backend_url) {
109
110                 // Autocomplete contacts
111                 contacts = {
112                         match: /(^|\s)(@\!*)([^ \n]+)$/,
113                         index: 3,
114                         search: function(term, callback) { contact_search(term, callback, backend_url, 'c'); },
115                         replace: editor_replace,
116                         template: contact_format,
117                 };
118
119                 smilies = {
120                         match: /(^|\s)(:[a-z]{2,})$/,
121                         index: 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 + ' '; },
125                 };
126                 this.attr('autocomplete','off');
127                 this.textcomplete([contacts,smilies], {className:'acpopup', zIndex:1020});
128         };
129 })( jQuery );
130
131 /**
132  * jQuery plugin 'search_autocomplete'
133  */
134 (function( $ ) {
135         $.fn.search_autocomplete = function(backend_url) {
136                 // Autocomplete contacts
137                 contacts = {
138                         match: /(^@)([^\n]{2,})$/,
139                         index: 2,
140                         search: function(term, callback) { contact_search(term, callback, backend_url, 'x'); },
141                         replace: basic_replace,
142                         template: contact_format,
143                 };
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); });
147         };
148 })( jQuery );
149
150 (function( $ ) {
151         $.fn.contact_autocomplete = function(backend_url, typ, autosubmit, onselect) {
152                 if(typeof typ === 'undefined') typ = '';
153                 if(typeof autosubmit === 'undefined') autosubmit = false;
154
155                 // Autocomplete contacts
156                 contacts = {
157                         match: /(^)([^\n]+)$/,
158                         index: 2,
159                         search: function(term, callback) { contact_search(term, callback, backend_url, typ); },
160                         replace: basic_replace,
161                         template: contact_format,
162                 };
163
164                 this.attr('autocomplete','off');
165                 var a = this.textcomplete([contacts], {className:'acpopup', zIndex:1020});
166
167                 if(autosubmit)
168                         a.on('textComplete:select', function(e,value,strategy) { submit_form(this); });
169
170                 if(typeof onselect !== 'undefined')
171                         a.on('textComplete:select', function(e, value, strategy) { onselect(value); });
172         };
173 })( jQuery );
174
175
176 (function( $ ) {
177         $.fn.name_autocomplete = function(backend_url, typ, autosubmit, onselect) {
178                 if(typeof typ === 'undefined') typ = '';
179                 if(typeof autosubmit === 'undefined') autosubmit = false;
180
181                 // Autocomplete contacts
182                 names = {
183                         match: /(^)([^\n]+)$/,
184                         index: 2,
185                         search: function(term, callback) { contact_search(term, callback, backend_url, typ); },
186                         replace: trim_replace,
187                         template: contact_format,
188                 };
189
190                 this.attr('autocomplete','off');
191                 var a = this.textcomplete([names], {className:'acpopup', zIndex:1020});
192
193                 if(autosubmit)
194                         a.on('textComplete:select', function(e,value,strategy) { submit_form(this); });
195
196                 if(typeof onselect !== 'undefined')
197                         a.on('textComplete:select', function(e, value, strategy) { onselect(value); });
198         };
199 })( jQuery );
200
201
202 /**
203  * Friendica people autocomplete legacy
204  * code which is needed for tinymce
205  *
206  * require jQuery, jquery.textareas
207  */
208
209 function ACPopup(elm,backend_url){
210         this.idsel=-1;
211         this.element = elm;
212         this.searchText="";
213         this.ready=true;
214         this.kp_timer = false;
215         this.url = backend_url;
216
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);
221
222         var w = 530;
223         var h = 130;
224
225
226         if(tinyMCE.activeEditor == null) {
227                 style = $(elm).offset();
228                 w = $(elm).width();
229                 h = $(elm).height();
230         }
231         else {
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();
239         }
240
241         style.top=style.top+h;
242         style.width = w;
243         style.position = 'absolute';
244         /*      style['max-height'] = '150px';
245                 style.border = '1px solid red';
246                 style.background = '#cccccc';
247
248                 style.overflow = 'auto';
249                 style['z-index'] = '100000';
250         */
251         style.display = 'none';
252
253         this.cont = $("<div class='acpopup-mce'></div>");
254         this.cont.css(style);
255
256         $("body").append(this.cont);
257     }
258
259 ACPopup.prototype.close = function(){
260         $(this.cont).remove();
261         this.ready=false;
262 }
263 ACPopup.prototype.search = function(text){
264         var that = this;
265         this.searchText=text;
266         if (this.kp_timer) clearTimeout(this.kp_timer);
267         this.kp_timer = setTimeout( function(){that._search();}, 500);
268 }
269
270 ACPopup.prototype._search = function(){
271         console.log("_search");
272         var that = this;
273         var postdata = {
274                 start:0,
275                 count:100,
276                 search:this.searchText,
277                 type:'c',
278                 conversation: this.conversation_id,
279         }
280
281         $.ajax({
282                 type:'POST',
283                 url: this.url,
284                 data: postdata,
285                 dataType: 'json',
286                 success:function(data){
287                         that.cont.html("");
288                         if (data.tot>0){
289                                 that.cont.show();
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);
295                                 });
296                         } else {
297                                 that.cont.hide();
298                         }
299                 }
300         });
301
302 }
303
304 ACPopup.prototype.add = function(label, value){
305         var that=this;
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") {
310                         el=$(that.element);
311                         sel = el.getSelection();
312                         sel.start = sel.start- that.searchText.length;
313                         el.setSelection(sel.start,sel.end).replaceSelectedText(t+' ').collapseSelection(false);
314                         that.close();
315                 }
316                 else {
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();
322                         that.close();
323                 }
324         });
325         $(this.cont).append(elm);
326 }
327
328 ACPopup.prototype.onkey = function(event){
329         if (event.keyCode == '13') {
330                 if(this.idsel>-1) {
331                         this.cont.children()[this.idsel].click();
332                         event.preventDefault();
333                 }
334                 else
335                         this.close();
336         }
337         if (event.keyCode == '38') { //cursor up
338                 cmax = this.cont.children().size()-1;
339                 this.idsel--;
340                 if (this.idsel<0) this.idsel=cmax;
341                 event.preventDefault();
342         }
343         if (event.keyCode == '40' || event.keyCode == '9') { //cursor down
344                 cmax = this.cont.children().size()-1;
345                 this.idsel++;
346                 if (this.idsel>cmax) this.idsel=0;
347                 event.preventDefault();
348         }
349
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');
353         }
354
355         if (event.keyCode == '27') { //ESC
356                 this.close();
357         }
358 }
359