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