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