You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

513 lines
23 KiB

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