From e76932910c30ac2d8b3d915f629025811a4e2e2a Mon Sep 17 00:00:00 2001
From: windingwind <33902321+windingwind@users.noreply.github.com>
Date: Fri, 21 Jul 2023 09:16:16 +0800
Subject: [PATCH] update: plugin template
---
.eslintrc.json | 12 +-
.github/FUNDING.yml | 2 +-
.gitignore | 2 +-
.release-it copy.json | 13 --
.vscode/launch.json | 28 ++--
.vscode/settings.json | 3 +-
addon/bootstrap.js | 52 ++-----
addon/chrome/content/previewPDF.html | 26 ++--
package.json | 5 +-
scripts/build.mjs | 208 +++++++++++++++++----------
scripts/reload.mjs | 17 +--
scripts/start.mjs | 84 ++++++++---
scripts/zotero-cmd-default.json | 14 +-
src/hooks.ts | 4 +-
src/modules/container.ts | 4 +-
src/modules/listeners.ts | 8 +-
src/modules/preference.ts | 2 +-
src/modules/preview.ts | 14 +-
src/modules/split.ts | 28 ++--
src/modules/tab.ts | 2 +-
src/utils/locale.ts | 25 ++--
src/utils/wait.ts | 4 +-
tsconfig.json | 4 +-
{typing => typings}/global.d.ts | 0
24 files changed, 324 insertions(+), 237 deletions(-)
delete mode 100644 .release-it copy.json
rename {typing => typings}/global.d.ts (100%)
diff --git a/.eslintrc.json b/.eslintrc.json
index 5aa2af8..bd779ad 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -17,13 +17,21 @@
},
"plugins": ["@typescript-eslint"],
"rules": {
- "@typescript-eslint/ban-ts-comment": ["warn", "allow-with-description"],
+ "@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/no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": ["off", { "ignoreRestArgs": true }],
"@typescript-eslint/no-non-null-assertion": "off"
},
"ignorePatterns": [
- "**/builds/**",
+ "**/build/**",
"**/dist/**",
"**/node_modules/**",
"**/scripts/**",
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c06fda3..4ea3d26 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -10,4 +10,4 @@ liberapay: windingwind
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
-custom: ["https://paypal.me/windingwind?country.x=C2&locale.x=zh_XC", ]
+custom: ["https://paypal.me/windingwind?country.x=C2&locale.x=zh_XC"]
diff --git a/.gitignore b/.gitignore
index d2d96bf..d277962 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-**/builds
+**/build
node_modules
package-lock.json
zotero-cmd.json
\ No newline at end of file
diff --git a/.release-it copy.json b/.release-it copy.json
deleted file mode 100644
index 2b66daf..0000000
--- a/.release-it copy.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "npm": {
- "publish": false
- },
- "github": {
- "release": true,
- "assets": ["builds/*.xpi"]
- },
- "hooks": {
- "after:bump": "npm run build",
- "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
- }
-}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 393360c..07326a3 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,15 +1,15 @@
{
- // 使用 IntelliSense 了解相关属性。
- // 悬停以查看现有属性的描述。
- // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [
- {
- "type": "node",
- "request": "launch",
- "name": "Restart",
- "runtimeExecutable": "npm",
- "runtimeArgs": ["run", "restart"],
- }
- ]
-}
\ No newline at end of file
+ // 使用 IntelliSense 了解相关属性。
+ // 悬停以查看现有属性的描述。
+ // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Restart",
+ "runtimeExecutable": "npm",
+ "runtimeArgs": ["run", "restart"]
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 7a73a41..0967ef4 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,2 +1 @@
-{
-}
\ No newline at end of file
+{}
diff --git a/addon/bootstrap.js b/addon/bootstrap.js
index 752b416..887e3ab 100644
--- a/addon/bootstrap.js
+++ b/addon/bootstrap.js
@@ -51,7 +51,7 @@ async function waitForZotero() {
resolve();
}
},
- false
+ false,
);
},
};
@@ -71,17 +71,13 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
rootURI = resourceURI.spec;
}
- 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/"],
- ]);
- } else {
- setDefaultPrefs(rootURI);
- }
+ 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/"],
+ ]);
/**
* Global variables for plugin code.
@@ -95,8 +91,8 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
ctx._globalThis = ctx;
Services.scriptloader.loadSubScript(
- `${rootURI}/chrome/content/scripts/index.js`,
- ctx
+ `${rootURI}/chrome/content/scripts/__addonRef__.js`,
+ ctx,
);
}
@@ -106,7 +102,7 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
}
if (typeof Zotero === "undefined") {
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
- Components.interfaces.nsISupports
+ Components.interfaces.nsISupports,
).wrappedJSObject;
}
Zotero.__addonInstance__.hooks.onShutdown();
@@ -115,7 +111,7 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
.getService(Components.interfaces.nsIStringBundleService)
.flushBundles();
- Cu.unload(`${rootURI}/chrome/content/scripts/index.js`);
+ Cu.unload(`${rootURI}/chrome/content/scripts/__addonRef__.js`);
if (chromeHandle) {
chromeHandle.destruct();
@@ -124,27 +120,3 @@ 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);
-}
diff --git a/addon/chrome/content/previewPDF.html b/addon/chrome/content/previewPDF.html
index 2c44fb0..4558f70 100644
--- a/addon/chrome/content/previewPDF.html
+++ b/addon/chrome/content/previewPDF.html
@@ -1,4 +1,4 @@
-
+
@@ -152,7 +152,7 @@
function getScale() {
return parseFloat(
- Zotero.Prefs.get("__prefsPrefix__.previewScale", true)
+ Zotero.Prefs.get("__prefsPrefix__.previewScale", true),
);
}
@@ -160,7 +160,7 @@
Zotero.Prefs.set(
"__prefsPrefix__.previewScale",
String(currentScale),
- true
+ true,
);
}
@@ -274,7 +274,7 @@
return;
}
const annotationLayer = document.querySelector(
- `#annotation-layer-${pageNumber}`
+ `#annotation-layer-${pageNumber}`,
);
annotationLayer.width = viewport.width * ratio;
annotationLayer.height = viewport.height * ratio;
@@ -316,7 +316,7 @@
rect[0] * totalScale,
(H - rect[3]) * totalScale,
(rect[2] - rect[0]) * totalScale,
- (rect[3] - rect[1]) * totalScale
+ (rect[3] - rect[1]) * totalScale,
);
}
} else if (annot.type === "image") {
@@ -326,7 +326,7 @@
rect[0] * totalScale,
(H - rect[3]) * totalScale,
(rect[2] - rect[0]) * totalScale,
- (rect[3] - rect[1]) * totalScale
+ (rect[3] - rect[1]) * totalScale,
);
}
} else if (annot.type === "note") {
@@ -336,7 +336,7 @@
_ctx.drawImage(
this,
rect[0] * totalScale,
- (H - rect[3]) * totalScale
+ (H - rect[3]) * totalScale,
);
};
let str =
@@ -344,7 +344,7 @@
encodeURIComponent(
noteIcon
.replace(/__size__/g, 24 * totalScale)
- .replace(/__color__/g, annot.color)
+ .replace(/__color__/g, annot.color),
);
img.src = str;
}
@@ -354,12 +354,12 @@
_ctx.beginPath();
_ctx.moveTo(
path[0] * totalScale,
- (H - path[1]) * totalScale
+ (H - path[1]) * totalScale,
);
for (i = 2; i < path.length; i += 2) {
_ctx.lineTo(
path[i] * totalScale,
- (H - path[i + 1]) * totalScale
+ (H - path[i + 1]) * totalScale,
);
}
_ctx.stroke();
@@ -389,7 +389,7 @@
let pages = [];
const pageNumber = Zotero.Prefs.get(
"__prefsPrefix__.previewPageNum",
- true
+ true,
);
const totalNumber = cachedData.pdfDocument.numPages;
if (pageNumber.includes(",") || pageNumber.includes(":")) {
@@ -458,7 +458,7 @@
}
// pdf.js page index starts from 1
const currentAnnotations = cachedData.appAnnotations.filter(
- (annot) => annot.position.pageIndex === i - 1
+ (annot) => annot.position.pageIndex === i - 1,
);
console.log(cachedData.appAnnotations, currentAnnotations);
const canvasLayer = document.createElement("canvas");
@@ -473,7 +473,7 @@
i,
canvasLayer,
cachedData.viewportWidth,
- currentAnnotations
+ currentAnnotations,
);
}
Zotero.PDFPreview.data.state.loadingPromise.resolve();
diff --git a/package.json b/package.json
index 56af942..4f6ab2d 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
"restart": "npm run restart-dev",
"reload": "npm run build-dev && node scripts/reload.mjs",
"watch": "chokidar \"src/**\" \"addon/**\" -c \"npm run reload\"",
- "release": "release-it",
+ "release": "npm run lint && release-it",
"lint": "prettier --write . && eslint . --ext .ts --fix",
"test": "echo \"Error: no test specified\" && exit 1",
"update-deps": "npm update --save"
@@ -46,7 +46,7 @@
"devDependencies": {
"@types/node": "^20.3.1",
"@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^5.60.0",
+ "@typescript-eslint/parser": "^6.0.0",
"chokidar-cli": "^3.0.0",
"compressing": "^1.9.0",
"concurrently": "^8.2.0",
@@ -54,7 +54,6 @@
"esbuild": "^0.18.6",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
- "minimist": "^1.2.8",
"prettier": "3.0.0",
"release-it": "^16.1.2",
"replace-in-file": "^7.0.1",
diff --git a/scripts/build.mjs b/scripts/build.mjs
index ab4a3eb..2ef6a96 100644
--- a/scripts/build.mjs
+++ b/scripts/build.mjs
@@ -1,6 +1,6 @@
import { build } from "esbuild";
import { zip } from "compressing";
-import { join, basename } from "path";
+import path from "path";
import {
existsSync,
lstatSync,
@@ -13,18 +13,22 @@ import {
} from "fs";
import { env, exit } from "process";
import replaceInFile from "replace-in-file";
-const { sync } = replaceInFile;
+const { replaceInFileSync } = replaceInFile;
import details from "../package.json" assert { type: "json" };
const { name, author, description, homepage, version, config } = details;
+const t = new Date();
+const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());
+const buildDir = "build";
+
function copyFileSync(source, target) {
var targetFile = target;
// If target is a directory, a new file with the same name will be created
if (existsSync(target)) {
if (lstatSync(target).isDirectory()) {
- targetFile = join(target, basename(source));
+ targetFile = path.join(target, path.basename(source));
}
}
@@ -35,7 +39,7 @@ function copyFolderRecursiveSync(source, target) {
var files = [];
// Check if folder needs to be created or integrated
- var targetFolder = join(target, basename(source));
+ var targetFolder = path.join(target, path.basename(source));
if (!existsSync(targetFolder)) {
mkdirSync(targetFolder);
}
@@ -44,7 +48,7 @@ function copyFolderRecursiveSync(source, target) {
if (lstatSync(source).isDirectory()) {
files = readdirSync(source);
files.forEach(function (file) {
- var curSource = join(source, file);
+ var curSource = path.join(source, file);
if (lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
@@ -77,44 +81,39 @@ function dateFormat(fmt, date) {
if (ret) {
fmt = fmt.replace(
ret[1],
- ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
+ ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"),
);
}
}
return fmt;
}
-async function main() {
- const t = new Date();
- const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", t);
- const buildDir = "builds";
-
- console.log(
- `[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
- env.NODE_ENV,
- ]}`
- );
-
- clearFolder(buildDir);
-
- copyFolderRecursiveSync("addon", buildDir);
-
- copyFileSync("update-template.rdf", "update.rdf");
- copyFileSync("update-template.json", "update.json");
+function renameLocaleFiles() {
+ const localeDir = path.join(buildDir, "addon/locale");
+ const localeFolders = readdirSync(localeDir, { withFileTypes: true })
+ .filter((dirent) => dirent.isDirectory())
+ .map((dirent) => dirent.name);
- await build({
- entryPoints: ["src/index.ts"],
- define: {
- __env__: `"${env.NODE_ENV}"`,
- },
- bundle: true,
- outfile: join(buildDir, "addon/chrome/content/scripts/index.js"),
- // Don't turn minify on
- // minify: true,
- }).catch(() => exit(1));
+ for (const localeSubFolder of localeFolders) {
+ const localeSubDir = path.join(localeDir, localeSubFolder);
+ const localeSubFiles = readdirSync(localeSubDir, {
+ withFileTypes: true,
+ })
+ .filter((dirent) => dirent.isFile())
+ .map((dirent) => dirent.name);
- console.log("[Build] Run esbuild OK");
+ for (const localeSubFile of localeSubFiles) {
+ if (localeSubFile.endsWith(".ftl")) {
+ renameSync(
+ path.join(localeSubDir, localeSubFile),
+ path.join(localeSubDir, `${config.addonRef}-${localeSubFile}`),
+ );
+ }
+ }
+ }
+}
+function replaceString() {
const replaceFrom = [
/__author__/g,
/__description__/g,
@@ -122,25 +121,20 @@ async function main() {
/__buildVersion__/g,
/__buildTime__/g,
];
-
const replaceTo = [author, description, homepage, version, buildTime];
replaceFrom.push(
- ...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g"))
+ ...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g")),
);
replaceTo.push(...Object.values(config));
const optionsAddon = {
files: [
- join(buildDir, "**/*.rdf"),
- join(buildDir, "**/*.dtd"),
- join(buildDir, "**/*.html"),
- join(buildDir, "**/*.xhtml"),
- join(buildDir, "**/*.json"),
- join(buildDir, "addon/prefs.js"),
- join(buildDir, "addon/chrome.manifest"),
- join(buildDir, "addon/manifest.json"),
- join(buildDir, "addon/bootstrap.js"),
+ `${buildDir}/addon/**/*.xhtml`,
+ `${buildDir}/addon/**/*.json`,
+ `${buildDir}/addon/prefs.js`,
+ `${buildDir}/addon/manifest.json`,
+ `${buildDir}/addon/bootstrap.js`,
"update.json",
],
from: replaceFrom,
@@ -148,53 +142,121 @@ async function main() {
countMatches: true,
};
- const replaceResult = sync(optionsAddon);
+ const replaceResult = replaceInFileSync(optionsAddon);
+
+ const localeMessage = new Set();
+ const localeMessageMiss = new Set();
+
+ const replaceResultFlt = replaceInFileSync({
+ files: [`${buildDir}/addon/locale/**/*.ftl`],
+ processor: (fltContent) => {
+ const lines = fltContent.split("\n");
+ const prefixedLines = lines.map((line) => {
+ // https://regex101.com/r/lQ9x5p/1
+ const match = line.match(
+ /^(?[a-zA-Z]\S*)([ ]*=[ ]*)(?.*)$/m,
+ );
+ if (match) {
+ localeMessage.add(match.groups.message);
+ return `${config.addonRef}-${line}`;
+ } else {
+ return line;
+ }
+ });
+ return prefixedLines.join("\n");
+ },
+ });
+
+ const replaceResultXhtml = replaceInFileSync({
+ files: [`${buildDir}/addon/**/*.xhtml`],
+ processor: (input) => {
+ const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)];
+ matchs.map((match) => {
+ if (localeMessage.has(match[2])) {
+ input = input.replace(
+ match[0],
+ `${match[1]}="${config.addonRef}-${match[2]}"`,
+ );
+ } else {
+ localeMessageMiss.add(match[2]);
+ }
+ });
+ return input;
+ },
+ });
+
console.log(
"[Build] Run replace in ",
replaceResult
.filter((f) => f.hasChanged)
- .map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`)
+ .map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`),
+ replaceResultFlt.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
+ replaceResultXhtml.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
);
- console.log("[Build] Replace OK");
+ if (localeMessageMiss.size !== 0) {
+ console.warn(
+ `[Build] [Warn] Fluent message [${new Array(
+ ...localeMessageMiss,
+ )}] do not exsit in addon's locale files.`,
+ );
+ }
+}
- // Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl
- const localeDir = join(buildDir, "addon/locale");
- const localeFolders = readdirSync(localeDir, { withFileTypes: true })
- .filter((dirent) => dirent.isDirectory())
- .map((dirent) => dirent.name);
+async function esbuild() {
+ await build({
+ entryPoints: ["src/index.ts"],
+ define: {
+ __env__: `"${env.NODE_ENV}"`,
+ },
+ bundle: true,
+ target: "firefox102",
+ outfile: path.join(
+ buildDir,
+ `addon/chrome/content/scripts/${config.addonRef}.js`,
+ ),
+ // Don't turn minify on
+ // minify: true,
+ }).catch(() => exit(1));
+}
- for (const localeSubFolder of localeFolders) {
- const localeSubDir = join(localeDir, localeSubFolder);
- const localeSubFiles = readdirSync(localeSubDir, {
- withFileTypes: true,
- })
- .filter((dirent) => dirent.isFile())
- .map((dirent) => dirent.name);
+async function main() {
+ console.log(
+ `[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
+ env.NODE_ENV,
+ ]}`,
+ );
- for (const localeSubFile of localeSubFiles) {
- if (localeSubFile.endsWith(".ftl")) {
- renameSync(
- join(localeSubDir, localeSubFile),
- join(localeSubDir, `${config.addonRef}-${localeSubFile}`)
- );
- }
- }
- }
+ clearFolder(buildDir);
+
+ copyFolderRecursiveSync("addon", buildDir);
+
+ copyFileSync("update-template.json", "update.json");
+
+ await esbuild();
+
+ console.log("[Build] Run esbuild OK");
+
+ replaceString();
+
+ console.log("[Build] Replace OK");
+
+ // Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl
+ renameLocaleFiles();
console.log("[Build] Addon prepare OK");
await zip.compressDir(
- join(buildDir, "addon"),
- join(buildDir, `${name}.xpi`),
+ path.join(buildDir, "addon"),
+ path.join(buildDir, `${name}.xpi`),
{
ignoreBase: true,
- }
+ },
);
console.log("[Build] Addon pack OK");
console.log(
- `[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`
+ `[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`,
);
}
diff --git a/scripts/reload.mjs b/scripts/reload.mjs
index f6ea774..5cf84c7 100644
--- a/scripts/reload.mjs
+++ b/scripts/reload.mjs
@@ -1,20 +1,13 @@
-import { exit, argv } from "process";
-import minimist from "minimist";
+import { exit } from "process";
import { execSync } from "child_process";
import details from "../package.json" assert { type: "json" };
-const { addonID, addonName } = details.config;
-const version = details.version;
import cmd from "./zotero-cmd.json" assert { type: "json" };
-const { exec } = cmd;
-// Run node reload.js -h for help
-const args = minimist(argv.slice(2));
+const { addonID, addonName } = details.config;
+const { version } = details;
+const { zoteroBinPath, profilePath } = cmd.exec;
-const zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]];
-const profile = args.profile || args.p;
-const startZotero = `${zoteroPath} --debugger --purgecaches ${
- profile ? `-p ${profile}` : ""
-}`;
+const startZotero = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`;
const script = `
(async () => {
diff --git a/scripts/start.mjs b/scripts/start.mjs
index 5cec962..118b5a0 100644
--- a/scripts/start.mjs
+++ b/scripts/start.mjs
@@ -1,28 +1,74 @@
-import process from "process";
import { execSync } from "child_process";
import { exit } from "process";
-import minimist from "minimist";
+import { existsSync, writeFileSync, readFileSync, mkdirSync } from "fs";
+import path from "path";
+import details from "../package.json" assert { type: "json" };
import cmd from "./zotero-cmd.json" assert { type: "json" };
-const { exec } = cmd;
-
-// Run node start.js -h for help
-const args = minimist(process.argv.slice(2));
-
-if (args.help || args.h) {
- console.log("Start Zotero Args:");
- console.log(
- "--zotero(-z): Zotero exec key in zotero-cmd.json. Default the first one."
- );
- console.log("--profile(-p): Zotero profile name.");
- exit(0);
+
+const { addonID } = details.config;
+const { zoteroBinPath, profilePath, dataDir } = cmd.exec;
+
+if (!existsSync(zoteroBinPath)) {
+ throw new Error("Zotero binary does not exist.");
}
-const zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]];
-const profile = args.profile || args.p;
+if (existsSync(profilePath)) {
+ const addonProxyFilePath = path.join(profilePath, `extensions/${addonID}`);
+ const buildPath = path.resolve("build/addon");
+
+ if (!existsSync(path.join(buildPath, "./manifest.json"))) {
+ throw new Error(
+ `The built file does not exist, maybe you need to build the addon first.`,
+ );
+ }
+
+ function writeAddonProxyFile() {
+ writeFileSync(addonProxyFilePath, buildPath);
+ console.log(
+ `[info] Addon proxy file has been updated.
+ File path: ${addonProxyFilePath}
+ Addon path: ${buildPath} `,
+ );
+ }
+
+ if (existsSync(addonProxyFilePath)) {
+ if (readFileSync(addonProxyFilePath, "utf-8") !== buildPath) {
+ writeAddonProxyFile();
+ }
+ } else {
+ if (
+ existsSync(profilePath) &&
+ !existsSync(path.join(profilePath, "extensions"))
+ ) {
+ mkdirSync(path.join(profilePath, "extensions"));
+ }
+ writeAddonProxyFile();
+ }
+
+ const prefsPath = path.join(profilePath, "prefs.js");
+ if (existsSync(prefsPath)) {
+ const PrefsLines = readFileSync(prefsPath, "utf-8").split("\n");
+ const filteredLines = PrefsLines.map((line) => {
+ if (
+ line.includes("extensions.lastAppBuildId") ||
+ line.includes("extensions.lastAppVersion")
+ ) {
+ return;
+ }
+ if (line.includes("extensions.zotero.dataDir") && dataDir !== "") {
+ return `user_pref("extensions.zotero.dataDir", "${dataDir}");`;
+ }
+ return line;
+ });
+ const updatedPrefs = filteredLines.join("\n");
+ writeFileSync(prefsPath, updatedPrefs, "utf-8");
+ console.log("[info] The /prefs.js has been modified.");
+ }
+} else {
+ throw new Error("The given Zotero profile does not exist.");
+}
-const startZotero = `${zoteroPath} --debugger --purgecaches ${
- profile ? `-p ${profile}` : ""
-}`;
+const startZotero = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`;
execSync(startZotero);
exit(0);
diff --git a/scripts/zotero-cmd-default.json b/scripts/zotero-cmd-default.json
index 3109efa..c02a99f 100644
--- a/scripts/zotero-cmd-default.json
+++ b/scripts/zotero-cmd-default.json
@@ -3,6 +3,18 @@
"killZoteroWindows": "taskkill /f /im zotero.exe",
"killZoteroUnix": "kill -9 $(ps -x | grep zotero)",
"exec": {
- "7": "/path/to/zotero7.exe"
+ "@comment-zoteroBinPath": "Please input the path of the Zotero binary file in `zoteroBinPath`.",
+ "@comment-zoteroBinPath-tip": "The path delimiter should be escaped as `\\` for win32. The path is `*/Zotero.app/Contents/MacOS/zotero` for MacOS.",
+ "zoteroBinPath": "/path/to/zotero.exe",
+
+ "@comment-profilePath": "Please input the path of the profile used for development in `profilePath`.",
+ "@comment-profilePath-tip": "Start the profile manager by `/path/to/zotero.exe -p` to create a profile for development",
+ "@comment-profilePath-see": "https://www.zotero.org/support/kb/profile_directory",
+ "profilePath": "/path/to/profile",
+
+ "@comment-dataDir": "Please input the directory where the database is located in dataDir",
+ "@comment-dataDir-tip": "If this field is kept empty, Zotero will start with the default data.",
+ "@comment-dataDir-see": "https://www.zotero.org/support/zotero_data",
+ "dataDir": ""
}
}
diff --git a/src/hooks.ts b/src/hooks.ts
index 8d29a43..aaf6846 100644
--- a/src/hooks.ts
+++ b/src/hooks.ts
@@ -21,7 +21,7 @@ async function onStartup() {
initLocale();
ztoolkit.ProgressWindow.setIconURI(
"default",
- `chrome://${config.addonRef}/content/icons/favicon.png`
+ `chrome://${config.addonRef}/content/icons/favicon.png`,
);
registerPrefPane();
registerPreviewTab();
@@ -79,7 +79,7 @@ async function onNotify(
event: string,
type: string,
ids: Array,
- extraData: { [key: string]: any }
+ extraData: { [key: string]: any },
) {
// You can add your code to the corresponding notify type
ztoolkit.log("notify", event, type, ids, extraData);
diff --git a/src/modules/container.ts b/src/modules/container.ts
index c3d981f..f168732 100644
--- a/src/modules/container.ts
+++ b/src/modules/container.ts
@@ -27,7 +27,7 @@ async function initContainer(type: PreviewType, position: "before" | "after") {
},
removeIfExists: true,
},
- container
+ container,
);
const iframe = container.querySelector("iframe");
@@ -38,6 +38,6 @@ async function initContainer(type: PreviewType, position: "before" | "after") {
type: "updateToolbar",
previewType: type,
},
- "*"
+ "*",
);
}
diff --git a/src/modules/listeners.ts b/src/modules/listeners.ts
index 8bc924c..0c4f8ad 100644
--- a/src/modules/listeners.ts
+++ b/src/modules/listeners.ts
@@ -19,7 +19,7 @@ function initItemSelectListener() {
function initPreviewResizeListener() {
const splitter = document.querySelector(
- "#zotero-items-splitter"
+ "#zotero-items-splitter",
) as HTMLElement;
const onResize = (e: MouseEvent) => {
if (!addon.data.alive) {
@@ -30,8 +30,8 @@ function initPreviewResizeListener() {
const iframe = document.querySelector(
`#${getContainerId(
getPreviewType(),
- addon.data.state.splitPosition
- )}-iframe`
+ addon.data.state.splitPosition,
+ )}-iframe`,
) as HTMLIFrameElement | null;
if (!iframe) {
return;
@@ -48,7 +48,7 @@ function initPreviewResizeListener() {
type: "updateWidth",
width: width - 40,
},
- "*"
+ "*",
);
addon.hooks.onPreview(true);
};
diff --git a/src/modules/preference.ts b/src/modules/preference.ts
index c7d89e3..bbe1eca 100644
--- a/src/modules/preference.ts
+++ b/src/modules/preference.ts
@@ -8,7 +8,7 @@ export function registerPrefPane() {
label: getString("pref-title"),
image: `chrome://${config.addonRef}/content/icons/favicon.png`,
defaultXUL: true,
- // @ts-ignore
+ // @ts-ignore helpURL is not defined but available
helpURL: homepage,
});
}
diff --git a/src/modules/preview.ts b/src/modules/preview.ts
index 08b2045..bf77860 100644
--- a/src/modules/preview.ts
+++ b/src/modules/preview.ts
@@ -57,13 +57,13 @@ async function preview(type: PreviewType, force = false) {
addon.data.state.previewCounts[type] = 0;
await addon.hooks.onInitContainer(
type,
- type === PreviewType.preview ? "after" : addon.data.state.splitPosition
+ type === PreviewType.preview ? "after" : addon.data.state.splitPosition,
);
force = true;
}
await addon.data.state.initPromise.promise;
const iframe = document.querySelector(
- `#${getContainerId(type, addon.data.state.splitPosition)}-iframe`
+ `#${getContainerId(type, addon.data.state.splitPosition)}-iframe`,
) as HTMLIFrameElement | null;
if (!iframe) {
return;
@@ -79,9 +79,9 @@ async function preview(type: PreviewType, force = false) {
*/
await updatePreviewItem(
type !== addon.data.state.lastType ||
- // @ts-ignore
+ // @ts-ignore cachedData is not a standard property
addon.data.state.item?.id !== iframe.contentWindow?.cachedData?.itemID ||
- force
+ force,
);
if (addon.data.state.item && !addon.data.state.skipRendering) {
@@ -112,13 +112,13 @@ async function preview(type: PreviewType, force = false) {
i.annotationType,
JSON.parse(i.annotationPosition),
i.annotationColor,
- i.annotationPageLabel
- )
+ i.annotationPageLabel,
+ ),
)
: [],
previewType: type,
},
- "*"
+ "*",
);
iframe.hidden = false;
} else {
diff --git a/src/modules/split.ts b/src/modules/split.ts
index d442c84..77e5444 100644
--- a/src/modules/split.ts
+++ b/src/modules/split.ts
@@ -13,14 +13,14 @@ async function registerSplit(type: PreviewType) {
switch (type) {
case PreviewType.info: {
await waitUtilAsync(() =>
- Boolean(document.querySelector("#zotero-editpane-item-box"))
+ Boolean(document.querySelector("#zotero-editpane-item-box")),
);
zitembox = document.querySelector("#zotero-editpane-item-box") as XUL.Box;
break;
}
case PreviewType.attachment: {
await waitUtilAsync(() =>
- Boolean(document.querySelector("#zotero-attachment-box"))
+ Boolean(document.querySelector("#zotero-attachment-box")),
);
zitembox = document.querySelector("#zotero-attachment-box") as XUL.Box;
break;
@@ -62,12 +62,12 @@ async function registerSplit(type: PreviewType) {
addon.data.state.splitHeight = parseFloat(
doc
.querySelector(`#${boxBeforeId}`)
- ?.getAttribute("height") || "0"
+ ?.getAttribute("height") || "0",
);
setSplitCollapsed(
doc
.querySelector(`#${splitterBeforeId}`)
- ?.getAttribute("state") === "collapsed"
+ ?.getAttribute("state") === "collapsed",
);
},
},
@@ -75,7 +75,7 @@ async function registerSplit(type: PreviewType) {
},
],
},
- zitembox
+ zitembox,
);
zitembox.after(
@@ -96,12 +96,12 @@ async function registerSplit(type: PreviewType) {
listener: (e: Event) => {
addon.data.state.splitHeight = parseFloat(
doc.querySelector(`#${boxAfterId}`)?.getAttribute("height") ||
- "0"
+ "0",
);
setSplitCollapsed(
doc
.querySelector(`#${splitterAfterId}`)
- ?.getAttribute("state") === "collapsed"
+ ?.getAttribute("state") === "collapsed",
);
},
},
@@ -112,13 +112,13 @@ async function registerSplit(type: PreviewType) {
id: boxAfterId,
},
],
- })
+ }),
);
}
function setSplitCollapsed(
collapsed: boolean | undefined = undefined,
- quietly = false
+ quietly = false,
) {
if (typeof collapsed === "undefined") {
collapsed = !addon.data.state.splitCollapsed;
@@ -182,10 +182,10 @@ function updateSplit(type: PreviewType) {
const hidden = !getPref("enableSplit");
const position = addon.data.state.splitPosition || "after";
const splitContainer = document.querySelector(
- `#${getContainerId(type, position)}`
+ `#${getContainerId(type, position)}`,
) as XUL.Box;
const splitSplitter = document.querySelector(
- `#${getSplitterId(type, position)}`
+ `#${getSplitterId(type, position)}`,
) as XUL.Splitter;
if (hidden) {
splitContainer.setAttribute("height", "0");
@@ -194,7 +194,7 @@ function updateSplit(type: PreviewType) {
} else {
splitContainer.setAttribute(
"height",
- addon.data.state.splitHeight.toString()
+ addon.data.state.splitHeight.toString(),
);
splitContainer.style.removeProperty("visibility");
splitSplitter.style.removeProperty("visibility");
@@ -204,10 +204,10 @@ function updateSplit(type: PreviewType) {
const hiddenPosition: "before" | "after" =
position === "before" ? "after" : "before";
const hiddenContainer = document.querySelector(
- `#${getContainerId(type, hiddenPosition)}`
+ `#${getContainerId(type, hiddenPosition)}`,
) as XUL.Box;
const hiddenSplitter = document.querySelector(
- `#${getSplitterId(type, hiddenPosition)}`
+ `#${getSplitterId(type, hiddenPosition)}`,
) as XUL.Splitter;
hiddenContainer.setAttribute("height", "0");
hiddenContainer.style.visibility = "collapse";
diff --git a/src/modules/tab.ts b/src/modules/tab.ts
index 619b566..16617d8 100644
--- a/src/modules/tab.ts
+++ b/src/modules/tab.ts
@@ -15,7 +15,7 @@ function registerPreviewTab() {
{
panelId,
tabId,
- }
+ },
);
}
diff --git a/src/utils/locale.ts b/src/utils/locale.ts
index 516dec5..4c6bb81 100644
--- a/src/utils/locale.ts
+++ b/src/utils/locale.ts
@@ -42,8 +42,8 @@ function initLocale() {
function getString(localString: string): string;
function getString(localString: string, branch: string): string;
function getString(
- localString: string,
- options: { branch?: string | undefined; args?: Record }
+ localeString: string,
+ options: { branch?: string | undefined; args?: Record },
): string;
function getString(...inputs: any[]) {
if (inputs.length === 1) {
@@ -60,19 +60,28 @@ function getString(...inputs: any[]) {
}
function _getString(
- localString: string,
- options: { branch?: string | undefined; args?: Record } = {}
+ localeString: string,
+ options: { branch?: string | undefined; args?: Record } = {},
): string {
+ switch (localeString) {
+ case "alt":
+ return Zotero.isMac ? "⌥" : "Alt";
+ case "ctrl":
+ return Zotero.isMac ? "⌘" : "Ctrl";
+ case "shift":
+ return Zotero.isMac ? "⇧" : "Shift";
+ }
+ const localStringWithPrefix = `${config.addonRef}-${localeString}`;
const { branch, args } = options;
const pattern = addon.data.locale?.current.formatMessagesSync([
- { id: localString, args },
+ { id: localStringWithPrefix, args },
])[0];
if (!pattern) {
- return localString;
+ return localStringWithPrefix;
}
if (branch && pattern.attributes) {
- return pattern.attributes[branch] || localString;
+ return pattern.attributes[branch] || localStringWithPrefix;
} else {
- return pattern.value || localString;
+ return pattern.value || localStringWithPrefix;
}
}
diff --git a/src/utils/wait.ts b/src/utils/wait.ts
index 604237f..5d2e765 100644
--- a/src/utils/wait.ts
+++ b/src/utils/wait.ts
@@ -10,7 +10,7 @@ export function waitUntil(
condition: () => boolean,
callback: () => void,
interval = 100,
- timeout = 10000
+ timeout = 10000,
) {
const start = Date.now();
const intervalId = ztoolkit.getGlobal("setInterval")(() => {
@@ -32,7 +32,7 @@ export function waitUntil(
export function waitUtilAsync(
condition: () => boolean,
interval = 100,
- timeout = 10000
+ timeout = 10000,
) {
return new Promise((resolve, reject) => {
const start = Date.now();
diff --git a/tsconfig.json b/tsconfig.json
index 65c5124..a033072 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -7,6 +7,6 @@
"skipLibCheck": true,
"strict": true
},
- "include": ["src", "typing", "node_modules/zotero-types"],
- "exclude": ["builds", "addon"]
+ "include": ["src", "typings", "node_modules/zotero-types"],
+ "exclude": ["build", "addon"]
}
diff --git a/typing/global.d.ts b/typings/global.d.ts
similarity index 100%
rename from typing/global.d.ts
rename to typings/global.d.ts