diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index f941be5..d95a3aa 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -7,6 +7,9 @@ prefs-action-data = Data prefs-action-shortcut = Shortcut prefs-action-enabled = Enabled prefs-action-menu = Menu Label +prefs-action-showInMenuItem = In Item Menu +prefs-action-showInMenuReader = In Reader Menu +prefs-action-showInMenuReaderAnnotation = In Annotation Menu prefs-action-event-none = None prefs-action-event-createItem = Create Item diff --git a/addon/locale/it-IT/addon.ftl b/addon/locale/it-IT/addon.ftl index 8b4593f..cd18901 100644 --- a/addon/locale/it-IT/addon.ftl +++ b/addon/locale/it-IT/addon.ftl @@ -7,6 +7,9 @@ prefs-action-data = Data prefs-action-shortcut = Scorciatoia prefs-action-enabled = Attivo prefs-action-menu = Etichetta menu +prefs-action-showInMenuItem = In Item Menu +prefs-action-showInMenuReader = In Reader Menu +prefs-action-showInMenuReaderAnnotation = In Annotation Menu prefs-action-event-none = Nessuno prefs-action-event-createItem = Crea Elemento diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl index 173aa87..eab0a74 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -7,6 +7,9 @@ prefs-action-data = 数据 prefs-action-shortcut = 快捷键 prefs-action-enabled = 启用 prefs-action-menu = 菜单项 +prefs-action-showInMenuItem = 条目菜单中 +prefs-action-showInMenuReader = 阅读器菜单中 +prefs-action-showInMenuReaderAnnotation = 注释菜单中 prefs-action-event-none = 无 prefs-action-event-createItem = 新建条目 diff --git a/package.json b/package.json index b5955b3..72e65e8 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,6 @@ "release-it": "^16.2.1", "replace-in-file": "^7.0.1", "typescript": "^5.2.2", - "zotero-types": "^1.3.4" + "zotero-types": "^1.3.7" } } diff --git a/src/hooks.ts b/src/hooks.ts index d6fad8a..98d0c75 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -8,7 +8,7 @@ import { initWindowShortcuts, unInitWindowShortcuts, } from "./modules/shortcuts"; -import { buildItemMenu, initMenu, initReaderMenu } from "./modules/menu"; +import { buildItemMenu, initItemMenu, initReaderAnnotationMenu, initReaderMenu } from "./modules/menu"; import { editAction } from "./modules/edit"; import { exportToFile, importFromFile } from "./modules/backup"; @@ -41,12 +41,14 @@ async function onStartup() { initReaderMenu(); + initReaderAnnotationMenu(); + await onMainWindowLoad(window); } async function onMainWindowLoad(win: Window): Promise { initWindowShortcuts(win); - initMenu(win); + initItemMenu(win); await addon.api.actionManager.dispatchActionByEvent( ActionEventTypes.mainWindowLoad, { diff --git a/src/modules/edit.ts b/src/modules/edit.ts index 7c1fe7d..0a15ae7 100644 --- a/src/modules/edit.ts +++ b/src/modules/edit.ts @@ -22,6 +22,11 @@ async function editAction(currentKey?: string) { dialogData.shortcut = new KeyModifier(action.shortcut || "").getLocalized() || `[${getString("prefs-action-edit-shortcut-empty")}]`; + dialogData.showInMenuItem = !(action.showInMenu?.item === false); + dialogData.showInMenuReader = !(action.showInMenu?.reader === false); + dialogData.showInMenuReaderAnnotation = !( + action.showInMenu?.readerAnnotation === false + ); const dialog = new ztoolkit.Dialog(1, 1) .setDialogData(dialogData) .addCell(0, 0, { @@ -138,7 +143,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; @@ -172,7 +177,7 @@ async function editAction(currentKey?: string) { const key = ev.target as HTMLElement; const win = dialog.window; key.textContent = `[${getString( - "prefs-rule-edit-shortcut-placeholder", + "prefs-action-edit-shortcut-placeholder" )}]`; dialogData.shortcut = ""; const keyDownListener = (e: KeyboardEvent) => { @@ -211,17 +216,92 @@ async function editAction(currentKey?: string) { }, }, { - tag: "input", - attributes: { - "data-bind": "menu", - "data-prop": "value", - }, - properties: { - placeholder: getString("prefs-action-edit-menu-placeholder"), - }, + tag: "div", styles: { - width: "fit-content", + display: "grid", + gridTemplateColumns: "1fr 3fr", + rowGap: "10px", + columnGap: "5px", }, + children: [ + { + tag: "input", + attributes: { + "data-bind": "menu", + "data-prop": "value", + }, + properties: { + placeholder: getString("prefs-action-edit-menu-placeholder"), + }, + styles: { + width: "fit-content", + gridColumnStart: "1", + 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-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", + }, + }, + ], }, { tag: "label", @@ -234,7 +314,6 @@ async function editAction(currentKey?: string) { tag: "input", properties: { type: "checkbox", - checked: action.enabled, }, attributes: { "data-bind": "enabled", @@ -277,8 +356,13 @@ async function editAction(currentKey?: string) { enabled: dialogData.enabled, menu: dialogData.menu, name: dialogData.name, + showInMenu: { + item: dialogData.showInMenuItem, + reader: dialogData.showInMenuReader, + readerAnnotation: dialogData.showInMenuReaderAnnotation, + }, }, - currentKey, + currentKey ); edited = true; } @@ -301,7 +385,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 }>; diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 9c70960..19911af 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -2,12 +2,17 @@ import { TagElementProps } from "zotero-plugin-toolkit/dist/tools/ui"; import { config } from "../../package.json"; import { getString } from "../utils/locale"; import { getPref } from "../utils/prefs"; -import { ActionData } from "../utils/actions"; +import { ActionData, ActionShowInMenu } from "../utils/actions"; import { getCurrentItems } from "../utils/items"; -export { initMenu, initReaderMenu, buildItemMenu }; +export { + initItemMenu, + initReaderMenu, + initReaderAnnotationMenu, + buildItemMenu, +}; -function initMenu(win: Window) { +function initItemMenu(win: Window) { ztoolkit.Menu.register("item", { tag: "menu", popupId: `${config.addonRef}-item-popup`, @@ -36,7 +41,7 @@ function initMenu(win: Window) { }, ], }, - win.document.querySelector("popupset")!, + win.document.querySelector("popupset")! ); } @@ -104,7 +109,7 @@ function initReaderMenu() { classList: ["dropmarker"], }, ], - }), + }) ); append( ztoolkit.UI.createElement(doc, "style", { @@ -112,15 +117,37 @@ function initReaderMenu() { properties: { textContent: readerButtonCSS, }, - }), + }) ); }); } +function initReaderAnnotationMenu() { + Zotero.Reader.registerEventListener( + "createAnnotationContextMenu", + (event) => { + const { append, params, reader } = event; + const actions = getActionsByMenu("readerAnnotation"); + for (const action of actions) { + append({ + label: action.menu!, + onCommand: () => { + triggerMenuCommand( + action.key, + reader._item.libraryID, + ...params.ids + ); + }, + }); + } + } + ); +} + function buildItemMenu(win: Window, target: "item" | "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) { @@ -128,7 +155,7 @@ function buildItemMenu(win: Window, target: "item" | "reader") { } // Add new children let elemProp: TagElementProps; - const enabledActions = getActionsByMenu(); + const enabledActions = getActionsByMenu(target); if (enabledActions.length === 0) { elemProp = { tag: "menuitem", @@ -159,16 +186,22 @@ function buildItemMenu(win: Window, target: "item" | "reader") { }; }), }, - popup, + popup ); } } -function getActionsByMenu() { +function getActionsByMenu(target: ActionShowInMenu) { const sortBy = (getPref("menuSortBy") || "menu") as keyof ActionData; return Array.from(addon.data.actions.map.keys()) .map((k) => Object.assign({}, addon.data.actions.map.get(k), { key: k })) - .filter((action) => action && action.menu && action.enabled) + .filter( + (action) => + action && + action.menu && + action.enabled && + (!action.showInMenu || action.showInMenu[target] !== false) + ) .sort((x, y) => { if (!x && !y) { return 0; @@ -181,13 +214,24 @@ function getActionsByMenu() { } return ((x[sortBy] as string) || "").localeCompare( (y[sortBy] || "") as string, - Zotero.locale, + Zotero.locale ); }); } -async function triggerMenuCommand(key: string) { - const items = getCurrentItems(); +async function triggerMenuCommand( + key: string, + libraryID?: number, + ...itemKeys: string[] +) { + let items: Zotero.Item[]; + if (libraryID && itemKeys) { + items = itemKeys + .map((key) => Zotero.Items.getByLibraryAndKey(libraryID, key)) + .filter((item) => item) as Zotero.Item[]; + } else { + items = getCurrentItems(); + } // Trigger action for all items await addon.api.actionManager.dispatchActionByKey(key, { itemIDs: items.map((item) => item.id), diff --git a/src/utils/actions.ts b/src/utils/actions.ts index 4fa5878..071dcdf 100644 --- a/src/utils/actions.ts +++ b/src/utils/actions.ts @@ -12,6 +12,7 @@ export { ActionMap, ActionData, ActionEventTypes, + ActionShowInMenu, ActionOperationTypes, ActionArgs, initActions, @@ -45,6 +46,8 @@ enum ActionOperationTypes { "triggerAction", } +type ActionShowInMenu = "item" | "reader" | "readerAnnotation"; + interface ActionData { event: ActionEventTypes; operation: T; @@ -53,6 +56,7 @@ interface ActionData { enabled?: boolean; menu?: string; name?: string; + showInMenu?: Partial>; } const defaultActions: ActionMap = new Map([ @@ -88,6 +92,11 @@ const emptyAction: ActionData = { enabled: true, menu: "", name: "", + showInMenu: { + item: false, + reader: false, + readerAnnotation: false, + }, }; type ActionMap = Map; @@ -101,7 +110,7 @@ function initActions() { addon.data.actions.map = new ztoolkit.LargePref( `${config.prefsPrefix}.rules`, `${config.prefsPrefix}.rules.`, - "parser", + "parser" ).asMapLike() as ActionMap; if (!getPref("rulesInit")) { for (const key of defaultActions.keys()) { @@ -149,7 +158,7 @@ function initActions() { function updateCachedActionKeys() { addon.data.actions.cachedKeys = Array.from( - addon.data.actions.map.keys(), + addon.data.actions.map.keys() ).sort((a, b) => { const actionA = addon.data.actions.map.get(a); const actionB = addon.data.actions.map.get(b); @@ -160,13 +169,13 @@ function updateCachedActionKeys() { actionA[ addon.data.prefs.columns[addon.data.prefs.columnIndex] .dataKey as keyof ActionData - ] || "", + ] || "" ); const valueB = String( actionB[ addon.data.prefs.columns[addon.data.prefs.columnIndex] .dataKey as keyof ActionData - ] || "", + ] || "" ); return addon.data.prefs.columnAscending @@ -222,7 +231,7 @@ async function applyAction(action: ActionData, args: ActionArgs) { } } message = `Toggle tag ${tags.join(",")} to item ${item?.getField( - "title", + "title" )}`; break; } @@ -269,7 +278,7 @@ async function applyAction(action: ActionData, args: ActionArgs) { const actions = getActions(); // Find the action by name const nextAction = Object.values(actions).find( - (_action) => _action.name === action.data, + (_action) => _action.name === action.data ); if (nextAction) { await applyAction(nextAction, args); @@ -290,7 +299,7 @@ async function applyAction(action: ActionData, args: ActionArgs) { function getActions(): Record; function getActions(key: string): ActionData | undefined; function getActions( - key?: string, + key?: string ): Record | ActionData | undefined { if (!key) { const map = addon.data.actions.map;