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