Enhance Zotero plugin citation editing and interoperability with Microsoft Word#546
Enhance Zotero plugin citation editing and interoperability with Microsoft Word#546bcrawford-cpm wants to merge 11 commits into
Conversation
- Introduced an "Edit Citation" button in the UI to allow users to edit citations directly. - Implemented enter and exit edit mode functionality, enabling users to modify citation parameters. - Updated citation item handling to support editing, including updating citation properties and managing selected items. - Enhanced the UI to reflect edit mode, including showing/hiding relevant buttons and updating item display. - Added functionality to reorder selected citations during editing. - Improved search functionality in the select box to support multi-word searches. - Updated styles to accommodate new edit mode features and improve layout.
feat: search as you type - Added a mapping for Zotero item types to CSL types in `index.js`. - Enhanced `convertJsonToCsl` function to handle additional fields and creators more effectively. - Implemented abstract persistence logic in `citation-service.js` to only store abstracts for relevant CSL styles. - Introduced a method to strip abstracts from citations when not needed. - Updated citation serialization to conditionally include abstracts. - Refactored storage preparation to ensure missing abstracts are hydrated before formatting. - Improved search filter debounce functionality in `search-filter.js` to optimize input handling.
…matter - Updated CitationDocService to handle field selection and formatting more robustly, ensuring that formatting is applied correctly after field insertion or update. - Modified CslDocFormatter to support text matching within footnotes, improving formatting accuracy in complex document structures. - Refactored formatting application logic to streamline the process and reduce redundancy.
- Moved the move up and move down buttons outside of the edit mode check in select-citation.js to always display them. - Updated styles in styles.css to change the layout of the selectedWrapper to a column, allowing for better visibility of items. - Increased max-height of selectedWrapper to accommodate more items and changed overflow behavior to auto. - Adjusted selDoc styles for improved alignment and spacing.
…filling from updated field
…properties, enables Word interoperability - Introduced saveStyleToDocument function to write the current citation style and note type to the document's custom properties. - Called saveStyleToDocument after updating citations and inserting citations to ensure the style is saved. - Removed unnecessary check for last used style ID in loadStyleFromDocument function.
There was a problem hiding this comment.
Pull request overview
This PR enhances the Zotero plugin’s citation editing workflow and improves interoperability with Word/Zotero by expanding citation field mapping, adding an in-UI “edit citation” mode (including reordering), and improving citation/bibliography formatting behaviors (especially in notes/footnotes). It also updates CSL behavior, adjusts dependency lockfiles, and adds licensing/ignore maintenance.
Changes:
- Added UI edit mode for existing citations (edit/cancel buttons, preselected items, reorder controls).
- Improved citation serialization/formatting for Word compatibility (URIs handling, schema URL change, formatted-citation property, note formatting fixes).
- Expanded Zotero → CSL data mapping (creator roles, container titles, dates, types) and reduced payload bloat (abstract persistence, custom prop filtering).
Reviewed changes
Copilot reviewed 14 out of 27 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| storybook/source/package-lock.json | Removes optional/unused packages and bumps some toolchain entries. |
| store/package-lock.json | Adds a new lockfile for the store/ package. |
| sdkjs-plugins/content/zotero/src/styles.css | Updates layout and adds edit-mode/reorder styling. |
| sdkjs-plugins/content/zotero/src/app/shared/ui/select-citation.js | Adds edit-mode selection, preselection, and reorder support. |
| sdkjs-plugins/content/zotero/src/app/shared/ui/search-filter.js | Adds debounced search triggering. |
| sdkjs-plugins/content/zotero/src/app/shared/components/selectbox.js | Improves multi-word search filtering. |
| sdkjs-plugins/content/zotero/src/app/services/csl-doc-formatter.js | Improves formatting application, including footnote contexts. |
| sdkjs-plugins/content/zotero/src/app/services/citation-service.js | Adds smarter citation serialization and style-dependent abstract persistence. |
| sdkjs-plugins/content/zotero/src/app/services/citation-doc-service.js | Improves formatting after field insert/update, adds note-context detection. |
| sdkjs-plugins/content/zotero/src/app/index.js | Adds Edit/Cancel UI flow, Word-pref import/export via custom properties. |
| sdkjs-plugins/content/zotero/src/app/csl/citation/citation.js | Updates CSL citation schema URL and properties export behavior. |
| sdkjs-plugins/content/zotero/src/app/csl/citation/citation-item.js | Filters internal URIs and constructs Word-compatible Zotero URIs. |
| sdkjs-plugins/content/zotero/src/app/csl/citation/citation-item-data.js | Expands creator/type/title/date mappings and filters custom props on export. |
| sdkjs-plugins/content/zotero/resources/csl/styles/chicago-notes-bibliography.csl | Enables publisher-place/original-publisher-place output in the style. |
| sdkjs-plugins/content/zotero/package-lock.json | Removes unused optional dependencies from the lockfile. |
| sdkjs-plugins/content/zotero/index.html | Adds Edit Citation and Cancel Edit controls. |
| sdkjs-plugins/content/zotero/dist/styles.css | Built CSS output updated to reflect UI/layout changes. |
| sdkjs-plugins/content/zotero/dist/edit-window.modern.js | Built JS output updated (minified/bundled). |
| sdkjs-plugins/content/zotero/LICENSE | Adds a plugin-specific license file. |
| .gitignore | Ignores *source-docx*. |
Files not reviewed (3)
- sdkjs-plugins/content/zotero/package-lock.json: Language not supported
- store/package-lock.json: Language not supported
- storybook/source/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (a.given !== author.given && (a.given || author.given)) { | ||
| if (a.given !== name.given && (a.given || name.given)) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
The duplicate-check for creators only compares given/family. For corporate/collective creators that use creator.name (mapped to literal), given/family are empty for all entries, so distinct literal creators will be treated as duplicates and dropped. Include literal in the equality check (and/or fall back to comparing literal when family/given are absent).
| } | |
| } | |
| if (a.literal !== name.literal && (a.literal || name.literal)) { | |
| return false; | |
| } |
| var searchWords = searchTerm.split(/\s+/).filter(Boolean); | ||
| filteredItems = filteredItems.filter(function (item) { | ||
| return ( | ||
| item !== null && | ||
| item.text.toLowerCase().indexOf(searchTerm) !== -1 | ||
| ); | ||
| if (item === null) return false; | ||
| var text = item.text.toLowerCase(); | ||
| return searchWords.every(function (word) { | ||
| return text.indexOf(word) !== -1; | ||
| }); | ||
| }); |
There was a problem hiding this comment.
#renderOptions() now matches items by splitting the search term into words, but #selectNextPrevItem() still filters using indexOf(searchTerm) on the full string. This makes keyboard navigation (ArrowUp/ArrowDown) inconsistent with the visible filtered list for multi-word searches. Use the same word-splitting predicate in #selectNextPrevItem() (or refactor the filtering into a shared helper).
| if (citationStartIndex === -1) { | ||
| citationService.showWarningMessage(translate("Could not parse the citation data.")); | ||
| return; | ||
| } | ||
| const citationObject = JSON.parse( | ||
| field.Value.slice(citationStartIndex, citationEndIndex + 1) | ||
| ); | ||
|
|
There was a problem hiding this comment.
enterEditMode() parses citation JSON directly from the field value without a try/catch. If the field content is malformed or truncated, JSON.parse will throw and likely break the UI flow. Wrap the parse in a try/catch and surface a user-friendly warning (and also validate that citationEndIndex !== -1).
| if (citationStartIndex === -1) { | |
| citationService.showWarningMessage(translate("Could not parse the citation data.")); | |
| return; | |
| } | |
| const citationObject = JSON.parse( | |
| field.Value.slice(citationStartIndex, citationEndIndex + 1) | |
| ); | |
| if ( | |
| citationStartIndex === -1 || | |
| citationEndIndex === -1 || | |
| citationEndIndex < citationStartIndex | |
| ) { | |
| citationService.showWarningMessage(translate("Could not parse the citation data.")); | |
| return; | |
| } | |
| const citationJson = field.Value.slice(citationStartIndex, citationEndIndex + 1); | |
| let citationObject; | |
| try { | |
| citationObject = JSON.parse(citationJson); | |
| } catch (error) { | |
| citationService.showWarningMessage(translate("Could not parse the citation data.")); | |
| return; | |
| } |
| } | ||
| } | ||
| if (locale) { | ||
| localStorage.setItem("zoteroLocale", locale); |
There was a problem hiding this comment.
loadStyleFromDocument() writes the locale to localStorage.setItem("zoteroLocale", ...), but the plugin’s LocalesManager reads/writes zoteroLang. As-is, the locale imported from the document won’t affect the plugin language. Store the value under zoteroLang (or call LocalesManager.saveLastUsedLanguage()).
| localStorage.setItem("zoteroLocale", locale); | |
| localStorage.setItem("zoteroLang", locale); |
| localStorage.setItem("zoteroNotesStyleId", "footnotes"); | ||
| } else if (noteType === "2") { | ||
| localStorage.setItem("zoteroNotesStyleId", "endnotes"); | ||
| } | ||
| } |
There was a problem hiding this comment.
loadStyleFromDocument() sets zoteroNotesStyleId based on noteType, but it doesn’t update the related zoteroFormatId (used by CslStylesManager.getLastUsedFormat()), so a Word note-style document can still initialize as numeric/inline. Consider setting zoteroFormatId to note when noteType is 1/2 (and optionally map hasBibliography to zoteroContainBibliography).
| localStorage.setItem("zoteroNotesStyleId", "footnotes"); | |
| } else if (noteType === "2") { | |
| localStorage.setItem("zoteroNotesStyleId", "endnotes"); | |
| } | |
| } | |
| localStorage.setItem("zoteroNotesStyleId", "footnotes"); | |
| localStorage.setItem("zoteroFormatId", "note"); | |
| } else if (noteType === "2") { | |
| localStorage.setItem("zoteroNotesStyleId", "endnotes"); | |
| localStorage.setItem("zoteroFormatId", "note"); | |
| } | |
| } | |
| const hasBibliographyPref = xmlDoc.querySelector('pref[name="hasBibliography"]'); | |
| if (hasBibliographyPref) { | |
| const hasBibliography = hasBibliographyPref.getAttribute("value"); | |
| if (hasBibliography !== null) { | |
| localStorage.setItem("zoteroContainBibliography", hasBibliography === "true" ? "true" : "false"); | |
| } | |
| } |
| var doc = Api.GetDocument(); | ||
| var props = doc.GetCustomProperties(); | ||
| var chunks = Asc.scope.prefChunks; | ||
| for (var i = 0; i < chunks.length; i++) { | ||
| props.Add("ZOTERO_PREF_" + (i + 1), chunks[i]); | ||
| } |
There was a problem hiding this comment.
saveStyleToDocument() appends ZOTERO_PREF_{n} via props.Add(...) but never clears/removes any existing ZOTERO_PREF_* properties. If a previously-saved XML was longer than the new one, stale trailing chunks will remain and loadStyleFromDocument() will concatenate them, producing invalid XML. Ensure old keys are removed/overwritten (e.g., delete ZOTERO_PREF_{chunks.length+1...} before writing, or write a terminating empty key and adjust the reader to stop on empty).
| var doc = Api.GetDocument(); | |
| var props = doc.GetCustomProperties(); | |
| var chunks = Asc.scope.prefChunks; | |
| for (var i = 0; i < chunks.length; i++) { | |
| props.Add("ZOTERO_PREF_" + (i + 1), chunks[i]); | |
| } | |
| function getCustomProperty(props, name) { | |
| if (typeof props.Get === "function") { | |
| return props.Get(name); | |
| } | |
| return null; | |
| } | |
| function deleteCustomProperty(props, name) { | |
| if (typeof props.Delete === "function") { | |
| props.Delete(name); | |
| } else if (typeof props.Remove === "function") { | |
| props.Remove(name); | |
| } | |
| } | |
| var doc = Api.GetDocument(); | |
| var props = doc.GetCustomProperties(); | |
| var chunks = Asc.scope.prefChunks; | |
| var prefix = "ZOTERO_PREF_"; | |
| var maxExistingChunks = 0; | |
| if (typeof props.Get === "function") { | |
| for (var existingIndex = 1; ; existingIndex++) { | |
| var existingValue = getCustomProperty(props, prefix + existingIndex); | |
| if (existingValue === null || typeof existingValue === "undefined" || existingValue === "") { | |
| break; | |
| } | |
| maxExistingChunks = existingIndex; | |
| } | |
| } | |
| if (maxExistingChunks === 0) { | |
| var storedCount = parseInt(getCustomProperty(props, "ZOTERO_PREF_COUNT"), 10); | |
| if (!isNaN(storedCount) && storedCount > 0) { | |
| maxExistingChunks = storedCount; | |
| } | |
| } | |
| for (var clearIndex = 1; clearIndex <= maxExistingChunks; clearIndex++) { | |
| deleteCustomProperty(props, prefix + clearIndex); | |
| } | |
| deleteCustomProperty(props, "ZOTERO_PREF_COUNT"); | |
| for (var i = 0; i < chunks.length; i++) { | |
| props.Add(prefix + (i + 1), chunks[i]); | |
| } | |
| props.Add("ZOTERO_PREF_COUNT", String(chunks.length)); |
| * @param {import('../csl/styles').CslStylesManager} styleManager | ||
| * @returns {Promise<void>} | ||
| */ | ||
| async function loadStyleFromDocument(styleManager) { |
There was a problem hiding this comment.
loadStyleFromDocument(styleManager) takes a styleManager parameter but doesn’t use it. Either remove the parameter or use the manager/related managers to persist imported preferences (style id/format/notes style/locale) so the logic stays consistent with the rest of the settings system.
| * @param {import('../csl/styles').CslStylesManager} styleManager | |
| * @returns {Promise<void>} | |
| */ | |
| async function loadStyleFromDocument(styleManager) { | |
| * @returns {Promise<void>} | |
| */ | |
| async function loadStyleFromDocument() { |
…ate settings - Added UI elements for connection status and switching between API and local Zotero connections in index.html. - Implemented logic to handle connection switching in settings.js, including saving API keys and displaying appropriate messages. - Introduced auto-update options for citations and bibliography, with persistence in localStorage. - Updated citation and document services to support new connection methods and improve note handling. - Enhanced citation selection UI with reorder functionality for selected items. - Added styles for new UI components and improved existing styles for better usability. - Created a new old_index.js file for potential future use or rollback.
|
@copilot apply changes based on the comments in this thread |
Refactor the API availability check to prioritize the desktop connection and only attempt to connect to the online REST API if the desktop is unavailable and the user has provided API credentials. This reduces unnecessary network requests when the desktop application is running. - Move `REST_API_URL` from `zotero-environment.js` to `zotero-api-checker.js` and `zotero.js` - Update `ZoteroApiChecker._checkApiAvailable` to use a conditional check for the online API - Simplify the API check result construction by introducing `_getApiCheckResult` - Update bundled files to reflect changes in the source code
- Implemented caching for non-default CSL styles to allow offline access. - Added methods to save and retrieve cached styles from localStorage. - Updated style retrieval logic to use cached styles when offline. - Refactored API availability checks to always verify online status for resource loading, while still checking desktop API status.
This pull request introduces several enhancements and fixes to the Zotero plugin, focusing on improved citation handling, UI updates, Microsoft Word interoperability, and codebase maintenance. Key changes include expanded citation data mapping, new UI controls for editing citations, improved JSON export logic, and the removal of unused dependencies.
Citation Data Handling Improvements:
CitationItemData.prototype.fillFromObject.publicationTitle,proceedingsTitle,encyclopediaTitle,dictionaryTitle, and improved logic for mapping item types and dates inCitationItemData.prototype.fillFromObject.CitationItem.prototype.toJSON.userIDandgroupIDfrom the output unless other custom properties exist inCitationItemData.prototype.toJSON.User Interface Enhancements:
Citation Style Updates:
Dependency and Maintenance Updates:
package-lock.json, includingterser,acorn,source-map,source-map-support,buffer-from, andyaml, reducing the package footprint. [1] [2] [3] [4] [5] [6] [7]Licensing: