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