diff --git a/src/public/app/services/link.js b/src/public/app/services/link.js index 95065ee33..e799d33e0 100644 --- a/src/public/app/services/link.js +++ b/src/public/app/services/link.js @@ -90,27 +90,27 @@ function getNotePathFromLink($link) { return url ? getNotePathFromUrl(url) : null; } -function goToLink(e) { - const $link = $(e.target).closest("a,.block-link"); +function goToLink(evt) { + const $link = $(evt.target).closest("a,.block-link"); const address = $link.attr('href'); if (address?.startsWith("data:")) { return true; } - e.preventDefault(); - e.stopPropagation(); + evt.preventDefault(); + evt.stopPropagation(); const notePath = getNotePathFromLink($link); - const ctrlKey = (!utils.isMac() && e.ctrlKey) || (utils.isMac() && e.metaKey); + const ctrlKey = utils.isCtrlKey(evt); if (notePath) { - if ((e.which === 1 && ctrlKey) || e.which === 2) { + if ((evt.which === 1 && ctrlKey) || evt.which === 2) { appContext.tabManager.openTabWithNoteWithHoisting(notePath); } - else if (e.which === 1) { - const ntxId = $(e.target).closest("[data-ntx-id]").attr("data-ntx-id"); + else if (evt.which === 1) { + const ntxId = $(evt.target).closest("[data-ntx-id]").attr("data-ntx-id"); const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) @@ -124,7 +124,7 @@ function goToLink(e) { } } else { - if ((e.which === 1 && ctrlKey) || e.which === 2 + if ((evt.which === 1 && ctrlKey) || evt.which === 2 || $link.hasClass("ck-link-actions__preview") // within edit link dialog single click suffices || $link.closest("[contenteditable]").length === 0 // outside of CKEditor single click suffices ) { diff --git a/src/public/app/services/utils.js b/src/public/app/services/utils.js index e0a2fa5dd..d8805c14e 100644 --- a/src/public/app/services/utils.js +++ b/src/public/app/services/utils.js @@ -60,6 +60,11 @@ function isMac() { return navigator.platform.indexOf('Mac') > -1; } +function isCtrlKey(evt) { + return (!isMac() && evt.ctrlKey) + || (isMac() && evt.metaKey); +} + function assertArguments() { for (const i in arguments) { if (!arguments[i]) { @@ -362,6 +367,7 @@ export default { now, isElectron, isMac, + isCtrlKey, assertArguments, escapeHtml, stopWatch, diff --git a/src/public/app/widgets/buttons/launcher/note_launcher.js b/src/public/app/widgets/buttons/launcher/note_launcher.js index 91e483e80..87e85caec 100644 --- a/src/public/app/widgets/buttons/launcher/note_launcher.js +++ b/src/public/app/widgets/buttons/launcher/note_launcher.js @@ -1,23 +1,55 @@ import AbstractLauncher from "./abstract_launcher.js"; import dialogService from "../../../services/dialog.js"; import appContext from "../../../components/app_context.js"; +import utils from "../../../services/utils.js"; +import linkContextMenuService from "../../../menus/link_context_menu.js"; +// we're intentionally displaying the launcher title and icon instead of the target +// e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok), +// but on the launchpad you want them distinguishable. +// for titles, the note titles may follow a different scheme than maybe desirable on the launchpad +// another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons). +// The only downside is more work in setting up the typical case +// where you actually want to have both title and icon in sync, but for those cases there are bookmarks export default class NoteLauncher extends AbstractLauncher { constructor(launcherNote) { super(launcherNote); this.title(this.launcherNote.title) .icon(this.launcherNote.getIcon()) - .onClick(() => this.launch()); + .onClick((widget, evt) => this.launch(evt)) + .onAuxClick((widget, evt) => this.launch(evt)) + .onContextMenu(evt => { + const targetNoteId = this.getTargetNoteId(); + if (!targetNoteId) { + return; + } + + linkContextMenuService.openContextMenu(targetNoteId, evt); + }); } - launch() { - // we're intentionally displaying the launcher title and icon instead of the target - // e.g. you want to make launchers to 2 mermaid diagrams which both have mermaid icon (ok), - // but on the launchpad you want them distinguishable. - // for titles, the note titles may follow a different scheme than maybe desirable on the launchpad - // another reason is the discrepancy between what user sees on the launchpad and in the config (esp. icons). - // The only (but major) downside is more work in setting up the typical case where you actually want to have both title and icon in sync. + launch(evt) { + const targetNoteId = this.getTargetNoteId(); + if (!targetNoteId) { + return; + } + + if (!evt) { + // keyboard shortcut + appContext.tabManager.getActiveContext().setNote(targetNoteId) + return; + } + + const ctrlKey = utils.isCtrlKey(evt); + if ((evt.which === 1 && ctrlKey) || evt.which === 2) { + appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId); + } else { + appContext.tabManager.getActiveContext().setNote(targetNoteId); + } + } + + getTargetNoteId() { const targetNoteId = this.launcherNote.getRelationValue('targetNote'); if (!targetNoteId) { @@ -25,7 +57,7 @@ export default class NoteLauncher extends AbstractLauncher { return; } - appContext.tabManager.openTabWithNoteWithHoisting(targetNoteId, true); + return targetNoteId; } getTitle() { diff --git a/src/public/app/widgets/buttons/onclick_button.js b/src/public/app/widgets/buttons/onclick_button.js index 00e724054..ea4fafd5f 100644 --- a/src/public/app/widgets/buttons/onclick_button.js +++ b/src/public/app/widgets/buttons/onclick_button.js @@ -13,10 +13,23 @@ export default class OnClickButtonWidget extends AbstractButtonWidget { } else { console.warn(`Button widget '${this.componentId}' has no defined click handler`, this.settings); } + + if (this.settings.onAuxClick) { + this.$widget.on("auxclick", e => { + this.$widget.tooltip("hide"); + + this.settings.onAuxClick(this, e); + }); + } } onClick(handler) { this.settings.onClick = handler; return this; } + + onAuxClick(handler) { + this.settings.onAuxClick = handler; + return this; + } } diff --git a/src/services/hidden_subtree.js b/src/services/hidden_subtree.js index 4b2ae362f..f99c4976d 100644 --- a/src/services/hidden_subtree.js +++ b/src/services/hidden_subtree.js @@ -56,6 +56,12 @@ const HIDDEN_SUBTREE_DEFINITION = { title: 'Bulk action', type: 'doc', }, + { + // place for user scripts hidden stuff (scripts should not create notes directly under hidden root) + id: 'userHidden', + title: 'User Hidden', + type: 'text', + }, { id: LBTPL_ROOT, title: 'Launch Bar Templates', diff --git a/src/services/notes.js b/src/services/notes.js index 77b16c236..aa7bc5c26 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -111,7 +111,11 @@ function getAndValidateParent(params) { throw new ValidationError(`Parent note "${params.parentNoteId}" not found.`); } - if (!params.ignoreForbiddenParents && (parentNote.isLaunchBarConfig() || parentNote.isOptions())) { + if (parentNote.type === 'launcher' && parentNote.noteId !== 'lbBookmarks') { + throw new ValidationError(`Creating child notes into launcher notes is not allowed.`); + } + + if (!params.ignoreForbiddenParents && (['lbRoot', 'hidden'].includes(parentNote.noteId) || parentNote.isOptions())) { throw new ValidationError(`Creating child notes into '${parentNote.noteId}' is not allowed.`); }