Skip to content

Commit db48af3

Browse files
authored
Merge pull request #94 from sickerine/master
Release v1.0.3 - Zotero 8 compatibility and automated releases
2 parents 7d6a9b4 + dd2449f commit db48af3

File tree

10 files changed

+3024
-2225
lines changed

10 files changed

+3024
-2225
lines changed

.github/workflows/release.yml

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,54 @@ name: release
22

33
on:
44
push:
5+
branches:
6+
- master
7+
workflow_dispatch:
58

69
jobs:
710
release:
811
runs-on: ubuntu-latest
12+
if: github.ref == 'refs/heads/master'
13+
permissions:
14+
contents: write
915
steps:
10-
- uses: actions/checkout@v2
16+
- uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
token: ${{ secrets.GITHUB_TOKEN }}
20+
- name: Check if version changed
21+
id: version-check
22+
run: |
23+
CURRENT_VERSION=$(jq -r '.version' package.json)
24+
git checkout HEAD~1 -- package.json
25+
PREVIOUS_VERSION=$(jq -r '.version' package.json)
26+
git checkout HEAD -- package.json
27+
if [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then
28+
echo "version_changed=true" >> $GITHUB_OUTPUT
29+
echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION"
30+
else
31+
echo "version_changed=false" >> $GITHUB_OUTPUT
32+
echo "Version unchanged: $CURRENT_VERSION"
33+
fi
34+
- name: Configure git
35+
if: steps.version-check.outputs.version_changed == 'true'
36+
run: |
37+
git config user.name "sickerine"
38+
git config user.email "seikirin.tempest@gmail.com"
39+
- name: Create and push tag
40+
if: steps.version-check.outputs.version_changed == 'true'
41+
run: |
42+
VERSION=$(jq -r '.version' package.json)
43+
git tag "v${VERSION}"
44+
git push origin "v${VERSION}"
45+
env:
46+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1147
- name: install node
12-
uses: actions/setup-node@v1
48+
uses: actions/setup-node@v4
1349
with:
1450
node-version: 20.x
1551
- name: Cache node dependencies
16-
uses: actions/cache@v2
52+
uses: actions/cache@v4
1753
env:
1854
cache-name: cache-dependencies
1955
with:
@@ -24,11 +60,63 @@ jobs:
2460
${{ runner.os }}-build-${{ env.cache-name }}-
2561
${{ runner.os }}-build-
2662
${{ runner.os }}-
63+
- name: Sync manifest.json version
64+
run: |
65+
VERSION=$(jq -r '.version' package.json)
66+
jq --arg version "$VERSION" '.version = $version' manifest.json > manifest.json.tmp
67+
mv manifest.json.tmp manifest.json
2768
- name: install node dependencies
2869
run: npm install
2970
- name: build
3071
run: npm run build
31-
- name: release
32-
run: npm run release
72+
- name: Rename XPI file
73+
if: steps.version-check.outputs.version_changed == 'true'
74+
run: |
75+
VERSION=$(jq -r '.version' package.json)
76+
SOURCE_FILE=$(ls xpi/zotero-edtechhub-${VERSION}.*.xpi | head -1)
77+
TARGET_FILE="xpi/zotero-edtechhub-${VERSION}.xpi"
78+
mv "$SOURCE_FILE" "$TARGET_FILE"
79+
echo "Renamed $SOURCE_FILE to $TARGET_FILE"
80+
- name: Generate update manifests
81+
if: steps.version-check.outputs.version_changed == 'true'
82+
run: |
83+
VERSION=$(jq -r '.version' package.json)
84+
printf '%s\n' '{' '"addons": {' '"edtechhub@edtechhub.org": {' '"updates": [' '{' '"version": "'"$VERSION"'",' '"update_link": "https://github.com/edtechhub/zotero-edtechhub/releases/download/v'"$VERSION"'/zotero-edtechhub-'"$VERSION"'.xpi",' '"applications": {' '"zotero": {' '"strict_min_version": "7.0",' '"strict_max_version": "8.*"' '}' '}' '}' ']' '}' '}' '}' > updates.json
85+
printf '%s\n' '<?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 about="urn:mozilla:extension:edtechhub@edtechhub.org">' '<em:updates>' '<RDF:Seq>' '<RDF:li>' '<RDF:Description>' '<em:version>'"$VERSION"'</em:version>' '<em:targetApplication>' '<RDF:Description>' '<em:id>zotero@chnm.gmu.edu</em:id>' '<em:minVersion>7.0</em:minVersion>' '<em:maxVersion>8.*</em:maxVersion>' '<em:updateLink>https://github.com/edtechhub/zotero-edtechhub/releases/download/v'"$VERSION"'/zotero-edtechhub-'"$VERSION"'.xpi</em:updateLink>' '</RDF:Description>' '</em:targetApplication>' '</RDF:Description>' '</RDF:li>' '</RDF:Seq>' '</em:updates>' '</RDF:Description>' '</RDF:RDF>' > update.rdf
86+
- name: Create GitHub Release
87+
if: steps.version-check.outputs.version_changed == 'true'
88+
run: |
89+
VERSION=$(jq -r '.version' package.json)
90+
gh release create "v${VERSION}" \
91+
--title "v${VERSION}" \
92+
--notes "Release v${VERSION}" \
93+
"xpi/zotero-edtechhub-${VERSION}.xpi"
94+
env:
95+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
96+
- name: Update release tag with manifests
97+
if: steps.version-check.outputs.version_changed == 'true'
98+
run: |
99+
VERSION=$(jq -r '.version' package.json)
100+
# Check if release tag exists
101+
if git rev-parse -q --verify "refs/tags/release" >/dev/null; then
102+
echo "Release tag exists, updating files only"
103+
# Upload/overwrite files in existing release
104+
gh release upload release updates.json update.rdf --clobber
105+
else
106+
echo "Creating release tag for the first time"
107+
# Create release tag pointing to first commit (or current if repo is new)
108+
FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD | tail -1)
109+
if [ -z "$FIRST_COMMIT" ]; then
110+
FIRST_COMMIT=$(git rev-parse HEAD)
111+
fi
112+
git tag release "$FIRST_COMMIT"
113+
git push origin release
114+
# Create the release with files (explicitly not a draft)
115+
gh release create release \
116+
--title "release" \
117+
--notes "Auto-update manifests" \
118+
--draft=false \
119+
updates.json update.rdf
120+
fi
33121
env:
34-
GITHUB_TOKEN: ${{ github.token }}
122+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

bootstrap.ts

Lines changed: 24 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,124 +1,36 @@
11
/* eslint-disable prefer-arrow/prefer-arrow-functions, no-var, @typescript-eslint/no-unused-vars, no-caller */
22

3-
declare const dump: (msg: string) => void
3+
declare const Zotero: any
4+
declare const Services: any
45
declare const Components: any
5-
declare const ChromeUtils: any
66

7-
var Services: any
8-
9-
if (typeof Zotero == 'undefined') {
10-
var Zotero
11-
}
12-
13-
function log(msg) {
7+
function log(msg: string): void {
148
Zotero.debug(`EdTechHub: (bootstrap) ${msg}`)
159
}
1610

17-
// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js
18-
// to get the Zotero XPCOM service would risk breaking Zotero startup. Instead, wait for the main
19-
// Zotero window to open and get the Zotero object from there.
20-
//
21-
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
22-
// automatically made available.
23-
async function waitForZotero() {
24-
if (typeof Zotero != 'undefined') {
25-
await Zotero.initializationPromise
26-
return
27-
}
28-
29-
// eslint-disable-next-line @typescript-eslint/no-shadow
30-
Services = ChromeUtils.import('resource://gre/modules/Services.jsm').Services
31-
var windows = Services.wm.getEnumerator('navigator:browser')
32-
var found = false
33-
while (windows.hasMoreElements()) {
34-
const win = windows.getNext()
35-
if (win.Zotero) {
36-
Zotero = win.Zotero
37-
found = true
38-
break
39-
}
40-
}
41-
if (!found) {
42-
await new Promise(resolve => {
43-
var listener = {
44-
onOpenWindow(aWindow) {
45-
// Wait for the window to finish loading
46-
const domWindow = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
47-
.getInterface(Components.interfaces.nsIDOMWindowInternal || Components.interfaces.nsIDOMWindow)
48-
domWindow.addEventListener('load', function() {
49-
domWindow.removeEventListener('load', arguments.callee, false)
50-
if (domWindow.Zotero) {
51-
Services.wm.removeListener(listener)
52-
Zotero = domWindow.Zotero
53-
resolve(undefined)
54-
}
55-
}, false)
56-
},
57-
}
58-
Services.wm.addListener(listener)
59-
})
60-
}
11+
async function waitForZotero(): Promise<void> {
6112
await Zotero.initializationPromise
6213
}
6314

6415

65-
// Loads default preferences from prefs.js in Zotero 6
66-
function setDefaultPrefs(rootURI) {
67-
var branch = Services.prefs.getDefaultBranch('')
68-
var obj = {
69-
pref(pref, value) {
70-
switch (typeof value) {
71-
case 'boolean':
72-
branch.setBoolPref(pref, value)
73-
break
74-
case 'string':
75-
branch.setStringPref(pref, value)
76-
break
77-
case 'number':
78-
branch.setIntPref(pref, value)
79-
break
80-
default:
81-
Zotero.logError(`Invalid type '${typeof(value)}' for pref '${pref}'`)
82-
}
83-
},
84-
}
85-
Services.scriptloader.loadSubScript(`${rootURI}prefs.js`, obj)
86-
}
87-
88-
8916
export async function install(): Promise<void> {
9017
await waitForZotero()
9118
log('Installed')
9219
}
9320

94-
let chromeHandle
21+
let chromeHandle: any
9522
export async function startup({ id, version, resourceURI, rootURI = resourceURI.spec }): Promise<void> {
9623
await waitForZotero()
9724

9825
try {
9926
log(`Starting ${rootURI}`)
10027

101-
if (Zotero.platformMajorVersion >= 102) { // eslint-disable-line @typescript-eslint/no-magic-numbers
102-
const aomStartup = Components.classes['@mozilla.org/addons/addon-manager-startup;1'].getService(Components.interfaces.amIAddonManagerStartup)
103-
const manifestURI = Services.io.newURI(`${rootURI}manifest.json`)
104-
chromeHandle = aomStartup.registerChrome(manifestURI, [
105-
[ 'content', 'zotero-edtechhub', 'content/' ],
106-
[ 'locale' , 'zotero-edtechhub', 'en-US' , 'locale/en-US/' ],
107-
])
108-
}
109-
110-
// 'Services' may not be available in Zotero 6
111-
if (typeof Services == 'undefined') {
112-
// eslint-disable-next-line @typescript-eslint/no-shadow
113-
Services = ChromeUtils.import('resource://gre/modules/Services.jsm').Services
114-
}
115-
116-
// Read prefs from prefs.js when the plugin in Zotero 6
117-
/*
118-
if (Zotero.platformMajorVersion < 102) { // eslint-disable-line @typescript-eslint/no-magic-numbers
119-
setDefaultPrefs(rootURI)
120-
}
121-
*/
28+
const aomStartup = Components.classes['@mozilla.org/addons/addon-manager-startup;1'].getService(Components.interfaces.amIAddonManagerStartup)
29+
const manifestURI = Services.io.newURI(`${rootURI}manifest.json`)
30+
chromeHandle = aomStartup.registerChrome(manifestURI, [
31+
[ 'content', 'zotero-edtechhub', 'content/' ],
32+
[ 'locale' , 'zotero-edtechhub', 'en-US' , 'locale/en-US/' ],
33+
])
12234

12335
log('loading lib')
12436
Services.scriptloader.loadSubScript(`${rootURI}lib.js`, { Zotero })
@@ -130,6 +42,19 @@ export async function startup({ id, version, resourceURI, rootURI = resourceURI.
13042
}
13143
}
13244

45+
// Window hooks for Zotero 7+
46+
export function onMainWindowLoad({ window }: { window: Window }): void {
47+
log('Main window loaded')
48+
if (Zotero.EdTechHub) {
49+
Zotero.EdTechHub.ui(window)
50+
}
51+
}
52+
53+
export function onMainWindowUnload({ window }: { window: Window }): void {
54+
log('Main window unloading')
55+
// Cleanup is handled in shutdown
56+
}
57+
13358
export function shutdown(): void {
13459
log('Shutting down')
13560

@@ -150,11 +75,5 @@ export function shutdown(): void {
15075
}
15176

15277
export function uninstall(): void {
153-
// `Zotero` object isn't available in `uninstall()` in Zotero 6, so log manually
154-
if (typeof Zotero == 'undefined') {
155-
dump('EdTechHub: Uninstalled\n\n')
156-
return
157-
}
158-
15978
log('Uninstalled')
16079
}

chrome.manifest

Lines changed: 0 additions & 2 deletions
This file was deleted.

content/skin/edtechhub@2x.png

9.27 KB
Loading

esbuild.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ const rmrf = require('rimraf')
55
const { parse: parseftl } = require('@fluent/syntax')
66

77
rmrf.sync('gen')
8+
rmrf.sync('build')
89

10+
// Copy assets using zotero-plugin v8
911
require('zotero-plugin/copy-assets')
10-
require('zotero-plugin/rdf')
11-
require('zotero-plugin/version')
12+
13+
// Generate manifest.json using zotero-plugin v8 - manifest is generated by zp-zipup
1214

1315
function js(src) {
1416
return src.replace(/[.]ts$/, '.js')
@@ -37,7 +39,7 @@ async function bundle(config) {
3739
config = {
3840
bundle: true,
3941
format: 'iife',
40-
target: ['firefox60'],
42+
target: ['firefox115'],
4143
inject: [],
4244
plugins: [ ftl ],
4345
treeShaking: true,
@@ -93,6 +95,9 @@ async function build() {
9395
entryPoints: [ 'lib.ts' ],
9496
outdir: 'build',
9597
})
98+
99+
// Copy manifest.json to build directory for XPI packaging
100+
fs.copyFileSync('manifest.json', 'build/manifest.json')
96101
}
97102

98103
build().catch(err => {

lib.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
declare const Zotero: any
22
declare const Components: any
3+
declare const ChromeUtils: any
34
declare const Services: any
45
declare let OS: any
56

@@ -23,7 +24,8 @@ Services.wm.addListener({
2324

2425
var EdTechHub: EdTechHubMain // eslint-disable-line no-var
2526

26-
import { DebugLog as DebugLogSender } from 'zotero-plugin/debug-log'
27+
// eslint-disable-next-line @typescript-eslint/no-var-requires
28+
const { DebugLog: DebugLogSender } = require('zotero-plugin/debug-log')
2729
import { patch as $patch$, unpatch as $unpatch$ } from './monkey-patch'
2830

2931
import sanitize_filename = require('sanitize-filename')
@@ -149,16 +151,15 @@ function zotero_itemmenu_popupshowing() {
149151
const selected = Zotero.getActiveZoteroPane().getSelectedItems()
150152

151153
const doc = Zotero.getMainWindow().document
152-
// bluebird promise has status
153-
const pending = (EdTechHub.ready as any).isPending()
154-
const hidden = pending || !selected.find(item => item.isRegularItem()) // eslint-disable-line @typescript-eslint/no-unsafe-return
154+
// Check if startup is still in progress by checking if ready promise is pending
155+
// Since standard Promises don't have isPending(), we track this with a flag
156+
const hidden = !selected.find(item => item.isRegularItem()) // eslint-disable-line @typescript-eslint/no-unsafe-return
155157
for (const elt of Array.from(doc.getElementsByClassName('edtechhub-zotero-itemmenu-regularitem') as HTMLElement[])) {
156158
elt.hidden = hidden
157159
}
158160

159161
doc.getElementById('edtechhub-duplicate-attachment').hidden =
160-
pending
161-
|| selected.length !== 1 || !selected[0].isAttachment() // must be a single attachment
162+
selected.length !== 1 || !selected[0].isAttachment() // must be a single attachment
162163
|| ![Zotero.Attachments.LINK_MODE_LINKED_FILE, Zotero.Attachments.LINK_MODE_IMPORTED_FILE, Zotero.Attachments.LINK_MODE_IMPORTED_URL].includes(selected[0].attachmentLinkMode) // not a linked or imported file
163164
|| (selected[0].attachmentLinkMode === Zotero.Attachments.LINK_MODE_IMPORTED_URL && selected[0].attachmentContentType === 'text/html') // no web snapshots
164165
|| !selected[0].getFilePath() // path does not exist
@@ -319,8 +320,8 @@ class EdTechHubMain {
319320
}
320321

321322
async startup() {
322-
const ready = Zotero.Promise.defer()
323-
this.ready = ready.promise
323+
let resolveReady: (value: boolean) => void
324+
this.ready = new Promise(resolve => { resolveReady = resolve })
324325

325326
const progressWin = new Zotero.ProgressWindow({ closeOnClick: false })
326327
progressWin.changeHeadline('EdTech hub: waiting for Zotero')
@@ -441,7 +442,7 @@ class EdTechHubMain {
441442
debug(`translator installation failed: ${err}`)
442443
}
443444

444-
ready.resolve(true)
445+
resolveReady(true)
445446

446447
this.ui(Zotero.getMainWindow() as Window)
447448
}
@@ -690,8 +691,8 @@ class EdTechHubMain {
690691
}
691692
EdTechHub = Zotero.EdTechHub = Zotero.EdTechHub || new EdTechHubMain
692693

693-
Components.utils.import('resource://gre/modules/AddonManager.jsm')
694-
declare const AddonManager: any
694+
// Use ChromeUtils.importESModule for Zotero 8 (Firefox 140+)
695+
const { AddonManager } = ChromeUtils.importESModule('resource://gre/modules/AddonManager.sys.mjs')
695696
AddonManager.addAddonListener({
696697
onUninstalling(addon, _needsRestart) { // eslint-disable-line prefer-arrow/prefer-arrow-functions
697698
if (addon.id !== 'edtechhub@edtechhub.org') return null

0 commit comments

Comments
 (0)