Skip to content

Commit

Permalink
Merge pull request #34 from ChenglongMa/dev
Browse files Browse the repository at this point in the history
Release v3.0.1
  • Loading branch information
ChenglongMa authored May 2, 2024
2 parents 7567248 + bf8696c commit e00f897
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 134 deletions.
9 changes: 6 additions & 3 deletions .idea/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ A plugin that does one thing only: **Detect** and **Manage** duplicate items in
# Changelog

## v3.0.1

<details markdown="1" open>
<summary><i>Click here to show more.</i></summary>

In this version, we have made the following changes:

1. 🧬 **CHANGE!**: We have updated the UI of buttons in the duplicate pane.
2. 🐛 **FIX!**: We have optimized the performance of duplicate search and detection.

</details>

## v3.0.0

<details markdown="1" open>
Expand Down
39 changes: 15 additions & 24 deletions addon/chrome/content/zoplicate.css
Original file line number Diff line number Diff line change
@@ -1,30 +1,21 @@
.duplicate-box-button {
-moz-appearance: none;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, .65);
background: -moz-linear-gradient(rgba(110, 110, 110, .9), rgba(70, 70, 70, .9) 49%, rgba(50, 50, 50, .9) 51%, rgba(40, 40, 40, .9));
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), inset 0 0 1px rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1);
background-clip: padding-box;
background-origin: padding-box;
padding: 2px 9px;
margin: 6px;
min-height: 22px;
.duplicate-custom-head {
display: flex;
flex-direction: row;
align-self: stretch;
gap: 6px;
padding: 6px 8px;
background: var(--material-toolbar);
border-bottom: var(--material-panedivider);
height: 28px;
}

.duplicate-box-button[disabled="false"]:-moz-focusring {
box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
}

.duplicate-box-button[disabled="false"]:hover{
background: white;
.duplicate-custom-head button {
height: 26px;
margin: 0;
flex-grow: 1;
cursor: pointer;
}

.duplicate-box-button[disabled="false"]:hover:active {
background: -moz-linear-gradient(rgba(83, 128, 232, .9), rgba(91, 118, 255, .9));
box-shadow: inset 0 0 3px rgba(0, 0, 0, .2), inset 0 1px 7px rgba(0, 0, 0, .4), 0 1px 0 rgba(255, 255, 255, .1);
}

.duplicate-box-button[disabled="true"] {
filter: grayscale(100%);
.duplicate-custom-head:empty {
display: none;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zoplicate",
"version": "3.0.0",
"version": "3.0.1",
"description": "Detect and manage duplicate items in Zotero.",
"config": {
"addonName": "Zoplicate",
Expand Down
3 changes: 3 additions & 0 deletions src/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Addon {
duplicateSearchObj: { [libraryID: number]: Zotero.Search };
duplicateCounts: { [libraryID: number]: { total: number; unique: number } };
duplicateSets: { [libraryID: number]: typeof Zotero.DisjointSetForest };
nonDuplicateSectionID: string | false;
};
// Lifecycle hooks
public hooks: typeof hooks;
Expand All @@ -44,6 +45,7 @@ class Addon {
duplicateSearchObj: {},
duplicateCounts: {},
duplicateSets: {},
nonDuplicateSectionID: false,
};
this.hooks = hooks;
this.api = {};
Expand All @@ -61,6 +63,7 @@ class Addon {
duplicateSearchObj: {},
duplicateCounts: {},
duplicateSets: {},
nonDuplicateSectionID: false,
};
this.hooks = hooks;
this.api = {};
Expand Down
54 changes: 22 additions & 32 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import { fetchDuplicates, registerButtonsInDuplicatePane } from "./modules/dupli
import menus from "./modules/menus";
// import "./modules/zduplicates.js";
import database from "./modules/db";
import { NonDuplicates, registerNonDuplicatesSection } from "./modules/nonDuplicates";
import { patchGetSearchObject, patchItemSaveData } from "./modules/patcher";
import { registerNonDuplicatesSection } from "./modules/nonDuplicates";
import {
patchFindDuplicates,
patchGetSearchObject,
patchItemSaveData
} from "./modules/patcher";
import { containsRegularItem, isInDuplicatesPane, refreshItemTree } from "./utils/zotero";
import { registerDuplicateStats } from "./modules/duplicateStats";
import { waitUtilAsync } from "./utils/wait";
import Dexie from "dexie";

async function onStartup() {
await Promise.all([Zotero.initializationPromise, Zotero.unlockPromise, Zotero.uiReadyPromise]);
Expand All @@ -29,46 +32,33 @@ async function onStartup() {
await onMainWindowLoad(window);
}

function handleError(e:any) {
switch (e.name) {
case "AbortError":
if (e.inner) {
return handleError(e.inner);
}
ztoolkit.log("Abort error " + e.message);
break;
case "QuotaExceededError":
ztoolkit.log("QuotaExceededError " + e.message);
break;
default:
ztoolkit.log(e);
break;
}
}

async function onMainWindowLoad(win: Window): Promise<void> {
await waitUtilAsync(() => document.readyState === "complete");
// Dexie.dependencies.indexedDB = window.indexedDB;
// Dexie.dependencies.IDBKeyRange = window.IDBKeyRange;
// await waitUtilAsync(() => document.readyState === "complete");

// create ztoolkit
addon.data.ztoolkit = createZToolkit();

// init database
const db = database.getDatabase();
ztoolkit.log("onMainWindowLoad before db init", window.IDBKeyRange);
await db.init();
db.insertNonDuplicatePair(1, 2, 1);//.catch((e) => handleError(e));
ztoolkit.log("insert done");

NonDuplicates.getInstance().init(db);
registerNonDuplicatesSection(db);
// register stylesheets and preferences
registerStyleSheets();
registerPrefs();

// patch Zotero duplicate search object and events
Notifier.registerNotifier();
BulkDuplicates.getInstance().registerUIElements(win);
menus.registerMenus(win);
patchFindDuplicates(db);
patchGetSearchObject();
patchItemSaveData();

// register duplicate UI elements
await registerDuplicateStats();
registerButtonsInDuplicatePane(win);
ztoolkit.log("onMainWindowLoad done");
await registerButtonsInDuplicatePane(win);
BulkDuplicates.getInstance().registerUIElements(win);
registerNonDuplicatesSection(db);

menus.registerMenus(win);
}

async function onMainWindowUnload(win: Window): Promise<void> {
Expand Down
63 changes: 24 additions & 39 deletions src/modules/bulkDuplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { getString } from "../utils/locale";
import { config } from "../../package.json";
import { getPref, MasterItem } from "../utils/prefs";
import { truncateString } from "../utils/utils";
import { fetchDuplicates } from "./duplicates";
import { fetchDuplicates, updateDuplicateButtonsVisibilities } from "./duplicates";
import { merge } from "./merger";
import { isInDuplicatesPane, refreshItemTree } from "../utils/zotero";
import { updateButtonDisabled } from "../utils/view";
import { DuplicateItems } from "./duplicateItems";

export class BulkDuplicates {
Expand Down Expand Up @@ -45,18 +44,21 @@ export class BulkDuplicates {
}

private getBulkMergeButtons(win: Window) {
return [win.document.getElementById(BulkDuplicates.innerButtonID), win.document.getElementById(BulkDuplicates.externalButtonID)];
return [
win.document.getElementById(BulkDuplicates.innerButtonID),
win.document.getElementById(BulkDuplicates.externalButtonID),
];
}

public createBulkMergeButton(win: Window, id: string): TagElementProps {
public createBulkMergeButton(win: Window, id: string, showing = true): TagElementProps {
return {
tag: "button",
id: id,
attributes: {
label: getString("bulk-merge-title"),
image: `chrome://${config.addonRef}/content/icons/merge.svg`,
hidden: !showing,
},
classList: ["duplicate-box-button"],
namespace: "xul",
listeners: [
{
Expand Down Expand Up @@ -175,47 +177,30 @@ export class BulkDuplicates {

registerUIElements(win: Window): void {
this.win = win;
const msgID = "zoplicate-bulk-merge-message";
const msgVBox: TagElementProps = {
tag: "vbox",
id: msgID,
properties: {
textContent: getString("duplicate-panel-message"),
},
ignoreIfExists: true,
};

ZoteroPane.collectionsView &&
ZoteroPane.collectionsView.onSelect.addListener(async () => {
ztoolkit.log(`Main window loaded`, win.indexedDB);

const groupBox = win.document.getElementById("zotero-item-pane-groupbox") as Element;
if (isInDuplicatesPane()) {
ztoolkit.UI.appendElement(msgVBox, groupBox);
ztoolkit.UI.appendElement(this.createBulkMergeButton(win, BulkDuplicates.externalButtonID), groupBox);
if (this._isRunning && ZoteroPane.itemsView) {
await ZoteroPane.itemsView.waitForLoad();
ZoteroPane.itemsView.selection.clearSelection();
}
} else {
const externalButton = win.document.getElementById(BulkDuplicates.externalButtonID);
if (externalButton) {
groupBox.removeChild(win.document.getElementById(msgID)!);
groupBox.removeChild(externalButton);
}
ztoolkit.log("collectionsView onSelect");
const inDuplicatePane = isInDuplicatesPane();
if (ZoteroPane.itemsView && inDuplicatePane && this._isRunning) {
await ZoteroPane.itemsView.waitForLoad();
ZoteroPane.itemsView.selection.clearSelection();
}
});

ZoteroPane.itemsView &&
ZoteroPane.itemsView.onRefresh.addListener(() => {
// ztoolkit.log("refresh");
if (isInDuplicatesPane() && ZoteroPane.itemsView) {
const disabled = ZoteroPane.itemsView.rowCount <= 0;
updateButtonDisabled(win!, disabled, BulkDuplicates.innerButtonID, BulkDuplicates.externalButtonID);
if (this._isRunning) {
ZoteroPane.itemsView.selection.clearSelection();
}
ZoteroPane.itemsView.onRefresh.addListener(async () => {
ztoolkit.log("refresh");
const precondition = isInDuplicatesPane();
if (precondition && ZoteroPane.itemsView && this._isRunning) {
ZoteroPane.itemsView.selection.clearSelection();
}
await updateDuplicateButtonsVisibilities();
});

ZoteroPane.itemsView &&
ZoteroPane.itemsView.onSelect.addListener(async () => {
ztoolkit.log("itemsView.onSelect");
await updateDuplicateButtonsVisibilities();
});
}
}
52 changes: 42 additions & 10 deletions src/modules/duplicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,55 @@ import { DialogHelper } from "zotero-plugin-toolkit/dist/helpers/dialog";
import { TagElementProps } from "zotero-plugin-toolkit/dist/tools/ui";
import { getPref, setPref, Action, MasterItem } from "../utils/prefs";
import { merge } from "./merger";
import { goToDuplicatesPane } from "../utils/zotero";
import { goToDuplicatesPane, isInDuplicatesPane } from "../utils/zotero";
import { DuplicateItems } from "./duplicateItems";
import { createNonDuplicateButton } from "./nonDuplicates";
import { createNonDuplicateButton, NonDuplicates } from "./nonDuplicates";
import { BulkDuplicates } from "./bulkDuplicates";
import { toggleButtonHidden } from "../utils/view";

function addButtonsInDuplicatePanes(innerButton: boolean, siblingElement: Element) {
const mergeButtonID = innerButton ? BulkDuplicates.innerButtonID : BulkDuplicates.externalButtonID;
const nonDuplicateButtonID = innerButton ? NonDuplicates.innerButtonID : NonDuplicates.externalButtonID;
ztoolkit.UI.insertElementBefore(
{
tag: "div",
namespace: "html",
classList: ["duplicate-custom-head", "empty"],
children: [
BulkDuplicates.getInstance().createBulkMergeButton(siblingElement.ownerDocument.defaultView!, mergeButtonID),
createNonDuplicateButton(nonDuplicateButtonID),
],
},
siblingElement,
);
}

export function registerButtonsInDuplicatePane(win: Window) {
const mergeButton = win.document.getElementById("zotero-duplicates-merge-button") as Element;
export async function registerButtonsInDuplicatePane(win: Window) {
// const duplicatePane = win.document.getElementById("zotero-duplicates-merge-pane");
// 1. when selecting items in duplicatePane
const mergeButton = win.document.getElementById("zotero-duplicates-merge-button");
if (mergeButton) {
ztoolkit.UI.insertElementBefore(createNonDuplicateButton(), mergeButton);
ztoolkit.UI.insertElementBefore(
BulkDuplicates.getInstance().createBulkMergeButton(win, BulkDuplicates.innerButtonID),
mergeButton,
);
const groupBox = mergeButton.parentElement as Element;
addButtonsInDuplicatePanes(true, groupBox);
}
// 2. when not selecting items, i.e., in itemMessagePane
const customHead = win.document.querySelector("item-message-pane .custom-head");
if (customHead) {
addButtonsInDuplicatePanes(false, customHead);
}

await updateDuplicateButtonsVisibilities();
}

export async function updateDuplicateButtonsVisibilities() {
const inDuplicatePane = isInDuplicatesPane();
const showBulkMergeButton = inDuplicatePane && ZoteroPane.itemsView && ZoteroPane.itemsView.rowCount > 0;
const showNonDuplicateButton = inDuplicatePane && (await areDuplicates());
toggleButtonHidden(window, !showBulkMergeButton, BulkDuplicates.innerButtonID, BulkDuplicates.externalButtonID);
toggleButtonHidden(window, !showNonDuplicateButton, NonDuplicates.innerButtonID, NonDuplicates.externalButtonID);
}

export async function areDuplicates(items: number[] | Zotero.Item[]) {
export async function areDuplicates(items: number[] | Zotero.Item[] = ZoteroPane.getSelectedItems()) {
if (items.length < 2) return false;
const libraryIDs = new Set(
items.map((item) => (typeof item === "number" ? Zotero.Items.get(item).libraryID : item.libraryID)),
Expand Down
Loading

0 comments on commit e00f897

Please sign in to comment.