From 99dbdfa9ac020c464720f787ba75070acbbbf1d5 Mon Sep 17 00:00:00 2001 From: Bogdan Abaev Date: Mon, 25 May 2026 14:51:31 -0700 Subject: [PATCH] context-aware consolidated quick-copy command Menu > Copy and Cmd/Ctrl + C are now one entry point for context-aware copying of selected items. Updated behavior: - when a regular item is selected, copy the citation - when a note or annotation is selected, copy them via quickCopy in note format - when a reader tab is open and nothing is selected, copy the citation of the top-level item - when a reader tab is open and an annotation is selected, copy the annotation - when a reader tab is open and text is selected, select the text - no changes here. Depending on which items are selected, Menu > Copy command labels will be updated to Menu > Copy Citation or Menu > Copy Note, or Menu > Copy Annotations. Added helpers to the reader and ZoteroPane to indicate what Cmd/Ctrl+C is supposed to do based on the selection (classifyCopyContext), so we can determine what label to use. Removed old Cmd-Shift-A "Copy Citation" shortcut. Updated preferences Export panel text, renamed preferences to "copyAsBibliography" and "copyAsExport" for clarity. --- chrome/content/zotero/elements/noteEditor.js | 27 +++ chrome/content/zotero/fileInterface.js | 12 +- .../preferences/preferences_advanced.xhtml | 12 +- .../zotero/preferences/preferences_export.jsx | 32 ++- .../content/zotero/standalone/standalone.js | 68 ++----- chrome/content/zotero/xpcom/prefs.js | 17 +- chrome/content/zotero/xpcom/reader.js | 34 ++++ .../zotero/xpcom/utilities_internal.js | 6 +- chrome/content/zotero/zoteroPane.js | 186 ++++++++++++++---- chrome/content/zotero/zoteroPane.xhtml | 36 +--- chrome/locale/en-US/zotero/preferences.ftl | 6 + chrome/locale/en-US/zotero/zotero.ftl | 11 +- chrome/locale/en-US/zotero/zotero.properties | 2 - defaults/preferences/zotero.js | 5 +- test/tests/fileInterfaceTest.js | 2 +- test/tests/quickCopyTest.js | 151 +++++++++++++- 16 files changed, 458 insertions(+), 149 deletions(-) diff --git a/chrome/content/zotero/elements/noteEditor.js b/chrome/content/zotero/elements/noteEditor.js index bafe54dffc..3a2ee687f4 100644 --- a/chrome/content/zotero/elements/noteEditor.js +++ b/chrome/content/zotero/elements/noteEditor.js @@ -89,6 +89,33 @@ 'zotero/item': event.dataTransfer.getData('zotero/item') }, this._iframe.contentWindow); }, true); + // Same workaround for paste: the iframe's clipboardData.getData() + // returns empty for non-standard MIME types (zotero/annotation, + // zotero/item), so read them from the system clipboard here and + // proxy into the iframe scope. + this._iframe.contentWindow.addEventListener('paste', (_event) => { + let read = (flavor) => { + try { + let xfer = Components.classes['@mozilla.org/widget/transferable;1'] + .createInstance(Components.interfaces.nsITransferable); + xfer.init(null); + xfer.addDataFlavor(flavor); + Components.classes['@mozilla.org/widget/clipboard;1'] + .getService(Components.interfaces.nsIClipboard) + .getData(xfer, Components.interfaces.nsIClipboard.kGlobalClipboard); + let str = {}; + xfer.getTransferData(flavor, str, {}); + return str.value.QueryInterface(Components.interfaces.nsISupportsString).data; + } + catch (e) { + return ''; + } + }; + this._iframe.contentWindow.wrappedJSObject.pastedData = Components.utils.cloneInto({ + 'zotero/annotation': read('zotero/annotation'), + 'zotero/item': read('zotero/item') + }, this._iframe.contentWindow); + }, true); this._iframe.docShell.windowDraggingAllowed = true; this._initialized = true; }); diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js index 857e9cfa09..18862bbfae 100644 --- a/chrome/content/zotero/fileInterface.js +++ b/chrome/content/zotero/fileInterface.js @@ -248,7 +248,7 @@ var Zotero_File_Interface = new function () { */ this.writeToClipboard = function (content) { if (!content) return; - let { text, html } = content; + let { text, html, annotations } = content; let transferable = Components.classes['@mozilla.org/widget/transferable;1'] .createInstance(Components.interfaces.nsITransferable); @@ -270,6 +270,16 @@ var Zotero_File_Interface = new function () { transferable.addDataFlavor('text/plain'); transferable.setTransferData('text/plain', str, text.length * 2); } + // Rich annotation payload for the note editor's paste handler; without + // it, pasted annotations land as plain text with unresolved {citation} + // placeholders. + if (annotations) { + let str = Components.classes['@mozilla.org/supports-string;1'] + .createInstance(Components.interfaces.nsISupportsString); + str.data = annotations; + transferable.addDataFlavor('zotero/annotation'); + transferable.setTransferData('zotero/annotation', str, annotations.length * 2); + } clipboardService.setData(transferable, null, Components.interfaces.nsIClipboard.kGlobalClipboard); }; diff --git a/chrome/content/zotero/preferences/preferences_advanced.xhtml b/chrome/content/zotero/preferences/preferences_advanced.xhtml index cc0901cfcc..f97a178e97 100644 --- a/chrome/content/zotero/preferences/preferences_advanced.xhtml +++ b/chrome/content/zotero/preferences/preferences_advanced.xhtml @@ -168,16 +168,16 @@ - &zotero.preferences.keys.copySelectedItemCitationsToClipboard; + - - &zotero.preferences.keys.copySelectedItemsToClipboard; + - &zotero.preferences.keys.toggleAllRead; diff --git a/chrome/content/zotero/preferences/preferences_export.jsx b/chrome/content/zotero/preferences/preferences_export.jsx index 2a1b315f06..040f46638e 100644 --- a/chrome/content/zotero/preferences/preferences_export.jsx +++ b/chrome/content/zotero/preferences/preferences_export.jsx @@ -505,22 +505,20 @@ Zotero_Preferences.Export = { updateQuickCopyInstructions: function () { - var prefix = Zotero.isMac ? Zotero.getString('general.keys.cmdShift') : Zotero.getString('general.keys.ctrlShift'); - - var key = Zotero.Prefs.get('keys.copySelectedItemsToClipboard'); - var str = Zotero.getString('zotero.preferences.export.quickCopy.instructions', prefix + key); - var instr = document.getElementById('quickCopy-instructions'); - while (instr.hasChildNodes()) { - instr.removeChild(instr.firstChild); - } - instr.appendChild(document.createTextNode(str)); - - key = Zotero.Prefs.get('keys.copySelectedItemCitationsToClipboard'); - str = Zotero.getString('zotero.preferences.export.quickCopy.citationInstructions', prefix + key); - instr = document.getElementById('quickCopy-citationInstructions'); - while (instr.hasChildNodes()) { - instr.removeChild(instr.firstChild); - } - instr.appendChild(document.createTextNode(str)); + var shiftPrefix = Zotero.getString("command-or-control") + "+" + Zotero.getString("general-key-shift") + "+"; + + document.l10n.setAttributes( + document.getElementById('quickCopy-instructions'), + 'preferences-quickCopy-instructions', + { + bibShortcut: shiftPrefix + Zotero.Prefs.get('keys.copyAsBibliography'), + exportShortcut: shiftPrefix + Zotero.Prefs.get('keys.copyAsExport') + } + ); + document.l10n.setAttributes( + document.getElementById('quickCopy-citationInstructions'), + 'preferences-quickCopy-citationInstructions', + { citationShortcut: Zotero.getString("command-or-control") + "+C" } + ); } }; diff --git a/chrome/content/zotero/standalone/standalone.js b/chrome/content/zotero/standalone/standalone.js index dfff443f2c..e4dad3c461 100644 --- a/chrome/content/zotero/standalone/standalone.js +++ b/chrome/content/zotero/standalone/standalone.js @@ -64,7 +64,7 @@ const ZoteroStandalone = new function () { setTimeout(async () => { // Item and other things might not be loaded yet when reopening tabs await Zotero.Schema.schemaUpdatePromise; - this.updateQuickCopyOptions(); + ZoteroPane.updateQuickCopyMenu(); }, 0); // "library", "reader" or "note" let type = extraData[ids[0]].type; @@ -95,12 +95,10 @@ const ZoteroStandalone = new function () { return Zotero.initializationPromise; }) .then(async function () { - document.getElementById('key_copyCitation') - .setAttribute('key', Zotero.Keys.getKeyForCommand('copySelectedItemCitationsToClipboard')); document.getElementById('key_copyBibliography') - .setAttribute('key', Zotero.Keys.getKeyForCommand('copySelectedItemsToClipboard')); + .setAttribute('key', Zotero.Keys.getKeyForCommand('copyAsBibliography')); document.getElementById('key_copyExport') - .setAttribute('key', Zotero.Keys.getKeyForCommand('copySelectedItemsToClipboardAsExport')); + .setAttribute('key', Zotero.Keys.getKeyForCommand('copyAsExport')); document.getElementById('key_showTabsMenu') .setAttribute('key', Zotero.Keys.getKeyForCommand('showTabsMenu')); // Force menu to update with shortcut key at startup -- as of fx128, this is necessary @@ -236,9 +234,10 @@ const ZoteroStandalone = new function () { }; this.onEditMenuOpen = function (event) { - this.updateQuickCopyOptions(); // goUpdateGlobalEditMenuItems(true) is necessary to update Edit menu when contenteditable is focused + // must happen before updateQuickCopyMenu so it knows the state of standard copy command window.goUpdateGlobalEditMenuItems(true); + ZoteroPane.updateQuickCopyMenu(); this.onUpdateCustomMenus(event, 'edit'); }; @@ -337,53 +336,18 @@ const ZoteroStandalone = new function () { }; - this.updateQuickCopyOptions = function () { - var selected = []; - - let win = Zotero.getMainWindow(); - if (win) { - try { - selected = win.ZoteroPane.getSelectedItems(); - } - catch (e) { - } - win.ZoteroPane.updateQuickCopyCommands(selected); + /** + * Handle consolidated Edit > Copy menu clicked. + * Native cmd_copy when text is selected, otherwise delegate to copySelectedItemsToClipboard + * that handles copying of items/notes/annotation in library and reader tabs. + */ + this.handleCopyMenuCommand = function () { + if (ZoteroPane.classifyCopyContext() === 'text') { + // eslint-disable-next-line no-undef + goDoCommand('cmd_copy'); } - - var exportingNotes = selected.every(item => item.isNote() || item.isAttachment()); - var exportingAnnotations = selected.every(item => item.isAnnotation()); - - var copyCitation = document.getElementById('menu_copyCitation'); - var copyBibliography = document.getElementById('menu_copyBibliography'); - var copyExport = document.getElementById('menu_copyExport'); - var copyNote = document.getElementById('menu_copyNote'); - var copyAnnotation = document.getElementById('menu_copyAnnotation'); - - copyCitation.hidden = !selected.length || exportingNotes || exportingAnnotations; - copyBibliography.hidden = !selected.length || exportingNotes || exportingAnnotations; - copyExport.hidden = !selected.length || exportingNotes || exportingAnnotations; - copyNote.hidden = !selected.length || !exportingNotes; - copyAnnotation.hidden = !selected.length || !exportingAnnotations; - document.l10n.setAttributes(copyAnnotation, "menu-edit-copy-annotation", { count: selected.length }); - - // Label the export menu with the selected translator name - var exportFormat = Zotero.QuickCopy.getFormat({ mode: 'export' }); - if (exportFormat.id) { - try { - let translator = Zotero.Translators.get(exportFormat.id); - if (translator) { - copyExport.label = Zotero.getString('quickCopy.copyAs', translator.label); - } - else { - copyExport.hidden = true; - } - } - catch (e) { - if (!(e instanceof Zotero.Exception.UnloadedDataException && e.dataType == 'translators')) { - Zotero.logError(e); - } - copyExport.hidden = true; - } + else { + ZoteroPane.copySelectedItemsToClipboard(true, 'bibliography'); } }; diff --git a/chrome/content/zotero/xpcom/prefs.js b/chrome/content/zotero/xpcom/prefs.js index 9a2540a1c3..f297ae03aa 100644 --- a/chrome/content/zotero/xpcom/prefs.js +++ b/chrome/content/zotero/xpcom/prefs.js @@ -46,7 +46,7 @@ Zotero.Prefs = new function () { // Process pref version updates var fromVersion = this.get('prefVersion'); - var toVersion = 23; + var toVersion = 24; if (!fromVersion) { this.set('prefVersion', toVersion); } @@ -261,6 +261,21 @@ Zotero.Prefs = new function () { this.clear('export.quickCopy.locale'); } break; + + case 24: + // Separate Cmd+Shift+A shortcut to copy citation is removed - now handled with Cmd/Ctrl + C + this.clear('keys.copySelectedItemCitationsToClipboard'); + // Rename copySelectedItemsToClipboard to copyAsBibliography for clarity + if (this.prefHasUserValue("keys.copySelectedItemsToClipboard")) { + this.set("keys.copyAsBibliography", this.get("keys.copySelectedItemsToClipboard")); + this.clear("keys.copySelectedItemsToClipboard"); + } + // Rename copySelectedItemsToClipboardAsExport to copyAsExport for consistency with the above + if (this.prefHasUserValue("keys.copySelectedItemsToClipboardAsExport")) { + this.set("keys.copyAsExport", this.get("keys.copySelectedItemsToClipboardAsExport")); + this.clear("keys.copySelectedItemsToClipboardAsExport"); + } + break; } } this.set('prefVersion', toVersion); diff --git a/chrome/content/zotero/xpcom/reader.js b/chrome/content/zotero/xpcom/reader.js index 7c48ae65bb..39d8c67d77 100644 --- a/chrome/content/zotero/xpcom/reader.js +++ b/chrome/content/zotero/xpcom/reader.js @@ -110,6 +110,40 @@ class ReaderInstance { return state ? JSON.parse(JSON.stringify(state)) : undefined; } + /** + * Keys of the annotations currently selected in the reader. + * + * @returns {String[]} + */ + getSelectedAnnotationIDs() { + let ids = this._internalReader?._state?.selectedAnnotationIDs; + return ids ? Array.from(ids) : []; + } + + /** + * What is currently selected in the reader's view(s), used to predict what + * a copy action will produce so the Edit menu and Cmd+C handler can label + * and behave accordingly. + * + * @returns {'annotation' | 'text' | null} + */ + getSelectionType() { + if (!this._internalReader) return null; + + if (this.getSelectedAnnotationIDs().length > 0) { + return 'annotation'; + } + + let view = this._internalReader._lastViewPrimary ? this._internalReader._primaryView : this._internalReader._secondaryView; + if (!view || !view._iframeWindow) return null; + let sel = view._iframeWindow.getSelection(); + if (sel && !sel.isCollapsed && String(sel).length > 0) { + return 'text'; + } + + return null; + } + async migrateMendeleyColors(libraryID, annotations) { let colorMap = new Map(); colorMap.set('#fff5ad', '#ffd400'); diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 6c04b30902..5e419fee2f 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -3425,7 +3425,11 @@ Zotero.Utilities.Internal.onDragItems = function (event, itemIDs, dragImage = ev // Get Quick Copy format for current URL (set via /ping from connector) // and the items being dragged. getContentFromItems handles the rest // (annotation wrapping, note-citation reformatting, regular-item filter). - let format = Zotero.QuickCopy.getFormat({ items }); + // Shift-Drag means the user wants a citation cluster, so use bibliography + // mode then regardless of the pref + let format = event.shiftKey + ? Zotero.QuickCopy.getFormat({ mode: 'bibliography', items }) + : Zotero.QuickCopy.getFormat({ items }); Zotero.debug("Dragging with format " + JSON.stringify(format)); try { diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js index 1dba92c990..e446147285 100644 --- a/chrome/content/zotero/zoteroPane.js +++ b/chrome/content/zotero/zoteroPane.js @@ -1361,11 +1361,10 @@ var ZoteroPane = new function () { break; // Handled by s in standalone.js, pointing to s in zoteroPane.xul, - // which are enabled or disabled by this.updateQuickCopyCommands(), called by + // which are enabled or disabled by this.updateQuickCopyMenu(), called by // this.itemSelected() - case 'copySelectedItemCitationsToClipboard': - case 'copySelectedItemsToClipboard': - case 'copySelectedItemsToClipboardAsExport': + case 'copyAsBibliography': + case 'copyAsExport': return; default: @@ -1901,10 +1900,9 @@ var ZoteroPane = new function () { this.itemPane.editable = this.collectionsView.editable; this.itemPane.updateItemPaneButtons(selectedItems); - // Tab selection observer in standalone.js makes sure that - // updateQuickCopyCommands is called + // Keep the copy command + menu state current as the library selection changes if (Zotero_Tabs.selectedType == 'library') { - this.updateQuickCopyCommands(selectedItems); + this.updateQuickCopyMenu(); } // Check if selection has actually changed. The onselect event that calls this @@ -1996,29 +1994,65 @@ var ZoteroPane = new function () { }; /** - * Update the elements that control the shortcut keys and the enabled state of the - * "Copy Citation"/"Copy Bibliography"/"Copy as"/"Copy Note" menu options. When disabled, the shortcuts are - * still caught in handleKeyPress so that we can show an alert about not having references selected. + * Refresh the Edit-menu copy entries to match the current context. + * Sets `menu_copy` item's label and disabled state (Cmd/Ctrl+C), as well as + * Copy Bibliography and Export as ... disabled state. */ - this.updateQuickCopyCommands = function (selectedItems) { - let canCopyBibliography = false; - let canCopyExport = false; - // If all items are notes/attachments and at least one note is not empty - if (selectedItems.every(item => item.isNote() || item.isAttachment())) { - if (selectedItems.some(item => item.note)) { - canCopyBibliography = true; - canCopyExport = true; - } - } - else { - canCopyBibliography = selectedItems.some(item => item.isRegularItem() || item.isAnnotation()); - canCopyExport = selectedItems.length > 0; + this.updateQuickCopyMenu = function () { + let selected = this.getSelectedItems(); + + // Consolidated Copy entry: reflect what Cmd+C would do in this context + let copyItem = document.getElementById('menu_copy'); + let action = this.classifyCopyContext(); + copyItem.disabled = false; + let count = selected.length; + document.l10n.setAttributes(copyItem, `menu-edit-copy-${action}`, { count }); + // Default Copy -- mirror cmd_copy's enabled state + if (action === "text") { + copyItem.disabled = document.getElementById('cmd_copy').hasAttribute('disabled'); } - document.getElementById('cmd_zotero_copyCitation').setAttribute('disabled', !canCopyBibliography); - document.getElementById('cmd_zotero_copyBibliography').setAttribute('disabled', !canCopyBibliography); - document.getElementById('cmd_zotero_copyExport').setAttribute('disabled', !canCopyExport); - document.getElementById('cmd_zotero_copyAnnotation').setAttribute('disabled', !canCopyBibliography); + // Copy Bibliography / Copy as ...: + // - Library tab: only available when the selection contains regular items. + // - Reader tab: available when the open item has a parent regular item + // to act on. + let hideBibAndExport; + if (Zotero_Tabs.selectedType === 'reader') { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + let hasMetadata = Zotero.Items.get(reader.itemID).parentItem; + hideBibAndExport = !hasMetadata; + } + else { + let exportingNotes = selected.every(item => item.isNote() || item.isAttachment()); + let exportingAnnotations = selected.every(item => item.isAnnotation()); + hideBibAndExport = !selected.length || exportingNotes || exportingAnnotations; + } + let copyBibliography = document.getElementById('menu_copyBibliography'); + let copyExport = document.getElementById('menu_copyExport'); + copyBibliography.hidden = hideBibAndExport; + copyExport.hidden = hideBibAndExport; + document.getElementById('cmd_zotero_copyBibliography').setAttribute('disabled', hideBibAndExport); + document.getElementById('cmd_zotero_copyExport').setAttribute('disabled', hideBibAndExport); + + // Label "Copy as ..." with the selected export translator's name + let exportFormat = Zotero.QuickCopy.getFormat({ mode: 'export' }); + if (exportFormat.id) { + try { + let translator = Zotero.Translators.get(exportFormat.id); + if (translator) { + copyExport.label = Zotero.getString('quickCopy.copyAs', translator.label); + } + else { + copyExport.hidden = true; + } + } + catch (e) { + if (!(e instanceof Zotero.Exception.UnloadedDataException && e.dataType == 'translators')) { + Zotero.logError(e); + } + copyExport.hidden = true; + } + } }; @@ -2698,26 +2732,95 @@ var ZoteroPane = new function () { this.copySelectedItemsToClipboard = function (asCitations, mode = 'bibliography') { - let itemIDs = this.getSelectedItems(true); - // Get selected item IDs in the item tree order - itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id)); - let items = Zotero.Items.get(itemIDs); + let items; + // "Copy Bibliography" / "Copy as ..." in the reader tab always act + // on the parent item being read -- annotation selection is for the + // smart-copy (Cmd+C / asCitations=true) path only. + if (!asCitations && Zotero_Tabs.selectedType === 'reader') { + let reader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); + let item = Zotero.Items.get(reader.itemID); + if (item.parentItem) item = item.parentItem; + items = [item]; + } + else { + let itemIDs = this.getSelectedItems(true); + // Get selected item IDs in the item tree order + itemIDs = this.getSortedItems(true).filter(id => itemIDs.includes(id)); + items = Zotero.Items.get(itemIDs); + } if (!items.length) return; let format = Zotero.QuickCopy.getFormat({ mode, items }); - // asCitations only applies to bibliography mode - if (asCitations && format.mode !== 'bibliography') return; - - let content = Zotero.QuickCopy.getContentFromItems(items, format, { asCitations }); + // asCitations only applies to bibliography mode; for notes/annotations + // the note format is used and asCitations is ignored + let content = Zotero.QuickCopy.getContentFromItems(items, format, { + asCitations: asCitations && format.mode === 'bibliography' + }); if (!content) { Services.prompt.alert(null, "", Zotero.getString("fileInterface.noReferencesError")); return; } + // When copying annotations, also set the zotero/annotation flavor so + // the note editor's paste handler can render them with linked + // citations (matches the rich path used by drag-and-drop from the + // reader). Shape mirrors reader.js _getAnnotation -- the note + // editor's paste plugin expects `id` (not `key`) and normalized tags. + if (items.every(item => item.isAnnotation())) { + content.annotations = JSON.stringify(items.map((annotation) => { + let json = Zotero.Annotations.toJSONSync(annotation); + json.id = annotation.key; + delete json.key; + json.attachmentItemID = annotation.parentItemID; + json.tags = json.tags || []; + return json; + })); + } Zotero_File_Interface.writeToClipboard(content); } - - + + + /** + * Classify what Cmd/Ctrl+C in the current context should do + * + * @returns {'text' | 'citation' | 'note' | 'annotation'} + * - 'citation' if we should quick-copy as an in-text citation + * - 'note'/'annotation' if we should quick-copy via the note format + * - 'text' - default, copy should copy text + */ + this.classifyCopyContext = function () { + let tab = Zotero_Tabs.getTabInfo(); + + if (tab.type === 'reader') { + let reader = Zotero.Reader.getByTabID(tab.id); + // Quick-copy only applies when the reader is focused + if (!reader?._iframeWindow?.document.hasFocus()) return 'text'; + let hasMetadata = Zotero.Items.get(reader.itemID).parentItem; + let defaultMode = hasMetadata ? "citation" : "text"; + // if nothing is selected in the reader, Copy should copy the citation if top level item exists + return reader.getSelectionType() || defaultMode; + } + + if (tab.type === 'library') { + // Quick-copy only applies when the items tree owns focus + if (!this.itemsView.domEl.contains(document.activeElement)) { + return 'text'; + } + let items = this.getSelectedItems(); + if (!items.length) return 'text'; + if (items.every(item => item.isAnnotation())) return 'annotation'; + if (items.every(item => item.isNote() || item.isAttachment())) { + return items.some(item => item.note) ? 'note' : 'text'; + } + if (items.some(item => item.isRegularItem() || item.isAnnotation())) { + return 'citation'; + } + } + + return 'text'; + }; + + this.clearQuicksearch = async function (skipSearchRun) { var search = document.getElementById('zotero-tb-search'); if (search.searchTextbox.value !== '') { @@ -3089,6 +3192,8 @@ var ZoteroPane = new function () { * Return an array of Item objects for selected items * * If asIDs is true, return an array of itemIDs instead + * + * For the reader tab, returns the selected annotation item(s) in sidebar, if any. */ this.getSelectedItems = function (asIDs) { switch (Zotero_Tabs.selectedType) { @@ -3104,6 +3209,13 @@ var ZoteroPane = new function () { if (item.parentItem) { item = item.parentItem; } + // Return annotations selected in reader's sidebar if any + let selectedAnnotations = reader.getSelectedAnnotationIDs() + .map(id => Zotero.Items.getByLibraryAndKey(item.libraryID, id)) + .filter(Boolean); + if (selectedAnnotations.length) { + return asIDs ? selectedAnnotations.map(item => item.id) : selectedAnnotations; + } return asIDs ? [item.id] : [item]; } return []; diff --git a/chrome/content/zotero/zoteroPane.xhtml b/chrome/content/zotero/zoteroPane.xhtml index 111b3fcafc..e57f760f97 100644 --- a/chrome/content/zotero/zoteroPane.xhtml +++ b/chrome/content/zotero/zoteroPane.xhtml @@ -103,18 +103,12 @@ - - @@ -169,9 +163,13 @@ key="&importCmd.key;" command="cmd_zotero_importFromClipboard" modifiers="accel shift alt"/> - + + @@ -376,13 +374,9 @@ key="key_cut" command="cmd_cut" data-l10n-id="text-action-cut"/> -