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