diff --git a/README.md b/README.md index c713556..f6cfbea 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,7 @@ An action has the following settings: image - Collection Menu image + - Tools Menu - Reader Menu image - Annotation Menu diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index 7b863ac..cbe8cc9 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -9,6 +9,7 @@ prefs-action-enabled = Enabled prefs-action-menu = Menu Label prefs-action-showInMenuItem = In Item Menu prefs-action-showInMenuCollection = In Collection Menu +prefs-action-showInMenuTools = In Tools Menu prefs-action-showInMenuReader = In Reader Menu prefs-action-showInMenuReaderAnnotation = In Annotation Menu diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl index b833a32..c5b9f15 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -9,6 +9,7 @@ prefs-action-enabled = 启用 prefs-action-menu = 菜单项 prefs-action-showInMenuItem = 条目菜单中 prefs-action-showInMenuCollection = 分类菜单中 +prefs-action-showInMenuTools = 工具菜单中 prefs-action-showInMenuReader = 阅读器菜单中 prefs-action-showInMenuReaderAnnotation = 注释菜单中 diff --git a/src/modules/edit.ts b/src/modules/edit.ts index e105cce..4f1dded 100644 --- a/src/modules/edit.ts +++ b/src/modules/edit.ts @@ -24,6 +24,7 @@ async function editAction(currentKey?: string) { `[${getString("prefs-action-edit-shortcut-empty")}]`; dialogData.showInMenuItem = !(action.showInMenu?.item === false); dialogData.showInMenuCollection = !(action.showInMenu?.collection === false); + dialogData.showInMenuTools = !(action.showInMenu?.tools === false); dialogData.showInMenuReader = !(action.showInMenu?.reader === false); dialogData.showInMenuReaderAnnotation = !( action.showInMenu?.readerAnnotation === false @@ -144,7 +145,7 @@ async function editAction(currentKey?: string) { const content = await openEditorWindow(dialogData.data); ( dialog.window.document.querySelector( - "#data-input", + "#data-input" ) as HTMLTextAreaElement ).value = content; dialogData.data = content; @@ -178,7 +179,7 @@ async function editAction(currentKey?: string) { const key = ev.target as HTMLElement; const win = dialog.window; key.textContent = `[${getString( - "prefs-action-edit-shortcut-placeholder", + "prefs-action-edit-shortcut-placeholder" )}]`; dialogData.shortcut = ""; const keyDownListener = (e: KeyboardEvent) => { @@ -240,88 +241,30 @@ async function editAction(currentKey?: string) { gridColumnEnd: "3", }, }, - { - tag: "label", - namespace: "html", - properties: { - textContent: getString("prefs-action-showInMenuItem"), - }, - }, - { - tag: "input", - properties: { - type: "checkbox", - }, - attributes: { - "data-bind": "showInMenuItem", - "data-prop": "checked", - }, - styles: { - width: "fit-content", - }, - }, - { - tag: "label", - namespace: "html", - properties: { - textContent: getString("prefs-action-showInMenuCollection"), - }, - }, - { - tag: "input", - properties: { - type: "checkbox", - }, - attributes: { - "data-bind": "showInMenuCollection", - "data-prop": "checked", - }, - styles: { - width: "fit-content", - }, - }, - { - tag: "label", - namespace: "html", - properties: { - textContent: getString("prefs-action-showInMenuReader"), - }, - }, - { - tag: "input", - properties: { - type: "checkbox", - }, - attributes: { - "data-bind": "showInMenuReader", - "data-prop": "checked", - }, - styles: { - width: "fit-content", - }, - }, - { - tag: "label", - namespace: "html", - properties: { - textContent: getString( - "prefs-action-showInMenuReaderAnnotation", - ), - }, - }, - { - tag: "input", - properties: { - type: "checkbox", - }, - attributes: { - "data-bind": "showInMenuReaderAnnotation", - "data-prop": "checked", - }, - styles: { - width: "fit-content", - }, - }, + ...["Item", "Collection", "Tools", "Reader", "ReaderAnnotation"] + .map((key) => [ + { + tag: "label", + namespace: "html", + properties: { + textContent: getString(`prefs-action-showInMenu${key}`), + }, + }, + { + tag: "input", + properties: { + type: "checkbox", + }, + attributes: { + "data-bind": `showInMenu${key}`, + "data-prop": "checked", + }, + styles: { + width: "fit-content", + }, + }, + ]) + .flat(), ], }, { @@ -380,11 +323,12 @@ async function editAction(currentKey?: string) { showInMenu: { item: dialogData.showInMenuItem, collection: dialogData.showInMenuCollection, + tools: dialogData.showInMenuTools, reader: dialogData.showInMenuReader, readerAnnotation: dialogData.showInMenuReaderAnnotation, }, }, - currentKey, + currentKey ); edited = true; } @@ -408,7 +352,7 @@ async function openEditorWindow(content: string) { const editorWin = addon.data.prefs.window?.openDialog( "chrome://scaffold/content/monaco/monaco.html", "monaco", - "chrome,centerscreen,dialog=no,resizable,scrollbars=yes,width=800,height=600", + "chrome,centerscreen,dialog=no,resizable,scrollbars=yes,width=800,height=600" ) as | (Window & { loadMonaco: (options: Record) => Promise<{ editor: any }>; @@ -419,7 +363,7 @@ async function openEditorWindow(content: string) { } await waitUtilAsync(() => !!editorWin.loadMonaco); const isDark = addon.data.prefs.window?.matchMedia( - "(prefers-color-scheme: dark)", + "(prefers-color-scheme: dark)" ).matches; const { editor } = await editorWin.loadMonaco({ language: "javascript", diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 6e43f70..7c973c0 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -43,6 +43,21 @@ function initItemMenu(win: Window) { ], }); + ztoolkit.Menu.register("menuTools", { + tag: "menu", + popupId: `${config.addonRef}-tools-popup`, + label: getString("menupopup-label"), + icon: `chrome://${config.addonRef}/content/icons/favicon.png`, + onpopupshowing: `Zotero.${config.addonInstance}.hooks.onMenuEvent("showing", { window, target: "tools" })`, + children: [ + { + tag: "menuitem", + label: getString("menupopup-placeholder"), + disabled: true, + }, + ], + }); + ztoolkit.UI.appendElement( { tag: "menupopup", @@ -56,7 +71,7 @@ function initItemMenu(win: Window) { }, ], }, - win.document.querySelector("popupset")!, + win.document.querySelector("popupset")! ); } @@ -85,56 +100,55 @@ function initReaderMenu() { z-index: 1; } `; - Zotero.Reader.registerEventListener("renderToolbar", (event) => { - const { append, doc } = event; - append( - ztoolkit.UI.createElement(doc, "button", { - namespace: "html", - classList: ["toolbarButton", "actions-tags-reader-menu"], - properties: { - tabIndex: -1, - title: "Actions", - }, - listeners: [ - { - type: "click", - listener: (ev: Event) => { - const _ev = ev as MouseEvent; - const target = ev.target as HTMLElement; - const elemRect = target.getBoundingClientRect(); - - const x = _ev.screenX - _ev.offsetX; - const y = - _ev.screenY - _ev.offsetY + elemRect.bottom - elemRect.top; - - document - .querySelector(`#${config.addonRef}-reader-popup`) - // @ts-ignore XUL.MenuPopup - ?.openPopupAtScreen(x + 1, y + 1, true); - }, - }, - ], - children: [ - { - tag: "span", - classList: ["button-background"], + Zotero.Reader.registerEventListener( + "renderToolbar", + (event) => { + const { append, doc } = event; + append( + ztoolkit.UI.createElement(doc, "button", { + namespace: "html", + classList: ["toolbarButton", "actions-tags-reader-menu"], + properties: { + tabIndex: -1, + title: "Actions", }, - { - tag: "span", - classList: ["dropmarker"], + listeners: [ + { + type: "click", + listener: (ev: Event) => { + document + .querySelector(`#${config.addonRef}-reader-popup`) + // @ts-ignore XUL.MenuPopup + ?.openPopup( + doc.querySelector(".actions-tags-reader-menu"), + "after_start" + ); + }, + }, + ], + children: [ + { + tag: "span", + classList: ["button-background"], + }, + { + tag: "span", + classList: ["dropmarker"], + }, + ], + }) + ); + append( + ztoolkit.UI.createElement(doc, "style", { + id: `${config.addonRef}-reader-button`, + properties: { + textContent: readerButtonCSS, }, - ], - }), - ); - append( - ztoolkit.UI.createElement(doc, "style", { - id: `${config.addonRef}-reader-button`, - properties: { - textContent: readerButtonCSS, - }, - }), - ); - }); + }) + ); + }, + config.addonID, + ); } function initReaderAnnotationMenu() { @@ -148,19 +162,23 @@ function initReaderAnnotationMenu() { label: action.menu!, onCommand: () => { triggerMenuCommand(action.key, () => - getItemsByKey(reader._item.libraryID, ...params.ids), + getItemsByKey(reader._item.libraryID, ...params.ids) ); }, }); } }, + config.addonID ); } -function buildItemMenu(win: Window, target: "item" | "collection" | "reader") { +function buildItemMenu( + win: Window, + target: "item" | "collection" | "tools" | "reader" +) { const doc = win.document; const popup = doc.querySelector( - `#${config.addonRef}-${target}-popup`, + `#${config.addonRef}-${target}-popup` ) as XUL.MenuPopup; // Remove all children in popup while (popup?.firstChild) { @@ -196,7 +214,7 @@ function buildItemMenu(win: Window, target: "item" | "collection" | "reader") { triggerMenuCommand( action.key, () => getCurrentItems(target), - target === "collection", + target === "collection" ); }, }, @@ -217,7 +235,7 @@ function getActionsByMenu(target: ActionShowInMenu) { action && action.menu && action.enabled && - (!action.showInMenu || action.showInMenu[target] !== false), + (!action.showInMenu || action.showInMenu[target] !== false) ) .sort((x, y) => { if (!x && !y) { @@ -231,7 +249,7 @@ function getActionsByMenu(target: ActionShowInMenu) { } return ((x[sortBy] as string) || "").localeCompare( (y[sortBy] || "") as string, - Zotero.locale, + Zotero.locale ); }); } @@ -241,7 +259,7 @@ async function triggerMenuCommand( getItems: () => | Zotero.DataObject[] | Promise = getCurrentItems, - withCollection: boolean = false, + withCollection: boolean = false ) { const items = await getItems(); let collection: Zotero.Collection | undefined = undefined; diff --git a/src/utils/actions.ts b/src/utils/actions.ts index 1d3b916..735db26 100644 --- a/src/utils/actions.ts +++ b/src/utils/actions.ts @@ -46,7 +46,7 @@ enum ActionOperationTypes { "triggerAction", } -type ActionShowInMenu = "item" | "collection" | "reader" | "readerAnnotation"; +type ActionShowInMenu = "item" | "collection" | "tools" | "reader" | "readerAnnotation"; interface ActionData { event: ActionEventTypes; @@ -95,6 +95,7 @@ const emptyAction: ActionData = { showInMenu: { item: false, collection: false, + tools: false, reader: false, readerAnnotation: false, }, diff --git a/src/utils/items.ts b/src/utils/items.ts index 8dceae7..c2dfb3b 100644 --- a/src/utils/items.ts +++ b/src/utils/items.ts @@ -4,7 +4,7 @@ export { getCurrentItems, getItemsByKey }; async function getCurrentItems(type?: ActionShowInMenu) { let items = [] as Zotero.Item[]; - if (!type) { + if (!type || type === "tools") { type = getCurrentTargetType(); } switch (type) {