diff --git a/README.md b/README.md index 5a83307..366390c 100644 --- a/README.md +++ b/README.md @@ -12,38 +12,71 @@ ![GitHub License](https://img.shields.io/github/license/ChenglongMa/zoplicate) [![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template) -[![Discord](https://img.shields.io/discord/1217704439612313631)](https://discord.gg/qFdNpSNb) [![GitHub Repo stars](https://img.shields.io/github/stars/ChenglongMa/zoplicate)](https://github.com/ChenglongMa/zoplicate) ---- +
+ English | 简体中文 +
+ A plugin that does one thing only: **Detect** and **Manage** duplicate items in [![zotero](https://www.zotero.org/support/lib/exe/fetch.php?tok=2735f1&media=https%3A%2F%2Fwww.zotero.org%2Fstatic%2Fimages%2Fpromote%2Fzotero-logo-128x31.png)](https://www.zotero.org/). # Main Features * **Duplicates Detection** * Detects if newly imported items are duplicates of existing ones in the Zotero library. - * You can: - 1. **Keep This**: Save the last imported item and delete the rest. - 2. **Keep Others**: Delete the last imported item and save the rest. + * You can choose which version of duplicate items to use as the **master item** and **merge** them. + * The actions you can take: + 1. **Keep New**: Set the **new item** as the master item and merge the duplicates. + 2. **Keep Old**: Set the **existing item** as the master item and merge the duplicates. 3. **Keep All**: Keep both the new item and the existing item. - 4. **Merge Manually**: Go to the Duplicate Panel and merge the duplicate item manually. + 4. **Merge Manually**: Go to the Duplicate Items Panel and merge the duplicate item manually. * **Auto-Bulk Merge** * Merges all duplicate items in the library automatically. * Introduced in Version 2.0.0. * **Non-duplicates Management** * Allows marking items as "Non-duplicates" if mistakenly identified as duplicates by Zotero. * Introduced in Version 3.0.0. +* **Show Duplicate Statistics** + * Append the duplicate count on the label of Duplicate Items pane. + * Introduced in Version 2.3.0. *If you find this project helpful, please consider [giving it a star](https://github.com/ChenglongMa/zoplicate)* ⭐. *It would be a great encouragement for me!* -> [!NOTE] -> We use the same method as Zotero to *detect* and *merge* duplicate items. +> [!IMPORTANT] +> +> Zoplicate does NOT delete the duplicate items arbitrarily. > -> See [Zotero Documentation - Duplicate Detection](https://www.zotero.org/support/duplicate_detection) for more details. +> Instead, it extracts the useful information from the duplicate items and merges them into the retained item. +> +> It makes some improvements based on [the official detection and merging methods of Zotero](https://www.zotero.org/support/duplicate_detection). +> # Changelog +## v3.0.3 + +
+ Click here to show more. + +In this version, we have made the following changes: + +1. 🐛 **FIX!**: We have fixed the bug that caused the attachment to be lost when merging duplicate items. + * Thanks to [tommyhosman](https://github.com/tommyhosman) for reporting this bug in [issue #43](https://github.com/ChenglongMa/zoplicate/issues/43). + * Thanks to [csdaboluo](https://github.com/csdaboluo) for reporting this bug in [issue #51](https://github.com/ChenglongMa/zoplicate/issues/51). +2. 🐛 **FIX!**: Zoplicate will now ignore the **Feed** items when detecting duplicates. + * Thanks to [Raphael-Hao](https://github.com/Raphael-Hao) for reporting this bug in [issue #54](https://github.com/ChenglongMa/zoplicate/issues/54). +3. 🐛 **FIX!**: We have fixed the bug that caused the detection dialog blocking the main thread. +4. 🐛 **FIX!**: We have fixed the bug that caused the deleted items to be showing in NonDuplicates panel. +5. 🐛 **FIX!**: Now, the NonDuplicates panel won't show up when clicking attachment items. +6. ✨ **NEW!**: We added a prompt to remind the duplicates when Zotero is in background. + +
+ +
+ Click here to show more. + ## v3.0.2
@@ -182,14 +215,30 @@ Thanks [ChinJCheung](https://github.com/ChinJCheung)'s idea mentioned in [issue
+
+ # Install +## From GitHub + 1. Download `.xpi` file according to the version of Zotero you are using. - **For Zotero 7**: Visit the [release page](https://github.com/ChenglongMa/zoplicate/releases/latest) and download [the latest `.xpi` file](https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi). - **For Zotero 6**: Visit the [release page](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6) and download [the `.xpi` file for Zotero 6](https://github.com/ChenglongMa/zoplicate/releases/download/zotero6/zoplicate.xpi). - If you are using FireFox, right-click on the link of the XPI file and select "Save As...". 2. Then, in Zotero, click `Tools` -> `Add-ons` and drag the `.xpi` onto the Add-ons window. - See [how to install a Zotero addon](https://www.zotero.org/support/plugins). + - See [how to install a Zotero addon](https://www.zotero.org/support/plugins). + +## From Add-on Market Plugin for Zotero + +_Additional third-party plugin is required._ + +1. Install Add-on Market for Zotero from [here](https://github.com/syt2/zotero-addons). +2. Search `Zoplicate` in the Add-on Market and install it. + +## 通过 Zotero 插件商店 安装 (For Chinese Users) + +1. 前往 [Zotero 插件商店](https://zotero-chinese.com/plugins/). +2. 搜索 `Zoplicate` 然后单击 `下载` 按钮。 # Usage @@ -236,7 +285,7 @@ the dialog will show all the duplicate items and the actions you can take. Inspired by [csdaboluo](https://github.com/csdaboluo)'s idea and [ZoteroDuplicatesMerger](https://github.com/frangoud/ZoteroDuplicatesMerger), we have added the **Bulk Merge** functionality in **Version 2.0.0**. -In the `Duplicate Items` panel, you can find the **Bulk Merge** button: +In the `Duplicate Items` panel, you can find the Bulk Merge All Duplicate Items button: ![zoplicate bulk merge](docs/bulk-merge-nonselection.png) @@ -283,7 +332,7 @@ When your mouse hovers over the Duplicate Items entry, a tooltip will ![Show duplicate count](docs/show-duplicate-count.png) -The duplicate count will be updated automatically. You can also find the Refresh Duplicate Count menu to update the count manually. +The duplicate count will be updated automatically. You can also find the Refresh menu to update the count manually. ![Refresh duplicate count](docs/refresh-duplicate-count.png) diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 0000000..1a53c23 --- /dev/null +++ b/README_CN.md @@ -0,0 +1,214 @@ +
+ zoplicate banner +
+ +👉 +[![zotero target version](https://img.shields.io/badge/Zotero-7-green?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org) +[![Version for Zotero 7](https://img.shields.io/github/package-json/v/ChenglongMa/zoplicate)](https://github.com/ChenglongMa/zoplicate/releases/latest) +[![Downloads for Zotero 7](https://img.shields.io/github/downloads/ChenglongMa/zoplicate/total)](https://github.com/ChenglongMa/zoplicate/releases/latest)👈 +👉[![zotero target version](https://img.shields.io/badge/Zotero-6-green?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org) +[![Version for Zotero 6](https://img.shields.io/github/package-json/v/ChenglongMa/zoplicate/zotero-6)](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6) +[![Downloads for Zotero 6](https://img.shields.io/github/downloads/ChenglongMa/zoplicate/zotero6/total)](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6)👈 + +![GitHub License](https://img.shields.io/github/license/ChenglongMa/zoplicate) +[![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template) +[![GitHub Repo stars](https://img.shields.io/github/stars/ChenglongMa/zoplicate)](https://github.com/ChenglongMa/zoplicate) + +---- + +
+ English | 简体中文 +
+ +Zoplicate 是一个用于 [![zotero](https://www.zotero.org/support/lib/exe/fetch.php?tok=2735f1&media=https%3A%2F%2Fwww.zotero.org%2Fstatic%2Fimages%2Fpromote%2Fzotero-logo-128x31.png)](https://www.zotero.org/) 的插件,它只专注一件事:**检测和管理重复的文献条目**。 + +# 主要功能 + +- **检测重复条目**: + * 自动检测新添加的文献条目是否与已有条目重复。 + * 你可以选择**主条目**的版本,然后将重复条目**合并**到主条目中。 + * 支持的操作有: + * **保留最新的**:以**新导入**条目为主条目,合并重复条目。 + * **保留已有的**:以**已有**条目为主条目,合并重复条目。 + * **保留全部**:保留所有条目,不合并任何项。 + * **手动合并**:前往 "重复条目" 面板,手动选择要合并的条目。 +- **批量处理重复条目**: + * 你可以在 "重复条目" 面板中,批量合并多个重复条目。 + * 你可以在设置中选择**主条目**的版本。 +- **标记"非重复条目"** + * 如果你认为某些条目不是重复的,你可以将其标记为"非重复条目"。 +- **显示重复条目统计信息** + * 你可以设置在"重复条目"标签后显示重复条目数量。如,重复条目 2/6。 + +_如果你觉得这个插件对你有帮助,欢迎给它一个 ⭐️。感谢你的支持!_ + +> [!IMPORTANT] +> +> Zoplicate 不会单纯地**删除**重复的条目。 +> +> 相反,它会从重复的条目中提取有用的信息,并将其合并到保留的条目中。 +> +> 它在 [Zotero 官方的重复检测和合并方法](https://www.zotero.org/support/duplicate_detection) 的基础上做了一些改进。 +> + +# 视频教程 + +[![tutorial](./docs/tutorial_cn.png)](https://www.bilibili.com/video/BV1WnaseEEvB/?share_source=copy_web) + +# 安装 + +## 通过 GitHub 安装 + +1. 根据你的 Zotero 版本,下载最新的 `zoplicate.xpi` 文件: + - **Zotero 7**:从该 [release page](https://github.com/ChenglongMa/zoplicate/releases/latest) 中下载 [最新的 `.xpi` 文件](https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi)。 + - **Zotero 6**:从该 [release page](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6) 中下载 [Zotero 6 版本的 `.xpi` 文件](https://github.com/ChenglongMa/zoplicate/releases/download/zotero6/zoplicate.xpi). +2. 打开 Zotero,然后前往 `工具` -> `插件`。选择 `从文件安装插件...`。 + * 详情请参考 [Zotero 官方文档](https://www.zotero.org/support/plugins)。 + +## 通过 Add-on Market Plugin for Zotero 安装 + +_需要安装额外的第三方插件。_ + +1. 单击 [这里](https://github.com/syt2/zotero-addons) 安装 Add-on Market Plugin for Zotero 插件。 +2. 在 Add-on Market 中搜索 `Zoplicate` 并安装。 + +## 通过 Zotero 插件商店 安装 + +1. 前往 [Zotero 插件商店](https://zotero-chinese.com/plugins/). +2. 搜索 `Zoplicate` 然后单击 `下载` 按钮。 + +# 使用方法 + +## 设置 + +打开 Zotero,然后前往 `编辑` -> `设置`。单击 `Zoplicate` 选项卡,你会看到如下设置: + + ![Zoplicate settings](./docs/settings.png) + +1. 当检测到重复条目时,默认会弹出对话框询问你如何处理。你可以选择是否**自动合并**重复条目。 + * 默认选项为 `始终询问`。 +2. 你可以选择批量合并时**主条目**的版本。 + * 默认选项为 `最早添加的`。 +3. 你可以选择是否在 "重复条目" 标签后显示重复条目数量。 + +## 检测重复条目 + +默认情况下,当添加新的文献条目时,Zoplicate 会自动检测是否有重复的条目。如果有,你会看到如下对话框: + + ![Zoplicate dialog](./docs/dialog.png) + +该对话框会显示所有重复的条目和你可以选择的操作。 + +1. 选择你要执行的操作,然后单击 应用 来合并重复条目。 +2. 单击 手动合并 将打开 "重复条目" 面板,你可以在那里手动选择要合并的条目。 +3. 单击 取消 将关闭对话框并保存所有条目。 +4. 勾选 将此操作设为默认值 将记住你的选择,下次将自动执行该操作而不再弹出对话框。 + +### 检测到多个重复条目 + +当引入多个重复条目或在处理之前的重复条目之前引入另一个重复条目时,对话框将显示所有重复条目和你可以采取的操作。 + +![Zoplicate dialog with multiple duplicates](./docs/dialog2.png) + +1. 你可以为不同的重复条目选择不同的操作。 +2. 单击选项的列头将会应用该操作到所有重复条目。 +3. 将此操作设为默认值 只有在所有重复条目都选择了相同的操作时才可用。 + +## 批量处理重复条目 + +在 2.0.0 版本中,Zoplicate 引入了 "批量合并" 功能。 + +你可以在 "重复条目" 面板中找到 批量合并所有重复条目 按钮: + +![zoplicate bulk merge](./docs/bulk-merge-nonselection.png) + +或当你选择了一个或多个重复条目时: + +![zoplicate bulk merge](./docs/bulk-merge-selection.png) + +> [!WARNING] +> +> 1. 在点击按钮之前,请确保你已经在 [设置](#设置) 中正确配置了**主条目**的偏好设置。 +> 2. **批量合并** 功能将比较耗时。如果你有大量的重复条目,这个过程可能会花费一些时间。 +> + + +你可以在 "重复条目" 面板中看到合并的进度: +![zoplicate bulk merge process](docs/bulk-merge-progress.png) + +### 中止批量合并 + +如果你想中止批量合并,你可以单击 暂停合并 按钮。 + +此时会弹出确认对话框: +![zoplicate bulk merge suspend](docs/bulk-merge-suspended-dialog.png) + +1. 单击 继续 将继续合并剩余的重复条目。 +2. 单击 取消 将停止合并并保留已合并的条目。 + * 如果勾选了 恢复已删除的条目,已合并的条目将被恢复。 + * 请注意 恢复已删除的条目 选项只在**取消**合并时可用。 + +> [!TIP] +> +> 如果你想**恢复**已合并的重复条目,你可以前往 `回收站` 面板并将它们恢复。 +> +> 1. 选择你想要恢复的重复条目。 +> 2. 单击 恢复到库 按钮来处理。 +> + +## 显示重复条目统计信息 + +你可以在[设置](#设置)中选择是否在 "重复条目" 标签后显示重复条目数量。 + +启用后,当鼠标悬停在 "重复条目" 标签上时,你会看到详细的重复条目数量。 + +![zoplicate duplicate count](docs/show-duplicate-count.png) + +该信息会自动更新,你也可以右键单击 刷新 来手动更新。 + +![Refresh duplicate count](docs/refresh-duplicate-count.png) + +## "非重复条目" 管理 + +如果你认为某些条目不是重复的,你可以将其标记为"非重复条目"。 + +在条目的侧边栏中,你可以管理这些 **非重复条目**: +![side_panel_not_duplicates](./docs/non_duplicates/side_panel_non_duplicates.png) + +* 单击 ![non-duplicate button](./addon/chrome/content/icons/non-duplicate.svg) 按钮将显示 **非重复条目** 面板。 +* 单击 + 按钮可以选择一个条目并将其标记为 **非重复条目**。 +* 单击 - 按钮可以取消 **非重复条目** 标记。 + +当然,还有以下几种方式可以标记或取消标记 **非重复条目**: + +### 标记 "非重复条目" + +1. 如前所述,你可以在侧边栏![non-duplicate button](./addon/chrome/content/icons/non-duplicate.svg) 中单击 + 按钮来标记 **非重复条目**。 +2. 你可以在条目的右键菜单中选择 标记为非重复条目 来标记 **非重复条目**。 + * 该选项只有在选择的条目**被 Zotero 误认为是重复条目**时才会显示。 + * ![menu_not_duplicates](./docs/non_duplicates/menu_not_duplicates.png) +3. 你可以在 "重复条目" 面板中选择多个条目,然后单击 标记为非重复条目 按钮来标记 **非重复条目**。 + * ![button_not_duplicates](./docs/non_duplicates/button_not_duplicates.png) + +### 取消标记 "非重复条目" + +1. 你可以在侧边栏![non-duplicate button](./addon/chrome/content/icons/non-duplicate.svg) 中单击 - 按钮来取消标记 **非重复条目**。 +2. 你可以在条目的右键菜单中选择 取消标记为非重复条目 来取消标记 **非重复条目**。 + * 该选项只有在选择的条目**被标记为非重复条目后**才会显示。 + * ![menu_not_duplicates](./docs/non_duplicates/menu_duplicates.png) + +# 开源贡献 + +👋 欢迎关注 **Zoplicate**!很高兴有你的参与。以下是你可以参与的方式: + +1. 💡 **讨论新想法**:有创意的想法或建议?在[Discussion](https://github.com/ChenglongMa/Zoplicate/discussions)页面开始讨论,分享你的想法并从获得反馈。 + +2. ❓ **提问**:对仓库中的某些内容有疑问?随时开一个标记为“问题”的[issue](https://github.com/ChenglongMa/Zoplicate/issues)或参与[Discussion](https://github.com/ChenglongMa/Zoplicate/discussions)。 + +3. 🐛 **报告错误**:如果你发现了一个bug,请开一个新的[issue](https://github.com/ChenglongMa/Zoplicate/issues),并清楚描述问题、复现步骤以及你的运行环境。 + +4. ✨ **引入新功能**:想要为项目添加新功能或增强吗?Fork仓库,创建一个新分支,并提交一个带有你更改的[PR](https://github.com/ChenglongMa/Zoplicate/pulls)。确保遵循我们的贡献指南。 + +5. 💖 **赞助**:如果您想更多地支持该项目,你可以通过[在GitHub上赞助仓库](https://github.com/sponsors/ChenglongMa)来实现。感谢各位金主大佬! + +非常感谢各位对 **Zoplicate** 的关注和支持!🙏 diff --git a/addon/locale/en-US/preferences.ftl b/addon/locale/en-US/preferences.ftl index ba5c69d..295ceda 100644 --- a/addon/locale/en-US/preferences.ftl +++ b/addon/locale/en-US/preferences.ftl @@ -4,9 +4,9 @@ pref-action-title = Action Preferences pref-default-action-description = Default action to process duplicate items pref-default-action-keep-this = - .label = [Keep This]: Save the last imported item and delete the rest + .label = [Keep New]: Set the last imported item as the master item and merge the duplicates. pref-default-action-keep-others = - .label = [Keep Others]: Delete the last imported item and save the rest + .label = [Keep Old]: Set the existing item as the master item and merge the duplicates. pref-default-action-keep-all = .label = [Keep All]: Save all items pref-default-action-always-ask = diff --git a/addon/locale/zh-CN/preferences.ftl b/addon/locale/zh-CN/preferences.ftl index 2d81941..2987847 100644 --- a/addon/locale/zh-CN/preferences.ftl +++ b/addon/locale/zh-CN/preferences.ftl @@ -4,11 +4,11 @@ pref-action-title = 默认操作设置 pref-default-action-description = 处理重复条目的默认操作 pref-default-action-keep-this = - .label = [保留最新的]: 保留新导入条目,删除库中原有的 + .label = [保留最新的]: 以新导入条目为主条目,合并重复条目 pref-default-action-keep-others = - .label = [保留已有的]: 保留库中原有条目,删除新导入的 + .label = [保留已有的]: 以已有条目为主条目,合并重复条目 pref-default-action-keep-all = - .label = [保留全部]: 保留所有条目,不删除任何项 + .label = [保留全部]: 保留所有条目,不合并任何项 pref-default-action-always-ask = .label = [始终询问]: 每次都询问我该如何处理 @@ -30,6 +30,6 @@ pref-default-master-item-always-ask = pref-view-title = 视图设置 pref-view-description = 选择重复条目的显示方式 pref-view-duplicate-stats = - .label = 在“重复条目”选项后显示重复条目数量。如,重复条目 2/6 + .label = 在"重复条目"标签后显示重复条目数量。如,重复条目 2/6 pref-help = { $name } Build { $version } { $time } diff --git a/docs/tutorial_cn.png b/docs/tutorial_cn.png new file mode 100644 index 0000000..0f35b9f Binary files /dev/null and b/docs/tutorial_cn.png differ diff --git a/package-lock.json b/package-lock.json index b0ba505..1d43c52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,28 +1,27 @@ { "name": "zoplicate", - "version": "3.0.2", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zoplicate", - "version": "3.0.2", + "version": "3.1.0", "license": "AGPL-3.0-or-later", "dependencies": { - "dexie": "^4.0.7", - "zotero-plugin-toolkit": "^2.3.36" + "zotero-plugin-toolkit": "^2.3.37" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@types/node": "^20.14.9", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", + "@types/node": "^20.14.10", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "fake-indexeddb": "^6.0.0", "jest": "^29.7.0", "prettier": "^3.3.2", - "ts-jest": "^29.1.5", + "ts-jest": "^29.2.2", "ts-node": "^10.9.2", "typescript": "^5.5.3", "zotero-plugin-scaffold": "^0.0.32", @@ -2750,9 +2749,9 @@ } }, "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2826,17 +2825,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz", - "integrity": "sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", + "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/type-utils": "7.15.0", - "@typescript-eslint/utils": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/type-utils": "7.16.0", + "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2860,16 +2859,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.15.0.tgz", - "integrity": "sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", + "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/typescript-estree": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", "debug": "^4.3.4" }, "engines": { @@ -2889,14 +2888,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz", - "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", + "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0" + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2907,14 +2906,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz", - "integrity": "sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", + "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.15.0", - "@typescript-eslint/utils": "7.15.0", + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/utils": "7.16.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2935,9 +2934,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", - "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", + "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", "dev": true, "license": "MIT", "engines": { @@ -2949,14 +2948,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz", - "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", + "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/visitor-keys": "7.15.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2978,16 +2977,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz", - "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", + "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.15.0", - "@typescript-eslint/types": "7.15.0", - "@typescript-eslint/typescript-estree": "7.15.0" + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -3001,13 +3000,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz", - "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", + "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/types": "7.16.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -5240,11 +5239,6 @@ "node": ">=8" } }, - "node_modules/dexie": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.7.tgz", - "integrity": "sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==" - }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -5413,6 +5407,22 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.783", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz", @@ -6084,6 +6094,29 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7409,6 +7442,49 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.1.tgz", + "integrity": "sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jed": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/jed/-/jed-1.1.1.tgz", @@ -11368,13 +11444,14 @@ } }, "node_modules/ts-jest": { - "version": "29.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", - "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", + "version": "29.2.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.2.tgz", + "integrity": "sha512-sSW7OooaKT34AAngP6k1VS669a0HdLxkQZnlC7T76sckGCokXFnvJ3yRlQZGRTAoV5K19HfSgCiSwWOSIfcYlg==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "0.x", + "ejs": "^3.0.0", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", @@ -12422,9 +12499,9 @@ } }, "node_modules/zotero-plugin-toolkit": { - "version": "2.3.36", - "resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-2.3.36.tgz", - "integrity": "sha512-rmlDKtMN6fNtj1RKZrQohqh0cifgNMVdYwLnwVnx3WhhdaSXOqPM2Z6ZAxK5Wuhwl8nP9f75AJF8HC3S/ccePw==", + "version": "2.3.37", + "resolved": "https://registry.npmjs.org/zotero-plugin-toolkit/-/zotero-plugin-toolkit-2.3.37.tgz", + "integrity": "sha512-C1e9o+pWR8vPJQaGpHPDKEsWcTJwjef90vX+kQQ6aDlXYNomhtHNXHmweIQWoufNQz26wQQkFFU1zkvIQ4Ff6A==", "license": "MIT", "dependencies": { "zotero-types": "^2.0.3" diff --git a/package.json b/package.json index 73724d8..e6b9e96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zoplicate", - "version": "3.1.0", + "version": "3.0.3", "description": "Detect and manage duplicate items in Zotero.", "config": { "addonName": "Zoplicate", @@ -30,19 +30,19 @@ }, "homepage": "https://chenglongma.com/zoplicate/", "dependencies": { - "zotero-plugin-toolkit": "^2.3.36" + "zotero-plugin-toolkit": "^2.3.37" }, "devDependencies": { "@jest/globals": "^29.7.0", - "@types/node": "^20.14.9", - "@typescript-eslint/eslint-plugin": "^7.15.0", - "@typescript-eslint/parser": "^7.15.0", + "@types/node": "^20.14.10", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "fake-indexeddb": "^6.0.0", "jest": "^29.7.0", "prettier": "^3.3.2", - "ts-jest": "^29.1.5", + "ts-jest": "^29.2.2", "ts-node": "^10.9.2", "typescript": "^5.5.3", "zotero-plugin-scaffold": "^0.0.32", diff --git a/src/db/duplicateFinder.ts b/src/db/duplicateFinder.ts index b1581f2..393649b 100644 --- a/src/db/duplicateFinder.ts +++ b/src/db/duplicateFinder.ts @@ -28,11 +28,48 @@ export class DuplicateFinder { return this.candidateItemIDs; } + public static async findByRelations(item: Zotero.Item, predicate: _ZoteroTypes.RelationsPredicate, asIDs = true) { + let queue: Zotero.Item[] = [item]; + let candidates = []; + let processedURIs = new Set(); + + while (queue.length > 0) { + const currentItem = queue.shift(); + if (!currentItem) { + continue; + } + + const currentURI = Zotero.URI.getItemURI(currentItem); + if (processedURIs.has(currentURI)) { + continue; + } + + const prevVersionItems: Zotero.Item[] = await Zotero.Relations.getByPredicateAndObject( + "item", + predicate, + currentURI, + ); + // If there are no previous versions, then this is a candidate + if (prevVersionItems.length === 0 && !currentItem.deleted) { + ztoolkit.log("Found candidate", currentItem.id, currentItem.getDisplayTitle()); + candidates.push(asIDs ? currentItem.id : currentItem); + } + // Otherwise, add the previous versions to the queue + for (const prevItem of prevVersionItems) { + const uri = Zotero.URI.getItemURI(prevItem); + if (!processedURIs.has(uri)) { + queue.push(prevItem); + } + } + processedURIs.add(currentURI); + } + + return candidates; + } + private async findByDcReplacesRelation() { const predicate = Zotero.Relations.replacedItemPredicate; - const thisURI = Zotero.URI.getItemURI(this.item); - const mergeItems: Zotero.Item[] = await Zotero.Relations.getByPredicateAndObject("item", predicate, thisURI); - this.candidateItemIDs = mergeItems.map((item) => item.id); + this.candidateItemIDs = (await DuplicateFinder.findByRelations(this.item, predicate, true)) as number[]; return this; } diff --git a/src/modules/duplicates.ts b/src/modules/duplicates.ts index 3547d92..6310091 100644 --- a/src/modules/duplicates.ts +++ b/src/modules/duplicates.ts @@ -131,12 +131,12 @@ export async function processDuplicates(duplicateMaps: Map Zotero.Items.get(newItemID).numAttachments() > 0, 100, 300); - // } catch (e) { - // ztoolkit.log(e); - // } + try { + // Wait for potential attachments to be downloaded + await waitUntilAsync(() => Zotero.Items.get(newItemID).numAttachments() > 0, 1000, 5000); + } catch (e) { + ztoolkit.log(e); + } const newItem = Zotero.Items.get(newItemID); if (action === Action.KEEP) { diff --git a/src/modules/nonDuplicates.ts b/src/modules/nonDuplicates.ts index 14ba84b..062afc3 100644 --- a/src/modules/nonDuplicates.ts +++ b/src/modules/nonDuplicates.ts @@ -17,11 +17,6 @@ export function registerNonDuplicatesSection(db: NonDuplicatesDB) { icon: `chrome://${config.addonRef}/content/icons/non-duplicate.svg`, //20x20 l10nID: `${config.addonRef}-section-non-duplicate-sidenav`, }, - // bodyXHTML: ` - // - // - // - // `, sectionButtons: [ { @@ -129,9 +124,13 @@ export function registerNonDuplicatesSection(db: NonDuplicatesDB) { } }, onItemChange: ({ body, item, setEnabled }) => { + ztoolkit.log("onItemChange non duplicates", item); + setEnabled(item?.isRegularItem()); body.dataset.itemID = String(item.id); }, - onRender: () => {}, + onRender: ({ body, item, editable }) => { + ztoolkit.log("onRender non duplicates", item); + }, onAsyncRender: async ({ body, item, editable }) => { ztoolkit.log("onAsyncRender non duplicates", body); diff --git a/src/modules/patcher.ts b/src/modules/patcher.ts index 920d320..5f0117e 100644 --- a/src/modules/patcher.ts +++ b/src/modules/patcher.ts @@ -85,6 +85,8 @@ export function patchItemSaveData() { const duItems = new DuplicateItems(newParents, masterItemPref); if (newParents.length > 0) { + // TODO: check if this is correct, should use official API + // Such as Zotero.Items.moveChildItems, etc. this.parentID = duItems.masterItem.id; } } diff --git a/update-beta.json b/update-beta.json new file mode 100644 index 0000000..700a129 --- /dev/null +++ b/update-beta.json @@ -0,0 +1,18 @@ +{ + "addons": { + "zoplicate@chenglongma.com": { + "updates": [ + { + "version": "3.0.3", + "update_link": "https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi", + "applications": { + "zotero": { + "strict_min_version": "6.999", + "strict_max_version": "7.0.*" + } + } + } + ] + } + } +} diff --git a/update.json b/update.json new file mode 100644 index 0000000..700a129 --- /dev/null +++ b/update.json @@ -0,0 +1,18 @@ +{ + "addons": { + "zoplicate@chenglongma.com": { + "updates": [ + { + "version": "3.0.3", + "update_link": "https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi", + "applications": { + "zotero": { + "strict_min_version": "6.999", + "strict_max_version": "7.0.*" + } + } + } + ] + } + } +} diff --git a/zotero-plugin.config.ts b/zotero-plugin.config.ts index d1659e0..8e70ecc 100644 --- a/zotero-plugin.config.ts +++ b/zotero-plugin.config.ts @@ -8,12 +8,8 @@ export default defineConfig({ name: pkg.config.addonName, id: pkg.config.addonID, namespace: pkg.config.addonRef, - updateURL: `https://github.com/{{owner}}/{{repo}}/releases/download/release/${ - pkg.version.includes("-") ? "update-beta.json" : "update.json" - }`, - xpiDownloadLink: - "https://github.com/{{owner}}/{{repo}}/releases/download/v{{version}}/{{xpiName}}.xpi", - + updateURL: "https://raw.githubusercontent.com/ChenglongMa/zoplicate/main/update.json", + xpiDownloadLink: "https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi", server: { asProxy: true, }, @@ -40,15 +36,15 @@ export default defineConfig({ }, ], // If you want to checkout update.json into the repository, uncomment the following lines: - // makeUpdateJson: { - // hash: false, - // }, - // hooks: { - // "build:makeUpdateJSON": (ctx) => { - // copyFileSync("build/update.json", "update.json"); - // copyFileSync("build/update-beta.json", "update-beta.json"); - // }, - // }, + makeUpdateJson: { + hash: false, + }, + hooks: { + "build:makeUpdateJSON": (ctx) => { + copyFileSync("build/update.json", "update.json"); + copyFileSync("build/update-beta.json", "update-beta.json"); + }, + }, }, // release: { // bumpp: {