mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
replace note tree context menu with bootstrap dropdown, #203
This commit is contained in:
parent
20baa47d26
commit
24dfe0fa98
@ -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');
|
|
||||||
$tree.contextmenu("enableEntry", "copy", isNotRoot);
|
|
||||||
$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
|
if (!item) {
|
||||||
node.setActive();
|
throw new Error(`Command ${cmd} has not been found!`);
|
||||||
|
|
||||||
// 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;
|
||||||
@ -513,3 +512,11 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
|
|||||||
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>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user