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