diff --git a/addon/chrome/content/preferences.xhtml b/addon/chrome/content/preferences.xhtml
index c159137..3669f11 100644
--- a/addon/chrome/content/preferences.xhtml
+++ b/addon/chrome/content/preferences.xhtml
@@ -3,80 +3,88 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
+
\ No newline at end of file
diff --git a/addon/locale/en-US/addon.ftl b/addon/locale/en-US/addon.ftl
index 03485a6..8f82180 100644
--- a/addon/locale/en-US/addon.ftl
+++ b/addon/locale/en-US/addon.ftl
@@ -61,6 +61,10 @@ bulk-merge-popup-restore = Restoring: { $item }
duplicate-tooltip = Found { $total } duplicates from { $unique } unique { $items }.
duplicate-not-found-tooltip = No duplicate items found.
+type-mismatch-title = Item Types Are Different
+type-mismatch-message = Some items have different types. Would you like to convert them to match or skip merging?
+type-mismatch-convert = Convert Types
+type-mismatch-skip = Skip Merging
## Menus
menuitem-refresh-duplicates = Refresh
@@ -72,4 +76,4 @@ menuitem-is-duplicate = They are duplicates
# Add non-duplicate
add-not-duplicates-alert-error-duplicates = The selected items are not identified as Duplicates.
add-not-duplicates-alert-error-exist = The selected items already exist.
-add-not-duplicates-alert-error-diff-library = Cannot add items from different libraries.
+add-not-duplicates-alert-error-diff-library = Cannot add items from different libraries.
\ No newline at end of file
diff --git a/addon/locale/en-US/preferences.ftl b/addon/locale/en-US/preferences.ftl
index 295ceda..b09df44 100644
--- a/addon/locale/en-US/preferences.ftl
+++ b/addon/locale/en-US/preferences.ftl
@@ -27,9 +27,19 @@ pref-default-master-item-detailed =
pref-default-master-item-always-ask =
.label = [Always Ask]: Ask for master item every time
+pref-type-mismatch-title = Type Mismatch Preferences
+pref-type-mismatch-description = How to handle when merging items of different types
+
+pref-type-mismatch-skip =
+ .label = [Skip]: Skip merging items of different types
+pref-type-mismatch-convert =
+ .label = [Convert]: Convert items to match the master item type
+pref-type-mismatch-ask =
+ .label = [Ask]: Ask what to do when types don't match
+
pref-view-title = View Preferences
pref-view-description = Choose the widget to display in the main window
pref-view-duplicate-stats =
.label = Append duplicate counts to the "Duplicate Items" entry, e.g., Duplicate Items 2/6
-pref-help = { $name } Build { $version } { $time }
+pref-help = { $name } Build { $version } { $time }
\ No newline at end of file
diff --git a/addon/locale/zh-CN/addon.ftl b/addon/locale/zh-CN/addon.ftl
index 0a4285a..aba2421 100644
--- a/addon/locale/zh-CN/addon.ftl
+++ b/addon/locale/zh-CN/addon.ftl
@@ -62,6 +62,10 @@ bulk-merge-popup-restore = 正在恢复: { $item }
duplicate-tooltip = 检测到 { $total } 个重复项,源自 { $unique } 个唯一条目。
duplicate-not-found-tooltip = 未找到重复条目
+type-mismatch-title = 条目类型不匹配
+type-mismatch-message = 部分条目类型不一致。您想转换它们的类型还是跳过合并?
+type-mismatch-convert = 转换类型
+type-mismatch-skip = 跳过合并
## Menus
menuitem-refresh-duplicates = 刷新
@@ -73,4 +77,4 @@ menuitem-is-duplicate = 标记为重复条目
# Add non-duplicate
add-not-duplicates-alert-error-duplicates = 所选条目没有被识别为重复条目。
add-not-duplicates-alert-error-exist = 所选条目已存在。
-add-not-duplicates-alert-error-diff-library = 无法添加不同库中的条目。
+add-not-duplicates-alert-error-diff-library = 无法添加不同库中的条目。
\ No newline at end of file
diff --git a/addon/locale/zh-CN/preferences.ftl b/addon/locale/zh-CN/preferences.ftl
index 2987847..48a1f89 100644
--- a/addon/locale/zh-CN/preferences.ftl
+++ b/addon/locale/zh-CN/preferences.ftl
@@ -27,9 +27,19 @@ pref-default-master-item-detailed =
pref-default-master-item-always-ask =
.label = [始终询问]: 每次都询问我该如何选择主条目
+pref-type-mismatch-title = 条目类型不匹配设置
+pref-type-mismatch-description = 如何处理不同类型条目的合并
+
+pref-type-mismatch-skip =
+ .label = [跳过]: 跳过不同类型条目的合并
+pref-type-mismatch-convert =
+ .label = [转换]: 将条目转换为主条目类型
+pref-type-mismatch-ask =
+ .label = [询问]: 类型不匹配时询问如何处理
+
pref-view-title = 视图设置
pref-view-description = 选择重复条目的显示方式
pref-view-duplicate-stats =
.label = 在"重复条目"标签后显示重复条目数量。如,重复条目 2/6
-pref-help = { $name } Build { $version } { $time }
+pref-help = { $name } Build { $version } { $time }
\ No newline at end of file
diff --git a/addon/prefs.js b/addon/prefs.js
index aed0e64..c83cb49 100644
--- a/addon/prefs.js
+++ b/addon/prefs.js
@@ -2,3 +2,4 @@
pref("__prefsPrefix__.duplicate.default.action", "ask");
pref("__prefsPrefix__.bulk.master.item", "oldest");
pref("__prefsPrefix__.duplicate.stats.enable", true);
+pref("__prefsPrefix__.duplicate.type.mismatch", "skip"); // Options: skip, convert, ask
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index c97685f..22806b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8873,10 +8873,11 @@
}
},
"node_modules/micromatch": {
- "version": "4.0.7",
- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
- "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==",
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
diff --git a/src/modules/merger.ts b/src/modules/merger.ts
index 4bf9acd..e59fd55 100644
--- a/src/modules/merger.ts
+++ b/src/modules/merger.ts
@@ -1,3 +1,15 @@
+import { getPref, TypeMismatch, setPref } from "../utils/prefs";
+import { getString } from "../utils/locale";
+
+async function convertItemType(item: Zotero.Item, targetTypeID: number) {
+ await Zotero.DB.executeTransaction(async () => {
+ item.setType(targetTypeID);
+ await item.save();
+ // Small delay to ensure DB operations complete
+ await Zotero.Promise.delay(50);
+ });
+}
+
export async function merge(
masterItem: Zotero.Item,
otherItems: Zotero.Item[], // Already sorted
@@ -5,16 +17,103 @@ export async function merge(
Zotero.CollectionTreeCache.clear();
const masterItemType = masterItem.itemTypeID;
- otherItems = otherItems.filter((item) => item.itemTypeID === masterItemType);
- if (otherItems.length === 0) {
- return;
+ // Check if any items need type conversion
+ const hasMismatch = otherItems.some(item => item.itemTypeID !== masterItemType);
+
+ if (hasMismatch) {
+ const typeMismatchPref = getPref("duplicate.type.mismatch") as TypeMismatch;
+
+ if (typeMismatchPref === TypeMismatch.ASK) {
+ const dialog = new ztoolkit.Dialog(3, 1)
+ .setDialogData({
+ action: TypeMismatch.SKIP,
+ savePreference: false,
+ // Add promise to track dialog completion
+ dialogPromise: Zotero.Promise.defer()
+ })
+ .addCell(0, 0, {
+ tag: "p",
+ properties: { innerHTML: getString("type-mismatch-message") }
+ })
+ .addCell(1, 0, {
+ tag: "div",
+ children: [
+ {
+ tag: "input",
+ id: "save_pref",
+ attributes: {
+ type: "checkbox",
+ "data-bind": "savePreference",
+ "data-prop": "checked"
+ }
+ },
+ {
+ tag: "label",
+ attributes: { for: "save_pref" },
+ properties: { innerHTML: getString("du-dialog-as-default") }
+ }
+ ]
+ })
+ .addButton(getString("type-mismatch-convert"), "btn_convert", {
+ callback: () => {
+ dialog.dialogData.action = TypeMismatch.CONVERT;
+ if (dialog.dialogData.savePreference) {
+ setPref("duplicate.type.mismatch", TypeMismatch.CONVERT);
+ }
+ dialog.dialogData.dialogPromise.resolve();
+ }
+ })
+ .addButton(getString("type-mismatch-skip"), "btn_skip", {
+ callback: () => {
+ dialog.dialogData.action = TypeMismatch.SKIP;
+ if (dialog.dialogData.savePreference) {
+ setPref("duplicate.type.mismatch", TypeMismatch.SKIP);
+ }
+ dialog.dialogData.dialogPromise.resolve();
+ }
+ });
+
+ dialog.open(getString("type-mismatch-title"), {
+ centerscreen: true,
+ resizable: true
+ });
+
+ // Wait for both dialog load and user action
+ await dialog.dialogData.loadLock?.promise;
+ await dialog.dialogData.dialogPromise.promise;
+
+ if (dialog.dialogData.action === TypeMismatch.CONVERT) {
+ // Convert items one by one
+ for (const item of otherItems) {
+ if (item.itemTypeID !== masterItemType) {
+ await convertItemType(item, masterItemType);
+ }
+ }
+ } else {
+ otherItems = otherItems.filter(item => item.itemTypeID === masterItemType);
+ }
+ }
+ else if (typeMismatchPref === TypeMismatch.CONVERT) {
+ // Convert items one by one
+ for (const item of otherItems) {
+ if (item.itemTypeID !== masterItemType) {
+ await convertItemType(item, masterItemType);
+ }
+ }
+ otherItems = otherItems.filter(item => item.itemTypeID === masterItemType);
+ }
+ else { // TypeMismatch.SKIP
+ otherItems = otherItems.filter(item => item.itemTypeID === masterItemType);
+ }
}
+ if (otherItems.length === 0) return;
+
const masterJSON = masterItem.toJSON();
const candidateJSON: {
+ //[field in Zotero.Item.DataType]?: string | unknown;
[field in _ZoteroTypes.Item.DataType]?: string | unknown;
} = otherItems.reduce((acc, obj) => ({ ...acc, ...obj.toJSON() }), {});
-
// Refer to https://github.com/zotero/zotero/blob/main/chrome/content/zotero/duplicatesMerge.js#L151
// New link since 02/02/2024: https://github.com/zotero/zotero/blob/main/chrome/content/zotero/elements/duplicatesMergePane.js#L172
// Exclude certain properties that are empty in the cloned object, so we don't clobber them
@@ -22,4 +121,4 @@ export async function merge(
masterItem.fromJSON({ ...keep, ...masterJSON });
return await Zotero.Items.merge(masterItem, otherItems);
-}
+}
\ No newline at end of file
diff --git a/src/utils/prefs.ts b/src/utils/prefs.ts
index 14b5c39..8e96d9f 100644
--- a/src/utils/prefs.ts
+++ b/src/utils/prefs.ts
@@ -51,3 +51,12 @@ export enum MasterItem {
MODIFIED = "modified",
DETAILED = "detailed",
}
+
+/**
+ * NOTE: Corresponding to radio values in addon/chrome/content/preferences.xhtml.
+ */
+export enum TypeMismatch {
+ SKIP = "skip",
+ CONVERT = "convert",
+ ASK = "ask"
+}
\ No newline at end of file