Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ChenglongMa committed Dec 7, 2023
1 parent 8927348 commit 8217856
Show file tree
Hide file tree
Showing 38 changed files with 659 additions and 539 deletions.
25 changes: 14 additions & 11 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"es2021": true
},
"root": true,
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
Expand All @@ -13,18 +17,17 @@
},
"plugins": ["@typescript-eslint"],
"rules": {
"@typescript-eslint/ban-ts-comment": [
"warn",
{
"ts-expect-error": "allow-with-description",
"ts-ignore": "allow-with-description",
"ts-nocheck": "allow-with-description",
"ts-check": "allow-with-description"
}
],
"@typescript-eslint/ban-ts-comment": ["warn", "allow-with-description"],
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": ["off", { "ignoreRestArgs": true }],
"@typescript-eslint/no-non-null-assertion": "off"
},
"ignorePatterns": ["**/build/**", "**/dist/**", "**/node_modules/**", "**/scripts/**", "**/*.js", "**/*.bak"]
"ignorePatterns": [
"**/builds/**",
"**/dist/**",
"**/node_modules/**",
"**/scripts/**",
"**/*.js",
"**/*.bak"
]
}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
**/build
**/builds
node_modules
package-lock.json
zotero-cmd.json
zotero-cmd.json
1 change: 1 addition & 0 deletions .idea/zoplicate.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .release-it.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
},
"github": {
"release": true,
"assets": ["build/*.xpi"]
"assets": ["builds/*.xpi"]
},
"hooks": {
"before:init": "npm run lint",
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
<img src="https://raw.githubusercontent.com/ChenglongMa/zoplicate/main/docs/banner.png" alt="zoplicate banner">
</div>

[![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/ChenglongMa/zoplicate?include_prereleases)](https://github.com/ChenglongMa/zoplicate/releases/latest)
[![Github All Releases](https://img.shields.io/github/downloads/ChenglongMa/zoplicate/total.svg)](https://github.com/ChenglongMa/zoplicate/releases)
[![GitHub package.json version (branch)](https://img.shields.io/github/package-json/v/ChenglongMa/zoplicate/zotero-6)
](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6)
[![Github All Releases](https://img.shields.io/github/downloads/ChenglongMa/zoplicate/latest/total)](https://github.com/ChenglongMa/zoplicate/releases)
[![GitHub release (by tag)](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)
[![zotero target version](https://img.shields.io/badge/Zotero-7-green?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org)
[![zotero target version](https://img.shields.io/badge/Zotero-6-green?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org)
[![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)

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/).
Expand All @@ -22,12 +24,14 @@ The actions you can take are:

# Install

1. Go to the [release page](https://github.com/ChenglongMa/zoplicate/releases) to download [the latest `.xpi` file](https://github.com/ChenglongMa/zoplicate/releases/latest/download/zoplicate.xpi).
1. Go to the [zotero 6 release page](https://github.com/ChenglongMa/zoplicate/releases/tag/zotero6) to 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).

Note: The latest version is only supported for Zotero 7.
## Note
* [The latest version](https://github.com/ChenglongMa/zoplicate/releases/latest) is only supported for Zotero 7.
* This version is only supported for Zotero 6.

# Usage

Expand Down
124 changes: 104 additions & 20 deletions addon/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,85 @@
* [2] https://www.zotero.org/support/dev/zotero_7_for_developers
*/

if (typeof Zotero == "undefined") {
var Zotero;
}

var chromeHandle;

// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js
// to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main
// Zotero window to open and get the Zotero object from there.
//
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
// automatically made available.
async function waitForZotero() {
if (typeof Zotero != "undefined") {
await Zotero.initializationPromise;
}

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var windows = Services.wm.getEnumerator("navigator:browser");
var found = false;
while (windows.hasMoreElements()) {
let win = windows.getNext();
if (win.Zotero) {
Zotero = win.Zotero;
found = true;
break;
}
}
if (!found) {
await new Promise((resolve) => {
var listener = {
onOpenWindow: function (aWindow) {
// Wait for the window to finish loading
let domWindow = aWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
domWindow.addEventListener(
"load",
function () {
domWindow.removeEventListener("load", arguments.callee, false);
if (domWindow.Zotero) {
Services.wm.removeListener(listener);
Zotero = domWindow.Zotero;
resolve();
}
},
false
);
},
};
Services.wm.addListener(listener);
});
}
await Zotero.initializationPromise;
}

function install(data, reason) {}

async function startup({ id, version, resourceURI, rootURI }, reason) {
await Zotero.initializationPromise;
await waitForZotero();

// String 'rootURI' introduced in Zotero 7
if (!rootURI) {
rootURI = resourceURI.spec;
}

var aomStartup = Components.classes["@mozilla.org/addons/addon-manager-startup;1"].getService(
Components.interfaces.amIAddonManagerStartup,
);
var manifestURI = Services.io.newURI(rootURI + "manifest.json");
chromeHandle = aomStartup.registerChrome(manifestURI, [["content", "__addonRef__", rootURI + "chrome/content/"]]);
if (Zotero.platformMajorVersion >= 102) {
var aomStartup = Components.classes[
"@mozilla.org/addons/addon-manager-startup;1"
].getService(Components.interfaces.amIAddonManagerStartup);
var manifestURI = Services.io.newURI(rootURI + "manifest.json");
chromeHandle = aomStartup.registerChrome(manifestURI, [
["content", "__addonRef__", rootURI + "chrome/content/"],
["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
]);
} else {
setDefaultPrefs(rootURI);
}

/**
* Global variables for plugin code.
Expand All @@ -34,30 +96,28 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
};
ctx._globalThis = ctx;

Services.scriptloader.loadSubScript(`${rootURI}/chrome/content/scripts/__addonRef__.js`, ctx);
}

async function onMainWindowLoad({ window }, reason) {
Zotero.__addonInstance__?.hooks.onMainWindowLoad(window);
}

async function onMainWindowUnload({ window }, reason) {
Zotero.__addonInstance__?.hooks.onMainWindowUnload(window);
Services.scriptloader.loadSubScript(
`${rootURI}/chrome/content/scripts/index.js`,
ctx
);
}

function shutdown({ id, version, resourceURI, rootURI }, reason) {
if (reason === APP_SHUTDOWN) {
return;
}

if (typeof Zotero === "undefined") {
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(Components.interfaces.nsISupports).wrappedJSObject;
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports
).wrappedJSObject;
}
Zotero.__addonInstance__?.hooks.onShutdown();
Zotero.__addonInstance__.hooks.onShutdown();

Cc["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService).flushBundles();
Cc["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.flushBundles();

Cu.unload(`${rootURI}/chrome/content/scripts/__addonRef__.js`);
Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);

if (chromeHandle) {
chromeHandle.destruct();
Expand All @@ -66,3 +126,27 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
}

function uninstall(data, reason) {}

// Loads default preferences from defaults/preferences/prefs.js in Zotero 6
function setDefaultPrefs(rootURI) {
var branch = Services.prefs.getDefaultBranch("");
var obj = {
pref(pref, value) {
switch (typeof value) {
case "boolean":
branch.setBoolPref(pref, value);
break;
case "string":
branch.setStringPref(pref, value);
break;
case "number":
branch.setIntPref(pref, value);
break;
default:
Zotero.logError(`Invalid type '${typeof value}' for pref '${pref}'`);
}
},
};
Zotero.getMainWindow().console.log(rootURI + "prefs.js");
Services.scriptloader.loadSubScript(rootURI + "prefs.js", obj);
}
3 changes: 3 additions & 0 deletions addon/chrome.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
content __addonRef__ chrome/content/
locale __addonRef__ en-US chrome/locale/en-US/
locale __addonRef__ zh-CN chrome/locale/zh-CN/
33 changes: 12 additions & 21 deletions addon/chrome/content/preferences.xhtml
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
<linkset>
<html:link rel="localization" href="__addonRef__-preferences.ftl" />
</linkset>
<vbox id="zotero-prefpane-__addonRef__" onload="Zotero.__addonInstance__.hooks.onPrefsEvent('load', {window})">
<label>
<html:h1>__addonName__</html:h1>
</label>
<groupbox>
<label>
<html:h2 data-l10n-id="pref-title"></html:h2>
</label>
<label><html:h2>&zotero.__addonRef__.pref.title;</html:h2></label>

<label data-l10n-id="pref-default-action-title" />
<!-- `preference="extensions.zotero.__addonRef__.default.action"` also works. -->
<!-- Because __prefsPrefix__ = extensions.zotero.__addonRef__ in package.json-->
<label value="&zotero.__addonRef__.pref.default.action.title;"/>
<radiogroup
id="zotero-prefpane-__addonRef__-default-action"
preference="__prefsPrefix__.duplicate.default.action"
orient="vertical">
<radio data-l10n-id="pref-default-action-keep-this" value="keep" />
<radio data-l10n-id="pref-default-action-keep-others" value="discard" />
<radio data-l10n-id="pref-default-action-keep-all" value="cancel" />
<radio data-l10n-id="pref-default-action-always-ask" value="ask" />
<radio label="&zotero.__addonRef__.pref.default.action.keep.this;" value="keep" />
<radio label="&zotero.__addonRef__.pref.default.action.keep.others;" value="discard" />
<radio label="&zotero.__addonRef__.pref.default.action.keep.all;" value="cancel" />
<radio label="&zotero.__addonRef__.pref.default.action.always.ask;" value="ask" />
</radiogroup>
</groupbox>
</vbox>

<spacer height="20" />
<vbox>
<html:label
data-l10n-id="pref-help"
data-l10n-args='{"time": "__buildTime__","name": "__addonName__","version":"__buildVersion__"}'></html:label>
</vbox>
<groupbox>
<label><html:h2>About</html:h2></label>
<label value="&zotero.__addonRef__.help.version.label;" />
<label value="&zotero.__addonRef__.help.releasetime.label;" />
<label value="Help" class="zotero-text-link" href="__homepage__" />
</groupbox>
30 changes: 30 additions & 0 deletions addon/chrome/locale/en-US/addon.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
startup.begin = Addon is loading
startup.finish = Addon is ready
menuitem.label = Addon Template: Helper Examples
menupopup.label = Addon Template: Menupopup
menuitem.submenulabel = Addon Template
menuitem.filemenulabel = Addon Template: File Menuitem
prefs.table.title = Title
prefs.table.detail = Detail
tabpanel.lib.tab.label = Lib Tab
tabpanel.reader.tab.label = Reader Tab

prefs.title = Zoplicate
general.cancel = Cancel

du.dialog.title = Found Duplicate Items
du.dialog.header = The following items have existed in your library. How would you like to process them?

du.dialog.table.title = Title
du.dialog.table.keep = Keep This
du.dialog.table.discard = Keep Others
du.dialog.table.cancel = Keep All
du.dialog.as.default = Use this action by default

du.dialog.button.apply = Apply
du.dialog.button.go.duplicates = Go to Duplicates
du.dialog.button.cancel = Cancel

du.progress.text = Processing Duplicates...
du.progress.done = All duplicates have been processed.

9 changes: 9 additions & 0 deletions addon/chrome/locale/en-US/overlay.dtd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!ENTITY zotero.__addonRef__.pref.title "Default Preferences">
<!ENTITY zotero.__addonRef__.pref.default.action.title "Default action to process duplicate items">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.this "[Keep This]: Save the last imported item and delete the rest">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.others "[Keep Others]: Delete the last imported item and save the rest">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.all "[Keep All]: Save all items">
<!ENTITY zotero.__addonRef__.pref.default.action.always.ask "[Always Ask]: Ask for action every time">

<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
30 changes: 30 additions & 0 deletions addon/chrome/locale/zh-CN/addon.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
startup.begin = 插件加载中
startup.finish = 插件已就绪
menuitem.label = 插件模板: 帮助工具样例
menupopup.label = 插件模板: 弹出菜单
menuitem.submenulabel = 插件模板:子菜单
menuitem.filemenulabel = 插件模板: 文件菜单
prefs.table.title = 标题
prefs.table.detail = 详情
tabpanel.lib.tab.label = 库标签
tabpanel.reader.tab.label = 阅读器标签


prefs.title = Zoplicate
general.cancel = 取消

du.dialog.title = 检测到重复条目
du.dialog.header = 以下条目已存在于您的库中。您想如何处理它们?

du.dialog.table.title = 标题
du.dialog.table.keep = 保留最新的
du.dialog.table.discard = 保留已有的
du.dialog.table.cancel = 保留全部
du.dialog.as.default = 将此操作设为默认值

du.dialog.button.apply = 应用
du.dialog.button.go.duplicates = 手动合并
du.dialog.button.cancel = 取消

du.progress.text = 正在处理重复条目...
du.progress.done = 所有重复条目已处理完成。
9 changes: 9 additions & 0 deletions addon/chrome/locale/zh-CN/overlay.dtd
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!ENTITY zotero.__addonRef__.pref.title "默认设置">
<!ENTITY zotero.__addonRef__.pref.default.action.title "处理重复条目的默认操作">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.this "[保留最新的]: 保留新导入条目,删除库中原有的">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.others "[保留已有的]: 保留库中原有条目,删除新导入的">
<!ENTITY zotero.__addonRef__.pref.default.action.keep.all "[保留全部]: 保留所有条目,不删除任何项">
<!ENTITY zotero.__addonRef__.pref.default.action.always.ask "[始终询问]: 每次都询问我该如何处理">

<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">
Loading

0 comments on commit 8217856

Please sign in to comment.