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