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