xref: /dokuwiki/lib/scripts/linkwiz.js (revision d4caa43b1b7b6d8cead392e391bdfadd08b24c11)
1/**
2 * The Link Wizard
3 *
4 * @author Andreas Gohr <gohr@cosmocode.de>
5 */
6var linkwiz = {
7    wiz:    null,
8    entry:  null,
9    result: null,
10    timer:  null,
11    sack:   null,
12    textArea: null,
13    selected: -1,
14
15    /**
16     * Initialize the linkwizard by creating the needed HTML
17     * and attaching the eventhandlers
18     */
19    init: function(textArea){
20        // prepare AJAX object
21        linkwiz.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
22        linkwiz.sack.AjaxFailedAlert = '';
23        linkwiz.sack.encodeURIString = false;
24
25        // create HTML Structure
26        linkwiz.wiz = document.createElement('div');
27        linkwiz.wiz.id = 'link__wiz';
28        linkwiz.wiz.className     = 'picker';
29        linkwiz.wiz.style.top  = (findPosY(textArea)+20)+'px';
30        linkwiz.wiz.style.left = (findPosX(textArea)+80)+'px';
31        linkwiz.wiz.style.marginLeft = '-10000px';
32
33        linkwiz.wiz.innerHTML =
34             '<div id="link__wiz_header">'+
35             '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+
36             LANG['linkwiz']+'</div>'+
37             '<div>'+LANG['linkto']+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
38             '<div id="link__wiz_result"></div>';
39        textArea.form.parentNode.appendChild(linkwiz.wiz);
40        linkwiz.textArea = textArea;
41        linkwiz.result = $('link__wiz_result');
42        linkwiz.entry = $('link__wiz_entry');
43
44        // attach event handlers
45        var obj;
46        obj = $('link__wiz_close');
47        obj.onclick = linkwiz.hide;
48
49        linkwiz.sack.elementObj = linkwiz.result;
50        addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);
51        addEvent(linkwiz.result,'click',linkwiz.onResultClick);
52        drag.attach(linkwiz.wiz,$('link__wiz_header'));
53    },
54
55    /**
56     * handle all keyup events in the entry field
57     */
58    onEntry: function(e){
59        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
60            return true; //ignore
61        }
62        if(e.keyCode == 27){
63            linkwiz.hide();
64            e.preventDefault();
65            e.stopPropagation();
66            return false;
67        }
68        if(e.keyCode == 38){ //Up
69            linkwiz.select(linkwiz.selected -1);
70            e.preventDefault();
71            e.stopPropagation();
72            return false;
73        }
74        if(e.keyCode == 40){ //Down
75            linkwiz.select(linkwiz.selected +1);
76            e.preventDefault();
77            e.stopPropagation();
78            return false;
79        }
80        if(e.keyCode == 13){ //Enter
81            if(linkwiz.selected > -1){
82                var obj = linkwiz.getResult(linkwiz.selected);
83                if(obj){
84                    var a = obj.getElementsByTagName('A')[0];
85                    linkwiz.resultClick(a);
86                }
87            }else if(linkwiz.entry.value){
88                linkwiz.insertLink(linkwiz.entry.value);
89            }
90
91            e.preventDefault();
92            e.stopPropagation();
93            return false;
94        }
95        linkwiz.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    getResult: function(num){
105        var obj;
106        var childs = linkwiz.result.getElementsByTagName('DIV');
107        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            linkwiz.deselect();
121            return;
122        }
123
124        var obj = linkwiz.getResult(num);
125        if(obj){
126            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 > linkwiz.result.scrollTop + linkwiz.result.clientHeight){
132                linkwiz.result.scrollTop += obj.clientHeight;
133            }else if(obj.offsetTop - linkwiz.result.clientHeight < linkwiz.result.scrollTop){ // this works but isn't quite right, fixes welcome
134                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 > linkwiz.result.scrollTop + linkwiz.result.clientHeight) ||
138                (obj.offsetTop < linkwiz.result.scrollTop) ){
139                obj.scrollIntoView();
140            }
141
142            linkwiz.selected = num;
143        }
144    },
145
146    /**
147     * deselect a result if any is selected
148     */
149    deselect: function(){
150        if(linkwiz.selected > -1){
151            var obj = linkwiz.getResult(linkwiz.selected);
152            if(obj){
153                obj.className = obj.className.replace(/ ?selected/,'');
154            }
155        }
156        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        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 id = a.title;
176        if(id == '' || id.substr(id.length-1) == ':'){
177            linkwiz.entry.value = id;
178            linkwiz.autocomplete_exec();
179        }else{
180            linkwiz.entry.value = id;
181            if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){
182                linkwiz.insertLink(a.nextSibling.innerHTML);
183            }else{
184                linkwiz.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    insertLink: function(title){
196        if(!linkwiz.entry.value) return;
197        var sel  = getSelection(linkwiz.textArea);
198        var stxt = sel.getText();
199        if(!stxt && !DOKU_UHC) stxt=title;
200
201        // prepend colon inside namespaces for non namespace pages
202        if(linkwiz.textArea.form['id'].value.indexOf(':') != -1 &&
203           linkwiz.entry.value.indexOf(':') == -1){
204            linkwiz.entry.value = ':'+linkwiz.entry.value;
205        }
206
207        var link = '[['+linkwiz.entry.value+'|';
208        if(stxt) link += stxt;
209        link += ']]';
210
211        var so = linkwiz.entry.value.length+3;
212        var eo = 2;
213
214        pasteText(sel,link,{startofs: so, endofs: eo});
215        linkwiz.hide();
216    },
217
218    /**
219     * Start the page/namespace lookup timer
220     *
221     * Calls autocomplete_exec when the timer runs out
222     */
223    autocomplete: function(){
224        if(linkwiz.timer !== null){
225            window.clearTimeout(linkwiz.timer);
226            linkwiz.timer = null;
227        }
228
229        linkwiz.timer = window.setTimeout(linkwiz.autocomplete_exec,350);
230    },
231
232    /**
233     * Executes the AJAX call for the page/namespace lookup
234     */
235    autocomplete_exec: function(){
236        linkwiz.deselect();
237        linkwiz.result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />';
238        linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));
239    },
240
241    /**
242     * Clears the result area
243     */
244    clear: function(){
245        linkwiz.result.innerHTML = 'Search for a matching page name above, or browse through the pages on the right';
246        linkwiz.entry.value = '';
247    },
248
249    /**
250     * Show the linkwizard
251     */
252    show: function(){
253        linkwiz.wiz.style.marginLeft = '0px';
254        linkwiz.entry.focus();
255        linkwiz.autocomplete();
256    },
257
258    /**
259     * Hide the link wizard
260     */
261    hide: function(){
262        linkwiz.wiz.style.marginLeft = '-10000px';
263        linkwiz.textArea.focus();
264    },
265
266    /**
267     * Toggle the link wizard
268     */
269    toggle: function(){
270        if(linkwiz.wiz.style.marginLeft == '-10000px'){
271            linkwiz.show();
272        }else{
273            linkwiz.hide();
274        }
275    }
276};
277
278