mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01: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 noteDetailService from './note_detail.js';
 | 
			
		||||
import protectedSessionHolder from './protected_session_holder.js';
 | 
			
		||||
import treeChangesService from './branches.js';
 | 
			
		||||
import treeUtils from './tree_utils.js';
 | 
			
		||||
import utils from './utils.js';
 | 
			
		||||
import server from './server.js';
 | 
			
		||||
@ -16,6 +15,7 @@ import Branch from '../entities/branch.js';
 | 
			
		||||
import NoteShort from '../entities/note_short.js';
 | 
			
		||||
 | 
			
		||||
const $tree = $("#tree");
 | 
			
		||||
const $treeContextMenu = $("#tree-context-menu");
 | 
			
		||||
const $createTopLevelNoteButton = $("#create-top-level-note-button");
 | 
			
		||||
const $collapseTreeButton = $("#collapse-tree-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() {
 | 
			
		||||
 | 
			
		||||
@ -76,135 +76,137 @@ function cut(nodes) {
 | 
			
		||||
    infoService.showMessage("Note(s) have been cut into clipboard.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const contextMenuOptions = {
 | 
			
		||||
    delegate: "span.fancytree-title",
 | 
			
		||||
    autoFocus: true,
 | 
			
		||||
    menu: [
 | 
			
		||||
        {title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"},
 | 
			
		||||
        {title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"},
 | 
			
		||||
        {title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
 | 
			
		||||
        {title: "----"},
 | 
			
		||||
        {title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"},
 | 
			
		||||
        {title: "----"},
 | 
			
		||||
        {title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"},
 | 
			
		||||
        {title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"},
 | 
			
		||||
        {title: "----"},
 | 
			
		||||
        {title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
 | 
			
		||||
        {title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
 | 
			
		||||
        {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
 | 
			
		||||
        {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
 | 
			
		||||
        {title: "----"},
 | 
			
		||||
        {title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne", children: [
 | 
			
		||||
            {title: "Native Tar", cmd: "exportSubtreeToTar"},
 | 
			
		||||
            {title: "OPML", cmd: "exportSubtreeToOpml"},
 | 
			
		||||
            {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';
 | 
			
		||||
const contextMenuItems = [
 | 
			
		||||
    {title: "Insert note here <kbd>Ctrl+O</kbd>", cmd: "insertNoteHere", uiIcon: "ui-icon-plus"},
 | 
			
		||||
    {title: "Insert child note <kbd>Ctrl+P</kbd>", cmd: "insertChildNote", uiIcon: "ui-icon-plus"},
 | 
			
		||||
    {title: "Delete", cmd: "delete", uiIcon: "ui-icon-trash"},
 | 
			
		||||
    {title: "----"},
 | 
			
		||||
    {title: "Edit branch prefix <kbd>F2</kbd>", cmd: "editBranchPrefix", uiIcon: "ui-icon-pencil"},
 | 
			
		||||
    {title: "----"},
 | 
			
		||||
    {title: "Protect subtree", cmd: "protectSubtree", uiIcon: "ui-icon-locked"},
 | 
			
		||||
    {title: "Unprotect subtree", cmd: "unprotectSubtree", uiIcon: "ui-icon-unlocked"},
 | 
			
		||||
    {title: "----"},
 | 
			
		||||
    {title: "Copy / clone <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
 | 
			
		||||
    {title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
 | 
			
		||||
    {title: "Paste into <kbd>Ctrl+V</kbd>", cmd: "pasteInto", uiIcon: "ui-icon-clipboard"},
 | 
			
		||||
    {title: "Paste after", cmd: "pasteAfter", uiIcon: "ui-icon-clipboard"},
 | 
			
		||||
    {title: "----"},
 | 
			
		||||
    {title: "Export subtree", cmd: "exportSubtree", uiIcon: " ui-icon-arrowthick-1-ne"},
 | 
			
		||||
    {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"}
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
        // Modify menu entries depending on node status
 | 
			
		||||
        $tree.contextmenu("enableEntry", "insertNoteHere", isNotRoot && parentNote.type !== 'search');
 | 
			
		||||
        $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
 | 
			
		||||
        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);
 | 
			
		||||
        }
 | 
			
		||||
function enableItem(cmd, enabled) {
 | 
			
		||||
    const item = contextMenuItems.find(item => item.cmd === cmd);
 | 
			
		||||
    
 | 
			
		||||
    if (!item) {
 | 
			
		||||
        throw new Error(`Command ${cmd} has not been found!`);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
    
 | 
			
		||||
    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 {
 | 
			
		||||
    pasteAfter,
 | 
			
		||||
    pasteInto,
 | 
			
		||||
    cut,
 | 
			
		||||
    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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#note-type .dropdown-menu li:not(.divider) {
 | 
			
		||||
    padding: 5px;
 | 
			
		||||
    width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-menu li:not(.divider):hover, .dropdown-menu li:not(.divider) a:hover {
 | 
			
		||||
    background-color: #ccc !important;
 | 
			
		||||
.dropdown-menu a:hover:not(.disabled) {
 | 
			
		||||
    background-color: #eee !important;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -324,6 +319,10 @@ div.ui-tooltip {
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dropdown-item.disabled, .dropdown-item.disabled kbd {
 | 
			
		||||
    color: #aaa !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#note-detail-code {
 | 
			
		||||
    min-height: 200px;
 | 
			
		||||
    overflow: auto;
 | 
			
		||||
@ -512,4 +511,12 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th
 | 
			
		||||
#note-detail-render-help {
 | 
			
		||||
    margin: 50px;
 | 
			
		||||
    padding: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context-menu {
 | 
			
		||||
    font-size: small;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.context-menu .dropdown-item {
 | 
			
		||||
    padding: 2px 10px 2px 10px;
 | 
			
		||||
}
 | 
			
		||||
@ -754,6 +754,12 @@
 | 
			
		||||
        </form>
 | 
			
		||||
      </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;">
 | 
			
		||||
        <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