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