1/*--------------------------------------------------------| 2 | dTree 2.05 | www.destroydrop.com/javascript/tree/ | 3 |--------------------------------------------------------| 4 | Copyright (c) 2002-2003 Geir Landro | 5 | | 6 | This script can be used freely as long as all | 7 | copyright messages are intact. | 8 | | 9 | Updated: 17.04.2003 | 10 |--------------------------------------------------------| 11 | Modified for Dokuwiki by | 12 | Samuele Tognini <samuele@samuele.netsons.org> | 13 | under GPL 2 license | 14 | (http://www.gnu.org/licenses/gpl.html) | 15 | Updated: 29.08.2009 | 16 |--------------------------------------------------------| 17 | Modified for Dokuwiki by | 18 | Rene Hadler <rene.hadler@iteas.at> | 19 | under GPL 2 license | 20 | (http://www.gnu.org/licenses/gpl.html) | 21 | Updated: 07.08.2012 | 22 |--------------------------------------------------------| 23 | jQuery update - 27 02 2012 | 24 | Gerrit Uitslag <klapinklapin@gmail.com | 25 |--------------------------------------------------------| 26 | indexmenu | https://www.dokuwiki.org/plugin:indexmenu | 27 |-------------------------------------------------------*/ 28/* global DOKU_COOKIE_PARAM */ 29/* global DOKU_BASE */ 30 31/* 32 * ids used in the dTree: 33 * - div#cdtree_<id indexmenu> div top level 34 * - div#dtree_<id indexmenu> div contains all nodes 35 * - div#toc_<id indexmenu> ?? 36 * - div.dtreeNode 37 * - img#i<id indexmenu><nodenr?> icon 38 * - a#s<id indexmenu><nodenr?> url to page/namespace with title 39 * - div#t<id indexmenu><nodenr?> button for opening ToC, included if hovered 40 * - div.d<id indexmenu><nodenr?> 41 * repeats: - div.dtreeNode (with img#i, a#s and div#t) 42 * repeats: - div.d<id indexmenu><nodenr?> 43 * - z<id indexmenu> scroll rightward arrows 44 * - left_<id indexmenu> scroll leftward arrows 45 * 46 * at the end of body: 47 * - picker_<id indexmenu> popup with ToC 48 * - r<id indexmenu> rightmouse button menu 49 */ 50 51/** 52 * dTreeNode object 53 * 54 * @param {string} dokuid page id of node 55 * @param {number} id node id 56 * @param {number} pid parent node id 57 * @param {string} name Page Title 58 * @param {number|string} hns page id of headpage of namespace 59 * @param {number} isdir is directory? 60 * @param {number} ajax load subnodes by ajax 61 * @constructor 62 */ 63function dTreeNode(dokuid, id, pid, name, hns, isdir, ajax) { 64 /** @type {string} */ 65 this.dokuid = dokuid; // page id of node 66 /** @type {number} */ 67 this.id = id; // id number of node 68 /** @type {number} */ 69 this.pid = pid; // id number of parent node 70 /** @type {string} */ 71 this.name = name; // ns/page title 72 /** @type {number|string} */ 73 this.hns = hns; // headpage of namespace or zero 74 /** @type {boolean} */ 75 this.isdir = Boolean(isdir); // is directory 76 /** @type {boolean} */ 77 this.ajax = Boolean(ajax); // load its nodes by ajax 78 /** @type {boolean} */ 79 this._io = false; // is node open 80 /** @type {boolean} */ 81 this._is = false; // is selected 82 /** @type {boolean} */ 83 this._ls = false; // is last sibling 84 /** @type {boolean} */ 85 this._hc = Boolean(ajax); // has children 86 /** @type {number} */ 87 this._ai = 0; // id number of first child.... 88 /** @type {dTreeNode} */ 89 this._p = undefined; // parent dTreeNode 90 /** @type {number} */ 91 this._lvl = 0; // level 92 /** @type {boolean} */ 93 this._ok = false; // all children are loaded 94 /** @type {boolean} */ 95 this._cp = false; // current page 96 /** @type {string} */ 97 this.icon = ''; // icon of closed node 98 /** @type {string} */ 99 this.iconOpen = ''; // icon of opened node 100} 101 102/** 103 * Tree object 104 * 105 * @param {string} treeName id of the indexmenu, has form 'indexmenu_<identifier>' 106 * @param {string} theme name of theme dir 107 * @constructor 108 */ 109function dTree(treeName, theme) { 110 let imgExt = IndexmenuUtils.determineExtension(theme); 111 this.config = { 112 urlbase: DOKU_BASE + 'doku.php?id=', // base of dokuwiki (set in page) 113 plugbase: DOKU_BASE + 'lib/plugins/indexmenu', // base of plugin folder 114 useCookies: true, // use cookies (set in page) e.g. disabled for context option 115 scroll: true, // enable scrolling of tree in too small columns (set in page) 116 toc: true, // enable ToC popups in tree (set in page) 117 maxjs: 1, // number set by maxjs option (set in page) 118 jsajax: '', // &max=#&sort=(t|d)&msort=(indexmenu_n|<metakey>)&rsort=1&nsort=1&hsort=1&nopg=1&skipns=+=/.../&skipfile=+=/.../(set in page) 119 sepchar: ':', // value ':', ';' or '/' (set in page) 120 theme: theme // dir name of theme folder 121 }; 122 let imagePath = this.config.plugbase + '/images/' + theme + '/'; 123 this.icon = { 124 root: imagePath + 'base.' + imgExt, 125 folder: imagePath + 'folder.' + imgExt, 126 folderH: imagePath + 'folderh.' + imgExt, 127 folderOpen: imagePath + 'folderopen.' + imgExt, 128 folderHOpen: imagePath + 'folderhopen.' + imgExt, 129 node: imagePath + 'page.' + imgExt, 130 empty: imagePath + 'empty.' + imgExt, 131 line: imagePath + 'line.' + imgExt, 132 join: imagePath + 'join.' + imgExt, 133 joinBottom: imagePath + 'joinbottom.' + imgExt, 134 plus: imagePath + 'plus.' + imgExt, 135 plusBottom: imagePath + 'plusbottom.' + imgExt, 136 minus: imagePath + 'minus.' + imgExt, 137 minusBottom: imagePath + 'minusbottom.' + imgExt, 138 nlPlus: imagePath + 'nolines_plus.' + imgExt, 139 nlMinus: imagePath + 'nolines_minus.' + imgExt 140 }; 141 /** @type {string} */ 142 this.treeName = treeName; // (unique) name of this indexmenu 143 /** @type {dTreeNode[]} */ 144 this.aNodes = []; // array of nodes 145 /** @type {number[]} */ 146 this.aIndent = []; // array stores the indents of the tree (contains values 0 or 1) 147 /** @type {dTreeNode} */ 148 this.root = new dTreeNode(false, -1); 149 /** @type {number} */ 150 this.selectedNode = undefined; // node id 151 /** @type {boolean} */ 152 this.selectedFound = false; // set to true when found 153 /** @type {boolean} */ 154 this.completed = false; // succesfull written js tree to the page 155 /** @type {number} */ 156 this.scrllTmr = 0; // store timer id for horizontal scrolling the page 157 /** @type {string} */ 158 this.pageid = JSINFO.id || ''; // current page 159 160 this.fajax = false; // if retrieve next level of opened nodes 161} 162/** 163 * CSS classes: 164 * 165 * a.nodeFdUrl Namespace with url link (headpage) 166 * a.node Namespace without url link 167 * a.nodeUrl Page 168 * a.nodeSel Last visited page 169 * a.navSel Current page 170 */ 171 172 173/** 174 * Adds a new node to the node array 175 * 176 * @param {string} dokuid page id of node 177 * @param {number} id node id 178 * @param {number} pid parent node id 179 * @param {string} name Page Title 180 * @param {number|string} hns page id of headpage of namespace 181 * @param {number} isdir is directory? 182 * @param {number} ajax load subnodes by ajax 183 */ 184dTree.prototype.add = function (dokuid, id, pid, name, hns, isdir, ajax) { 185 this.aNodes[this.aNodes.length] = new dTreeNode(dokuid, id, pid, name, hns, isdir, ajax); 186}; 187 188/** 189 * Open all nodes, if no node status was stored in cookie 190 */ 191dTree.prototype.openAll = function () { 192 if (!this.getCookie('co' + this.treeName)) { 193 this.oAll(true); 194 } 195}; 196 197/** 198 * Outputs the tree to the page. Called by document.write after adding the nodes to the tree. 199 * 200 * @returns {string} html of whole tree 201 */ 202dTree.prototype.toString = function () { 203 let str = ''; 204 this.pageid = this.pageid.replace(/:/g,this.config.sepchar); 205 if (this.config.scroll) { 206 str += '<div id="cdtree_' + this.treeName + '" class="dtree" style="position:relative;overflow:hidden;width:100%;">'; 207 } 208 str += '<div id="dtree_' + this.treeName + '" class="dtree ' + this.config.theme + '" style="overflow:'; 209 if (this.config.scroll) { 210 str += 'visible;position:relative;width:100%"'; 211 } else { 212 str += 'hidden;"'; 213 } 214 str += '>'; 215 if (jQuery('#dtree_' + this.treeName)[0]) { 216 str += '<div class="error">Indexmenu id conflict</div>'; 217 } 218 if (this.config.toc) { 219 str += '<div id="t' + this.treeName + '" class="indexmenu_tocbullet ' + this.config.theme + '" style="display:none;" title="Table of contents"></div>'; 220 str += '<div id="toc_' + this.treeName + '" style="display:none;"></div>'; 221 } 222 if (this.config.useCookies) { 223 this.selectedNode = this.getSelected(); 224 } 225 str += this.addNode(this.root) + '</div>'; 226 if (this.config.scroll) { 227 str += '<div id="z' + this.treeName + '" class="indexmenu_rarrow"></div>'; 228 str += '<div id="left_' + this.treeName + '" class="indexmenu_larrow" style="display:none;" title="Click to scroll back" onmousedown="' + this.treeName + '.scroll(\'r\',1)" onmouseup="' + this.treeName + '.stopscroll()"></div>'; 229 str += '</div>'; 230 } 231 this.completed = true; 232 //hide the fallback nojs indexmenu 233 jQuery('#nojs_' + this.treeName).css("display", "none"); //using .hide(); let's crash opera 234 return str; 235}; 236 237/** 238 * Creates the tree structure 239 * 240 * @param {dTreeNode} pNode 241 * @returns {string} html of node (inclusive children) 242 */ 243dTree.prototype.addNode = function (pNode) { 244 let str = '', cn, n = pNode._ai, l = pNode._lvl + 1; 245 for (n; n < this.aNodes.length; n++) { 246 if (this.aNodes[n].pid === pNode.id) { 247 cn = this.aNodes[n]; 248 cn._p = pNode; 249 cn._ai = n; 250 cn._lvl = l; 251 this.setCS(cn); 252 if (cn._hc && !cn._io && this.config.useCookies) { 253 cn._io = this.isOpen(cn.id); 254 } 255 if (this.pageid === (!cn.hns && cn.dokuid || cn.hns)) { 256 cn._cp = true; 257 } else if (cn.id === this.selectedNode && !this.selectedFound) { 258 cn._is = true; 259 this.selectedNode = n; 260 this.selectedFound = true; 261 } 262 if (!cn._hc && cn.isdir && !cn.ajax && !cn.hns) { 263 if (cn._ls) { 264 str += this.noderr(cn, n); 265 } 266 } else { 267 str += this.node(cn, n); 268 } 269 if (cn._ls) { 270 break; 271 } 272 } 273 } 274 return str; 275}; 276 277/** 278 * Create empty node 279 * 280 * @param {dTreeNode} node 281 * @param {int} nodeId 282 * @returns {string} html of empty node 283 */ 284dTree.prototype.noderr = function (node, nodeId) { 285 let str = '<div class="dTreeNode">' + this.indent(node, nodeId); 286 str += '<div class="emptynode" title="Empty"></div></div>'; 287 return str; 288}; 289 290/** 291 * Creates the node icon, url and text 292 * 293 * @param {dTreeNode} node 294 * @param {int} nodeId 295 * @returns {string} html of node (inclusive children) 296 */ 297dTree.prototype.node = function (node, nodeId) { 298 let h = 1, jsfnc, str; 299 jsfnc = 'onmouseover="' + this.treeName + '.show_feat(\'' + nodeId + '\');" onmousedown="return IndexmenuContextmenu.checkcontextm(\'' + nodeId + '\',' + this.treeName + ',event);" oncontextmenu="return IndexmenuContextmenu.stopevt(event)"'; 300 if (node._lvl > this.config.maxjs) { 301 h = 0; 302 } else { 303 node._ok = true; 304 } 305 str = '<div class="dTreeNode">' + this.indent(node, nodeId); 306 node.icon = (this.root.id === node.pid) ? this.icon.root : ((node.hns) ? this.icon.folderH : ((node._hc) ? this.icon.folder : this.icon.node)); 307 node.iconOpen = (node._hc) ? ((node.hns) ? this.icon.folderHOpen : this.icon.folderOpen) : this.icon.node; 308 if (this.root.id === node.pid) { 309 node.icon = this.icon.root; 310 node.iconOpen = this.icon.root; 311 } 312 str += '<img id="i' + this.treeName + nodeId + '" src="' + ((node._io) ? node.iconOpen : node.icon) + '" alt="" />'; 313 if (!node._hc || node.hns) { 314 str += '<a id="s' + this.treeName + nodeId + '" class="' + ((node._cp) ? 'navSel' : ((node._is) ? 'nodeSel' : (node._hc) ? 'nodeFdUrl' : 'nodeUrl')); 315 str += '" href="' + this.config.urlbase; 316 (node.hns) ? str += node.hns : str += node.dokuid; 317 str += '"' + ' title="' + node.name + '"' + jsfnc; 318 str += ' onclick="javascript: ' + this.treeName + '.s(' + nodeId + ');"'; 319 str += '>' + node.name + '</a>'; 320 } 321 else if (node.pid !== this.root.id) { 322 str += '<a id="s' + this.treeName + nodeId + '" href="javascript: ' + this.treeName + '.o(' + nodeId + '); " class="node"' + jsfnc + '>' + node.name + '</a>'; 323 } else { 324 str += node.name; 325 } 326 str += '</div>'; 327 if (node._hc) { 328 str += '<div id="d' + this.treeName + nodeId + '" class="clip" style="display:' + ((this.root.id === node.pid || node._io) ? 'block' : 'none') + ';">'; 329 if (h) { 330 str += this.addNode(node); 331 } 332 str += '</div>'; 333 } 334 this.aIndent.pop(); 335 return str; 336}; 337 338/** 339 * Adds the empty and line icons which indent the node 340 * 341 * @param {dTreeNode} node 342 * @param {int} nodeId 343 * @returns {string} html of indent icons 344 */ 345dTree.prototype.indent = function (node, nodeId) { 346 let n, str = ''; 347 if (this.root.id !== node.pid) { 348 for (n = 0; n < this.aIndent.length; n++) { 349 str += '<img src="' + ( (this.aIndent[n] === 1) ? this.icon.line : this.icon.empty ) + '" alt="" />'; 350 } 351 if (node._ls) { 352 this.aIndent.push(0); 353 } else { 354 this.aIndent.push(1); 355 } 356 if (node._hc) { 357 str += '<a href="javascript: ' + this.treeName + '.o(' + nodeId + ');">' + 358 '<img id="j' + this.treeName + nodeId + '" src="' + 359 ( (node._io) ? ((node._ls) ? this.icon.minusBottom : this.icon.minus) : ((node._ls) ? this.icon.plusBottom : this.icon.plus ) ) + 360 '" alt="" /></a>'; 361 } else { 362 str += '<img src="' + ((node._ls) ? this.icon.joinBottom : this.icon.join) + '" alt="" />'; 363 } 364 } 365 return str; 366}; 367 368/** 369 * Checks if a node has any children and if it is the last sibling 370 * 371 * @param {dTreeNode} node 372 */ 373dTree.prototype.setCS = function (node) { 374 let lastId, n; 375 for (n = 0; n < this.aNodes.length; n++) { 376 if (this.aNodes[n].pid === node.id) { 377 node._hc = true; 378 } 379 if (this.aNodes[n].pid === node.pid) { 380 lastId = this.aNodes[n].id; 381 } 382 } 383 if (lastId === node.id) { 384 node._ls = true; 385 } 386}; 387 388/** 389 * Returns the selected node as stored in cookie 390 * 391 * @returns {int} node id 392 */ 393dTree.prototype.getSelected = function () { 394 let sn = this.getCookie('cs' + this.treeName); 395 return (sn) ? parseInt(sn, 10) : null; 396}; 397 398/** 399 * Highlights the selected node 400 * 401 * @param {int} id node id 402 */ 403dTree.prototype.s = function (id) { 404 let eOld, eNew, cn = this.aNodes[id]; 405 if (this.selectedNode !== id) { 406 eNew = jQuery("#s" + this.treeName + id)[0]; 407 if (!eNew) { 408 return; 409 } 410 if (this.selectedNode || this.selectedNode === 0) { 411 eOld = jQuery("#s" + this.treeName + this.selectedNode)[0]; 412 eOld.className = "node"; 413 } 414 eNew.className = "nodeSel"; 415 this.selectedNode = id; 416 if (this.config.useCookies) { 417 this.setCookie('cs' + this.treeName, cn.id); 418 } 419 } 420}; 421 422/** 423 * Toggle Open or close 424 * 425 * @param {int} id node id 426 */ 427dTree.prototype.o = function (id) { 428 let cn = this.aNodes[id]; 429 this.nodeStatus(!cn._io, id, cn._ls); 430 cn._io = !cn._io; 431 if (this.config.useCookies) { 432 this.updateCookie(); 433 } 434 // scroll 435 this.divdisplay('z', false); 436 this.resizescroll("block"); 437}; 438 439/** 440 * Open or close all nodes 441 * 442 * @param {boolean} status if true open 443 */ 444dTree.prototype.oAll = function (status) { 445 for (let n = 0; n < this.aNodes.length; n++) { 446 if (this.aNodes[n]._hc && this.aNodes[n].pid !== this.root.id) { 447 this.nodeStatus(status, n, this.aNodes[n]._ls); 448 this.aNodes[n]._io = status; 449 } 450 } 451 if (this.config.useCookies) { 452 this.updateCookie(); 453 } 454}; 455 456/** 457 * Opens the tree to a specific node 458 * 459 * @param {number} nId node id 460 * @param {boolean} bSelect 461 * @param {boolean} bFirst 462 */ 463dTree.prototype.openTo = function (nId, bSelect, bFirst) { 464 let n, cn; 465 if (!bFirst) { 466 for (n = 0; n < this.aNodes.length; n++) { 467 if (this.aNodes[n].id === nId) { 468 nId = n; 469 break; 470 } 471 } 472 } 473 this.fill(this.aNodes[nId].pid); 474 cn = this.aNodes[nId]; 475 if (cn.pid === this.root.id || !cn._p) { 476 return; 477 } 478 cn._io = 1; 479 if (this.completed && cn._hc) { 480 this.nodeStatus(true, cn._ai, cn._ls); 481 } 482 if (cn._is) { 483 (this.completed) ? this.s(cn._ai) : this._sn = cn._ai; 484 } 485 this.openTo(cn._p._ai, false, true); 486}; 487 488/** 489 * Open the given nodes, if no node status is already stored 490 * 491 * @param {Array|string} nodes array of nodes to open or empty string to open all nodes 492 */ 493dTree.prototype.getOpenTo = function (nodes) { 494 if (nodes === '') { 495 this.openAll(); 496 } else if (!this.config.useCookies || !this.getCookie('co' + this.treeName)) { 497 for (let n = 0; n < nodes.length; n++) { 498 this.openTo(nodes[n], false, true); 499 } 500 } 501}; 502 503/** 504 * Change the status of a node(open or closed) 505 * 506 * @param {boolean} status true if open 507 * @param {int} id node id 508 * @param {boolean} bottom true if bottom node 509 */ 510dTree.prototype.nodeStatus = function (status, id, bottom) { 511 if (status && !this.fill(id)) { 512 return; 513 } 514 let eJoin, eIcon; 515 eJoin = jQuery('#j' + this.treeName + id)[0]; 516 eIcon = jQuery('#i' + this.treeName + id)[0]; 517 eIcon.src = (status) ? this.aNodes[id].iconOpen : this.aNodes[id].icon; 518 eJoin.src = ((status) ? ((bottom) ? this.icon.minusBottom : this.icon.minus) : ((bottom) ? this.icon.plusBottom : this.icon.plus)); 519 jQuery('#d' + this.treeName + id)[0].style.display = (status) ? 'block' : 'none'; 520}; 521 522/** 523 * [Cookie] Clears a cookie 524 */ 525dTree.prototype.clearCookie = function () { 526 let now, yday; 527 now = new Date(); 528 yday = new Date(now.getTime() - 1000 * 60 * 60 * 24); 529 this.setCookie('co' + this.treeName, 'cookieValue', yday); 530 this.setCookie('cs' + this.treeName, 'cookieValue', yday); 531}; 532 533/** 534 * [Cookie] Sets value in a cookie 535 * 536 * @param {string} cookieName 537 * @param {string} cookieValue 538 * @param {boolean|Date} expires 539 */ 540dTree.prototype.setCookie = function (cookieName, cookieValue, expires = false) { 541 document.cookie = 542 encodeURIComponent(cookieName) + '=' + encodeURIComponent(cookieValue) + 543 (expires ? '; expires=' + expires.toUTCString() : '') + 544 '; path=' + DOKU_COOKIE_PARAM.path + 545 '; secure=' + DOKU_COOKIE_PARAM.secure; 546}; 547 548/** 549 * [Cookie] Gets a value from a cookie 550 * 551 * @param cookieName 552 * @returns {string} 553 */ 554dTree.prototype.getCookie = function (cookieName) { 555 let cookieValue = '', pN, posValue, endPos; 556 pN = document.cookie.indexOf(encodeURIComponent(cookieName) + '='); 557 if (pN !== -1) { 558 posValue = pN + (encodeURIComponent(cookieName) + '=').length; 559 endPos = document.cookie.indexOf(';', posValue); 560 if (endPos !== -1) { 561 cookieValue = decodeURIComponent(document.cookie.substring(posValue, endPos)); 562 } 563 else { 564 cookieValue = decodeURIComponent(document.cookie.substring(posValue)); 565 } 566 } 567 return (cookieValue); 568}; 569 570/** 571 * [Cookie] Stores ids of open nodes as a string in cookie 572 */ 573dTree.prototype.updateCookie = function () { 574 let str = '', n; 575 for (n = 0; n < this.aNodes.length; n++) { 576 if (this.aNodes[n]._io && this.aNodes[n].pid !== this.root.id) { 577 if (str) { 578 str += '.'; 579 } 580 str += this.aNodes[n].id; 581 } 582 } 583 this.setCookie('co' + this.treeName, str); 584}; 585 586/** 587 * [Cookie] Checks if a node id is in the cookie 588 * 589 * @param {int} id node id 590 * @return {Boolean} if open true 591 */ 592dTree.prototype.isOpen = function (id) { 593 let n, aOpen = this.getCookie('co' + this.treeName).split('.'); 594 for (n = 0; n < aOpen.length; n++) { 595 if (parseInt(aOpen[n],10) === id) { 596 return true; 597 } 598 } 599 return false; 600}; 601 602/** 603 * Open the node of the current namespace 604 * 605 * @param {int} max 606 */ 607dTree.prototype.openCurNS = function (max) { 608 let r, cn, match, t, i, n, cnsa, cna; 609 let cns = this.pageid; 610 r = new RegExp("\\b" + this.config.sepchar + "\\b", "g"); 611 match = cns.match(r) || -1; 612 if (max > 0 && match.length >= max) { 613 t = cns.split(this.config.sepchar); 614 n = (this.aNodes[0].dokuid === '') ? 0 : this.aNodes[0].dokuid.split(this.config.sepchar).length; 615 t.splice(max + n, t.length); 616 cnsa = t.join(this.config.sepchar); 617 } 618 for (i = 0; i < this.aNodes.length; i++) { 619 cn = this.aNodes[i]; 620 if (cns === cn.dokuid || cns === cn.hns) { 621 this.openTo(cn.id, false, true); 622 this.fajax = false; 623 if (cn.pid >= 0) { 624 jQuery(this.scroll("l", 4, cn.pid, 1)); 625 } 626 break; 627 } 628 if (cnsa === cn.dokuid || cnsa === cn.hns) { 629 cna = cn; 630 this.fajax = true; 631 } 632 } 633 if (cna) { 634 this.openTo(cna.id, false, true); 635 } 636}; 637 638/** 639 * Load children when not available 640 * 641 * @param {int} id node id 642 * @returns {boolean} 643 */ 644dTree.prototype.fill = function (id) { 645 if (id === -1 || this.aNodes[id]._ok) { 646 return true; 647 } 648 let n = id, $eLoad, a, rd, ln, eDiv; 649 if (this.aNodes[n].ajax) { 650 //temporary load indicator 651 $eLoad = jQuery('#l' + this.treeName); 652 if (!$eLoad.length) { 653 $eLoad = IndexmenuUtils.createPicker('l' + this.treeName, 'picker'); 654 } 655 jQuery('#s' + this.treeName + n).parent().append($eLoad); 656 $eLoad 657 .html('Loading ...') 658 .css({width: 'auto'}) 659 .show(); 660 661 //retrieves children 662 this.getAjax(n); 663 return true; 664 } 665 rd = []; 666 while (!this.aNodes[n]._ok) { 667 rd[rd.length] = n; 668 n = this.aNodes[n].pid; 669 } 670 for (ln = rd.length - 1; ln >= 0; ln--) { 671 id = rd[ln]; 672 a = this.aNodes[id]; 673 eDiv = jQuery('#d' + this.treeName + id)[0]; 674 if (!eDiv) { 675 return false; 676 } 677 this.aIndent = []; 678 n = a; 679 while (n.pid >= 0) { 680 if (n._ls) { 681 this.aIndent.unshift(0); 682 } else { 683 this.aIndent.unshift(1); 684 } 685 n = n._p; 686 } 687 eDiv.innerHTML = this.addNode(a); 688 a._ok = true; 689 } 690 return true; 691}; 692 693/** 694 * Open the nodes stored in cookie 695 */ 696dTree.prototype.openCookies = function () { 697 let n, cn, aOpen = this.getCookie('co' + this.treeName).split('.'); 698 for (n = 0; n < aOpen.length; n++) { 699 if (aOpen[n] === "") { 700 break; 701 } 702 cn = this.aNodes[aOpen[n]]; 703 if (!cn._ok) { 704 this.nodeStatus(true, aOpen[n], cn._ls); 705 cn._io = true; 706 } 707 } 708}; 709 710/** 711 * Scrolls the index 712 * 713 * @param {string} where to move to 714 * @param {int} s start 715 * @param {int} n parent node id 716 * @param {int} i 717 */ 718dTree.prototype.scroll = function (where, s, n, i) { 719 if (!this.config.scroll) { 720 return false; 721 } 722 let w, dtree, dtreel, nodeId; 723 dtree = jQuery('#dtree_' + this.treeName)[0]; 724 dtreel = parseInt(dtree.offsetLeft); 725 if (where === "r") { 726 jQuery('#left_' + this.treeName)[0].style.border = "thin inset"; 727 this.scrollRight(dtreel, s); 728 } else { 729 nodeId = jQuery('#s' + this.treeName + n)[0]; 730 if (nodeId == null) { 731 return false; 732 } 733 w = parseInt(dtree.parentNode.offsetWidth - nodeId.offsetWidth - nodeId.offsetLeft); 734 if (this.config.toc) { 735 w = w - 11; 736 } 737 if (dtreel <= w) { 738 return; 739 } 740 this.resizescroll("none"); 741 this.stopscroll(); 742 this.scrollLeft(dtreel, s, w - 3, i); 743 } 744}; 745 746/** 747 * Scroll index to the left 748 * 749 * @param {int} lft current position 750 * @param {int} s start 751 * @param {int} w width 752 * @param {int} i 753 */ 754dTree.prototype.scrollLeft = function (lft, s, w, i) { 755 if (lft < w - i - 10) { 756 this.divdisplay('z', false); 757 this.scrllTmr = 0; 758 return; 759 } 760 var self = this; 761 jQuery('#dtree_' + self.treeName)[0].style.left = lft + "px"; 762 this.scrllTmr = setTimeout(function () { 763 self.scrollLeft(lft - s, s + i, w, i); 764 }, 20); 765}; 766 767/** 768 * Scroll Index back to the right 769 * 770 * @param {int} lft current position 771 * @param {int} s start 772 */ 773dTree.prototype.scrollRight = function (lft, s) { 774 if (lft >= s) { 775 this.divdisplay('left_', false); 776 this.stopscroll(); 777 return; 778 } 779 var self = this; 780 jQuery('#dtree_' + self.treeName)[0].style.left = lft + "px"; 781 if (lft > -15) { 782 s = 1; 783 } 784 this.scrllTmr = setTimeout(function () { 785 self.scrollRight(lft + s, s + 1); 786 }, 20); 787}; 788 789/** 790 * Stop scroll movement 791 */ 792dTree.prototype.stopscroll = function () { 793 jQuery('#left_' + this.treeName)[0].style.border = "none"; 794 clearTimeout(this.scrllTmr); 795 this.scrllTmr = 0; 796}; 797 798/** 799 * Show features and add event handlers for ToC and scroll 800 * 801 * @param {int} n node id 802 */ 803dTree.prototype.show_feat = function (n) { 804 var w, div, id, dtree, dtreel, self, node = jQuery('#s' + this.treeName + n)[0]; 805 self = this; 806 if (this.config.toc && node.className !== "node") { 807 div = jQuery('#t' + this.treeName)[0]; 808 id = (this.aNodes[n].hns) ? this.aNodes[n].hns : this.aNodes[n].dokuid; 809 div.onmousedown = function () { 810 IndexmenuContextmenu.createTocMenu('call=indexmenu&req=toc&id=' + decodeURIComponent(id), 'picker_' + self.treeName, 't' + self.treeName); 811 }; 812 node.parentNode.appendChild(div); 813 if (div.style.display === "none") { 814 div.style.display = "inline"; 815 } 816 } 817 if (this.config.scroll) { 818 div = jQuery('#z' + this.treeName)[0]; 819 div.onmouseover = function () { 820 div.style.border = "none"; 821 self.scroll("l", 1, n, 0); 822 }; 823 div.onmousedown = function () { 824 div.style.border = "thin inset"; 825 self.scroll("l", 4, n, 1); 826 }; 827 div.onmouseout = function () { 828 div.style.border = "none"; 829 self.stopscroll(); 830 }; 831 div.onmouseup = div.onmouseover; 832 dtree = jQuery('#dtree_' + this.treeName)[0]; 833 dtreel = parseInt(dtree.offsetLeft); 834 w = parseInt(dtree.parentNode.offsetWidth - node.offsetWidth - node.offsetLeft + 1); 835 if (dtreel > w) { 836 div.style.display = "none"; 837 div.style.top = node.offsetTop + "px"; 838 div.style.left = parseInt(node.offsetLeft + node.offsetWidth + w - 12) + "px"; 839 div.style.display = "block"; 840 } 841 } 842}; 843 844/** 845 * Show and resize the scroll-back button relatively to size of tree 846 * 847 * @param {string} status 'block' or 'none' 848 */ 849dTree.prototype.resizescroll = function (status) { 850 let dtree, w, h, left = jQuery('#left_' + this.treeName)[0]; 851 if (!left) { 852 return; 853 } 854 if (left.style.display === status) { 855 dtree = jQuery('#dtree_' + this.treeName)[0]; 856 w = Math.trunc(dtree.offsetHeight / 3); 857 h = Math.trunc(w / 50) * 50; 858 if (h < 50) { 859 h = 50; 860 } 861 left.style.height = h + "px"; 862 left.style.top = w + "px"; 863 if (status === "none") { 864 left.style.display = "block"; 865 } 866 } 867}; 868 869/** 870 * Toggle Open or close 871 * 872 * @param {int} n node id 873 */ 874dTree.prototype.getAjax = function (n) { 875 var node, selft = this; 876 let req, curns; 877 node = selft.aNodes[n]; 878 879 req = 'req=index&idx=' + node.dokuid + decodeURIComponent(this.config.jsajax); 880 881 curns = this.pageid.substring(0, this.pageid.lastIndexOf(this.config.sepchar)); 882 if (this.fajax) { 883 req += '&nss=' + curns + '&max=1'; 884 } 885 886 var onCompletion = function (data) { 887 var i, ajxnodes, ajxnode, plus; 888 plus = selft.aNodes.length - 1; 889 eval(data); 890 if (!ajxnodes instanceof Array || ajxnodes.length < 1) { 891 ajxnodes = [ 892 ['', 1, 0, '', 0, 1, 0] 893 ]; 894 } 895 node.ajax = false; 896 for (i = 0; i < ajxnodes.length; i++) { 897 ajxnode = ajxnodes[i]; 898 ajxnode[2] = (ajxnode[2] == 0) ? node.id : ajxnode[2] + plus; 899 ajxnode[1] += plus; 900 selft.add(ajxnode[0], ajxnode[1], ajxnode[2], ajxnode[3], ajxnode[4], ajxnode[5], ajxnode[6]); 901 } 902 if (selft.fajax) { 903 selft.fajax = false; 904 selft.openCurNS(0); 905 } else { 906 selft.openTo(node.id, false, true); 907 } 908 jQuery('#l' + selft.treeName).hide(); 909 }; 910 911 jQuery.post( 912 DOKU_BASE + 'lib/exe/ajax.php', 913 'call=indexmenu&'+req, 914 onCompletion, 915 'html' 916 ); 917}; 918 919/** 920 * Load custom css for theme 921 */ 922dTree.prototype.loadCss = function () { 923 let oLink = document.createElement("link"); 924 oLink.href = this.config.plugbase + '/images/' + this.config.theme + '/style.css'; 925 oLink.rel = "stylesheet"; 926 oLink.type = "text/css"; 927 document.getElementsByTagName('head')[0].appendChild(oLink); 928}; 929 930/** 931 * Show the contextmenu 932 * 933 * @param {int} n node id 934 * @param {Event} e event 935 * @returns {boolean} 936 */ 937dTree.prototype.contextmenu = function (n, e) { 938 let type, node, cdtree, $rmenu; 939 cdtree = jQuery("#cdtree_" + this.treeName)[0]; 940 $rmenu = jQuery('#r' + this.treeName)[0]; 941 if (!$rmenu) { 942 return true; 943 } 944 IndexmenuContextmenu.mouseposition($rmenu, e); 945 let cmenu = window.indexmenu_contextmenu; 946 node = this.aNodes[n]; 947 $rmenu.innerHTML = '<div class="indexmenu_rmenuhead" title="' + node.name + '">' + node.name + "</div>"; 948 $rmenu.appendChild(document.createElement('ul')); 949 type = (node.isdir || node._hc) ? 'ns' : 'pg'; 950 IndexmenuContextmenu.arrconcat(cmenu['all'][type], this, n); 951 if (node.hns) { 952 IndexmenuContextmenu.arrconcat(cmenu[type], this, n); 953 type = 'pg'; 954 IndexmenuContextmenu.arrconcat(cmenu['all'][type], this, n); 955 } 956 IndexmenuContextmenu.arrconcat(cmenu[type], this, n); 957 $rmenu.style.display = 'inline'; 958 return false; 959}; 960 961/** 962 * Show/hide object with given id of current indexmenu 963 * 964 * @param {string} objName name of object, which is combined with the unique id of the indexmenu 965 * @param {boolean} visible true: visible, false: hide. 966 */ 967dTree.prototype.divdisplay = function (objName, visible) { 968 let o = jQuery('#' + objName + this.treeName)[0]; 969 if (!o) { 970 return; 971 } 972 (visible) ? o.style.display = 'inline' : o.style.display = 'none'; 973}; 974 975/** 976 * Initialise the dTree index 977 * 978 * @param {int} hasstyle has an additional css style sheet 979 * @param {int} nocookies use no cookies 980 * @param {string} opennodes string of initial opened nodes 981 * @param {int} nav is navbar option set 982 * @param {int} max max level of available nodes (deeper levels are included with js) 983 * @param {int} nomenu show no menu 984 */ 985dTree.prototype.init = function (hasstyle, nocookies, opennodes, nav, max, nomenu) { 986 if (hasstyle) { 987 this.loadCss(); 988 } 989 if (!nocookies) { 990 this.openCookies(); 991 } 992 //open given nodes 993 if (opennodes) { 994 this.getOpenTo(opennodes.split(" ")); 995 } 996 if (nav) { 997 this.openCurNS(max); 998 } 999 //create contextmenu 1000 if (!nomenu) { 1001 var self = this; 1002 IndexmenuUtils.createPicker('r' + this.treeName, 'indexmenu_rmenu ' + this.config.theme); 1003 jQuery('#r' + this.treeName)[0].oncontextmenu = IndexmenuContextmenu.stopevt; 1004 jQuery(document).on("click",function() { 1005 self.divdisplay('r', false); 1006 }); 1007 } 1008}; 1009