Skip to content

Commit

Permalink
Bringing Crossref, Semantic Scholar, Open Citations and Open Alex loo…
Browse files Browse the repository at this point in the history
…kup + auto-import + redesign to Cita for Zotero 7 (#300)

Huge commit that includes:
* Getting citations from Crossref, Semantic Scholar, Open Alex, and Open Citations
* Storing arXiv ID, Open Citations OMID, Open Alex ID, and Semantic Scholar CorpusID for each item
* Restyle the citations pane to match the Zotero 7 item pane style
* Split the bottom of the Citations tab into an Identifiers tab to keep track of all an item's identifiers
* Allow drag and drop reordering of citation order
* Significant code refactoring, with the indexer and pid classes
* much more...
  • Loading branch information
thebluepotato authored Nov 18, 2024
1 parent 1c83e04 commit 49a1273
Show file tree
Hide file tree
Showing 87 changed files with 12,520 additions and 2,046 deletions.
2,166 changes: 1,821 additions & 345 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,22 @@
},
"homepage": "https://github.com/diegodlh/zotero-cita",
"dependencies": {
"@react-hook/resize-observer": "^2.0.2",
"openalex-sdk": "^1.1.6",
"prop-types": "^15.7.2",
"quickstatements-to-wikibase-edit": "^1.2.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^4.1.1",
"react-intersection-observer": "^9.13.1",
"semver-parser": "^4.1.6",
"wikibase-edit": "^7.0.4",
"wikibase-sdk": "^10.0.2",
"zotero-plugin-toolkit": "^2.3.37"
"zotero-plugin-toolkit": "^4.0.6"
},
"devDependencies": {
"@types/language-tags": "^1.0.4",
"@types/lodash": "^4.17.9",
"@types/node": "^20.10.4",
"@types/react-dom": "^18.3.0",
"eslint": "^8.55.0",
Expand All @@ -58,7 +64,7 @@
"typescript": "^5.3.3",
"typescript-eslint": "^7.14.1",
"zotero-plugin-scaffold": "^0.0.34",
"zotero-types": "^2.1.0"
"zotero-types": "^3.0.1"
},
"engines": {
"npm": ">=8.1"
Expand Down
91 changes: 91 additions & 0 deletions patches/openalex-sdk+1.1.6.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
diff --git a/node_modules/openalex-sdk/dist/src/index.js b/node_modules/openalex-sdk/dist/src/index.js
index 70b2df5..518d455 100644
--- a/node_modules/openalex-sdk/dist/src/index.js
+++ b/node_modules/openalex-sdk/dist/src/index.js
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const authors_1 = require("./utils/authors");
const exportCSV_1 = require("./utils/exportCSV");
const helpers_1 = require("./utils/helpers");
diff --git a/node_modules/openalex-sdk/dist/src/utils/authors.js b/node_modules/openalex-sdk/dist/src/utils/authors.js
index 0b1cbc5..f824766 100644
--- a/node_modules/openalex-sdk/dist/src/utils/authors.js
+++ b/node_modules/openalex-sdk/dist/src/utils/authors.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleAllAuthorsPages = exports.handleMultipleAuthorsPages = exports.validateAuthorParameters = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const exportCSV_1 = require("./exportCSV");
const helpers_1 = require("./helpers");
const http_1 = require("./http");
diff --git a/node_modules/openalex-sdk/dist/src/utils/exportCSV.js b/node_modules/openalex-sdk/dist/src/utils/exportCSV.js
index 7f4d748..9c01df5 100644
--- a/node_modules/openalex-sdk/dist/src/utils/exportCSV.js
+++ b/node_modules/openalex-sdk/dist/src/utils/exportCSV.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToCSV = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
function flattenObject(obj, parentKey = '', depth = 0) {
const flattened = {};
for (const [key, value] of Object.entries(obj)) {
diff --git a/node_modules/openalex-sdk/dist/src/utils/institutions.js b/node_modules/openalex-sdk/dist/src/utils/institutions.js
index 1082435..099275a 100644
--- a/node_modules/openalex-sdk/dist/src/utils/institutions.js
+++ b/node_modules/openalex-sdk/dist/src/utils/institutions.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleAllInstitutionsPages = exports.handleMultipleInstitutionsPages = exports.validateInstitutionsParameters = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const exportCSV_1 = require("./exportCSV");
const helpers_1 = require("./helpers");
const http_1 = require("./http");
diff --git a/node_modules/openalex-sdk/dist/src/utils/sources.js b/node_modules/openalex-sdk/dist/src/utils/sources.js
index 699da8d..38c68b8 100644
--- a/node_modules/openalex-sdk/dist/src/utils/sources.js
+++ b/node_modules/openalex-sdk/dist/src/utils/sources.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleAllSourcesPages = exports.handleMultipleSourcesPages = exports.validateSourcesParameters = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const exportCSV_1 = require("./exportCSV");
const helpers_1 = require("./helpers");
const http_1 = require("./http");
diff --git a/node_modules/openalex-sdk/dist/src/utils/topics.js b/node_modules/openalex-sdk/dist/src/utils/topics.js
index b2946cc..b9fd0ed 100644
--- a/node_modules/openalex-sdk/dist/src/utils/topics.js
+++ b/node_modules/openalex-sdk/dist/src/utils/topics.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleAllTopicsPages = exports.handleMultipleTopicsPages = exports.validateTopicsParameters = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const exportCSV_1 = require("./exportCSV");
const helpers_1 = require("./helpers");
const http_1 = require("./http");
diff --git a/node_modules/openalex-sdk/dist/src/utils/works.js b/node_modules/openalex-sdk/dist/src/utils/works.js
index 46d5d38..2a5591c 100644
--- a/node_modules/openalex-sdk/dist/src/utils/works.js
+++ b/node_modules/openalex-sdk/dist/src/utils/works.js
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatNumber = exports.handleAllPagesInChunks = exports.handleMultiplePages = exports.handleAllPages = exports.validateParameters = void 0;
-const fs_1 = __importDefault(require("fs"));
+//const fs_1 = __importDefault(require("fs"));
const exportCSV_1 = require("./exportCSV");
const helpers_1 = require("./helpers");
const http_1 = require("./http");
23 changes: 23 additions & 0 deletions patches/zotero-types+3.0.1.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
diff --git a/node_modules/zotero-types/types/xpcom/db.d.ts b/node_modules/zotero-types/types/xpcom/db.d.ts
index 8a2aeb1..8f9bf2d 100644
--- a/node_modules/zotero-types/types/xpcom/db.d.ts
+++ b/node_modules/zotero-types/types/xpcom/db.d.ts
@@ -70,12 +70,12 @@ declare namespace _ZoteroTypes {
executeTransaction<T>(
func: () => T | Promise<T>,
options?: {
- disbledForeignKeys: boolean;
- timeout: number;
- vacuumOnCommit: boolean;
- inBackup: boolean;
- onCommit: (id: string) => void;
- onRollback: (id: string) => void;
+ disbledForeignKeys?: boolean;
+ timeout?: number;
+ vacuumOnCommit?: boolean;
+ inBackup?: boolean;
+ onCommit?: (id: string) => void;
+ onRollback?: (id: string) => void;
},
): Promise<T>;

164 changes: 164 additions & 0 deletions src/cita/PID.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
export default class PID {
type: PIDType;
id: string;

constructor(type: PIDType, id: string) {
this.type = type;
this.id = id;
}

get url(): string | null {
const cleanPID = this.cleanID;
if (cleanPID) {
switch (this.type) {
case "DOI": {
const url =
"https://doi.org/" +
// From Zotero's itembox.xml:
// Encode some characters that are technically valid in DOIs,
// though generally not used. '/' doesn't need to be encoded.
cleanPID
.replace(/#/g, "%23")
.replace(/\?/g, "%3f")
.replace(/%/g, "%25")
.replace(/"/g, "%22");
return url;
}
case "OMID":
return "https://opencitations.net/meta/" + cleanPID;
case "OpenAlex":
return "https://openalex.org/works/" + cleanPID;
case "arXiv":
return "https://arxiv.org/abs/" + cleanPID;
case "QID":
return "https://www.wikidata.org/wiki/" + cleanPID;
case "CorpusID":
return (
"https://api.semanticscholar.org/CorpusID:" + cleanPID
);
}
}
return null;
}

/** Get the cleaned ID
* @returns The cleaned ID or null if it couldn't be cleaned
*/
get cleanID(): string | null {
switch (this.type) {
case "DOI":
return Zotero.Utilities.cleanDOI(this.id);
case "ISBN":
return Zotero.Utilities.cleanISBN(this.id) || null;
case "QID": {
let qid = this.id.toUpperCase().trim();
if (qid[0] !== "Q") qid = "Q" + qid;
if (!qid.match(/^Q\d+$/)) return null;
return qid;
}
case "OMID": {
let omid = this.id.toLowerCase().trim();
if (/^https?:/.test(omid))
omid = omid.match(/br\/\d+/)?.[0] ?? "";
if (omid.substring(0, 3) !== "br/") omid = "br/" + omid;
if (!omid.match(/^br\/06[1-9]*0\d+$/)) return null;
return omid;
}
case "arXiv": {
const arXiv_RE =
/\b(([-A-Za-z.]+\/\d{7}|\d{4}\.\d{4,5})(?:v(\d+))?)(?!\d)/g; // 1: full ID, 2: ID without version, 3: version #
const m = arXiv_RE.exec(this.id);
if (m) {
const cleanArXiv = m[2];
return cleanArXiv;
}

return null;
}
case "OpenAlex": {
let openAlex = this.id.trim();
if (/^https?:/.test(openAlex))
openAlex = openAlex.match(/[Ww]\d+/)?.[0] ?? "";
openAlex = openAlex.toUpperCase();
if (openAlex[0] !== "W") openAlex = "W" + openAlex;
if (!openAlex.match(/^W\d+$/)) return null;
return openAlex;
}
case "CorpusID": {
const _semantic = parseInt(this.id.trim(), 10);
const semantic = `${_semantic}`;
if (!semantic) return null;
return semantic;
}
default:
return this.id;
}
}

/**
* Clean the ID if possible
* @returns The cleaned ID or null if it couldn't be cleaned
*/
cleaned(): this | null {
if (this.cleanID) {
this.id = this.cleanID;
return this;
} else {
return null;
}
}

get zoteroIdentifier():
| { DOI: string }
| { ISBN: string }
| { arXiv: string }
| { adsBibcode: string }
| { PMID: string }
| null {
return Zotero.Utilities.extractIdentifiers(this.id)[0] ?? null;
}

get comparable(): string | undefined {
const rawID = this.cleanID?.toLowerCase();
return rawID ? `${this.type}:${rawID}` : undefined;
}

static readonly allTypes: PIDType[] = [
"DOI",
"ISBN",
"QID",
"OMID",
"arXiv",
"OpenAlex",
"MAG",
"CorpusID",
"PMID",
"PMCID",
];

static readonly showable: PIDType[] = [
"DOI",
"ISBN",
"QID",
"OMID",
"arXiv",
"OpenAlex",
"CorpusID",
// Don't show PMID or PMCID because we can't fetch citations from them
];

static readonly fetchable: PIDType[] = [
"QID",
"OMID",
"OpenAlex",
"DOI",
"CorpusID",
];

static isEqual(a: PID, b: PID): boolean {
if (a.type !== b.type) return false;
const aComp = a.comparable;
const bComp = b.comparable;
return aComp !== null && aComp === bComp;
}
}
14 changes: 9 additions & 5 deletions src/cita/citation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Wikidata, { CitesWorkClaim } from "./wikidata";
import ItemWrapper from "./itemWrapper";
import SourceItemWrapper from "./sourceItemWrapper";
import Matcher from "./matcher";
import OCI from "../oci";
import OCI, { OCIPIDType } from "../oci";
import Progress from "./progress";
import { EntityId } from "wikibase-sdk";

Expand All @@ -15,11 +15,12 @@ class Citation {
ocis: {
citingId: string;
citedId: string;
idType: "qid" | "doi" | "occ";
idType: OCIPIDType;
oci: string;
supplierName: string;
valid: boolean;
}[];
readonly uuid: string;

/**
* Create a citation.
Expand Down Expand Up @@ -92,6 +93,9 @@ class Citation {
// this.series_ordinal;
// // crosref does provide a citation key which seems to have some ordinal information
// // but I say to leave this out for now

// generate a unique identifier for this citation
this.uuid = crypto.randomUUID();
}

addCreator(creatorType: any, creatorName: string) {
Expand All @@ -114,8 +118,8 @@ class Citation {
try {
newOci = OCI.getOci(
supplier,
this.source[idType]!,
this.target[idType]!,
this.source.getPID(idType)!.id,
this.target.getPID(idType)!.id,
);
} catch {
//
Expand Down Expand Up @@ -330,7 +334,7 @@ class Citation {
return;
}

if (item.libraryID !== this.source.item.libraryID) {
if (item.libraryID && item.libraryID !== this.source.item.libraryID) {
Services.prompt.alert(
window as mozIDOMWindowProxy,
"",
Expand Down
Loading

0 comments on commit 49a1273

Please sign in to comment.