xref: /dokuwiki/lib/scripts/linkwiz.js (revision f2b500f5e7be771601ea1b1bc110337883d7caf2)
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               .dialog({
27                   autoOpen: false,
28                   draggable: true,
29                   title: LANG.linkwiz,
30                   resizable: false
31               })
32               .html(
33                    '<div>'+LANG.linkto+' <input type="text" class="edit" id="link__wiz_entry" autocomplete="off" /></div>'+
34                    '<div id="link__wiz_result"></div>'
35                    )
36               .parent()
37               .attr('id','link__wiz')
38               .css({
39                    'position':    'absolute',
40                    'top':         (pos.top+20)+'px',
41                    'left':        (pos.left+80)+'px'
42                   })
43               .hide()
44               .appendTo('.dokuwiki:first');
45
46        dw_linkwiz.textArea = $editor[0];
47        dw_linkwiz.result = jQuery('#link__wiz_result')[0];
48
49        // scrollview correction on arrow up/down gets easier
50        jQuery(dw_linkwiz.result).css('position', 'relative');
51
52        dw_linkwiz.$entry = jQuery('#link__wiz_entry');
53        if(JSINFO.namespace){
54            dw_linkwiz.$entry.val(JSINFO.namespace+':');
55        }
56
57        // attach event handlers
58        jQuery('#link__wiz .ui-dialog-titlebar-close').click(dw_linkwiz.hide);
59        dw_linkwiz.$entry.keyup(dw_linkwiz.onEntry);
60        jQuery(dw_linkwiz.result).delegate('a', 'click', dw_linkwiz.onResultClick);
61    },
62
63    /**
64     * handle all keyup events in the entry field
65     */
66    onEntry: function(e){
67        if(e.keyCode == 37 || e.keyCode == 39){ //left/right
68            return true; //ignore
69        }
70        if(e.keyCode == 27){ //Escape
71            dw_linkwiz.hide();
72            e.preventDefault();
73            e.stopPropagation();
74            return false;
75        }
76        if(e.keyCode == 38){ //Up
77            dw_linkwiz.select(dw_linkwiz.selected -1);
78            e.preventDefault();
79            e.stopPropagation();
80            return false;
81        }
82        if(e.keyCode == 40){ //Down
83            dw_linkwiz.select(dw_linkwiz.selected +1);
84            e.preventDefault();
85            e.stopPropagation();
86            return false;
87        }
88        if(e.keyCode == 13){ //Enter
89            if(dw_linkwiz.selected > -1){
90                var $obj = dw_linkwiz.$getResult(dw_linkwiz.selected);
91                if($obj.length > 0){
92                    dw_linkwiz.resultClick($obj.find('a')[0]);
93                }
94            }else if(dw_linkwiz.$entry.val()){
95                dw_linkwiz.insertLink(dw_linkwiz.$entry.val());
96            }
97
98            e.preventDefault();
99            e.stopPropagation();
100            return false;
101        }
102        dw_linkwiz.autocomplete();
103    },
104
105    /**
106     * Get one of the results by index
107     *
108     * @param   num int result div to return
109     * @returns DOMObject or null
110     */
111    getResult: function(num){
112        DEPRECATED('use dw_linkwiz.$getResult()[0] instead');
113        return dw_linkwiz.$getResult()[0] || null;
114    },
115
116    /**
117     * Get one of the results by index
118     *
119     * @param   num int result div to return
120     * @returns jQuery object
121     */
122    $getResult: function(num) {
123        return jQuery(dw_linkwiz.result).find('div').eq(num);
124    },
125
126    /**
127     * Select the given result
128     */
129    select: function(num){
130        if(num < 0){
131            dw_linkwiz.deselect();
132            return;
133        }
134
135        var $obj = dw_linkwiz.$getResult(num);
136        if ($obj.length === 0) {
137            return;
138        }
139
140        dw_linkwiz.deselect();
141        $obj.addClass('selected');
142
143        // make sure the item is viewable in the scroll view
144
145        //getting child position within the parent
146        var childPos = $obj.position().top;
147        //getting difference between the childs top and parents viewable area
148        var yDiff = childPos + $obj.outerHeight() - jQuery(dw_linkwiz.result).innerHeight();
149
150        if (childPos < 0) {
151            //if childPos is above viewable area (that's why it goes negative)
152            jQuery(dw_linkwiz.result)[0].scrollTop += childPos;
153        } else if(yDiff > 0) {
154            // if difference between childs top and parents viewable area is
155            // greater than the height of a childDiv
156            jQuery(dw_linkwiz.result)[0].scrollTop += yDiff;
157        }
158
159        dw_linkwiz.selected = num;
160    },
161
162    /**
163     * deselect a result if any is selected
164     */
165    deselect: function(){
166        if(dw_linkwiz.selected > -1){
167            dw_linkwiz.$getResult(dw_linkwiz.selected).removeClass('selected');
168        }
169        dw_linkwiz.selected = -1;
170    },
171
172    /**
173     * Handle clicks in the result set an dispatch them to
174     * resultClick()
175     */
176    onResultClick: function(e){
177        if(!jQuery(this).is('a')) {
178            return;
179        }
180        e.stopPropagation();
181        e.preventDefault();
182        dw_linkwiz.resultClick(this);
183        return false;
184    },
185
186    /**
187     * Handles the "click" on a given result anchor
188     */
189    resultClick: function(a){
190        dw_linkwiz.$entry.val(a.title);
191        if(a.title == '' || a.title.substr(a.title.length-1) == ':'){
192            dw_linkwiz.autocomplete_exec();
193        }else{
194            if (jQuery(a.nextSibling).is('span')) {
195                dw_linkwiz.insertLink(a.nextSibling.innerHTML);
196            }else{
197                dw_linkwiz.insertLink('');
198            }
199        }
200    },
201
202    /**
203     * Insert the id currently in the entry box to the textarea,
204     * replacing the current selection or at the cursor position.
205     * When no selection is available the given title will be used
206     * as link title instead
207     */
208    insertLink: function(title){
209        var link = dw_linkwiz.$entry.val(),
210            sel, stxt;
211        if(!link) {
212            return;
213        }
214
215        sel = getSelection(dw_linkwiz.textArea);
216        if(sel.start == 0 && sel.end == 0) {
217            sel = dw_linkwiz.selection;
218        }
219
220        stxt = sel.getText();
221
222        // don't include trailing space in selection
223        if(stxt.charAt(stxt.length - 1) == ' '){
224            sel.end--;
225            stxt = sel.getText();
226        }
227
228        if(!stxt && !DOKU_UHC) {
229            stxt=title;
230        }
231
232        // prepend colon inside namespaces for non namespace pages
233        if(dw_linkwiz.textArea.form.id.value.indexOf(':') != -1 &&
234           link.indexOf(':') == -1){
235           link = ':' + link;
236        }
237
238        var so = link.length+3;
239
240        link = '[['+link+'|';
241        if(stxt) {
242            link += stxt;
243        }
244        link += ']]';
245
246        pasteText(sel,link,{startofs: so, endofs: 2});
247        dw_linkwiz.hide();
248
249        // reset the entry to the parent namespace
250        dw_linkwiz.$entry.val(dw_linkwiz.$entry.val().replace(/[^:]*$/, ''));
251    },
252
253    /**
254     * Start the page/namespace lookup timer
255     *
256     * Calls autocomplete_exec when the timer runs out
257     */
258    autocomplete: function(){
259        if(dw_linkwiz.timer !== null){
260            window.clearTimeout(dw_linkwiz.timer);
261            dw_linkwiz.timer = null;
262        }
263
264        dw_linkwiz.timer = window.setTimeout(dw_linkwiz.autocomplete_exec,350);
265    },
266
267    /**
268     * Executes the AJAX call for the page/namespace lookup
269     */
270    autocomplete_exec: function(){
271        var $res = jQuery(dw_linkwiz.result);
272        dw_linkwiz.deselect();
273        $res.html('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="" width="16" height="16" />')
274            .load(
275            DOKU_BASE + 'lib/exe/ajax.php',
276            {
277                call: 'linkwiz',
278                q: dw_linkwiz.$entry.val()
279            }
280        );
281    },
282
283    /**
284     * Show the link wizard
285     */
286    show: function(){
287        dw_linkwiz.selection  = getSelection(dw_linkwiz.textArea);
288        dw_linkwiz.$wiz.show();
289        dw_linkwiz.$entry.focus();
290        dw_linkwiz.autocomplete();
291    },
292
293    /**
294     * Hide the link wizard
295     */
296    hide: function(){
297        dw_linkwiz.$wiz.hide();
298        dw_linkwiz.textArea.focus();
299    },
300
301    /**
302     * Toggle the link wizard
303     */
304    toggle: function(){
305        if(dw_linkwiz.$wiz.css('display') == 'none'){
306            dw_linkwiz.show();
307        }else{
308            dw_linkwiz.hide();
309        }
310    }
311};
312