Merge pull request #4138 from dokuwiki/issue4099
[dokuwiki.git] / lib / scripts / linkwiz.js
blobd82ca96814183c7a31d9f9426d76469d3312f8dc
1 /**
2  * The Link Wizard
3  *
4  * @author Andreas Gohr <gohr@cosmocode.de>
5  * @author Pierre Spring <pierre.spring@caillou.ch>
6  */
7 var dw_linkwiz = {
8     $wiz: null,
9     $entry: null,
10     result: null,
11     timer: null,
12     textArea: null,
13     selected: null,
14     selection: null,
16     /**
17      * Initialize the dw_linkwizard by creating the needed HTML
18      * and attaching the eventhandlers
19      */
20     init: function($editor){
21         // position relative to the text area
22         var pos = $editor.position();
24         // create HTML Structure
25         if(dw_linkwiz.$wiz)
26             return;
27         dw_linkwiz.$wiz = jQuery(document.createElement('div'))
28                .dialog({
29                    autoOpen: false,
30                    draggable: true,
31                    title: LANG.linkwiz,
32                    resizable: false
33                })
34                .html(
35                     '<div>'+LANG.linkto+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
36                     '<div id="link__wiz_result"></div>'
37                     )
38                .parent()
39                .attr('id','link__wiz')
40                .css({
41                     'position':    'absolute',
42                     'top':         (pos.top+20)+'px',
43                     'left':        (pos.left+80)+'px'
44                    })
45                .hide()
46                .appendTo('.dokuwiki:first');
48         dw_linkwiz.textArea = $editor[0];
49         dw_linkwiz.result = jQuery('#link__wiz_result')[0];
51         // scrollview correction on arrow up/down gets easier
52         jQuery(dw_linkwiz.result).css('position', 'relative');
54         dw_linkwiz.$entry = jQuery('#link__wiz_entry');
55         if(JSINFO.namespace){
56             dw_linkwiz.$entry.val(JSINFO.namespace+':');
57         }
59         // attach event handlers
60         jQuery('#link__wiz .ui-dialog-titlebar-close').on('click', dw_linkwiz.hide);
61         dw_linkwiz.$entry.keyup(dw_linkwiz.onEntry);
62         jQuery(dw_linkwiz.result).on('click', 'a', dw_linkwiz.onResultClick);
63     },
65     /**
66      * handle all keyup events in the entry field
67      */
68     onEntry: function(e){
69         if(e.keyCode == 37 || e.keyCode == 39){ //left/right
70             return true; //ignore
71         }
72         if(e.keyCode == 27){ //Escape
73             dw_linkwiz.hide();
74             e.preventDefault();
75             e.stopPropagation();
76             return false;
77         }
78         if(e.keyCode == 38){ //Up
79             dw_linkwiz.select(dw_linkwiz.selected -1);
80             e.preventDefault();
81             e.stopPropagation();
82             return false;
83         }
84         if(e.keyCode == 40){ //Down
85             dw_linkwiz.select(dw_linkwiz.selected +1);
86             e.preventDefault();
87             e.stopPropagation();
88             return false;
89         }
90         if(e.keyCode == 13){ //Enter
91             if(dw_linkwiz.selected > -1){
92                 var $obj = dw_linkwiz.$getResult(dw_linkwiz.selected);
93                 if($obj.length > 0){
94                     dw_linkwiz.resultClick($obj.find('a')[0]);
95                 }
96             }else if(dw_linkwiz.$entry.val()){
97                 dw_linkwiz.insertLink(dw_linkwiz.$entry.val());
98             }
100             e.preventDefault();
101             e.stopPropagation();
102             return false;
103         }
104         dw_linkwiz.autocomplete();
105     },
107     /**
108      * Get one of the results by index
109      *
110      * @param   num int result div to return
111      * @returns DOMObject or null
112      */
113     getResult: function(num){
114         DEPRECATED('use dw_linkwiz.$getResult()[0] instead');
115         return dw_linkwiz.$getResult()[0] || null;
116     },
118     /**
119      * Get one of the results by index
120      *
121      * @param   num int result div to return
122      * @returns jQuery object
123      */
124     $getResult: function(num) {
125         return jQuery(dw_linkwiz.result).find('div').eq(num);
126     },
128     /**
129      * Select the given result
130      */
131     select: function(num){
132         if(num < 0){
133             dw_linkwiz.deselect();
134             return;
135         }
137         var $obj = dw_linkwiz.$getResult(num);
138         if ($obj.length === 0) {
139             return;
140         }
142         dw_linkwiz.deselect();
143         $obj.addClass('selected');
145         // make sure the item is viewable in the scroll view
147         //getting child position within the parent
148         var childPos = $obj.position().top;
149         //getting difference between the childs top and parents viewable area
150         var yDiff = childPos + $obj.outerHeight() - jQuery(dw_linkwiz.result).innerHeight();
152         if (childPos < 0) {
153             //if childPos is above viewable area (that's why it goes negative)
154             jQuery(dw_linkwiz.result)[0].scrollTop += childPos;
155         } else if(yDiff > 0) {
156             // if difference between childs top and parents viewable area is
157             // greater than the height of a childDiv
158             jQuery(dw_linkwiz.result)[0].scrollTop += yDiff;
159         }
161         dw_linkwiz.selected = num;
162     },
164     /**
165      * deselect a result if any is selected
166      */
167     deselect: function(){
168         if(dw_linkwiz.selected > -1){
169             dw_linkwiz.$getResult(dw_linkwiz.selected).removeClass('selected');
170         }
171         dw_linkwiz.selected = -1;
172     },
174     /**
175      * Handle clicks in the result set an dispatch them to
176      * resultClick()
177      */
178     onResultClick: function(e){
179         if(!jQuery(this).is('a')) {
180             return;
181         }
182         e.stopPropagation();
183         e.preventDefault();
184         dw_linkwiz.resultClick(this);
185         return false;
186     },
188     /**
189      * Handles the "click" on a given result anchor
190      */
191     resultClick: function(a){
192         dw_linkwiz.$entry.val(a.title);
193         if(a.title == '' || a.title.substr(a.title.length-1) == ':'){
194             dw_linkwiz.autocomplete_exec();
195         }else{
196             if (jQuery(a.nextSibling).is('span')) {
197                 dw_linkwiz.insertLink(a.nextSibling.innerHTML);
198             }else{
199                 dw_linkwiz.insertLink('');
200             }
201         }
202     },
204     /**
205      * Insert the id currently in the entry box to the textarea,
206      * replacing the current selection or at the cursor position.
207      * When no selection is available the given title will be used
208      * as link title instead
209      */
210     insertLink: function(title){
211         var link = dw_linkwiz.$entry.val(),
212             sel, stxt;
213         if(!link) {
214             return;
215         }
217         sel = DWgetSelection(dw_linkwiz.textArea);
218         if(sel.start == 0 && sel.end == 0) {
219             sel = dw_linkwiz.selection;
220         }
222         stxt = sel.getText();
224         // don't include trailing space in selection
225         if(stxt.charAt(stxt.length - 1) == ' '){
226             sel.end--;
227             stxt = sel.getText();
228         }
230         if(!stxt && !DOKU_UHC) {
231             stxt=title;
232         }
234         // prepend colon inside namespaces for non namespace pages
235         if(dw_linkwiz.textArea.form.id.value.indexOf(':') != -1 &&
236            link.indexOf(':') == -1){
237            link = ':' + link;
238         }
240         var so = link.length;
241         var eo = 0;
242         if(dw_linkwiz.val){
243             if(dw_linkwiz.val.open) {
244                 so += dw_linkwiz.val.open.length;
245                 link = dw_linkwiz.val.open+link;
246             }
247             link += '|';
248             so += 1;
249             if(stxt) {
250                 link += stxt;
251             }
252             if(dw_linkwiz.val.close) {
253                 link += dw_linkwiz.val.close;
254                 eo = dw_linkwiz.val.close.length;
255             }
256         }
258         pasteText(sel,link,{startofs: so, endofs: eo});
259         dw_linkwiz.hide();
261         // reset the entry to the parent namespace
262         var externallinkpattern = new RegExp('^((f|ht)tps?:)?//', 'i'),
263             entry_value;
264         if (externallinkpattern.test(dw_linkwiz.$entry.val())) {
265             if (JSINFO.namespace) {
266                 entry_value = JSINFO.namespace + ':';
267             } else {
268                 entry_value = ''; //reset whole external links
269             }
270         } else {
271             entry_value = dw_linkwiz.$entry.val().replace(/[^:]*$/, '')
272         }
273         dw_linkwiz.$entry.val(entry_value);
274     },
276     /**
277      * Start the page/namespace lookup timer
278      *
279      * Calls autocomplete_exec when the timer runs out
280      */
281     autocomplete: function(){
282         if(dw_linkwiz.timer !== null){
283             window.clearTimeout(dw_linkwiz.timer);
284             dw_linkwiz.timer = null;
285         }
287         dw_linkwiz.timer = window.setTimeout(dw_linkwiz.autocomplete_exec,350);
288     },
290     /**
291      * Executes the AJAX call for the page/namespace lookup
292      */
293     autocomplete_exec: function(){
294         var $res = jQuery(dw_linkwiz.result);
295         dw_linkwiz.deselect();
296         $res.html('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />')
297             .load(
298             DOKU_BASE + 'lib/exe/ajax.php',
299             {
300                 call: 'linkwiz',
301                 q: dw_linkwiz.$entry.val()
302             }
303         );
304     },
306     /**
307      * Show the link wizard
308      */
309     show: function(){
310         dw_linkwiz.selection  = DWgetSelection(dw_linkwiz.textArea);
311         dw_linkwiz.$wiz.show();
312         dw_linkwiz.$entry.focus();
313         dw_linkwiz.autocomplete();
315         // Move the cursor to the end of the input
316         var temp = dw_linkwiz.$entry.val();
317         dw_linkwiz.$entry.val('');
318         dw_linkwiz.$entry.val(temp);
319     },
321     /**
322      * Hide the link wizard
323      */
324     hide: function(){
325         dw_linkwiz.$wiz.hide();
326         dw_linkwiz.textArea.focus();
327     },
329     /**
330      * Toggle the link wizard
331      */
332     toggle: function(){
333         if(dw_linkwiz.$wiz.css('display') == 'none'){
334             dw_linkwiz.show();
335         }else{
336             dw_linkwiz.hide();
337         }
338     }