xref: /dokuwiki/lib/scripts/linkwiz.js (revision 449095130497f47d1f9ec3f67d70d2eb1c99446e)
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    selection: null,
15
16    /**
17     * Initialize the linkwizard by creating the needed HTML
18     * and attaching the eventhandlers
19     */
20    init: function(textArea){
21        // prepare AJAX object
22        linkwiz.sack = new sack(DOKU_BASE + 'lib/exe/ajax.php');
23        linkwiz.sack.AjaxFailedAlert = '';
24        linkwiz.sack.encodeURIString = false;
25
26        // create HTML Structure
27        linkwiz.wiz = document.createElement('div');
28        linkwiz.wiz.id = 'link__wiz';
29        linkwiz.wiz.className     = 'picker';
30        linkwiz.wiz.style.top  = (findPosY(textArea)+20)+'px';
31        linkwiz.wiz.style.left = (findPosX(textArea)+80)+'px';
32        linkwiz.wiz.style.marginLeft = '-10000px';
33        linkwiz.wiz.style.marginTop  = '-10000px';
34        linkwiz.wiz.style.position = 'absolute';
35
36        linkwiz.wiz.innerHTML =
37             '<div id="link__wiz_header">'+
38             '<img src="'+DOKU_BASE+'lib/images/close.png" width="16" height="16" align="right" alt="" id="link__wiz_close" />'+
39             LANG['linkwiz']+'</div>'+
40             '<div>'+LANG['linkto']+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
41             '<div id="link__wiz_result"></div>';
42        $('dw__editform').parentNode.appendChild(linkwiz.wiz);
43        linkwiz.textArea = textArea;
44        linkwiz.result = $('link__wiz_result');
45        linkwiz.entry = $('link__wiz_entry');
46
47        // attach event handlers
48        var obj;
49        obj = $('link__wiz_close');
50        obj.onclick = linkwiz.hide;
51
52        linkwiz.sack.elementObj = linkwiz.result;
53        addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);
54        addEvent(linkwiz.result,'click',linkwiz.onResultClick);
55        drag.attach(linkwiz.wiz,$('link__wiz_header'));
56    },
57
58    /**
59     * handle all keyup events in the entry field
60     */
61    onEntry: function(e){
62        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
63            return true; //ignore
64        }
65        if(e.keyCode == 27){
66            linkwiz.hide();
67            e.preventDefault();
68            e.stopPropagation();
69            return false;
70        }
71        if(e.keyCode == 38){ //Up
72            linkwiz.select(linkwiz.selected -1);
73            e.preventDefault();
74            e.stopPropagation();
75            return false;
76        }
77        if(e.keyCode == 40){ //Down
78            linkwiz.select(linkwiz.selected +1);
79            e.preventDefault();
80            e.stopPropagation();
81            return false;
82        }
83        if(e.keyCode == 13){ //Enter
84            if(linkwiz.selected > -1){
85                var obj = linkwiz.getResult(linkwiz.selected);
86                if(obj){
87                    var a = obj.getElementsByTagName('A')[0];
88                    linkwiz.resultClick(a);
89                }
90            }else if(linkwiz.entry.value){
91                linkwiz.insertLink(linkwiz.entry.value);
92            }
93
94            e.preventDefault();
95            e.stopPropagation();
96            return false;
97        }
98        linkwiz.autocomplete();
99    },
100
101    /**
102     * Get one of the result by index
103     *
104     * @param int result div to return
105     * @returns DOMObject or null
106     */
107    getResult: function(num){
108        var obj;
109        var childs = linkwiz.result.getElementsByTagName('DIV');
110        obj = childs[num];
111        if(obj){
112            return obj;
113        }else{
114            return null;
115        }
116    },
117
118    /**
119     * Select the given result
120     */
121    select: function(num){
122        if(num < 0){
123            linkwiz.deselect();
124            return;
125        }
126
127        var obj = linkwiz.getResult(num);
128        if(obj){
129            linkwiz.deselect();
130            obj.className += ' selected';
131
132            // make sure the item is viewable in the scroll view
133            // FIXME check IE compatibility
134            if(obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight){
135                linkwiz.result.scrollTop += obj.clientHeight;
136            }else if(obj.offsetTop - linkwiz.result.clientHeight < linkwiz.result.scrollTop){ // this works but isn't quite right, fixes welcome
137                linkwiz.result.scrollTop -= obj.clientHeight;
138            }
139            // now recheck - if still not in view, the user used the mouse to scroll
140            if( (obj.offsetTop > linkwiz.result.scrollTop + linkwiz.result.clientHeight) ||
141                (obj.offsetTop < linkwiz.result.scrollTop) ){
142                obj.scrollIntoView();
143            }
144
145            linkwiz.selected = num;
146        }
147    },
148
149    /**
150     * deselect a result if any is selected
151     */
152    deselect: function(){
153        if(linkwiz.selected > -1){
154            var obj = linkwiz.getResult(linkwiz.selected);
155            if(obj){
156                obj.className = obj.className.replace(/ ?selected/,'');
157            }
158        }
159        linkwiz.selected = -1;
160    },
161
162    /**
163     * Handle clicks in the result set an dispatch them to
164     * resultClick()
165     */
166    onResultClick: function(e){
167        if(e.target.tagName != 'A') return;
168        e.stopPropagation();
169        e.preventDefault();
170        linkwiz.resultClick(e.target);
171        return false;
172    },
173
174    /**
175     * Handles the "click" on a given result anchor
176     */
177    resultClick: function(a){
178        var id = a.title;
179        if(id == '' || id.substr(id.length-1) == ':'){
180            linkwiz.entry.value = id;
181            linkwiz.autocomplete_exec();
182        }else{
183            linkwiz.entry.value = id;
184            if(a.nextSibling && a.nextSibling.tagName == 'SPAN'){
185                linkwiz.insertLink(a.nextSibling.innerHTML);
186            }else{
187                linkwiz.insertLink('');
188            }
189        }
190    },
191
192    /**
193     * Insert the id currently in the entry box to the textarea,
194     * replacing the current selection or at the curso postion.
195     * When no selection is available the given title will be used
196     * as link title instead
197     */
198    insertLink: function(title){
199        if(!linkwiz.entry.value) return;
200
201        var sel = getSelection(linkwiz.textArea);
202        if(sel.start == 0 && sel.end == 0) sel = linkwiz.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(linkwiz.textArea.form['id'].value.indexOf(':') != -1 &&
216           linkwiz.entry.value.indexOf(':') == -1){
217            linkwiz.entry.value = ':'+linkwiz.entry.value;
218        }
219
220        var link = '[['+linkwiz.entry.value+'|';
221        if(stxt) link += stxt;
222        link += ']]';
223
224        var so = linkwiz.entry.value.length+3;
225        var eo = 2;
226
227        pasteText(sel,link,{startofs: so, endofs: eo});
228        linkwiz.hide();
229        // reset the entry to the parent namespace and remove : at the beginning
230        linkwiz.entry.value = linkwiz.entry.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(linkwiz.timer !== null){
240            window.clearTimeout(linkwiz.timer);
241            linkwiz.timer = null;
242        }
243
244        linkwiz.timer = window.setTimeout(linkwiz.autocomplete_exec,350);
245    },
246
247    /**
248     * Executes the AJAX call for the page/namespace lookup
249     */
250    autocomplete_exec: function(){
251        linkwiz.deselect();
252        linkwiz.result.innerHTML = '<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />';
253        linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));
254    },
255
256    /**
257     * Clears the result area
258     */
259    clear: function(){
260        linkwiz.result.innerHTML = 'Search for a matching page name above, or browse through the pages on the right';
261        linkwiz.entry.value = '';
262    },
263
264    /**
265     * Show the linkwizard
266     */
267    show: function(){
268        linkwiz.selection  = getSelection(linkwiz.textArea);
269        linkwiz.wiz.style.marginLeft = '0px';
270        linkwiz.wiz.style.marginTop = '0px';
271        linkwiz.entry.focus();
272        linkwiz.autocomplete();
273    },
274
275    /**
276     * Hide the link wizard
277     */
278    hide: function(){
279        linkwiz.wiz.style.marginLeft = '-10000px';
280        linkwiz.wiz.style.marginTop  = '-10000px';
281        linkwiz.textArea.focus();
282    },
283
284    /**
285     * Toggle the link wizard
286     */
287    toggle: function(){
288        if(linkwiz.wiz.style.marginLeft == '-10000px'){
289            linkwiz.show();
290        }else{
291            linkwiz.hide();
292        }
293    }
294};
295
296