xref: /dokuwiki/lib/scripts/linkwiz.js (revision c4bb7947fcb2d4a5e5f8a15d9e3bbec333e44e13)
1/**
2 * The Link Wizard
3 *
4 * @author Andreas Gohr <gohr@cosmocode.de>
5 * @author Pierre Spring <pierre.spring@caillou.ch>
6 */
7(function($){
8    $.fn.extend({
9        linkwiz: function(editor) {
10
11            var wiz;
12            var entry;
13            var result
14            var timer;
15            var textArea;
16            var selected;
17            var selection;
18
19            /**
20             * Initialize the linkwizard by creating the needed HTML
21             * and attaching the eventhandlers
22             */
23            var init = function(textAreaElement){
24
25                // create HTML Structure
26                wiz = document.createElement('div');
27                wiz.id = 'link__wiz';
28                wiz.className     = 'picker';
29                wiz.style.top  = (findPosY(textAreaElement)+20)+'px';
30                wiz.style.left = (findPosX(textAreaElement)+80)+'px';
31                wiz.style.marginLeft = '-10000px';
32                wiz.style.marginTop  = '-10000px';
33
34                wiz.innerHTML =
35                     '<div id="link__wiz_header">'+
36                     '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+
37                     LANG['linkwiz']+'</div>'+
38                     '<div>'+LANG['linkto']+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
39                     '<div id="link__wiz_result"></div>';
40                $('#dw__editform')[0].parentNode.appendChild(wiz);
41                textArea = textAreaElement;
42                result = $('#link__wiz_result')[0];
43                entry = $('#link__wiz_entry')[0];
44
45                // attach event handlers
46                var obj;
47                var obj = $('#link__wiz_close')[0];
48                obj.onclick = hide;
49
50                addEvent(entry,'keyup',onEntry);
51                addEvent(result,'click',onResultClick);
52
53                $(wiz).draggable({handle: '#link__wiz_header'});
54
55            };
56
57            /**
58             * handle all keyup events in the entry field
59             */
60            var onEntry = function(e){
61                if(e.keyCode == 37 || e.keyCode == 39){ //left/right
62                    return true; //ignore
63                }
64                if(e.keyCode == 27){
65                    hide();
66                    e.preventDefault();
67                    e.stopPropagation();
68                    return false;
69                }
70                if(e.keyCode == 38){ //Up
71                    select(selected -1);
72                    e.preventDefault();
73                    e.stopPropagation();
74                    return false;
75                }
76                if(e.keyCode == 40){ //Down
77                    select(selected +1);
78                    e.preventDefault();
79                    e.stopPropagation();
80                    return false;
81                }
82                if(e.keyCode == 13){ //Enter
83                    if(selected > -1){
84                        var obj = getResult(selected);
85                        if(obj){
86                            var a = $(obj).find('a')[0];
87                            resultClick(a);
88                        }
89                    }else if(entry.value){
90                        insertLink(entry.value);
91                    }
92
93                    e.preventDefault();
94                    e.stopPropagation();
95                    return false;
96                }
97                autocomplete();
98            };
99
100            /**
101             * Get one of the result by index
102             *
103             * @param int result div to return
104             * @returns DOMObject or null
105             */
106            var getResult = function(num){
107                var obj;
108                var childs = $(result).find('div');
109                var obj = childs[num];
110                if(obj){
111                    return obj;
112                }else{
113                    return null;
114                }
115            };
116
117            /**
118             * Select the given result
119             */
120            var select = function(num){
121                if(num < 0){
122                    deselect();
123                    return;
124                }
125
126                var obj = getResult(num);
127                if(obj){
128                    deselect();
129                    obj.className += ' selected';
130
131                    // make sure the item is viewable in the scroll view
132                    // FIXME check IE compatibility
133                    if(obj.offsetTop > result.scrollTop + result.clientHeight){
134                        result.scrollTop += obj.clientHeight;
135                    }else if(obj.offsetTop - result.clientHeight < result.scrollTop){ // this works but isn't quite right, fixes welcome
136                        result.scrollTop -= obj.clientHeight;
137                    }
138                    // now recheck - if still not in view, the user used the mouse to scroll
139                    if( (obj.offsetTop > result.scrollTop + result.clientHeight) ||
140                        (obj.offsetTop < result.scrollTop) ){
141                        obj.scrollIntoView();
142                    }
143
144                    selected = num;
145                }
146            };
147
148            /**
149             * deselect a result if any is selected
150             */
151            var deselect = function(){
152                if(selected > -1){
153                    var obj = getResult(selected);
154                    if(obj){
155                        obj.className = obj.className.replace(/ ?selected/,'');
156                    }
157                }
158                selected = -1;
159            };
160
161            /**
162             * Handle clicks in the result set an dispatch them to
163             * resultClick()
164             */
165            var onResultClick = function(e){
166                if(e.target.tagName != 'A') return;
167                e.stopPropagation();
168                e.preventDefault();
169                resultClick(e.target);
170                return false;
171            };
172
173            /**
174             * Handles the "click" on a given result anchor
175             */
176            var resultClick = function(a){
177                var id = a.title;
178                if(id == '' || id.substr(id.length-1) == ':'){
179                    entry.value = id;
180                    autocomplete_exec();
181                }else{
182                    entry.value = id;
183                    if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){
184                        insertLink(a.nextSibling.innerHTML);
185                    }else{
186                        insertLink('');
187                    }
188                }
189            };
190
191            /**
192             * Insert the id currently in the entry box to the textarea,
193             * replacing the current selection or at the curso postion.
194             * When no selection is available the given title will be used
195             * as link title instead
196             */
197            var insertLink = function(title){
198                if(!entry.value) return;
199
200                var sel = getSelection(textArea);
201                if(sel.start == 0 && sel.end == 0) sel = selection;
202
203                var stxt = sel.getText();
204
205                // don't include trailing space in selection
206                if(stxt.charAt(stxt.length - 1) == ' '){
207                    sel.end--;
208                    var stxt = sel.getText();
209                }
210
211                if(!stxt && !DOKU_UHC) stxt=title;
212
213                // prepend colon inside namespaces for non namespace pages
214                if(textArea.form['id'].value.indexOf(':') != -1 &&
215                   entry.value.indexOf(':') == -1){
216                    entry.value = ':'+entry.value;
217                }
218
219                var link = '[['+entry.value+'|';
220                if(stxt) link += stxt;
221                link += ']]';
222
223                var so = entry.value.length+3;
224                var eo = 2;
225
226                pasteText(sel,link,{startofs: so, endofs: eo});
227                hide();
228                // reset the entry to the parent namespace and remove : at the beginning
229                entry.value = entry.value.replace(/(^:)?[^:]*$/, '');
230            };
231
232            /**
233             * Start the page/namespace lookup timer
234             *
235             * Calls autocomplete_exec when the timer runs out
236             */
237            var autocomplete = function(){
238                if(timer !== null){
239                    window.clearTimeout(timer);
240                    timer = null;
241                }
242
243                timer = window.setTimeout(autocomplete_exec,350);
244            };
245
246            /**
247             * Executes the AJAX call for the page/namespace lookup
248             */
249            var autocomplete_exec = function(){
250                deselect();
251                result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />';
252
253                // because we need to use POST, we
254                // can not use the .load() function.
255                $.post(
256                    DOKU_BASE + 'lib/exe/ajax.php',
257                    {
258                        call: 'linkwiz',
259                        q: entry.value
260                    },
261                    function (data) {
262                        result.innerHTML = data;
263                    },
264                    'html'
265                );
266            };
267
268            /**
269             * Clears the result area
270             */
271            var clear = function(){
272                result.innerHTML = 'Search for a matching page name above, or browse through the pages on the right';
273                entry.value = '';
274            };
275
276            /**
277             * Show the linkwizard
278             */
279            var show = function(){
280                selection  = getSelection(textArea);
281                wiz.style.marginLeft = '0px';
282                wiz.style.marginTop = '0px';
283                entry.focus();
284                autocomplete();
285            };
286
287            /**
288             * Hide the link wizard
289             */
290            var hide = function(){
291                wiz.style.marginLeft = '-10000px';
292                wiz.style.marginTop  = '-10000px';
293                textArea.focus();
294            };
295
296            /**
297             * Toggle the link wizard
298             */
299            var toggle = function(){
300                if(wiz.style.marginLeft == '-10000px'){
301                    show();
302                }else{
303                    hide();
304                }
305            };
306
307            init($(editor)[0]);
308
309            return this.each(function() {
310                $(this).click(function (e) {
311                    e.preventDefault();
312                    toggle();
313                });
314            });
315        }
316    });
317})(jQuery);