1/**
2 * Right Context Menu configuration
3 *
4 * Some usefull variables:
5 *   node.hns = headpage id;
6 *   node.isdir = node is namespace;
7 *   node.dokuid = the DW id (namespace parent in case of headpage);
8 *   id = the DW id of the selected node (headpage id in case of headpage);
9 *   index.config.urlbase = Url Base;
10 *   index.config.sepchar = Url separator;
11 *
12 * HOWTO EDIT:
13 *
14 * To override menu entries or add a menu entry:
15 *  - PLEASE EDIT ONLY the scripts/contextmenu.local.js file
16 *  - DON'T EDIT this file, it is overwritten at plugin update
17 *
18 * Base structure of the context menu is displayed below.
19 * The entries with 'pg' are shown for page noded, these with 'ns' only for namespaces.
20 *
21 * Current available for everybody:
22 *   indexmenu_contextmenu['all']['pg']['view'] = [...array with menu description here... ];
23 *   indexmenu_contextmenu['all']['pg']['edit'] = [ ... ];
24 *   indexmenu_contextmenu['all']['ns']['view'] = [ ... ];
25 *
26 * Current available for admins:
27 *   indexmenu_contextmenu['pg']['view'] = [ ... ];
28 *   indexmenu_contextmenu['ns']['view'] = [ ... ];
29 *
30 * Current available for authenticated users:
31 *   indexmenu_contextmenu['pg']['view'] = [ ... ];
32 *   indexmenu_contextmenu['ns']['view'] = [ ... ];
33 *
34 * A menu description may contain four kind of entries:
35 *  - section title: array with one entry e.g.:
36 *      ['Section title (html allowed)']
37 *  - menu action: array with two entries e.g.:
38 *      ['Title of action 1 (html allowed)', 'javascript here ... see for examples scripts/contextmenu.js']
39 *  - menu action with custom tooltip: array with three entries e.g.:
40 *      ['Title of action 1 (html allowed)', 'javascript here ... see for examples scripts/contextmenu.js', 'Customized title']
41 *  - submenu: array with two entries where second entry is an array that describes again a menu e.g.:
42 *      ['title of submenu (html allowed)', [ ...array with menu actions... ]]
43 *
44 *
45 *  Examples:
46 *  A menu description array:
47 *   ... = [
48 *           ['section title'],
49 *           ['title of action 1', 'javascript here'],
50 *           ['title of submenu', [['title of subaction 1', 'javascript here'], ['title of subaction 1', 'javascript here', 'Click here for action']] ]
51 *         ];
52 *
53 * To Override the common menu title:
54 *  indexmenu_contextmenu['all']['pg']['view'][0] = ['customtitle'];
55 *
56 * To override a menu entry, for example the menu title:
57 *   indexmenu_contextmenu['all']['pg']['view'][0] = ['Custom Title'];
58 *
59 * To add option to page menu:
60 *   Array.splice(index, howManyToRemove, description1)
61 *     index = position to start (start counting at zero)
62 *     howManyToRemove = number of elements that are removed (set to 1 to replace a element)
63 *     description1 = array with menu entry description
64 *     -> optional: description2 = optional you can add more elements at once by splice(index, howManyToRemove, description1, description2, etc)
65 *
66 *   indexmenu_contextmenu['all']['pg']['view'].splice(1, 0, ['Input new page', '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\');"']);
67 */
68
69/* global LANG */
70/* global DOKU_BASE */
71/* global JSINFO */
72
73
74// IMPORTANT: DON'T MODIFY THIS FILE, BUT EDIT contextmenu.local.js PLEASE!
75// THIS FILE IS OVERWRITTEN WHEN PLUGIN IS UPDATED
76
77/**
78 * Right Context Menu configuration for all users:
79 */
80indexmenu_contextmenu['all']['pg'] = {
81    'view': [
82        ['<span class="indexmenu_titlemenu"><b>'+LANG.plugins.indexmenu.page+'</b></span>'],
83        [LANG.plugins.indexmenu.revs, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=revisions"'],
84        [LANG.plugins.indexmenu.tocpreview, '"javascript: IndexmenuContextmenu.createTocMenu(\'call=indexmenu&req=toc&id="+id+"\',\'picker_"+index.treeName+"\',\'s"+index.treeName+node.id+"\');"']
85    ],
86    //Menu items in edit mode, when previewing
87    'edit': [
88        ['<span class="indexmenu_titlemenu"><b>'+LANG.plugins.indexmenu.editmode+'</b></span>'],
89        [LANG.plugins.indexmenu.insertdwlink, '"javascript: IndexmenuContextmenu.insertTags(\'"+id+"\',\'"+index.config.sepchar+"\');"+index.treeName+".divdisplay(\'r\',0);"', LANG.plugins.indexmenu.insertdwlinktooltip]
90    ]
91};
92
93indexmenu_contextmenu['all']['ns'] = {
94    'view': [
95        ['<span class="indexmenu_titlemenu"><b>'+LANG.plugins.indexmenu.ns+'</b></span>'],
96        [LANG.plugins.indexmenu.search, '"javascript: IndexmenuContextmenu.srchpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.isdir+"\',\'"+node.dokuid+"\');"', LANG.plugins.indexmenu.searchtooltip]
97    ]
98};
99
100
101if (JSINFO && JSINFO.isadmin) {
102    /**
103     * Right Context Menu configuration for admin users:
104     */
105    indexmenu_contextmenu['pg'] = {
106        'view': [
107            [LANG.plugins.indexmenu.edit, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=edit"'],
108            ['<em>'+LANG.plugins.indexmenu.create+'--></em>', [
109                [LANG.plugins.indexmenu.headpage, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\',\'"+node.name+"\');"', LANG.plugins.indexmenu.headpagetooltip],
110                [LANG.plugins.indexmenu.startpage, 'IndexmenuContextmenu.getid(index.config.urlbase,id+index.config.sepchar+"start")+"do=edit"', LANG.plugins.indexmenu.startpagetooltip],
111                [LANG.plugins.indexmenu.custompage, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\');"', LANG.plugins.indexmenu.custompagetooltip]
112            ]],
113            ['<em>'+LANG.plugins.indexmenu.more+'--></em>', [
114                [LANG.plugins.indexmenu.acls, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=admin&page=acl"'],
115                [LANG.plugins.indexmenu.purgecache, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"purge=true"'],
116                [LANG.plugins.indexmenu.exporthtml, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=export_xhtml"'],
117                [LANG.plugins.indexmenu.exporttext, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=export_raw"']
118            ]]
119        ]
120    };
121
122    indexmenu_contextmenu['ns'] = {
123        'view': [
124            [LANG.plugins.indexmenu.newpage, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\');"', LANG.plugins.indexmenu.newpagetooltip],
125            ['<em>'+LANG.plugins.indexmenu.more+'--></em>', [
126                [LANG.plugins.indexmenu.headpagehere, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\',\'"+node.name+"\');"', LANG.plugins.indexmenu.headpageheretooltip],
127                [LANG.plugins.indexmenu.acls, 'IndexmenuContextmenu.getid(index.config.urlbase,node.dokuid)+"do=admin&page=acl"']
128            ]]
129        ]
130    };
131
132} else if (JSINFO && JSINFO.isauth) {
133    /**
134     * Right Context Menu configuration for authenticated users:
135     */
136    indexmenu_contextmenu['pg'] = {
137        'view': [
138            [LANG.plugins.indexmenu.newpagehere, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\');"'],
139            ['Edit', 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=edit"', 1, 0 ],
140            ['<em>'+LANG.plugins.indexmenu.more+'--></em>', [
141                [LANG.plugins.indexmenu.headpagehere, '"javascript: IndexmenuContextmenu.reqpage(\'"+index.config.urlbase+"\',\'"+index.config.sepchar+"\',\'"+node.dokuid+"\',\'"+node.name+"\');"'],
142                [LANG.plugins.indexmenu.purgecache, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"purge=true"'],
143                [LANG.plugins.indexmenu.exporthtml, 'IndexmenuContextmenu.getid(index.config.urlbase,id)+"do=export_xhtml"']
144            ]]
145        ]
146    };
147
148}
149
150var IndexmenuContextmenu = {
151
152    /**
153     * Common functions
154     * Insert your custom functions (available for all users) here.
155     */
156
157    /**
158     * Navigate to the search page
159     *
160     * @param {string} urlbase index.config.urlbase
161     * @param {string} sepchar index.config.sepchar
162     * @param {boolean} isdir whether a directory (probably boolean, might be string)
163     * @param {string} nid  page id of node (dokuid mentioned in indexmenu.js)
164     */
165
166    srchpage: function (urlbase, sepchar, isdir, nid) {
167        const enteredText = prompt(LANG.plugins.indexmenu.insertkeywords, "");
168        if (enteredText) {
169            let fnid = nid;
170            if (isdir == "0") {
171                fnid = fnid.substring(0, nid.lastIndexOf(sepchar));
172            }
173            let b = urlbase, re = new RegExp(sepchar, 'g');
174            fnid = fnid.replace(re, ":");
175            b += (urlbase.indexOf("?id=") < 0) ? '?id=' : '';
176            window.location.href = IndexmenuContextmenu.getid(b, enteredText + " @" + fnid) + "do=search";
177        }
178    },
179
180    /**
181     * Build a url to a wiki page
182     *
183     * @param {string} urlbase
184     * @param {string} id
185     * @returns {string}
186     */
187    getid: function (urlbase, id) {
188        let url = (urlbase || '') + encodeURIComponent(id || '');
189        url += (urlbase.indexOf("?") < 0) ? '?' : '&';
190        return url;
191    },
192
193    /**
194     * Navigate to the editor window
195     *
196     * @param {string} urlbase
197     * @param {string} sepchar
198     * @param {string} id
199     * @param {string} pagename
200     */
201    reqpage: function (urlbase, sepchar, id, pagename) {
202        let newpageid;
203        if (pagename) {
204            newpageid = id + sepchar + pagename;
205        } else {
206            newpageid = prompt(LANG.plugins.indexmenu.insertpagename, "");
207            if (!newpageid) {
208                return;
209            }
210            newpageid = id + sepchar + newpageid;
211        }
212        if (newpageid) {
213            window.location.href = IndexmenuContextmenu.getid(urlbase, newpageid) + "do=edit";
214        }
215    },
216
217    /**
218     * Insert link syntax with given id in current editor window
219     *
220     * @param {string} lnk page id
221     * @param {string} sepchar
222     */
223    insertTags: function (lnk, sepchar) {
224        let r, l = lnk;
225        if (sepchar) {
226            r = new RegExp(sepchar, "g");
227            l = lnk.replace(r, ':');
228        }
229        insertTags('wiki__text', '[[', ']]', l);
230    },
231
232    /**
233     * Create or catch the picker and hide it, next call the ajax content loading to get the ToC
234     *
235     * @param {string} get    query string
236     * @param {string} picker id of picker
237     * @param {string} btn    id of button
238     */
239    createTocMenu: function (get, picker, btn) {
240        var $toc_picker = jQuery('#' + picker);
241        if (!$toc_picker.length) {
242            $toc_picker = IndexmenuUtils.createPicker(picker, 'indexmenu_toc');
243            $toc_picker
244                .html('<a href="#"><img src="' + DOKU_BASE + 'lib/plugins/indexmenu/images/close.gif" class="indexmenu_close" /></a><div />')
245                .children().first().click(function (event) {
246                    event.stopPropagation();
247                    return IndexmenuContextmenu.togglePicker($toc_picker, jQuery('#' + btn));
248                });
249        } else {
250            $toc_picker.hide();
251        }
252        IndexmenuContextmenu.ajaxmenu(get, $toc_picker, jQuery('#' + btn), $toc_picker.children().last(), null);
253    },
254
255    /**
256     * Shows the picker and adds to it or to an internal containter the ajax content
257     *
258     * @param {string}   get        query string
259     * @param {jQuery}   $picker
260     * @param {jQuery}   $btn
261     * @param {jQuery}   $container if defined ajax result is added to it, otherwise to $picker
262     * @param {function} oncomplete called when defined to handle ajax result
263     */
264    ajaxmenu: function (get, $picker, $btn, $container, oncomplete) {
265        var $indx_list;
266        $indx_list = $container || $picker;
267
268        if (!IndexmenuContextmenu.togglePicker($picker, $btn)) return;
269
270        var onComplete = function (data) {
271            $indx_list.html('');
272            if (typeof oncomplete == 'function') {
273                oncomplete(data, $indx_list);
274            } else {
275                $indx_list.html(data);
276            }
277        };
278
279        //get content for picker/container
280        jQuery.ajax({
281            type: "POST",
282            url: DOKU_BASE + 'lib/exe/ajax.php',
283            data: get,
284            beforeSend: function () {
285                $indx_list.html('<div class="tocheader">'+LANG.plugins.indexmenu.loading+'</div>');
286            },
287            success: onComplete,
288            dataType: 'html'
289        });
290    },
291
292
293    /**
294     * Hide/show picker, will be shown beside btn
295     *
296     * @param {string|jQuery} $picker
297     * @param {jQuery}        $btn
298     * @return {Boolean} true if open, false closed
299     */
300    togglePicker: function ($picker, $btn) {
301        var x = 8, y = 0;
302
303        if (!$picker.is(':visible')) {
304            var pos = $btn.offset();
305            //position + width of button
306            x += pos.left + $btn[0].offsetWidth;
307            y += pos.top;
308
309            $picker
310                .show()
311                .offset({
312                    left: x,
313                    top: y
314                });
315
316            return true;
317        } else {
318            $picker.hide();
319            return false;
320        }
321    },
322
323    /**
324     * Fills the contextmenu by creating entries from the given configuration arrays and concatenating these
325     * to the #r<id> picker
326     *
327     * @param {any[]} amenu (part of) the configuration array
328     * @param {dTree} index the indexmenu object
329     * @param {int} n node id
330     */
331    arrconcat: function (amenu, index, n) {
332        var html, id, item, a, li;
333        if (typeof amenu == 'undefined' || typeof amenu['view'] == 'undefined') {
334            return;
335        }
336        var cmenu = amenu['view'];
337        if (jQuery('#tool__bar')[0] && amenu['edit'] instanceof Array) {
338            cmenu = amenu['edit'].concat(cmenu);
339        }
340        var node = index.aNodes[n];
341        id = node.hns || node.dokuid;
342
343        var createCMenuEntry = function (entry) {
344            return '<a title="' + ((entry[2]) ? entry[2] : entry[0]) + '" href="' + eval(entry[1]) + '">' + entry[0] + '</a>';
345        };
346
347        jQuery.each(cmenu, function (i, cmenuentry) {
348            if (cmenuentry == '') {
349                return true;
350            }
351            item = document.createElement('li');
352            var $cmenu = jQuery('#r' + index.treeName);
353            if (cmenuentry[1]) {
354                if (cmenuentry[1] instanceof Array) {
355                    html = document.createElement('ul');
356                    jQuery.each(cmenuentry[1], function (a, subcmenuentry) {
357                        li = document.createElement('li');
358                        li.innerHTML = createCMenuEntry(subcmenuentry);
359                        html.appendChild(li);
360                    });
361
362                    //}
363                    item.innerHTML = '<span class="indexmenu_submenu">' + cmenuentry[0] + '</span>';
364                    html.left = $cmenu[0].width;
365                    item.appendChild(html);
366                } else {
367                    item.innerHTML = createCMenuEntry(cmenuentry);
368                }
369            } else {
370                item.innerHTML = cmenuentry;
371            }
372            $cmenu.children().last().append(item);
373        });
374    },
375
376    /**
377     * Absolute positioning of the div at place of mouseclick
378     *
379     * @param obj div element
380     * @param e
381     */
382    mouseposition: function (obj, e) {
383        //http://www.quirksmode.org/js/events_properties.html
384        var X = 0, Y = 0;
385        if (!e) e = window.event;
386        if (e.pageX || e.pageY) {
387            X = e.pageX;
388            Y = e.pageY;
389        }
390        else if (e.clientX || e.clientY) {
391            X = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
392            Y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
393        }
394        obj.style.left = X - 5 + 'px';
395        obj.style.top = Y - 5 + 'px';
396    },
397
398    /**
399     * Check mouse button onmousedown event, only for middle and right mouse button contextmenu is shown
400     *
401     * @param {int} n node id
402     * @param {string|dTree} obj the unique name of a dTree object
403     * @param {event} e
404     */
405    checkcontextm: function (n, obj, e) {
406        e = e || event;
407        // mouse clicks: which 3 === right, button 2 === right button
408        if ((e.which === 3 || e.button === 2) || (window.opera && e.which === 1 && e.ctrlKey)) {
409            obj.contextmenu(n, e);
410            IndexmenuContextmenu.stopevt(e);
411        }
412    },
413
414    /**
415     * Prevent default oncontextmenu event
416     *
417     * @param {event} e
418     * @returns {boolean}
419     */
420    stopevt: function (e) {
421        if (!window.indexmenu_contextmenu) {
422            return true;
423        }
424        e = e || event;
425        e.preventDefault ? e.preventDefault() : e.returnValue = false;
426        return false;
427    }
428};
429
430