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