Merge branch 'windingwind:bootstrap' into bootstrap

This commit is contained in:
volatile-static 2023-01-15 08:46:43 +08:00 committed by GitHub
commit 0f33389dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 168 additions and 115 deletions

183
README.md
View File

@ -60,7 +60,7 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts
### UI Examples ### 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) - registerStyleSheet(the official make-it-red example)
- registerRightClickMenuItem - registerRightClickMenuItem
@ -71,11 +71,33 @@ Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts
- registerCustomCellRenderer - registerCustomCellRenderer
- registerLibraryTabPanel - registerLibraryTabPanel
- registerReaderTabPanel - 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 ## 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; - Git clone the forked repo;
- Enter the repo folder; - Enter the repo folder;
- Modify the settings in `./package.json`, including: - 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. > - 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. > - 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 ### About Hooks
> See also [`src/hooks.ts`](https://github.com/windingwind/zotero-plugin-template/blob/bootstrap/src/hooks.ts) > 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`: 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. - 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`. Using `createElement`, it switches API depending on the current platform automatically. - 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 ### Directory Structure
@ -233,67 +343,6 @@ This section shows the directory structure of a template.
└─ progressWindow.ts # progressWindow tool └─ 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 ## Disclaimer
Use this code under AGPL. No warranties are provided. Keep the laws of your locality in mind! Use this code under AGPL. No warranties are provided. Keep the laws of your locality in mind!

View File

@ -14,6 +14,8 @@
"build-dev": "cross-env NODE_ENV=development node scripts/build.js", "build-dev": "cross-env NODE_ENV=development node scripts/build.js",
"build-prod": "cross-env NODE_ENV=production node scripts/build.js", "build-prod": "cross-env NODE_ENV=production node scripts/build.js",
"build": "npm run build-prod", "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", "start": "node scripts/start.js",
"stop": "node scripts/stop.js", "stop": "node scripts/stop.js",
"restart-dev": "npm run build-dev && npm run stop && npm run start", "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", "homepage": "https://github.com/windingwind/zotero-addon-template#readme",
"dependencies": { "dependencies": {
"zotero-plugin-toolkit": "^1.0.3" "zotero-plugin-toolkit": "^1.0.4"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^18.11.17", "@types/node": "^18.11.17",
@ -42,6 +44,7 @@
"esbuild": "^0.16.10", "esbuild": "^0.16.10",
"release-it": "^15.6.0", "release-it": "^15.6.0",
"replace-in-file": "^6.3.5", "replace-in-file": "^6.3.5",
"minimist": "^1.2.7",
"zotero-types": "^1.0.0" "zotero-types": "^1.0.0"
} }
} }

View File

@ -1,6 +1,25 @@
const { execSync } = require("child_process"); const { execSync } = require("child_process");
const { exit } = require("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); execSync(startZotero);
exit(0); exit(0);

View File

@ -2,5 +2,8 @@
"usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.", "usage": "Copy and rename this file to zotero-cmd.json. Edit the cmd.",
"killZoteroWindows": "taskkill /f /im zotero.exe", "killZoteroWindows": "taskkill /f /im zotero.exe",
"killZoteroUnix": "kill -9 $(ps -x | grep zotero)", "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"
}
} }

View File

@ -15,7 +15,7 @@ if (!basicTool.getGlobal("Zotero").AddonTemplate) {
_globalThis.ztoolkit = addon.data.ztoolkit; _globalThis.ztoolkit = addon.data.ztoolkit;
ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`;
ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production"; ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production";
ztoolkit.UI.elementOptions.enableElementJSONLog = ztoolkit.UI.basicOptions.ui.enableElementJSONLog =
addon.data.env === "development"; addon.data.env === "development";
Zotero.AddonTemplate = addon; Zotero.AddonTemplate = addon;
// Trigger addon hook for initialization // Trigger addon hook for initialization

View File

@ -184,14 +184,13 @@ export class KeyExampleFactory {
export class UIExampleFactory { export class UIExampleFactory {
@example @example
static registerStyleSheet() { static registerStyleSheet() {
const styles = ztoolkit.UI.creatElementsFromJSON(document, { const styles = ztoolkit.UI.createElement(document, "link", {
tag: "link", properties: {
directAttributes: {
type: "text/css", type: "text/css",
rel: "stylesheet", rel: "stylesheet",
href: `chrome://${config.addonRef}/content/zoteroPane.css`, href: `chrome://${config.addonRef}/content/zoteroPane.css`,
}, },
}) as HTMLLinkElement; });
document.documentElement.appendChild(styles); document.documentElement.appendChild(styles);
document document
.getElementById("zotero-item-pane-content") .getElementById("zotero-item-pane-content")
@ -218,7 +217,7 @@ export class UIExampleFactory {
{ {
tag: "menu", tag: "menu",
label: getString("menupopup.label"), label: getString("menupopup.label"),
subElementOptions: [ children: [
{ {
tag: "menuitem", tag: "menuitem",
label: getString("menuitem.submenulabel"), label: getString("menuitem.submenulabel"),
@ -313,28 +312,24 @@ export class UIExampleFactory {
const tabId = ztoolkit.LibraryTabPanel.register( const tabId = ztoolkit.LibraryTabPanel.register(
getString("tabpanel.lib.tab.label"), getString("tabpanel.lib.tab.label"),
(panel: XUL.Element, win: Window) => { (panel: XUL.Element, win: Window) => {
const elem = ztoolkit.UI.creatElementsFromJSON(win.document, { const elem = ztoolkit.UI.createElement(win.document, "vbox", {
tag: "vbox", children: [
namespace: "xul",
subElementOptions: [
{ {
tag: "h2", tag: "h2",
namespace: "html", properties: {
directAttributes: {
innerText: "Hello World!", innerText: "Hello World!",
}, },
}, },
{ {
tag: "div", tag: "div",
namespace: "html", properties: {
directAttributes: {
innerText: "This is a library tab.", innerText: "This is a library tab.",
}, },
}, },
{ {
tag: "button", tag: "button",
namespace: "html", namespace: "html",
directAttributes: { properties: {
innerText: "Unregister", innerText: "Unregister",
}, },
listeners: [ listeners: [
@ -373,46 +368,40 @@ export class UIExampleFactory {
return; return;
} }
ztoolkit.log(reader); ztoolkit.log(reader);
const elem = ztoolkit.UI.creatElementsFromJSON(win.document, { const elem = ztoolkit.UI.createElement(win.document, "vbox", {
tag: "vbox",
id: `${config.addonRef}-${reader._instanceID}-extra-reader-tab-div`, id: `${config.addonRef}-${reader._instanceID}-extra-reader-tab-div`,
namespace: "xul",
// This is important! Don't create content for multiple times // This is important! Don't create content for multiple times
// ignoreIfExists: true, // ignoreIfExists: true,
removeIfExists: true, removeIfExists: true,
subElementOptions: [ children: [
{ {
tag: "h2", tag: "h2",
namespace: "html", properties: {
directAttributes: {
innerText: "Hello World!", innerText: "Hello World!",
}, },
}, },
{ {
tag: "div", tag: "div",
namespace: "html", properties: {
directAttributes: {
innerText: "This is a reader tab.", innerText: "This is a reader tab.",
}, },
}, },
{ {
tag: "div", tag: "div",
namespace: "html", properties: {
directAttributes: {
innerText: `Reader: ${reader._title.slice(0, 20)}`, innerText: `Reader: ${reader._title.slice(0, 20)}`,
}, },
}, },
{ {
tag: "div", tag: "div",
namespace: "html", properties: {
directAttributes: {
innerText: `itemID: ${reader.itemID}.`, innerText: `itemID: ${reader.itemID}.`,
}, },
}, },
{ {
tag: "button", tag: "button",
namespace: "html", namespace: "html",
directAttributes: { properties: {
innerText: "Unregister", innerText: "Unregister",
}, },
listeners: [ listeners: [

View File

@ -48,25 +48,15 @@ async function updatePrefsUI() {
const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer(); const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer();
const tableHelper = new ztoolkit.VirtualizedTabel(addon.data.prefs?.window!) const tableHelper = new ztoolkit.VirtualizedTabel(addon.data.prefs?.window!)
.setContainerId(`${config.addonRef}-table-container`) .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({ .setProp({
id: `${config.addonRef}-prefs-table`, 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, showHeader: true,
multiSelect: true, multiSelect: true,
staticColumns: true, staticColumns: true,