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