diff --git a/README.md b/README.md index 6594e38..b82de37 100644 --- a/README.md +++ b/README.md @@ -225,6 +225,8 @@ if (items?.length > 0) { +- `collection`: The target collection object, only available when triggered by collection menu. + - `require`: The `require` function to import global variables. Use `const window = require('window')` to import the `window` variable.
diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl index d95a3aa..7b863ac 100644 --- a/addon/locale/en-US/addon.ftl +++ b/addon/locale/en-US/addon.ftl @@ -8,6 +8,7 @@ prefs-action-shortcut = Shortcut prefs-action-enabled = Enabled prefs-action-menu = Menu Label prefs-action-showInMenuItem = In Item Menu +prefs-action-showInMenuCollection = In Collection Menu prefs-action-showInMenuReader = In Reader Menu prefs-action-showInMenuReaderAnnotation = In Annotation Menu diff --git a/addon/locale/it-IT/addon.ftl b/addon/locale/it-IT/addon.ftl index cd18901..d2c96de 100644 --- a/addon/locale/it-IT/addon.ftl +++ b/addon/locale/it-IT/addon.ftl @@ -8,6 +8,7 @@ prefs-action-shortcut = Scorciatoia prefs-action-enabled = Attivo prefs-action-menu = Etichetta menu prefs-action-showInMenuItem = In Item Menu +prefs-action-showInMenuCollection = In Collection 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 eab0a74..b833a32 100644 --- a/addon/locale/zh-CN/addon.ftl +++ b/addon/locale/zh-CN/addon.ftl @@ -8,6 +8,7 @@ prefs-action-shortcut = 快捷键 prefs-action-enabled = 启用 prefs-action-menu = 菜单项 prefs-action-showInMenuItem = 条目菜单中 +prefs-action-showInMenuCollection = 分类菜单中 prefs-action-showInMenuReader = 阅读器菜单中 prefs-action-showInMenuReaderAnnotation = 注释菜单中 diff --git a/src/modules/edit.ts b/src/modules/edit.ts index 4362add..e105cce 100644 --- a/src/modules/edit.ts +++ b/src/modules/edit.ts @@ -23,6 +23,7 @@ async function editAction(currentKey?: string) { new KeyModifier(action.shortcut || "").getLocalized() || `[${getString("prefs-action-edit-shortcut-empty")}]`; dialogData.showInMenuItem = !(action.showInMenu?.item === false); + dialogData.showInMenuCollection = !(action.showInMenu?.collection === false); dialogData.showInMenuReader = !(action.showInMenu?.reader === false); dialogData.showInMenuReaderAnnotation = !( action.showInMenu?.readerAnnotation === false @@ -259,6 +260,26 @@ async function editAction(currentKey?: string) { 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", @@ -358,6 +379,7 @@ async function editAction(currentKey?: string) { name: dialogData.name, showInMenu: { item: dialogData.showInMenuItem, + collection: dialogData.showInMenuCollection, reader: dialogData.showInMenuReader, readerAnnotation: dialogData.showInMenuReaderAnnotation, }, @@ -376,6 +398,7 @@ async function editAction(currentKey?: string) { break; } closeWindow(addon.data.prefs.editorWindow!); + addon.data.prefs.window?.focus(); return edited; } @@ -410,6 +433,7 @@ async function openEditorWindow(content: string) { }); editor.setValue(content); await unloadLock.promise; + addon.data.prefs.dialogWindow?.focus(); return modifiedContent; } diff --git a/src/modules/menu.ts b/src/modules/menu.ts index 273b69b..f3dbf1c 100644 --- a/src/modules/menu.ts +++ b/src/modules/menu.ts @@ -3,7 +3,7 @@ import { config } from "../../package.json"; import { getString } from "../utils/locale"; import { getPref } from "../utils/prefs"; import { ActionData, ActionShowInMenu } from "../utils/actions"; -import { getCurrentItems } from "../utils/items"; +import { getCurrentItems, getItemsByKey } from "../utils/items"; export { initItemMenu, @@ -28,6 +28,21 @@ function initItemMenu(win: Window) { ], }); + ztoolkit.Menu.register("collection", { + tag: "menu", + popupId: `${config.addonRef}-collection-popup`, + label: getString("menupopup-label"), + icon: `chrome://${config.addonRef}/content/icons/favicon.png`, + onpopupshowing: `Zotero.${config.addonInstance}.hooks.onMenuEvent("showing", { window, target: "collection" })`, + children: [ + { + tag: "menuitem", + label: getString("menupopup-placeholder"), + disabled: true, + }, + ], + }); + ztoolkit.UI.appendElement( { tag: "menupopup", @@ -41,7 +56,7 @@ function initItemMenu(win: Window) { }, ], }, - win.document.querySelector("popupset")!, + win.document.querySelector("popupset")! ); } @@ -109,7 +124,7 @@ function initReaderMenu() { classList: ["dropmarker"], }, ], - }), + }) ); append( ztoolkit.UI.createElement(doc, "style", { @@ -117,7 +132,7 @@ function initReaderMenu() { properties: { textContent: readerButtonCSS, }, - }), + }) ); }); } @@ -132,25 +147,23 @@ function initReaderAnnotationMenu() { append({ label: action.menu!, onCommand: () => { - triggerMenuCommand( - action.key, - reader._item.libraryID, - ...params.ids, + triggerMenuCommand(action.key, () => + getItemsByKey(reader._item.libraryID, ...params.ids) ); }, }); } - }, + } ); } -function buildItemMenu(win: Window, target: "item" | "reader") { +function buildItemMenu(win: Window, target: "item" | "collection" | "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) { + while (popup?.firstChild) { popup.removeChild(popup.firstChild); } // Add new children @@ -161,34 +174,38 @@ function buildItemMenu(win: Window, target: "item" | "reader") { tag: "menuitem", properties: { label: getString("menupopup-placeholder"), + }, + attributes: { disabled: true, }, }; } else { - ztoolkit.UI.appendElement( - { - tag: "fragment", - children: enabledActions.map((action) => { - return { - tag: "menuitem", - properties: { - label: - action.menu + (action.shortcut ? ` (${action.shortcut})` : ""), - }, - listeners: [ - { - type: "command", - listener: (event) => { - triggerMenuCommand(action.key); - }, + elemProp = { + tag: "fragment", + children: enabledActions.map((action) => { + return { + tag: "menuitem", + properties: { + label: + action.menu + (action.shortcut ? ` (${action.shortcut})` : ""), + }, + listeners: [ + { + type: "command", + listener: (event) => { + triggerMenuCommand( + action.key, + () => getCurrentItems(target), + target === "collection" + ); }, - ], - }; - }), - }, - popup, - ); + }, + ], + }; + }), + }; } + ztoolkit.UI.appendElement(elemProp, popup); } function getActionsByMenu(target: ActionShowInMenu) { @@ -200,7 +217,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) { @@ -214,32 +231,34 @@ function getActionsByMenu(target: ActionShowInMenu) { } return ((x[sortBy] as string) || "").localeCompare( (y[sortBy] || "") as string, - Zotero.locale, + Zotero.locale ); }); } async function triggerMenuCommand( key: string, - libraryID?: number, - ...itemKeys: string[] + getItems: () => + | Zotero.DataObject[] + | Promise = getCurrentItems, + withCollection: boolean = false ) { - 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(); + const items = await getItems(); + let collection: Zotero.Collection | undefined = undefined; + if (withCollection) { + collection = Zotero.getActiveZoteroPane().getSelectedCollection(); } + // Trigger action for all items await addon.api.actionManager.dispatchActionByKey(key, { itemIDs: items.map((item) => item.id), + collectionID: collection?.id, }); // Trigger action for each item for (const item of items) { await addon.api.actionManager.dispatchActionByKey(key, { itemID: item.id, + collectionID: collection?.id, }); } } diff --git a/src/utils/actions.ts b/src/utils/actions.ts index abe8f38..52660c3 100644 --- a/src/utils/actions.ts +++ b/src/utils/actions.ts @@ -46,7 +46,7 @@ enum ActionOperationTypes { "triggerAction", } -type ActionShowInMenu = "item" | "reader" | "readerAnnotation"; +type ActionShowInMenu = "item" | "collection" | "reader" | "readerAnnotation"; interface ActionData { event: ActionEventTypes; @@ -94,6 +94,7 @@ const emptyAction: ActionData = { name: "", showInMenu: { item: false, + collection: false, reader: false, readerAnnotation: false, }, @@ -103,6 +104,8 @@ type ActionMap = Map; type ActionArgs = { itemID?: number; + itemIDs?: number[]; + collectionID?: number; [key: string]: any; }; @@ -110,7 +113,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()) { @@ -158,7 +161,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); @@ -169,13 +172,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 @@ -231,7 +234,7 @@ async function applyAction(action: ActionData, args: ActionArgs) { } } message = `Toggle tag ${tags.join(",")} to item ${item?.getField( - "title", + "title" )}`; break; } @@ -240,8 +243,13 @@ async function applyAction(action: ActionData, args: ActionArgs) { const _require = (module: string) => ztoolkit.getGlobal(module); const items = Zotero.Items.get(args.itemIDs || []) || null; - let paramList: any[] = [item, items, _require]; - let paramSign = "item, items, require"; + let collection: Zotero.Collection | false = false; + if (args.collectionID) { + collection = Zotero.Collections.get(args.collectionID) as Zotero.Collection | false; + } + + let paramList: any[] = [item, items, collection, _require]; + let paramSign = "item, items, collection, require"; switch (action.event) { case ActionEventTypes.mainWindowLoad: case ActionEventTypes.mainWindowUnload: @@ -278,7 +286,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); @@ -299,7 +307,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; diff --git a/src/utils/items.ts b/src/utils/items.ts index d3cde89..245e2a0 100644 --- a/src/utils/items.ts +++ b/src/utils/items.ts @@ -1,12 +1,29 @@ -export { getCurrentItems }; +import { ActionShowInMenu } from "./actions"; -function getCurrentItems() { - let items = [] as Zotero.Item[]; - switch (Zotero_Tabs.selectedType) { - case "library": { +export { getCurrentItems, getItemsByKey }; + +async function getCurrentItems(type?: ActionShowInMenu) { + let items = [] as Zotero.DataObject[]; + if (!type) { + type = getCurrentTargetType(); + } + switch (type) { + case "item": { items = Zotero.getActiveZoteroPane().getSelectedItems(); break; } + case "collection": { + const collection = Zotero.getActiveZoteroPane().getSelectedCollection(); + if (collection) { + items = collection?.getChildItems() as Zotero.Item[]; + } else { + const libraryID = Zotero.getActiveZoteroPane().getSelectedLibraryID(); + if (libraryID) { + items = await Zotero.Items.getAll(libraryID); + } + } + break; + } case "reader": { const reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); if (reader) { @@ -17,3 +34,27 @@ function getCurrentItems() { } return items; } + +function getItemsByKey(libraryID: number, ...keys: string[]) { + const items = [] as Zotero.DataObject[]; + for (const key of keys) { + const item = Zotero.Items.getByLibraryAndKey(libraryID, key); + if (item) { + items.push(item); + } + } + return items; +} + +function getCurrentTargetType() { + switch (Zotero_Tabs.selectedType) { + case "library": { + return "item"; + } + case "reader": { + return "reader"; + } + default: + return "item"; + } +}