diff --git a/README.md b/README.md index bcc7d0c..07cbca0 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts ### UI Examples -![image](https://user-images.githubusercontent.com/33902321/209274492-7aa94912-af38-4154-af46-dc8f59640de3.png) +![image](https://user-images.githubusercontent.com/33902321/211739774-cc5c2df8-5fd9-42f0-9cdf-0f2e5946d427.png) - registerStyleSheet(the official make-it-red example) - registerRightClickMenuItem @@ -71,11 +71,33 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts - registerCustomCellRenderer - registerLibraryTabPanel - registerReaderTabPanel -- unregisterUIExamples + +### Preference Pane Examples + +![image](https://user-images.githubusercontent.com/33902321/211737987-cd7c5c87-9177-4159-b975-dc67690d0490.png) + +- Preferences bindings +- UI Events +- Tabel +- Locale + +See [`src/modules/preferenceScript.ts`](./src/modules/preferenceScript.ts) ## Quick Start Guide -- Fork this repo; +### Install Pre-built `xpi` + +See how the examples work by directly downloading the `xpi` file from GitHub release and install it to your Zotero. + +This is also how your plugin will be released and used by others. + +> The release do not promise any real functions. It is probably not up-to-date. +> +> The `xpi` package is a zip file. However, please don't modify it directly. Modify the source code and build it. + +### Build from Source + +- Fork this repo/Click `Use this template`; - Git clone the forked repo; - Enter the repo folder; - Modify the settings in `./package.json`, including: @@ -104,6 +126,66 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts > - This environment variable is stored in `Zotero.AddonTemplate.data.env`. The outputs to console is disabled in prod mode. > - You can decide what users cannot see/use based on this variable. +### Release + +To build and release, use + +```shell +# A release-it command: version increase, npm run build, git push, and GitHub release +# You need to set the environment variable GITHUB_TOKEN https://github.com/settings/tokens +# release-it: https://github.com/release-it/release-it +npm run release +``` + +### Setup Development Environment + +1. Install a beta version of Zotero: https://www.zotero.org/support/beta_builds (Zotero 7 beta: https://www.zotero.org/support/dev/zotero_7_for_developers) + +2. Install Firefox 60(for Zotero 6)/Firefox 102(for Zotero 7) + +3. Copy zotero command line config file. Modify the commands that starts your installation of the beta Zotero. + +> (Optional) Do this only once: Start the beta Zotero with `/path/to/zotero -p`. Create a new profile and use it as your development profile. +> Use `/path/to/zotero -p {profile_name}` to specify which profile to run with. + +```sh +cp ./scripts/zotero-cmd-default.json ./scripts/zotero-cmd.json +vim ./scripts/zotero-cmd.json +``` + +4. Setup plugin development environment following this [link](https://www.zotero.org/support/dev/client_coding/plugin_development#setting_up_a_plugin_development_environment). + +5. Build plugin and restart Zotero with `npm run restart`. + +6. Launch Firefox 60(Zotero 6)/Firefox 102(Zotero 7) + +7. In Firefox, go to devtools, go to settings, click "enable remote debugging" and the one next to it that's also about debugging + +> Press `shift+F8` in FF 60, or enter `about:debugging#/setup` in FF 102. + +8. In Zotero, go to setting, advanced, config editor, look up "debugging" and click on "allow remote debugging". + +9. Connect to Zotero in Firefox. + +> In FF 60, click the hamburger menu in the top right -> web developer -> Connect..., then enter `localhost:6100`. + +> In FF 102, enter `localhost:6100` in the bottom input of remote-debugging page and click `add`. + +10. Click `connect` in the leftside-bar of Firefox remote-debugging page. + +11. Click "Inspect Main Process" + +### Debug in Zotero + +You can also: + +- Test code snipastes in Tools->Developer->Run Javascript; +- Debug output with `Zotero.debug()`. Find the outputs in Help->Debug Output Logging->View Output; +- Debug UI. Zotero is built on the Firefox XUL framework. Debug XUL UI with software like [XUL Explorer](https://udn.realityripple.com/docs/Archive/Mozilla/XUL_Explorer). + > XUL Documentation: http://www.devdoc.net/web/developer.mozilla.org/en-US/docs/XUL.html + +## Details + ### About Hooks > See also [`src/hooks.ts`](https://github.com/windingwind/zotero-plugin-template/blob/bootstrap/src/hooks.ts) @@ -165,10 +247,38 @@ Remember to call `unregister()` on plugin unload. The plugin template provides new APIs for bootstrap plugins. We have two reasons to use these APIs, instead of the `createElement/createElementNS`: -- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `createElement`, the plugin template will maintain these elements. Just `unregister` on exit. -- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while Zotero 6 doesn't support `createXULElement`. Using `createElement`, it switches API depending on the current platform automatically. +- In bootstrap mode, plugins have to clean up all UI elements on exit (disable or uninstall), which is very annoying. Using the `createElement`, the plugin template will maintain these elements. Just `unregisterAll` at the exit. +- Zotero 7 requires createElement()/createElementNS() → createXULElement() for remaining XUL elements, while Zotero 6 doesn't support `createXULElement`. The React.createElement-like API `createElement` detects namespace(xul/html/svg) and creates elements automatically, with the return element in the corresponding TS element type. -There are more advanced APIs for creating elements in batch: `creatElementsFromJSON`. Input an element tree in JSON and return a fragment/element. These elements are also maintained by this plugin template. +```ts +createElement(document, "div"); // returns HTMLDivElement +createElement(document, "hbox"); // returns XUL.Box +createElement(document, "button", { namespace: "xul" }); // manually set namespace. returns XUL.Button +``` + +### About Build + +Use Esbuild to build `.ts` source code to `.js`. + +Use `replace-in-file` to replace keywords and configurations defined in `package.json` in non-build files (`.xul/xhtml`, `.dtd`, and `.properties`). + +Steps in `scripts/build.js`: + +1. Clean `./builds` +2. Copy `./addon` to `./builds` +3. Esbuild to `./builds/addon/chrome/content/scripts` +4. Replace `__buildVersion__` and `__buildTime__` in `./builds/addon` +5. Zip the `./builds/addon` to `./builds/*.xpi` + +### About Zotero API + +Zotero docs are outdated and incomplete. Clone https://github.com/zotero/zotero and search the keyword globally. + +> ⭐The [zotero-types](https://github.com/windingwind/zotero-types) provides most frequently used Zotero APIs. It's included in this template by default. Your IDE would provide hint for most of the APIs. + +A trick for finding the API you want: + +Search the UI label in `.xul`(`.xhtml`)/`.dtd`/`.properties` files, find the corresponding key in locale file. Then search this keys in `.js`/`.jsx` files. ### Directory Structure @@ -233,67 +343,6 @@ This section shows the directory structure of a template. └─ progressWindow.ts # progressWindow tool ``` -### Build - -```shell -# A release-it command: version increase, npm run build, git push, and GitHub release -# You need to set the environment variable GITHUB_TOKEN https://github.com/settings/tokens -# release-it: https://github.com/release-it/release-it -npm run release -``` - -Alternatively, build it directly using build.js: `npm run build` - -### Build Steps - -1. Clean `./builds` -2. Copy `./addon` to `./builds` -3. Esbuild to `./builds/addon/chrome/content/scripts` -4. Replace `__buildVersion__` and `__buildTime__` in `./builds/addon` -5. Zip the `./builds/addon` to `./builds/*.xpi` - -### Debug - -1. Copy zotero command line config file. Modify the commands. - -```sh -cp ./scripts/zotero-cmd-default.json ./scripts/zotero-cmd.json -vim ./scripts/zotero-cmd.json -``` - -2. Setup plugin development environment following this [link](https://www.zotero.org/support/dev/client_coding/plugin_development#setting_up_a_plugin_development_environment). - -3. Build plugin and restart Zotero with this npm command. - -4. Launch Firefox 60 -5. In Firefox, go to devtools, go to settings, click "enable remote debugging" and the one next to it that's also about debugging(or press `shift+F8`). -6. In Zotero, go to setting, advanced, config editor, look up "debugging" and click on "allow remote debugging" -7. In Firefox, click the hamburger menu in the top right -> web developer -> Connect... -8. Enter localhost:6100 -9. Connect -10. Click "Inspect Main Process" - -```sh -npm run restart -``` - -You can also debug code in these ways: - -- Test code segments in Tools->Developer->Run Javascript; -- Debug output with `Zotero.debug()`. Find the outputs in Help->Debug Output Logging->View Output; -- UI debug. Zotero is built on the Firefox XUL framework. Debug XUL UI with software like [XUL Explorer](https://udn.realityripple.com/docs/Archive/Mozilla/XUL_Explorer). - > XUL Documents: - > https://www.xul.fr/tutorial/ - > http://www.xulplanet.com/ - -### Development - -**Search for a Zotero API** -Zotero docs are outdated or incomplete. Searching the source code of Zotero is unavoidable. -Clone https://github.com/zotero/zotero and search the keyword globally. You can search the UI text in `.xul`/`.dtd` files, and then search the keys of the text value in `.js`/`.xul` files. - -> ⭐The [zotero-types](https://github.com/windingwind/zotero-types) provides most frequently used Zotero APIs. It's included in this template by default. - ## Disclaimer Use this code under AGPL. No warranties are provided. Keep the laws of your locality in mind! diff --git a/package.json b/package.json index 37ff77e..c27c21a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "build-dev": "cross-env NODE_ENV=development node scripts/build.js", "build-prod": "cross-env NODE_ENV=production node scripts/build.js", "build": "npm run build-prod", + "start-z6": "node scripts/start.js --z 6", + "start-z7": "node scripts/start.js --z 7", "start": "node scripts/start.js", "stop": "node scripts/stop.js", "restart-dev": "npm run build-dev && npm run stop && npm run start", @@ -33,7 +35,7 @@ }, "homepage": "https://github.com/windingwind/zotero-addon-template#readme", "dependencies": { - "zotero-plugin-toolkit": "^1.0.3" + "zotero-plugin-toolkit": "^1.0.4" }, "devDependencies": { "@types/node": "^18.11.17", @@ -42,6 +44,7 @@ "esbuild": "^0.16.10", "release-it": "^15.6.0", "replace-in-file": "^6.3.5", + "minimist": "^1.2.7", "zotero-types": "^1.0.0" } } diff --git a/scripts/start.js b/scripts/start.js index 08a6b78..2485412 100644 --- a/scripts/start.js +++ b/scripts/start.js @@ -1,6 +1,25 @@ const { execSync } = require("child_process"); const { exit } = require("process"); -const { startZotero } = require("./zotero-cmd.json"); +const { exec } = require("./zotero-cmd.json"); + +// Run node start.js -h for help +const args = require("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 zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]]; +const profile = args.profile || args.p; + +const startZotero = `${zoteroPath} --debugger --purgecaches ${ + profile ? `-p ${profile}` : "" +}`; execSync(startZotero); exit(0); diff --git a/scripts/zotero-cmd-default.json b/scripts/zotero-cmd-default.json index 73a9c13..9bb8011 100644 --- a/scripts/zotero-cmd-default.json +++ b/scripts/zotero-cmd-default.json @@ -2,5 +2,8 @@ "usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.", "killZoteroWindows": "taskkill /f /im zotero.exe", "killZoteroUnix": "kill -9 $(ps -x | grep zotero)", - "startZotero": "/path/to/zotero.exe --debugger --purgecaches" + "exec": { + "6": "/path/to/zotero6.exe", + "7": "/path/to/zotero7.exe" + } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0b008dd..ba1d1de 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,7 +15,7 @@ if (!basicTool.getGlobal("Zotero").AddonTemplate) { _globalThis.ztoolkit = addon.data.ztoolkit; ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production"; - ztoolkit.UI.elementOptions.enableElementJSONLog = + ztoolkit.UI.basicOptions.ui.enableElementJSONLog = addon.data.env === "development"; Zotero.AddonTemplate = addon; // Trigger addon hook for initialization diff --git a/src/modules/examples.ts b/src/modules/examples.ts index 804a043..2ea7959 100644 --- a/src/modules/examples.ts +++ b/src/modules/examples.ts @@ -184,14 +184,13 @@ export class KeyExampleFactory { export class UIExampleFactory { @example static registerStyleSheet() { - const styles = ztoolkit.UI.creatElementsFromJSON(document, { - tag: "link", - directAttributes: { + const styles = ztoolkit.UI.createElement(document, "link", { + properties: { type: "text/css", rel: "stylesheet", href: `chrome://${config.addonRef}/content/zoteroPane.css`, }, - }) as HTMLLinkElement; + }); document.documentElement.appendChild(styles); document .getElementById("zotero-item-pane-content") @@ -218,7 +217,7 @@ export class UIExampleFactory { { tag: "menu", label: getString("menupopup.label"), - subElementOptions: [ + children: [ { tag: "menuitem", label: getString("menuitem.submenulabel"), @@ -313,28 +312,24 @@ export class UIExampleFactory { const tabId = ztoolkit.LibraryTabPanel.register( getString("tabpanel.lib.tab.label"), (panel: XUL.Element, win: Window) => { - const elem = ztoolkit.UI.creatElementsFromJSON(win.document, { - tag: "vbox", - namespace: "xul", - subElementOptions: [ + const elem = ztoolkit.UI.createElement(win.document, "vbox", { + children: [ { tag: "h2", - namespace: "html", - directAttributes: { + properties: { innerText: "Hello World!", }, }, { tag: "div", - namespace: "html", - directAttributes: { + properties: { innerText: "This is a library tab.", }, }, { tag: "button", namespace: "html", - directAttributes: { + properties: { innerText: "Unregister", }, listeners: [ @@ -373,46 +368,40 @@ export class UIExampleFactory { return; } ztoolkit.log(reader); - const elem = ztoolkit.UI.creatElementsFromJSON(win.document, { - tag: "vbox", + const elem = ztoolkit.UI.createElement(win.document, "vbox", { id: `${config.addonRef}-${reader._instanceID}-extra-reader-tab-div`, - namespace: "xul", // This is important! Don't create content for multiple times // ignoreIfExists: true, removeIfExists: true, - subElementOptions: [ + children: [ { tag: "h2", - namespace: "html", - directAttributes: { + properties: { innerText: "Hello World!", }, }, { tag: "div", - namespace: "html", - directAttributes: { + properties: { innerText: "This is a reader tab.", }, }, { tag: "div", - namespace: "html", - directAttributes: { + properties: { innerText: `Reader: ${reader._title.slice(0, 20)}`, }, }, { tag: "div", - namespace: "html", - directAttributes: { + properties: { innerText: `itemID: ${reader.itemID}.`, }, }, { tag: "button", namespace: "html", - directAttributes: { + properties: { innerText: "Unregister", }, listeners: [ diff --git a/src/modules/preferenceScript.ts b/src/modules/preferenceScript.ts index ce0318f..348ffb1 100644 --- a/src/modules/preferenceScript.ts +++ b/src/modules/preferenceScript.ts @@ -48,25 +48,15 @@ async function updatePrefsUI() { const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer(); const tableHelper = new ztoolkit.VirtualizedTabel(addon.data.prefs?.window!) .setContainerId(`${config.addonRef}-table-container`) - // Add locale for table columns - // Object.fromEntries is only available on firefox 62+, - .setLocale( - Array.from( - new Map( - addon.data.prefs?.columns.map((column) => [ - column.label, - getString(column.label), - ]) - ) - ).reduce((obj, [key, value]) => { - obj[key] = value; - return obj; - }, {} as { [k: string]: string }) - ) - // id and getRowCount are required, others are optional. .setProp({ id: `${config.addonRef}-prefs-table`, - columns: addon.data.prefs?.columns, + // Do not use setLocale, as it modifies the Zotero.Intl.strings + // Set locales directly to columns + columns: addon.data.prefs?.columns.map((column) => + Object.assign(column, { + label: getString(column.label) || column.label, + }) + ), showHeader: true, multiSelect: true, staticColumns: true,