mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	replace note tree context menu with bootstrap dropdown, #203
This commit is contained in:
		| @@ -4,7 +4,6 @@ import linkService from './link.js'; | |||||||
| import messagingService from './messaging.js'; | import messagingService from './messaging.js'; | ||||||
| import noteDetailService from './note_detail.js'; | import noteDetailService from './note_detail.js'; | ||||||
| import protectedSessionHolder from './protected_session_holder.js'; | import protectedSessionHolder from './protected_session_holder.js'; | ||||||
| import treeChangesService from './branches.js'; |  | ||||||
| import treeUtils from './tree_utils.js'; | import treeUtils from './tree_utils.js'; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import server from './server.js'; | import server from './server.js'; | ||||||
| @@ -16,6 +15,7 @@ import Branch from '../entities/branch.js'; | |||||||
| import NoteShort from '../entities/note_short.js'; | import NoteShort from '../entities/note_short.js'; | ||||||
|  |  | ||||||
| const $tree = $("#tree"); | const $tree = $("#tree"); | ||||||
|  | const $treeContextMenu = $("#tree-context-menu"); | ||||||
| const $createTopLevelNoteButton = $("#create-top-level-note-button"); | const $createTopLevelNoteButton = $("#create-top-level-note-button"); | ||||||
| const $collapseTreeButton = $("#collapse-tree-button"); | const $collapseTreeButton = $("#collapse-tree-button"); | ||||||
| const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button"); | const $scrollToCurrentNoteButton = $("#scroll-to-current-note-button"); | ||||||
| @@ -378,7 +378,48 @@ function initFancyTree(tree) { | |||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     $tree.contextmenu(treeContextMenuService.contextMenuOptions); |     $treeContextMenu.on('click', '.dropdown-item', function(e) { | ||||||
|  |         const cmd = $(e.target).prop("data-cmd"); | ||||||
|  |  | ||||||
|  |         treeContextMenuService.selectContextMenuItem(e, cmd); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     async function openContextMenu(e) { | ||||||
|  |         $treeContextMenu.empty(); | ||||||
|  |  | ||||||
|  |         const contextMenuItems = await treeContextMenuService.getContextMenuItems(e); | ||||||
|  |  | ||||||
|  |         for (const item of contextMenuItems) { | ||||||
|  |             if (item.title === '----') { | ||||||
|  |                 $treeContextMenu.append($("<div>").addClass("dropdown-divider")); | ||||||
|  |             } else { | ||||||
|  |                 const $item = $("<a>") | ||||||
|  |                     .addClass("dropdown-item") | ||||||
|  |                     .prop("data-cmd", item.cmd) | ||||||
|  |                     .append(item.title); | ||||||
|  |  | ||||||
|  |                 if (item.enabled !== undefined && !item.enabled) { | ||||||
|  |                     $item.addClass("disabled"); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 $treeContextMenu.append($item); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $treeContextMenu.css({ | ||||||
|  |             display: "block", | ||||||
|  |             top: e.pageY - 10, | ||||||
|  |             left: e.pageX - 40 | ||||||
|  |         }).addClass("show"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $(document).click(() => $(".context-menu").hide()); | ||||||
|  |  | ||||||
|  |     $tree.on('contextmenu', '.fancytree-node', function(e) { | ||||||
|  |         openContextMenu(e); | ||||||
|  |  | ||||||
|  |         return false; // blocks default browser right click menu | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| function getTree() { | function getTree() { | ||||||
|   | |||||||
| @@ -76,135 +76,137 @@ function cut(nodes) { | |||||||
|     infoService.showMessage("Note(s) have been cut into clipboard."); |     infoService.showMessage("Note(s) have been cut into clipboard."); | ||||||
| } | } | ||||||
|  |  | ||||||
| const contextMenuOptions = { | const contextMenuItems = [ | ||||||
|     delegate: "span.fancytree-title", |     {title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"}, | ||||||
|     autoFocus: true, |     {title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"}, | ||||||
|     menu: [ |     {title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"}, | ||||||
|         {title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"}, |     {title: "----"}, | ||||||
|         {title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"}, |     {title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"}, | ||||||
|         {title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"}, |     {title: "----"}, | ||||||
|         {title: "----"}, |     {title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"}, | ||||||
|         {title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"}, |     {title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"}, | ||||||
|         {title: "----"}, |     {title: "----"}, | ||||||
|         {title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"}, |     {title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"}, | ||||||
|         {title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"}, |     {title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"}, | ||||||
|         {title: "----"}, |     {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, | ||||||
|         {title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"}, |     {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, | ||||||
|         {title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"}, |     {title: "----"}, | ||||||
|         {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"}, |     {title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne"}, | ||||||
|         {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"}, |     {title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "ui-icon-arrowthick-1-sw"}, | ||||||
|         {title: "----"}, |     {title: "----"}, | ||||||
|         {title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne", children: [ |     {title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "ui-icon-minus"}, | ||||||
|             {title: "Native Tar", cmd: "exportSubtreeToTar"}, |     {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, | ||||||
|             {title: "OPML", cmd: "exportSubtreeToOpml"}, |     {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} | ||||||
|             {title: "Markdown", cmd: "exportSubtreeToMarkdown"} | ]; | ||||||
|         ]}, |  | ||||||
|         {title: "Import into note (tar, opml, md, enex)", cmd: "importIntoNote", uiIcon: "ui-icon-arrowthick-1-sw"}, |  | ||||||
|         {title: "----"}, |  | ||||||
|         {title: "Collapse subtree <kbd>Alt+-</kbd>", cmd: "collapseSubtree", uiIcon: "ui-icon-minus"}, |  | ||||||
|         {title: "Force note sync", cmd: "forceNoteSync", uiIcon: "ui-icon-refresh"}, |  | ||||||
|         {title: "Sort alphabetically <kbd>Alt+S</kbd>", cmd: "sortAlphabetically", uiIcon: " ui-icon-arrowthick-2-n-s"} |  | ||||||
|     ], |  | ||||||
|     beforeOpen: async (event, ui) => { |  | ||||||
|         const node = $.ui.fancytree.getNode(ui.target); |  | ||||||
|         const branch = await treeCache.getBranch(node.data.branchId); |  | ||||||
|         const note = await treeCache.getNote(node.data.noteId); |  | ||||||
|         const parentNote = await treeCache.getNote(branch.parentNoteId); |  | ||||||
|         const isNotRoot = note.noteId !== 'root'; |  | ||||||
|  |  | ||||||
|         // Modify menu entries depending on node status | function enableItem(cmd, enabled) { | ||||||
|         $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search'); |     const item = contextMenuItems.find(item => item.cmd === cmd); | ||||||
|         $tree.contextmenu("enableEntry", "insertChildNote", note.type !== 'search'); |      | ||||||
|         $tree.contextmenu("enableEntry", "delete", isNotRoot && parentNote.type !== 'search'); |     if (!item) { | ||||||
|         $tree.contextmenu("enableEntry", "copy", isNotRoot); |         throw new Error(`Command ${cmd} has not been found!`); | ||||||
|         $tree.contextmenu("enableEntry", "cut", isNotRoot); |  | ||||||
|         $tree.contextmenu("enableEntry", "pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "pasteInto", clipboardIds.length > 0 && note.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "importIntoNote", note.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "exportSubtree", note.type !== 'search'); |  | ||||||
|         $tree.contextmenu("enableEntry", "editBranchPrefix", isNotRoot && parentNote.type !== 'search'); |  | ||||||
|  |  | ||||||
|         // Activate node on right-click |  | ||||||
|         node.setActive(); |  | ||||||
|  |  | ||||||
|         // right click resets selection to just this node |  | ||||||
|         // this is important when e.g. you right click on a note while having different note active |  | ||||||
|         // and then click on delete - obviously you want to delete only that one right-clicked |  | ||||||
|         node.setSelected(true); |  | ||||||
|         treeService.clearSelectedNodes(); |  | ||||||
|  |  | ||||||
|         // Disable tree keyboard handling |  | ||||||
|         ui.menu.prevKeyboard = node.tree.options.keyboard; |  | ||||||
|         node.tree.options.keyboard = false; |  | ||||||
|     }, |  | ||||||
|     close: (event, ui) => {}, |  | ||||||
|     select: (event, ui) => { |  | ||||||
|         const node = $.ui.fancytree.getNode(ui.target); |  | ||||||
|  |  | ||||||
|         if (ui.cmd === "insertNoteHere") { |  | ||||||
|             const parentNoteId = node.data.parentNoteId; |  | ||||||
|             const isProtected = treeUtils.getParentProtectedStatus(node); |  | ||||||
|  |  | ||||||
|             treeService.createNote(node, parentNoteId, 'after', isProtected); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "insertChildNote") { |  | ||||||
|             treeService.createNote(node, node.data.noteId, 'into'); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "editBranchPrefix") { |  | ||||||
|             branchPrefixDialog.showDialog(node); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "protectSubtree") { |  | ||||||
|             protectedSessionService.protectSubtree(node.data.noteId, true); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "unprotectSubtree") { |  | ||||||
|             protectedSessionService.protectSubtree(node.data.noteId, false); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "copy") { |  | ||||||
|             copy(treeService.getSelectedNodes()); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "cut") { |  | ||||||
|             cut(treeService.getSelectedNodes()); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "pasteAfter") { |  | ||||||
|             pasteAfter(node); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "pasteInto") { |  | ||||||
|             pasteInto(node); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "delete") { |  | ||||||
|             treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "exportSubtreeToTar") { |  | ||||||
|             exportService.exportSubtree(node.data.branchId, 'tar'); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "exportSubtreeToOpml") { |  | ||||||
|             exportService.exportSubtree(node.data.branchId, 'opml'); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "exportSubtreeToMarkdown") { |  | ||||||
|             exportService.exportSubtree(node.data.branchId, 'markdown'); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "importIntoNote") { |  | ||||||
|             exportService.importIntoNote(node.data.noteId); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "collapseSubtree") { |  | ||||||
|             treeService.collapseTree(node); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "forceNoteSync") { |  | ||||||
|             syncService.forceNoteSync(node.data.noteId); |  | ||||||
|         } |  | ||||||
|         else if (ui.cmd === "sortAlphabetically") { |  | ||||||
|             treeService.sortAlphabetically(node.data.noteId); |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             messagingService.logError("Unknown command: " + ui.cmd); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| }; |      | ||||||
|  |     item.enabled = enabled; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | async function getContextMenuItems(event) { | ||||||
|  |     const node = $.ui.fancytree.getNode(event); | ||||||
|  |     const branch = await treeCache.getBranch(node.data.branchId); | ||||||
|  |     const note = await treeCache.getNote(node.data.noteId); | ||||||
|  |     const parentNote = await treeCache.getNote(branch.parentNoteId); | ||||||
|  |     const isNotRoot = note.noteId !== 'root'; | ||||||
|  |  | ||||||
|  |     // Modify menu entries depending on node status | ||||||
|  |     enableItem("insertNoteHere", isNotRoot && parentNote.type !== 'search'); | ||||||
|  |     enableItem("insertChildNote", note.type !== 'search'); | ||||||
|  |     enableItem("delete", isNotRoot && parentNote.type !== 'search'); | ||||||
|  |     enableItem("copy", isNotRoot); | ||||||
|  |     enableItem("cut", isNotRoot); | ||||||
|  |     enableItem("pasteAfter", clipboardIds.length > 0 && isNotRoot && parentNote.type !== 'search'); | ||||||
|  |     enableItem("pasteInto", clipboardIds.length > 0 && note.type !== 'search'); | ||||||
|  |     enableItem("importIntoNote", note.type !== 'search'); | ||||||
|  |     enableItem("exportSubtree", note.type !== 'search'); | ||||||
|  |     enableItem("editBranchPrefix", isNotRoot && parentNote.type !== 'search'); | ||||||
|  |  | ||||||
|  |     // Activate node on right-click | ||||||
|  |     node.setActive(); | ||||||
|  |  | ||||||
|  |     // right click resets selection to just this node | ||||||
|  |     // this is important when e.g. you right click on a note while having different note active | ||||||
|  |     // and then click on delete - obviously you want to delete only that one right-clicked | ||||||
|  |     node.setSelected(true); | ||||||
|  |     treeService.clearSelectedNodes(); | ||||||
|  |  | ||||||
|  |     return contextMenuItems; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function selectContextMenuItem(event, cmd) { | ||||||
|  |     const node = $.ui.fancytree.getNode(event); | ||||||
|  |  | ||||||
|  |     if (cmd === "insertNoteHere") { | ||||||
|  |         const parentNoteId = node.data.parentNoteId; | ||||||
|  |         const isProtected = treeUtils.getParentProtectedStatus(node); | ||||||
|  |  | ||||||
|  |         treeService.createNote(node, parentNoteId, 'after', isProtected); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "insertChildNote") { | ||||||
|  |         treeService.createNote(node, node.data.noteId, 'into'); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "editBranchPrefix") { | ||||||
|  |         branchPrefixDialog.showDialog(node); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "protectSubtree") { | ||||||
|  |         protectedSessionService.protectSubtree(node.data.noteId, true); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "unprotectSubtree") { | ||||||
|  |         protectedSessionService.protectSubtree(node.data.noteId, false); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "copy") { | ||||||
|  |         copy(treeService.getSelectedNodes()); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "cut") { | ||||||
|  |         cut(treeService.getSelectedNodes()); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "pasteAfter") { | ||||||
|  |         pasteAfter(node); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "pasteInto") { | ||||||
|  |         pasteInto(node); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "delete") { | ||||||
|  |         treeChangesService.deleteNodes(treeService.getSelectedNodes(true)); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "exportSubtreeToTar") { | ||||||
|  |         exportService.exportSubtree(node.data.branchId, 'tar'); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "exportSubtreeToOpml") { | ||||||
|  |         exportService.exportSubtree(node.data.branchId, 'opml'); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "exportSubtreeToMarkdown") { | ||||||
|  |         exportService.exportSubtree(node.data.branchId, 'markdown'); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "importIntoNote") { | ||||||
|  |         exportService.importIntoNote(node.data.noteId); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "collapseSubtree") { | ||||||
|  |         treeService.collapseTree(node); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "forceNoteSync") { | ||||||
|  |         syncService.forceNoteSync(node.data.noteId); | ||||||
|  |     } | ||||||
|  |     else if (cmd === "sortAlphabetically") { | ||||||
|  |         treeService.sortAlphabetically(node.data.noteId); | ||||||
|  |     } | ||||||
|  |     else { | ||||||
|  |         messagingService.logError("Unknown command: " + cmd); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|     pasteAfter, |     pasteAfter, | ||||||
|     pasteInto, |     pasteInto, | ||||||
|     cut, |     cut, | ||||||
|     copy, |     copy, | ||||||
|     contextMenuOptions |     getContextMenuItems, | ||||||
|  |     selectContextMenuItem | ||||||
| }; | }; | ||||||
| @@ -1,631 +0,0 @@ | |||||||
| /******************************************************************************* |  | ||||||
|  * jquery.ui-contextmenu.js plugin. |  | ||||||
|  * |  | ||||||
|  * jQuery plugin that provides a context menu (based on the jQueryUI menu widget). |  | ||||||
|  * |  | ||||||
|  * @see https://github.com/mar10/jquery-ui-contextmenu |  | ||||||
|  * |  | ||||||
|  * Copyright (c) 2013-2017, Martin Wendt (http://wwWendt.de). Licensed MIT. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| (function( factory ) { |  | ||||||
| 	"use strict"; |  | ||||||
| 	if ( typeof define === "function" && define.amd ) { |  | ||||||
| 		// AMD. Register as an anonymous module. |  | ||||||
| 		define([ "jquery", "jquery-ui/ui/widgets/menu" ], factory ); |  | ||||||
| 	} else { |  | ||||||
| 		// Browser globals |  | ||||||
| 		factory( jQuery ); |  | ||||||
| 	} |  | ||||||
| }(function( $ ) { |  | ||||||
|  |  | ||||||
| "use strict"; |  | ||||||
|  |  | ||||||
| var supportSelectstart = "onselectstart" in document.createElement("div"), |  | ||||||
| 	match = $.ui.menu.version.match(/^(\d)\.(\d+)/), |  | ||||||
| 	uiVersion = { |  | ||||||
| 		major: parseInt(match[1], 10), |  | ||||||
| 		minor: parseInt(match[2], 10) |  | ||||||
| 	}, |  | ||||||
| 	isLTE110 = ( uiVersion.major < 2 && uiVersion.minor <= 10 ), |  | ||||||
| 	isLTE111 = ( uiVersion.major < 2 && uiVersion.minor <= 11 ); |  | ||||||
|  |  | ||||||
| $.widget("moogle.contextmenu", { |  | ||||||
| 	version: "@VERSION", |  | ||||||
| 	options: { |  | ||||||
| 		addClass: "ui-contextmenu",  // Add this class to the outer <ul> |  | ||||||
| 		closeOnWindowBlur: true,     // Close menu when window loses focus |  | ||||||
| 		autoFocus: false,     // Set keyboard focus to first entry on open |  | ||||||
| 		autoTrigger: true,    // open menu on browser's `contextmenu` event |  | ||||||
| 		delegate: null,       // selector |  | ||||||
| 		hide: { effect: "fadeOut", duration: "fast" }, |  | ||||||
| 		ignoreParentSelect: true, // Don't trigger 'select' for sub-menu parents |  | ||||||
| 		menu: null,           // selector or jQuery pointing to <UL>, or a definition hash |  | ||||||
| 		position: null,       // popup positon |  | ||||||
| 		preventContextMenuForPopup: false, // prevent opening the browser's system |  | ||||||
| 										   // context menu on menu entries |  | ||||||
| 		preventSelect: false, // disable text selection of target |  | ||||||
| 		show: { effect: "slideDown", duration: "fast" }, |  | ||||||
| 		taphold: false,       // open menu on taphold events (requires external plugins) |  | ||||||
| 		uiMenuOptions: {},	  // Additional options, used when UI Menu is created |  | ||||||
| 		// Events: |  | ||||||
| 		beforeOpen: $.noop,   // menu about to open; return `false` to prevent opening |  | ||||||
| 		blur: $.noop,         // menu option lost focus |  | ||||||
| 		close: $.noop,        // menu was closed |  | ||||||
| 		create: $.noop,       // menu was initialized |  | ||||||
| 		createMenu: $.noop,   // menu was initialized (original UI Menu) |  | ||||||
| 		focus: $.noop,        // menu option got focus |  | ||||||
| 		open: $.noop,         // menu was opened |  | ||||||
| 		select: $.noop        // menu option was selected; return `false` to prevent closing |  | ||||||
| 	}, |  | ||||||
| 	/** Constructor */ |  | ||||||
| 	_create: function() { |  | ||||||
| 		var cssText, eventNames, targetId, |  | ||||||
| 			opts = this.options; |  | ||||||
|  |  | ||||||
| 		this.$headStyle = null; |  | ||||||
| 		this.$menu = null; |  | ||||||
| 		this.menuIsTemp = false; |  | ||||||
| 		this.currentTarget = null; |  | ||||||
| 		this.extraData = {}; |  | ||||||
| 		this.previousFocus = null; |  | ||||||
|  |  | ||||||
| 		if (opts.delegate == null) { |  | ||||||
| 			$.error("ui-contextmenu: Missing required option `delegate`."); |  | ||||||
| 		} |  | ||||||
| 		if (opts.preventSelect) { |  | ||||||
| 			// Create a global style for all potential menu targets |  | ||||||
| 			// If the contextmenu was bound to `document`, we apply the |  | ||||||
| 			// selector relative to the <body> tag instead |  | ||||||
| 			targetId = ($(this.element).is(document) ? $("body") |  | ||||||
| 				: this.element).uniqueId().attr("id"); |  | ||||||
| 			cssText = "#" + targetId + " " + opts.delegate + " { " + |  | ||||||
| 					"-webkit-user-select: none; " + |  | ||||||
| 					"-khtml-user-select: none; " + |  | ||||||
| 					"-moz-user-select: none; " + |  | ||||||
| 					"-ms-user-select: none; " + |  | ||||||
| 					"user-select: none; " + |  | ||||||
| 					"}"; |  | ||||||
| 			this.$headStyle = $("<style class='moogle-contextmenu-style' />") |  | ||||||
| 				.prop("type", "text/css") |  | ||||||
| 				.appendTo("head"); |  | ||||||
|  |  | ||||||
| 			try { |  | ||||||
| 				this.$headStyle.html(cssText); |  | ||||||
| 			} catch ( e ) { |  | ||||||
| 				// issue #47: fix for IE 6-8 |  | ||||||
| 				this.$headStyle[0].styleSheet.cssText = cssText; |  | ||||||
| 			} |  | ||||||
| 			// TODO: the selectstart is not supported by FF? |  | ||||||
| 			if (supportSelectstart) { |  | ||||||
| 				this.element.on("selectstart" + this.eventNamespace, opts.delegate, |  | ||||||
| 									  function(event) { |  | ||||||
| 					event.preventDefault(); |  | ||||||
| 				}); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		this._createUiMenu(opts.menu); |  | ||||||
|  |  | ||||||
| 		eventNames = "contextmenu" + this.eventNamespace; |  | ||||||
| 		if (opts.taphold) { |  | ||||||
| 			eventNames += " taphold" + this.eventNamespace; |  | ||||||
| 		} |  | ||||||
| 		this.element.on(eventNames, opts.delegate, $.proxy(this._openMenu, this)); |  | ||||||
| 	}, |  | ||||||
| 	/** Destructor, called on $().contextmenu("destroy"). */ |  | ||||||
| 	_destroy: function() { |  | ||||||
| 		this.element.off(this.eventNamespace); |  | ||||||
|  |  | ||||||
| 		this._createUiMenu(null); |  | ||||||
|  |  | ||||||
| 		if (this.$headStyle) { |  | ||||||
| 			this.$headStyle.remove(); |  | ||||||
| 			this.$headStyle = null; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	/** (Re)Create jQuery UI Menu. */ |  | ||||||
| 	_createUiMenu: function(menuDef) { |  | ||||||
| 		var ct, ed, |  | ||||||
| 			opts = this.options; |  | ||||||
|  |  | ||||||
| 		// Remove temporary <ul> if any |  | ||||||
| 		if (this.isOpen()) { |  | ||||||
| 			// #58: 'replaceMenu' in beforeOpen causing select: to lose ui.target |  | ||||||
| 			ct = this.currentTarget; |  | ||||||
| 			ed = this.extraData; |  | ||||||
| 			// close without animation, to force async mode |  | ||||||
| 			this._closeMenu(true); |  | ||||||
| 			this.currentTarget = ct; |  | ||||||
| 			this.extraData = ed; |  | ||||||
| 		} |  | ||||||
| 		if (this.menuIsTemp) { |  | ||||||
| 			this.$menu.remove(); // this will also destroy ui.menu |  | ||||||
| 		} else if (this.$menu) { |  | ||||||
| 			this.$menu |  | ||||||
| 				.menu("destroy") |  | ||||||
| 				.removeClass(this.options.addClass) |  | ||||||
| 				.hide(); |  | ||||||
| 		} |  | ||||||
| 		this.$menu = null; |  | ||||||
| 		this.menuIsTemp = false; |  | ||||||
| 		// If a menu definition array was passed, create a hidden <ul> |  | ||||||
| 		// and generate the structure now |  | ||||||
| 		if ( !menuDef ) { |  | ||||||
| 			return; |  | ||||||
| 		} else if ($.isArray(menuDef)) { |  | ||||||
| 			this.$menu = $.moogle.contextmenu.createMenuMarkup(menuDef); |  | ||||||
| 			this.menuIsTemp = true; |  | ||||||
| 		}else if ( typeof menuDef === "string" ) { |  | ||||||
| 			this.$menu = $(menuDef); |  | ||||||
| 		} else { |  | ||||||
| 			this.$menu = menuDef; |  | ||||||
| 		} |  | ||||||
| 		// Create - but hide - the jQuery UI Menu widget |  | ||||||
| 		this.$menu |  | ||||||
| 			.hide() |  | ||||||
| 			.addClass(opts.addClass) |  | ||||||
| 			// Create a menu instance that delegates events to our widget |  | ||||||
| 			.menu($.extend(true, {}, opts.uiMenuOptions, { |  | ||||||
| 				items: "> :not(.ui-widget-header)", |  | ||||||
| 				blur: $.proxy(opts.blur, this), |  | ||||||
| 				create: $.proxy(opts.createMenu, this), |  | ||||||
| 				focus: $.proxy(opts.focus, this), |  | ||||||
| 				select: $.proxy(function(event, ui) { |  | ||||||
| 					// User selected a menu entry |  | ||||||
| 					var retval, |  | ||||||
| 						isParent = $.moogle.contextmenu.isMenu(ui.item), |  | ||||||
| 						actionHandler = ui.item.data("actionHandler"); |  | ||||||
|  |  | ||||||
| 					ui.cmd = ui.item.attr("data-command"); |  | ||||||
| 					ui.target = $(this.currentTarget); |  | ||||||
| 					ui.extraData = this.extraData; |  | ||||||
| 					// ignore clicks, if they only open a sub-menu |  | ||||||
| 					if ( !isParent || !opts.ignoreParentSelect) { |  | ||||||
| 						retval = this._trigger.call(this, "select", event, ui); |  | ||||||
| 						if ( actionHandler ) { |  | ||||||
| 							retval = actionHandler.call(this, event, ui); |  | ||||||
| 						} |  | ||||||
| 						if ( retval !== false ) { |  | ||||||
| 							this._closeMenu.call(this); |  | ||||||
| 						} |  | ||||||
| 						event.preventDefault(); |  | ||||||
| 					} |  | ||||||
| 				}, this) |  | ||||||
| 			})); |  | ||||||
| 	}, |  | ||||||
| 	/** Open popup (called on 'contextmenu' event). */ |  | ||||||
| 	_openMenu: function(event, recursive) { |  | ||||||
| 		var res, promise, ui, |  | ||||||
| 			opts = this.options, |  | ||||||
| 			posOption = opts.position, |  | ||||||
| 			self = this, |  | ||||||
| 			manualTrigger = !!event.isTrigger; |  | ||||||
|  |  | ||||||
| 		if ( !opts.autoTrigger && !manualTrigger ) { |  | ||||||
| 			// ignore browser's `contextmenu` events |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 		// Prevent browser from opening the system context menu |  | ||||||
| 		event.preventDefault(); |  | ||||||
|  |  | ||||||
| 		this.currentTarget = event.target; |  | ||||||
| 		this.extraData = event._extraData || {}; |  | ||||||
|  |  | ||||||
| 		ui = { menu: this.$menu, target: $(this.currentTarget), extraData: this.extraData, |  | ||||||
| 			   originalEvent: event, result: null }; |  | ||||||
|  |  | ||||||
| 		if ( !recursive ) { |  | ||||||
| 			res = this._trigger("beforeOpen", event, ui); |  | ||||||
| 			promise = (ui.result && $.isFunction(ui.result.promise)) ? ui.result : null; |  | ||||||
| 			ui.result = null; |  | ||||||
| 			if ( res === false ) { |  | ||||||
| 				this.currentTarget = null; |  | ||||||
| 				return false; |  | ||||||
| 			} else if ( promise ) { |  | ||||||
| 				// Handler returned a Deferred or Promise. Delay menu open until |  | ||||||
| 				// the promise is resolved |  | ||||||
| 				promise.done(function() { |  | ||||||
| 					self._openMenu(event, true); |  | ||||||
| 				}); |  | ||||||
| 				this.currentTarget = null; |  | ||||||
| 				return false; |  | ||||||
| 			} |  | ||||||
| 			ui.menu = this.$menu; // Might have changed in beforeOpen |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Register global event handlers that close the dropdown-menu |  | ||||||
| 		$(document).on("keydown" + this.eventNamespace, function(event) { |  | ||||||
| 			if ( event.which === $.ui.keyCode.ESCAPE ) { |  | ||||||
| 				self._closeMenu(); |  | ||||||
| 			} |  | ||||||
| 		}).on("mousedown" + this.eventNamespace + " touchstart" + this.eventNamespace, |  | ||||||
| 				function(event) { |  | ||||||
| 			// Close menu when clicked outside menu |  | ||||||
| 			if ( !$(event.target).closest(".ui-menu-item").length ) { |  | ||||||
| 				self._closeMenu(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 		$(window).on("blur" + this.eventNamespace, function(event) { |  | ||||||
| 			if ( opts.closeOnWindowBlur ) { |  | ||||||
| 				self._closeMenu(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		// required for custom positioning (issue #18 and #13). |  | ||||||
| 		if ($.isFunction(posOption)) { |  | ||||||
| 			posOption = posOption(event, ui); |  | ||||||
| 		} |  | ||||||
| 		posOption = $.extend({ |  | ||||||
| 			my: "left top", |  | ||||||
| 			at: "left bottom", |  | ||||||
| 			// if called by 'open' method, event does not have pageX/Y |  | ||||||
| 			of: (event.pageX === undefined) ? event.target : event, |  | ||||||
| 			collision: "fit" |  | ||||||
| 		}, posOption); |  | ||||||
|  |  | ||||||
| 		// Update entry statuses from callbacks |  | ||||||
| 		this._updateEntries(this.$menu); |  | ||||||
|  |  | ||||||
| 		// Finally display the popup |  | ||||||
| 		this.$menu |  | ||||||
| 			.show() // required to fix positioning error |  | ||||||
| 			.css({ |  | ||||||
| 				position: "absolute", |  | ||||||
| 				left: 0, |  | ||||||
| 				top: 0 |  | ||||||
| 			}).position(posOption) |  | ||||||
| 			.hide(); // hide again, so we can apply nice effects |  | ||||||
|  |  | ||||||
| 		if ( opts.preventContextMenuForPopup ) { |  | ||||||
| 			this.$menu.on("contextmenu" + this.eventNamespace, function(event) { |  | ||||||
| 				event.preventDefault(); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 		this._show(this.$menu, opts.show, function() { |  | ||||||
| 			var $first; |  | ||||||
|  |  | ||||||
| 			// Set focus to first active menu entry |  | ||||||
| 			if ( opts.autoFocus ) { |  | ||||||
| 				self.previousFocus = $(event.target); |  | ||||||
| 				// self.$menu.focus(); |  | ||||||
| 				$first = self.$menu |  | ||||||
| 					.children("li.ui-menu-item") |  | ||||||
| 					.not(".ui-state-disabled") |  | ||||||
| 					.first(); |  | ||||||
| 				self.$menu.menu("focus", null, $first).focus(); |  | ||||||
| 			} |  | ||||||
| 			self._trigger.call(self, "open", event, ui); |  | ||||||
| 		}); |  | ||||||
| 	}, |  | ||||||
| 	/** Close popup. */ |  | ||||||
| 	_closeMenu: function(immediately) { |  | ||||||
| 		var self = this, |  | ||||||
| 			hideOpts = immediately ? false : this.options.hide, |  | ||||||
| 			ui = { menu: this.$menu, target: $(this.currentTarget), extraData: this.extraData }; |  | ||||||
|  |  | ||||||
| 		// Note: we don't want to unbind the 'contextmenu' event |  | ||||||
| 		$(document) |  | ||||||
| 			.off("mousedown" + this.eventNamespace) |  | ||||||
| 			.off("touchstart" + this.eventNamespace) |  | ||||||
| 			.off("keydown" + this.eventNamespace); |  | ||||||
| 		$(window) |  | ||||||
| 			.off("blur" + this.eventNamespace); |  | ||||||
|  |  | ||||||
| 		self.currentTarget = null; // issue #44 after hide animation is too late |  | ||||||
| 		self.extraData = {}; |  | ||||||
| 		if ( this.$menu ) { // #88: widget might have been destroyed already |  | ||||||
| 			this.$menu |  | ||||||
| 				.off("contextmenu" + this.eventNamespace); |  | ||||||
| 			this._hide(this.$menu, hideOpts, function() { |  | ||||||
| 				if ( self.previousFocus ) { |  | ||||||
| 					self.previousFocus.focus(); |  | ||||||
| 					self.previousFocus = null; |  | ||||||
| 				} |  | ||||||
| 				self._trigger("close", null, ui); |  | ||||||
| 			}); |  | ||||||
| 		} else { |  | ||||||
| 			self._trigger("close", null, ui); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	/** Handle $().contextmenu("option", key, value) calls. */ |  | ||||||
| 	_setOption: function(key, value) { |  | ||||||
| 		switch (key) { |  | ||||||
| 		case "menu": |  | ||||||
| 			this.replaceMenu(value); |  | ||||||
| 			break; |  | ||||||
| 		} |  | ||||||
| 		$.Widget.prototype._setOption.apply(this, arguments); |  | ||||||
| 	}, |  | ||||||
| 	/** Return ui-menu entry (<LI> tag). */ |  | ||||||
| 	_getMenuEntry: function(cmd) { |  | ||||||
| 		return this.$menu.find("li[data-command=" + cmd + "]"); |  | ||||||
| 	}, |  | ||||||
| 	/** Close context menu. */ |  | ||||||
| 	close: function() { |  | ||||||
| 		if (this.isOpen()) { |  | ||||||
| 			this._closeMenu(); |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	/* Apply status callbacks when menu is opened. */ |  | ||||||
| 	_updateEntries: function() { |  | ||||||
| 		var self = this, |  | ||||||
| 			ui = { |  | ||||||
| 				menu: this.$menu, target: $(this.currentTarget), extraData: this.extraData }; |  | ||||||
|  |  | ||||||
| 		$.each(this.$menu.find(".ui-menu-item"), function(i, o) { |  | ||||||
| 			var $entry = $(o), |  | ||||||
| 				fn = $entry.data("disabledHandler"), |  | ||||||
| 				res = fn ? fn({ type: "disabled" }, ui) : null; |  | ||||||
|  |  | ||||||
| 			ui.item = $entry; |  | ||||||
| 			ui.cmd = $entry.attr("data-command"); |  | ||||||
| 			// Evaluate `disabled()` callback |  | ||||||
| 			if ( res != null ) { |  | ||||||
| 				self.enableEntry(ui.cmd, !res); |  | ||||||
| 				self.showEntry(ui.cmd, res !== "hide"); |  | ||||||
| 			} |  | ||||||
| 			// Evaluate `title()` callback |  | ||||||
| 			fn = $entry.data("titleHandler"), |  | ||||||
| 			res = fn ? fn({ type: "title" }, ui) : null; |  | ||||||
| 			if ( res != null ) { |  | ||||||
| 				self.setTitle(ui.cmd, "" + res); |  | ||||||
| 			} |  | ||||||
| 			// Evaluate `tooltip()` callback |  | ||||||
| 			fn = $entry.data("tooltipHandler"), |  | ||||||
| 			res = fn ? fn({ type: "tooltip" }, ui) : null; |  | ||||||
| 			if ( res != null ) { |  | ||||||
| 				$entry.attr("title", "" + res); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	}, |  | ||||||
| 	/** Enable or disable the menu command. */ |  | ||||||
| 	enableEntry: function(cmd, flag) { |  | ||||||
| 		this._getMenuEntry(cmd).toggleClass("ui-state-disabled", (flag === false)); |  | ||||||
| 	}, |  | ||||||
| 	/** Return ui-menu entry (LI tag) as jQuery object. */ |  | ||||||
| 	getEntry: function(cmd) { |  | ||||||
| 		return this._getMenuEntry(cmd); |  | ||||||
| 	}, |  | ||||||
| 	/** Return ui-menu entry wrapper as jQuery object. |  | ||||||
| 		UI 1.10: this is the <a> tag inside the LI |  | ||||||
| 		UI 1.11: this is the LI istself |  | ||||||
| 		UI 1.12: this is the <div> tag inside the LI |  | ||||||
| 	 */ |  | ||||||
| 	getEntryWrapper: function(cmd) { |  | ||||||
| 		return this._getMenuEntry(cmd).find(">[role=menuitem]").addBack("[role=menuitem]"); |  | ||||||
| 	}, |  | ||||||
| 	/** Return Menu element (UL). */ |  | ||||||
| 	getMenu: function() { |  | ||||||
| 		return this.$menu; |  | ||||||
| 	}, |  | ||||||
| 	/** Return true if menu is open. */ |  | ||||||
| 	isOpen: function() { |  | ||||||
| //            return this.$menu && this.$menu.is(":visible"); |  | ||||||
| 		return !!this.$menu && !!this.currentTarget; |  | ||||||
| 	}, |  | ||||||
| 	/** Open context menu on a specific target (must match options.delegate) |  | ||||||
| 	 *  Optional `extraData` is passed to event handlers as `ui.extraData`. |  | ||||||
| 	 */ |  | ||||||
| 	open: function(targetOrEvent, extraData) { |  | ||||||
| 		// Fake a 'contextmenu' event |  | ||||||
| 		extraData = extraData || {}; |  | ||||||
|  |  | ||||||
| 		var isEvent = (targetOrEvent && targetOrEvent.type && targetOrEvent.target), |  | ||||||
| 			event =  isEvent ? targetOrEvent : {}, |  | ||||||
| 			target = isEvent ? targetOrEvent.target : targetOrEvent, |  | ||||||
| 			e = jQuery.Event("contextmenu", { |  | ||||||
| 				target: $(target).get(0), |  | ||||||
| 				pageX: event.pageX, |  | ||||||
| 				pageY: event.pageY, |  | ||||||
| 				originalEvent: isEvent ? targetOrEvent : undefined, |  | ||||||
| 				_extraData: extraData |  | ||||||
| 			}); |  | ||||||
| 		return this.element.trigger(e); |  | ||||||
| 	}, |  | ||||||
| 	/** Replace the menu altogether. */ |  | ||||||
| 	replaceMenu: function(data) { |  | ||||||
| 		this._createUiMenu(data); |  | ||||||
| 	}, |  | ||||||
| 	/** Redefine a whole menu entry. */ |  | ||||||
| 	setEntry: function(cmd, entry) { |  | ||||||
| 		var $ul, |  | ||||||
| 			$entryLi = this._getMenuEntry(cmd); |  | ||||||
|  |  | ||||||
| 		if (typeof entry === "string") { |  | ||||||
| 			window.console && window.console.warn( |  | ||||||
| 				"setEntry(cmd, t) with a plain string title is deprecated since v1.18." + |  | ||||||
| 				"Use setTitle(cmd, '" + entry + "') instead."); |  | ||||||
| 			return this.setTitle(cmd, entry); |  | ||||||
| 		} |  | ||||||
| 		$entryLi.empty(); |  | ||||||
| 		entry.cmd = entry.cmd || cmd; |  | ||||||
| 		$.moogle.contextmenu.createEntryMarkup(entry, $entryLi); |  | ||||||
| 		if ($.isArray(entry.children)) { |  | ||||||
| 			$ul = $("<ul/>").appendTo($entryLi); |  | ||||||
| 			$.moogle.contextmenu.createMenuMarkup(entry.children, $ul); |  | ||||||
| 		} |  | ||||||
| 		// #110: jQuery UI 1.12: refresh only works when this class is not set: |  | ||||||
| 		$entryLi.removeClass("ui-menu-item"); |  | ||||||
| 		this.getMenu().menu("refresh"); |  | ||||||
| 	}, |  | ||||||
| 	/** Set icon (pass null to remove). */ |  | ||||||
| 	setIcon: function(cmd, icon) { |  | ||||||
| 		return this.updateEntry(cmd, { uiIcon: icon }); |  | ||||||
| 	}, |  | ||||||
| 	/** Set title. */ |  | ||||||
| 	setTitle: function(cmd, title) { |  | ||||||
| 		return this.updateEntry(cmd, { title: title }); |  | ||||||
| 	}, |  | ||||||
| 	// /** Set tooltip (pass null to remove). */ |  | ||||||
| 	// setTooltip: function(cmd, tooltip) { |  | ||||||
| 	// 	this._getMenuEntry(cmd).attr("title", tooltip); |  | ||||||
| 	// }, |  | ||||||
| 	/** Show or hide the menu command. */ |  | ||||||
| 	showEntry: function(cmd, flag) { |  | ||||||
| 		this._getMenuEntry(cmd).toggle(flag !== false); |  | ||||||
| 	}, |  | ||||||
| 	/** Redefine selective attributes of a menu entry. */ |  | ||||||
| 	updateEntry: function(cmd, entry) { |  | ||||||
| 		var $icon, $wrapper, |  | ||||||
| 			$entryLi = this._getMenuEntry(cmd); |  | ||||||
|  |  | ||||||
| 		if ( entry.title !== undefined ) { |  | ||||||
| 			$.moogle.contextmenu.updateTitle($entryLi, "" + entry.title); |  | ||||||
| 		} |  | ||||||
| 		if ( entry.tooltip !== undefined ) { |  | ||||||
| 			if ( entry.tooltip === null ) { |  | ||||||
| 				$entryLi.removeAttr("title"); |  | ||||||
| 			} else { |  | ||||||
| 				$entryLi.attr("title", entry.tooltip); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if ( entry.uiIcon !== undefined ) { |  | ||||||
| 			$wrapper = this.getEntryWrapper(cmd), |  | ||||||
| 			$icon = $wrapper.find("span.ui-icon").not(".ui-menu-icon"); |  | ||||||
| 			$icon.remove(); |  | ||||||
| 			if ( entry.uiIcon ) { |  | ||||||
| 				$wrapper.append($("<span class='ui-icon' />").addClass(entry.uiIcon)); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if ( entry.hide !== undefined ) { |  | ||||||
| 			$entryLi.toggle(!entry.hide); |  | ||||||
| 		} else if ( entry.show !== undefined ) { |  | ||||||
| 			// Note: `show` is an undocumented variant. `hide: false` is preferred |  | ||||||
| 			$entryLi.toggle(!!entry.show); |  | ||||||
| 		} |  | ||||||
| 		// if ( entry.isHeader !== undefined ) { |  | ||||||
| 		// 	$entryLi.toggleClass("ui-widget-header", !!entry.isHeader); |  | ||||||
| 		// } |  | ||||||
| 		if ( entry.data !== undefined ) { |  | ||||||
| 			$entryLi.data(entry.data); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Set/clear class names, but handle ui-state-disabled separately |  | ||||||
| 		if ( entry.disabled === undefined ) { |  | ||||||
| 			entry.disabled = $entryLi.hasClass("ui-state-disabled"); |  | ||||||
| 		} |  | ||||||
| 		if ( entry.setClass ) { |  | ||||||
| 			if ( $entryLi.hasClass("ui-menu-item") ) { |  | ||||||
| 				entry.setClass += " ui-menu-item"; |  | ||||||
| 			} |  | ||||||
| 			$entryLi.removeClass(); |  | ||||||
| 			$entryLi.addClass(entry.setClass); |  | ||||||
| 		} else if ( entry.addClass ) { |  | ||||||
| 			$entryLi.addClass(entry.addClass); |  | ||||||
| 		} |  | ||||||
| 		$entryLi.toggleClass("ui-state-disabled", !!entry.disabled); |  | ||||||
| 		// // #110: jQuery UI 1.12: refresh only works when this class is not set: |  | ||||||
| 		// $entryLi.removeClass("ui-menu-item"); |  | ||||||
| 		// this.getMenu().menu("refresh"); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| /* |  | ||||||
|  * Global functions |  | ||||||
|  */ |  | ||||||
| $.extend($.moogle.contextmenu, { |  | ||||||
| 	/** Convert a menu description into a into a <li> content. */ |  | ||||||
| 	createEntryMarkup: function(entry, $parentLi) { |  | ||||||
| 		var $wrapper = null; |  | ||||||
|  |  | ||||||
| 		$parentLi.attr("data-command", entry.cmd); |  | ||||||
|  |  | ||||||
| 		if ( !/[^\-\u2014\u2013\s]/.test( entry.title ) ) { |  | ||||||
| 			// hyphen, em dash, en dash: separator as defined by UI Menu 1.10 |  | ||||||
| 			$parentLi.text(entry.title); |  | ||||||
| 		} else { |  | ||||||
| 			if ( isLTE110 ) { |  | ||||||
| 				// jQuery UI Menu 1.10 or before required an `<a>` tag |  | ||||||
| 				$wrapper = $("<a/>", { |  | ||||||
| 						html: "" + entry.title, |  | ||||||
| 						href: "#" |  | ||||||
| 					}).appendTo($parentLi); |  | ||||||
|  |  | ||||||
| 			} else if ( isLTE111 ) { |  | ||||||
| 				// jQuery UI Menu 1.11 preferes to avoid `<a>` tags or <div> wrapper |  | ||||||
| 				$parentLi.html("" + entry.title); |  | ||||||
| 				$wrapper = $parentLi; |  | ||||||
|  |  | ||||||
| 			} else { |  | ||||||
| 				// jQuery UI Menu 1.12 introduced `<div>` wrappers |  | ||||||
| 				$wrapper = $("<div/>", { |  | ||||||
| 						html: "" + entry.title |  | ||||||
| 					}).appendTo($parentLi); |  | ||||||
| 			} |  | ||||||
| 			if ( entry.uiIcon ) { |  | ||||||
| 				$wrapper.append($("<span class='ui-icon' />").addClass(entry.uiIcon)); |  | ||||||
| 			} |  | ||||||
| 			// Store option callbacks in entry's data |  | ||||||
| 			$.each( [ "action", "disabled", "title", "tooltip" ], function(i, attr) { |  | ||||||
| 				if ( $.isFunction(entry[attr]) ) { |  | ||||||
| 					$parentLi.data(attr + "Handler", entry[attr]); |  | ||||||
| 				} |  | ||||||
| 			}); |  | ||||||
| 			if ( entry.disabled === true ) { |  | ||||||
| 				$parentLi.addClass("ui-state-disabled"); |  | ||||||
| 			} |  | ||||||
| 			if ( entry.isHeader ) { |  | ||||||
| 				$parentLi.addClass("ui-widget-header"); |  | ||||||
| 			} |  | ||||||
| 			if ( entry.addClass ) { |  | ||||||
| 				$parentLi.addClass(entry.addClass); |  | ||||||
| 			} |  | ||||||
| 			if ( $.isPlainObject(entry.data) ) { |  | ||||||
| 				$parentLi.data(entry.data); |  | ||||||
| 			} |  | ||||||
| 			if ( typeof entry.tooltip === "string" ) { |  | ||||||
| 				$parentLi.attr("title", entry.tooltip); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	/** Convert a nested array of command objects into a <ul> structure. */ |  | ||||||
| 	createMenuMarkup: function(options, $parentUl) { |  | ||||||
| 		var i, menu, $ul, $li; |  | ||||||
| 		if ( $parentUl == null ) { |  | ||||||
| 			$parentUl = $("<ul class='ui-helper-hidden' />").appendTo("body"); |  | ||||||
| 		} |  | ||||||
| 		for (i = 0; i < options.length; i++) { |  | ||||||
| 			menu = options[i]; |  | ||||||
| 			$li = $("<li/>").appendTo($parentUl); |  | ||||||
|  |  | ||||||
| 			$.moogle.contextmenu.createEntryMarkup(menu, $li); |  | ||||||
|  |  | ||||||
| 			if ( $.isArray(menu.children) ) { |  | ||||||
| 				$ul = $("<ul/>").appendTo($li); |  | ||||||
| 				$.moogle.contextmenu.createMenuMarkup(menu.children, $ul); |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return $parentUl; |  | ||||||
| 	}, |  | ||||||
| 	/** Returns true if the menu item has child menu items */ |  | ||||||
| 	isMenu: function(item) { |  | ||||||
| 		if ( isLTE110 ) { |  | ||||||
| 			return item.has(">a[aria-haspopup='true']").length > 0; |  | ||||||
| 		} else if ( isLTE111 ) {  // jQuery UI 1.11 used no tag wrappers |  | ||||||
| 			return item.is("[aria-haspopup='true']"); |  | ||||||
| 		} else { |  | ||||||
| 			return item.has(">div[aria-haspopup='true']").length > 0; |  | ||||||
| 		} |  | ||||||
| 	}, |  | ||||||
| 	/** Replace the title of elem', but retain icons andchild entries. */ |  | ||||||
| 	replaceFirstTextNodeChild: function(elem, html) { |  | ||||||
| 		var $icons = elem.find(">span.ui-icon,>ul.ui-menu").detach(); |  | ||||||
|  |  | ||||||
| 		elem |  | ||||||
| 			.empty() |  | ||||||
| 			.html(html) |  | ||||||
| 			.append($icons); |  | ||||||
| 	}, |  | ||||||
| 	/** Updates the menu item's title */ |  | ||||||
| 	updateTitle: function(item, title) { |  | ||||||
| 		if ( isLTE110 ) {  // jQuery UI 1.10 and before used <a> tags |  | ||||||
| 			$.moogle.contextmenu.replaceFirstTextNodeChild($("a", item), title); |  | ||||||
| 		} else if ( isLTE111 ) {  // jQuery UI 1.11 used no tag wrappers |  | ||||||
| 			$.moogle.contextmenu.replaceFirstTextNodeChild(item, title); |  | ||||||
| 		} else {  // jQuery UI 1.12+ introduced <div> tag wrappers |  | ||||||
| 			$.moogle.contextmenu.replaceFirstTextNodeChild($("div", item), title); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| })); |  | ||||||
| @@ -298,13 +298,8 @@ div.ui-tooltip { | |||||||
|     display: none; |     display: none; | ||||||
| } | } | ||||||
|  |  | ||||||
| #note-type .dropdown-menu li:not(.divider) { | .dropdown-menu a:hover:not(.disabled) { | ||||||
|     padding: 5px; |     background-color: #eee !important; | ||||||
|     width: 200px; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover { |  | ||||||
|     background-color: #ccc !important; |  | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -324,6 +319,10 @@ div.ui-tooltip { | |||||||
|     float: right; |     float: right; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .dropdown-item.disabled, .dropdown-item.disabled kbd { | ||||||
|  |     color: #aaa !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| #note-detail-code { | #note-detail-code { | ||||||
|     min-height: 200px; |     min-height: 200px; | ||||||
|     overflow: auto; |     overflow: auto; | ||||||
| @@ -512,4 +511,12 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th | |||||||
| #note-detail-render-help { | #note-detail-render-help { | ||||||
|     margin: 50px; |     margin: 50px; | ||||||
|     padding: 20px; |     padding: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .context-menu { | ||||||
|  |     font-size: small; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .context-menu .dropdown-item { | ||||||
|  |     padding: 2px 10px 2px 10px; | ||||||
| } | } | ||||||
| @@ -754,6 +754,12 @@ | |||||||
|         </form> |         </form> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  |       <div class="dropdown-menu dropdown-menu-sm context-menu" id="tree-context-menu"> | ||||||
|  |         <a class="dropdown-item" href="#">Action</a> | ||||||
|  |         <a class="dropdown-item" href="#">Another action</a> | ||||||
|  |         <a class="dropdown-item" href="#">Something else here</a> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|       <div id="markdown-import-dialog" class="tdialog" title="Markdown import" style="padding: 20px;"> |       <div id="markdown-import-dialog" class="tdialog" title="Markdown import" style="padding: 20px;"> | ||||||
|         <p>Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button</p> |         <p>Because of browser sandbox it's not possible to directly read clipboard from JavaScript. Please paste the Markdown to import to textarea below and click on Import button</p> | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user