Compare commits

..

239 Commits
0.1.1 ... main

Author SHA1 Message Date
syt ed129f2a45
Merge pull request #12 from syt2/11-更新时报错-typeerror-tiscollection-is-not-a-function
fix isCollection func
2024-07-25 16:53:31 +08:00
ytshen 2a9125aa99 fix isCollection func 2024-07-25 16:52:52 +08:00
shenyutao 5d4dde170a Release 1.0.7 2024-04-17 18:33:55 +08:00
shenyutao 2d10915c7d fix error 2024-04-17 18:33:35 +08:00
ytshen 18fac41190 update release v -> V 2024-04-16 23:42:07 +08:00
ytshen f653f8a11a Release 1.0.6 2024-04-16 23:36:43 +08:00
ytshen 28cf7c86de update 2024-04-16 23:36:29 +08:00
ytshen 6edfda0388 update icon 2024-04-16 23:35:47 +08:00
ytshen 6056c4208f update pane registe 2024-04-16 23:30:59 +08:00
ytshen 5cc74c4986 Merge branch 'template'
# Conflicts:
#	README.md
#	addon/locale/en-US/preferences.ftl
#	package.json
#	src/hooks.ts
#	src/modules/examples.ts
#	update.json
2024-04-16 23:11:34 +08:00
ytshen a26f5fd0a9 update 2024-04-16 22:56:23 +08:00
windingwind eec0d47a96
Merge pull request #107 from windingwind/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-7.3.1
build(deps-dev): bump @typescript-eslint/eslint-plugin from 6.21.0 to 7.3.1
2024-03-25 22:14:44 +08:00
dependabot[bot] df847e59be
build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 6.21.0 to 7.3.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.3.1/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-18 23:25:48 +00:00
shenyutao 50956fefff Release 1.0.5 2024-03-13 17:15:07 +08:00
shenyutao f93d22d29b update 2024-03-13 17:14:35 +08:00
shenyutao a9d13a20c6 同时注册于row中 2024-03-13 17:13:42 +08:00
shenyutao 3c9695fa71 Release 1.0.4 2024-03-11 11:48:00 +08:00
shenyutao 735da8a43a update 2024-03-11 11:47:42 +08:00
shenyutao b56669ae8f update dependence 2024-03-11 11:46:32 +08:00
windingwind 66c0460513
Merge pull request #102 from windingwind/dependabot/npm_and_yarn/esbuild-0.20.1
build(deps-dev): bump esbuild from 0.19.12 to 0.20.1
2024-03-09 22:01:30 +08:00
windingwind f52eb9f100
Merge pull request #104 from windingwind/dependabot/npm_and_yarn/typescript-eslint/parser-7.1.1
build(deps-dev): bump @typescript-eslint/parser from 6.21.0 to 7.1.1
2024-03-09 22:01:18 +08:00
dependabot[bot] 0075dc8a43
build(deps-dev): bump @typescript-eslint/parser from 6.21.0 to 7.1.1
Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 6.21.0 to 7.1.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v7.1.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-04 23:31:03 +00:00
dependabot[bot] cd14a26c11
build(deps-dev): bump esbuild from 0.19.12 to 0.20.1
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.19.12 to 0.20.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.19.12...v0.20.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-19 23:37:21 +00:00
shenyutao fdd4d20a95 move from itemBox row to note 2024-01-31 13:47:29 +08:00
Chénglóng Mǎ 2cc3ac7622
Update start.mjs (#97) 2024-01-12 10:06:01 +08:00
Northword 0663e5b0b1
fix: log miss ftl messages in build (#95) 2024-01-06 00:14:07 +08:00
Northword 7738410680
fix: comment for release (#93)
* fix: comment for release

* chore: update deps

* Delete release-it after:release hook
2024-01-06 00:13:29 +08:00
windingwind d1f6367825 Release 1.1.1 2023-12-28 10:43:50 +08:00
windingwind 2839b3ac53 chore: doc lint 2023-12-28 10:43:23 +08:00
Northword 7f6b7c54b2
fix: only prompt version when run release, and submit comment to closed issues, fix prerelease version number breaks auto update (#92)
* chore: only prompt version when run release

* chore: submit comment in issue when new version release
closes: #91

* fix: set default prerelease id to beta to resolve auto update wrong
2023-12-26 21:43:26 +08:00
syt fc8a4ac9d9
Remove original xpi file if exists in `extensions` folder (#90)
* remove original xpi file in extensions folder to avoid load add-on from xpi

* Update README.md
2023-12-18 15:49:00 +08:00
windingwind f5f2fd3297 add: build replace-in-file includes html and css 2023-12-18 00:03:28 +08:00
KikkiZ be0b82f23d
Fixes issue #86 (#87) 2023-12-17 19:19:26 +08:00
Dae 6f3a6d112e
update killZoteroUnix command to ignore grep pipe (#89) 2023-12-17 10:48:07 +08:00
windingwind 3d9f8a8a0d update: registerShortcuts 2023-12-14 22:33:10 +08:00
windingwind 032aa74f6e fix: devtool startup 2023-12-13 22:21:26 +08:00
windingwind 57028c7ba8 chore: change default dev tool start delay 2023-12-13 21:23:19 +08:00
windingwind 4eb662ea2f rename: cmd template 2023-12-12 23:01:33 +08:00
windingwind 56c427b26a add: DS_Store to gitignore 2023-12-12 22:30:41 +08:00
windingwind 133dde03c7 Release 1.1.0 2023-12-12 15:58:01 +08:00
dependabot[bot] 3c8f1b3db3
build(deps-dev): bump release-it from 16.3.0 to 17.0.1 (#85)
Bumps [release-it](https://github.com/release-it/release-it) from 16.3.0 to 17.0.1.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/main/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/16.3.0...17.0.1)

---
updated-dependencies:
- dependency-name: release-it
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-12 15:55:24 +08:00
Northword fb9109d87d
refactor: rebuild development scripts and release workflow (#84)
* refactor: add dev server and fix `update-beta.json` bug

* chore: add renovate config

* feat: release plugin to GitHub release via action

* chore: update vsc settings

* Cancel release by action

* docs: update readme

* Update README.md

* Update README.md

fix format

* Update README.md

* Update README.md

fix hint

* tweak

* Merge #81

* Fix indent

* Merge ade49628ff

* Revent delete env

* tweak

* feat: release via GitHub action

* docs: update readme

* Update README.md

* style: fix prettier

* add: stdout log to file

* Update README.md

fix typo

* Update release.yml

fix typo

* write Zotero log to `logs/zotero.log`

* do not provide update-beta.json default

* fix prettier in readme

* tweak

* fix wrong link in readme

* Move 3rd package config to package.json

---------

Co-authored-by: windingwind <33902321+windingwind@users.noreply.github.com>
2023-12-12 15:55:00 +08:00
Denis Maier e2885ec563
Correct typo (#83) 2023-12-08 20:21:56 +08:00
windingwind ade49628ff update: bundle devtool 2023-12-08 11:15:09 +08:00
windingwind 1208ad54a9 add: devtool support 2023-12-07 21:48:02 +08:00
windingwind a56e31fa0d chore: onStartup call 2023-11-30 20:39:44 +08:00
KikkiZ 12e7cb9641
feat: add chinese readme document (#75)
* feat: add chinese readme document

* docs: fixed some translation errors

* move: readme-zhCN

---------

Co-authored-by: windingwind <33902321+windingwind@users.noreply.github.com>
2023-11-13 02:37:28 -06:00
windingwind 77e43bf378 Release 1.0.2 2023-11-11 01:02:33 +08:00
windingwind 57319f498f fix: #74 2023-11-11 01:01:52 +08:00
windingwind df7bde4757 Release 1.0.1 2023-11-10 23:23:22 +08:00
windingwind 1364288293
Merge pull request #72 from plin349/fix-grammar
fix grammar - 'conflict' instead of 'confliction'
2023-11-06 09:21:12 +08:00
plin 1572a57f22
fix grammar - 'conflict' instead of 'confliction' 2023-11-05 10:45:47 -05:00
windingwind ddb2bc6898 fix: build update url bug 2023-09-15 16:01:13 +08:00
windingwind 23ffc5fdf8 add: support beta (prerelease) auto update 2023-09-15 15:33:02 +08:00
windingwind 25990fd552 update: readme 2023-09-11 20:12:08 +08:00
windingwind 10e57afadb fix: #70 2023-09-11 19:52:18 +08:00
shenyutao 7f436b320f Release 1.0.3 2023-08-28 12:00:24 +08:00
shenyutao 296ce35e16 update store path 2023-08-28 12:00:04 +08:00
windingwind 6d4e9628a5 update: deps 2023-08-26 23:01:58 +08:00
windingwind 0d67969ad8
Merge pull request #69 from windingwind:dependabot/npm_and_yarn/esbuild-0.19.2
build(deps-dev): bump esbuild from 0.18.20 to 0.19.2
2023-08-26 23:00:50 +08:00
shenyutao dad1df7b52 Release 1.0.2 2023-08-25 16:14:49 +08:00
shenyutao 6ebb421185 update icon 2023-08-25 16:14:24 +08:00
shenyutao 861d6685a0 update readme 2023-08-25 15:49:42 +08:00
shenyutao 67498280a0 Release 1.0.1 2023-08-25 15:45:13 +08:00
shenyutao 309447ca68 ipv code 2023-08-25 15:44:44 +08:00
shenyutao e9b9e7ef46 ipv code & language 2023-08-25 15:43:17 +08:00
shenyutao f1d5275e6f Release 1.0.0 2023-08-18 18:31:42 +08:00
shenyutao 24c229f736 update code 2023-08-18 18:31:10 +08:00
shenyutao 7fd2de3554 update 2023-08-18 18:29:46 +08:00
shenyutao eed95d402e update 2023-08-17 16:11:25 +08:00
shenyutao 033bafa5bc init proj 2023-08-17 14:38:20 +08:00
shenyutao 03fadd7b25 init 2023-08-16 11:59:55 +08:00
dependabot[bot] 526a18cc37
build(deps-dev): bump esbuild from 0.18.20 to 0.19.2
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.18.20 to 0.19.2.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.20...v0.19.2)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-14 23:12:41 +00:00
windingwind e1088f36da
Merge pull request #68 from windingwind/dependabot/npm_and_yarn/eslint-config-prettier-9.0.0
build(deps-dev): bump eslint-config-prettier from 8.10.0 to 9.0.0
2023-08-10 10:07:01 +08:00
dependabot[bot] 51afe19524
build(deps-dev): bump eslint-config-prettier from 8.10.0 to 9.0.0
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.10.0 to 9.0.0.
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.10.0...v9.0.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-07 23:47:54 +00:00
windingwind 005a0160aa update: ztoolkit 2.2.2 2023-08-03 22:00:37 +08:00
windingwind 9f5fc12560 Release 1.0.0 2023-08-02 23:09:37 +08:00
windingwind ba57111764 update: ztoolkit lib 2023-08-02 23:08:41 +08:00
windingwind b69feab780 fix: shutdown bug 2023-07-31 23:02:32 +08:00
windingwind ebddeeb3ef fix: main window hook params 2023-07-29 11:20:24 +08:00
windingwind 1e12c84ba6 update: main window hooks 2023-07-29 11:12:20 +08:00
windingwind b48953f405 update: ztoolkit init 2023-07-29 11:06:20 +08:00
windingwind a5517736fa add: prerelease do not change update.json 2023-07-28 20:20:37 +08:00
windingwind 3c780febc9
Merge pull request #67 from windingwind/fixWindowLoad
add: window load hook
2023-07-26 21:33:19 +08:00
windingwind 4f5f6f8e12 add: window load hook 2023-07-24 19:28:58 +08:00
windingwind 96245a0923 fix: start script bug 2023-07-21 09:11:32 +08:00
windingwind 9f91c764c0 fix: locale.ts typo 2023-07-20 00:47:03 +08:00
windingwind 11134e365e fix: npm run start bug when `extensions` not exist 2023-07-19 23:26:43 +08:00
windingwind efd56c94e1
Merge pull request #66 from northword:change-build-outfile-to-addonRef
chore: change build outfile to addonRef.js
2023-07-19 22:31:21 +08:00
windingwind 9da839374c
Merge pull request #65 from northword/check-manifest-before-start
fix: check manifest before start
2023-07-17 23:20:11 +08:00
Northword 9fd98494f7 chore: change build outfile to addonRef.js 2023-07-17 17:17:27 +08:00
Northword 9495059eb9 fix: remove `\n` in template literals 2023-07-17 10:28:41 +08:00
Northword 8a36eb2ab6 fix: typo 2023-07-17 10:26:43 +08:00
Northword 3738657b9a fix: check manifest before start 2023-07-14 15:51:50 +08:00
windingwind 229411ae61 fix: reload.mjs startZotero command 2023-07-13 13:04:38 +08:00
windingwind 18476d9624
Merge pull request #64 from northword:fix-lint
Fix lint and prettier
2023-07-13 13:02:56 +08:00
Northword 79e87f050b
Merge branch 'main' into fix-lint 2023-07-13 12:38:08 +08:00
windingwind 10d9879252
Merge pull request #63 from northword:fix-replace-in-file-glob-pattern
Fix glob pattern in ReplaceInFileConfig.files
2023-07-13 12:17:25 +08:00
windingwind 0ff56d5303 fix: start.mjs profile path 2023-07-13 12:03:14 +08:00
Northword 0971bd0f44 chore: run lint before release init 2023-07-13 11:04:13 +08:00
Northword 458a6a5f84 style: prettier and lint 2023-07-13 11:00:09 +08:00
Northword 3adad053b1 Fix glob pattern in ReplaceInFileConfig.files 2023-07-13 10:17:24 +08:00
windingwind a2d2d3a36c add: readme start with github codespace 2023-07-13 00:29:53 +08:00
windingwind acb6cd79d1 update: deps @typescript-eslint/parser 2023-07-13 00:06:39 +08:00
windingwind d45b72111d update: scripts import 2023-07-12 23:59:32 +08:00
windingwind c2808745c8
Merge pull request #58 from windingwind/dependabot/npm_and_yarn/prettier-3.0.0
build(deps-dev): bump prettier from 2.8.8 to 3.0.0
2023-07-12 23:49:59 +08:00
windingwind 724a2eb2e6
Merge pull request #57 from windingwind/dependabot/npm_and_yarn/typescript-eslint/eslint-plugin-6.0.0
build(deps-dev): bump @typescript-eslint/eslint-plugin from 5.62.0 to 6.0.0
2023-07-12 23:49:42 +08:00
dependabot[bot] 7c8ab3b5dc
build(deps-dev): bump prettier from 2.8.8 to 3.0.0
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.8 to 3.0.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.8...3.0.0)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-12 15:49:29 +00:00
windingwind 0ceff9728a
Merge pull request #59 from windingwind/dependabot/npm_and_yarn/release-it-16.1.0
build(deps-dev): bump release-it from 15.11.0 to 16.1.0
2023-07-12 23:48:41 +08:00
dependabot[bot] 0713c8212b
build(deps-dev): bump release-it from 15.11.0 to 16.1.0
Bumps [release-it](https://github.com/release-it/release-it) from 15.11.0 to 16.1.0.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/main/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/15.11.0...16.1.0)

---
updated-dependencies:
- dependency-name: release-it
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-12 15:47:42 +00:00
windingwind 932d491d9e
Merge pull request #62 from northword:refactor-start-script
Auto update `<profile>/extensions/${addonID}` before start Zotero
2023-07-12 23:46:15 +08:00
windingwind 66c0b12f3f update: macos zoteroBinPath tip 2023-07-12 23:42:10 +08:00
windingwind 01d80eb730 remove: dev dependency minimist 2023-07-12 23:41:49 +08:00
windingwind cb9efcfaae
Merge pull request #60 from northword:add-prefix-for-ftl
Add addonRef prefix for ftl
2023-07-12 23:20:29 +08:00
windingwind bd8c6c78d6
Merge pull request #61 from northword:rename-vscode-settings-json
Rename vscode settings.json
2023-07-12 23:12:30 +08:00
Northword 110b806eca Throw error when profile do not exist 2023-07-12 22:36:42 +08:00
Northword f7b7483a8a Update docs for setup development environment 2023-07-12 22:22:53 +08:00
Northword 6184e7a9c1 Update addon proxy file before start 2023-07-12 21:42:52 +08:00
Northword e28af8724d Add addon path file before start Zotero 2023-07-12 21:25:10 +08:00
Northword b08fd4642b Fix typo in match index 2023-07-12 20:18:25 +08:00
Northword 3e6b744307 Refactor the functions in main into separate functions 2023-07-12 20:04:52 +08:00
Northword 4d7f61ee0b Replace xhtml locale keys that only exist in ftl 2023-07-12 19:53:24 +08:00
Northword 164c8a01be Rename vscode settings.json 2023-07-12 18:23:35 +08:00
Northword ab845ddb35 Update reg exp for data-l10n-id 2023-07-11 19:08:13 +08:00
Northword 9e2553661d Add edbuild target to firefox 102 2023-07-11 18:41:58 +08:00
Northword a30f0c7c64 Add prefix for locale files when build 2023-07-11 18:36:04 +08:00
dependabot[bot] 138c6316ac
build(deps-dev): bump @typescript-eslint/eslint-plugin
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.62.0 to 6.0.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v6.0.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 23:41:11 +00:00
windingwind eeb1ace01d
Merge pull request #53 from northword/remove-zo6-code
Z7-related: remove zo6 code, add homepage url, and add vscode-fluent
2023-07-02 14:49:34 +08:00
windingwind 55368dea50
Merge pull request #54 from northword/update-readme
Update readme
2023-07-02 14:48:39 +08:00
windingwind d274334398
Merge pull request #55 from northword/rename-folder
Rename typings and build folder
2023-07-02 14:47:54 +08:00
Northword 5cf4e57228 Fix typo in readme 2023-07-01 22:04:10 +08:00
Northword ac15ec5035 Update readme due to typo 2023-07-01 18:23:44 +08:00
Northword 6576618634 Rename build folder 2023-07-01 18:04:36 +08:00
Northword 9c824d6faa Update readme 2023-07-01 17:54:55 +08:00
Northword fd3fafe8fc Rename `typings` folder 2023-07-01 17:50:50 +08:00
Northword 88c2f9e686 Add fluent syntax highlighting plugin for vs code 2023-07-01 17:02:24 +08:00
Northword 6f6d3e463a Add homepage_url into manifest 2023-07-01 16:58:06 +08:00
Northword 0b74eab10d Remove Zotero 6 compatibility code 2023-07-01 16:57:33 +08:00
windingwind d2d8922f66
Update README.md 2023-06-27 13:58:35 +08:00
windingwind 4d983132c2 update: start-watch with build 2023-06-20 11:53:16 +08:00
windingwind f22a071042 fix: eslint errors 2023-06-20 11:52:27 +08:00
windingwind 1121dc5d6c Release 0.2.0 2023-06-19 16:11:03 +08:00
windingwind 29a318e828 update: update.json 2023-06-19 16:10:33 +08:00
windingwind 74c3a03952 fix: updaterdf 2023-06-19 16:04:30 +08:00
windingwind a2697d1dfb update: remove support to Zotero 6 2023-06-19 16:03:48 +08:00
windingwind 389e7590e1
Merge pull request #51 from windingwind/zotero6-bootstrap
merge before changing main branch
2023-06-19 15:59:15 +08:00
windingwind 9850e5e676
Merge pull request #50 from northword:main
chore: update eslint ignore patterns
2023-06-19 10:39:51 +08:00
windingwind 35afb45200 add: getString with args 2023-06-17 23:36:42 +08:00
windingwind 4d3bc65b94 add: getString with args 2023-06-17 23:30:01 +08:00
Northword dcef4b554f chore: update eslint ignore patterns 2023-06-17 22:05:49 +08:00
windingwind a254ff8bc3 update: use Localization 2023-06-16 15:14:08 +08:00
windingwind 4d0b0a457d fix: initLocale in en-US 2023-06-16 11:02:17 +08:00
windingwind a4741696e6 fix: getString from ftl
remove: getStringAsync
2023-06-16 10:00:19 +08:00
windingwind 25081bf450 update: dtd2ftl 2023-06-16 00:19:09 +08:00
windingwind 8540d1b4cf fix: hot reload clear cache bug 2023-06-13 16:20:30 +08:00
windingwind 2dac131b12
Merge pull request #46 from windingwind/dependabot/npm_and_yarn/esbuild-0.18.1
build(deps-dev): bump esbuild from 0.17.19 to 0.18.1
2023-06-13 14:29:58 +08:00
windingwind 8680035d8d
Merge pull request #47 from northword/patch-2
fix: watch files in subfolder
2023-06-13 14:29:45 +08:00
Northword b84489ff6b
fix: watch files in subfolder 2023-06-13 13:26:16 +08:00
windingwind b2b490b191 update: hot reload without waiting 2023-06-13 12:51:22 +08:00
dependabot[bot] f7592ebf38
build(deps-dev): bump esbuild from 0.17.19 to 0.18.1
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.17.19 to 0.18.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.17.19...v0.18.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 00:05:10 +00:00
windingwind 27c68b1167 fix: typo 2023-06-12 23:42:31 +08:00
windingwind 3ba1f9d40e add: auto hot reload support 2023-06-12 23:24:42 +08:00
windingwind c4fc2c44e7 fix: close dialog when shutdown 2023-06-12 22:56:44 +08:00
windingwind 671315e9ad update: lint 2023-06-11 15:34:52 +08:00
windingwind 2c3f6d1d7f fix: scripts/stop wait for process exit 2023-06-11 15:34:45 +08:00
windingwind d9680008d5 fix: #43 downgrade replace-in-file 2023-06-11 10:50:50 +08:00
windingwind f7d095bade
Merge pull request #42 from windingwind/dependabot/npm_and_yarn/replace-in-file-7.0.1
build(deps-dev): bump replace-in-file from 6.3.5 to 7.0.1
2023-06-01 22:06:57 +08:00
dependabot[bot] 55d15eb796
build(deps-dev): bump replace-in-file from 6.3.5 to 7.0.1
Bumps [replace-in-file](https://github.com/adamreisnz/replace-in-file) from 6.3.5 to 7.0.1.
- [Release notes](https://github.com/adamreisnz/replace-in-file/releases)
- [Changelog](https://github.com/adamreisnz/replace-in-file/blob/main/CHANGELOG.md)
- [Commits](https://github.com/adamreisnz/replace-in-file/commits)

---
updated-dependencies:
- dependency-name: replace-in-file
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-30 00:01:44 +00:00
windingwind 4a92c2795e
Merge pull request #40 from windingwind/dependabot/npm_and_yarn/types/node-20.1.1
build(deps-dev): bump @types/node from 18.16.6 to 20.1.1
2023-05-15 00:13:21 +08:00
dependabot[bot] b592a35bd5
build(deps-dev): bump @types/node from 18.16.6 to 20.1.1
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.16.6 to 20.1.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 00:01:27 +00:00
windingwind 49fe7c5ec4 update: plugins 2023-05-07 11:35:45 +08:00
windingwind 1c43ef671a add: useful utils 2023-05-07 11:27:15 +08:00
windingwind 98a1c7770b Merge branch 'bootstrap' of https://github.com/windingwind/zotero-plugin-template into bootstrap 2023-05-07 01:43:21 +08:00
windingwind 3bd5b1137d fix: update bug 2023-05-07 01:43:15 +08:00
windingwind c48e085b8d fix: build async bug
fix: eslint esm bug
2023-05-07 01:43:04 +08:00
windingwind dccda31611
Merge pull request #32 from windingwind/dependabot/npm_and_yarn/concurrently-8.0.1
build(deps-dev): bump concurrently from 7.6.0 to 8.0.1
2023-05-04 20:15:55 +08:00
windingwind 17fa2b83e7
Merge pull request #34 from windingwind/dependabot/npm_and_yarn/typescript-5.0.4
build(deps-dev): bump typescript from 4.9.5 to 5.0.4
2023-05-04 20:15:40 +08:00
dependabot[bot] 15f1a04259
build(deps-dev): bump typescript from 4.9.5 to 5.0.4
Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.9.5 to 5.0.4.
- [Release notes](https://github.com/Microsoft/TypeScript/releases)
- [Commits](https://github.com/Microsoft/TypeScript/compare/v4.9.5...v5.0.4)

---
updated-dependencies:
- dependency-name: typescript
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-04 12:13:44 +00:00
windingwind 7f733416ee
Merge pull request #38 from northword:eslint
Add ESlint and Prettier
2023-05-04 20:12:24 +08:00
windingwind fe224ba50d change: disable formatOnType 2023-05-04 20:11:30 +08:00
windingwind ab0157dbe1 change: eol auto to lf 2023-05-04 20:10:43 +08:00
windingwind fb98d85c5c fix: update.rdf version bug 2023-05-04 19:50:41 +08:00
Northword 6bfe8c28d2 chore: add update deps script 2023-04-30 14:17:23 +08:00
Northword 5852ecfc9f style: lint and prettier 2023-04-30 14:11:44 +08:00
Northword 67f2dcb7fc chore: add edlint and prettier 2023-04-30 14:11:12 +08:00
dependabot[bot] 11668e9c15
build(deps-dev): bump concurrently from 7.6.0 to 8.0.1
Bumps [concurrently](https://github.com/open-cli-tools/concurrently) from 7.6.0 to 8.0.1.
- [Release notes](https://github.com/open-cli-tools/concurrently/releases)
- [Commits](https://github.com/open-cli-tools/concurrently/compare/v7.6.0...v8.0.1)

---
updated-dependencies:
- dependency-name: concurrently
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-04 00:07:20 +00:00
windingwind 0e8f5b34d0 Release 0.1.6 2023-03-13 00:33:00 +08:00
windingwind 414bf81fd1 fix: prompt example type error
update: zotero-types
2023-03-13 00:32:29 +08:00
windingwind bb4c31b54c
Merge pull request #26 from MuiseDestiny:bootstrap
add: Prompt exapmles
2023-03-13 00:26:14 +08:00
windingwind 5abb83c518
Merge pull request #27 from zotero-cn:bootstrap
fix: update type error in template code
2023-03-13 00:23:49 +08:00
windingwind 0afe7772c5 fix: #28 2023-03-13 00:22:53 +08:00
Linxzh f625f326eb
Update src/hooks.ts
Co-authored-by: volatile-static <1043064987@qq.com>
2023-03-05 13:51:08 +08:00
Linxzh b0010ee8c7 fix: Update type in hooks, match the variable types between examples.ts and hooks.ts 2023-03-03 15:08:13 +08:00
Linxzh 833a7ee8ec fix: update type error in template code 2023-03-03 09:46:53 +08:00
MuiseDestiny 6467a12089 add: registerAnonymousCommandExample logic or 2023-02-18 22:03:55 +08:00
MuiseDestiny 2ff86f92db Fix: spelling error 2023-02-18 21:49:09 +08:00
MuiseDestiny 8be6b0ba49 Fix: spelling error 2023-02-18 21:48:13 +08:00
MuiseDestiny 792263726d add: Prompt exapmles 2023-02-18 21:42:39 +08:00
windingwind c4777e35a9
Update README.md 2023-02-10 01:34:17 +08:00
windingwind 9ed41e33aa Release 0.1.5 2023-02-09 18:30:43 +08:00
windingwind 080736cd0f add: itemBox examples 2023-02-09 18:30:05 +08:00
windingwind 47fdd995d5 Release 0.1.4 2023-02-07 22:56:34 +08:00
windingwind 296f03944a update: toolkit 2.0.0 2023-02-07 22:56:00 +08:00
xiangyu e2112995ad update: readme 2023-02-05 23:10:43 +08:00
xiangyu 7ee1619b8f remove: bootstrap.js license 2023-02-05 16:38:17 +08:00
xiangyu 576c08b5cf fix: default prefs loading bug 2023-01-28 14:40:46 +08:00
xiangyu 7b9e139cb8 change: config.addonInstance 2023-01-28 11:08:14 +08:00
windingwind c1ad9c5ee7
Update README.md 2023-01-27 23:17:55 +08:00
xiangyu aa51abaeb7 add: prompt example 2023-01-27 23:08:53 +08:00
xiangyu 91d295dfe6 fix: build replaceinfile prefs.js 2023-01-24 10:38:04 +08:00
xiangyu ceb4d602cc change: zotero-types 1.0.3 2023-01-24 10:25:20 +08:00
windingwind fac772b7b8
Merge pull request #25 from windingwind/dependabot/npm_and_yarn/esbuild-0.17.4
build(deps-dev): bump esbuild from 0.16.17 to 0.17.4
2023-01-24 10:17:30 +08:00
dependabot[bot] 4f0e936f09
build(deps-dev): bump esbuild from 0.16.17 to 0.17.4
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.16.17 to 0.17.4.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.16.17...v0.17.4)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-24 02:17:02 +00:00
xiangyu 7a39b8223c add: tsconfig skipLibCheck 2023-01-24 10:15:01 +08:00
Emiliano Heyns bbbd2509a8 show typescript compilation errors 2023-01-24 00:00:31 +01:00
windingwind 29b57965e2
Merge pull request #23 from retorquere:patch-1
Report build errors as error status
2023-01-23 23:29:00 +08:00
Emiliano Heyns 20c2a1f7de
Report build errors as error status 2023-01-23 13:36:32 +01:00
windingwind d66a00e50f
Merge pull request #20 from volatile-static/bootstrap
 add simple snippets
2023-01-19 10:12:32 +08:00
xiangyu 623c3830f5 fix: default prefs
see https://www.zotero.org/support/dev/zotero_7_for_developers#default_preferences
2023-01-18 20:12:21 +08:00
volatile-static 18e77880da
Update toolkit.code-snippets 2023-01-16 07:57:15 +08:00
xiangyu e6aa4f1b6b Release 0.1.3 2023-01-15 20:38:17 +08:00
xiangyu ffbaf8cd3d add: dialog examples 2023-01-15 20:37:56 +08:00
volatile-static a0b33a2211
Update toolkit.code-snippets 2023-01-15 08:47:26 +08:00
volatile-static 0f33389dae
Merge branch 'windingwind:bootstrap' into bootstrap 2023-01-15 08:46:43 +08:00
volatile static 1ad2832a6b add simple snippets 2023-01-15 08:43:44 +08:00
xiangyu c80f3ed457 fix: prefs locale example 2023-01-12 16:55:18 +08:00
xiangyu 8025496b44 update: toolkit 1.0.4 2023-01-12 16:41:03 +08:00
xiangyu 4524527867 add: start.js for different Zotero & profile 2023-01-11 16:49:08 +08:00
windingwind 72acc1b2fe
Update README.md 2023-01-11 15:50:51 +08:00
windingwind ce636bdfd3
Merge pull request #17 from windingwind/dependabot/npm_and_yarn/release-it-15.6.0
build(deps-dev): bump release-it from 14.14.3 to 15.6.0
2023-01-11 14:26:41 +08:00
dependabot[bot] baede0c9c1
build(deps-dev): bump release-it from 14.14.3 to 15.6.0
Bumps [release-it](https://github.com/release-it/release-it) from 14.14.3 to 15.6.0.
- [Release notes](https://github.com/release-it/release-it/releases)
- [Changelog](https://github.com/release-it/release-it/blob/master/CHANGELOG.md)
- [Commits](https://github.com/release-it/release-it/compare/14.14.3...15.6.0)

---
updated-dependencies:
- dependency-name: release-it
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-11 04:59:04 +00:00
xiangyu 33ccc61267 Release 0.1.2 2023-01-11 12:57:23 +08:00
xiangyu 4a06cd0535 add: custom toolkit modules example 2023-01-11 12:56:53 +08:00
xiangyu d48a628693 update: scripts 2023-01-11 12:56:29 +08:00
xiangyu 05ad059c04 fix: preferencepane loading bug 2023-01-11 12:14:07 +08:00
xiangyu 33c38d3595 fix: prefs table locale bug on Zotero 6 2023-01-10 20:20:48 +08:00
xiangyu 3ea8520d60 fix: install.rdf 2023-01-10 20:12:46 +08:00
xiangyu 16c3952f80 update: zotero-types 1.0.0 2023-01-10 19:21:42 +08:00
xiangyu a8dcf5e310 add: prefs table local example 2023-01-10 19:16:47 +08:00
68 changed files with 2308 additions and 1538 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

21
.github/renovate.json vendored Normal file
View File

@ -0,0 +1,21 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":semanticPrefixChore",
":prHourlyLimitNone",
":prConcurrentLimitNone",
":enableVulnerabilityAlerts",
":dependencyDashboard",
"schedule:weekends"
],
"packageRules": [
{
"matchPackageNames": ["zotero-plugin-toolkit", "zotero-types"],
"automerge": true
}
],
"git-submodules": {
"enabled": true
}
}

43
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Release
on:
push:
tags:
- V**
permissions:
contents: write
issues: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GitHub_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install deps
run: npm install
- name: Release to GitHub
run: |
npm run release -- --no-increment --no-git --github.release --ci --VV
sleep 1s
- name: Notify release
uses: apexskier/github-release-commenter@v1
continue-on-error: true
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment-template: |
:rocket: _This ticket has been resolved in {release_tag}. See {release_link} for release notes._

8
.gitignore vendored
View File

@ -1,4 +1,8 @@
**/builds
build
logs
node_modules
package-lock.json
zotero-cmd.json
pnpm-lock.yaml
yarn.lock
zotero-cmd.json
.DS_Store

7
.prettierignore Normal file
View File

@ -0,0 +1,7 @@
build
logs
node_modules
package-lock.json
yarn.lock
pnpm-lock.yaml
# zotero-cmd.json

View File

@ -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}."
}
}

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"macabeus.vscode-fluent"
]
}

35
.vscode/launch.json vendored
View File

@ -1,15 +1,22 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Restart",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "restart"],
}
]
}
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Start",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start"]
},
{
"type": "node",
"request": "launch",
"name": "Build",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "build"]
}
]
}

7
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"editor.formatOnType": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

45
.vscode/toolkit.code-snippets vendored Normal file
View File

@ -0,0 +1,45 @@
{
"appendElement - full": {
"scope": "javascript,typescript",
"prefix": "appendElement",
"body": [
"appendElement({",
"\ttag: '${1:div}',",
"\tid: '${2:id}',",
"\tnamespace: '${3:html}',",
"\tclassList: ['${4:class}'],",
"\tstyles: {${5:style}: '$6'},",
"\tproperties: {},",
"\tattributes: {},",
"\t[{ '${7:onload}', (e: Event) => $8, ${9:false} }],",
"\tcheckExistanceParent: ${10:HTMLElement},",
"\tignoreIfExists: ${11:true},",
"\tskipIfExists: ${12:true},",
"\tremoveIfExists: ${13:true},",
"\tcustomCheck: (doc: Document, options: ElementOptions) => ${14:true},",
"\tchildren: [$15]",
"}, ${16:container});",
],
},
"appendElement - minimum": {
"scope": "javascript,typescript",
"prefix": "appendElement",
"body": "appendElement({ tag: '$1' }, $2);",
},
"register Notifier": {
"scope": "javascript,typescript",
"prefix": "registerObserver",
"body": [
"registerObserver({",
"\t notify: (",
"\t\tevent: _ZoteroTypes.Notifier.Event,",
"\t\ttype: _ZoteroTypes.Notifier.Type,",
"\t\tids: string[],",
"\t\textraData: _ZoteroTypes.anyObj",
"\t) => {",
"\t\t$0",
"\t}",
"});",
],
},
}

310
README.md
View File

@ -1,305 +1,17 @@
# Zotero Plugin Template
# Zotero TL;DR
![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-better-notes?label=zotero-better-notes)
![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-pdf-preview?label=zotero-pdf-preview)
![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-pdf-translate?label=zotero-pdf-translate)
![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-tag?label=zotero-tag)
![GitHub Repo stars](https://img.shields.io/github/stars/iShareStuff/ZoteroTheme?label=zotero-theme)
![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/zotero-reference?label=zotero-reference)
![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/ZoteroStyle?label=zotero-style)
![GitHub Repo stars](https://img.shields.io/github/stars/volatile-static/Chartero?label=Chartero)
![GitHub Repo stars](https://img.shields.io/github/stars/l0o0/tara?label=tara)
[![zotero target version](https://img.shields.io/badge/Zotero-7-red?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org)
[![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template)
This is a plugin template for [Zotero](https://www.zotero.org/). Plugins using this template are shown above.
This is an add-on for [Zotero 7+](https://www.zotero.org) that automatically fetch TL;DR (Too Long; Didn't Read) from [Sematic scholar](https://www.semanticscholar.org) for items.
📖[Plugin Development Documentation](https://zotero.yuque.com/books/share/8d230829-6004-4934-b4c6-685a7001bfa0/vec88d)(Chinese, provides English translation)
## Install
🛠️[Zotero Plugin Toolkit](https://github.com/windingwind/zotero-plugin-toolkit) | [API Documentation](https://github.com/windingwind/zotero-plugin-toolkit/blob/master/docs/zotero-plugin-toolkit.md)
1. Download the [latest release](https://github.com/syt2/zotero-tldr/releases/latest/download/zotero-tldr.xpi) xpi file.
2. Install in Zotero (Tools -> Add-ons)
[Zotero Type Definitions](https://github.com/windingwind/zotero-types)
## Usage
📜[Zotero Source Code](https://github.com/zotero/zotero)
📌[Zotero Plugin Template](https://github.com/windingwind/zotero-plugin-template)(This repo)
> 👍You are currently in `bootstrap` extension mode. To use `overlay` mode, plsase switch to `overlay` branch in git.
> 👁 Watch this repo so that you can be notified whenever there are fixes & updates.
## Features
- Event-driven, functional programming, under extensive skeleton;
- Simple and user-friendly, works out-of-the-box.
- Abundant examples in `src/modules/examples.ts`, covering most of the commonly used APIs in plugins(using [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit));
- TypeScript support:
- Full type definition support for the whole Zotero project, which is written in JavaScript(using [zotero-types](https://github.com/windingwind/zotero-types));
- Global variables and environment setup;
- Plugin build/test/release workflow:
- Automatically generate/update plugin id/version, update configrations, and set environment variables(`development/production`);
- Automatically build and reload code in Zotero;
- Automatically release to GitHub(using [release-it](https://github.com/release-it/release-it));
- ⭐[New!]Compatibilities for Zotero 6 & Zotero 7.(using [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit))
## Examples
This repo provides examples for [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit) APIs.
Search `@example` in `src/examples.ts`. The examples are called in `src/hooks.ts`.
### Basic Examples
- registerNotifier
- registerPrefs, unregisterPrefs
### Shortcut Keys Examples
- registerShortcuts
- exampleShortcutLargerCallback
- exampleShortcutSmallerCallback
- exampleShortcutConflictionCallback
### UI Examples
![image](https://user-images.githubusercontent.com/33902321/209274492-7aa94912-af38-4154-af46-dc8f59640de3.png)
- registerStyleSheet(the official make-it-red example)
- registerRightClickMenuItem
- registerRightClickMenuPopup
- registerWindowMenuWithSeprator
- registerExtraColumn
- registerExtraColumnWithCustomCell
- registerCustomCellRenderer
- registerLibraryTabPanel
- registerReaderTabPanel
- unregisterUIExamples
## Quick Start Guide
- Fork this repo;
- Git clone the forked repo;
- Enter the repo folder;
- Modify the settings in `./package.json`, including:
```
version,
author,
description,
homepage,
config {
releasepage,
updaterdf,
addonName,
addonID,
addonRef
}
```
> Be careful to set the addonID and addonRef to avoid confliction.
- Run `npm install` to set up the plugin and install dependencies. If you don't have NodeJS installed, please download it [here](https://nodejs.org/en/);
- Run `npm run build` to build the plugin in production mode. Run `npm run build-dev` to build the plugin in development mode. The xpi for installation and the built code is under `builds` folder.
> What the difference between dev & prod?
>
> - 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.
### About Hooks
> See also [`src/hooks.ts`](https://github.com/windingwind/zotero-plugin-template/blob/bootstrap/src/hooks.ts)
1. When install/enable/startup triggered from Zotero, `bootstrap.js` > `startup` is called
- Wait for Zotero ready
- Load `index.js` (the main entrance of plugin code, built from `index.ts`)
- Register resources if Zotero 7+
2. In the main entrance `index.js`, the plugin object is injected under `Zotero` and `hooks.ts` > `onStartup` is called.
- Initialize anything you want, including notify listeners, preference panes, and UI elements.
3. When uninstall/disabled triggered from Zotero, `bootstrap.js` > `shutdown` is called.
- `events.ts` > `onShutdown` is called. Remove UI elements, preference panes, or anything created by the plugin.
- Remove scripts and release resources.
### About Global Variables
> See also [`src/index.ts`](https://github.com/windingwind/zotero-plugin-template/blob/bootstrap/src/index.ts)
The bootstrapped plugin runs in a sandbox, which does not have default global variables like `Zotero` or `window`, which we used to have in the overlay plugins' window environment.
This template registers the following variables to the global scope:
```ts
Zotero, ZoteroPane, Zotero_Tabs, window, document, rootURI, ztoolkit, addon;
```
### About Preference
Zotero 6 doesn't support preference pane injection in bootstrap mode, thus I write a register for Zotero 6 or lower.
You only need to maintain one `preferences.xhtml` which runs natively on Zotero 7 and let the plugin template handle it when it is running on Zotero 6.
<table style="margin-left: auto; margin-right: auto;">
<tr>
<td>
<img width="350px" src="https://user-images.githubusercontent.com/33902321/208080125-2a776a98-f427-4c81-8924-7877bf803e3d.png"/>
<div>Zotero 7</div>
</td>
<td>
<img width="300px" src="https://user-images.githubusercontent.com/33902321/208080491-b7006c08-2679-4f85-9a28-dba8e622d745.png"/>
<div>Zotero 6</div>
</td>
</tr>
</table>
https://github.com/windingwind/zotero-plugin-template/blob/08d72a4e2b3bacff574f537bbd06cb33e6b22480/src/modules/examples.ts#L73-L85
> `<preferences>` element is deprecated. Please use the full pref-key in the elements' `preference` attribute. Like:
```xml
<checkbox label="&zotero.__addonRef__.pref.enable.label;" preference="extensions.zotero.__addonRef__.enable" />
```
The elements with `preference` attributes will bind to Zotero preferences.
Remember to call `unregister()` on plugin unload.
### Create Elements API
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.
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.
### Directory Structure
This section shows the directory structure of a template.
- All `.js/.ts` code files are in `./src`;
- Addon config files: `./addon/chrome.manifest`, `./addon/install.rdf`, and `./addon/manifest.json`;
- UI files: `./addon/chrome/content/*.xhtml`.
- Locale files: `./addon/chrome/locale/[*.dtd, *.properties]`;
- Resource files: `./addon/chrome/skin/default/__addonRef__/*.dtd`;
- Preferences file: `./addon/chrome/defaults/preferences/defaults.js`;
> Don't break the lines in the `defaults.js`
```shell
│ .gitignore
│ .release-it.json # release-it conf
| tsconfig.json # https://code.visualstudio.com/docs/languages/jsconfig#
│ build.js # esbuild
│ LICENSE
│ package.json # npm conf
│ README.md # readme
│ update.rdf # addon update
├─.github # github conf
├─addon # addon dir
│ │ chrome.manifest # for Zotero 6
│ │ manifest.json # for Zotero 7
│ │ install.rdf # addon install conf, for Zotero 6
│ │ bootstrap.js # addon load/unload script, like a main.c
│ │
│ └─chrome
│ ├─content # UI
│ │ │ preferences.xhtml
│ │ │
│ │ ├─icons
│ │ │ favicon.png
│ │ │ favicon@0.5x.png
│ │ │
│ │ └─scripts
│ └─locale # locale
│ ├─en-US
│ │ overlay.dtd
│ │ addon.properties
│ │
│ ├─zh-CN
│ | overlay.dtd
│ └─ addon.properties
├─builds # build dir
│ └─.xpi
└─src # source code
│ index.ts # main entry
│ addon.ts # base class
│ hooks.ts # lifecycle hooks
|
└─modules # sub modules
│ examples.ts # examples factory
│ locale.ts # locale .properties
│ preferenceScript.ts # script runs in preferences.xhtml
└─ 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!
If you want to change the license, please contact me at wyzlshx@foxmail.com
Part of the code of this repo refers to other open-source projects within the allowed scope.
- zotero-better-bibtex(`d.ts`)
There are no configuration steps required.
The add-on will automatically fetch the TL;DR information for all items.
You can view the TLDR information in details on the right side.

113
addon/bootstrap.js vendored
View File

@ -1,114 +1,73 @@
/* Copyright 2012 Will Shanks.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
if (typeof Zotero == "undefined") {
var Zotero;
}
/**
* Most of this code is from Zotero team's official Make It Red example[1]
* or the Zotero 7 documentation[2].
* [1] https://github.com/zotero/make-it-red
* [2] https://www.zotero.org/support/dev/zotero_7_for_developers
*/
var chromeHandle;
// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js
// to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main
// Zotero window to open and get the Zotero object from there.
//
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
// automatically made available.
async function waitForZotero() {
if (typeof Zotero != "undefined") {
await Zotero.initializationPromise;
}
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var windows = Services.wm.getEnumerator("navigator:browser");
var found = false;
while (windows.hasMoreElements()) {
let win = windows.getNext();
if (win.Zotero) {
Zotero = win.Zotero;
found = true;
break;
}
}
if (!found) {
await new Promise((resolve) => {
var listener = {
onOpenWindow: function (aWindow) {
// Wait for the window to finish loading
let domWindow = aWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
domWindow.addEventListener(
"load",
function () {
domWindow.removeEventListener("load", arguments.callee, false);
if (domWindow.Zotero) {
Services.wm.removeListener(listener);
Zotero = domWindow.Zotero;
resolve();
}
},
false
);
},
};
Services.wm.addListener(listener);
});
}
await Zotero.initializationPromise;
}
function install(data, reason) {}
async function startup({ id, version, resourceURI, rootURI }, reason) {
await waitForZotero();
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/"],
["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
]);
}
await Zotero.initializationPromise;
// String 'rootURI' introduced in Zotero 7
if (!rootURI) {
rootURI = resourceURI.spec;
}
// Global variables for plugin code
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.
* The `_globalThis` is the global root variable of the plugin sandbox environment
* and all child variables assigned to it is globally accessible.
* See `src/index.ts` for details.
*/
const ctx = {
rootURI,
};
ctx._globalThis = ctx;
Services.scriptloader.loadSubScript(
`${rootURI}/chrome/content/scripts/index.js`,
ctx
`${rootURI}/chrome/content/scripts/__addonRef__.js`,
ctx,
);
Zotero.__addonInstance__.hooks.onStartup();
}
async function onMainWindowLoad({ window }, reason) {
Zotero.__addonInstance__?.hooks.onMainWindowLoad(window);
}
async function onMainWindowUnload({ window }, reason) {
Zotero.__addonInstance__?.hooks.onMainWindowUnload(window);
}
function shutdown({ id, version, resourceURI, rootURI }, reason) {
if (reason === APP_SHUTDOWN) {
return;
}
if (typeof Zotero === "undefined") {
Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports
Components.interfaces.nsISupports,
).wrappedJSObject;
}
Zotero.AddonTemplate.hooks.onShutdown();
Zotero.__addonInstance__?.hooks.onShutdown();
Cc["@mozilla.org/intl/stringbundle;1"]
.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();

View File

@ -1,3 +0,0 @@
content __addonRef__ chrome/content/
locale __addonRef__ en-US chrome/locale/en-US/
locale __addonRef__ zh-CN chrome/locale/zh-CN/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 677 B

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -1,31 +0,0 @@
<vbox
id="zotero-prefpane-__addonRef__"
onload="Zotero.AddonTemplate.hooks.onPrefsEvent('load', {window})"
>
<groupbox>
<label><html:h2>&zotero.__addonRef__.pref.title;</html:h2></label>
<checkbox
id="zotero-prefpane-__addonRef__-enable"
label="&zotero.__addonRef__.pref.enable.label;"
preference="extensions.zotero.__addonRef__.enable"
/>
<hbox>
<html:label for="zotero-prefpane-__addonRef__-input"
>&zotero.__addonRef__.pref.input.label;</html:label
>
<html:input
type="text"
id="zotero-prefpane-__addonRef__-input"
preference="extensions.zotero.__addonRef__.input"
></html:input>
</hbox>
<hbox class="virtualized-table-container" flex="1" height="300px">
<html:div id="__addonRef__-table-container" />
</hbox>
</groupbox>
</vbox>
<vbox>
<label
value="&zotero.__addonRef__.help.version.label; &zotero.__addonRef__.help.releasetime.label;"
></label>
</vbox>

View File

@ -1,3 +0,0 @@
.makeItRed {
background-color: tomato;
}

View File

@ -1,9 +0,0 @@
startup.begin=Addon is loading
startup.finish=Addon is ready
menuitem.label=Addon Template: Menuitem
menupopup.label=Addon Template: Menupopup
menuitem.submenulabel=Addon Template
menuitem.filemenulabel=Addon Template: File Menuitem
prefs.title=Template
tabpanel.lib.tab.label=Lib Tab
tabpanel.reader.tab.label=Reader Tab

View File

@ -1,7 +0,0 @@
<!ENTITY zotero.__addonRef__.pref.title "Addon Template Example">
<!ENTITY zotero.__addonRef__.itemmenu.test.label "addon template">
<!ENTITY zotero.__addonRef__.pref.enable.label "Enable">
<!ENTITY zotero.__addonRef__.pref.input.label "Input">
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ VERSION __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">

View File

@ -1,9 +0,0 @@
startup.begin=插件加载中
startup.finish=插件已就绪
menuitem.label=插件模板: 菜单
menupopup.label=插件模板: 弹出菜单
menuitem.submenulabel=插件模板:子菜单
menuitem.filemenulabel=插件模板: 文件菜单
prefs.title=插件模板
tabpanel.lib.tab.label=库标签
tabpanel.reader.tab.label=阅读器标签

View File

@ -1,7 +0,0 @@
<!ENTITY zotero.__addonRef__.pref.title "插件模板设置示例">
<!ENTITY zotero.__addonRef__.itemmenu.test.label "插件模板">
<!ENTITY zotero.__addonRef__.pref.enable.label "开启">
<!ENTITY zotero.__addonRef__.pref.input.label "输入">
<!ENTITY zotero.__addonRef__.help.version.label "__addonName__ 版本 __buildVersion__">
<!ENTITY zotero.__addonRef__.help.releasetime.label "Build __buildTime__">

View File

@ -1,2 +0,0 @@
pref("extensions.zotero.__addonRef__.enable", true);
pref("extensions.zotero.__addonRef__.input", "This is input");

View File

@ -1,37 +0,0 @@
<?xml version="1.0"?>
<RDF:RDF
xmlns:em="http://www.mozilla.org/2004/em-rdf#"
xmlns:NC="http://home.netscape.com/NC-rdf#"
xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<RDF:Description
RDF:about="urn:mozilla:install-manifest"
em:id="__addonID__"
em:name="__addonName__"
em:version="__buildVersion__"
em:type="2"
em:creator="__author__"
em:description="__description__"
em:homepageURL="__homepage__"
em:iconURL="chrome://__addonRef__/content/icons/favicon.png"
em:optionsURL="chrome://__addonRef__/content/preferences.xul"
em:updateURL="__updaterdf__"
em:multiprocessCompatible="true"
em:bootstrap="true">>
<em:type>2</em:type>
<em:targetApplication RDF:resource="rdf:#$x61SL3"/>
<em:targetApplication>
<Description>
<em:id>zotero@chnm.gmu.edu</em:id>
<em:minVersion>5.0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
<em:targetApplication>
<Description>
<em:id>juris-m@juris-m.github.io</em:id>
<em:minVersion>5.0</em:minVersion>
<em:maxVersion>*</em:maxVersion>
</Description>
</em:targetApplication>
</RDF:Description>
</RDF:RDF>

View File

@ -0,0 +1,8 @@
menuitem-updatetldrlabel = update TLDR
menucollection-updatetldrlabel = update TLDR
itembox-tldrlabel = TLDR
tldr-unrelated = TLDR Unrelated in Semantic scholar
tldr-itemnotfound = Item Not Found in Semantic scholar
popWindow-succeed = Succeed
popWindow-failed = Failed
popWindow-waiting = Waiting

View File

@ -0,0 +1,4 @@
itemPaneSection-header =
.label = TLDR
itemPaneSection-sidenav =
.tooltiptext = TLDR

View File

View File

@ -0,0 +1,8 @@
menuitem-updatetldrlabel = 更新TLDR
menucollection-updatetldrlabel = 批量更新TLDR
itembox-tldrlabel = TLDR
tldr-unrelated = 未关联TLDR
tldr-itemnotfound = 未搜索到此条目
popWindow-succeed = 成功
popWindow-failed = 失败
popWindow-waiting = 等待

View File

@ -0,0 +1,4 @@
itemPaneSection-header =
.label = TLDR
itemPaneSection-sidenav =
.tooltiptext = TLDR

View File

View File

@ -3,6 +3,7 @@
"name": "__addonName__",
"version": "__buildVersion__",
"description": "__description__",
"homepage_url": "__homepage__",
"author": "__author__",
"icons": {
"48": "chrome/content/icons/favicon@0.5x.png",
@ -11,7 +12,7 @@
"applications": {
"zotero": {
"id": "__addonID__",
"update_url": "__updaterdf__",
"update_url": "__updateURL__",
"strict_min_version": "6.999",
"strict_max_version": "7.0.*"
}

1
addon/prefs.js Normal file
View File

@ -0,0 +1 @@
/* eslint-disable no-undef */

420
doc/README-zhCN.md Normal file
View File

@ -0,0 +1,420 @@
# Zotero Plugin Template
[![zotero target version](https://img.shields.io/badge/Zotero-7-green?style=flat-square&logo=zotero&logoColor=CC2936)](https://www.zotero.org)
[![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template)
这是 [Zotero](https://www.zotero.org/) 的插件模板.
[English](../README.md) | [简体中文](./README-zhCN.md)
📖 [插件开发文档](https://zotero.yuque.com/books/share/8d230829-6004-4934-b4c6-685a7001bfa0/vec88d) (中文版,已过时)
[📖 Zotero 7 插件开发文档](https://www.zotero.org/support/dev/zotero_7_for_developers)
🛠️ [Zotero 插件工具包](https://github.com/windingwind/zotero-plugin-toolkit) | [API 文档](https://github.com/windingwind/zotero-plugin-toolkit/blob/master/docs/zotero-plugin-toolkit.md)
[Zotero 类型定义](https://github.com/windingwind/zotero-types)
📜 [Zotero 源代码](https://github.com/zotero/zotero)
📌 [Zotero 插件模板](https://github.com/windingwind/zotero-plugin-template) (即本仓库)
> [!tip]
> 👁 Watch 本仓库,以及时收到修复或更新的通知.
## 使用此模板构建的插件
[![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-better-notes?label=zotero-better-notes&style=flat-square)](https://github.com/windingwind/zotero-better-notes)
[![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-pdf-preview?label=zotero-pdf-preview&style=flat-square)](https://github.com/windingwind/zotero-pdf-preview)
[![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-pdf-translate?label=zotero-pdf-translate&style=flat-square)](https://github.com/windingwind/zotero-pdf-translate)
[![GitHub Repo stars](https://img.shields.io/github/stars/windingwind/zotero-tag?label=zotero-tag&style=flat-square)](https://github.com/windingwind/zotero-tag)
[![GitHub Repo stars](https://img.shields.io/github/stars/iShareStuff/ZoteroTheme?label=zotero-theme&style=flat-square)](https://github.com/iShareStuff/ZoteroTheme)
[![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/zotero-reference?label=zotero-reference&style=flat-square)](https://github.com/MuiseDestiny/zotero-reference)
[![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/zotero-citation?label=zotero-citation&style=flat-square)](https://github.com/MuiseDestiny/zotero-citation)
[![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/ZoteroStyle?label=zotero-style&style=flat-square)](https://github.com/MuiseDestiny/ZoteroStyle)
[![GitHub Repo stars](https://img.shields.io/github/stars/volatile-static/Chartero?label=Chartero&style=flat-square)](https://github.com/volatile-static/Chartero)
[![GitHub Repo stars](https://img.shields.io/github/stars/l0o0/tara?label=tara&style=flat-square)](https://github.com/l0o0/tara)
[![GitHub Repo stars](https://img.shields.io/github/stars/redleafnew/delitemwithatt?label=delitemwithatt&style=flat-square)](https://github.com/redleafnew/delitemwithatt)
[![GitHub Repo stars](https://img.shields.io/github/stars/redleafnew/zotero-updateifsE?label=zotero-updateifsE&style=flat-square)](https://github.com/redleafnew/zotero-updateifsE)
[![GitHub Repo stars](https://img.shields.io/github/stars/northword/zotero-format-metadata?label=zotero-format-metadata&style=flat-square)](https://github.com/northword/zotero-format-metadata)
[![GitHub Repo stars](https://img.shields.io/github/stars/inciteful-xyz/inciteful-zotero-plugin?label=inciteful-zotero-plugin&style=flat-square)](https://github.com/inciteful-xyz/inciteful-zotero-plugin)
[![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/zotero-gpt?label=zotero-gpt&style=flat-square)](https://github.com/MuiseDestiny/zotero-gpt)
[![GitHub Repo stars](https://img.shields.io/github/stars/zoushucai/zotero-journalabbr?label=zotero-journalabbr&style=flat-square)](https://github.com/zoushucai/zotero-journalabbr)
[![GitHub Repo stars](https://img.shields.io/github/stars/MuiseDestiny/zotero-figure?label=zotero-figure&style=flat-square)](https://github.com/MuiseDestiny/zotero-figure)
[![GitHub Repo stars](https://img.shields.io/github/stars/l0o0/jasminum?label=jasminum&style=flat-square)](https://github.com/l0o0/jasminum)
[![GitHub Repo stars](https://img.shields.io/github/stars/lifan0127/ai-research-assistant?label=ai-research-assistant&style=flat-square)](https://github.com/lifan0127/ai-research-assistant)
[![GitHub Repo stars](https://img.shields.io/github/stars/daeh/zotero-markdb-connect?label=zotero-markdb-connect&style=flat-square)](https://github.com/daeh/zotero-markdb-connect)
如果你正在使用此库,我建议你将这个标志 ([![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template)) 放在 README 文件中:
```md
[![Using Zotero Plugin Template](https://img.shields.io/badge/Using-Zotero%20Plugin%20Template-blue?style=flat-square&logo=github)](https://github.com/windingwind/zotero-plugin-template)
```
## Features 特性
- 事件驱动、函数式编程的可扩展框架;
- 简单易用,开箱即用;
- ⭐[新特性!]自动热重载!每当修改源码时,都会自动编译并重新加载插件;[详情请跳转→](#自动热重载)
- `src/modules/examples.ts` 中有丰富的示例涵盖了插件中常用的大部分API (使用的插件工具包 zotero-plugin-toolkit仓库地址 https://github.com/windingwind/zotero-plugin-toolkit)
- TypeScript 支持:
- 为使用 JavaScript 编写的Zotero源码提供全面的类型定义支持 (使用类型定义包 zotero-types仓库地址 https://github.com/windingwind/zotero-types)
- 全局变量和环境设置;
- 插件开发/构建/发布工作流:
- 自动生成/更新插件id和版本、更新配置和设置环境变量 (`development`/`production`)
- 自动在 Zotero 中构建和重新加载代码;
- 自动发布到GitHub (使用[release-it](https://github.com/release-it/release-it));
- 集成Prettier和ES Lint;
> [!warning]
> Zotero本地化已升级(`dtd` 已弃用,我们将不再使用 `.properties`). 主分支将只支持 Zotero 7.0.0-beta.12 或更高版本. 如果需要支持 Zotero 6你可能需要同时使用`dtd`、`properties` 和`ftl`. 请参考此库的 `zotero6-bootstrap` 分支.
## Examples 示例
此库提供了 [zotero-plugin-toolkit](https://github.com/windingwind/zotero-plugin-toolkit) 中API的示例.
`src/examples.ts` 中搜索`@example` 查看示例. 这些示例在 `src/hooks.ts` 中调用演示.
### 基本示例(Basic Examples)
- registerNotifier
- registerPrefs, unregisterPrefs
### 快捷键示例(Shortcut Keys Examples)
- registerShortcuts
- exampleShortcutLargerCallback
- exampleShortcutSmallerCallback
- exampleShortcutConflictionCallback
### UI示例(UI Examples)
![image](https://user-images.githubusercontent.com/33902321/211739774-cc5c2df8-5fd9-42f0-9cdf-0f2e5946d427.png)
- registerStyleSheet(the official make-it-red example)
- registerRightClickMenuItem
- registerRightClickMenuPopup
- registerWindowMenuWithSeprator
- registerExtraColumn
- registerExtraColumnWithCustomCell
- registerCustomItemBoxRow
- registerLibraryTabPanel
- registerReaderTabPanel
### 首选项面板示例(Preference Pane Examples)
![image](https://user-images.githubusercontent.com/33902321/211737987-cd7c5c87-9177-4159-b975-dc67690d0490.png)
- Preferences bindings
- UI Events
- Table
- Locale
详情参见 [`src/modules/preferenceScript.ts`](./src/modules/preferenceScript.ts)
### 帮助示例(HelperExamples)
![image](https://user-images.githubusercontent.com/33902321/215119473-e7d0d0ef-6d96-437e-b989-4805ffcde6cf.png)
- dialogExample
- clipboardExample
- filePickerExample
- progressWindowExample
- vtableExample(See Preference Pane Examples)
### 指令行示例(PromptExamples)
Obsidian风格的指令输入模块它通过接受文本来运行插件并在弹出窗口中显示可选项.
使用 `Shift+P` 激活.
![image](https://user-images.githubusercontent.com/33902321/215120009-e7c7ed27-33a0-44fe-b021-06c272481a92.png)
- registerAlertPromptExample
## Quick Start Guide 快速入门指南
### 0 前置要求(Requirement)
1. 安装测试版 Zoterohttps://www.zotero.org/support/beta_builds
2. 安装 Node.jshttps://nodejs.org/en/)和 Githttps://git-scm.com/
> [!note]
> 本指南假定你已经对 Zotero 插件的基本结构和工作原理有初步的了解. 如果你还不了解请先参考官方文档https://www.zotero.org/support/dev/zotero_7_for_developers和官方插件样例 Make It Red仓库地址 https://github.com/zotero/make-it-red.
### 1 创建你的仓库(Create Your Repo)
1. 点击 `Use this template`
2. 使用 `git clone` 克隆上一步生成的仓库;
<details >
<summary>💡 从 GitHub Codespace 开始</summary>
_GitHub CodeSpace_ 使你可以直接开始开发而无需在本地下载代码/IDE/依赖.
重复下列步骤,仅需三十秒即可开始构建你的第一个插件!
- 去 [homepage](https://github.com/windingwind/zotero-plugin-template)顶部,点击绿色按钮`Use this template`,点击 `Open in codespace` 你需要登录你的GitHub账号.
- 等待 codespace 加载.
</details>
3. 进入项目文件夹;
### 2 配置模板和开发环境(Config Template Settings and Enviroment)
1. 修改 `./package.json` 中的设置,包括:
```json5
{
version: "", // to 0.0.0
author: "",
description: "",
homepage: "",
config: {
addonName: "", // name to be displayed in the plugin manager
addonID: "", // ID to avoid conflict. IMPORTANT!
addonRef: "", // e.g. Element ID prefix
addonInstance: "", // the plugin's root instance: Zotero.${addonInstance}
prefsPrefix: "extensions.zotero.${addonRef}", // the prefix of prefs
releasePage: "", // URL to releases
updateJSON: "", // URL to update.json
},
}
```
> [!warning]
> 注意设置 addonID 和 addonRef 以避免冲突.
如果你需要在GitHub以外的地方托管你的 XPI 包,请删除 `releasePage` 并添加 `updateLink`,并将值设置为你的 XPI 下载地址.
2. 复制 Zotero 启动配置,填入 Zotero 可执行文件路径和 profile 路径.
> (可选项) 此操作仅需执行一次: 使用 `/path/to/zotero -p` 启动 Zotero创建一个新的配置文件并用作开发配置文件.
> 将配置文件的路径 `profilePath` 放入 `zotero-cmd.json` 中,以指定要使用的配置文件.
```sh
cp ./scripts/zotero-cmd-template.json ./scripts/zotero-cmd.json
vim ./scripts/zotero-cmd.json
```
3. 运行 `npm install` 以安装相关依赖
> 如果你使用 `pnpm` 作为包管理器,你需要添加 `public-hoist-pattern[]=*@types/bluebird*` 到`.npmrc`, 详情请查看 zotero-typeshttps://github.com/windingwind/zotero-types?tab=readme-ov-file#usage的文档.
### 3 开始开发(Coding)
使用 `npm start` 启动开发服务器,它将:
- 在开发模式下预构建插件
- 启动 Zotero ,并让其从 `build/` 中加载插件
- 打开开发者工具devtool
- 监听 `src/**``addon/**`.
- 如果 `src/**` 修改了,运行 esbuild 并且重新加载
- 如果 `addon/**` 修改了,(在开发模式下)重新构建插件并且重新加载
#### 自动热重载
厌倦了无休止的重启吗?忘掉它,拥抱热加载!
1. 运行 `npm start`.
2. 编码. (是的,就这么简单)
当检测到 `src``addon` 中的文件修改时,插件将自动编译并重新加载.
<details style="text-indent: 2em">
<summary>💡 将此功能添加到现有插件的步骤</summary>
1. 复制 `scripts/**.mjs`
2. 复制 `server` 、`build` 和 `stop` 命令到 `package.json`
3. 运行 `npm install --save-dev chokidar`
4. 结束.
</details>
#### 在 Zotero 中 Debug
你还可以:
- 在 Tools->Developer->Run Javascript 中测试代码片段;
- 使用 `Zotero.debug()` 调试输出. 在 Help->Debug Output Logging->View Output 查看输出;
- 调试 UI. Zotero 建立在 Firefox XUL 框架之上. 使用 [XUL Explorer](https://udn.realityripple.com/docs/Archive/Mozilla/XUL_Explorer) 等软件调试 XUL UI.
> XUL 文档: <http://www.devdoc.net/web/developer.mozilla.org/en-US/docs/XUL.html>
### 4 构建(Build)
运行 `npm run build` 在生产模式下构建插件,构建的结果位于 `build/` 目录中.
`scripts/build.mjs` 的运行步骤:
- 创建/清空 `build/`
- 复制 `addon/**``build/addon/**`
- 替换占位符:使用 `replace-in-file` 去替换在 `package.json` 中定义的关键字和配置 (`xhtml`、`.flt` 等)
- 准备本地化文件以避免冲突查看官方文档了解更多https://www.zotero.org/support/dev/zotero_7_for_developers#avoiding_localization_conflicts
- 重命名`**/*.flt` 为 `**/${addonRef}-*.flt`
- 在每个消息前加上 `addonRef-`
- 使用 Esbuild 来将 `.ts` 源码构建为 `.js`,从 `src/index.ts` 构建到`./build/addon/chrome/content/scripts`
- (仅在生产模式下工作) 压缩 `./build/addon` 目录为 `./build/*.xpi`
- (仅在生产模式下工作) 准备 `update.json``update-beta.json`
> [!note]
>
> **Dev & prod 两者有什么区别?**
>
> - 此环境变量存储在 `Zotero.${addonInstance}.data.env` 中,控制台输出在生产模式下被禁用.
> - 你可以根据此变量决定用户无法查看/使用的内容.
> - 在生产模式下,构建脚本将自动打包插件并更新 `update.json`.
### 5 发布(Release)
如果要构建和发布插件,运行如下指令:
```shell
# A release-it command: version increase, npm run build, git push, and GitHub release
# release-it: https://github.com/release-it/release-it
npm run release
```
> [!note]
> 在此模板中release-it 被配置为在本地升级版本、构建、推送提交和 git 标签随后GitHub Action 将重新构建插件并将 XPI 发布到 GitHub Release.
>
> 如果你需要发布一个本地构建的 XPI`package.json` 中的 `release-it.github.release` 设置为 `true`,然后移除 `.github/workflows/release.yml`. 此外,你还需要设置环境变量 `GITHUB_TOKEN`,获取 GitHub Tokenhttps://github.com/settings/tokens.
#### 关于预发布
该模板将 `prerelease` 定义为插件的测试版,当你在 release-it 中选择 `prerelease` 版本 (版本号中带有 `-` ),构建脚本将创建一个 `update-beta.json` 给预发布版本使用,这将确保常规版本的用户不会自动更新到测试版,只有手动下载并安装了测试版的用户才能自动更新到下一个测试版. 当下一个正式版本更新时,脚本将同步更新 `update.json``update-beta.json`,这将使正式版和测试版用户都可以更新到最新的正式版.
> [!warning]
> 严格来说,区分 Zotero 6 和 Zotero 7 兼容的插件版本应该通过 `update.json``addons.__addonID__.updates[]` 中分别配置 `applications.zotero.strict_min_version`,这样 Zotero 才能正确识别,详情在 Zotero 7 开发文档https://www.zotero.org/support/dev/zotero_7_for_developers#updaterdf_updatesjson获取.
## Details 更多细节
### 关于Hooks(About Hooks)
> 可以在 [`src/hooks.ts`](https://github.com/windingwind/zotero-plugin-template/blob/main/src/hooks.ts) 中查看更多
1. 当在 Zotero 中触发安装/启用/启动时,`bootstrap.js` > `startup` 被调用
- 等待 Zotero 就绪
- 加载 `index.js` (插件代码的主入口,从 `index.ts` 中构建)
- 如果是 Zotero 7 以上的版本则注册资源
2. 主入口 `index.js` 中,插件对象被注入到 `Zotero` ,并且 `hooks.ts` > `onStartup` 被调用.
- 初始化插件需要的资源包括通知监听器、首选项面板和UI元素.
3. 当在 Zotero 中触发卸载/禁用时,`bootstrap.js` > `shutdown` 被调用.
- `events.ts` > `onShutdown` 被调用. 移除 UI 元素、首选项面板或插件创建的任何内容.
- 移除脚本并释放资源.
### 关于全局变量(About Global Variables)
> 可以在 [`src/index.ts`](https://github.com/windingwind/zotero-plugin-template/blob/main/src/index.ts)中查看更多
bootstrap插件在沙盒中运行但沙盒中没有默认的全局变量例如 `Zotero``window` 等我们曾在overlay插件环境中使用的变量.
此模板将以下变量注册到全局范围:
```ts
Zotero, ZoteroPane, Zotero_Tabs, window, document, rootURI, ztoolkit, addon;
```
### 创建元素 API(Create Elements API)
插件模板为 bootstrap 插件提供了一些新的API. 我们有两个原因使用这些 API而不是使用 `createElement/createElementNS`
- 在 bootstrap 模式下,插件必须在推出(禁用或卸载)时清理所有 UI 元素,这非常麻烦. 使用 `createElement`,插件模板将维护这些元素. 仅仅在退出时 `unregisterAll` .
- Zotero 7 需要 createElement()/createElementNS() → createXULElement() 来表示其他的 XUL 元素,而 Zotero 6 并不支持 `createXULElement`. 类似于 React.createElement 的API `createElement` 检测 namespace(xul/html/svg) 并且自动创建元素,返回元素为对应的 TypeScript 元素类型.
```ts
createElement(document, "div"); // returns HTMLDivElement
createElement(document, "hbox"); // returns XUL.Box
createElement(document, "button", { namespace: "xul" }); // manually set namespace. returns XUL.Button
```
### 关于 Zotero API(About Zotero API)
Zotero 文档已过时且不完整,克隆 https://github.com/zotero/zotero 并全局搜索关键字.
> ⭐[zotero-types](https://github.com/windingwind/zotero-types) 提供了最常用的 Zotero API在默认情况下它被包含在此模板中. 你的 IDE 将为大多数的 API 提供提醒.
猜你需要:查找所需 API的技巧
`.xhtml`/`.flt` 文件中搜索 UI 标签,然后在 locale 文件中找到对应的键. ,然后在 `.js`/`.jsx` 文件中搜索此键.
### 目录结构(Directory Structure)
本部分展示了模板的目录结构.
- 所有的 `.js/.ts` 代码都在 `./src`;
- 插件配置文件:`./addon/manifest.json`;
- UI 文件: `./addon/chrome/content/*.xhtml`.
- 区域设置文件: `./addon/locale/**/*.flt`;
- 首选项文件: `./addon/prefs.js`;
> 不要在 `prefs.js` 中换行
```shell
.
|-- .eslintrc.json # eslint conf
|-- .gitattributes # git conf
|-- .github/ # github conf
|-- .gitignore # git conf
|-- .prettierrc # prettier conf
|-- .release-it.json # release-it conf
|-- .vscode # vs code conf
| |-- extensions.json
| |-- launch.json
| |-- setting.json
| `-- toolkit.code-snippets
|-- package-lock.json # npm conf
|-- package.json # npm conf
|-- LICENSE
|-- README.md
|-- addon
| |-- bootstrap.js # addon load/unload script, like a main.c
| |-- chrome
| | `-- content
| | |-- icons/
| | |-- preferences.xhtml # preference panel
| | `-- zoteroPane.css
| |-- locale # locale
| | |-- en-US
| | | |-- addon.ftl
| | | `-- preferences.ftl
| | `-- zh-CN
| | |-- addon.ftl
| | `-- preferences.ftl
| |-- manifest.json # addon config
| `-- prefs.js
|-- build/ # build dir
|-- scripts # scripts for dev
| |-- build.mjs # script to build plugin
| |-- scripts.mjs # scripts send to Zotero, such as reload, openDevTool, etc
| |-- server.mjs # script to start a development server
| |-- start.mjs # script to start Zotero process
| |-- stop.mjs # script to kill Zotero process
| |-- utils.mjs # utils functions for dev scripts
| |-- update-template.json # template of `update.json`
| `-- zotero-cmd-template.json # template of local env
|-- src # source code
| |-- addon.ts # base class
| |-- hooks.ts # lifecycle hooks
| |-- index.ts # main entry
| |-- modules # sub modules
| | |-- examples.ts
| | `-- preferenceScript.ts
| `-- utils # utilities
| |-- locale.ts
| |-- prefs.ts
| |-- wait.ts
| `-- window.ts
|-- tsconfig.json # https://code.visualstudio.com/docs/languages/jsconfig
|-- typings # ts typings
| `-- global.d.ts
`-- update.json
```
## Disclaimer 免责声明
在 AGPL 下使用此代码. 不提供任何保证. 遵守你所在地区的法律!
如果你想更改许可,请通过 <wyzlshx@foxmail.com> 与我联系.

View File

@ -1,45 +1,134 @@
{
"name": "zotero-addon-template",
"version": "0.1.1",
"description": "Zotero Addon Template",
"name": "zotero-tldr",
"version": "1.0.7",
"description": "TLDR(too long; didn't read) from sematic scholar",
"config": {
"addonName": "Zotero Addon Template",
"addonID": "addontemplate@euclpts.com",
"addonRef": "addontemplate",
"releasepage": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
"updaterdf": "https://raw.githubusercontent.com/windingwind/zotero-addon-template/bootstrap/update.json"
"addonName": "Zotero TLDR",
"addonID": "zoterotldr@syt.com",
"addonRef": "zoterotldr",
"addonInstance": "ZoteroTLDR",
"prefsPrefix": "extensions.zotero.zoterotldr",
"releasepage": "https://github.com/syt2/zotero-tldr/releases",
"updateJSON": "https://raw.githubusercontent.com/syt2/zotero-tldr/main/update.json"
},
"main": "src/index.ts",
"scripts": {
"build-dev": "cross-env NODE_ENV=development node scripts/build.js",
"build": "cross-env NODE_ENV=production node scripts/build.js",
"start": "node scripts/start.js",
"stop": "node scripts/stop.js",
"prerestart": "npm run build-dev",
"restart": "node scripts/restart.js",
"release": "release-it",
"test": "echo \"Error: no test specified\" && exit 1"
"start": "node scripts/server.mjs",
"build": "tsc --noEmit && node scripts/build.mjs production",
"stop": "node scripts/stop.mjs",
"lint": "prettier --write . && eslint . --ext .ts --fix",
"test": "echo \"Error: no test specified\" && exit 1",
"release": "release-it --only-version --preReleaseId=beta",
"update-deps": "npm update --save"
},
"repository": {
"type": "git",
"url": "git+https://github.com/windingwind/zotero-addon-template.git"
"url": "git+https://github.com/syt2/zotero-tldr.git"
},
"author": "windingwind",
"author": "syt2",
"license": "AGPL-3.0-or-later",
"bugs": {
"url": "https://github.com/windingwind/zotero-addon-template/issues"
"url": "https://github.com/syt2/zotero-tldr/issues"
},
"homepage": "https://github.com/windingwind/zotero-addon-template#readme",
"homepage": "https://github.com/syt2/zotero-tldr#readme",
"dependencies": {
"zotero-plugin-toolkit": "^1.0.1"
"zotero-plugin-toolkit": "^2.3.29"
},
"devDependencies": {
"@types/node": "^18.11.17",
"compressing": "^1.6.3",
"cross-env": "^7.0.3",
"esbuild": "^0.16.10",
"release-it": "^14.14.3",
"replace-in-file": "^6.3.5",
"zotero-types": "^0.1.5"
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.1.1",
"chokidar": "^3.5.3",
"compressing": "^1.10.0",
"esbuild": "^0.20.1",
"eslint": "^8.55.0",
"eslint-config-prettier": "^9.1.0",
"prettier": "^3.1.1",
"release-it": "^17.0.1",
"replace-in-file": "^7.0.2",
"typescript": "^5.3.3",
"zotero-types": "^1.3.10"
},
"eslintConfig": {
"env": {
"browser": true,
"es2021": true
},
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@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": [
"**/build/**",
"**/logs/**",
"**/dist/**",
"**/node_modules/**",
"**/scripts/**",
"**/*.js",
"**/*.bak"
]
},
"prettier": {
"printWidth": 80,
"tabWidth": 2,
"endOfLine": "lf",
"overrides": [
{
"files": [
"*.xhtml"
],
"options": {
"htmlWhitespaceSensitivity": "css"
}
}
]
},
"release-it": {
"git": {
"tagName": "V${version}"
},
"npm": {
"publish": false
},
"github": {
"release": false,
"assets": [
"build/*.xpi"
]
},
"hooks": {
"before:init": "npm run lint",
"after:bump": "npm run build"
}
}
}

View File

@ -1,193 +0,0 @@
const esbuild = require("esbuild");
const compressing = require("compressing");
const path = require("path");
const fs = require("fs");
const process = require("process");
const replace = require("replace-in-file");
const {
name,
author,
description,
homepage,
version,
config,
} = require("../package.json");
function copyFileSync(source, target) {
var targetFile = target;
// If target is a directory, a new file with the same name will be created
if (fs.existsSync(target)) {
if (fs.lstatSync(target).isDirectory()) {
targetFile = path.join(target, path.basename(source));
}
}
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
function copyFolderRecursiveSync(source, target) {
var files = [];
// Check if folder needs to be created or integrated
var targetFolder = path.join(target, path.basename(source));
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
// Copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
var curSource = path.join(source, file);
if (fs.lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
function clearFolder(target) {
if (fs.existsSync(target)) {
fs.rmSync(target, { recursive: true, force: true });
}
fs.mkdirSync(target, { recursive: true });
}
function dateFormat(fmt, date) {
let ret;
const opt = {
"Y+": date.getFullYear().toString(),
"m+": (date.getMonth() + 1).toString(),
"d+": date.getDate().toString(),
"H+": date.getHours().toString(),
"M+": date.getMinutes().toString(),
"S+": date.getSeconds().toString(),
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(
ret[1],
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=${[
process.env.NODE_ENV,
]}`
);
clearFolder(buildDir);
copyFolderRecursiveSync("addon", buildDir);
copyFileSync("update-template.json", "update.json");
copyFileSync("update-template.rdf", "update.rdf");
await esbuild
.build({
entryPoints: ["src/index.ts"],
define: {
__env__: `"${process.env.NODE_ENV}"`,
},
bundle: true,
// Entry should be the same as addon/chrome/content/overlay.xul
outfile: path.join(buildDir, "addon/chrome/content/scripts/index.js"),
// minify: true,
})
.catch(() => process.exit(1));
console.log("[Build] Run esbuild OK");
const optionsAddon = {
files: [
path.join(buildDir, "**/*.rdf"),
path.join(buildDir, "**/*.dtd"),
path.join(buildDir, "**/*.xul"),
path.join(buildDir, "**/*.xhtml"),
path.join(buildDir, "**/*.json"),
path.join(buildDir, "addon/defaults", "**/*.js"),
path.join(buildDir, "addon/chrome.manifest"),
path.join(buildDir, "addon/manifest.json"),
path.join(buildDir, "addon/bootstrap.js"),
"update.json",
"update.rdf",
],
from: [
/__author__/g,
/__description__/g,
/__homepage__/g,
/__releasepage__/g,
/__updaterdf__/g,
/__addonName__/g,
/__addonID__/g,
/__addonRef__/g,
/__buildVersion__/g,
/__buildTime__/g,
],
to: [
author,
description,
homepage,
config.releasepage,
config.updaterdf,
config.addonName,
config.addonID,
config.addonRef,
version,
buildTime,
],
countMatches: true,
};
_ = replace.sync(optionsAddon);
console.log(
"[Build] Run replace in ",
_.filter((f) => f.hasChanged).map(
(f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`
)
);
// _ = replace.sync({
// files: [path.join(buildDir, "addon/chrome/content/scripts/index.js")],
// from: [/__env__/g]
// });
// console.log(
// "[Build] Run replace in ",
// _.filter((f) => f.hasChanged).map(
// (f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`
// )
// );
console.log("[Build] Replace OK");
console.log("[Build] Addon prepare OK");
compressing.zip.compressDir(
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.`
);
}
main();

243
scripts/build.mjs Normal file
View File

@ -0,0 +1,243 @@
import details from "../package.json" assert { type: "json" };
import {
Logger,
clearFolder,
copyFileSync,
copyFolderRecursiveSync,
dateFormat,
} from "./utils.mjs";
import { zip } from "compressing";
import { build } from "esbuild";
import { existsSync, readdirSync, renameSync } from "fs";
import path from "path";
import { env, exit } from "process";
import replaceInFile from "replace-in-file";
const { replaceInFileSync } = replaceInFile;
process.env.NODE_ENV =
process.argv[2] === "production" ? "production" : "development";
const buildDir = "build";
const { name, author, description, homepage, version, config } = details;
const isPreRelease = version.includes("-");
function replaceString(buildTime) {
const replaceFrom = [
/__author__/g,
/__description__/g,
/__homepage__/g,
/__buildVersion__/g,
/__buildTime__/g,
];
const replaceTo = [author, description, homepage, version, buildTime];
config.updateURL = isPreRelease
? config.updateJSON.replace("update.json", "update-beta.json")
: config.updateJSON;
replaceFrom.push(
...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g")),
);
replaceTo.push(...Object.values(config));
const replaceResult = replaceInFileSync({
files: [
`${buildDir}/addon/**/*.xhtml`,
`${buildDir}/addon/**/*.html`,
`${buildDir}/addon/**/*.css`,
`${buildDir}/addon/**/*.json`,
`${buildDir}/addon/prefs.js`,
`${buildDir}/addon/manifest.json`,
`${buildDir}/addon/bootstrap.js`,
],
from: replaceFrom,
to: replaceTo,
countMatches: true,
});
// Logger.debug(
// "[Build] Run replace in ",
// replaceResult.filter((f) => f.hasChanged).map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`),
// );
}
function prepareLocaleFiles() {
// Prefix Fluent messages in xhtml
const MessagesInHTML = new Set();
replaceInFileSync({
files: [`${buildDir}/addon/**/*.xhtml`, `${buildDir}/addon/**/*.html`],
processor: (input) => {
const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)];
matchs.map((match) => {
input = input.replace(
match[0],
`${match[1]}="${config.addonRef}-${match[2]}"`,
);
MessagesInHTML.add(match[2]);
});
return input;
},
});
// Walk the sub folders of `build/addon/locale`
const localesPath = path.join(buildDir, "addon/locale"),
localeNames = readdirSync(localesPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
for (const localeName of localeNames) {
const localePath = path.join(localesPath, localeName);
const ftlFiles = readdirSync(localePath, {
withFileTypes: true,
})
.filter((dirent) => dirent.isFile())
.map((dirent) => dirent.name);
// rename *.ftl to addonRef-*.ftl
for (const ftlFile of ftlFiles) {
if (ftlFile.endsWith(".ftl")) {
renameSync(
path.join(localePath, ftlFile),
path.join(localePath, `${config.addonRef}-${ftlFile}`),
);
}
}
// Prefix Fluent messages in each ftl
const MessageInThisLang = new Set();
replaceInFileSync({
files: [`${buildDir}/addon/locale/${localeName}/*.ftl`],
processor: (fltContent) => {
const lines = fltContent.split("\n");
const prefixedLines = lines.map((line) => {
// https://regex101.com/r/lQ9x5p/1
const match = line.match(
/^(?<message>[a-zA-Z]\S*)([ ]*=[ ]*)(?<pattern>.*)$/m,
);
if (match) {
MessageInThisLang.add(match.groups.message);
return `${config.addonRef}-${line}`;
} else {
return line;
}
});
return prefixedLines.join("\n");
},
});
// If a message in xhtml but not in ftl of current language, log it
MessagesInHTML.forEach((message) => {
if (!MessageInThisLang.has(message)) {
Logger.error(`[Build] ${message} don't exist in ${localeName}`);
}
});
}
}
function prepareUpdateJson() {
// If it is a pre-release, use update-beta.json
if (!isPreRelease) {
copyFileSync("scripts/update-template.json", "update.json");
}
if (existsSync("update-beta.json") || isPreRelease) {
copyFileSync("scripts/update-template.json", "update-beta.json");
}
const updateLink =
config.updateLink ?? isPreRelease
? `${config.releasePage}/download/v${version}/${name}.xpi`
: `${config.releasePage}/latest/download/${name}.xpi`;
const replaceResult = replaceInFileSync({
files: [
"update-beta.json",
isPreRelease ? "pass" : "update.json",
`${buildDir}/addon/manifest.json`,
],
from: [
/__addonID__/g,
/__buildVersion__/g,
/__updateLink__/g,
/__updateURL__/g,
],
to: [config.addonID, version, updateLink, config.updateURL],
countMatches: true,
});
Logger.debug(
`[Build] Prepare Update.json for ${
isPreRelease
? "\u001b[31m Prerelease \u001b[0m"
: "\u001b[32m Release \u001b[0m"
}`,
replaceResult
.filter((f) => f.hasChanged)
.map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`),
);
}
export const esbuildOptions = {
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: env.NODE_ENV === "production",
};
export async function main() {
const t = new Date();
const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());
Logger.info(
`[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
env.NODE_ENV,
]}`,
);
clearFolder(buildDir);
copyFolderRecursiveSync("addon", buildDir);
Logger.debug("[Build] Replacing");
replaceString(buildTime);
Logger.debug("[Build] Preparing locale files");
prepareLocaleFiles();
Logger.debug("[Build] Running esbuild");
await build(esbuildOptions);
Logger.debug("[Build] Addon prepare OK");
if (process.env.NODE_ENV === "production") {
Logger.debug("[Build] Packing Addon");
await zip.compressDir(
path.join(buildDir, "addon"),
path.join(buildDir, `${name}.xpi`),
{
ignoreBase: true,
},
);
prepareUpdateJson();
Logger.debug(
`[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`,
);
}
}
if (process.env.NODE_ENV === "production") {
main().catch((err) => {
Logger.error(err);
exit(1);
});
}

View File

@ -1,8 +0,0 @@
const { execSync } = require("child_process");
const { killZotero, startZotero } = require("./zotero-cmd.json");
try {
execSync(killZotero);
} catch (e) {}
execSync(startZotero);

75
scripts/scripts.mjs Normal file
View File

@ -0,0 +1,75 @@
import details from "../package.json" assert { type: "json" };
const { addonID, addonName } = details.config;
const { version } = details;
export const reloadScript = `
(async () => {
Services.obs.notifyObservers(null, "startupcache-invalidate", null);
const { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
const addon = await AddonManager.getAddonByID("${addonID}");
await addon.reload();
const progressWindow = new Zotero.ProgressWindow({ closeOnClick: true });
progressWindow.changeHeadline("${addonName} Hot Reload");
progressWindow.progress = new progressWindow.ItemProgress(
"chrome://zotero/skin/tick.png",
"VERSION=${version}, BUILD=${new Date().toLocaleString()}. By zotero-plugin-toolkit"
);
progressWindow.progress.setProgress(100);
progressWindow.show();
progressWindow.startCloseTimer(5000);
})()`;
export const openDevToolScript = `
(async () => {
// const { BrowserToolboxLauncher } = ChromeUtils.import(
// "resource://devtools/client/framework/browser-toolbox/Launcher.jsm",
// );
// BrowserToolboxLauncher.init();
// TODO: Use the above code to open the devtool after https://github.com/zotero/zotero/pull/3387
Zotero.Prefs.set("devtools.debugger.remote-enabled", true, true);
Zotero.Prefs.set("devtools.debugger.remote-port", 6100, true);
Zotero.Prefs.set("devtools.debugger.prompt-connection", false, true);
Zotero.Prefs.set("devtools.debugger.chrome-debugging-websocket", false, true);
env =
Services.env ||
Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
env.set("MOZ_BROWSER_TOOLBOX_PORT", 6100);
Zotero.openInViewer(
"chrome://devtools/content/framework/browser-toolbox/window.html",
{
onLoad: (doc) => {
doc.querySelector("#status-message-container").style.visibility =
"collapse";
let toolboxBody;
waitUntil(
() => {
toolboxBody = doc
.querySelector(".devtools-toolbox-browsertoolbox-iframe")
?.contentDocument?.querySelector(".theme-body");
return toolboxBody;
},
() => {
toolboxBody.style = "pointer-events: all !important";
}
);
},
}
);
function waitUntil(condition, callback, interval = 100, timeout = 10000) {
const start = Date.now();
const intervalId = setInterval(() => {
if (condition()) {
clearInterval(intervalId);
callback();
} else if (Date.now() - start > timeout) {
clearInterval(intervalId);
}
}, interval);
}
})()`;

87
scripts/server.mjs Normal file
View File

@ -0,0 +1,87 @@
import { main as build, esbuildOptions } from "./build.mjs";
import { openDevToolScript, reloadScript } from "./scripts.mjs";
import { main as startZotero } from "./start.mjs";
import { Logger } from "./utils.mjs";
import cmd from "./zotero-cmd.json" assert { type: "json" };
import { execSync } from "child_process";
import chokidar from "chokidar";
import { context } from "esbuild";
import { exit } from "process";
process.env.NODE_ENV = "development";
const { zoteroBinPath, profilePath } = cmd.exec;
const startZoteroCmd = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`;
async function watch() {
const watcher = chokidar.watch(["src/**", "addon/**"], {
ignored: /(^|[\/\\])\../, // ignore dotfiles
persistent: true,
});
let esbuildCTX = await context(esbuildOptions);
watcher
.on("ready", () => {
Logger.info("Server Ready! \n");
})
.on("change", async (path) => {
Logger.info(`${path} changed.`);
if (path.startsWith("src")) {
await esbuildCTX.rebuild();
} else if (path.startsWith("addon")) {
await build()
// Do not abort the watcher when errors occur in builds triggered by the watcher.
.catch((err) => {
Logger.error(err);
});
}
// reload
reload();
})
.on("error", (err) => {
Logger.error("Server start failed!", err);
});
}
function reload() {
Logger.debug("Reloading...");
const url = `zotero://ztoolkit-debug/?run=${encodeURIComponent(
reloadScript,
)}`;
const command = `${startZoteroCmd} -url "${url}"`;
execSync(command);
}
function openDevTool() {
Logger.debug("Open dev tools...");
const url = `zotero://ztoolkit-debug/?run=${encodeURIComponent(
openDevToolScript,
)}`;
const command = `${startZoteroCmd} -url "${url}"`;
execSync(command);
}
async function main() {
// build
await build();
// start Zotero
startZotero(openDevTool);
// watch
await watch();
}
main().catch((err) => {
Logger.error(err);
// execSync("node scripts/stop.mjs");
exit(1);
});
process.on("SIGINT", (code) => {
execSync("node scripts/stop.mjs");
Logger.info(`Server terminated with signal ${code}.`);
exit(0);
});

View File

@ -1,6 +0,0 @@
const { execSync } = require("child_process");
const { exit } = require("process");
const { startZotero } = require("./zotero-cmd.json");
execSync(startZotero);
exit(0);

119
scripts/start.mjs Normal file
View File

@ -0,0 +1,119 @@
import details from "../package.json" assert { type: "json" };
import { Logger } from "./utils.mjs";
import cmd from "./zotero-cmd.json" assert { type: "json" };
import { spawn } from "child_process";
import { existsSync, readFileSync, writeFileSync, rmSync } from "fs";
import { clearFolder } from "./utils.mjs";
import path from "path";
import { exit } from "process";
const { addonID } = details.config;
const { zoteroBinPath, profilePath, dataDir } = cmd.exec;
// Keep in sync with the addon's onStartup
const loadDevToolWhen = `Plugin ${addonID} startup`;
const logPath = "logs";
const logFilePath = path.join(logPath, "zotero.log");
if (!existsSync(zoteroBinPath)) {
throw new Error("Zotero binary does not exist.");
}
if (!existsSync(profilePath)) {
throw new Error("The given Zotero profile does not exist.");
}
function prepareDevEnv() {
const addonProxyFilePath = path.join(profilePath, `extensions/${addonID}`);
const buildPath = path.resolve("build/addon");
function writeAddonProxyFile() {
writeFileSync(addonProxyFilePath, buildPath);
Logger.debug(
`Addon proxy file has been updated.
File path: ${addonProxyFilePath}
Addon path: ${buildPath} `,
);
}
if (existsSync(addonProxyFilePath)) {
if (readFileSync(addonProxyFilePath, "utf-8") !== buildPath) {
writeAddonProxyFile();
}
} else {
writeAddonProxyFile();
}
const addonXpiFilePath = path.join(profilePath, `extensions/${addonID}.xpi`);
if (existsSync(addonXpiFilePath)) {
rmSync(addonXpiFilePath);
}
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.replace(/\\\\?/g, "\\\\")}");`;
}
return line;
});
const updatedPrefs = filteredLines.join("\n");
writeFileSync(prefsPath, updatedPrefs, "utf-8");
Logger.debug("The <profile>/prefs.js has been modified.");
}
}
function prepareLog() {
clearFolder(logPath);
writeFileSync(logFilePath, "");
}
export function main(callback) {
let isZoteroReady = false;
prepareDevEnv();
prepareLog();
const zoteroProcess = spawn(zoteroBinPath, [
"--debugger",
"--purgecaches",
"-profile",
profilePath,
]);
zoteroProcess.stdout.on("data", (data) => {
if (!isZoteroReady && data.toString().includes(loadDevToolWhen)) {
isZoteroReady = true;
callback();
}
writeFileSync(logFilePath, data, {
flag: "a",
});
});
zoteroProcess.stderr.on("data", (data) => {
writeFileSync(logFilePath, data, {
flag: "a",
});
});
zoteroProcess.on("close", (code) => {
Logger.info(`Zotero terminated with code ${code}.`);
exit(0);
});
process.on("SIGINT", () => {
// Handle interrupt signal (Ctrl+C) to gracefully terminate Zotero process
zoteroProcess.kill();
exit();
});
}

View File

@ -1,10 +0,0 @@
const { execSync } = require("child_process");
const { killZoteroWindows, killZoteroUnix } = require("./zotero-cmd.json");
try {
if (process.platform === "win32") {
execSync(killZoteroWindows);
} else {
execSync(killZoteroUnix);
}
} catch (e) {}

26
scripts/stop.mjs Normal file
View File

@ -0,0 +1,26 @@
import { Logger, isRunning } from "./utils.mjs";
import cmd from "./zotero-cmd.json" assert { type: "json" };
import { execSync } from "child_process";
import process from "process";
const { killZoteroWindows, killZoteroUnix } = cmd;
isRunning("zotero", (status) => {
if (status) {
killZotero();
} else {
Logger.warn("No Zotero running.");
}
});
function killZotero() {
try {
if (process.platform === "win32") {
execSync(killZoteroWindows);
} else {
execSync(killZoteroUnix);
}
} catch (e) {
Logger.error(e);
}
}

View File

@ -0,0 +1,17 @@
{
"addons": {
"__addonID__": {
"updates": [
{
"version": "__buildVersion__",
"update_link": "__updateLink__",
"applications": {
"zotero": {
"strict_min_version": "6.999"
}
}
}
]
}
}
}

129
scripts/utils.mjs Normal file
View File

@ -0,0 +1,129 @@
import { exec } from "child_process";
import {
existsSync,
lstatSync,
mkdirSync,
readFileSync,
readdirSync,
rmSync,
writeFileSync,
} from "fs";
import path from "path";
export 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 = path.join(target, path.basename(source));
}
}
writeFileSync(targetFile, readFileSync(source));
}
export function copyFolderRecursiveSync(source, target) {
var files = [];
// Check if folder needs to be created or integrated
var targetFolder = path.join(target, path.basename(source));
if (!existsSync(targetFolder)) {
mkdirSync(targetFolder);
}
// Copy
if (lstatSync(source).isDirectory()) {
files = readdirSync(source);
files.forEach(function (file) {
var curSource = path.join(source, file);
if (lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder);
} else {
copyFileSync(curSource, targetFolder);
}
});
}
}
export function clearFolder(target) {
if (existsSync(target)) {
rmSync(target, { recursive: true, force: true });
}
mkdirSync(target, { recursive: true });
}
export function dateFormat(fmt, date) {
let ret;
const opt = {
"Y+": date.getFullYear().toString(),
"m+": (date.getMonth() + 1).toString(),
"d+": date.getDate().toString(),
"H+": date.getHours().toString(),
"M+": date.getMinutes().toString(),
"S+": date.getSeconds().toString(),
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(
ret[1],
ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"),
);
}
}
return fmt;
}
export class Logger {
static log(...args) {
console.log(...args);
}
// red
static error(...args) {
console.error("\u001b[31m [ERROR]", ...args, "\u001b[0m");
}
// yellow
static warn(...args) {
console.warn("\u001b[33m [WARN]", ...args, "\u001b[0m");
}
// blue
static debug(...args) {
console.log("\u001b[34m [DEBUG]\u001b[0m", ...args);
}
// green
static info(...args) {
console.log("\u001b[32m [INFO]", ...args, "\u001b[0m");
}
// cyan
static trace(...args) {
console.log("\u001b[36m [TRACE]\u001b[0m", ...args);
}
}
export function isRunning(query, cb) {
let platform = process.platform;
let cmd = "";
switch (platform) {
case "win32":
cmd = `tasklist`;
break;
case "darwin":
cmd = `ps -ax | grep ${query}`;
break;
case "linux":
cmd = `ps -A`;
break;
default:
break;
}
exec(cmd, (err, stdout, stderr) => {
cb(stdout.toLowerCase().indexOf(query.toLowerCase()) > -1);
});
}

View File

@ -1,6 +0,0 @@
{
"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"
}

View File

@ -0,0 +1,20 @@
{
"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 '[z]otero' | awk '{print $1}')",
"exec": {
"@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": ""
}
}

View File

@ -1,32 +1,31 @@
import ZoteroToolkit from "zotero-plugin-toolkit/dist/index";
import { ColumnOptions } from "zotero-plugin-toolkit/dist/helpers/virtualizedTable";
import { DialogHelper } from "zotero-plugin-toolkit/dist/helpers/dialog";
import hooks from "./hooks";
import { createZToolkit } from "./utils/ztoolkit";
class Addon {
public data: {
alive: boolean;
// Env type, see build.js
env: "development" | "production";
ztoolkit: ZoteroToolkit;
ztoolkit: ZToolkit;
locale?: {
stringBundle: any;
current: any;
};
prefs?: {
window: Window;
columns: Array<ColumnOptions>;
rows: Array<{ [dataKey: string]: string }>;
};
dialog?: DialogHelper;
};
// Lifecycle hooks
public hooks: typeof hooks;
// APIs
public api: {};
public api: object;
constructor() {
this.data = {
alive: true,
env: __env__,
ztoolkit: new ZoteroToolkit(),
ztoolkit: createZToolkit(),
};
this.hooks = hooks;
this.api = {};

View File

@ -1,11 +1,10 @@
import {
BasicExampleFactory,
KeyExampleFactory,
UIExampleFactory,
} from "./modules/examples";
import { RegisterFactory, UIFactory } from "./modules/Common";
import { config } from "../package.json";
import { getString, initLocale } from "./modules/locale";
import { getString, initLocale } from "./utils/locale";
import { registerPrefsScripts } from "./modules/preferenceScript";
import { createZToolkit } from "./utils/ztoolkit";
import { tldrs } from "./modules/dataStorage";
import { TLDRFetcher } from "./modules/tldrFetcher";
async function onStartup() {
await Promise.all([
@ -13,67 +12,51 @@ async function onStartup() {
Zotero.unlockPromise,
Zotero.uiReadyPromise,
]);
// TODO: Remove this after zotero#3387 is merged
if (__env__ === "development") {
// Keep in sync with the scripts/startup.mjs
const loadDevToolWhen = `Plugin ${config.addonID} startup`;
ztoolkit.log(loadDevToolWhen);
}
initLocale();
ztoolkit.ProgressWindow.setIconURI(
"default",
`chrome://${config.addonRef}/content/icons/favicon.png`
await tldrs.getAsync();
RegisterFactory.registerNotifier();
await onMainWindowLoad(window);
}
async function onMainWindowLoad(win: Window): Promise<void> {
// Create ztoolkit for every window
addon.data.ztoolkit = createZToolkit();
(win as any).MozXULElement.insertFTLIfNeeded(
`${config.addonRef}-mainWindow.ftl`,
);
const popupWin = new ztoolkit.ProgressWindow(config.addonName, {
closeOnClick: true,
closeTime: -1,
})
.createLine({
text: getString("startup.begin"),
type: "default",
progress: 0,
})
.show();
UIFactory.registerRightClickMenuItem();
BasicExampleFactory.registerPrefs();
UIFactory.registerRightClickCollectionMenuItem();
BasicExampleFactory.registerNotifier();
UIFactory.registerTLDRItemBoxRow();
KeyExampleFactory.registerShortcuts();
onLoad();
}
await Zotero.Promise.delay(1000);
popupWin.changeLine({
progress: 30,
text: `[30%] ${getString("startup.begin")}`,
});
UIExampleFactory.registerStyleSheet();
UIExampleFactory.registerRightClickMenuItem();
UIExampleFactory.registerRightClickMenuPopup();
UIExampleFactory.registerWindowMenuWithSeprator();
await UIExampleFactory.registerExtraColumn();
await UIExampleFactory.registerExtraColumnWithCustomCell();
await UIExampleFactory.registerCustomCellRenderer();
UIExampleFactory.registerLibraryTabPanel();
await UIExampleFactory.registerReaderTabPanel();
await Zotero.Promise.delay(1000);
popupWin.changeLine({
progress: 100,
text: `[100%] ${getString("startup.finish")}`,
});
popupWin.startCloseTimer(5000);
async function onMainWindowUnload(win: Window): Promise<void> {
ztoolkit.unregisterAll();
addon.data.dialog?.window?.close();
}
function onShutdown(): void {
ztoolkit.unregisterAll();
addon.data.dialog?.window?.close();
// Remove addon object
addon.data.alive = false;
delete Zotero.AddonTemplate;
delete Zotero[config.addonInstance];
}
/**
@ -83,19 +66,14 @@ function onShutdown(): void {
async function onNotify(
event: string,
type: string,
ids: Array<string>,
extraData: { [key: string]: any }
ids: Array<string | number>,
extraData: { [key: string]: any },
) {
// You can add your code to the corresponding notify type
ztoolkit.log("notify", event, type, ids, extraData);
if (
event == "select" &&
type == "tab" &&
extraData[ids[0]].type == "reader"
) {
BasicExampleFactory.exampleNotifierCallback();
} else {
return;
Zotero.log(`${event} ${type} ${ids}, ${extraData}`);
if (event == "add" && type == "item" && ids.length > 0) {
onNotifyAddItems(ids);
} else if (event == "delete" && type == "item" && ids.length > 0) {
noNotifyDeleteItem(ids);
}
}
@ -115,30 +93,112 @@ async function onPrefsEvent(type: string, data: { [key: string]: any }) {
}
}
function onShortcuts(type: string) {
switch (type) {
case "larger":
KeyExampleFactory.exampleShortcutLargerCallback();
break;
case "smaller":
KeyExampleFactory.exampleShortcutSmallerCallback();
break;
case "confliction":
KeyExampleFactory.exampleShortcutConflictionCallback();
break;
default:
break;
function onLoad() {
(async () => {
let needFetchItems: Zotero.Item[] = [];
for (const lib of Zotero.Libraries.getAll()) {
needFetchItems = needFetchItems.concat(
(await Zotero.Items.getAll(lib.id)).filter((item: Zotero.Item) => {
return item.isRegularItem();
}),
);
}
onUpdateItems(needFetchItems, false);
})();
}
function noNotifyDeleteItem(ids: (string | number)[]) {
tldrs.modify((data) => {
ids.forEach((id) => {
delete data[id];
});
return data;
});
}
function onNotifyAddItems(ids: (string | number)[]) {
const addedRegularItems: Zotero.Item[] = [];
for (const id of ids) {
const item = Zotero.Items.get(id);
if (item.isRegularItem()) {
addedRegularItems.push(item);
}
}
(async function () {
await Zotero.Promise.delay(3000);
onUpdateItems(addedRegularItems, false);
})();
}
function onUpdateItems(items: Zotero.Item[], forceFetch: boolean = false) {
items = items.filter((item: Zotero.Item) => {
if (!item.getField("title")) {
return false;
}
if (!forceFetch && item.key in tldrs.get()) {
return false;
}
return true;
});
if (items.length <= 0) {
return;
}
const newPopWin = (closeOnClick = true) => {
return new ztoolkit.ProgressWindow(config.addonName, {
closeOnClick: closeOnClick,
}).createLine({
text: `${getString("popWindow-waiting")}: ${items.length}; ${getString(
"popWindow-succeed",
)}: 0; ${getString("popWindow-failed")}: 0`,
type: "default",
progress: 0,
});
};
const popupWin = newPopWin().show(-1);
(async function () {
const count = items.length;
const failedItems: Zotero.Item[] = [];
const succeedItems: Zotero.Item[] = [];
await (async function () {
for (const [index, item] of items.entries()) {
(await new TLDRFetcher(item).fetchTLDR())
? succeedItems.push(item)
: failedItems.push(item);
await Zotero.Promise.delay(50);
popupWin.changeLine({
progress: (index * 100) / count,
text: `${getString("popWindow-waiting")}: ${
count - index - 1
}; ${getString("popWindow-succeed")}: ${
succeedItems.length
}; ${getString("popWindow-failed")}: ${failedItems.length}`,
});
}
})();
await (async function () {
popupWin.changeLine({
type: "success",
progress: 100,
text: `${getString("popWindow-succeed")}: ${
succeedItems.length
}; ${getString("popWindow-failed")}: ${failedItems.length}`,
});
popupWin.startCloseTimer(3000);
})();
})();
}
// Add your hooks here. For element click, etc.
// Keep in mind hooks only do dispatch. Don't add code that does real jobs in hooks.
// Otherwise the code would be hard to read and maintian.
// Otherwise the code would be hard to read and maintain.
export default {
onStartup,
onShutdown,
onMainWindowLoad,
onMainWindowUnload,
onNotify,
onPrefsEvent,
onShortcuts,
onUpdateItems,
};

View File

@ -4,18 +4,24 @@ import { config } from "../package.json";
const basicTool = new BasicTool();
if (!basicTool.getGlobal("Zotero").AddonTemplate) {
// Set global variables
_globalThis.Zotero = basicTool.getGlobal("Zotero");
_globalThis.ZoteroPane = basicTool.getGlobal("ZoteroPane");
_globalThis.Zotero_Tabs = basicTool.getGlobal("Zotero_Tabs");
_globalThis.window = basicTool.getGlobal("window");
_globalThis.document = basicTool.getGlobal("document");
if (!basicTool.getGlobal("Zotero")[config.addonInstance]) {
defineGlobal("window");
defineGlobal("document");
defineGlobal("ZoteroPane");
defineGlobal("Zotero_Tabs");
_globalThis.addon = new Addon();
_globalThis.ztoolkit = addon.data.ztoolkit;
ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`;
ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production";
Zotero.AddonTemplate = addon;
// Trigger addon hook for initialization
addon.hooks.onStartup();
defineGlobal("ztoolkit", () => {
return _globalThis.addon.data.ztoolkit;
});
Zotero[config.addonInstance] = addon;
}
function defineGlobal(name: Parameters<BasicTool["getGlobal"]>[0]): void;
function defineGlobal(name: string, getter: () => any): void;
function defineGlobal(name: string, getter?: () => any) {
Object.defineProperty(_globalThis, name, {
get() {
return getter ? getter() : basicTool.getGlobal(name);
},
});
}

117
src/modules/Common.ts Normal file
View File

@ -0,0 +1,117 @@
import { config } from "../../package.json";
import { getString } from "../utils/locale";
import { tldrs } from "./dataStorage";
export class RegisterFactory {
// 注册zotero的通知
static registerNotifier() {
const callback = {
notify: async (
event: string,
type: string,
ids: number[] | string[],
extraData: { [key: string]: any },
) => {
if (!addon?.data.alive) {
this.unregisterNotifier(notifierID);
return;
}
addon.hooks.onNotify(event, type, ids, extraData);
},
};
// Register the callback in Zotero as an item observer
const notifierID = Zotero.Notifier.registerObserver(callback, ["item"]);
// Unregister callback when the window closes (important to avoid a memory leak)
window.addEventListener(
"unload",
(e: Event) => {
this.unregisterNotifier(notifierID);
},
false,
);
}
private static unregisterNotifier(notifierID: string) {
Zotero.Notifier.unregisterObserver(notifierID);
}
}
export class UIFactory {
// item右键菜单
static registerRightClickMenuItem() {
const menuIcon = `chrome://${config.addonRef}/content/icons/favicon.png`;
// item menuitem with icon
ztoolkit.Menu.register("item", {
tag: "menuitem",
id: "zotero-itemmenu-tldr",
label: getString("menuitem-updatetldrlabel"),
commandListener: (ev) => {
const selectedItems = ZoteroPane.getSelectedItems() ?? [];
addon.hooks.onUpdateItems(selectedItems, selectedItems.length <= 1);
},
icon: menuIcon,
});
}
// collection右键菜单
static registerRightClickCollectionMenuItem() {
const menuIcon = `chrome://${config.addonRef}/content/icons/favicon.png`;
ztoolkit.Menu.register("collection", {
tag: "menuitem",
id: "zotero-collectionmenu-tldr",
label: getString("menucollection-updatetldrlabel"),
commandListener: (ev) =>
addon.hooks.onUpdateItems(
ZoteroPane.getSelectedCollection()?.getChildItems() ?? [],
false,
),
icon: menuIcon,
});
}
// tldr行
static async registerTLDRItemBoxRow() {
const itemTLDR = (item: Zotero.Item) => {
const noteKey = tldrs.get()[item.key];
if (noteKey) {
const obj = Zotero.Items.getByLibraryAndKey(item.libraryID, noteKey);
if (
obj &&
obj instanceof Zotero.Item &&
item.getNotes().includes(obj.id)
) {
let str = obj.getNote();
if (str.startsWith("<p>TL;DR</p>\n<p>")) {
str = str.slice("<p>TL;DR</p>\n<p>".length);
}
if (str.endsWith("</p>")) {
str = str.slice(0, -4);
}
return str;
}
}
return "";
};
Zotero.ItemPaneManager.registerSection({
paneID: config.addonRef,
pluginID: config.addonID,
header: {
l10nID: `${config.addonRef}-itemPaneSection-header`,
icon: `chrome://${config.addonRef}/content/icons/favicon@16.png`,
},
sidenav: {
l10nID: `${config.addonRef}-itemPaneSection-sidenav`,
icon: `chrome://${config.addonRef}/content/icons/favicon@20.png`,
},
onRender: ({ body, item }: any) => {
let tldr = itemTLDR(item);
if (tldr.length <= 0 && item.parentItem) {
tldr = itemTLDR(item.parentItem);
}
body.textContent = tldr;
},
});
}
}

124
src/modules/dataStorage.ts Normal file
View File

@ -0,0 +1,124 @@
import { config } from "../../package.json";
export class Data<K extends string | number | symbol, V> {
[x: string]: any;
private dataType: string;
private filePath?: string;
private _data: Record<K, V>;
constructor(dataType: string) {
this.dataType = dataType;
this._data = {} as Record<K, V>;
}
async getAsync() {
await this.initDataIfNeed();
return this.data;
}
get() {
return this.data;
}
async modify(
action: (data: Record<K, V>) => Record<K, V> | Promise<Record<K, V>>,
) {
await this.initDataIfNeed();
const data = this.data;
const newData = await action(data);
if (this.filePath) {
try {
await IOUtils.writeJSON(this.filePath, newData, {
mode: "overwrite",
compress: false,
});
this.data = newData;
return newData;
} catch (error) {
return data;
}
} else {
this.data = newData;
return newData;
}
}
async delete() {
if (this.filePath) {
try {
await IOUtils.remove(this.filePath);
this.data = {} as Record<K, V>;
return true;
} catch (error) {
return false;
}
} else {
this.data = {} as Record<K, V>;
return true;
}
}
private get data() {
return this._data;
}
private set data(value: Record<K, V>) {
this._data = value;
}
private async initDataIfNeed() {
if (this.inited) {
return;
}
this.inited = true;
const prefsFile = PathUtils.join(PathUtils.profileDir, "prefs.js");
const prefs = await Zotero.Profile.readPrefsFromFile(prefsFile);
let dir = prefs["extensions.zotero.dataDir"];
if (dir) {
dir = PathUtils.join(dir, config.addonName);
} else {
dir = PathUtils.join(
PathUtils.profileDir,
"extensions",
config.addonName,
);
}
IOUtils.makeDirectory(dir, {
createAncestors: true,
ignoreExisting: true,
});
this.filePath = PathUtils.join(dir, this.dataType);
try {
this.data = await IOUtils.readJSON(this.filePath, { decompress: false });
} catch (error) {
this.data = {} as Record<K, V>;
}
}
}
export class DataStorage {
private dataMap: { [key: string]: Data<any, any> } = {};
private static shared = new DataStorage();
static instance<K extends string | number | symbol, V>(
dataType: string,
): Data<K, V> {
if (this.shared.dataMap[dataType] === undefined) {
const data = new Data<K, V>(dataType);
this.shared.dataMap[dataType] = data;
return data;
} else {
return this.shared.dataMap[dataType];
}
}
private constructor() {
// empty
}
}
export const tldrs = DataStorage.instance<string, string | false>(
"fetchedItems.json",
);

View File

@ -1,436 +0,0 @@
import { config } from "../../package.json";
import { getString } from "./locale";
function example(
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any) {
try {
ztoolkit.log(`Calling example ${target.name}.${String(propertyKey)}`);
return original.apply(this, args);
} catch (e) {
ztoolkit.log(`Error in example ${target.name}.${String(propertyKey)}`, e);
throw e;
}
};
return descriptor;
}
export class BasicExampleFactory {
@example
static registerNotifier() {
const callback = {
notify: async (
event: string,
type: string,
ids: Array<string>,
extraData: { [key: string]: any }
) => {
if (!addon?.data.alive) {
this.unregisterNotifier(notifierID);
return;
}
addon.hooks.onNotify(event, type, ids, extraData);
},
};
// Register the callback in Zotero as an item observer
const notifierID = Zotero.Notifier.registerObserver(callback, [
"tab",
"item",
"file",
]);
// Unregister callback when the window closes (important to avoid a memory leak)
window.addEventListener(
"unload",
(e: Event) => {
this.unregisterNotifier(notifierID);
},
false
);
}
@example
static exampleNotifierCallback() {
new ztoolkit.ProgressWindow(config.addonName)
.createLine({
text: "Open Tab Detected!",
type: "success",
progress: 100,
})
.show();
}
@example
private static unregisterNotifier(notifierID: string) {
Zotero.Notifier.unregisterObserver(notifierID);
}
@example
static registerPrefs() {
const prefOptions = {
pluginID: config.addonID,
src: rootURI + "chrome/content/preferences.xhtml",
label: getString("prefs.title"),
image: `chrome://${config.addonRef}/content/icons/favicon.png`,
extraDTD: [`chrome://${config.addonRef}/locale/overlay.dtd`],
defaultXUL: true,
};
ztoolkit.PreferencePane.register(prefOptions);
}
}
export class KeyExampleFactory {
@example
static registerShortcuts() {
const keysetId = `${config.addonRef}-keyset`;
const cmdsetId = `${config.addonRef}-cmdset`;
const cmdSmallerId = `${config.addonRef}-cmd-smaller`;
// Register an event key for Alt+L
ztoolkit.Shortcut.register("event", {
id: `${config.addonRef}-key-larger`,
key: "L",
modifiers: "alt",
callback: (keyOptions) => {
addon.hooks.onShortcuts("larger");
},
});
// Register an element key using <key> for Alt+S
ztoolkit.Shortcut.register("element", {
id: `${config.addonRef}-key-smaller`,
key: "S",
modifiers: "alt",
xulData: {
document,
command: cmdSmallerId,
_parentId: keysetId,
_commandOptions: {
id: cmdSmallerId,
document,
_parentId: cmdsetId,
oncommand: "Zotero.AddonTemplate.hooks.onShortcuts('smaller')",
},
},
});
// Here we register an conflict key for Alt+S
// just to show how the confliction check works.
// This is something you should avoid in your plugin.
ztoolkit.Shortcut.register("event", {
id: `${config.addonRef}-key-smaller-conflict`,
key: "S",
modifiers: "alt",
callback: (keyOptions) => {
ztoolkit.getGlobal("alert")("Smaller! This is a conflict key.");
},
});
// Register an event key to check confliction
ztoolkit.Shortcut.register("event", {
id: `${config.addonRef}-key-check-conflict`,
key: "C",
modifiers: "alt",
callback: (keyOptions) => {
addon.hooks.onShortcuts("confliction");
},
});
new ztoolkit.ProgressWindow(config.addonName)
.createLine({
text: "Example Shortcuts: Alt+L/S/C",
type: "success",
})
.show();
}
@example
static exampleShortcutLargerCallback() {
new ztoolkit.ProgressWindow(config.addonName)
.createLine({
text: "Larger!",
type: "default",
})
.show();
}
@example
static exampleShortcutSmallerCallback() {
new ztoolkit.ProgressWindow(config.addonName)
.createLine({
text: "Smaller!",
type: "default",
})
.show();
}
@example
static exampleShortcutConflictionCallback() {
const conflictionGroups = ztoolkit.Shortcut.checkAllKeyConfliction();
new ztoolkit.ProgressWindow("Check Key Confliction")
.createLine({
text: `${conflictionGroups.length} groups of confliction keys found. Details are in the debug output/console.`,
})
.show(-1);
ztoolkit.log(
"Conflictions:",
conflictionGroups,
"All keys:",
ztoolkit.Shortcut.getAll()
);
}
}
export class UIExampleFactory {
@example
static registerStyleSheet() {
const styles = ztoolkit.UI.creatElementsFromJSON(document, {
tag: "link",
directAttributes: {
type: "text/css",
rel: "stylesheet",
href: `chrome://${config.addonRef}/content/zoteroPane.css`,
},
}) as HTMLLinkElement;
document.documentElement.appendChild(styles);
document
.getElementById("zotero-item-pane-content")
?.classList.add("makeItRed");
}
@example
static registerRightClickMenuItem() {
const menuIcon = `chrome://${config.addonRef}/content/icons/favicon@0.5x.png`;
// item menuitem with icon
ztoolkit.Menu.register("item", {
tag: "menuitem",
id: "zotero-itemmenu-addontemplate-test",
label: getString("menuitem.label"),
oncommand: "alert('Hello World! Default Menuitem.')",
icon: menuIcon,
});
}
@example
static registerRightClickMenuPopup() {
ztoolkit.Menu.register(
"item",
{
tag: "menu",
label: getString("menupopup.label"),
subElementOptions: [
{
tag: "menuitem",
label: getString("menuitem.submenulabel"),
oncommand: "alert('Hello World! Sub Menuitem.')",
},
],
},
"before",
document.querySelector(
"#zotero-itemmenu-addontemplate-test"
) as XUL.MenuItem
);
}
@example
static registerWindowMenuWithSeprator() {
ztoolkit.Menu.register("menuFile", {
tag: "menuseparator",
});
// menu->File menuitem
ztoolkit.Menu.register("menuFile", {
tag: "menuitem",
label: getString("menuitem.filemenulabel"),
oncommand: "alert('Hello World! File Menuitem.')",
});
}
@example
static async registerExtraColumn() {
await ztoolkit.ItemTree.register(
"test1",
"text column",
(
field: string,
unformatted: boolean,
includeBaseMapped: boolean,
item: Zotero.Item
) => {
return field + String(item.id);
},
{
iconPath: "chrome://zotero/skin/cross.png",
}
);
}
@example
static async registerExtraColumnWithCustomCell() {
await ztoolkit.ItemTree.register(
"test2",
"custom column",
(
field: string,
unformatted: boolean,
includeBaseMapped: boolean,
item: Zotero.Item
) => {
return String(item.id);
},
{
renderCellHook(index, data, column) {
const span = document.createElementNS(
"http://www.w3.org/1999/xhtml",
"span"
);
span.style.background = "#0dd068";
span.innerText = "⭐" + data;
return span;
},
}
);
}
@example
static async registerCustomCellRenderer() {
await ztoolkit.ItemTree.addRenderCellHook(
"title",
(index: number, data: string, column: any, original: Function) => {
const span = original(index, data, column) as HTMLSpanElement;
span.style.background = "rgb(30, 30, 30)";
span.style.color = "rgb(156, 220, 240)";
return span;
}
);
// @ts-ignore
// This is a private method. Make it public in toolkit.
await ztoolkit.ItemTree.refresh();
}
@example
static registerLibraryTabPanel() {
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: [
{
tag: "h2",
namespace: "html",
directAttributes: {
innerText: "Hello World!",
},
},
{
tag: "div",
namespace: "html",
directAttributes: {
innerText: "This is a library tab.",
},
},
{
tag: "button",
namespace: "html",
directAttributes: {
innerText: "Unregister",
},
listeners: [
{
type: "click",
listener: () => {
ztoolkit.LibraryTabPanel.unregister(tabId);
},
},
],
},
],
});
panel.append(elem);
},
{
targetIndex: 1,
}
);
}
@example
static async registerReaderTabPanel() {
const tabId = await ztoolkit.ReaderTabPanel.register(
getString("tabpanel.reader.tab.label"),
(
panel: XUL.TabPanel | undefined,
deck: XUL.Deck,
win: Window,
reader: _ZoteroReaderInstance
) => {
if (!panel) {
ztoolkit.log(
"This reader do not have right-side bar. Adding reader tab skipped."
);
return;
}
ztoolkit.log(reader);
const elem = ztoolkit.UI.creatElementsFromJSON(win.document, {
tag: "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: [
{
tag: "h2",
namespace: "html",
directAttributes: {
innerText: "Hello World!",
},
},
{
tag: "div",
namespace: "html",
directAttributes: {
innerText: "This is a reader tab.",
},
},
{
tag: "div",
namespace: "html",
directAttributes: {
innerText: `Reader: ${reader._title.slice(0, 20)}`,
},
},
{
tag: "div",
namespace: "html",
directAttributes: {
innerText: `itemID: ${reader.itemID}.`,
},
},
{
tag: "button",
namespace: "html",
directAttributes: {
innerText: "Unregister",
},
listeners: [
{
type: "click",
listener: () => {
ztoolkit.ReaderTabPanel.unregister(tabId);
},
},
],
},
],
});
panel.append(elem);
},
{
targetIndex: 1,
}
);
}
}

View File

@ -1,13 +0,0 @@
import { config } from "../../package.json";
export function initLocale() {
addon.data.locale = {
stringBundle: Components.classes["@mozilla.org/intl/stringbundle;1"]
.getService(Components.interfaces.nsIStringBundleService)
.createBundle(`chrome://${config.addonRef}/locale/addon.properties`),
};
}
export function getString(localString: string): string {
return addon.data.locale?.stringBundle.GetStringFromName(localString);
}

View File

@ -1,121 +1,17 @@
import { config } from "../../package.json";
import { getString } from "../utils/locale";
export function registerPrefsScripts(_window: Window) {
export async function registerPrefsScripts(_window: Window) {
// This function is called when the prefs window is opened
// See addon/chrome/content/preferences.xul onpaneload
if (!addon.data.prefs) {
addon.data.prefs = {
window: _window,
columns: [
{
dataKey: "title",
label: "Title",
fixedWidth: true,
width: 100,
},
{
dataKey: "detail",
label: "Detail",
},
],
rows: [
{
title: "Orange",
detail: "It's juicy",
},
{
title: "Banana",
detail: "It's sweet",
},
{
title: "Apple",
detail: "I mean the fruit APPLE",
},
],
};
} else {
addon.data.prefs.window = _window;
}
updatePrefsUI();
bindPrefEvents();
}
async function updatePrefsUI() {
// You can initialize some UI elements on prefs window
// with addon.data.prefs.window.document
// Or bind some events to the elements
const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer();
const tableHelper = new ztoolkit.VirtualizedTabel(addon.data.prefs?.window!)
.setContainerId(`${config.addonRef}-table-container`)
.setProp({
id: `${config.addonRef}-prefs-table`,
columns: addon.data.prefs?.columns,
showHeader: true,
multiSelect: true,
staticColumns: true,
disableFontSizeScaling: true,
})
.setProp("getRowCount", () => addon.data.prefs?.rows.length || 0)
.setProp(
"getRowData",
(index) =>
addon.data.prefs?.rows[index] || {
title: "no data",
detail: "no data",
}
)
.setProp("onSelectionChange", (selection) => {
new ztoolkit.ProgressWindow(config.addonName)
.createLine({
text: `Selected line: ${addon.data.prefs?.rows
.filter((v, i) => selection.isSelected(i))
.map((row) => row.title)
.join(",")}`,
progress: 100,
})
.show();
})
.setProp("onKeyDown", (event: KeyboardEvent) => {
if (event.key == "Delete" || (Zotero.isMac && event.key == "Backspace")) {
addon.data.prefs!.rows =
addon.data.prefs?.rows.filter(
(v, i) => !tableHelper.treeInstance.selection.isSelected(i)
) || [];
tableHelper.render();
return false;
}
return true;
})
// For find-as-you-type
.setProp(
"getRowString",
(index) => addon.data.prefs?.rows[index].title || ""
)
.render(-1, () => {
renderLock.resolve();
});
await renderLock.promise;
ztoolkit.log("Preference table rendered!", tableHelper);
}
function bindPrefEvents() {
addon.data
.prefs!.window.document.querySelector(
`#zotero-prefpane-${config.addonRef}-enable`
)
?.addEventListener("command", (e) => {
ztoolkit.log(e);
addon.data.prefs!.window.alert(
`Successfully changed to ${(e.target as XUL.Checkbox).checked}!`
);
});
addon.data
.prefs!!.window.document.querySelector(
`#zotero-prefpane-${config.addonRef}-input`
)
?.addEventListener("change", (e) => {
ztoolkit.log(e);
addon.data.prefs!.window.alert(
`Successfully changed to ${(e.target as HTMLInputElement).value}!`
);
});
}
function bindPrefEvents() {}

176
src/modules/tldrFetcher.ts Normal file
View File

@ -0,0 +1,176 @@
import { tldrs } from "./dataStorage";
type SemanticScholarItemInfo = {
title?: string;
abstract?: string;
tldr?: string;
};
export class TLDRFetcher {
private readonly zoteroItem: Zotero.Item;
private readonly title?: string;
private readonly abstract?: string;
constructor(item: Zotero.Item) {
this.zoteroItem = item;
if (item.isRegularItem()) {
this.title = item.getField("title") as string;
this.abstract = item.getField("abstractNote") as string;
}
}
async fetchTLDR() {
if (!this.title || this.title.length <= 0) {
return false;
}
const noteKey = (await tldrs.getAsync())[this.zoteroItem.key];
try {
const infos = await this.fetchRelevanceItemInfos(this.title);
for (const info of infos) {
let match = false;
if (info.title && this.title && this.checkLCS(info.title, this.title)) {
match = true;
} else if (
info.abstract &&
this.abstract &&
this.checkLCS(info.abstract, this.abstract)
) {
match = true;
}
if (match && info.tldr) {
let note = new Zotero.Item("note");
if (noteKey) {
const obj = Zotero.Items.getByLibraryAndKey(
this.zoteroItem.libraryID,
noteKey,
);
if (
obj &&
obj instanceof Zotero.Item &&
this.zoteroItem.getNotes().includes(obj.id)
) {
note = obj;
}
}
note.setNote(`<p>TL;DR</p>\n<p>${info.tldr}</p>`);
note.parentID = this.zoteroItem.id;
await note.saveTx();
await tldrs.modify((data: any) => {
data[this.zoteroItem.key] = note.key;
return data;
});
return true;
}
}
await tldrs.modify((data: any) => {
data[this.zoteroItem.key] = false;
return data;
});
} catch (error) {
Zotero.log(`post semantic scholar request error: ${error}`);
}
}
private async fetchRelevanceItemInfos(
title: string,
): Promise<SemanticScholarItemInfo[]> {
const semanticScholarURL = "https://www.semanticscholar.org/api/1/search";
const params = {
queryString: title,
page: 1,
pageSize: 10,
sort: "relevance",
authors: [],
coAuthors: [],
venues: [],
performTitleMatch: true,
requireViewablePdf: false,
includeTldrs: true,
};
const resp = await Zotero.HTTP.request("POST", semanticScholarURL, {
headers: { "Content-Type": "application/json" },
body: JSON.stringify(params),
});
if (resp.status === 200) {
const results = JSON.parse(resp.response).results;
return results.map((item: any) => {
const result = {
title: item.title.text,
abstract: item.paperAbstract.text,
tldr: undefined,
};
if (item.tldr) {
result.tldr = item.tldr.text;
}
return result;
});
}
return [];
}
private checkLCS(pattern: string, content: string): boolean {
const LCS = StringMatchUtils.longestCommonSubsequence(pattern, content);
return LCS.length >= Math.max(pattern.length, content.length) * 0.9;
}
}
class StringMatchUtils {
static longestCommonSubsequence(text1: string, text2: string): string {
const m = text1.length;
const n = text2.length;
const dp: number[][] = new Array(m + 1);
for (let i = 0; i <= m; i++) {
dp[i] = new Array(n + 1).fill(0);
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
let i = m,
j = n;
const lcs: string[] = [];
while (i > 0 && j > 0) {
if (text1[i - 1] === text2[j - 1]) {
lcs.unshift(text1[i - 1]);
i--;
j--;
} else if (dp[i - 1][j] > dp[i][j - 1]) {
i--;
} else {
j--;
}
}
return lcs.join("");
}
// static minWindow(s: string, t: string): [number, number] | null {
// const m = s.length, n = t.length
// let start = -1, minLen = Number.MAX_SAFE_INTEGER, i = 0, j = 0, end;
// while (i < m) {
// if (s[i] == t[j]) {
// if (++j == n) {
// end = i + 1;
// while (--j >= 0) {
// while (s[i--] != t[j]);
// }
// ++i; ++j;
// if (end - i < minLen) {
// minLen = end - i;
// start = i;
// }
// }
// }
// ++i;
// }
// return start == -1 ? null : [start, minLen];
// }
}

79
src/utils/locale.ts Normal file
View File

@ -0,0 +1,79 @@
import { config } from "../../package.json";
export { initLocale, getString };
/**
* Initialize locale data
*/
function initLocale() {
const l10n = new (
typeof Localization === "undefined"
? ztoolkit.getGlobal("Localization")
: Localization
)([`${config.addonRef}-addon.ftl`], true);
addon.data.locale = {
current: l10n,
};
}
/**
* Get locale string, see https://firefox-source-docs.mozilla.org/l10n/fluent/tutorial.html#fluent-translation-list-ftl
* @param localString ftl key
* @param options.branch branch name
* @param options.args args
* @example
* ```ftl
* # addon.ftl
* addon-static-example = This is default branch!
* .branch-example = This is a branch under addon-static-example!
* addon-dynamic-example =
{ $count ->
[one] I have { $count } apple
*[other] I have { $count } apples
}
* ```
* ```js
* getString("addon-static-example"); // This is default branch!
* getString("addon-static-example", { branch: "branch-example" }); // This is a branch under addon-static-example!
* getString("addon-dynamic-example", { args: { count: 1 } }); // I have 1 apple
* getString("addon-dynamic-example", { args: { count: 2 } }); // I have 2 apples
* ```
*/
function getString(localString: string): string;
function getString(localString: string, branch: string): string;
function getString(
localeString: string,
options: { branch?: string | undefined; args?: Record<string, unknown> },
): string;
function getString(...inputs: any[]) {
if (inputs.length === 1) {
return _getString(inputs[0]);
} else if (inputs.length === 2) {
if (typeof inputs[1] === "string") {
return _getString(inputs[0], { branch: inputs[1] });
} else {
return _getString(inputs[0], inputs[1]);
}
} else {
throw new Error("Invalid arguments");
}
}
function _getString(
localeString: string,
options: { branch?: string | undefined; args?: Record<string, unknown> } = {},
): string {
const localStringWithPrefix = `${config.addonRef}-${localeString}`;
const { branch, args } = options;
const pattern = addon.data.locale?.current.formatMessagesSync([
{ id: localStringWithPrefix, args },
])[0];
if (!pattern) {
return localStringWithPrefix;
}
if (branch && pattern.attributes) {
return pattern.attributes[branch] || localStringWithPrefix;
} else {
return pattern.value || localStringWithPrefix;
}
}

29
src/utils/prefs.ts Normal file
View File

@ -0,0 +1,29 @@
import { config } from "../../package.json";
/**
* Get preference value.
* Wrapper of `Zotero.Prefs.get`.
* @param key
*/
export function getPref(key: string) {
return Zotero.Prefs.get(`${config.prefsPrefix}.${key}`, true);
}
/**
* Set preference value.
* Wrapper of `Zotero.Prefs.set`.
* @param key
* @param value
*/
export function setPref(key: string, value: string | number | boolean) {
return Zotero.Prefs.set(`${config.prefsPrefix}.${key}`, value, true);
}
/**
* Clear preference value.
* Wrapper of `Zotero.Prefs.clear`.
* @param key
*/
export function clearPref(key: string) {
return Zotero.Prefs.clear(`${config.prefsPrefix}.${key}`, true);
}

49
src/utils/wait.ts Normal file
View File

@ -0,0 +1,49 @@
/**
* Wait until the condition is `true` or timeout.
* The callback is triggered if condition returns `true`.
* @param condition
* @param callback
* @param interval
* @param timeout
*/
export function waitUntil(
condition: () => boolean,
callback: () => void,
interval = 100,
timeout = 10000,
) {
const start = Date.now();
const intervalId = ztoolkit.getGlobal("setInterval")(() => {
if (condition()) {
ztoolkit.getGlobal("clearInterval")(intervalId);
callback();
} else if (Date.now() - start > timeout) {
ztoolkit.getGlobal("clearInterval")(intervalId);
}
}, interval);
}
/**
* Wait async until the condition is `true` or timeout.
* @param condition
* @param interval
* @param timeout
*/
export function waitUtilAsync(
condition: () => boolean,
interval = 100,
timeout = 10000,
) {
return new Promise<void>((resolve, reject) => {
const start = Date.now();
const intervalId = ztoolkit.getGlobal("setInterval")(() => {
if (condition()) {
ztoolkit.getGlobal("clearInterval")(intervalId);
resolve();
} else if (Date.now() - start > timeout) {
ztoolkit.getGlobal("clearInterval")(intervalId);
reject();
}
}, interval);
});
}

10
src/utils/window.ts Normal file
View File

@ -0,0 +1,10 @@
export { isWindowAlive };
/**
* Check if the window is alive.
* Useful to prevent opening duplicate windows.
* @param win
*/
function isWindowAlive(win?: Window) {
return win && !Components.utils.isDeadWrapper(win) && !win.closed;
}

49
src/utils/ztoolkit.ts Normal file
View File

@ -0,0 +1,49 @@
import ZoteroToolkit from "zotero-plugin-toolkit";
import { config } from "../../package.json";
export { createZToolkit };
function createZToolkit() {
const _ztoolkit = new ZoteroToolkit();
/**
* Alternatively, import toolkit modules you use to minify the plugin size.
* You can add the modules under the `MyToolkit` class below and uncomment the following line.
*/
// const _ztoolkit = new MyToolkit();
initZToolkit(_ztoolkit);
return _ztoolkit;
}
function initZToolkit(_ztoolkit: ReturnType<typeof createZToolkit>) {
const env = __env__;
_ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`;
_ztoolkit.basicOptions.log.disableConsole = env === "production";
_ztoolkit.UI.basicOptions.ui.enableElementJSONLog = __env__ === "development";
_ztoolkit.UI.basicOptions.ui.enableElementDOMLog = __env__ === "development";
_ztoolkit.basicOptions.debug.disableDebugBridgePassword =
__env__ === "development";
_ztoolkit.basicOptions.api.pluginID = config.addonID;
_ztoolkit.ProgressWindow.setIconURI(
"default",
`chrome://${config.addonRef}/content/icons/favicon.png`,
);
}
import { BasicTool, unregister } from "zotero-plugin-toolkit/dist/basic";
import { UITool } from "zotero-plugin-toolkit/dist/tools/ui";
import { PreferencePaneManager } from "zotero-plugin-toolkit/dist/managers/preferencePane";
class MyToolkit extends BasicTool {
UI: UITool;
PreferencePane: PreferencePaneManager;
constructor() {
super();
this.UI = new UITool(this);
this.PreferencePane = new PreferencePaneManager(this);
}
unregisterAll() {
unregister(this);
}
}

View File

@ -4,15 +4,9 @@
"module": "commonjs",
"target": "ES2016",
"resolveJsonModule": true,
"strict": true
"skipLibCheck": true,
"strict": true,
},
"include": [
"src",
"typing",
"node_modules/zotero-types"
],
"exclude": [
"builds",
"addon"
]
}
"include": ["src", "typings", "node_modules/zotero-types"],
"exclude": ["build", "addon"],
}

View File

@ -1,18 +1,24 @@
declare const _globalThis: {
[key: string]: any;
Zotero: _ZoteroConstructable;
ZoteroPane: _ZoteroPaneConstructable;
Zotero: _ZoteroTypes.Zotero;
ZoteroPane: _ZoteroTypes.ZoteroPane;
Zotero_Tabs: typeof Zotero_Tabs;
window: Window;
document: Document;
ztoolkit: typeof ztoolkit;
ztoolkit: ZToolkit;
addon: typeof addon;
};
declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit;
declare type ZToolkit = ReturnType<
typeof import("../src/utils/ztoolkit").createZToolkit
>;
declare const ztoolkit: ZToolkit;
declare const rootURI: string;
declare const addon: import("../src/addon").default;
declare const __env__: "production" | "development";
declare class Localization {}

View File

@ -1,26 +0,0 @@
{
"addons": {
"__addonID__": {
"updates": [
{
"version": "__buildVersion__",
"update_link": "__releasepage__",
"applications": {
"gecko": {
"strict_min_version": "60.0"
}
}
},
{
"version": "__buildVersion__",
"update_link": "__releasepage__",
"applications": {
"zotero": {
"strict_min_version": "6.999"
}
}
}
]
}
}
}

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<rdf:Description rdf:about="urn:mozilla:extension:__addonID__">
<em:updates>
<rdf:Seq>
<rdf:li>
<rdf:Description>
<em:version>__buildVersion__</em:version>
<em:targetApplication>
<rdf:Description>
<em:id>zotero@chnm.gmu.edu</em:id>
<em:minVersion>6.999</em:minVersion>
<em:maxVersion>*</em:maxVersion>
<em:updateLink>__releasepage__</em:updateLink>
</rdf:Description>
</em:targetApplication>
<em:targetApplication>
<rdf:Description>
<em:id>juris-m@juris-m.github.io</em:id>
<em:minVersion>6.999</em:minVersion>
<em:maxVersion>*</em:maxVersion>
<em:updateLink>__releasepage__</em:updateLink>
</rdf:Description>
</em:targetApplication>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</em:updates>
</rdf:Description>
</rdf:RDF>

View File

@ -1,19 +1,10 @@
{
"addons": {
"addontemplate@euclpts.com": {
"zoterotldr@syt.com": {
"updates": [
{
"version": "0.1.1",
"update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
"applications": {
"gecko": {
"strict_min_version": "60.0"
}
}
},
{
"version": "0.1.1",
"update_link": "https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi",
"version": "1.0.7",
"update_link": "undefined/latest/download/zotero-tldr.xpi",
"applications": {
"zotero": {
"strict_min_version": "6.999"

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<rdf:Description rdf:about="urn:mozilla:extension:addontemplate@euclpts.com">
<em:updates>
<rdf:Seq>
<rdf:li>
<rdf:Description>
<em:version>0.1.1</em:version>
<em:targetApplication>
<rdf:Description>
<em:id>zotero@chnm.gmu.edu</em:id>
<em:minVersion>6.999</em:minVersion>
<em:maxVersion>*</em:maxVersion>
<em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
</rdf:Description>
</em:targetApplication>
<em:targetApplication>
<rdf:Description>
<em:id>juris-m@juris-m.github.io</em:id>
<em:minVersion>6.999</em:minVersion>
<em:maxVersion>*</em:maxVersion>
<em:updateLink>https://github.com/windingwind/zotero-addon-template/releases/latest/download/zotero-addon-template.xpi</em:updateLink>
</rdf:Description>
</em:targetApplication>
</rdf:Description>
</rdf:li>
</rdf:Seq>
</em:updates>
</rdf:Description>
</rdf:RDF>