1
2// Context menu
3var indexmenu_contextmenu = {'all': []};
4
5/* DOKUWIKI:include scripts/nojsindex.js */
6/* DOKUWIKI:include scripts/toolbarindexwizard.js */
7/* DOKUWIKI:include scripts/contextmenu.js */
8/* DOKUWIKI:include scripts/indexmenu.js */
9/* DOKUWIKI:include scripts/contextmenu.local.js */
10
11
12/* DOKUWIKI:include scripts/fancytree/jquery.fancytree-all.min.js */
13
14//   - page id without URL rewriting http://example.doku/doku.php?id=test:start
15//   - page id without URL rewriting http://example.doku/doku.php?id=test:plugins#interwikipaste
16//   - page id with .htaccess URL rewriting http://example.doku/test:plugins
17//   - page id with .htaccess URL rewriting and 'useslash' config http://example.doku/test/plugins
18//   - page id with internal URL rewriting http://example.doku/doku.php/test:plugins
19//   - http://example.doku/lib/exe/detail.php?id=test%3Aplugins&media=ns:image.jpg
20//   - http://example.doku/lib/exe/fetch.php?w=400&tok=097122&media=ns:image.jpg
21//   - http://example.doku/lib/exe/fetch.php?media=test:file.pdf
22//   - http://example.doku/_detail/ns:image.jpg?id=test%3Aplugins
23//   - http://example.doku/_media/test:file.pdf
24//   - http://example.doku/_detail/ns/image.jpg?id=test%3Aplugins
25//   - http://example.doku/_media/test/file.pdf
26
27
28jQuery(function(){  // on page load
29    // Create the tree inside the <div id="tree"> element.
30    const predefinedPresets = {
31        'bootstrap': { //works with template bootstrap3 or by manually adding resources to icon plugin assets
32            'preset': 'bootstrap3',
33            'map': {}
34        },
35        'bootstrap-n': { //works with template bootstrap3 or ..etc
36            'preset': 'bootstrap3',
37            'map': {}
38        },
39        'awesome': { //works with icons-plugin, settings: enable plugin»icons»loadFontAwesome
40            'preset': 'awesome4', //plugin icons does include only awesome4, not awesome5.
41            'map': {}
42        },
43        'material': { // add Material Icons font stylesheet to header with TPL_METAHEADER_OUTPUT in action component
44            'preset': 'material',
45            'map': {}
46        },
47        'mdi': { //works with icons-plugin, settings: enable plugin»icons»loadMaterialDesignIcons
48            'preset': '',
49            'map': {
50                _addClass: "mdi",
51                checkbox: "mdi-checkbox-blank-outline",
52                checkboxSelected: "mdi-check-box-outline",
53                checkboxUnknown: "mdi-checkbox-intermediate fancytree-helper-indeterminate-cb",
54                dragHelper: "mdi-play",
55                dropMarker: "mdi-skip-forward",
56                error: "mdi-warning",
57                expanderClosed: "mdi-chevron-right",
58                expanderLazy: "mdi-chevron-right",
59                expanderOpen: "mdi-chevron-down",
60                // We may prevent wobbling rotations on FF by creating a separate sub element:
61                loading: "mdi-refresh",
62                nodata: "mdi-information-outline",
63                noExpander: "",
64                radio: "mdi-radiobox-blank", // "fa-circle-o"
65                radioSelected: "mdi-radiobox-marked",
66                // Default node icons.
67                // (Use tree.options.icon callback to define custom icons based on node data)
68                doc: "mdi-file-outline",
69                docOpen: "mdi-file-outline",
70                folder: "mdi-folder",
71                folderOpen: "mdi-folder-open",
72            }
73        },
74        'typicons': { //works with icons-plugin, settings: enable plugin»icons»loadTypicons
75            'preset': '',
76            'map': {
77                _addClass: "typcn",
78                checkbox: "typcn-media-stop-outline",
79                checkboxSelected: "typcn-input-checked",
80                checkboxUnknown: "typcn-media-stop-outline fancytree-helper-indeterminate-cb",
81                dragHelper: "typcn-media-play-outline",
82                dropMarker: "typcn-media-fast-forward-outline",
83                error: "typcn-warning",
84                expanderClosed: "typcn-media-play",
85                expanderLazy: "typcn-media-play",
86                expanderOpen: "typcn-arrow-sorted-down",
87                // We may prevent wobbling rotations on FF by creating a separate sub element:
88                loading: "typcn-arrow-sync",
89                nodata: "typcn-info-large",
90                noExpander: "",
91                radio: "typcn-media-record-outline", // "fa-circle-o"
92                radioSelected: "typcn-media-record",
93                // Default node icons.
94                // (Use tree.options.icon callback to define custom icons based on node data)
95                doc: "typcn-document",
96                docOpen: "typcn-document",
97                folder: "typcn-folder",
98                folderOpen: "typcn-folder-open",
99            }
100        }
101
102    };
103    // userDefinedPresets can be defined in conf/userscript.js
104    const presets = {...predefinedPresets, ...(typeof userDefinedPresets === 'undefined' ? [] : userDefinedPresets)};
105    //let targettype;
106    // function logEvent(event, data, msg){
107    //     //        var args = Array.isArray(args) ? args.join(", ") :
108    //     msg = msg ? ": " + msg : "";
109    //     jQuery.ui.fancytree.info("Event('" + event.type + "', node=" + data.node + ")" + msg);
110    // }
111    jQuery(".indexmenu_js2").each(function(){
112        let $tree = jQuery(this),
113            id = $tree.attr('id');
114        const options = $tree.data('options');
115        // console.log("options");
116        // console.log(options);
117        let themePreset = presets[options.opts.theme];
118        let targettype; //to share type between handlers
119        let extensions = [];
120        if(themePreset) {
121            extensions.push("glyph");
122        }
123        if(options.opts.persist) {
124            extensions.push("persist");
125        }
126
127        $tree.fancytree({
128            //enabled extensions
129            extensions: extensions,
130            //settings for glyph extension
131            glyph: {
132                preset: themePreset ? themePreset.preset : '',
133                map: themePreset ? themePreset.map : {}
134            },
135            // 0=quite, 1=only errors, upto 4=also debug
136            //debugLevel: 4,
137            //settings for persist extension
138            persist: {
139                expandLazy: true,
140                // fireActivate: false,    // false: suppress `activate` event after active node was restored
141                // overrideSource: false,  // true: cookie takes precedence over `source` data attributes.
142                store: "auto" // 'cookie', 'local': use localStore, 'session': sessionStore
143                // Sample for a custom store:
144                // store: {
145                //   get: function(key){ this.info("get(" + key + ")"); return window.sessionStorage.getItem(key); },
146                //   set: function(key, value){ this.info("set(" + key + ", " + value + ")"); window.sessionStorage.setItem(key, value); },
147                //   remove: function(key){ this.info("remove(" + key + ")"); window.sessionStorage.removeItem(key); }
148            },
149            // number of levels already expanded, and not unexpandable.
150            //minExpandLevel: 2,
151            // expand with single click instead of dblclick
152            clickFolderMode: 3,
153            // closes other opened nodes, so only one node is opened
154            //autoCollapse: true,
155            // for keyboard..  --opening folders becomes jumpy
156            //autoScroll: true,
157            // Looping in combination with clicking
158            autoActivate: false,
159            // disabled because it causes also autoscrolling, such that select node is out-of-view
160            activeVisible: false,
161
162            escapeTitles: false,
163            tooltip: true,
164            //use same setting as wiki page
165            rtl: jQuery('html[dir=rtl]').length,
166
167            //for keyboard control
168            keydown: function (event, data) {
169                switch (event.which) {
170                    case 32: // [space]
171                        // logEvent(event,data);
172                        break;
173                    case 13: // [enter]
174                        // logEvent(event,data);
175                        if(data.node.data.url){
176                            // console.log('redirect');
177                            window.location.href = data.node.data.url;
178                        }
179                        break;
180                }
181            },
182
183            //store in click some event data for the activate handler
184            click: function(event, data) {
185                // return false to prevent default behavior (i.e. activation, ...)
186                targettype = data.targetType; //store target type, only available in click handler
187            },
188
189            //go to wiki page if node is activated
190            activate: function(event, data){
191                const node = data.node;
192
193                //prevent looping (hns is false or a page id)
194                if(node.key === JSINFO.id || node.data.hns === JSINFO.id) {
195                    //node is equal to current page, prevent to follow the url
196                    return;
197                }
198                if(options.opts.nopg && node.key === JSINFO.namespace + ':') {
199                    //nopg marks parent ns node active, prevent to follow the url
200                    return;
201                }
202
203                // expander should not follow link
204                if(targettype === 'expander') {
205                    targettype = false; //reset
206                    return false;
207                }
208
209                if(node.data.url === false) {
210                    return false;
211                }
212
213                if(node.data.url){
214                    window.location.href = node.data.url;
215                }
216            },
217
218            // active marked node (=current page)
219            init: function(event, data) {
220                //activate current node
221                data.tree.reactivate();
222            },
223            //add url
224            enhanceTitle: function(event, data) {
225                let node = data.node;
226
227                if(node.data.url === false) {
228                    return;
229                }
230                if(node.data.url) { // pagename 0 has url /0
231                    //nopg has potentially not existing pages
232                    let cls = '';
233                    if(node.data.hnsNotExisting) {
234                        cls = ' class="wikilink2"';
235                    }
236                    data.$title.html("<a href='" + node.data.url + "'"+cls+">" + node.title + "</a>");
237                }
238            },
239            //retrieve initial data
240            source: {
241                url: DOKU_BASE + 'lib/exe/ajax.php',
242                data: {
243                    ns: options.ns,
244                    call: 'indexmenu',
245                    req: 'fancytree',
246
247                    level: options.opts.level, //only init
248                    nons: options.opts.nons ? 1 : 0, //only init; without ns, no lower levels possible
249                    nopg: options.opts.nopg ? 1 : 0,
250                    subnss: options.opts.subnss, //subns to open. Only on init array, later just current ns string
251                    navbar: options.opts.navbar ? 1 : 0, //only init: open tree at current page
252                    currentpage: JSINFO.id,
253                    max: options.opts.max, //#n of max#n#m
254                    skipns: options.opts.skipns,
255                    skipfile: options.opts.skipfile,
256                    sort: options.sort.sort ? options.sort.sort : 0, //'t', 'd', false TODO is false handled correctly?
257                    msort: options.sort.msort ? options.sort.msort : 0, //'indexmenu_n', or metadata 'key subkey' TODO is empty handled correctly?
258                    rsort: options.sort.rsort ? 1 : 0,
259                    nsort: options.sort.nsort ? 1 : 0,
260                    hsort: options.sort.hsort ? 1 : 0,
261
262                    init: 1
263                }
264            },
265            //retrieve data of expanded nodes
266            lazyLoad: function(event, data) {
267                const node = data.node;
268                // Issue an Ajax request to load child nodes
269                data.result = {
270                    url: DOKU_BASE + 'lib/exe/ajax.php',
271                    data: {
272                        ns: node.key, // ns with trailing :
273                        call: 'indexmenu',
274                        req: 'fancytree',
275
276                        level: 1, //level opened nodes, for follow up ajax requests only next level, so:1
277                        nons: options.opts.nons ? 1 : 0,
278                        nopg: options.opts.nopg ? 1 : 0,
279                        subnss: '', //options.opts.subnss is used on init
280                        currentpage: JSINFO.id,
281                        max: options.opts.maxajax, //#m of max#n#m
282                        skipns: options.opts.skipns,
283                        skipfile: options.opts.skipfile,
284                        sort: options.sort.sort ? options.sort.sort  : 0,
285                        msort: options.sort.msort ? options.sort.msort : 0,
286                        rsort: options.sort.rsort ? 1 : 0,
287                        nsort: options.sort.nsort ? 1 : 0,
288                        hsort: options.sort.hsort ? 1 : 0,
289
290                        init: 0
291                    }
292                }
293            }
294        });
295
296        //hide the fallback nojs indexmenu
297        jQuery('#nojs_' + id.substring(6)).css("display", "none");
298
299
300        // Note: Loading and initialization may be asynchronous, so the nodes may not be accessible yet.
301
302        // On page load, activate node if node.data.href matches the url#href
303//         let tree = jQuery.ui.fancytree.getTree("#" + id),
304//             path = window.parent && window.parent.location.pathname;
305// // console.log(path);
306// // console.log('test');
307//         if(path) {
308//             let arr = path.split('/'); // not reliable with config:useslash?
309//             let last = arr[arr.length-1] || arr[arr.length-2];
310//             // console.log(arr);
311//             // console.log(last);
312//
313//             // tree.activateKey(last);
314//             // var node1=tree.getNodeByKey(last);
315//             // console.log(node1);
316//             //     node1.setActive();
317//             // also possible:
318//             //                $.ui.fancytree.getTree("#tree").getNodeByKey("id4.3.2").setActive();
319//
320//             // tree.visit(function(n) {
321//             //     console.log(n.key);
322//             //     console.log(n);
323//             //     if( n.key && n.key === last ) {
324//             //         n.setActive();  //if not using iframes, this creates a loops in combination with activate above
325//             //         return false; // done: break traversal
326//             //     }
327//             // });
328//         }
329// console.log(tree);
330// console.log("test");
331//         jQuery.contextMenu({
332//             selector: "span.fancytree-title",
333//             items: {
334//                 // "cut": {name: "Cut", icon: "cut",
335//                 //     callback: function(key, opt){
336//                 //         var node = jQuery.ui.fancytree.getNode(opt.$trigger);
337//                 //         alert("Clicked on " + key + " on " + node);
338//                 //     }
339//                 // },
340//                 "page": {name: "Page", icon: "", disabled: true },
341//                 "sep1": "----",
342//                 "revs": {name: "Revisions", icon: "ui-icon-arrowreturn-1-w", disabled: false },
343//                 "toc": {name: "ToC preview", icon: "ui-icon-bookmark", disabled: false },
344//                 "edit": {name: "Edit", icon: "edit", disabled: false },
345//                 "hpage": {name: "Headpage", icon: "add", disabled: false},
346//                 "spage": {name: "Start page", icon: "add", disabled: false},
347//                 "cpage": {name: "Custom page...", icon: "add", disabled: false},
348//                 "acls": {name: "Acls", icon: "ui-icon-locked", disabled: false},
349//                 "purge": {name: "Purge cache", icon: "loading", disabled: false},
350//                 "html": {name: "Export as HTML", icon: "ui-icon-document", disabled: false},
351//                 "text": {name: "Export as text", icon: "ui-icon-note", disabled: false},
352//                 "sep2": "----",
353//                 "ns": {name: "Namespace", icon: "", disabled: true},
354//                 "sep3": "----",
355//                 "search": {name: "Search...", icon: "ui-icon-search", disabled: false},
356//                 "npage": {name: "New page...", icon: "add", disabled: false},
357//                 "nshpage": {name: "Headpage here", icon: "add", disabled: false},
358//                 "nsacls": {name: "Acls", icon: "ui-icon-locked", disabled: false}
359//             },
360//             callback: function(itemKey, opt) {
361//                 var node = jQuery.ui.fancytree.getNode(opt.$trigger);
362//                 alert("select " + itemKey + " on " + node);
363//             }
364//         });
365
366        // $tree.contextmenu({
367        //     delegate: "span.fancytree-title",
368        //     autoFocus: true,
369        //     //      menu: "#options",
370        //     menu: [
371        //         {title: "Page", cmd: 'pg'},
372        //         {title: "----", cmd: 'pg'},
373        //         {title: "Revisions", cmd: "revs", uiIcon: "ui-icon-arrowreturn-1-w"},
374        //         {title: "ToC preview", cmd: "toc", uiIcon: "ui-icon-bookmark"},
375        //         {title: "Edit", cmd: "edit", uiIcon: "ui-icon-pencil", disabled: false },
376        //         {title: "Headpage", cmd: "hpage", uiIcon: "ui-icon-plus"},
377        //         {title: "Start page", cmd: "spage", uiIcon: "ui-icon-plus"},
378        //         {title: "Custom page...", cmd: "cpage", uiIcon: "ui-icon-plus"},
379        //         {title: "Acls", cmd: "acls", uiIcon: "ui-icon-locked", disabled: true },
380        //         {title: "Purge cache", cmd: "purge", uiIcon: "ui-icon-arrowrefresh-1-e"},
381        //         {title: "Export as HTML", cmd: "html", uiIcon: "ui-icon-document"},
382        //         {title: "Export as text", cmd: "text", uiIcon: "ui-icon-note"},
383        //         {title: "Namespace", cmd:'ns'},
384        //         {title: "----", cmd:'ns'},
385        //         {title: "Search...", cmd: "search", uiIcon: "ui-icon-search"},
386        //         {title: "New page...", cmd: "npage", uiIcon: "ui-icon-plus"},// children:[]
387        //         {title: "Headpage here", cmd: "nshpage", uiIcon: "ui-icon-plus"},
388        //         {title: "Acls", cmd: "nsacls", uiIcon: "ui-icon-locked"}
389        //     ],
390        //     beforeOpen: function(event, ui) {
391        //         var node = jQuery.ui.fancytree.getNode(ui.target);
392        //         // Modify menu entries depending on node status
393        //         $tree.contextmenu("enableEntry", "toc", node.isFolder());
394        //         // Show/hide single entries
395        //         $tree.contextmenu("showEntry", "pg", !node.isFolder());
396        //         $tree.contextmenu("showEntry", "revs", !node.isFolder());
397        //         $tree.contextmenu("showEntry", "toc", !node.isFolder());
398        //         $tree.contextmenu("showEntry", "edit", !node.isFolder());
399        //         $tree.contextmenu("showEntry", "hpage", !node.isFolder());
400        //         $tree.contextmenu("showEntry", "spage", !node.isFolder());
401        //         $tree.contextmenu("showEntry", "cpage", !node.isFolder());
402        //         $tree.contextmenu("showEntry", "acls", !node.isFolder());
403        //         $tree.contextmenu("showEntry", "purge", !node.isFolder());
404        //         $tree.contextmenu("showEntry", "html", !node.isFolder());
405        //         $tree.contextmenu("showEntry", "text", !node.isFolder());
406        //
407        //         $tree.contextmenu("showEntry", "ns", node.isFolder());
408        //         $tree.contextmenu("showEntry", "search", node.isFolder());
409        //         $tree.contextmenu("showEntry", "npage", node.isFolder());
410        //         $tree.contextmenu("showEntry", "nshpage", node.isFolder());
411        //         $tree.contextmenu("showEntry", "nsacls", node.isFolder());
412        //
413        //         // Activate node on right-click
414        //         node.setActive();
415        //         // Disable tree keyboard handling
416        //         ui.menu.prevKeyboard = node.tree.options.keyboard;
417        //         node.tree.options.keyboard = false;
418        //     },
419        //     close: function(event, ui) {
420        //         // Restore tree keyboard handling
421        //         // console.log("close", event, ui, this)
422        //         // Note: ui is passed since v1.15.0
423        //         var node = jQuery.ui.fancytree.getNode(ui.target);
424        //         node.tree.options.keyboard = ui.menu.prevKeyboard;
425        //         node.setFocus();
426        //     },
427        //     select: function(event, ui) {
428        //         var node = jQuery.ui.fancytree.getNode(ui.target);
429        //         alert("select " + ui.cmd + " on " + node);
430        //     }
431        // });
432    });
433});
434
435
436/**
437 * Add button action for the indexmenu wizard button
438 *
439 * @param  {jQuery}   $btn  Button element to add the action to
440 * @param  {Array}    props Associative array of button properties
441 * @param  {string}   edid  ID of the editor textarea
442 * @return {boolean}  If button should be appended
443 */
444function addBtnActionIndexmenu($btn, props, edid) {
445    indexmenu_wiz.init(jQuery('#' + edid));
446    $btn.on('click', function () {
447        indexmenu_wiz.toggle();
448        return false;
449    });
450    return true;
451}
452
453
454// try to add button to toolbar
455if (window.toolbar !== undefined) {
456    window.toolbar[window.toolbar.length] = {
457        "type": "Indexmenu",
458        "title": "Insert the Indexmenu tree",
459        "icon": "../../plugins/indexmenu/images/indexmenu_toolbar.png"
460    }
461}
462
463
464/**
465 *  functions for js index renderer and contextmenu
466 */
467var IndexmenuUtils = {
468
469    /**
470     * Determine extension from given theme dir name
471     *
472     * @param {string} themedir name of theme dir
473     * @returns {string} extension gif, png or jpg
474     */
475    determineExtension: function (themedir) {
476        let extension = "gif";
477        let posext = themedir.lastIndexOf(".");
478        if (posext > -1) {
479            posext++;
480            let ext = themedir.substring(posext, themedir.length).toLowerCase();
481            if ((ext === "png") || (ext === "jpg")) {
482                extension = ext;
483            }
484        }
485        return extension;
486    },
487
488    /**
489     * Create div with given id and class on body and return it
490     *
491     * @param {string} id picker id
492     * @param {string} cl class(es)
493     * @return {jQuery} jQuery div
494     */
495    createPicker: function (id, cl) {
496        return jQuery('<div>')
497            .addClass(cl || 'picker')
498            .attr('id', id)
499            .css({position: 'absolute'})
500            .hide()
501            .appendTo('body');
502    }
503
504};
505