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