update: plugin template

fix: reopen window bug
add: eslint
This commit is contained in:
windingwind 2023-07-26 23:18:30 +08:00
parent 5a4bdc72bf
commit 15d8cce685
73 changed files with 1178 additions and 924 deletions

41
.eslintrc.json Normal file
View File

@ -0,0 +1,41 @@
{
"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/**",
"**/dist/**",
"**/node_modules/**",
"**/scripts/**",
"**/*.js",
"**/*.bak"
]
}

1
.gitattributes vendored
View File

@ -1 +1,2 @@
addon/chrome/content/lib/** linguist-vendored addon/chrome/content/lib/** linguist-vendored
* text=auto eol=lf

2
.github/FUNDING.yml vendored
View File

@ -10,4 +10,4 @@ liberapay: windingwind
issuehunt: # Replace with a single IssueHunt username issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ["https://paypal.me/windingwind?country.x=C2&locale.x=zh_XC", ] custom: ["https://paypal.me/windingwind?country.x=C2&locale.x=zh_XC"]

View File

@ -3,8 +3,7 @@ name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: "[Bug]" title: "[Bug]"
labels: bug labels: bug
assignees: '' assignees: ""
--- ---
**Describe the bug** **Describe the bug**
@ -12,6 +11,7 @@ A clear and concise description of what the bug is.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
3. Scroll down to '....' 3. Scroll down to '....'
@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):** **Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari] - OS: [e.g. iOS]
- Version [e.g. 22] - Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):** **Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1] - Device: [e.g. iPhone6]
- Browser [e.g. stock browser, safari] - OS: [e.g. iOS8.1]
- Version [e.g. 22] - Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

View File

@ -3,8 +3,7 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "[Feature]" title: "[Feature]"
labels: enhancement labels: enhancement
assignees: '' assignees: ""
--- ---
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**

View File

@ -3,8 +3,7 @@ name: Note Template Issue
about: Need help with your custom note template about: Need help with your custom note template
title: "[Note Template]" title: "[Note Template]"
labels: help wanted labels: help wanted
assignees: '' assignees: ""
--- ---
## Template Type ## Template Type

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
**/builds **/build
node_modules node_modules
package-lock.json package-lock.json
zotero-cmd.json zotero-cmd.json

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"tabWidth": 2
}

View File

@ -4,7 +4,7 @@
}, },
"github": { "github": {
"release": true, "release": true,
"assets": ["builds/*.xpi"] "assets": ["build/*.xpi"]
}, },
"hooks": { "hooks": {
"after:bump": "npm run build", "after:bump": "npm run build",

View File

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

View File

@ -86,10 +86,10 @@ and:
## 👋 Install ## 👋 Install
- Download the latest release (.xpi file) from the [Releases Page](https://github.com/windingwind/zotero-better-notes/releases) - Download the latest release (.xpi file) from the [Releases Page](https://github.com/windingwind/zotero-better-notes/releases)
<details style="text-indent: 2em"> <details style="text-indent: 2em">
<summary>More Versions</summary> <summary>More Versions</summary>
- [Latest Stable](https://github.com/windingwind/zotero-better-notes/releases/latest) - [Latest Stable](https://github.com/windingwind/zotero-better-notes/releases/latest)
- [v1.0.4](https://github.com/windingwind/zotero-better-notes/releases/tag/1.0.4) (last for Zotero 6) - [v1.0.4](https://github.com/windingwind/zotero-better-notes/releases/tag/1.0.4) (last for Zotero 6)
- [v0.8.9](https://github.com/windingwind/zotero-better-notes/releases/tag/0.8.9) (last with auto-insert, tag-insert, math-ocr, for Zotero 6) - [v0.8.9](https://github.com/windingwind/zotero-better-notes/releases/tag/0.8.9) (last with auto-insert, tag-insert, math-ocr, for Zotero 6)
@ -98,6 +98,7 @@ and:
</details> </details>
_Note_: If you're using Firefox as your browser, right-click the `.xpi` and select "Save As.." _Note_: If you're using Firefox as your browser, right-click the `.xpi` and select "Save As.."
- In Zotero click `Tools` in the top menu bar and then click `Addons` - In Zotero click `Tools` in the top menu bar and then click `Addons`
- Go to the Extensions page and then click the gear icon in the top right. - Go to the Extensions page and then click the gear icon in the top right.
- Select `Install Add-on from file`. - Select `Install Add-on from file`.

157
addon/bootstrap.js vendored
View File

@ -11,6 +11,8 @@ if (typeof Zotero == "undefined") {
var chromeHandle; var chromeHandle;
var windowListener;
// In Zotero 6, bootstrap methods are called before Zotero is initialized, and using include.js // 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 // 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. // Zotero window to open and get the Zotero object from there.
@ -18,73 +20,85 @@ var chromeHandle;
// In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is // In Zotero 7, bootstrap methods are not called until Zotero is initialized, and the 'Zotero' is
// automatically made available. // automatically made available.
async function waitForZotero() { async function waitForZotero() {
if (typeof Zotero != "undefined") { await new Promise(async (resolve) => {
await Zotero.initializationPromise; if (typeof Zotero != "undefined") {
} resolve();
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) { const { Services } = ChromeUtils.import(
await new Promise((resolve) => { "resource://gre/modules/Services.jsm",
var listener = { );
onOpenWindow: function (aWindow) { const windows = Services.wm.getEnumerator("navigator:browser");
// Wait for the window to finish loading let found = false;
let domWindow = aWindow while (windows.hasMoreElements()) {
.QueryInterface(Ci.nsIInterfaceRequestor) let win = windows.getNext();
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow); if (win.Zotero) {
domWindow.addEventListener( Zotero = win.Zotero;
"load", found = true;
function () { resolve();
domWindow.removeEventListener("load", arguments.callee, false); break;
if (domWindow.Zotero) { }
Services.wm.removeListener(listener); }
Zotero = domWindow.Zotero; windowListener = {
resolve(); onOpenWindow: function (aWindow) {
} // Wait for the window to finish loading
}, const domWindow = aWindow
false .QueryInterface(Ci.nsIInterfaceRequestor)
); .getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
}, domWindow.addEventListener(
}; "load",
Services.wm.addListener(listener); async function () {
}); domWindow.removeEventListener("load", arguments.callee, false);
} if (!found && domWindow.Zotero) {
await Zotero.initializationPromise; Zotero = domWindow.Zotero;
resolve();
} else if (
domWindow.location.href ===
"chrome://zotero/content/zoteroPane.xhtml"
) {
// Call the hook for the main window load event
// Note that this is not called the first time the window is opened
// (when Zotero is initialized), but only when the window is re-opened
// after being closed
await Zotero.__addonInstance__?.hooks.onMainWindowLoad(domWindow);
}
},
false,
);
},
onCloseWindow: function (aWindow) {
const domWindow = aWindow
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal || Ci.nsIDOMWindow);
if (
domWindow.location.href === "chrome://zotero/content/zoteroPane.xhtml"
) {
Zotero.__addonInstance__?.hooks.onMainWindowUnload(domWindow);
}
},
};
Services.wm.addListener(windowListener);
});
} }
function install(data, reason) {} function install(data, reason) {}
async function startup({ id, version, resourceURI, rootURI }, reason) { async function startup({ id, version, resourceURI, rootURI }, reason) {
await waitForZotero(); await waitForZotero();
await Zotero.initializationPromise;
// String 'rootURI' introduced in Zotero 7 // String 'rootURI' introduced in Zotero 7
if (!rootURI) { if (!rootURI) {
rootURI = resourceURI.spec; rootURI = resourceURI.spec;
} }
if (Zotero.platformMajorVersion >= 102) { var aomStartup = Components.classes[
var aomStartup = Components.classes[ "@mozilla.org/addons/addon-manager-startup;1"
"@mozilla.org/addons/addon-manager-startup;1" ].getService(Components.interfaces.amIAddonManagerStartup);
].getService(Components.interfaces.amIAddonManagerStartup); var manifestURI = Services.io.newURI(rootURI + "manifest.json");
var manifestURI = Services.io.newURI(rootURI + "manifest.json"); chromeHandle = aomStartup.registerChrome(manifestURI, [
chromeHandle = aomStartup.registerChrome(manifestURI, [ ["content", "__addonRef__", rootURI + "chrome/content/"],
["content", "__addonRef__", rootURI + "chrome/content/"], ]);
["locale", "__addonRef__", "en-US", rootURI + "chrome/locale/en-US/"],
["locale", "__addonRef__", "zh-CN", rootURI + "chrome/locale/zh-CN/"],
["locale", "__addonRef__", "ru-RU", rootURI + "chrome/locale/ru-RU/"],
]);
} else {
setDefaultPrefs(rootURI);
}
/** /**
* Global variables for plugin code. * Global variables for plugin code.
@ -94,13 +108,14 @@ async function startup({ id, version, resourceURI, rootURI }, reason) {
*/ */
const ctx = { const ctx = {
rootURI, rootURI,
// Define main window's document to create a fake browser environment
document: Zotero.getMainWindow().document, document: Zotero.getMainWindow().document,
}; };
ctx._globalThis = ctx; ctx._globalThis = ctx;
Services.scriptloader.loadSubScript( Services.scriptloader.loadSubScript(
`${rootURI}/chrome/content/scripts/index.js`, `${rootURI}/chrome/content/scripts/__addonRef__.js`,
ctx ctx,
); );
} }
@ -108,9 +123,11 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
if (reason === APP_SHUTDOWN) { if (reason === APP_SHUTDOWN) {
return; return;
} }
Services.wm.removeListener(windowListener);
if (typeof Zotero === "undefined") { if (typeof Zotero === "undefined") {
Zotero = Components.classes["@zotero.org/Zotero;1"].getService( Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports Components.interfaces.nsISupports,
).wrappedJSObject; ).wrappedJSObject;
} }
Zotero.__addonInstance__.hooks.onShutdown(); Zotero.__addonInstance__.hooks.onShutdown();
@ -119,7 +136,7 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
.getService(Components.interfaces.nsIStringBundleService) .getService(Components.interfaces.nsIStringBundleService)
.flushBundles(); .flushBundles();
Cu.unload(`${rootURI}/chrome/content/scripts/index.js`); Cu.unload(`${rootURI}/chrome/content/scripts/__addonRef__.js`);
if (chromeHandle) { if (chromeHandle) {
chromeHandle.destruct(); chromeHandle.destruct();
@ -128,27 +145,3 @@ function shutdown({ id, version, resourceURI, rootURI }, reason) {
} }
function uninstall(data, reason) {} function uninstall(data, reason) {}
// Loads default preferences from defaults/preferences/prefs.js in Zotero 6
function setDefaultPrefs(rootURI) {
var branch = Services.prefs.getDefaultBranch("");
var obj = {
pref(pref, value) {
switch (typeof value) {
case "boolean":
branch.setBoolPref(pref, value);
break;
case "string":
branch.setStringPref(pref, value);
break;
case "number":
branch.setIntPref(pref, value);
break;
default:
Zotero.logError(`Invalid type '${typeof value}' for pref '${pref}'`);
}
},
};
Zotero.getMainWindow().console.log(rootURI + "prefs.js");
Services.scriptloader.loadSubScript(rootURI + "prefs.js", obj);
}

View File

@ -31,6 +31,7 @@
"reload": "npm run build-dev && node scripts/reload.mjs --z 7", "reload": "npm run build-dev && node scripts/reload.mjs --z 7",
"watch": "chokidar \"src/**\" \"addon/**\" -c \"npm run reload\"", "watch": "chokidar \"src/**\" \"addon/**\" -c \"npm run reload\"",
"release": "release-it", "release": "release-it",
"lint": "prettier --write src/** && eslint . --ext .ts --fix",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"update-deps": "npm update --save" "update-deps": "npm update --save"
}, },
@ -63,7 +64,6 @@
"remark-parse": "^10.0.2", "remark-parse": "^10.0.2",
"remark-rehype": "^10.1.0", "remark-rehype": "^10.1.0",
"remark-stringify": "^10.0.3", "remark-stringify": "^10.0.3",
"replace-in-file": "^6.3.5",
"seedrandom": "^3.0.5", "seedrandom": "^3.0.5",
"tree-model": "^1.0.7", "tree-model": "^1.0.7",
"unified": "^10.1.2", "unified": "^10.1.2",
@ -75,21 +75,25 @@
"devDependencies": { "devDependencies": {
"@types/browser-or-node": "^1.3.0", "@types/browser-or-node": "^1.3.0",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/node": "^20.3.0", "@types/node": "^20.4.2",
"@types/seedrandom": "^3.0.5", "@types/seedrandom": "^3.0.5",
"@types/yamljs": "^0.2.31", "@types/yamljs": "^0.2.31",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"chokidar-cli": "^3.0.0", "chokidar-cli": "^3.0.0",
"compressing": "^1.9.0", "compressing": "^1.9.0",
"concurrently": "^8.2.0", "concurrently": "^8.2.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esbuild": "^0.18.1", "esbuild": "^0.18.1",
"minimist": "^1.2.8", "eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"prettier": "^3.0.0",
"prosemirror-model": "^1.19.2", "prosemirror-model": "^1.19.2",
"prosemirror-state": "^1.4.3", "prosemirror-state": "^1.4.3",
"prosemirror-transform": "^1.7.3", "prosemirror-transform": "^1.7.3",
"prosemirror-view": "^1.31.4", "prosemirror-view": "^1.31.4",
"release-it": "^15.11.0", "release-it": "^15.11.0",
"replace-in-file": "^6.3.5", "replace-in-file": "^7.0.1",
"typescript": "^5.1.3", "typescript": "^5.1.3",
"zotero-types": "^1.0.15" "zotero-types": "^1.0.15"
} }

View File

@ -1,6 +1,6 @@
import { build } from "esbuild"; import { build } from "esbuild";
import { zip } from "compressing"; import { zip } from "compressing";
import { join, basename } from "path"; import path from "path";
import { import {
existsSync, existsSync,
lstatSync, lstatSync,
@ -13,18 +13,22 @@ import {
} from "fs"; } from "fs";
import { env, exit } from "process"; import { env, exit } from "process";
import replaceInFile from "replace-in-file"; import replaceInFile from "replace-in-file";
const { sync: replaceSync } = replaceInFile; const { replaceInFileSync } = replaceInFile;
import details from "../package.json" assert { type: "json" }; import details from "../package.json" assert { type: "json" };
const { name, author, description, homepage, version, config } = details; const { name, author, description, homepage, version, config } = details;
const t = new Date();
const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", new Date());
const buildDir = "build";
function copyFileSync(source, target) { function copyFileSync(source, target) {
var targetFile = target; var targetFile = target;
// If target is a directory, a new file with the same name will be created // If target is a directory, a new file with the same name will be created
if (existsSync(target)) { if (existsSync(target)) {
if (lstatSync(target).isDirectory()) { if (lstatSync(target).isDirectory()) {
targetFile = join(target, basename(source)); targetFile = path.join(target, path.basename(source));
} }
} }
@ -35,7 +39,7 @@ function copyFolderRecursiveSync(source, target) {
var files = []; var files = [];
// Check if folder needs to be created or integrated // Check if folder needs to be created or integrated
var targetFolder = join(target, basename(source)); var targetFolder = path.join(target, path.basename(source));
if (!existsSync(targetFolder)) { if (!existsSync(targetFolder)) {
mkdirSync(targetFolder); mkdirSync(targetFolder);
} }
@ -44,7 +48,7 @@ function copyFolderRecursiveSync(source, target) {
if (lstatSync(source).isDirectory()) { if (lstatSync(source).isDirectory()) {
files = readdirSync(source); files = readdirSync(source);
files.forEach(function (file) { files.forEach(function (file) {
var curSource = join(source, file); var curSource = path.join(source, file);
if (lstatSync(curSource).isDirectory()) { if (lstatSync(curSource).isDirectory()) {
copyFolderRecursiveSync(curSource, targetFolder); copyFolderRecursiveSync(curSource, targetFolder);
} else { } else {
@ -77,107 +81,21 @@ function dateFormat(fmt, date) {
if (ret) { if (ret) {
fmt = fmt.replace( fmt = fmt.replace(
ret[1], ret[1],
ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0") ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"),
); );
} }
} }
return fmt; return fmt;
} }
async function main() { function renameLocaleFiles() {
const t = new Date(); const localeDir = path.join(buildDir, "addon/locale");
const buildTime = dateFormat("YYYY-mm-dd HH:MM:SS", t);
const buildDir = "builds";
console.log(
`[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
env.NODE_ENV,
]}`
);
clearFolder(buildDir);
copyFolderRecursiveSync("addon", buildDir);
copyFileSync("update-template.json", "update.json");
copyFileSync("update-template.rdf", "update.rdf");
await build({
entryPoints: ["src/index.ts"],
define: {
__env__: `"${env.NODE_ENV}"`,
},
bundle: true,
outfile: join(buildDir, "addon/chrome/content/scripts/index.js"),
// Don't turn minify on
// minify: true,
target: ["firefox60"],
}).catch(() => exit(1));
await build({
entryPoints: ["src/extras/editorScript.ts"],
bundle: true,
outfile: join(buildDir, "addon/chrome/content/scripts/editorScript.js"),
target: ["firefox60"],
}).catch(() => exit(1));
await build({
entryPoints: ["src/extras/docxWorker.ts"],
bundle: true,
outfile: join(buildDir, "addon/chrome/content/scripts/docxWorker.js"),
target: ["firefox60"],
}).catch(() => exit(1));
console.log("[Build] Run esbuild OK");
const replaceFrom = [
/__author__/g,
/__description__/g,
/__homepage__/g,
/__buildVersion__/g,
/__buildTime__/g,
];
const replaceTo = [author, description, homepage, version, buildTime];
replaceFrom.push(
...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g"))
);
replaceTo.push(...Object.values(config));
const optionsAddon = {
files: [
join(buildDir, "**/*.html"),
join(buildDir, "**/*.xhtml"),
join(buildDir, "**/*.json"),
join(buildDir, "addon/prefs.js"),
join(buildDir, "addon/bootstrap.js"),
"update.json",
"update.rdf",
],
from: replaceFrom,
to: replaceTo,
countMatches: true,
};
const replaceResult = replaceSync(optionsAddon);
console.log(
"[Build] Run replace in ",
replaceResult
.filter((f) => f.hasChanged)
.map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`)
);
console.log("[Build] Replace OK");
// Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl
const localeDir = join(buildDir, "addon/locale");
const localeFolders = readdirSync(localeDir, { withFileTypes: true }) const localeFolders = readdirSync(localeDir, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory()) .filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name); .map((dirent) => dirent.name);
for (const localeSubFolder of localeFolders) { for (const localeSubFolder of localeFolders) {
const localeSubDir = join(localeDir, localeSubFolder); const localeSubDir = path.join(localeDir, localeSubFolder);
const localeSubFiles = readdirSync(localeSubDir, { const localeSubFiles = readdirSync(localeSubDir, {
withFileTypes: true, withFileTypes: true,
}) })
@ -187,22 +105,176 @@ async function main() {
for (const localeSubFile of localeSubFiles) { for (const localeSubFile of localeSubFiles) {
if (localeSubFile.endsWith(".ftl")) { if (localeSubFile.endsWith(".ftl")) {
renameSync( renameSync(
join(localeSubDir, localeSubFile), path.join(localeSubDir, localeSubFile),
join(localeSubDir, `${config.addonRef}-${localeSubFile}`) path.join(localeSubDir, `${config.addonRef}-${localeSubFile}`),
); );
} }
} }
} }
}
function replaceString() {
const replaceFrom = [
/__author__/g,
/__description__/g,
/__homepage__/g,
/__buildVersion__/g,
/__buildTime__/g,
];
const replaceTo = [author, description, homepage, version, buildTime];
replaceFrom.push(
...Object.keys(config).map((k) => new RegExp(`__${k}__`, "g")),
);
replaceTo.push(...Object.values(config));
const optionsAddon = {
files: [
`${buildDir}/addon/**/*.xhtml`,
`${buildDir}/addon/**/*.html`,
`${buildDir}/addon/**/*.json`,
`${buildDir}/addon/prefs.js`,
`${buildDir}/addon/manifest.json`,
`${buildDir}/addon/bootstrap.js`,
"update.json",
],
from: replaceFrom,
to: replaceTo,
countMatches: true,
};
const replaceResult = replaceInFileSync(optionsAddon);
const localeMessage = new Set();
const localeMessageMiss = new Set();
const replaceResultFlt = replaceInFileSync({
files: [`${buildDir}/addon/locale/**/*.ftl`],
processor: (fltContent) => {
const lines = fltContent.split("\n");
const prefixedLines = lines.map((line) => {
// https://regex101.com/r/lQ9x5p/1
const match = line.match(
/^(?<message>[a-zA-Z]\S*)([ ]*=[ ]*)(?<pattern>.*)$/m,
);
if (match) {
localeMessage.add(match.groups.message);
return `${config.addonRef}-${line}`;
} else {
return line;
}
});
return prefixedLines.join("\n");
},
});
const replaceResultXhtml = replaceInFileSync({
files: [`${buildDir}/addon/**/*.xhtml`],
processor: (input) => {
const matchs = [...input.matchAll(/(data-l10n-id)="(\S*)"/g)];
matchs.map((match) => {
if (localeMessage.has(match[2])) {
input = input.replace(
match[0],
`${match[1]}="${config.addonRef}-${match[2]}"`,
);
} else {
localeMessageMiss.add(match[2]);
}
});
return input;
},
});
console.log(
"[Build] Run replace in ",
replaceResult
.filter((f) => f.hasChanged)
.map((f) => `${f.file} : ${f.numReplacements} / ${f.numMatches}`),
replaceResultFlt.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
replaceResultXhtml.filter((f) => f.hasChanged).map((f) => `${f.file} : OK`),
);
if (localeMessageMiss.size !== 0) {
console.warn(
`[Build] [Warn] Fluent message [${new Array(
...localeMessageMiss,
)}] do not exsit in addon's locale files.`,
);
}
}
async function esbuild() {
await build({
entryPoints: ["src/index.ts"],
define: {
__env__: `"${env.NODE_ENV}"`,
},
bundle: true,
outfile: path.join(
buildDir,
`addon/chrome/content/scripts/${config.addonRef}.js`,
),
// Don't turn minify on
// minify: true,
target: ["firefox102"],
}).catch(() => exit(1));
await build({
entryPoints: ["src/extras/editorScript.ts"],
bundle: true,
outfile: path.join(
buildDir,
"addon/chrome/content/scripts/editorScript.js",
),
target: ["firefox102"],
}).catch(() => exit(1));
await build({
entryPoints: ["src/extras/docxWorker.ts"],
bundle: true,
outfile: path.join(buildDir, "addon/chrome/content/scripts/docxWorker.js"),
target: ["firefox102"],
}).catch(() => exit(1));
}
async function main() {
console.log(
`[Build] BUILD_DIR=${buildDir}, VERSION=${version}, BUILD_TIME=${buildTime}, ENV=${[
env.NODE_ENV,
]}`,
);
clearFolder(buildDir);
copyFolderRecursiveSync("addon", buildDir);
copyFileSync("update-template.json", "update.json");
await esbuild();
console.log("[Build] Run esbuild OK");
replaceString();
console.log("[Build] Replace OK");
// Walk the builds/addon/locale folder's sub folders and rename *.ftl to addonRef-*.ftl
renameLocaleFiles();
console.log("[Build] Addon prepare OK"); console.log("[Build] Addon prepare OK");
zip.compressDir(join(buildDir, "addon"), join(buildDir, `${name}.xpi`), { await zip.compressDir(
ignoreBase: true, path.join(buildDir, "addon"),
}); path.join(buildDir, `${name}.xpi`),
{
ignoreBase: true,
},
);
console.log("[Build] Addon pack OK"); console.log("[Build] Addon pack OK");
console.log( console.log(
`[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.` `[Build] Finished in ${(new Date().getTime() - t.getTime()) / 1000} s.`,
); );
} }

View File

@ -1,20 +1,13 @@
import { exit, argv } from "process"; import { exit } from "process";
import minimist from "minimist";
import { execSync } from "child_process"; import { execSync } from "child_process";
import details from "../package.json" assert { type: "json" }; import details from "../package.json" assert { type: "json" };
const { addonID, addonName } = details.config;
const version = details.version;
import cmd from "./zotero-cmd.json" assert { type: "json" }; import cmd from "./zotero-cmd.json" assert { type: "json" };
const { exec } = cmd;
// Run node reload.js -h for help const { addonID, addonName } = details.config;
const args = minimist(argv.slice(2)); const { version } = details;
const { zoteroBinPath, profilePath } = cmd.exec;
const zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]]; const startZotero = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`;
const profile = args.profile || args.p;
const startZotero = `${zoteroPath} --debugger --purgecaches ${
profile ? `-p ${profile}` : ""
}`;
const script = ` const script = `
(async () => { (async () => {

View File

@ -1,28 +1,74 @@
import process from "process";
import { execSync } from "child_process"; import { execSync } from "child_process";
import { exit } from "process"; import { exit } from "process";
import minimist from "minimist"; import { existsSync, writeFileSync, readFileSync, mkdirSync } from "fs";
import path from "path";
import details from "../package.json" assert { type: "json" };
import cmd from "./zotero-cmd.json" assert { type: "json" }; import cmd from "./zotero-cmd.json" assert { type: "json" };
const { exec } = cmd;
// Run node start.js -h for help const { addonID } = details.config;
const args = minimist(process.argv.slice(2)); const { zoteroBinPath, profilePath, dataDir } = cmd.exec;
if (args.help || args.h) { if (!existsSync(zoteroBinPath)) {
console.log("Start Zotero Args:"); throw new Error("Zotero binary does not exist.");
console.log(
"--zotero(-z): Zotero exec key in zotero-cmd.json. Default the first one."
);
console.log("--profile(-p): Zotero profile name.");
exit(0);
} }
const zoteroPath = exec[args.zotero || args.z || Object.keys(exec)[0]]; if (existsSync(profilePath)) {
const profile = args.profile || args.p; const addonProxyFilePath = path.join(profilePath, `extensions/${addonID}`);
const buildPath = path.resolve("build/addon");
const startZotero = `${zoteroPath} --debugger --purgecaches ${ if (!existsSync(path.join(buildPath, "./manifest.json"))) {
profile ? `-p ${profile}` : "" throw new Error(
}`; `The built file does not exist, maybe you need to build the addon first.`,
);
}
function writeAddonProxyFile() {
writeFileSync(addonProxyFilePath, buildPath);
console.log(
`[info] Addon proxy file has been updated.
File path: ${addonProxyFilePath}
Addon path: ${buildPath} `,
);
}
if (existsSync(addonProxyFilePath)) {
if (readFileSync(addonProxyFilePath, "utf-8") !== buildPath) {
writeAddonProxyFile();
}
} else {
if (
existsSync(profilePath) &&
!existsSync(path.join(profilePath, "extensions"))
) {
mkdirSync(path.join(profilePath, "extensions"));
}
writeAddonProxyFile();
}
const prefsPath = path.join(profilePath, "prefs.js");
if (existsSync(prefsPath)) {
const PrefsLines = readFileSync(prefsPath, "utf-8").split("\n");
const filteredLines = PrefsLines.map((line) => {
if (
line.includes("extensions.lastAppBuildId") ||
line.includes("extensions.lastAppVersion")
) {
return;
}
if (line.includes("extensions.zotero.dataDir") && dataDir !== "") {
return `user_pref("extensions.zotero.dataDir", "${dataDir}");`;
}
return line;
});
const updatedPrefs = filteredLines.join("\n");
writeFileSync(prefsPath, updatedPrefs, "utf-8");
console.log("[info] The <profile>/prefs.js has been modified.");
}
} else {
throw new Error("The given Zotero profile does not exist.");
}
const startZotero = `"${zoteroBinPath}" --debugger --purgecaches -profile "${profilePath}"`;
execSync(startZotero); execSync(startZotero);
exit(0); exit(0);

View File

@ -3,7 +3,18 @@
"killZoteroWindows": "taskkill /f /im zotero.exe", "killZoteroWindows": "taskkill /f /im zotero.exe",
"killZoteroUnix": "kill -9 $(ps -x | grep zotero)", "killZoteroUnix": "kill -9 $(ps -x | grep zotero)",
"exec": { "exec": {
"6": "/path/to/zotero6.exe", "@comment-zoteroBinPath": "Please input the path of the Zotero binary file in `zoteroBinPath`.",
"7": "/path/to/zotero7.exe" "@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

@ -133,7 +133,7 @@ class Addon {
recentMainNoteIdsArr recentMainNoteIdsArr
.slice(0, 10) .slice(0, 10)
.filter((id) => Zotero.Items.get(id).isNote()) .filter((id) => Zotero.Items.get(id).isNote())
.join(",") .join(","),
); );
}, },
previewId: -1, previewId: -1,

View File

@ -17,9 +17,9 @@ declare const _currentEditorInstance: {
}; };
function fromHTML(schema: Schema, html: string, slice?: boolean) { function fromHTML(schema: Schema, html: string, slice?: boolean) {
let domNode = document.createElement("div"); const domNode = document.createElement("div");
domNode.innerHTML = html; domNode.innerHTML = html;
let fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
while (domNode.firstChild) { while (domNode.firstChild) {
fragment.appendChild(domNode.firstChild); fragment.appendChild(domNode.firstChild);
} }
@ -63,7 +63,7 @@ function objectIncludes(object1: any, object2: any) {
function findMarkInSet( function findMarkInSet(
marks: readonly Mark[], marks: readonly Mark[],
type: MarkType, type: MarkType,
attributes = {} attributes = {},
) { ) {
return marks.find((item) => { return marks.find((item) => {
return item.type === type && objectIncludes(item.attrs, attributes); return item.type === type && objectIncludes(item.attrs, attributes);
@ -128,7 +128,7 @@ function getMarkRangeAtCursor(state: EditorState, type: MarkType) {
const start = $from.parent.childAfter($from.parentOffset); const start = $from.parent.childAfter($from.parentOffset);
if (start.node) { if (start.node) {
const mark = start.node.marks.find( const mark = start.node.marks.find(
(mark) => mark.type.name === type.name (mark) => mark.type.name === type.name,
); );
if (mark) { if (mark) {
return getMarkRange($from, type, mark.attrs); return getMarkRange($from, type, mark.attrs);
@ -164,7 +164,7 @@ function replaceRange(
to: number, to: number,
text: string | undefined, text: string | undefined,
type: MarkType, type: MarkType,
attrs: Attrs | string attrs: Attrs | string,
) { ) {
return (state: EditorState, dispatch: EditorView["dispatch"]) => { return (state: EditorState, dispatch: EditorView["dispatch"]) => {
const { tr } = state; const { tr } = state;
@ -189,7 +189,7 @@ function replaceRangeNode(
nodeAttrs: Attrs | string, nodeAttrs: Attrs | string,
markType?: MarkType, markType?: MarkType,
markAttrs?: Attrs | string, markAttrs?: Attrs | string,
select?: boolean select?: boolean,
) { ) {
return (state: EditorState, dispatch: EditorView["dispatch"]) => { return (state: EditorState, dispatch: EditorView["dispatch"]) => {
const { tr } = state; const { tr } = state;
@ -203,7 +203,7 @@ function replaceRangeNode(
const node = nodeType.create( const node = nodeType.create(
nodeAttrs, nodeAttrs,
state.schema.text(text || state.doc.textBetween(from, to)), state.schema.text(text || state.doc.textBetween(from, to)),
markType ? [markType.create(markAttrs)] : [] markType ? [markType.create(markAttrs)] : [],
); );
console.log("Replace Node", from, to, node); console.log("Replace Node", from, to, node);
tr.replaceWith(from, to, node); tr.replaceWith(from, to, node);
@ -218,7 +218,7 @@ function replaceRangeAtCursor(
text: string | undefined, text: string | undefined,
type: MarkType, type: MarkType,
attrs: Attrs | string, attrs: Attrs | string,
searchType: MarkType searchType: MarkType,
) { ) {
return (state: EditorState, dispatch: EditorView["dispatch"]) => { return (state: EditorState, dispatch: EditorView["dispatch"]) => {
const range = getMarkRangeAtCursor(state, searchType); const range = getMarkRangeAtCursor(state, searchType);
@ -323,9 +323,9 @@ function updateHeadingsInRange(from: number, to: number, levelOffset: number) {
}; };
} }
function refocusEditor(callback: Function) { function refocusEditor(callback: (args: void) => void) {
let scrollTop = document.querySelector(".editor-core")!.scrollTop; const scrollTop = document.querySelector(".editor-core")!.scrollTop;
let input = document.createElement("input"); const input = document.createElement("input");
input.style.position = "absolute"; input.style.position = "absolute";
input.style.opacity = "0"; input.style.opacity = "0";
document.body.append(input); document.body.append(input);
@ -344,9 +344,9 @@ function updateImageDimensions(
width: number, width: number,
height: number | undefined, height: number | undefined,
state: EditorState, state: EditorState,
dispatch: EditorView["dispatch"] dispatch: EditorView["dispatch"],
) { ) {
let { tr } = state; const { tr } = state;
state.doc.descendants((node: Node, pos: number) => { state.doc.descendants((node: Node, pos: number) => {
if (node.type.name === "image" && node.attrs.nodeID === nodeID) { if (node.type.name === "image" && node.attrs.nodeID === nodeID) {
// tr.step(new SetAttrsStep(pos, { ...node.attrs, width, height })); // tr.step(new SetAttrsStep(pos, { ...node.attrs, width, height }));
@ -356,7 +356,7 @@ function updateImageDimensions(
pos, pos,
node.type, node.type,
{ ...node.attrs, width, height }, { ...node.attrs, width, height },
node.marks node.marks,
); );
dispatch(tr); dispatch(tr);
return false; return false;

View File

@ -47,6 +47,7 @@ import {
createNoteFromMD, createNoteFromMD,
} from "./modules/createNote"; } from "./modules/createNote";
import { annotationTagAction } from "./modules/annotationTagAction"; import { annotationTagAction } from "./modules/annotationTagAction";
import { createZToolkit } from "./utils/ztoolkit";
async function onStartup() { async function onStartup() {
await Promise.all([ await Promise.all([
@ -57,26 +58,38 @@ async function onStartup() {
initLocale(); initLocale();
ztoolkit.ProgressWindow.setIconURI( ztoolkit.ProgressWindow.setIconURI(
"default", "default",
`chrome://${config.addonRef}/content/icons/favicon.png` `chrome://${config.addonRef}/content/icons/favicon.png`,
); );
registerNoteLinkProxyHandler(); registerNoteLinkProxyHandler();
registerNotify(["tab", "item", "item-tag"]);
registerEditorInstanceHook(); registerEditorInstanceHook();
initTemplates(); initTemplates();
registerPrefsWindow();
setSyncing();
await onMainWindowLoad(window);
}
async function onMainWindowLoad(win: Window): Promise<void> {
// Create ztoolkit for every window
addon.data.ztoolkit = createZToolkit();
registerNotify(["tab", "item", "item-tag"]);
registerMenus(); registerMenus();
registerWorkspaceTab(); registerWorkspaceTab();
registerReaderInitializer(); registerReaderInitializer();
}
registerPrefsWindow(); async function onMainWindowUnload(win: Window): Promise<void> {
ztoolkit.unregisterAll();
setSyncing(); unregisterReaderInitializer();
} }
function onShutdown(): void { function onShutdown(): void {
@ -96,7 +109,7 @@ function onNotify(
event: string, event: string,
type: string, type: string,
ids: number[] | string[], ids: number[] | string[],
extraData: { [key: string]: any } extraData: { [key: string]: any },
) { ) {
// Workspace tab select/unselect callback // Workspace tab select/unselect callback
if (event === "select" && type === "tab") { if (event === "select" && type === "tab") {
@ -128,7 +141,7 @@ function onNotify(
// Reader annotation buttons update // Reader annotation buttons update
if (event === "add" && type === "item") { if (event === "add" && type === "item") {
const annotationItems = Zotero.Items.get(ids as number[]).filter((item) => const annotationItems = Zotero.Items.get(ids as number[]).filter((item) =>
item.isAnnotation() item.isAnnotation(),
); );
if (annotationItems.length !== 0) { if (annotationItems.length !== 0) {
checkReaderAnnotationButton(annotationItems); checkReaderAnnotationButton(annotationItems);
@ -163,7 +176,7 @@ function onOpenNote(
mode: "auto" | "preview" | "workspace" | "standalone" = "auto", mode: "auto" | "preview" | "workspace" | "standalone" = "auto",
options: { options: {
lineIndex?: number; lineIndex?: number;
} = {} } = {},
) { ) {
const noteItem = Zotero.Items.get(noteId); const noteItem = Zotero.Items.get(noteId);
if (!noteItem?.isNote()) { if (!noteItem?.isNote()) {
@ -188,6 +201,7 @@ function onOpenNote(
break; break;
case "workspace": case "workspace":
addon.hooks.onSetWorkspaceNote(noteId, "main", options); addon.hooks.onSetWorkspaceNote(noteId, "main", options);
break;
case "standalone": case "standalone":
ZoteroPane.openNoteWindow(noteId); ZoteroPane.openNoteWindow(noteId);
break; break;
@ -201,7 +215,7 @@ function onSetWorkspaceNote(
type: "main" | "preview" = "main", type: "main" | "preview" = "main",
options: { options: {
lineIndex?: number; lineIndex?: number;
} = {} } = {},
) { ) {
if (type === "main") { if (type === "main") {
addon.data.workspace.mainId = noteId; addon.data.workspace.mainId = noteId;
@ -215,13 +229,13 @@ function onSetWorkspaceNote(
addon.data.workspace.window.container, addon.data.workspace.window.container,
type, type,
noteId, noteId,
options options,
); );
type === "preview" && type === "preview" &&
addon.hooks.onToggleWorkspacePane( addon.hooks.onToggleWorkspacePane(
"preview", "preview",
true, true,
addon.data.workspace.window.container addon.data.workspace.window.container,
); );
addon.data.workspace.window.window?.focus(); addon.data.workspace.window.window?.focus();
} }
@ -230,13 +244,13 @@ function onSetWorkspaceNote(
addon.data.workspace.tab.container, addon.data.workspace.tab.container,
type, type,
noteId, noteId,
options options,
); );
type === "preview" && type === "preview" &&
addon.hooks.onToggleWorkspacePane( addon.hooks.onToggleWorkspacePane(
"preview", "preview",
true, true,
addon.data.workspace.tab.container addon.data.workspace.tab.container,
); );
Zotero_Tabs.select(addon.data.workspace.tab.id!); Zotero_Tabs.select(addon.data.workspace.tab.id!);
} }
@ -263,7 +277,7 @@ const onInitWorkspace = initWorkspace;
function onToggleWorkspacePane( function onToggleWorkspacePane(
type: "outline" | "preview" | "notes", type: "outline" | "preview" | "notes",
visibility?: boolean, visibility?: boolean,
container?: XUL.Box container?: XUL.Box,
) { ) {
switch (type) { switch (type) {
case "outline": case "outline":
@ -274,6 +288,7 @@ function onToggleWorkspacePane(
break; break;
case "notes": case "notes":
toggleNotesPane(visibility); toggleNotesPane(visibility);
break;
default: default:
break; break;
} }
@ -311,6 +326,8 @@ const onCreateNoteFromMD = createNoteFromMD;
export default { export default {
onStartup, onStartup,
onMainWindowLoad,
onMainWindowUnload,
onShutdown, onShutdown,
onNotify, onNotify,
onPrefsEvent, onPrefsEvent,

View File

@ -7,22 +7,26 @@ const basicTool = new BasicTool();
if (!basicTool.getGlobal("Zotero")[config.addonInstance]) { if (!basicTool.getGlobal("Zotero")[config.addonInstance]) {
// Set global variables // Set global variables
_globalThis.Zotero = basicTool.getGlobal("Zotero"); _globalThis.Zotero = basicTool.getGlobal("Zotero");
_globalThis.ZoteroPane = basicTool.getGlobal("ZoteroPane"); defineGlobal("window");
_globalThis.Zotero_Tabs = basicTool.getGlobal("Zotero_Tabs"); defineGlobal("document");
_globalThis.window = basicTool.getGlobal("window"); defineGlobal("ZoteroPane");
_globalThis.document = basicTool.getGlobal("document"); defineGlobal("Zotero_Tabs");
_globalThis.OS = basicTool.getGlobal("OS") as typeof OS; defineGlobal("OS");
_globalThis.addon = new Addon(); _globalThis.addon = new Addon();
_globalThis.ztoolkit = addon.data.ztoolkit; Object.defineProperty(_globalThis, "ztoolkit", {
ztoolkit.basicOptions.log.prefix = `[${config.addonName}]`; get() {
ztoolkit.basicOptions.log.disableConsole = addon.data.env === "production"; return _globalThis.addon.data.ztoolkit;
ztoolkit.UI.basicOptions.ui.enableElementJSONLog = },
addon.data.env === "development"; });
ztoolkit.UI.basicOptions.ui.enableElementDOMLog =
addon.data.env === "development";
ztoolkit.basicOptions.debug.disableDebugBridgePassword =
addon.data.env === "development";
Zotero[config.addonInstance] = addon; Zotero[config.addonInstance] = addon;
// Trigger addon hook for initialization // Trigger addon hook for initialization
addon.hooks.onStartup(); addon.hooks.onStartup();
} }
function defineGlobal(name: Parameters<BasicTool["getGlobal"]>[0]) {
Object.defineProperty(_globalThis, name, {
get() {
return basicTool.getGlobal(name);
},
});
}

View File

@ -4,7 +4,7 @@ export { annotationTagAction };
async function annotationTagAction( async function annotationTagAction(
ids: Array<number | string>, ids: Array<number | string>,
extraData: Record<string, any> extraData: Record<string, any>,
) { ) {
const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId); const workspaceNote = Zotero.Items.get(addon.data.workspace.mainId);
if (!workspaceNote || !workspaceNote.isNote()) { if (!workspaceNote || !workspaceNote.isNote()) {
@ -14,7 +14,7 @@ async function annotationTagAction(
const headings: string[] = nodes.map((node) => node.model.name); const headings: string[] = nodes.map((node) => node.model.name);
for (const tagId of ids.filter((t) => for (const tagId of ids.filter((t) =>
(extraData[t].tag as string).startsWith("@") (extraData[t].tag as string).startsWith("@"),
)) { )) {
const tagName = (extraData[tagId].tag as string).slice(1).trim(); const tagName = (extraData[tagId].tag as string).slice(1).trim();
if (headings.includes(tagName) || tagName === "@") { if (headings.includes(tagName) || tagName === "@") {
@ -35,7 +35,7 @@ async function annotationTagAction(
await addon.api.convert.annotations2html([annotationItem], { await addon.api.convert.annotations2html([annotationItem], {
noteItem: workspaceNote, noteItem: workspaceNote,
}), }),
lineIndex lineIndex,
); );
} }
} }

View File

@ -54,22 +54,22 @@ async function note2md(
keepNoteLink?: boolean; keepNoteLink?: boolean;
withYAMLHeader?: boolean; withYAMLHeader?: boolean;
skipSavingImages?: boolean; skipSavingImages?: boolean;
} = {} } = {},
) { ) {
const noteStatus = addon.api.sync.getNoteStatus(noteItem.id)!; const noteStatus = addon.api.sync.getNoteStatus(noteItem.id)!;
const rehype = note2rehype(noteStatus.content); const rehype = note2rehype(noteStatus.content);
processN2MRehypeHighlightNodes( processN2MRehypeHighlightNodes(
getN2MRehypeHighlightNodes(rehype), getN2MRehypeHighlightNodes(rehype),
NodeMode.direct NodeMode.direct,
); );
processN2MRehypeCitationNodes( processN2MRehypeCitationNodes(
getN2MRehypeCitationNodes(rehype), getN2MRehypeCitationNodes(rehype),
NodeMode.direct NodeMode.direct,
); );
await processN2MRehypeNoteLinkNodes( await processN2MRehypeNoteLinkNodes(
getN2MRehypeNoteLinkNodes(rehype), getN2MRehypeNoteLinkNodes(rehype),
dir, dir,
options.keepNoteLink ? NodeMode.default : NodeMode.direct options.keepNoteLink ? NodeMode.default : NodeMode.direct,
); );
await processN2MRehypeImageNodes( await processN2MRehypeImageNodes(
getN2MRehypeImageNodes(rehype), getN2MRehypeImageNodes(rehype),
@ -77,7 +77,7 @@ async function note2md(
formatPath(OS.Path.join(dir, "attachments")), formatPath(OS.Path.join(dir, "attachments")),
options.skipSavingImages, options.skipSavingImages,
false, false,
NodeMode.direct NodeMode.direct,
); );
const remark = await rehype2remark(rehype); const remark = await rehype2remark(rehype);
let md = remark2md(remark); let md = remark2md(remark);
@ -89,16 +89,18 @@ async function note2md(
await addon.api.template.runTemplate( await addon.api.template.runTemplate(
"[ExportMDFileHeaderV2]", "[ExportMDFileHeaderV2]",
"noteItem", "noteItem",
[noteItem] [noteItem],
) ),
); );
} catch (e) {} } catch (e) {
ztoolkit.log(e);
}
Object.assign(header, { Object.assign(header, {
version: noteItem.version, version: noteItem.version,
libraryID: noteItem.libraryID, libraryID: noteItem.libraryID,
itemKey: noteItem.key, itemKey: noteItem.key,
}); });
let yamlFrontMatter = `---\n${YAML.stringify(header, 10)}\n---`; const yamlFrontMatter = `---\n${YAML.stringify(header, 10)}\n---`;
md = `${yamlFrontMatter}\n${md}`; md = `${yamlFrontMatter}\n${md}`;
} }
return md; return md;
@ -107,7 +109,7 @@ async function note2md(
async function md2note( async function md2note(
mdStatus: MDStatus, mdStatus: MDStatus,
noteItem: Zotero.Item, noteItem: Zotero.Item,
options: { isImport?: boolean } = {} options: { isImport?: boolean } = {},
) { ) {
const remark = md2remark(mdStatus.content); const remark = md2remark(mdStatus.content);
const _rehype = await remark2rehype(remark); const _rehype = await remark2rehype(remark);
@ -120,14 +122,14 @@ async function md2note(
processM2NRehypeHighlightNodes(getM2NRehypeHighlightNodes(rehype)); processM2NRehypeHighlightNodes(getM2NRehypeHighlightNodes(rehype));
await processM2NRehypeCitationNodes( await processM2NRehypeCitationNodes(
getM2NRehypeCitationNodes(rehype), getM2NRehypeCitationNodes(rehype),
options.isImport options.isImport,
); );
processM2NRehypeNoteLinkNodes(getM2NRehypeNoteLinkNodes(rehype)); processM2NRehypeNoteLinkNodes(getM2NRehypeNoteLinkNodes(rehype));
await processM2NRehypeImageNodes( await processM2NRehypeImageNodes(
getM2NRehypeImageNodes(rehype), getM2NRehypeImageNodes(rehype),
noteItem, noteItem,
mdStatus.filedir, mdStatus.filedir,
options.isImport options.isImport,
); );
const noteContent = rehype2note(rehype); const noteContent = rehype2note(rehype);
return noteContent; return noteContent;
@ -143,7 +145,7 @@ async function note2noteDiff(noteItem: Zotero.Item) {
function note2link( function note2link(
noteItem: Zotero.Item, noteItem: Zotero.Item,
options: Parameters<typeof getNoteLink>[1] options: Parameters<typeof getNoteLink>[1],
) { ) {
return getNoteLink(noteItem, options); return getNoteLink(noteItem, options);
} }
@ -154,9 +156,9 @@ function link2note(link: string) {
async function link2html( async function link2html(
link: string, link: string,
options: { noteItem?: Zotero.Item; dryRun?: boolean } = {} options: { noteItem?: Zotero.Item; dryRun?: boolean } = {},
) { ) {
ztoolkit.log("link2html", arguments); ztoolkit.log("link2html", link, options);
const linkParams = getNoteLinkParams(link); const linkParams = getNoteLinkParams(link);
if (!linkParams.noteItem) { if (!linkParams.noteItem) {
return ""; return "";
@ -173,7 +175,7 @@ async function link2html(
// Only embed the note content // Only embed the note content
html, html,
options.noteItem, options.noteItem,
refNotes refNotes,
); );
} }
} }
@ -194,7 +196,7 @@ async function html2md(html: string) {
function annotations2html( function annotations2html(
annotations: Zotero.Item[], annotations: Zotero.Item[],
options: Parameters<typeof parseAnnotationHTML>[1] = {} options: Parameters<typeof parseAnnotationHTML>[1] = {},
) { ) {
return parseAnnotationHTML(annotations, options); return parseAnnotationHTML(annotations, options);
} }
@ -227,7 +229,7 @@ function note2rehype(str: string) {
removeBlank(_n, parentNode, -1); removeBlank(_n, parentNode, -1);
removeBlank(_n, parentNode, 1); removeBlank(_n, parentNode, 1);
} }
} },
); );
// Make sure <span> and <img> wrapped by <p> // Make sure <span> and <img> wrapped by <p>
@ -245,7 +247,7 @@ function note2rehype(str: string) {
replace(_n, p); replace(_n, p);
} }
} }
} },
); );
// Make sure empty <p> under root node is removed // Make sure empty <p> under root node is removed
@ -259,7 +261,7 @@ function note2rehype(str: string) {
parentNode.children.splice(parentNode.children.indexOf(_n), 1); parentNode.children.splice(parentNode.children.indexOf(_n), 1);
} }
} }
} },
); );
return rehype; return rehype;
} }
@ -310,13 +312,13 @@ async function rehype2remark(rehype: HRoot) {
if (node.properties.style) { if (node.properties.style) {
hasStyle = true; hasStyle = true;
} }
} },
); );
if (0 && hasStyle) { // if (0 && hasStyle) {
return h(node, "styleTable", toHtml(node)); // return h(node, "styleTable", toHtml(node));
} else { // } else {
return defaultHandlers.table(h, node); return defaultHandlers.table(h, node);
} // }
}, },
wrapper: (h, node) => { wrapper: (h, node) => {
return h(node, "wrapper", toText(node)); return h(node, "wrapper", toText(node));
@ -389,7 +391,7 @@ function remark2md(remark: MRoot) {
}, },
}, },
} as any) } as any)
.stringify(remark) .stringify(remark),
); );
} }
@ -401,7 +403,7 @@ function md2remark(str: string) {
.replace( .replace(
/!\[.*\]\((.*)\)/g, /!\[.*\]\((.*)\)/g,
(s: string) => (s: string) =>
`![](${encodeURIComponent(s.match(/\(.*\)/g)![0].slice(1, -1))})` `![](${encodeURIComponent(s.match(/\(.*\)/g)![0].slice(1, -1))})`,
); );
const remark = unified() const remark = unified()
.use(remarkGfm) .use(remarkGfm)
@ -439,7 +441,7 @@ function rehype2note(rehype: HRoot) {
(node: any) => { (node: any) => {
node.tagName = "span"; node.tagName = "span";
node.properties.style = "text-decoration: line-through"; node.properties.style = "text-decoration: line-through";
} },
); );
// Code node // Code node
@ -454,7 +456,7 @@ function rehype2note(rehype: HRoot) {
node.value = toText(node); node.value = toText(node);
node.type = "text"; node.type = "text";
} }
} },
); );
// Table node with style // Table node with style
@ -472,14 +474,14 @@ function rehype2note(rehype: HRoot) {
if (node.properties.style) { if (node.properties.style) {
hasStyle = true; hasStyle = true;
} }
} },
); );
if (hasStyle) { if (hasStyle) {
node.value = toHtml(node).replace(/[\r\n]/g, ""); node.value = toHtml(node).replace(/[\r\n]/g, "");
node.children = []; node.children = [];
node.type = "raw"; node.type = "raw";
} }
} },
); );
// Convert thead to tbody // Convert thead to tbody
@ -490,7 +492,7 @@ function rehype2note(rehype: HRoot) {
node.value = toHtml(node).slice(7, -8); node.value = toHtml(node).slice(7, -8);
node.children = []; node.children = [];
node.type = "raw"; node.type = "raw";
} },
); );
// Wrap lines in list with <p> (for diff) // Wrap lines in list with <p> (for diff)
@ -522,9 +524,9 @@ function rehype2note(rehype: HRoot) {
node.children = node.children.filter( node.children = node.children.filter(
(_n: { type: string; value: string }) => (_n: { type: string; value: string }) =>
_n.type === "element" || _n.type === "element" ||
(_n.type === "text" && _n.value.replace(/[\r\n]/g, "")) (_n.type === "text" && _n.value.replace(/[\r\n]/g, "")),
); );
} },
); );
// Math node // Math node
@ -550,7 +552,7 @@ function rehype2note(rehype: HRoot) {
node.tagName = "pre"; node.tagName = "pre";
} }
node.properties.className = "math"; node.properties.className = "math";
} },
); );
// Ignore link rel attribute, which exists in note // Ignore link rel attribute, which exists in note
@ -559,7 +561,7 @@ function rehype2note(rehype: HRoot) {
(node: any) => node.type === "element" && (node as any).tagName === "a", (node: any) => node.type === "element" && (node as any).tagName === "a",
(node: any) => { (node: any) => {
node.properties.rel = undefined; node.properties.rel = undefined;
} },
); );
// Ignore empty lines, as they are not parsed to md // Ignore empty lines, as they are not parsed to md
@ -610,7 +612,7 @@ function getN2MRehypeHighlightNodes(rehype: HRoot) {
(node: any) => (node: any) =>
node.type === "element" && node.type === "element" &&
node.properties?.className?.includes("highlight"), node.properties?.className?.includes("highlight"),
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -622,7 +624,7 @@ function getN2MRehypeCitationNodes(rehype: HRoot) {
(node: any) => (node: any) =>
node.type === "element" && node.type === "element" &&
node.properties?.className?.includes("citation"), node.properties?.className?.includes("citation"),
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -636,7 +638,7 @@ function getN2MRehypeNoteLinkNodes(rehype: any) {
node.tagName === "a" && node.tagName === "a" &&
node.properties?.href && node.properties?.href &&
/zotero:\/\/note\/\w+\/\w+\//.test(node.properties?.href), /zotero:\/\/note\/\w+\/\w+\//.test(node.properties?.href),
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -649,14 +651,14 @@ function getN2MRehypeImageNodes(rehype: any) {
node.type === "element" && node.type === "element" &&
node.tagName === "img" && node.tagName === "img" &&
node.properties?.dataAttachmentKey, node.properties?.dataAttachmentKey,
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
function processN2MRehypeHighlightNodes( function processN2MRehypeHighlightNodes(
nodes: string | any[], nodes: string | any[],
mode: NodeMode = NodeMode.default mode: NodeMode = NodeMode.default,
) { ) {
if (!nodes.length) { if (!nodes.length) {
return; return;
@ -665,7 +667,7 @@ function processN2MRehypeHighlightNodes(
let annotation; let annotation;
try { try {
annotation = JSON.parse( annotation = JSON.parse(
decodeURIComponent(node.properties.dataAnnotation) decodeURIComponent(node.properties.dataAnnotation),
); );
} catch (e) { } catch (e) {
continue; continue;
@ -674,20 +676,20 @@ function processN2MRehypeHighlightNodes(
continue; continue;
} }
// annotation.uri was used before note-editor v4 // annotation.uri was used before note-editor v4
let uri = annotation.attachmentURI || annotation.uri; const uri = annotation.attachmentURI || annotation.uri;
let position = annotation.position; const position = annotation.position;
if (typeof uri === "string" && typeof position === "object") { if (typeof uri === "string" && typeof position === "object") {
let openURI; let openURI;
let uriParts = uri.split("/"); const uriParts = uri.split("/");
let libraryType = uriParts[3]; const libraryType = uriParts[3];
let key = uriParts[uriParts.length - 1]; const key = uriParts[uriParts.length - 1];
if (libraryType === "users") { if (libraryType === "users") {
openURI = "zotero://open-pdf/library/items/" + key; openURI = "zotero://open-pdf/library/items/" + key;
} }
// groups // groups
else { else {
let groupID = uriParts[4]; const groupID = uriParts[4];
openURI = "zotero://open-pdf/groups/" + groupID + "/items/" + key; openURI = "zotero://open-pdf/groups/" + groupID + "/items/" + key;
} }
@ -709,7 +711,7 @@ function processN2MRehypeHighlightNodes(
randomString( randomString(
8, 8,
Zotero.Utilities.Internal.md5(node.properties.dataAnnotation), Zotero.Utilities.Internal.md5(node.properties.dataAnnotation),
Zotero.Utilities.allowedKeyChars Zotero.Utilities.allowedKeyChars,
); );
if (mode === NodeMode.wrap) { if (mode === NodeMode.wrap) {
@ -731,7 +733,7 @@ function processN2MRehypeHighlightNodes(
function processN2MRehypeCitationNodes( function processN2MRehypeCitationNodes(
nodes: string | any[], nodes: string | any[],
mode: NodeMode = NodeMode.default mode: NodeMode = NodeMode.default,
) { ) {
if (!nodes.length) { if (!nodes.length) {
return; return;
@ -747,19 +749,19 @@ function processN2MRehypeCitationNodes(
continue; continue;
} }
let uris: any[] = []; const uris: any[] = [];
for (let citationItem of citation.citationItems) { for (const citationItem of citation.citationItems) {
let uri = citationItem.uris[0]; const uri = citationItem.uris[0];
if (typeof uri === "string") { if (typeof uri === "string") {
let uriParts = uri.split("/"); const uriParts = uri.split("/");
let libraryType = uriParts[3]; const libraryType = uriParts[3];
let key = uriParts[uriParts.length - 1]; const key = uriParts[uriParts.length - 1];
if (libraryType === "users") { if (libraryType === "users") {
uris.push("zotero://select/library/items/" + key); uris.push("zotero://select/library/items/" + key);
} }
// groups // groups
else { else {
let groupID = uriParts[4]; const groupID = uriParts[4];
uris.push("zotero://select/groups/" + groupID + "/items/" + key); uris.push("zotero://select/groups/" + groupID + "/items/" + key);
} }
} }
@ -772,7 +774,7 @@ function processN2MRehypeCitationNodes(
(_n: any) => _n.properties?.className.includes("citation-item"), (_n: any) => _n.properties?.className.includes("citation-item"),
(_n: any) => { (_n: any) => {
return childNodes?.push(_n); return childNodes?.push(_n);
} },
); );
// For unknown reasons, the element will be duplicated. Remove them. // For unknown reasons, the element will be duplicated. Remove them.
@ -797,7 +799,7 @@ function processN2MRehypeCitationNodes(
const citationKey = randomString( const citationKey = randomString(
8, 8,
Zotero.Utilities.Internal.md5(node.properties.dataCitation), Zotero.Utilities.Internal.md5(node.properties.dataCitation),
Zotero.Utilities.allowedKeyChars Zotero.Utilities.allowedKeyChars,
); );
if (mode === NodeMode.wrap) { if (mode === NodeMode.wrap) {
newNode.children.splice(0, 0, h("wrapperleft", `cite:${citationKey}`)); newNode.children.splice(0, 0, h("wrapperleft", `cite:${citationKey}`));
@ -817,7 +819,7 @@ function processN2MRehypeCitationNodes(
async function processN2MRehypeNoteLinkNodes( async function processN2MRehypeNoteLinkNodes(
nodes: string | any[], nodes: string | any[],
dir: string, dir: string,
mode: NodeMode = NodeMode.default mode: NodeMode = NodeMode.default,
) { ) {
if (!nodes.length) { if (!nodes.length) {
return; return;
@ -835,7 +837,7 @@ async function processN2MRehypeNoteLinkNodes(
const linkKey = randomString( const linkKey = randomString(
8, 8,
Zotero.Utilities.Internal.md5(node.properties.href), Zotero.Utilities.Internal.md5(node.properties.href),
Zotero.Utilities.allowedKeyChars Zotero.Utilities.allowedKeyChars,
); );
if (mode === NodeMode.wrap) { if (mode === NodeMode.wrap) {
const newNode = h("span", [ const newNode = h("span", [
@ -843,7 +845,7 @@ async function processN2MRehypeNoteLinkNodes(
h( h(
node.tagName, node.tagName,
Object.assign(node.properties, { href: link }), Object.assign(node.properties, { href: link }),
node.children node.children,
), ),
h("wrapperright", `note:${linkKey}`), h("wrapperright", `note:${linkKey}`),
]); ]);
@ -869,25 +871,25 @@ async function processN2MRehypeImageNodes(
dir: string, dir: string,
skipSavingImages: boolean = false, skipSavingImages: boolean = false,
absolutePath: boolean = false, absolutePath: boolean = false,
mode: NodeMode = NodeMode.default mode: NodeMode = NodeMode.default,
) { ) {
if (!nodes.length) { if (!nodes.length) {
return; return;
} }
for (const node of nodes) { for (const node of nodes) {
let imgKey = node.properties.dataAttachmentKey; const imgKey = node.properties.dataAttachmentKey;
const attachmentItem = (await Zotero.Items.getByLibraryAndKeyAsync( const attachmentItem = (await Zotero.Items.getByLibraryAndKeyAsync(
libraryID, libraryID,
imgKey imgKey,
)) as Zotero.Item; )) as Zotero.Item;
if (!attachmentItem) { if (!attachmentItem) {
continue; continue;
} }
let oldFile = String(await attachmentItem.getFilePathAsync()); const oldFile = String(await attachmentItem.getFilePathAsync());
let ext = oldFile.split(".").pop(); const ext = oldFile.split(".").pop();
let newAbsPath = formatPath(`${dir}/${imgKey}.${ext}`); const newAbsPath = formatPath(`${dir}/${imgKey}.${ext}`);
let newFile = oldFile; let newFile = oldFile;
try { try {
// Don't overwrite // Don't overwrite
@ -898,7 +900,7 @@ async function processN2MRehypeImageNodes(
newFile = newFile.replace(/\\/g, "/"); newFile = newFile.replace(/\\/g, "/");
} }
newFile = Zotero.File.normalizeToUnix( newFile = Zotero.File.normalizeToUnix(
absolutePath ? newFile : `attachments/${newFile.split(/\//).pop()}` absolutePath ? newFile : `attachments/${newFile.split(/\//).pop()}`,
); );
} catch (e) { } catch (e) {
ztoolkit.log(e); ztoolkit.log(e);
@ -922,7 +924,7 @@ function getM2NRehypeAnnotationNodes(rehype: any) {
visit( visit(
rehype, rehype,
(node: any) => node.type === "element" && node.properties?.dataAnnotation, (node: any) => node.type === "element" && node.properties?.dataAnnotation,
(node: any) => nodes.push(node) (node: any) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -933,7 +935,7 @@ function getM2NRehypeHighlightNodes(rehype: any) {
rehype, rehype,
(node: any) => (node: any) =>
node.type === "element" && node.properties?.ztype === "zhighlight", node.type === "element" && node.properties?.ztype === "zhighlight",
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -945,7 +947,7 @@ function getM2NRehypeCitationNodes(rehype: any) {
(node: any) => (node: any) =>
node.type === "element" && node.type === "element" &&
(node.properties?.ztype === "zcitation" || node.properties?.dataCitation), (node.properties?.ztype === "zcitation" || node.properties?.dataCitation),
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -956,7 +958,7 @@ function getM2NRehypeNoteLinkNodes(rehype: any) {
rehype, rehype,
(node: any) => (node: any) =>
node.type === "element" && node.properties?.ztype === "znotelink", node.type === "element" && node.properties?.ztype === "znotelink",
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -966,7 +968,7 @@ function getM2NRehypeImageNodes(rehype: any) {
visit( visit(
rehype, rehype,
(node: any) => node.type === "element" && node.tagName === "img", (node: any) => node.type === "element" && node.tagName === "img",
(node) => nodes.push(node) (node) => nodes.push(node),
); );
return new Array(...new Set(nodes)); return new Array(...new Set(nodes));
} }
@ -1002,7 +1004,7 @@ function processM2NRehypeHighlightNodes(nodes: string | any[]) {
async function processM2NRehypeCitationNodes( async function processM2NRehypeCitationNodes(
nodes: string | any[], nodes: string | any[],
isImport: boolean = false isImport: boolean = false,
) { ) {
if (!nodes.length) { if (!nodes.length) {
return; return;
@ -1021,10 +1023,10 @@ async function processM2NRehypeCitationNodes(
// "properties": {} // "properties": {}
// } // }
const dataCitation = JSON.parse( const dataCitation = JSON.parse(
decodeURIComponent(node.properties.dataCitation) decodeURIComponent(node.properties.dataCitation),
); );
const ids = dataCitation.citationItems.map((c: { uris: string[] }) => const ids = dataCitation.citationItems.map((c: { uris: string[] }) =>
Zotero.URI.getURIItemID(c.uris[0]) Zotero.URI.getURIItemID(c.uris[0]),
); );
const html = (await parseCitationHTML(ids)) || ""; const html = (await parseCitationHTML(ids)) || "";
const newNode = note2rehype(html); const newNode = note2rehype(html);
@ -1040,7 +1042,7 @@ async function processM2NRehypeCitationNodes(
(_n: any) => _n.properties?.className.includes("citation-item"), (_n: any) => _n.properties?.className.includes("citation-item"),
(_n) => { (_n) => {
_n.children = [{ type: "text", value: toText(_n) }]; _n.children = [{ type: "text", value: toText(_n) }];
} },
); );
delete node.properties?.ztype; delete node.properties?.ztype;
} }
@ -1064,7 +1066,7 @@ async function processM2NRehypeImageNodes(
nodes: any[], nodes: any[],
noteItem: Zotero.Item, noteItem: Zotero.Item,
fileDir: string, fileDir: string,
isImport: boolean = false isImport: boolean = false,
) { ) {
if (!nodes.length || (isImport && !noteItem)) { if (!nodes.length || (isImport && !noteItem)) {
return; return;
@ -1074,7 +1076,7 @@ async function processM2NRehypeImageNodes(
if (isImport) { if (isImport) {
// We encode the src in md2remark and decode it here. // We encode the src in md2remark and decode it here.
let src = Zotero.File.normalizeToUnix( let src = Zotero.File.normalizeToUnix(
decodeURIComponent(node.properties.src) decodeURIComponent(node.properties.src),
); );
const srcType = (src as string).startsWith("data:") const srcType = (src as string).startsWith("data:")
? "b64" ? "b64"

View File

@ -11,23 +11,23 @@ async function createWorkspaceNote() {
} }
const confirmOperation = window.confirm( const confirmOperation = window.confirm(
`${getString( `${getString(
"menuAddNote.newMainNote.confirmHead" "menuAddNote.newMainNote.confirmHead",
// @ts-ignore // @ts-ignore
)} '${currentCollection.getName()}' ${getString( )} '${currentCollection.getName()}' ${getString(
"menuAddNote.newMainNote.confirmTail" "menuAddNote.newMainNote.confirmTail",
)}` )}`,
); );
if (!confirmOperation) { if (!confirmOperation) {
return; return;
} }
const header = window.prompt( const header = window.prompt(
getString("menuAddNote.newMainNote.enterNoteTitle"), getString("menuAddNote.newMainNote.enterNoteTitle"),
`New Note ${new Date().toLocaleString()}` `New Note ${new Date().toLocaleString()}`,
); );
const noteID = await ZoteroPane.newNote(); const noteID = await ZoteroPane.newNote();
const noteItem = Zotero.Items.get(noteID); const noteItem = Zotero.Items.get(noteID);
noteItem.setNote( noteItem.setNote(
`<div data-schema-version="${config.dataSchemaVersion}"><h1>${header}</h1>\n</div>` `<div data-schema-version="${config.dataSchemaVersion}"><h1>${header}</h1>\n</div>`,
); );
await noteItem.saveTx(); await noteItem.saveTx();
addon.hooks.onSetWorkspaceNote(noteID, "main"); addon.hooks.onSetWorkspaceNote(noteID, "main");
@ -47,10 +47,8 @@ function getLibraryParentId() {
function getReaderParentId() { function getReaderParentId() {
const currentReader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID); const currentReader = Zotero.Reader.getByTabID(Zotero_Tabs.selectedID);
if (currentReader) {
}
const parentItemId = Zotero.Items.get( const parentItemId = Zotero.Items.get(
currentReader?.itemID || -1 currentReader?.itemID || -1,
).parentItemID; ).parentItemID;
return parentItemId; return parentItemId;
} }
@ -58,11 +56,11 @@ function getReaderParentId() {
async function createNoteFromTemplate(noteType: "standalone"): Promise<void>; async function createNoteFromTemplate(noteType: "standalone"): Promise<void>;
async function createNoteFromTemplate( async function createNoteFromTemplate(
noteType: "item", noteType: "item",
parentType: "reader" | "library" parentType: "reader" | "library",
): Promise<void>; ): Promise<void>;
async function createNoteFromTemplate( async function createNoteFromTemplate(
noteType: "standalone" | "item", noteType: "standalone" | "item",
parentType?: "reader" | "library" parentType?: "reader" | "library",
) { ) {
if (noteType === "item") { if (noteType === "item") {
const parentItemId = const parentItemId =
@ -94,7 +92,7 @@ async function createNoteFromMD() {
const filepaths = await new ztoolkit.FilePicker( const filepaths = await new ztoolkit.FilePicker(
"Import MarkDown", "Import MarkDown",
"multiple", "multiple",
[[`MarkDown(*.md)`, `*.md`]] [[`MarkDown(*.md)`, `*.md`]],
).open(); ).open();
if (!filepaths) { if (!filepaths) {

View File

@ -12,7 +12,7 @@ export function initEditorImagePreviewer(editor: Zotero.EditorInstance) {
addon.hooks.onShowImageViewer( addon.hooks.onShowImageViewer(
imageList.map((elem) => elem.src), imageList.map((elem) => elem.src),
imageList.indexOf(e.target as HTMLImageElement), imageList.indexOf(e.target as HTMLImageElement),
editor._item.getNoteTitle() editor._item.getNoteTitle(),
); );
}; };
editor._iframeWindow.document.addEventListener("dblclick", (e) => { editor._iframeWindow.document.addEventListener("dblclick", (e) => {

View File

@ -11,12 +11,12 @@ export function registerEditorInstanceHook() {
apply: ( apply: (
target, target,
thisArg, thisArg,
argumentsList: [instance: Zotero.EditorInstance] argumentsList: [instance: Zotero.EditorInstance],
) => { ) => {
target.apply(thisArg, argumentsList); target.apply(thisArg, argumentsList);
argumentsList.forEach(onEditorInstanceCreated); argumentsList.forEach(onEditorInstanceCreated);
}, },
} },
); );
} }

View File

@ -7,12 +7,12 @@ export async function injectEditorScripts(win: Window) {
id: "betternotes-script", id: "betternotes-script",
properties: { properties: {
innerHTML: await getFileContent( innerHTML: await getFileContent(
rootURI + "chrome/content/scripts/editorScript.js" rootURI + "chrome/content/scripts/editorScript.js",
), ),
}, },
ignoreIfExists: true, ignoreIfExists: true,
}, },
win.document.head win.document.head,
); );
} }
@ -60,6 +60,6 @@ export function injectEditorCSS(win: Window) {
}, },
ignoreIfExists: true, ignoreIfExists: true,
}, },
win.document.head win.document.head,
); );
} }

View File

@ -31,8 +31,8 @@ export function initEditorMenu(editor: Zotero.EditorInstance) {
editor._iframeWindow.prompt( editor._iframeWindow.prompt(
getString("editor.resizeImage.prompt"), getString("editor.resizeImage.prompt"),
// @ts-ignore // @ts-ignore
getEditorCore(editor).view.state.selection.node?.attrs?.width getEditorCore(editor).view.state.selection.node?.attrs?.width,
) || "" ) || "",
); );
if (newWidth && newWidth > 10) { if (newWidth && newWidth > 10) {
updateImageDimensionsAtCursor(editor, newWidth); updateImageDimensionsAtCursor(editor, newWidth);

View File

@ -38,7 +38,7 @@ export function initEditorPopup(editor: Zotero.EditorInstance) {
childList: true, childList: true,
attributes: true, attributes: true,
attributeFilter: ["href"], attributeFilter: ["href"],
} },
); );
} }
@ -68,7 +68,7 @@ async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
const templateText = await addon.api.template.runTemplate( const templateText = await addon.api.template.runTemplate(
"[QuickImportV2]", "[QuickImportV2]",
"link, noteItem", "link, noteItem",
[link, editorNote] [link, editorNote],
); );
// auto insert to anchor position // auto insert to anchor position
updateURLAtCursor( updateURLAtCursor(
@ -76,8 +76,8 @@ async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
undefined, undefined,
getNoteLink( getNoteLink(
linkNote, linkNote,
Object.assign({}, linkParams, { ignore: true }) Object.assign({}, linkParams, { ignore: true }),
)! )!,
); );
insert(editor, templateText); insert(editor, templateText);
} else { } else {
@ -86,14 +86,14 @@ async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
undefined, undefined,
getNoteLink( getNoteLink(
linkNote, linkNote,
Object.assign({}, linkParams, { ignore: null }) Object.assign({}, linkParams, { ignore: null }),
)! )!,
); );
const lineIndex = getLineAtCursor(editor); const lineIndex = getLineAtCursor(editor);
del( del(
editor, editor,
getPositionAtLine(editor, lineIndex), getPositionAtLine(editor, lineIndex),
getPositionAtLine(editor, lineIndex + 1) getPositionAtLine(editor, lineIndex + 1),
); );
} }
}, },
@ -117,7 +117,7 @@ async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
updateURLAtCursor( updateURLAtCursor(
editor, editor,
linkNote.getNoteTitle(), linkNote.getNoteTitle(),
getURLAtCursor(editor) getURLAtCursor(editor),
); );
}, },
}, },
@ -184,7 +184,7 @@ async function updateEditorLinkPopup(editor: Zotero.EditorInstance) {
// } // }
} else { } else {
Array.from(_window.document.querySelectorAll(".link-popup-extra")).forEach( Array.from(_window.document.querySelectorAll(".link-popup-extra")).forEach(
(elem) => elem.remove() (elem) => elem.remove(),
); );
} }
} }
@ -220,9 +220,9 @@ function updateEditorImagePopup(editor: Zotero.EditorInstance) {
editor._iframeWindow.document editor._iframeWindow.document
.querySelector(".primary-editor") .querySelector(".primary-editor")
?.querySelector(".selected") ?.querySelector(".selected")
?.querySelector("img") as HTMLImageElement ?.querySelector("img") as HTMLImageElement,
), ),
editor._item.getNoteTitle() editor._item.getNoteTitle(),
); );
}, },
}, },
@ -245,8 +245,8 @@ function updateEditorImagePopup(editor: Zotero.EditorInstance) {
getString("editor.resizeImage.prompt"), getString("editor.resizeImage.prompt"),
// @ts-ignore // @ts-ignore
getEditorCore(editor).view.state.selection.node?.attrs getEditorCore(editor).view.state.selection.node?.attrs
?.width ?.width,
) || "" ) || "",
); );
if (newWidth && newWidth > 10) { if (newWidth && newWidth > 10) {
updateImageDimensionsAtCursor(editor, newWidth); updateImageDimensionsAtCursor(editor, newWidth);
@ -257,6 +257,6 @@ function updateEditorImagePopup(editor: Zotero.EditorInstance) {
}, },
], ],
}, },
editor._iframeWindow.document.querySelector(".image-popup")! editor._iframeWindow.document.querySelector(".image-popup")!,
); );
} }

View File

@ -25,14 +25,14 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
ICONS.settings, ICONS.settings,
getString("editor.toolbar.settings.title"), getString("editor.toolbar.settings.title"),
"end", "end",
(e) => {} (e) => {},
); );
settingsButton.addEventListener("click", async (ev) => { settingsButton.addEventListener("click", async (ev) => {
ev.stopPropagation(); ev.stopPropagation();
function removePopup() { function removePopup() {
const popup = editor._iframeWindow.document.querySelector( const popup = editor._iframeWindow.document.querySelector(
`#${makeId("settings-popup")}` `#${makeId("settings-popup")}`,
); );
if (popup) { if (popup) {
popup.remove(); popup.remove();
@ -116,7 +116,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
`<a href="${link}">${ `<a href="${link}">${
e.editor._item.getNoteTitle().trim() || link e.editor._item.getNoteTitle().trim() || link
}</a>`, }</a>`,
"text/html" "text/html",
) )
.copy(); .copy();
showHint(`Link ${link} copied`); showHint(`Link ${link} copied`);
@ -158,7 +158,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
settingsButton, settingsButton,
`${config.addonRef}-settings-popup`, `${config.addonRef}-settings-popup`,
"right", "right",
settingsMenuData settingsMenuData,
).then((popup) => { ).then((popup) => {
settingsButton.querySelector(".toolbar-button")?.classList.add("active"); settingsButton.querySelector(".toolbar-button")?.classList.add("active");
editor._iframeWindow.document.addEventListener("click", removePopup); editor._iframeWindow.document.addEventListener("click", removePopup);
@ -173,7 +173,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
"middle", "middle",
ztoolkit.UI.createElement(editor._iframeWindow.document, "div", { ztoolkit.UI.createElement(editor._iframeWindow.document, "div", {
properties: { innerHTML: getString("editor.toolbar.main") }, properties: { innerHTML: getString("editor.toolbar.main") },
}) }),
); );
} else { } else {
const onTriggerMenu = (ev: MouseEvent) => { const onTriggerMenu = (ev: MouseEvent) => {
@ -188,7 +188,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
linkButton, linkButton,
`${config.addonRef}-link-popup`, `${config.addonRef}-link-popup`,
"middle", "middle",
linkMenu linkMenu,
); );
}; };
@ -207,7 +207,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
return; return;
} }
const lineIndex = parseInt( const lineIndex = parseInt(
(ev.target as HTMLDivElement).id.split("-").pop() || "-1" (ev.target as HTMLDivElement).id.split("-").pop() || "-1",
); );
const forwardLink = getNoteLink(noteItem); const forwardLink = getNoteLink(noteItem);
const backLink = getNoteLink(mainNote, { ignore: true, lineIndex }); const backLink = getNoteLink(mainNote, { ignore: true, lineIndex });
@ -221,9 +221,9 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
noteItem.getNoteTitle().trim() || forwardLink, noteItem.getNoteTitle().trim() || forwardLink,
noteItem, noteItem,
mainNote, mainNote,
] ],
), ),
lineIndex lineIndex,
); );
addLineToNote( addLineToNote(
noteItem, noteItem,
@ -236,8 +236,8 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
noteItem, noteItem,
mainNote, mainNote,
"", "",
] ],
) ),
); );
onExitMenu(ev); onExitMenu(ev);
ev.stopPropagation(); ev.stopPropagation();
@ -250,7 +250,7 @@ export async function initEditorToolbar(editor: Zotero.EditorInstance) {
ICONS.addon, ICONS.addon,
getString("editor.toolbar.link.title"), getString("editor.toolbar.link.title"),
"middle", "middle",
onClickMenu onClickMenu,
); );
linkButton.addEventListener("mouseenter", onTriggerMenu); linkButton.addEventListener("mouseenter", onTriggerMenu);
@ -312,7 +312,7 @@ function getLinkMenuData(editor: Zotero.EditorInstance): PopupData[] {
getPref("editor.link.insertPosition") getPref("editor.link.insertPosition")
? node.model.lineIndex - 1 ? node.model.lineIndex - 1
: node.model.endIndex : node.model.endIndex
}` }`,
), ),
text: node.model.name, text: node.model.name,
prefix: "·".repeat(node.model.level - 1), prefix: "·".repeat(node.model.level - 1),
@ -323,7 +323,7 @@ function getLinkMenuData(editor: Zotero.EditorInstance): PopupData[] {
async function registerEditorToolbar( async function registerEditorToolbar(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
id: string id: string,
) { ) {
await editor._initPromise; await editor._initPromise;
const _document = editor._iframeWindow.document; const _document = editor._iframeWindow.document;
@ -359,7 +359,7 @@ async function registerEditorToolbarDropdown(
icon: string, icon: string,
title: string, title: string,
position: "start" | "middle" | "end", position: "start" | "middle" | "end",
callback: (e: MouseEvent & { editor: Zotero.EditorInstance }) => any callback: (e: MouseEvent & { editor: Zotero.EditorInstance }) => any,
) { ) {
await editor._initPromise; await editor._initPromise;
const _document = editor._iframeWindow.document; const _document = editor._iframeWindow.document;
@ -386,7 +386,7 @@ async function registerEditorToolbarDropdown(
Object.assign(e, { editor }); Object.assign(e, { editor });
if (callback) { if (callback) {
callback( callback(
e as any as MouseEvent & { editor: Zotero.EditorInstance } e as any as MouseEvent & { editor: Zotero.EditorInstance },
); );
} }
}, },
@ -414,7 +414,7 @@ async function registerEditorToolbarPopup(
dropdown: HTMLDivElement, dropdown: HTMLDivElement,
id: string, id: string,
align: "middle" | "left" | "right", align: "middle" | "left" | "right",
popupLines: PopupData[] popupLines: PopupData[],
) { ) {
await editor._initPromise; await editor._initPromise;
const popup = ztoolkit.UI.appendElement( const popup = ztoolkit.UI.appendElement(
@ -449,7 +449,7 @@ async function registerEditorToolbarPopup(
props.callback( props.callback(
e as any as MouseEvent & { e as any as MouseEvent & {
editor: Zotero.EditorInstance; editor: Zotero.EditorInstance;
} },
); );
}, },
}, },
@ -458,7 +458,7 @@ async function registerEditorToolbarPopup(
}), }),
removeIfExists: true, removeIfExists: true,
}, },
dropdown dropdown,
) as HTMLDivElement; ) as HTMLDivElement;
let style: string = ""; let style: string = "";
if (align === "middle") { if (align === "middle") {
@ -476,7 +476,7 @@ async function registerEditorToolbarElement(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
toolbar: HTMLDivElement, toolbar: HTMLDivElement,
position: "start" | "middle" | "end", position: "start" | "middle" | "end",
elem: HTMLElement elem: HTMLElement,
) { ) {
await editor._initPromise; await editor._initPromise;
toolbar.querySelector(`.${position}`)?.append(elem); toolbar.querySelector(`.${position}`)?.append(elem);

View File

@ -22,7 +22,7 @@ async function exportNotes(
exportDocx?: boolean; exportDocx?: boolean;
exportPDF?: boolean; exportPDF?: boolean;
exportFreeMind?: boolean; exportFreeMind?: boolean;
} },
) { ) {
let inputNoteItems = noteItems; let inputNoteItems = noteItems;
// If embedLink or exportNote, create a new note item // If embedLink or exportNote, create a new note item
@ -51,26 +51,26 @@ async function exportNotes(
if (options.standaloneLink) { if (options.standaloneLink) {
const linkedNoteIds = [] as number[]; const linkedNoteIds = [] as number[];
for (const noteItem of inputNoteItems) { for (const noteItem of inputNoteItems) {
let linkedIds: number[] = getLinkedNotesRecursively( const linkedIds: number[] = getLinkedNotesRecursively(
getNoteLink(noteItem) || "", getNoteLink(noteItem) || "",
linkedNoteIds linkedNoteIds,
); );
linkedNoteIds.push(...linkedIds); linkedNoteIds.push(...linkedIds);
} }
const targetNoteItemIds = inputNoteItems.map((item) => item.id); const targetNoteItemIds = inputNoteItems.map((item) => item.id);
linkedNoteItems = Zotero.Items.get( linkedNoteItems = Zotero.Items.get(
linkedNoteIds.filter((id) => !targetNoteItemIds.includes(id)) linkedNoteIds.filter((id) => !targetNoteItemIds.includes(id)),
); );
} }
const allNoteItems = Array.from( const allNoteItems = Array.from(
new Set(inputNoteItems.concat(linkedNoteItems)) new Set(inputNoteItems.concat(linkedNoteItems)),
); );
if (options.exportMD) { if (options.exportMD) {
if (options.setAutoSync) { if (options.setAutoSync) {
const raw = await new ztoolkit.FilePicker( const raw = await new ztoolkit.FilePicker(
`${getString("fileInterface.sync")} MarkDown File`, `${getString("fileInterface.sync")} MarkDown File`,
"folder" "folder",
).open(); ).open();
if (raw) { if (raw) {
const syncDir = formatPath(raw); const syncDir = formatPath(raw);
@ -136,7 +136,7 @@ async function toMD(
filename?: string; filename?: string;
keepNoteLink?: boolean; keepNoteLink?: boolean;
withYAMLHeader?: boolean; withYAMLHeader?: boolean;
} = {} } = {},
) { ) {
let filename = options.filename; let filename = options.filename;
if (!filename) { if (!filename) {
@ -144,7 +144,7 @@ async function toMD(
`${Zotero.getString("fileInterface.export")} MarkDown File`, `${Zotero.getString("fileInterface.export")} MarkDown File`,
"save", "save",
[["MarkDown File(*.md)", "*.md"]], [["MarkDown File(*.md)", "*.md"]],
`${noteItem.getNoteTitle()}.md` `${noteItem.getNoteTitle()}.md`,
).open(); ).open();
if (!raw) return; if (!raw) return;
filename = formatPath(raw, ".md"); filename = formatPath(raw, ".md");
@ -155,7 +155,7 @@ async function toMD(
async function toSync( async function toSync(
noteItem: Zotero.Item, noteItem: Zotero.Item,
syncDir: string, syncDir: string,
overwrite: boolean = false overwrite: boolean = false,
) { ) {
if (!overwrite && addon.api.sync.isSyncNote(noteItem.id)) { if (!overwrite && addon.api.sync.isSyncNote(noteItem.id)) {
return; return;
@ -175,7 +175,7 @@ async function toDocx(noteItem: Zotero.Item) {
`${Zotero.getString("fileInterface.export")} MS Word Docx`, `${Zotero.getString("fileInterface.export")} MS Word Docx`,
"save", "save",
[["MS Word Docx File(*.docx)", "*.docx"]], [["MS Word Docx File(*.docx)", "*.docx"]],
`${noteItem.getNoteTitle()}.docx` `${noteItem.getNoteTitle()}.docx`,
).open(); ).open();
if (!raw) return; if (!raw) return;
const filename = formatPath(raw, ".docx"); const filename = formatPath(raw, ".docx");
@ -187,7 +187,7 @@ async function toFreeMind(noteItem: Zotero.Item) {
`${Zotero.getString("fileInterface.export")} FreeMind XML`, `${Zotero.getString("fileInterface.export")} FreeMind XML`,
"save", "save",
[["FreeMind XML File(*.mm)", "*.mm"]], [["FreeMind XML File(*.mm)", "*.mm"]],
`${noteItem.getNoteTitle()}.mm` `${noteItem.getNoteTitle()}.mm`,
).open(); ).open();
if (!raw) return; if (!raw) return;
const filename = formatPath(raw, ".mm"); const filename = formatPath(raw, ".mm");
@ -197,9 +197,9 @@ async function toFreeMind(noteItem: Zotero.Item) {
async function embedLinkedNotes(noteItem: Zotero.Item): Promise<string> { async function embedLinkedNotes(noteItem: Zotero.Item): Promise<string> {
const parser = ztoolkit.getDOMParser(); const parser = ztoolkit.getDOMParser();
let newLines: string[] = []; const newLines: string[] = [];
const noteLines = getLinesInNote(noteItem); const noteLines = getLinesInNote(noteItem);
for (let i in noteLines) { for (const i in noteLines) {
newLines.push(noteLines[i]); newLines.push(noteLines[i]);
const doc = parser.parseFromString(noteLines[i], "text/html"); const doc = parser.parseFromString(noteLines[i], "text/html");
const linkParams = Array.from(doc.querySelectorAll("a")) const linkParams = Array.from(doc.querySelectorAll("a"))
@ -210,7 +210,7 @@ async function embedLinkedNotes(noteItem: Zotero.Item): Promise<string> {
const html = await addon.api.template.runTemplate( const html = await addon.api.template.runTemplate(
"[QuickImportV2]", "[QuickImportV2]",
"link, noteItem", "link, noteItem",
[linkParam.link, noteItem] [linkParam.link, noteItem],
); );
newLines.push(html); newLines.push(html);
} }

View File

@ -35,7 +35,7 @@ async function note2docx(noteItem: Zotero.Item) {
jobId, jobId,
message: htmlDoc, message: htmlDoc,
}, },
"*" "*",
); );
await lock.promise; await lock.promise;
worker.contentWindow?.removeEventListener("message", listener); worker.contentWindow?.removeEventListener("message", listener);
@ -47,7 +47,7 @@ async function getWorker() {
return addon.data.export.docx.worker; return addon.data.export.docx.worker;
} }
const worker = Zotero.Browser.createHiddenBrowser( const worker = Zotero.Browser.createHiddenBrowser(
window window,
) as HTMLIFrameElement; ) as HTMLIFrameElement;
await waitUtilAsync(() => worker.contentDocument?.readyState === "complete"); await waitUtilAsync(() => worker.contentDocument?.readyState === "complete");
@ -57,11 +57,11 @@ async function getWorker() {
tag: "script", tag: "script",
properties: { properties: {
innerHTML: await getFileContent( innerHTML: await getFileContent(
rootURI + "chrome/content/scripts/docxWorker.js" rootURI + "chrome/content/scripts/docxWorker.js",
), ),
}, },
}, },
doc?.head! doc!.head,
); );
addon.data.export.docx.worker = worker; addon.data.export.docx.worker = worker;
return worker; return worker;

View File

@ -17,7 +17,7 @@ enum OPTIONS {
export async function showExportNoteOptions( export async function showExportNoteOptions(
noteIds: number[], noteIds: number[],
overwriteOptions: Record<string, any> = {} overwriteOptions: Record<string, any> = {},
) { ) {
const items = Zotero.Items.get(noteIds); const items = Zotero.Items.get(noteIds);
const noteItems: Zotero.Item[] = []; const noteItems: Zotero.Item[] = [];
@ -33,17 +33,20 @@ export async function showExportNoteOptions(
return; return;
} }
const dataKeys = Object.keys(OPTIONS).filter( const dataKeys = Object.keys(OPTIONS).filter(
(value) => typeof value === "string" (value) => typeof value === "string",
);
const data = dataKeys.reduce(
(acc, key) => {
acc[key] = getPref(`export.${key}`) as boolean;
return acc;
},
{} as Record<string, any>,
); );
const data = dataKeys.reduce((acc, key) => {
acc[key] = getPref(`export.${key}`) as boolean;
return acc;
}, {} as Record<string, any>);
data.loadCallback = () => { data.loadCallback = () => {
const doc = dialog.window.document; const doc = dialog.window.document;
const standaloneLinkRadio = doc.querySelector( const standaloneLinkRadio = doc.querySelector(
"#standaloneLink" "#standaloneLink",
) as HTMLInputElement; ) as HTMLInputElement;
const autoSyncRadio = doc.querySelector("#setAutoSync") as HTMLInputElement; const autoSyncRadio = doc.querySelector("#setAutoSync") as HTMLInputElement;
function updateSyncCheckbox() { function updateSyncCheckbox() {
@ -56,7 +59,7 @@ export async function showExportNoteOptions(
} }
} }
Array.from(doc.querySelectorAll('input[name="linkMode"]')).forEach((elem) => Array.from(doc.querySelectorAll('input[name="linkMode"]')).forEach((elem) =>
elem.addEventListener("change", updateSyncCheckbox) elem.addEventListener("change", updateSyncCheckbox),
); );
updateSyncCheckbox(); updateSyncCheckbox();
}; };
@ -77,7 +80,7 @@ export async function showExportNoteOptions(
properties: { properties: {
innerHTML: `${getString("export.target")}: ${fill( innerHTML: `${getString("export.target")}: ${fill(
slice(noteItems[0].getNoteTitle(), 40), slice(noteItems[0].getNoteTitle(), 40),
40 40,
)}${ )}${
noteItems.length > 1 ? ` and ${noteItems.length - 1} more` : "" noteItems.length > 1 ? ` and ${noteItems.length - 1} more` : ""
}`, }`,
@ -114,7 +117,7 @@ export async function showExportNoteOptions(
if (data._lastButtonId === "confirm") { if (data._lastButtonId === "confirm") {
await addon.api.$export.exportNotes( await addon.api.$export.exportNotes(
noteItems, noteItems,
Object.assign(data as Record<string, boolean>, overwriteOptions) Object.assign(data as Record<string, boolean>, overwriteOptions),
); );
dataKeys.forEach((key) => { dataKeys.forEach((key) => {
setPref(`export.${key}`, Boolean(data[key])); setPref(`export.${key}`, Boolean(data[key]));
@ -187,7 +190,7 @@ function makeCheckboxLine(dataKey: string, callback?: (ev: Event) => void) {
function makeRadioLine( function makeRadioLine(
dataKey: string, dataKey: string,
radioName: string, radioName: string,
callback?: (ev: Event) => void callback?: (ev: Event) => void,
) { ) {
return { return {
tag: "div", tag: "div",

View File

@ -12,10 +12,10 @@ export async function saveFreeMind(filename: string, noteId: number) {
async function note2mm( async function note2mm(
noteItem: Zotero.Item, noteItem: Zotero.Item,
options: { withContent?: boolean } = { withContent: true } options: { withContent?: boolean } = { withContent: true },
) { ) {
const root = getNoteTree(noteItem, false); const root = getNoteTree(noteItem, false);
const textNodeForEach = (e: Node, callbackfn: Function) => { const textNodeForEach = (e: Node, callbackfn: (e: any) => void) => {
if (e.nodeType === document.TEXT_NODE) { if (e.nodeType === document.TEXT_NODE) {
callbackfn(e); callbackfn(e);
return; return;
@ -57,7 +57,7 @@ async function note2mm(
}; };
const convertNode = (node: TreeModel.Node<NoteNodeData>) => { const convertNode = (node: TreeModel.Node<NoteNodeData>) => {
mmXML += `<node ID="${node.model.id}" TEXT="${html2Escape( mmXML += `<node ID="${node.model.id}" TEXT="${html2Escape(
node.model.name || noteItem.getNoteTitle() node.model.name || noteItem.getNoteTitle(),
)}"><hook NAME="AlwaysUnfoldedNode" />`; )}"><hook NAME="AlwaysUnfoldedNode" />`;
if ( if (
options.withContent && options.withContent &&
@ -70,9 +70,9 @@ async function note2mm(
node.model.lineIndex, node.model.lineIndex,
node.hasChildren() node.hasChildren()
? node.children[0].model.lineIndex ? node.children[0].model.lineIndex
: node.model.endIndex + 1 : node.model.endIndex + 1,
) )
.join("\n") .join("\n"),
)}</body></html></richcontent>`; )}</body></html></richcontent>`;
} }
if (node.hasChildren()) { if (node.hasChildren()) {

View File

@ -7,11 +7,11 @@ export async function saveMD(
options: { options: {
keepNoteLink?: boolean; keepNoteLink?: boolean;
withYAMLHeader?: boolean; withYAMLHeader?: boolean;
} },
) { ) {
const noteItem = Zotero.Items.get(noteId); const noteItem = Zotero.Items.get(noteId);
const dir = OS.Path.join( const dir = OS.Path.join(
...OS.Path.split(formatPath(filename)).components.slice(0, -1) ...OS.Path.split(formatPath(filename)).components.slice(0, -1),
); );
const hasImage = noteItem.getNote().includes("<img"); const hasImage = noteItem.getNote().includes("<img");
if (hasImage) { if (hasImage) {
@ -19,7 +19,7 @@ export async function saveMD(
} }
await Zotero.File.putContentsAsync( await Zotero.File.putContentsAsync(
filename, filename,
await addon.api.convert.note2md(noteItem, dir, options) await addon.api.convert.note2md(noteItem, dir, options),
); );
showHintWithLink(`Note Saved to ${filename}`, "Show in Folder", (ev) => { showHintWithLink(`Note Saved to ${filename}`, "Show in Folder", (ev) => {
@ -32,7 +32,7 @@ export async function syncMDBatch(saveDir: string, noteIds: number[]) {
await Zotero.File.createDirectoryIfMissingAsync(saveDir); await Zotero.File.createDirectoryIfMissingAsync(saveDir);
const attachmentsDir = formatPath(OS.Path.join(saveDir, "attachments")); const attachmentsDir = formatPath(OS.Path.join(saveDir, "attachments"));
const hasImage = noteItems.some((noteItem) => const hasImage = noteItems.some((noteItem) =>
noteItem.getNote().includes("<img") noteItem.getNote().includes("<img"),
); );
if (hasImage) { if (hasImage) {
await Zotero.File.createDirectoryIfMissingAsync(attachmentsDir); await Zotero.File.createDirectoryIfMissingAsync(attachmentsDir);
@ -51,7 +51,7 @@ export async function syncMDBatch(saveDir: string, noteIds: number[]) {
itemID: noteItem.id, itemID: noteItem.id,
md5: Zotero.Utilities.Internal.md5( md5: Zotero.Utilities.Internal.md5(
addon.api.sync.getMDStatusFromContent(content).content, addon.api.sync.getMDStatusFromContent(content).content,
false false,
), ),
noteMd5: Zotero.Utilities.Internal.md5(noteItem.getNote(), false), noteMd5: Zotero.Utilities.Internal.md5(noteItem.getNote(), false),
lastsync: new Date().getTime(), lastsync: new Date().getTime(),

View File

@ -9,7 +9,7 @@ export async function savePDF(noteId: number) {
const win = window.openDialog( const win = window.openDialog(
`chrome://${config.addonRef}/content/pdfPrinter.html`, `chrome://${config.addonRef}/content/pdfPrinter.html`,
`${config.addonRef}-imageViewer`, `${config.addonRef}-imageViewer`,
`chrome,centerscreen,resizable,status,width=900,height=650,dialog=no` `chrome,centerscreen,resizable,status,width=900,height=650,dialog=no`,
)!; )!;
await waitUtilAsync(() => win.document.readyState === "complete"); await waitUtilAsync(() => win.document.readyState === "complete");
await Zotero.Promise.delay(3000); await Zotero.Promise.delay(3000);

View File

@ -7,7 +7,7 @@ import { waitUtilAsync } from "../utils/wait";
export async function showImageViewer( export async function showImageViewer(
srcList: string[], srcList: string[],
idx: number, idx: number,
title: string title: string,
) { ) {
if ( if (
!addon.data.imageViewer.window || !addon.data.imageViewer.window ||
@ -19,16 +19,16 @@ export async function showImageViewer(
`${config.addonRef}-imageViewer`, `${config.addonRef}-imageViewer`,
`chrome,centerscreen,resizable,status,width=500,height=550,dialog=no${ `chrome,centerscreen,resizable,status,width=500,height=550,dialog=no${
addon.data.imageViewer.pined ? ",alwaysRaised=yes" : "" addon.data.imageViewer.pined ? ",alwaysRaised=yes" : ""
}` }`,
)!; )!;
await waitUtilAsync( await waitUtilAsync(
() => addon.data.imageViewer.window?.document.readyState === "complete" () => addon.data.imageViewer.window?.document.readyState === "complete",
); );
const container = addon.data.imageViewer.window.document.querySelector( const container = addon.data.imageViewer.window.document.querySelector(
".container" ".container",
) as HTMLDivElement; ) as HTMLDivElement;
const img = addon.data.imageViewer.window.document.querySelector( const img = addon.data.imageViewer.window.document.querySelector(
"#image" "#image",
) as HTMLImageElement; ) as HTMLImageElement;
addon.data.imageViewer.window.document addon.data.imageViewer.window.document
@ -75,26 +75,26 @@ export async function showImageViewer(
addon.data.imageViewer.window.document addon.data.imageViewer.window.document
.querySelector("#save") .querySelector("#save")
?.addEventListener("click", async (e) => { ?.addEventListener("click", async (e) => {
let parts = const parts =
addon.data.imageViewer.srcList[addon.data.imageViewer.idx].split(","); addon.data.imageViewer.srcList[addon.data.imageViewer.idx].split(",");
if (!parts[0].includes("base64")) { if (!parts[0].includes("base64")) {
return; return;
} }
let mime = parts[0].match(/:(.*?);/)![1]; const mime = parts[0].match(/:(.*?);/)![1];
let bstr = addon.data.imageViewer.window?.atob(parts[1])!; const bstr = ztoolkit.getGlobal("atob")(parts[1]);
let n = bstr.length; let n = bstr.length;
let u8arr = new Uint8Array(n); const u8arr = new Uint8Array(n);
while (n--) { while (n--) {
u8arr[n] = bstr.charCodeAt(n); u8arr[n] = bstr.charCodeAt(n);
} }
let ext = Zotero.MIME.getPrimaryExtension(mime, ""); const ext = Zotero.MIME.getPrimaryExtension(mime, "");
const filename = await new ztoolkit.FilePicker( const filename = await new ztoolkit.FilePicker(
Zotero.getString("noteEditor.saveImageAs"), Zotero.getString("noteEditor.saveImageAs"),
"save", "save",
[[`Image(*.${ext})`, `*.${ext}`]], [[`Image(*.${ext})`, `*.${ext}`]],
`${Zotero.getString("fileTypes.image").toLowerCase()}.${ext}`, `${Zotero.getString("fileTypes.image").toLowerCase()}.${ext}`,
addon.data.imageViewer.window, addon.data.imageViewer.window,
"images" "images",
).open(); ).open();
if (filename) { if (filename) {
await OS.File.writeAtomic(formatPath(filename), u8arr); await OS.File.writeAtomic(formatPath(filename), u8arr);
@ -103,7 +103,7 @@ export async function showImageViewer(
"Show in Folder", "Show in Folder",
(ev) => { (ev) => {
Zotero.File.reveal(filename); Zotero.File.reveal(filename);
} },
); );
} }
}); });
@ -112,7 +112,7 @@ export async function showImageViewer(
? ICONS.imageViewerPined ? ICONS.imageViewerPined
: ICONS.imageViewerPin; : ICONS.imageViewerPin;
addon.data.imageViewer.window.document.querySelector( addon.data.imageViewer.window.document.querySelector(
"#pin-tooltip" "#pin-tooltip",
)!.innerHTML = addon.data.imageViewer.pined ? "Unpin" : "Pin"; )!.innerHTML = addon.data.imageViewer.pined ? "Unpin" : "Pin";
addon.data.imageViewer.window.document addon.data.imageViewer.window.document
.querySelector("#pin") .querySelector("#pin")
@ -160,7 +160,7 @@ export async function showImageViewer(
if (e.ctrlKey) { if (e.ctrlKey) {
setScale( setScale(
addon.data.imageViewer.scaling * addon.data.imageViewer.scaling *
Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta))) Math.pow(delta > 0 ? 1.1 : 1 / 1.1, Math.round(Math.abs(delta))),
); );
} else if (e.shiftKey) { } else if (e.shiftKey) {
container.scrollLeft -= delta * 10; container.scrollLeft -= delta * 10;
@ -201,18 +201,18 @@ export async function showImageViewer(
function setImage() { function setImage() {
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#image" "#image",
) as HTMLImageElement ) as HTMLImageElement
).src = addon.data.imageViewer.srcList[addon.data.imageViewer.idx]; ).src = addon.data.imageViewer.srcList[addon.data.imageViewer.idx];
setTitle(); setTitle();
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#left-container" "#left-container",
) as HTMLButtonElement ) as HTMLButtonElement
).style.opacity = addon.data.imageViewer.idx === 0 ? "0.5" : "1"; ).style.opacity = addon.data.imageViewer.idx === 0 ? "0.5" : "1";
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#right-container" "#right-container",
) as HTMLButtonElement ) as HTMLButtonElement
).style.opacity = ).style.opacity =
addon.data.imageViewer.idx === addon.data.imageViewer.srcList.length - 1 addon.data.imageViewer.idx === addon.data.imageViewer.srcList.length - 1
@ -244,29 +244,29 @@ function setScale(scaling: number) {
addon.data.imageViewer.scaling = 0.1; addon.data.imageViewer.scaling = 0.1;
} }
const container = addon.data.imageViewer.window?.document.querySelector( const container = addon.data.imageViewer.window?.document.querySelector(
".container" ".container",
) as HTMLDivElement; ) as HTMLDivElement;
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#image" "#image",
) as HTMLImageElement ) as HTMLImageElement
).style.width = `calc(100% * ${addon.data.imageViewer.scaling})`; ).style.width = `calc(100% * ${addon.data.imageViewer.scaling})`;
if (addon.data.imageViewer.scaling > 1) { if (addon.data.imageViewer.scaling > 1) {
container.scrollLeft += container.scrollLeft +=
addon.data.imageViewer.anchorPosition?.left! * addon.data.imageViewer.anchorPosition!.left *
(addon.data.imageViewer.scaling - oldScale); (addon.data.imageViewer.scaling - oldScale);
container.scrollTop += container.scrollTop +=
addon.data.imageViewer.anchorPosition?.top! * addon.data.imageViewer.anchorPosition!.top *
(addon.data.imageViewer.scaling - oldScale); (addon.data.imageViewer.scaling - oldScale);
} }
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#bigger-container" "#bigger-container",
) as HTMLButtonElement ) as HTMLButtonElement
).style.opacity = addon.data.imageViewer.scaling === 10 ? "0.5" : "1"; ).style.opacity = addon.data.imageViewer.scaling === 10 ? "0.5" : "1";
( (
addon.data.imageViewer.window?.document.querySelector( addon.data.imageViewer.window?.document.querySelector(
"#smaller-container" "#smaller-container",
) as HTMLButtonElement ) as HTMLButtonElement
).style.opacity = addon.data.imageViewer.scaling === 0.1 ? "0.5" : "1"; ).style.opacity = addon.data.imageViewer.scaling === 0.1 ? "0.5" : "1";
// ( // (
@ -276,7 +276,7 @@ function setScale(scaling: number) {
function setTitle() { function setTitle() {
addon.data.imageViewer.window!.document.querySelector( addon.data.imageViewer.window!.document.querySelector(
"title" "title",
)!.innerText! = `${addon.data.imageViewer.idx + 1}/${ )!.innerText! = `${addon.data.imageViewer.idx + 1}/${
addon.data.imageViewer.srcList.length addon.data.imageViewer.srcList.length
}:${addon.data.imageViewer.title}`; }:${addon.data.imageViewer.title}`;
@ -288,6 +288,6 @@ function setPin() {
showImageViewer( showImageViewer(
addon.data.imageViewer.srcList, addon.data.imageViewer.srcList,
addon.data.imageViewer.idx, addon.data.imageViewer.idx,
addon.data.imageViewer.title addon.data.imageViewer.title,
); );
} }

View File

@ -8,7 +8,7 @@ export async function fromMD(
ignoreVersion?: boolean; ignoreVersion?: boolean;
append?: boolean; append?: boolean;
appendLineIndex?: number; appendLineIndex?: number;
} = {} } = {},
) { ) {
let mdStatus: MDStatus; let mdStatus: MDStatus;
try { try {
@ -26,7 +26,7 @@ export async function fromMD(
) { ) {
if ( if (
!window.confirm( !window.confirm(
`The target note seems to be newer than the file ${filepath}. Are you sure you want to import it anyway?` `The target note seems to be newer than the file ${filepath}. Are you sure you want to import it anyway?`,
) )
) { ) {
return; return;

View File

@ -13,7 +13,7 @@ export function registerMenus() {
icon: `chrome://${config.addonRef}/content/icons/favicon.png`, icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
commandListener: (ev) => { commandListener: (ev) => {
addon.hooks.onShowExportNoteOptions( addon.hooks.onShowExportNoteOptions(
ZoteroPane.getSelectedItems().map((item) => item.id) ZoteroPane.getSelectedItems().map((item) => item.id),
); );
}, },
}); });
@ -36,7 +36,7 @@ export function registerMenus() {
// menuEdit // menuEdit
const menuEditAnchor = document.querySelector( const menuEditAnchor = document.querySelector(
"#menu_EditPreferencesItem" "#menu_EditPreferencesItem",
) as XUL.MenuItem; ) as XUL.MenuItem;
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuEdit", "menuEdit",
@ -49,7 +49,7 @@ export function registerMenus() {
}, },
}, },
"before", "before",
menuEditAnchor menuEditAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuEdit", "menuEdit",
@ -62,7 +62,7 @@ export function registerMenus() {
}, },
}, },
"before", "before",
menuEditAnchor menuEditAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuEdit", "menuEdit",
@ -75,7 +75,7 @@ export function registerMenus() {
}, },
}, },
"before", "before",
menuEditAnchor menuEditAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuEdit", "menuEdit",
@ -88,13 +88,13 @@ export function registerMenus() {
}, },
}, },
"before", "before",
menuEditAnchor menuEditAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuEdit", "menuEdit",
{ tag: "menuseparator" }, { tag: "menuseparator" },
"before", "before",
menuEditAnchor menuEditAnchor,
); );
// menuTools // menuTools
@ -110,7 +110,7 @@ export function registerMenus() {
// menuFile // menuFile
const menuFileAnchor = document.querySelector( const menuFileAnchor = document.querySelector(
"#menu_newCollection" "#menu_newCollection",
) as XUL.MenuItem; ) as XUL.MenuItem;
const recentMainNotesMenuId = "zotero-recent-main-notes-menu"; const recentMainNotesMenuId = "zotero-recent-main-notes-menu";
const recentMainNotesMenuPopupId = "zotero-recent-main-notes-popup"; const recentMainNotesMenuPopupId = "zotero-recent-main-notes-popup";
@ -130,7 +130,7 @@ export function registerMenus() {
popupId: recentMainNotesMenuPopupId, popupId: recentMainNotesMenuPopupId,
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
document document
@ -142,7 +142,7 @@ export function registerMenus() {
((getPref("recentMainNoteIds") as string) || "") ((getPref("recentMainNoteIds") as string) || "")
.split(",") .split(",")
.map((id) => parseInt(id)) .map((id) => parseInt(id))
.filter((id) => id !== addon.data.workspace.mainId) .filter((id) => id !== addon.data.workspace.mainId),
) )
.filter((item) => item.isNote()) .filter((item) => item.isNote())
.map((item) => ({ .map((item) => ({
@ -155,11 +155,11 @@ export function registerMenus() {
: "📁" + : "📁" +
Zotero.Collections.get(item.getCollections()) Zotero.Collections.get(item.getCollections())
.map( .map(
(collection) => (collection as Zotero.Collection).name (collection) => (collection as Zotero.Collection).name,
) )
.join(", ") .join(", ")
}`, }`,
200 200,
), ),
}, },
listeners: [ listeners: [
@ -188,7 +188,7 @@ export function registerMenus() {
children: children.length === 0 ? defaultChildren : children, children: children.length === 0 ? defaultChildren : children,
enableElementRecord: false, enableElementRecord: false,
}, },
popup popup,
); );
return true; return true;
}); });
@ -213,13 +213,13 @@ export function registerMenus() {
}, },
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuFile", "menuFile",
{ tag: "menuseparator" }, { tag: "menuseparator" },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
// a copy of create note menu in library // a copy of create note menu in library
ztoolkit.Menu.register( ztoolkit.Menu.register(
@ -231,7 +231,7 @@ export function registerMenus() {
commandListener: () => addon.hooks.onCreateNoteFromMD(), commandListener: () => addon.hooks.onCreateNoteFromMD(),
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuFile", "menuFile",
@ -243,7 +243,7 @@ export function registerMenus() {
addon.hooks.onCreateNoteFromTemplate("item", "library"), addon.hooks.onCreateNoteFromTemplate("item", "library"),
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuFile", "menuFile",
@ -254,7 +254,7 @@ export function registerMenus() {
commandListener: () => addon.hooks.onCreateNoteFromTemplate("standalone"), commandListener: () => addon.hooks.onCreateNoteFromTemplate("standalone"),
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
ztoolkit.Menu.register( ztoolkit.Menu.register(
"menuFile", "menuFile",
@ -265,7 +265,7 @@ export function registerMenus() {
commandListener: addon.hooks.onCreateWorkspaceNote, commandListener: addon.hooks.onCreateWorkspaceNote,
}, },
"after", "after",
menuFileAnchor menuFileAnchor,
); );
// create note menu in library // create note menu in library
@ -301,7 +301,7 @@ export function registerMenus() {
// create note menu in reader side panel // create note menu in reader side panel
ztoolkit.Menu.register( ztoolkit.Menu.register(
document.querySelector( document.querySelector(
"#context-pane-add-child-note-button-popup" "#context-pane-add-child-note-button-popup",
) as XUL.MenuPopup, ) as XUL.MenuPopup,
{ {
tag: "menuitem", tag: "menuitem",
@ -309,6 +309,6 @@ export function registerMenus() {
icon: `chrome://${config.addonRef}/content/icons/favicon.png`, icon: `chrome://${config.addonRef}/content/icons/favicon.png`,
commandListener: () => commandListener: () =>
addon.hooks.onCreateNoteFromTemplate("item", "reader"), addon.hooks.onCreateNoteFromTemplate("item", "reader"),
} },
); );
} }

View File

@ -18,7 +18,7 @@ export function registerNotify(types: _ZoteroTypes.Notifier.Type[]) {
(e: Event) => { (e: Event) => {
unregisterNotify(notifyID); unregisterNotify(notifyID);
}, },
false false,
); );
} }

View File

@ -56,8 +56,11 @@ async function updatePrefsUI() {
// You can initialize some UI elements on prefs window // You can initialize some UI elements on prefs window
// with addon.data.prefs.window.document // with addon.data.prefs.window.document
// Or bind some events to the elements // Or bind some events to the elements
if (!addon.data.prefs?.window) {
return;
}
const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer(); const renderLock = ztoolkit.getGlobal("Zotero").Promise.defer();
const tableHelper = new ztoolkit.VirtualizedTable(addon.data.prefs?.window!) const tableHelper = new ztoolkit.VirtualizedTable(addon.data.prefs.window)
.setContainerId(`${config.addonRef}-table-container`) .setContainerId(`${config.addonRef}-table-container`)
.setProp({ .setProp({
id: `${config.addonRef}-prefs-table`, id: `${config.addonRef}-prefs-table`,
@ -66,7 +69,7 @@ async function updatePrefsUI() {
columns: addon.data.prefs?.columns.map((column) => columns: addon.data.prefs?.columns.map((column) =>
Object.assign(column, { Object.assign(column, {
label: getString(column.label) || column.label, label: getString(column.label) || column.label,
}) }),
), ),
showHeader: true, showHeader: true,
multiSelect: true, multiSelect: true,
@ -80,7 +83,7 @@ async function updatePrefsUI() {
addon.data.prefs?.rows[index] || { addon.data.prefs?.rows[index] || {
title: "no data", title: "no data",
detail: "no data", detail: "no data",
} },
) )
// Show a progress window when selection changes // Show a progress window when selection changes
.setProp("onSelectionChange", (selection) => { .setProp("onSelectionChange", (selection) => {
@ -100,7 +103,7 @@ async function updatePrefsUI() {
if (event.key == "Delete" || (Zotero.isMac && event.key == "Backspace")) { if (event.key == "Delete" || (Zotero.isMac && event.key == "Backspace")) {
addon.data.prefs!.rows = addon.data.prefs!.rows =
addon.data.prefs?.rows.filter( addon.data.prefs?.rows.filter(
(v, i) => !tableHelper.treeInstance.selection.isSelected(i) (v, i) => !tableHelper.treeInstance.selection.isSelected(i),
) || []; ) || [];
tableHelper.render(); tableHelper.render();
return false; return false;
@ -110,7 +113,7 @@ async function updatePrefsUI() {
// For find-as-you-type // For find-as-you-type
.setProp( .setProp(
"getRowString", "getRowString",
(index) => addon.data.prefs?.rows[index].title || "" (index) => addon.data.prefs?.rows[index].title || "",
) )
// Render the table. // Render the table.
.render(-1, () => { .render(-1, () => {
@ -123,23 +126,23 @@ async function updatePrefsUI() {
function bindPrefEvents() { function bindPrefEvents() {
addon.data addon.data
.prefs!.window.document.querySelector( .prefs!.window.document.querySelector(
`#zotero-prefpane-${config.addonRef}-enable` `#zotero-prefpane-${config.addonRef}-enable`,
) )
?.addEventListener("command", (e) => { ?.addEventListener("command", (e) => {
ztoolkit.log(e); ztoolkit.log(e);
addon.data.prefs!.window.alert( addon.data.prefs!.window.alert(
`Successfully changed to ${(e.target as XUL.Checkbox).checked}!` `Successfully changed to ${(e.target as XUL.Checkbox).checked}!`,
); );
}); });
addon.data addon.data
.prefs!!.window.document.querySelector( .prefs!.window.document.querySelector(
`#zotero-prefpane-${config.addonRef}-input` `#zotero-prefpane-${config.addonRef}-input`,
) )
?.addEventListener("change", (e) => { ?.addEventListener("change", (e) => {
ztoolkit.log(e); ztoolkit.log(e);
addon.data.prefs!.window.alert( addon.data.prefs!.window.alert(
`Successfully changed to ${(e.target as HTMLInputElement).value}!` `Successfully changed to ${(e.target as HTMLInputElement).value}!`,
); );
}); });
} }

View File

@ -8,7 +8,7 @@ export function registerReaderInitializer() {
ztoolkit.ReaderInstance.register( ztoolkit.ReaderInstance.register(
"initialized", "initialized",
`${config.addonRef}-annotationButtons`, `${config.addonRef}-annotationButtons`,
initializeReaderAnnotationButton initializeReaderAnnotationButton,
); );
// Force re-initialize // Force re-initialize
Zotero.Reader._readers.forEach((r) => { Zotero.Reader._readers.forEach((r) => {
@ -38,7 +38,7 @@ export async function checkReaderAnnotationButton(items: Zotero.Item[]) {
} }
async function initializeReaderAnnotationButton( async function initializeReaderAnnotationButton(
instance: _ZoteroTypes.ReaderInstance instance: _ZoteroTypes.ReaderInstance,
): Promise<Zotero.Item[]> { ): Promise<Zotero.Item[]> {
if (!instance) { if (!instance) {
return []; return [];
@ -68,7 +68,7 @@ async function initializeReaderAnnotationButton(
const libraryID = Zotero.Items.get(instance.itemID).libraryID; const libraryID = Zotero.Items.get(instance.itemID).libraryID;
const annotationItem = (await Zotero.Items.getByLibraryAndKeyAsync( const annotationItem = (await Zotero.Items.getByLibraryAndKeyAsync(
libraryID, libraryID,
itemKey itemKey,
)) as Zotero.Item; )) as Zotero.Item;
if (!annotationItem) { if (!annotationItem) {
@ -90,7 +90,7 @@ async function initializeReaderAnnotationButton(
listener: (e) => { listener: (e) => {
createNoteFromAnnotation( createNoteFromAnnotation(
annotationItem, annotationItem,
(e as MouseEvent).shiftKey ? "standalone" : "auto" (e as MouseEvent).shiftKey ? "standalone" : "auto",
); );
e.preventDefault(); e.preventDefault();
}, },
@ -105,7 +105,7 @@ async function initializeReaderAnnotationButton(
type: "mouseout", type: "mouseout",
listener: (e) => { listener: (e) => {
(e.target as HTMLElement).style.removeProperty( (e.target as HTMLElement).style.removeProperty(
"background-color" "background-color",
); );
}, },
}, },
@ -139,7 +139,7 @@ async function initializeReaderAnnotationButton(
type: "mouseout", type: "mouseout",
listener: (e) => { listener: (e) => {
(e.target as HTMLElement).style.removeProperty( (e.target as HTMLElement).style.removeProperty(
"background-color" "background-color",
); );
}, },
}, },
@ -153,14 +153,14 @@ async function initializeReaderAnnotationButton(
tag: "fragment", tag: "fragment",
children: annotationButtons, children: annotationButtons,
}, },
moreButton moreButton,
); );
} }
return hitItems; return hitItems;
} }
async function unInitializeReaderAnnotationButton( async function unInitializeReaderAnnotationButton(
instance: _ZoteroTypes.ReaderInstance instance: _ZoteroTypes.ReaderInstance,
): Promise<void> { ): Promise<void> {
if (!instance) { if (!instance) {
return; return;
@ -180,7 +180,7 @@ async function unInitializeReaderAnnotationButton(
async function createNoteFromAnnotation( async function createNoteFromAnnotation(
annotationItem: Zotero.Item, annotationItem: Zotero.Item,
openMode: "standalone" | "auto" = "auto" openMode: "standalone" | "auto" = "auto",
) { ) {
const annotationTags = annotationItem.getTags().map((_) => _.tag); const annotationTags = annotationItem.getTags().map((_) => _.tag);
const linkRegex = new RegExp("^zotero://note/(.*)$"); const linkRegex = new RegExp("^zotero://note/(.*)$");
@ -209,7 +209,7 @@ async function createNoteFromAnnotation(
const renderredTemplate = await addon.api.template.runTemplate( const renderredTemplate = await addon.api.template.runTemplate(
"[QuickNoteV5]", "[QuickNoteV5]",
"annotationItem, topItem, noteItem", "annotationItem, topItem, noteItem",
[annotationItem, annotationItem.parentItem!.parentItem, note] [annotationItem, annotationItem.parentItem!.parentItem, note],
); );
await addLineToNote(note, renderredTemplate); await addLineToNote(note, renderredTemplate);

View File

@ -38,7 +38,7 @@ async function getRelatedNoteIds(noteId: number): Promise<number[]> {
} }
const subNoteIds = ( const subNoteIds = (
await Promise.all( await Promise.all(
linkMatches.map(async (link) => getNoteLinkParams(link).noteItem) linkMatches.map(async (link) => getNoteLinkParams(link).noteItem),
) )
) )
.filter((item) => item && item.isNote()) .filter((item) => item && item.isNote())
@ -49,7 +49,7 @@ async function getRelatedNoteIds(noteId: number): Promise<number[]> {
} }
async function getRelatedNoteIdsFromNotes( async function getRelatedNoteIdsFromNotes(
noteIds: number[] noteIds: number[],
): Promise<number[]> { ): Promise<number[]> {
let allNoteIds: number[] = []; let allNoteIds: number[] = [];
for (const noteId of noteIds) { for (const noteId of noteIds) {
@ -101,7 +101,7 @@ function getNoteStatus(noteId: number) {
if (idx != -1) { if (idx != -1) {
ret.content = fullContent.substring( ret.content = fullContent.substring(
idx + match[0].length, idx + match[0].length,
fullContent.length - ret.tail.length fullContent.length - ret.tail.length,
); );
} }
return ret; return ret;
@ -117,7 +117,7 @@ function getSyncStatus(noteId?: number): SyncStatus {
itemID: -1, itemID: -1,
}); });
return JSON.parse( return JSON.parse(
(getPref(`syncDetail-${noteId}`) as string) || defaultStatus (getPref(`syncDetail-${noteId}`) as string) || defaultStatus,
); );
} }
@ -143,7 +143,7 @@ function getMDStatusFromContent(contentRaw: string): MDStatus {
} }
async function getMDStatus( async function getMDStatus(
source: Zotero.Item | number | string source: Zotero.Item | number | string,
): Promise<MDStatus> { ): Promise<MDStatus> {
let ret: MDStatus = { let ret: MDStatus = {
meta: null, meta: null,
@ -165,13 +165,13 @@ async function getMDStatus(
} }
filepath = Zotero.File.normalizeToUnix(filepath); filepath = Zotero.File.normalizeToUnix(filepath);
if (await OS.File.exists(filepath)) { if (await OS.File.exists(filepath)) {
let contentRaw = (await OS.File.read(filepath, { const contentRaw = (await OS.File.read(filepath, {
encoding: "utf-8", encoding: "utf-8",
})) as string; })) as string;
ret = getMDStatusFromContent(contentRaw); ret = getMDStatusFromContent(contentRaw);
const pathSplit = filepath.split("/"); const pathSplit = filepath.split("/");
ret.filedir = Zotero.File.normalizeToUnix( ret.filedir = Zotero.File.normalizeToUnix(
pathSplit.slice(0, -1).join("/") pathSplit.slice(0, -1).join("/"),
); );
ret.filename = filepath.split("/").pop() || ""; ret.filename = filepath.split("/").pop() || "";
const stat = await OS.File.stat(filepath); const stat = await OS.File.stat(filepath);
@ -214,7 +214,7 @@ async function getMDFileName(noteId: number, searchDir?: string) {
} }
} }
} }
} },
); );
if (matchedFileName) { if (matchedFileName) {
return matchedFileName; return matchedFileName;
@ -224,6 +224,6 @@ async function getMDFileName(noteId: number, searchDir?: string) {
return await addon.api.template.runTemplate( return await addon.api.template.runTemplate(
"[ExportMDFileNameV2]", "[ExportMDFileNameV2]",
"noteItem", "noteItem",
[noteItem] [noteItem],
); );
} }

View File

@ -50,7 +50,7 @@ export async function showSyncDiff(noteId: number, mdPath: string) {
} else { } else {
// Otherwise, merge manually // Otherwise, merge manually
const imageAttachemnts = Zotero.Items.get(noteItem.getAttachments()).filter( const imageAttachemnts = Zotero.Items.get(noteItem.getAttachments()).filter(
(attch) => attch.isEmbeddedImageAttachment() (attch) => attch.isEmbeddedImageAttachment(),
); );
const imageData = {} as Record<string, string>; const imageData = {} as Record<string, string>;
for (const image of imageAttachemnts) { for (const image of imageAttachemnts) {
@ -66,10 +66,10 @@ export async function showSyncDiff(noteId: number, mdPath: string) {
addon.data.sync.diff.window = window.open( addon.data.sync.diff.window = window.open(
`chrome://${config.addonRef}/content/syncDiff.html`, `chrome://${config.addonRef}/content/syncDiff.html`,
`${config.addonRef}-syncDiff`, `${config.addonRef}-syncDiff`,
`chrome,centerscreen,resizable,status,width=900,height=550` `chrome,centerscreen,resizable,status,width=900,height=550`,
)!; )!;
await waitUtilAsync( await waitUtilAsync(
() => addon.data.sync.diff.window?.document.readyState === "complete" () => addon.data.sync.diff.window?.document.readyState === "complete",
); );
} }
const win = addon.data.sync.diff.window as any; const win = addon.data.sync.diff.window as any;
@ -86,7 +86,7 @@ export async function showSyncDiff(noteId: number, mdPath: string) {
Object.assign(change, { Object.assign(change, {
id: id, id: id,
text: change.value, text: change.value,
}) }),
); );
win.imageData = imageData; win.imageData = imageData;
@ -115,7 +115,7 @@ export async function showSyncDiff(noteId: number, mdPath: string) {
switch (io.type) { switch (io.type) {
case "skip": case "skip":
alert( alert(
`Syncing of "${noteItem.getNoteTitle()}" is skipped.\nTo sync manually, go to File->Better Notes Sync Manager.` `Syncing of "${noteItem.getNoteTitle()}" is skipped.\nTo sync manually, go to File->Better Notes Sync Manager.`,
); );
addon.data.sync.diff.window?.closed || addon.data.sync.diff.window?.closed ||
addon.data.sync.diff.window?.close(); addon.data.sync.diff.window?.close();

View File

@ -8,20 +8,26 @@ function setSyncing() {
const syncPeriod = getPref("syncPeriodSeconds") as number; const syncPeriod = getPref("syncPeriodSeconds") as number;
if (syncPeriod > 0) { if (syncPeriod > 0) {
showHint(`${getString("sync.start.hint")} ${syncPeriod} s`); showHint(`${getString("sync.start.hint")} ${syncPeriod} s`);
const timer = ztoolkit.getGlobal("setInterval")(() => { const timer = ztoolkit.getGlobal("setInterval")(
if (!addon.data.alive) { () => {
showHint(getString("sync.stop.hint")); if (!addon.data.alive) {
ztoolkit.getGlobal("clearInterval")(timer); showHint(getString("sync.stop.hint"));
} ztoolkit.getGlobal("clearInterval")(timer);
// Only when Zotero is active and focused }
if (document.hasFocus() && (getPref("syncPeriodSeconds") as number) > 0) { // Only when Zotero is active and focused
callSyncing(undefined, { if (
quiet: true, document.hasFocus() &&
skipActive: true, (getPref("syncPeriodSeconds") as number) > 0
reason: "auto", ) {
}); callSyncing(undefined, {
} quiet: true,
}, Number(syncPeriod) * 1000); skipActive: true,
reason: "auto",
});
}
},
Number(syncPeriod) * 1000,
);
} }
} }
@ -31,7 +37,7 @@ async function callSyncing(
quiet: true, quiet: true,
skipActive: true, skipActive: true,
reason: "unknown", reason: "unknown",
} },
) { ) {
// Always log in development mode // Always log in development mode
if (addon.data.env === "development") { if (addon.data.env === "development") {
@ -61,11 +67,11 @@ async function callSyncing(
.filter( .filter(
(editor) => (editor) =>
!Components.utils.isDeadWrapper(editor._iframeWindow) && !Components.utils.isDeadWrapper(editor._iframeWindow) &&
editor._iframeWindow.document.hasFocus() editor._iframeWindow.document.hasFocus(),
) )
.map((editor) => editor._item.id); .map((editor) => editor._item.id);
const filteredItems = items.filter( const filteredItems = items.filter(
(item) => !activeNoteIds.includes(item.id) (item) => !activeNoteIds.includes(item.id),
); );
skippedCount = items.length - filteredItems.length; skippedCount = items.length - filteredItems.length;
items = filteredItems; items = filteredItems;
@ -76,7 +82,7 @@ async function callSyncing(
progress = new ztoolkit.ProgressWindow( progress = new ztoolkit.ProgressWindow(
`[${getString("sync.running.hint.title")}] ${ `[${getString("sync.running.hint.title")}] ${
addon.data.env === "development" ? reason : "Better Notes" addon.data.env === "development" ? reason : "Better Notes"
}` }`,
) )
.createLine({ .createLine({
text: `[${getString("sync.running.hint.check")}] 0/${ text: `[${getString("sync.running.hint.check")}] 0/${
@ -95,7 +101,7 @@ async function callSyncing(
for (const item of items) { for (const item of items) {
const syncStatus = addon.api.sync.getSyncStatus(item.id); const syncStatus = addon.api.sync.getSyncStatus(item.id);
const filepath = syncStatus.path; const filepath = syncStatus.path;
let compareResult = await doCompare(item); const compareResult = await doCompare(item);
switch (compareResult) { switch (compareResult) {
case SyncCode.NoteAhead: case SyncCode.NoteAhead:
if (Object.keys(toExport).includes(filepath)) { if (Object.keys(toExport).includes(filepath)) {
@ -139,7 +145,7 @@ async function callSyncing(
for (const syncStatus of toImport) { for (const syncStatus of toImport) {
progress?.changeLine({ progress?.changeLine({
text: `[${getString( text: `[${getString(
"sync.running.hint.updateNote" "sync.running.hint.updateNote",
)}] ${i}/${totalCount}, ${toDiff.length} queuing...`, )}] ${i}/${totalCount}, ${toDiff.length} queuing...`,
progress: ((i - 1) / totalCount) * 100, progress: ((i - 1) / totalCount) * 100,
}); });
@ -160,7 +166,7 @@ async function callSyncing(
await addon.hooks.onShowSyncDiff( await addon.hooks.onShowSyncDiff(
syncStatus.itemID, syncStatus.itemID,
OS.Path.join(syncStatus.path, syncStatus.filename) OS.Path.join(syncStatus.path, syncStatus.filename),
); );
i += 1; i += 1;
} }
@ -170,10 +176,10 @@ async function callSyncing(
text: text:
(syncCount (syncCount
? `[${getString( ? `[${getString(
"sync.running.hint.finish" "sync.running.hint.finish",
)}] ${syncCount} ${getString("sync.running.hint.synced")}` )}] ${syncCount} ${getString("sync.running.hint.synced")}`
: `[${getString("sync.running.hint.finish")}] ${getString( : `[${getString("sync.running.hint.finish")}] ${getString(
"sync.running.hint.upToDate" "sync.running.hint.upToDate",
)}`) + (skippedCount ? `, ${skippedCount} skipped.` : ""), )}`) + (skippedCount ? `, ${skippedCount} skipped.` : ""),
progress: 100, progress: 100,
}); });

View File

@ -18,7 +18,7 @@ export async function showSyncInfo(noteId: number) {
tag: "label", tag: "label",
properties: { properties: {
innerHTML: formatPath( innerHTML: formatPath(
OS.Path.join(slice(status.path, 30), status.filename) OS.Path.join(slice(status.path, 30), status.filename),
), ),
}, },
}) })
@ -57,7 +57,7 @@ export async function showSyncInfo(noteId: number) {
noClose: true, noClose: true,
callback: (ev) => { callback: (ev) => {
Zotero.File.reveal( Zotero.File.reveal(
formatPath(OS.Path.join(status.path, status.filename)) formatPath(OS.Path.join(status.path, status.filename)),
); );
}, },
}) })

View File

@ -15,7 +15,7 @@ export async function showSyncManager() {
`chrome://${config.addonRef}/content/syncManager.xhtml`, `chrome://${config.addonRef}/content/syncManager.xhtml`,
`${config.addonRef}-syncManager`, `${config.addonRef}-syncManager`,
`chrome,centerscreen,resizable,status,width=800,height=400,dialog=no`, `chrome,centerscreen,resizable,status,width=800,height=400,dialog=no`,
windowArgs windowArgs,
)!; )!;
await windowArgs._initPromise.promise; await windowArgs._initPromise.promise;
addon.data.sync.manager.window = win; addon.data.sync.manager.window = win;
@ -46,7 +46,7 @@ export async function showSyncManager() {
].map((column) => ].map((column) =>
Object.assign(column, { Object.assign(column, {
label: getString(column.label), label: getString(column.label),
}) }),
), ),
showHeader: true, showHeader: true,
multiSelect: true, multiSelect: true,
@ -65,7 +65,7 @@ export async function showSyncManager() {
noteName: "no data", noteName: "no data",
lastSync: "no data", lastSync: "no data",
filePath: "no data", filePath: "no data",
} },
) )
.setProp("onSelectionChange", (selection) => { .setProp("onSelectionChange", (selection) => {
updateButtons(); updateButtons();
@ -84,21 +84,21 @@ export async function showSyncManager() {
.setProp("onActivate", (ev) => { .setProp("onActivate", (ev) => {
const noteIds = getSelectedNoteIds(); const noteIds = getSelectedNoteIds();
noteIds.forEach((noteId) => noteIds.forEach((noteId) =>
addon.hooks.onOpenNote(noteId, "standalone") addon.hooks.onOpenNote(noteId, "standalone"),
); );
return true; return true;
}) })
.setProp( .setProp(
"getRowString", "getRowString",
(index) => addon.data.prefs?.rows[index].title || "" (index) => addon.data.prefs?.rows[index].title || "",
) )
.render(); .render();
const refreshButton = win.document.querySelector( const refreshButton = win.document.querySelector(
"#refresh" "#refresh",
) as HTMLButtonElement; ) as HTMLButtonElement;
const syncButton = win.document.querySelector("#sync") as HTMLButtonElement; const syncButton = win.document.querySelector("#sync") as HTMLButtonElement;
const unSyncButton = win.document.querySelector( const unSyncButton = win.document.querySelector(
"#unSync" "#unSync",
) as HTMLButtonElement; ) as HTMLButtonElement;
refreshButton.addEventListener("click", (ev) => { refreshButton.addEventListener("click", (ev) => {
refresh(); refresh();
@ -148,7 +148,7 @@ function updateButtons() {
return; return;
} }
const unSyncButton = win.document.querySelector( const unSyncButton = win.document.querySelector(
"#unSync" "#unSync",
) as HTMLButtonElement; ) as HTMLButtonElement;
if ( if (
addon.data.sync.manager.tableHelper?.treeInstance.selection.selected.size addon.data.sync.manager.tableHelper?.treeInstance.selection.selected.size
@ -179,13 +179,13 @@ async function unSyncNotes(itemIds: number[]) {
return; return;
} }
const unSyncLinkedNotes = addon.data.sync.manager.window?.confirm( const unSyncLinkedNotes = addon.data.sync.manager.window?.confirm(
`Un-sync their linked notes?` `Un-sync their linked notes?`,
); );
if (unSyncLinkedNotes) { if (unSyncLinkedNotes) {
for (const item of Zotero.Items.get(itemIds)) { for (const item of Zotero.Items.get(itemIds)) {
let linkedIds: number[] = getLinkedNotesRecursively( const linkedIds: number[] = getLinkedNotesRecursively(
getNoteLink(item) || "", getNoteLink(item) || "",
itemIds itemIds,
); );
itemIds.push(...linkedIds); itemIds.push(...linkedIds);
} }

View File

@ -19,7 +19,7 @@ async function runTemplate(
useDefault: true, useDefault: true,
dryRun: false, dryRun: false,
stage: "default", stage: "default",
} },
): Promise<string> { ): Promise<string> {
ztoolkit.log(`runTemplate: ${key}`); ztoolkit.log(`runTemplate: ${key}`);
if (argList.length > 0) { if (argList.length > 0) {
@ -64,8 +64,8 @@ async function runTemplate(
// Check the markdown pragma // Check the markdown pragma
templateLines = templateLines.slice(startIndex + 1, endIndex); templateLines = templateLines.slice(startIndex + 1, endIndex);
let useMarkdown = false; let useMarkdown = false;
let mdIndex = templateLines.findIndex((line) => const mdIndex = templateLines.findIndex((line) =>
line.startsWith("// @use-markdown") line.startsWith("// @use-markdown"),
); );
if (mdIndex >= 0) { if (mdIndex >= 0) {
useMarkdown = true; useMarkdown = true;
@ -87,7 +87,7 @@ async function runTemplate(
/\$\{\{([\s\S]*?)\}\}\$/g, /\$\{\{([\s\S]*?)\}\}\$/g,
(match, content) => { (match, content) => {
return constructFunction(content); return constructFunction(content);
} },
); );
try { try {
@ -110,9 +110,9 @@ async function runTextTemplate(
options: { options: {
targetNoteId?: number; targetNoteId?: number;
dryRun?: boolean; dryRun?: boolean;
} },
) { ) {
let { targetNoteId, dryRun } = options; const { targetNoteId, dryRun } = options;
const targetNoteItem = Zotero.Items.get(targetNoteId || -1); const targetNoteItem = Zotero.Items.get(targetNoteId || -1);
const sharedObj = {}; const sharedObj = {};
return await runTemplate( return await runTemplate(
@ -121,7 +121,7 @@ async function runTextTemplate(
[targetNoteItem, sharedObj], [targetNoteItem, sharedObj],
{ {
dryRun, dryRun,
} },
); );
} }
@ -131,7 +131,7 @@ async function runItemTemplate(
itemIds?: number[]; itemIds?: number[];
targetNoteId?: number; targetNoteId?: number;
dryRun?: boolean; dryRun?: boolean;
} },
): Promise<string> { ): Promise<string> {
/** /**
* args: * args:
@ -139,7 +139,8 @@ async function runItemTemplate(
* default stage: topItem, itemNotes, copyNoteImage, sharedObj * default stage: topItem, itemNotes, copyNoteImage, sharedObj
* afterloop stage: items, copyNoteImage, sharedObj * afterloop stage: items, copyNoteImage, sharedObj
*/ */
let { itemIds, targetNoteId, dryRun } = options; let { itemIds } = options;
const { targetNoteId, dryRun } = options;
if (!itemIds) { if (!itemIds) {
itemIds = await getItemTemplateData(); itemIds = await getItemTemplateData();
} }
@ -169,8 +170,8 @@ async function runItemTemplate(
stage: "beforeloop", stage: "beforeloop",
useDefault: false, useDefault: false,
dryRun, dryRun,
} },
) ),
); );
for (const topItem of items) { for (const topItem of items) {
@ -184,8 +185,8 @@ async function runItemTemplate(
[topItem, targetNoteItem, itemNotes, copyNoteImage, sharedObj], [topItem, targetNoteItem, itemNotes, copyNoteImage, sharedObj],
{ {
dryRun, dryRun,
} },
) ),
); );
} }
@ -198,8 +199,8 @@ async function runItemTemplate(
stage: "afterloop", stage: "afterloop",
useDefault: false, useDefault: false,
dryRun, dryRun,
} },
) ),
); );
let html = results.join("\n"); let html = results.join("\n");
@ -207,7 +208,7 @@ async function runItemTemplate(
html = await copyEmbeddedImagesInHTML( html = await copyEmbeddedImagesInHTML(
html, html,
targetNoteItem, targetNoteItem,
copyImageRefNotes copyImageRefNotes,
); );
} else { } else {
html = await renderNoteHTML(html, copyImageRefNotes); html = await renderNoteHTML(html, copyImageRefNotes);
@ -230,9 +231,9 @@ async function getItemTemplateData() {
slice( slice(
(firstSelectedItem.getField("title") as string) || (firstSelectedItem.getField("title") as string) ||
firstSelectedItem.key, firstSelectedItem.key,
40 40,
), ),
40 40,
)} ${ )} ${
librarySelectedIds.length > 1 librarySelectedIds.length > 1
? `and ${librarySelectedIds.length - 1} more` ? `and ${librarySelectedIds.length - 1} more`

View File

@ -13,7 +13,7 @@ export {
// Controller // Controller
function getTemplateKeys(): { name: string }[] { function getTemplateKeys(): { name: string }[] {
let templateKeys = getPref("templateKeys") as string; const templateKeys = getPref("templateKeys") as string;
return templateKeys ? JSON.parse(templateKeys) : []; return templateKeys ? JSON.parse(templateKeys) : [];
} }
@ -52,7 +52,7 @@ function getTemplateText(keyName: string): string {
function setTemplate( function setTemplate(
template: NoteTemplate, template: NoteTemplate,
updatePrompt: boolean = true updatePrompt: boolean = true,
): void { ): void {
template = JSON.parse(JSON.stringify(template)); template = JSON.parse(JSON.stringify(template));
addTemplateKey({ name: template.name }); addTemplateKey({ name: template.name });
@ -64,7 +64,7 @@ function setTemplate(
function removeTemplate( function removeTemplate(
keyName: string | undefined, keyName: string | undefined,
updatePrompt: boolean = true updatePrompt: boolean = true,
): void { ): void {
if (typeof keyName === "undefined") { if (typeof keyName === "undefined") {
return; return;
@ -77,7 +77,7 @@ function removeTemplate(
} }
function initTemplates() { function initTemplates() {
let templateKeys = getTemplateKeys(); const templateKeys = getTemplateKeys();
const currentNames = templateKeys.map((t) => t.name); const currentNames = templateKeys.map((t) => t.name);
for (const defaultTemplate of addon.api.template.DEFAULT_TEMPLATES) { for (const defaultTemplate of addon.api.template.DEFAULT_TEMPLATES) {
if (!currentNames.includes(defaultTemplate.name)) { if (!currentNames.includes(defaultTemplate.name)) {

View File

@ -18,13 +18,13 @@ export async function showTemplateEditor() {
`chrome://${config.addonRef}/content/templateEditor.xhtml`, `chrome://${config.addonRef}/content/templateEditor.xhtml`,
`${config.addonRef}-templateEditor`, `${config.addonRef}-templateEditor`,
`chrome,centerscreen,resizable,status,width=600,height=400,dialog=no`, `chrome,centerscreen,resizable,status,width=600,height=400,dialog=no`,
windowArgs windowArgs,
)!; )!;
addon.data.templateEditor.window = _window; addon.data.templateEditor.window = _window;
await windowArgs._initPromise.promise; await windowArgs._initPromise.promise;
updateData(); updateData();
addon.data.templateEditor.tableHelper = new ztoolkit.VirtualizedTable( addon.data.templateEditor.tableHelper = new ztoolkit.VirtualizedTable(
_window! _window!,
) )
.setContainerId("table-container") .setContainerId("table-container")
.setProp({ .setProp({
@ -40,7 +40,7 @@ export async function showTemplateEditor() {
].map((column) => ].map((column) =>
Object.assign(column, { Object.assign(column, {
label: getString(column.label), label: getString(column.label),
}) }),
), ),
showHeader: true, showHeader: true,
multiSelect: false, multiSelect: false,
@ -53,7 +53,7 @@ export async function showTemplateEditor() {
(index) => (index) =>
(addon.data.templateEditor.templates[index] as { name: string }) || { (addon.data.templateEditor.templates[index] as { name: string }) || {
name: "no data", name: "no data",
} },
) )
.setProp("onSelectionChange", (selection) => { .setProp("onSelectionChange", (selection) => {
updateEditor(); updateEditor();
@ -72,7 +72,7 @@ export async function showTemplateEditor() {
}) })
.setProp( .setProp(
"getRowString", "getRowString",
(index) => addon.data.prefs?.rows[index].title || "" (index) => addon.data.prefs?.rows[index].title || "",
) )
.render(); .render();
_window.document _window.document
@ -87,12 +87,12 @@ export async function showTemplateEditor() {
}); });
_window.document.querySelector("#help")?.addEventListener("click", (ev) => { _window.document.querySelector("#help")?.addEventListener("click", (ev) => {
Zotero.launchURL( Zotero.launchURL(
"https://github.com/windingwind/zotero-better-notes/blob/master/docs/about-note-template.md" "https://github.com/windingwind/zotero-better-notes/blob/master/docs/about-note-template.md",
); );
}); });
_window.document.querySelector("#more")?.addEventListener("click", (ev) => { _window.document.querySelector("#more")?.addEventListener("click", (ev) => {
Zotero.launchURL( Zotero.launchURL(
"https://github.com/windingwind/zotero-better-notes/discussions/categories/note-templates" "https://github.com/windingwind/zotero-better-notes/discussions/categories/note-templates",
); );
}); });
_window.document.querySelector("#save")?.addEventListener("click", (ev) => { _window.document.querySelector("#save")?.addEventListener("click", (ev) => {
@ -147,22 +147,22 @@ function updateEditor() {
const templateText = addon.api.template.getTemplateText(name); const templateText = addon.api.template.getTemplateText(name);
const header = addon.data.templateEditor.window?.document.getElementById( const header = addon.data.templateEditor.window?.document.getElementById(
"editor-name" "editor-name",
) as HTMLInputElement; ) as HTMLInputElement;
const text = addon.data.templateEditor.window?.document.getElementById( const text = addon.data.templateEditor.window?.document.getElementById(
"editor-textbox" "editor-textbox",
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
const saveTemplate = const saveTemplate =
addon.data.templateEditor.window?.document.getElementById( addon.data.templateEditor.window?.document.getElementById(
"save" "save",
) as XUL.Button; ) as XUL.Button;
const deleteTemplate = const deleteTemplate =
addon.data.templateEditor.window?.document.getElementById( addon.data.templateEditor.window?.document.getElementById(
"delete" "delete",
) as XUL.Button; ) as XUL.Button;
const resetTemplate = const resetTemplate =
addon.data.templateEditor.window?.document.getElementById( addon.data.templateEditor.window?.document.getElementById(
"reset" "reset",
) as XUL.Button; ) as XUL.Button;
if (!name) { if (!name) {
header.value = ""; header.value = "";
@ -194,11 +194,11 @@ function updateEditor() {
async function updatePreview() { async function updatePreview() {
const name = getSelectedTemplateName(); const name = getSelectedTemplateName();
let html = (await addon.api.template.renderTemplatePreview(name)) const html = (await addon.api.template.renderTemplatePreview(name))
.replace(/&nbsp;/g, "#160;") .replace(/&nbsp;/g, "#160;")
.replace(/<br>/g, "<br/>") .replace(/<br>/g, "<br/>")
.replace(/<hr>/g, "<hr/>") .replace(/<hr>/g, "<hr/>")
.replace(/<img([^>]+)\>/g, "<img$1/>"); .replace(/<img([^>]+)>/g, "<img$1/>");
const win = addon.data.templateEditor.window; const win = addon.data.templateEditor.window;
const container = win?.document.getElementById("preview-container"); const container = win?.document.getElementById("preview-container");
if (container) { if (container) {
@ -207,15 +207,18 @@ async function updatePreview() {
} else { } else {
container.innerHTML = ""; container.innerHTML = "";
container.appendChild( container.appendChild(
ztoolkit.getDOMParser().parseFromString(html, "text/html").body ztoolkit.getDOMParser().parseFromString(html, "text/html").body,
); );
} }
} }
} }
function getSelectedTemplateName() { function getSelectedTemplateName() {
const selectedTemplate = addon.data.templateEditor.templates.find((v, i) => const selectedTemplate = addon.data.templateEditor.templates.find(
addon.data.templateEditor.tableHelper?.treeInstance.selection.isSelected(i) (v, i) =>
addon.data.templateEditor.tableHelper?.treeInstance.selection.isSelected(
i,
),
); );
return selectedTemplate?.name || ""; return selectedTemplate?.name || "";
} }
@ -232,7 +235,7 @@ function createTemplate() {
async function importNoteTemplate() { async function importNoteTemplate() {
const ids = await itemPicker(); const ids = await itemPicker();
const note: Zotero.Item = Zotero.Items.get(ids).filter((item: Zotero.Item) => const note: Zotero.Item = Zotero.Items.get(ids).filter((item: Zotero.Item) =>
item.isNote() item.isNote(),
)[0]; )[0];
if (!note) { if (!note) {
return; return;
@ -248,10 +251,10 @@ async function importNoteTemplate() {
function saveSelectedTemplate() { function saveSelectedTemplate() {
const name = getSelectedTemplateName(); const name = getSelectedTemplateName();
const header = addon.data.templateEditor.window?.document.getElementById( const header = addon.data.templateEditor.window?.document.getElementById(
"editor-name" "editor-name",
) as HTMLInputElement; ) as HTMLInputElement;
const text = addon.data.templateEditor.window?.document.getElementById( const text = addon.data.templateEditor.window?.document.getElementById(
"editor-textbox" "editor-textbox",
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
if ( if (
@ -259,7 +262,7 @@ function saveSelectedTemplate() {
header.value !== name header.value !== name
) { ) {
showHint( showHint(
`Template ${name} is a system template. Modifying template name is not allowed.` `Template ${name} is a system template. Modifying template name is not allowed.`,
); );
return; return;
} }
@ -284,7 +287,7 @@ function deleteSelectedTemplate() {
const name = getSelectedTemplateName(); const name = getSelectedTemplateName();
if (addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(name)) { if (addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(name)) {
showHint( showHint(
`Template ${name} is a system template. Removing system template is note allowed.` `Template ${name} is a system template. Removing system template is note allowed.`,
); );
return; return;
} }
@ -296,7 +299,7 @@ function resetSelectedTemplate() {
const name = getSelectedTemplateName(); const name = getSelectedTemplateName();
if (addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(name)) { if (addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(name)) {
const text = addon.data.templateEditor.window?.document.getElementById( const text = addon.data.templateEditor.window?.document.getElementById(
"editor-textbox" "editor-textbox",
) as HTMLTextAreaElement; ) as HTMLTextAreaElement;
text.value = text.value =
addon.api.template.DEFAULT_TEMPLATES.find((t) => t.name === name)?.text || addon.api.template.DEFAULT_TEMPLATES.find((t) => t.name === name)?.text ||
@ -325,7 +328,7 @@ ${content
`; `;
new ztoolkit.Clipboard().addText(yaml, "text/unicode").copy(); new ztoolkit.Clipboard().addText(yaml, "text/unicode").copy();
showHint( showHint(
`Template ${name} is copied to clipboard. To import it, goto Zotero menu bar, click Edit->New Template from Clipboard. ` `Template ${name} is copied to clipboard. To import it, goto Zotero menu bar, click Edit->New Template from Clipboard. `,
); );
} }
@ -335,7 +338,7 @@ async function backupTemplates() {
"Save backup file", "Save backup file",
"save", "save",
[["yaml", "*.yaml"]], [["yaml", "*.yaml"]],
`bn-template-backup-${time}.yaml` `bn-template-backup-${time}.yaml`,
).open(); ).open();
if (!filepath) { if (!filepath) {
return; return;
@ -358,7 +361,7 @@ async function restoreTemplates(win: Window) {
[["yaml", "*.yaml"]], [["yaml", "*.yaml"]],
undefined, undefined,
win, win,
"text" "text",
).open(); ).open();
if (!filepath) { if (!filepath) {
return; return;
@ -370,7 +373,7 @@ async function restoreTemplates(win: Window) {
for (const t of templates) { for (const t of templates) {
if (existingNames.includes(t.name)) { if (existingNames.includes(t.name)) {
const overwrite = win.confirm( const overwrite = win.confirm(
`Template ${t.name} already exists. Overwrite?` `Template ${t.name} already exists. Overwrite?`,
); );
if (!overwrite) { if (!overwrite) {
continue; continue;

View File

@ -7,17 +7,17 @@ export { updateTemplatePicker, showTemplatePicker };
function showTemplatePicker( function showTemplatePicker(
mode: "insert", mode: "insert",
data?: { noteId?: number; lineIndex?: number } data?: { noteId?: number; lineIndex?: number },
): void; ): void;
function showTemplatePicker( function showTemplatePicker(
mode: "create", mode: "create",
data?: { noteType?: "standalone" | "item"; parentItemId?: number } data?: { noteType?: "standalone" | "item"; parentItemId?: number },
): void; ): void;
function showTemplatePicker(mode: "export", data?: {}): void; function showTemplatePicker(mode: "export", data?: Record<string, never>): void;
function showTemplatePicker(): void; function showTemplatePicker(): void;
function showTemplatePicker( function showTemplatePicker(
mode: typeof addon.data.templatePicker.mode = "insert", mode: typeof addon.data.templatePicker.mode = "insert",
data: Record<string, any> = {} data: Record<string, any> = {},
) { ) {
if (addon.data.prompt) { if (addon.data.prompt) {
addon.data.templatePicker.mode = mode; addon.data.templatePicker.mode = mode;
@ -25,8 +25,8 @@ function showTemplatePicker(
addon.data.prompt.promptNode.style.display = "flex"; addon.data.prompt.promptNode.style.display = "flex";
addon.data.prompt.showCommands( addon.data.prompt.showCommands(
addon.data.prompt.commands.filter( addon.data.prompt.commands.filter(
(cmd) => cmd.label === "BNotes Template" (cmd) => cmd.label === "BNotes Template",
) ),
); );
} }
} }
@ -38,7 +38,7 @@ function updateTemplatePicker() {
templates templates
.filter( .filter(
(template) => (template) =>
!addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template.name) !addon.api.template.SYSTEM_TEMPLATE_NAMES.includes(template.name),
) )
.map((template) => { .map((template) => {
return { return {
@ -46,7 +46,7 @@ function updateTemplatePicker() {
label: "BNotes Template", label: "BNotes Template",
callback: getTemplatePromptHandler(template.name), callback: getTemplatePromptHandler(template.name),
}; };
}) }),
); );
if (!addon.data.prompt) { if (!addon.data.prompt) {
addon.data.prompt = ToolkitGlobal.getInstance().prompt.instance; addon.data.prompt = ToolkitGlobal.getInstance().prompt.instance;
@ -77,7 +77,7 @@ function getTemplatePromptHandler(name: string) {
async function insertTemplateCallback(name: string) { async function insertTemplateCallback(name: string) {
const targetNoteItem = Zotero.Items.get( const targetNoteItem = Zotero.Items.get(
addon.data.templatePicker.data.noteId || addon.data.workspace.mainId addon.data.templatePicker.data.noteId || addon.data.workspace.mainId,
); );
let html = ""; let html = "";
if (name.toLowerCase().startsWith("[item]")) { if (name.toLowerCase().startsWith("[item]")) {
@ -92,7 +92,7 @@ async function insertTemplateCallback(name: string) {
await addLineToNote( await addLineToNote(
targetNoteItem, targetNoteItem,
html, html,
addon.data.templatePicker.data.lineIndex addon.data.templatePicker.data.lineIndex,
); );
} }

View File

@ -5,7 +5,7 @@ export { renderTemplatePreview };
async function renderTemplatePreview( async function renderTemplatePreview(
templateName: string, templateName: string,
inputItems?: Zotero.Item[] inputItems?: Zotero.Item[],
): Promise<string> { ): Promise<string> {
let html: string = "<p>Preview rendering failed</p>"; let html: string = "<p>Preview rendering failed</p>";
if (!inputItems) { if (!inputItems) {
@ -33,7 +33,7 @@ async function renderTemplatePreview(
[data], [data],
{ {
dryRun: true, dryRun: true,
} },
); );
} }
} else if (templateName.includes("ExportMDFileHeader")) { } else if (templateName.includes("ExportMDFileHeader")) {
@ -48,7 +48,7 @@ async function renderTemplatePreview(
[data], [data],
{ {
dryRun: true, dryRun: true,
} },
); );
const header = Object.assign({}, JSON.parse(raw), { const header = Object.assign({}, JSON.parse(raw), {
version: data.version, version: data.version,
@ -73,7 +73,7 @@ async function renderTemplatePreview(
[link, linkText, subNoteItem, noteItem], [link, linkText, subNoteItem, noteItem],
{ {
dryRun: true, dryRun: true,
} },
); );
} }
} else if (templateName.includes("QuickBackLink")) { } else if (templateName.includes("QuickBackLink")) {
@ -92,7 +92,7 @@ async function renderTemplatePreview(
[link, linkText, subNoteItem, noteItem], [link, linkText, subNoteItem, noteItem],
{ {
dryRun: true, dryRun: true,
} },
); );
} }
} else if (templateName.includes("QuickImport")) { } else if (templateName.includes("QuickImport")) {
@ -109,7 +109,7 @@ async function renderTemplatePreview(
[link, noteItem], [link, noteItem],
{ {
dryRun: true, dryRun: true,
} },
); );
} }
} else if (templateName.includes("QuickNote")) { } else if (templateName.includes("QuickNote")) {

View File

@ -20,7 +20,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
id: string, id: string,
content: string, content: string,
title: string, title: string,
callback: (ev: Event) => void callback: (ev: Event) => void,
) { ) {
return { return {
tag: "div", tag: "div",
@ -119,7 +119,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
getString("workspace.switchOutline"), getString("workspace.switchOutline"),
(ev) => { (ev) => {
setOutline(container); setOutline(container);
} },
), ),
makeTooltipProp( makeTooltipProp(
makeId("saveOutlineImage"), makeId("saveOutlineImage"),
@ -127,7 +127,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
getString("workspace.saveOutlineImage"), getString("workspace.saveOutlineImage"),
(ev) => { (ev) => {
saveImage(container); saveImage(container);
} },
), ),
makeTooltipProp( makeTooltipProp(
makeId("saveOutlineFreeMind"), makeId("saveOutlineFreeMind"),
@ -135,7 +135,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
getString("workspace.saveOutlineFreeMind"), getString("workspace.saveOutlineFreeMind"),
(ev) => { (ev) => {
saveFreeMind(); saveFreeMind();
} },
), ),
], ],
}, },
@ -219,7 +219,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
}, },
], ],
}, },
container container,
); );
// Manually add custom editor items in Zotero 7 // Manually add custom editor items in Zotero 7
if (ztoolkit.isZotero7()) { if (ztoolkit.isZotero7()) {
@ -227,10 +227,10 @@ export function initWorkspace(container: XUL.Box | undefined) {
const customElements = container.ownerGlobal const customElements = container.ownerGlobal
.customElements as CustomElementRegistry; .customElements as CustomElementRegistry;
const mainEditorContainer = container.querySelector( const mainEditorContainer = container.querySelector(
`#${makeId("editor-main-container")}` `#${makeId("editor-main-container")}`,
); );
const previewEditorContainer = container.querySelector( const previewEditorContainer = container.querySelector(
`#${makeId("editor-preview-container")}` `#${makeId("editor-preview-container")}`,
); );
const mainEditor = new (customElements.get("note-editor")!)(); const mainEditor = new (customElements.get("note-editor")!)();
mainEditor.id = makeId("editor-main"); mainEditor.id = makeId("editor-main");
@ -243,7 +243,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
} }
const outlineContainer = container.querySelector( const outlineContainer = container.querySelector(
`#${makeId("outline-container")}` `#${makeId("outline-container")}`,
) as XUL.Box; ) as XUL.Box;
const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))( const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))(
(mutations) => { (mutations) => {
@ -252,7 +252,7 @@ export function initWorkspace(container: XUL.Box | undefined) {
} else { } else {
outlineContainer.style.display = "flex"; outlineContainer.style.display = "flex";
} }
} },
); );
outlineMut.observe(outlineContainer, { outlineMut.observe(outlineContainer, {
attributes: true, attributes: true,
@ -269,7 +269,7 @@ export async function initWorkspaceEditor(
noteId: number, noteId: number,
options: { options: {
lineIndex?: number; lineIndex?: number;
} = {} } = {},
) { ) {
const noteItem = Zotero.Items.get(noteId); const noteItem = Zotero.Items.get(noteId);
if (!noteItem || !noteItem.isNote()) { if (!noteItem || !noteItem.isNote()) {
@ -279,7 +279,7 @@ export async function initWorkspaceEditor(
return; return;
} }
const editorElem = container?.querySelector( const editorElem = container?.querySelector(
`#${makeId("editor-" + type)}` `#${makeId("editor-" + type)}`,
) as EditorElement; ) as EditorElement;
await waitUtilAsync(() => Boolean(editorElem._initialized)) await waitUtilAsync(() => Boolean(editorElem._initialized))
.then(() => ztoolkit.log("ok")) .then(() => ztoolkit.log("ok"))
@ -300,7 +300,7 @@ export async function initWorkspaceEditor(
} }
function getContainerType( function getContainerType(
container: XUL.Box | undefined container: XUL.Box | undefined,
): "tab" | "window" | "unknown" { ): "tab" | "window" | "unknown" {
if (!container) { if (!container) {
return "unknown"; return "unknown";
@ -337,7 +337,7 @@ export function toggleNotesPane(visibility?: boolean) {
export function getWorkspaceEditor( export function getWorkspaceEditor(
workspaceType: "tab" | "window", workspaceType: "tab" | "window",
editorType: "main" | "preview" = "main" editorType: "main" | "preview" = "main",
) { ) {
const container = const container =
workspaceType === "tab" workspaceType === "tab"
@ -359,7 +359,7 @@ const SRC_LIST = [
function setOutline( function setOutline(
container: XUL.Box, container: XUL.Box,
newType: OutlineType = OutlineType.empty newType: OutlineType = OutlineType.empty,
) { ) {
if (newType === OutlineType.empty) { if (newType === OutlineType.empty) {
newType = addon.data.workspace.outline + 1; newType = addon.data.workspace.outline + 1;
@ -373,11 +373,11 @@ function setOutline(
).hidden = newType === OutlineType.treeView; ).hidden = newType === OutlineType.treeView;
( (
container.querySelector( container.querySelector(
`#${makeId("saveOutlineFreeMind")}` `#${makeId("saveOutlineFreeMind")}`,
) as HTMLDivElement ) as HTMLDivElement
).hidden = newType === OutlineType.treeView; ).hidden = newType === OutlineType.treeView;
const iframe = container.querySelector( const iframe = container.querySelector(
`#${makeId("outline-iframe")}` `#${makeId("outline-iframe")}`,
) as HTMLIFrameElement; ) as HTMLIFrameElement;
iframe.setAttribute("src", SRC_LIST[addon.data.workspace.outline]); iframe.setAttribute("src", SRC_LIST[addon.data.workspace.outline]);
updateOutline(container); updateOutline(container);
@ -386,22 +386,22 @@ function setOutline(
export async function updateOutline(container: XUL.Box) { export async function updateOutline(container: XUL.Box) {
const iframe = container.querySelector( const iframe = container.querySelector(
`#${makeId("outline-iframe")}` `#${makeId("outline-iframe")}`,
) as HTMLIFrameElement; ) as HTMLIFrameElement;
await waitUtilAsync( await waitUtilAsync(
() => iframe.contentWindow?.document.readyState === "complete" () => iframe.contentWindow?.document.readyState === "complete",
); );
iframe.contentWindow?.postMessage( iframe.contentWindow?.postMessage(
{ {
type: "setMindMapData", type: "setMindMapData",
nodes: getNoteTreeFlattened( nodes: getNoteTreeFlattened(
Zotero.Items.get(addon.data.workspace.mainId), Zotero.Items.get(addon.data.workspace.mainId),
{ keepLink: true } { keepLink: true },
), ),
workspaceType: getContainerType(container), workspaceType: getContainerType(container),
expandLevel: getPref("workspace.outline.expandLevel"), expandLevel: getPref("workspace.outline.expandLevel"),
}, },
"*" "*",
); );
} }
@ -413,20 +413,20 @@ function updateOutlineButtons(container: XUL.Box) {
).style.visibility = isTreeView ? "hidden" : "visible"; ).style.visibility = isTreeView ? "hidden" : "visible";
( (
container.querySelector( container.querySelector(
`#${makeId("saveOutlineFreeMind")}` `#${makeId("saveOutlineFreeMind")}`,
) as HTMLDivElement ) as HTMLDivElement
).style.visibility = isTreeView ? "hidden" : "visible"; ).style.visibility = isTreeView ? "hidden" : "visible";
} }
function saveImage(container: XUL.Box) { function saveImage(container: XUL.Box) {
const iframe = container.querySelector( const iframe = container.querySelector(
`#${makeId("outline-iframe")}` `#${makeId("outline-iframe")}`,
) as HTMLIFrameElement; ) as HTMLIFrameElement;
iframe.contentWindow?.postMessage( iframe.contentWindow?.postMessage(
{ {
type: "saveSVG", type: "saveSVG",
}, },
"*" "*",
); );
} }
@ -436,7 +436,7 @@ async function saveFreeMind() {
`${Zotero.getString("fileInterface.export")} FreeMind XML`, `${Zotero.getString("fileInterface.export")} FreeMind XML`,
"save", "save",
[["FreeMind XML File(*.mm)", "*.mm"]], [["FreeMind XML File(*.mm)", "*.mm"]],
`${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.mm` `${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.mm`,
).open(); ).open();
if (filename) { if (filename) {
await _saveFreeMind(filename, addon.data.workspace.mainId); await _saveFreeMind(filename, addon.data.workspace.mainId);

View File

@ -14,7 +14,7 @@ export async function messageHandler(ev: MessageEvent) {
case "jumpNode": { case "jumpNode": {
const editor = addon.api.workspace.getWorkspaceEditor( const editor = addon.api.workspace.getWorkspaceEditor(
ev.data.workspaceType, ev.data.workspaceType,
"main" "main",
); );
if (!editor) { if (!editor) {
return; return;
@ -34,21 +34,21 @@ export async function messageHandler(ev: MessageEvent) {
} }
case "moveNode": { case "moveNode": {
const noteItem = Zotero.Items.get(addon.data.workspace.mainId); const noteItem = Zotero.Items.get(addon.data.workspace.mainId);
let tree = getNoteTree(noteItem); const tree = getNoteTree(noteItem);
let fromNode = getNoteTreeNodeById(noteItem, ev.data.fromID, tree); const fromNode = getNoteTreeNodeById(noteItem, ev.data.fromID, tree);
let toNode = getNoteTreeNodeById(noteItem, ev.data.toID, tree); const toNode = getNoteTreeNodeById(noteItem, ev.data.toID, tree);
moveHeading( moveHeading(
getEditorInstance(noteItem.id), getEditorInstance(noteItem.id),
fromNode!, fromNode!,
toNode!, toNode!,
ev.data.moveType ev.data.moveType,
); );
return; return;
} }
case "editNode": { case "editNode": {
const editor = addon.api.workspace.getWorkspaceEditor( const editor = addon.api.workspace.getWorkspaceEditor(
ev.data.workspaceType, ev.data.workspaceType,
"main" "main",
); );
if (!editor) { if (!editor) {
return; return;
@ -56,7 +56,7 @@ export async function messageHandler(ev: MessageEvent) {
updateHeadingTextAtLine( updateHeadingTextAtLine(
editor, editor,
ev.data.lineIndex, ev.data.lineIndex,
ev.data.text.replace(/[\r\n]/g, "") ev.data.text.replace(/[\r\n]/g, ""),
); );
return; return;
} }
@ -65,7 +65,7 @@ export async function messageHandler(ev: MessageEvent) {
`${Zotero.getString("fileInterface.export")} SVG Image`, `${Zotero.getString("fileInterface.export")} SVG Image`,
"save", "save",
[["SVG File(*.svg)", "*.svg"]], [["SVG File(*.svg)", "*.svg"]],
`${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.svg` `${Zotero.Items.get(addon.data.workspace.mainId).getNoteTitle()}.svg`,
).open(); ).open();
if (filename) { if (filename) {
await Zotero.File.putContentsAsync(formatPath(filename), ev.data.image); await Zotero.File.putContentsAsync(formatPath(filename), ev.data.image);
@ -74,7 +74,7 @@ export async function messageHandler(ev: MessageEvent) {
"Show in Folder", "Show in Folder",
(ev) => { (ev) => {
Zotero.File.reveal(filename); Zotero.File.reveal(filename);
} },
); );
} }
return; return;

View File

@ -24,11 +24,9 @@ export function registerWorkspaceTab() {
attributeFilter: ["hidden"], attributeFilter: ["hidden"],
}); });
waitUtilAsync(() => waitUtilAsync(() =>
Boolean(ztoolkit.getGlobal("ZoteroContextPane")._notifierID) Boolean(ztoolkit.getGlobal("ZoteroContextPane")._notifierID),
).then(() => { ).then(() => {
addWorkspaceTab().then(() => { addWorkspaceTab();
restoreWorkspaceTab();
});
}); });
window.addEventListener("message", (e) => messageHandler(e), false); window.addEventListener("message", (e) => messageHandler(e), false);
} }
@ -38,7 +36,7 @@ export function unregisterWorkspaceTab() {
} }
async function addWorkspaceTab() { async function addWorkspaceTab() {
let { id, container } = Zotero_Tabs.add({ const { id, container } = Zotero_Tabs.add({
type: TAB_TYPE, type: TAB_TYPE,
title: getString("tab.name"), title: getString("tab.name"),
index: 1, index: 1,
@ -54,10 +52,10 @@ async function addWorkspaceTab() {
}, },
}); });
await waitUtilAsync(() => await waitUtilAsync(() =>
Boolean(document.querySelector(`.tabs-wrapper .tab[data-id=${id}]`)) Boolean(document.querySelector(`.tabs-wrapper .tab[data-id=${id}]`)),
); );
const tabElem = document.querySelector( const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${id}]` `.tabs-wrapper .tab[data-id=${id}]`,
) as HTMLDivElement; ) as HTMLDivElement;
tabElem.style.width = "30px"; tabElem.style.width = "30px";
tabElem.style.minWidth = "30px"; tabElem.style.minWidth = "30px";
@ -78,7 +76,7 @@ async function addWorkspaceTab() {
backgroundImage: `url("chrome://${config.addonRef}/content/icons/favicon.png")`, backgroundImage: `url("chrome://${config.addonRef}/content/icons/favicon.png")`,
}, },
}, },
content content,
); );
close.style.visibility = "hidden"; close.style.visibility = "hidden";
addon.data.workspace.tab.id = id; addon.data.workspace.tab.id = id;
@ -91,7 +89,7 @@ function hoverWorkspaceTab(hovered: boolean) {
(elem as HTMLDivElement).style.visibility = hovered ? "visible" : "hidden"; (elem as HTMLDivElement).style.visibility = hovered ? "visible" : "hidden";
}); });
const tabElem = document.querySelector( const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]` `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
) as HTMLDivElement; ) as HTMLDivElement;
const content = tabElem.querySelector(".tab-name") as HTMLDivElement; const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
content.removeAttribute("style"); content.removeAttribute("style");
@ -107,10 +105,10 @@ function hoverWorkspaceTab(hovered: boolean) {
function updateWorkspaceTabToggleButton( function updateWorkspaceTabToggleButton(
type: "outline" | "preview" | "notes", type: "outline" | "preview" | "notes",
state: "open" | "collapsed" state: "open" | "collapsed",
) { ) {
const elem = document.querySelector( const elem = document.querySelector(
`#betternotes-tab-toggle-${type}` `#betternotes-tab-toggle-${type}`,
) as HTMLDivElement; ) as HTMLDivElement;
if (!elem) { if (!elem) {
return; return;
@ -123,12 +121,12 @@ function updateWorkspaceTabToggleButton(
function registerWorkspaceTabPaneObserver() { function registerWorkspaceTabPaneObserver() {
const outlineSplitter = document.querySelector( const outlineSplitter = document.querySelector(
"#betternotes-workspace-outline-splitter" "#betternotes-workspace-outline-splitter",
); );
const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => { const outlineMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton( updateWorkspaceTabToggleButton(
"outline", "outline",
outlineSplitter!.getAttribute("state")! as "open" | "collapsed" outlineSplitter!.getAttribute("state")! as "open" | "collapsed",
); );
}); });
outlineMut.observe(outlineSplitter!, { outlineMut.observe(outlineSplitter!, {
@ -136,12 +134,12 @@ function registerWorkspaceTabPaneObserver() {
attributeFilter: ["state"], attributeFilter: ["state"],
}); });
const previewSplitter = document.querySelector( const previewSplitter = document.querySelector(
"#betternotes-workspace-preview-splitter" "#betternotes-workspace-preview-splitter",
); );
const previeweMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => { const previeweMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton( updateWorkspaceTabToggleButton(
"preview", "preview",
previewSplitter!.getAttribute("state")! as "open" | "collapsed" previewSplitter!.getAttribute("state")! as "open" | "collapsed",
); );
}); });
previeweMut.observe(previewSplitter!, { previeweMut.observe(previewSplitter!, {
@ -152,7 +150,7 @@ function registerWorkspaceTabPaneObserver() {
const notesMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => { const notesMut = new (ztoolkit.getGlobal("MutationObserver"))((muts) => {
updateWorkspaceTabToggleButton( updateWorkspaceTabToggleButton(
"notes", "notes",
notesSplitter!.getAttribute("state")! as "open" | "collapsed" notesSplitter!.getAttribute("state")! as "open" | "collapsed",
); );
}); });
notesMut.observe(notesSplitter!, { notesMut.observe(notesSplitter!, {
@ -173,7 +171,7 @@ export async function activateWorkspaceTab() {
document.querySelector("#zotero-tab-toolbar") as XUL.Box document.querySelector("#zotero-tab-toolbar") as XUL.Box
).style.visibility = "collapse"; ).style.visibility = "collapse";
const toolbar = document.querySelector( const toolbar = document.querySelector(
"#zotero-context-toolbar-extension" "#zotero-context-toolbar-extension",
) as XUL.Box; ) as XUL.Box;
toolbar.style.visibility = "collapse"; toolbar.style.visibility = "collapse";
toolbar.nextElementSibling?.setAttribute("selectedIndex", "1"); toolbar.nextElementSibling?.setAttribute("selectedIndex", "1");
@ -188,12 +186,12 @@ export async function activateWorkspaceTab() {
await waitUtilAsync(() => await waitUtilAsync(() =>
Boolean( Boolean(
document.querySelector( document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]` `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
) ),
) ),
); );
const tabElem = document.querySelector( const tabElem = document.querySelector(
`.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]` `.tabs-wrapper .tab[data-id=${addon.data.workspace.tab.id}]`,
) as HTMLDivElement; ) as HTMLDivElement;
tabElem.removeAttribute("style"); tabElem.removeAttribute("style");
const content = tabElem.querySelector(".tab-name") as HTMLDivElement; const content = tabElem.querySelector(".tab-name") as HTMLDivElement;
@ -222,7 +220,7 @@ export async function activateWorkspaceTab() {
addon.hooks.onToggleWorkspacePane( addon.hooks.onToggleWorkspacePane(
"outline", "outline",
undefined, undefined,
addon.data.workspace.tab.container addon.data.workspace.tab.container,
); );
}, },
}, },
@ -245,7 +243,7 @@ export async function activateWorkspaceTab() {
addon.hooks.onToggleWorkspacePane( addon.hooks.onToggleWorkspacePane(
"preview", "preview",
undefined, undefined,
addon.data.workspace.tab.container addon.data.workspace.tab.container,
); );
}, },
}, },
@ -281,7 +279,7 @@ export async function activateWorkspaceTab() {
}, },
], ],
}, },
close close,
); );
hoverWorkspaceTab(false); hoverWorkspaceTab(false);
tabElem.addEventListener("mouseenter", () => { tabElem.addEventListener("mouseenter", () => {
@ -320,19 +318,11 @@ export function deActivateWorkspaceTab() {
document.querySelector("#zotero-tab-toolbar") as XUL.Box document.querySelector("#zotero-tab-toolbar") as XUL.Box
).style.removeProperty("visibility"); ).style.removeProperty("visibility");
const toolbar = document.querySelector( const toolbar = document.querySelector(
"#zotero-context-toolbar-extension" "#zotero-context-toolbar-extension",
) as XUL.Box; ) as XUL.Box;
toolbar.style.removeProperty("visibility"); toolbar.style.removeProperty("visibility");
} }
function restoreWorkspaceTab() {
return;
if (1 || getPref("workspace.tab.active")) {
ztoolkit.log("restore workspace tab");
activateWorkspaceTab();
}
}
function setWorkspaceTabStatus(status: boolean) { function setWorkspaceTabStatus(status: boolean) {
addon.data.workspace.tab.active = status; addon.data.workspace.tab.active = status;
setPref("workspace.tab.active", status); setPref("workspace.tab.active", status);
@ -340,7 +330,7 @@ function setWorkspaceTabStatus(status: boolean) {
function initWorkspaceTabDragDrop( function initWorkspaceTabDragDrop(
container?: XUL.Box, container?: XUL.Box,
tabElem?: HTMLDivElement tabElem?: HTMLDivElement,
) { ) {
if (!container) { if (!container) {
return; return;
@ -401,10 +391,10 @@ function initWorkspaceTabDragDrop(
], ],
enableElementRecord: false, enableElementRecord: false,
}, },
container container,
); );
const dropElem = container.querySelector( const dropElem = container.querySelector(
"#bn-workspace-tab-drop" "#bn-workspace-tab-drop",
) as HTMLDivElement; ) as HTMLDivElement;
tabElem?.addEventListener("dragstart", (ev) => { tabElem?.addEventListener("dragstart", (ev) => {
dropElem.style.visibility = "visible"; dropElem.style.visibility = "visible";

View File

@ -14,14 +14,14 @@ export async function showWorkspaceWindow() {
`chrome://${config.addonRef}/content/workspaceWindow.xhtml`, `chrome://${config.addonRef}/content/workspaceWindow.xhtml`,
`${config.addonRef}-workspaceWindow`, `${config.addonRef}-workspaceWindow`,
`chrome,centerscreen,resizable,status,width=800,height=400,dialog=no`, `chrome,centerscreen,resizable,status,width=800,height=400,dialog=no`,
windowArgs windowArgs,
)!; )!;
await windowArgs._initPromise.promise; await windowArgs._initPromise.promise;
localeWindow(win); localeWindow(win);
addon.data.workspace.window.active = true; addon.data.workspace.window.active = true;
addon.data.workspace.window.window = win; addon.data.workspace.window.window = win;
addon.data.workspace.window.container = win.document.querySelector( addon.data.workspace.window.container = win.document.querySelector(
"#workspace-container" "#workspace-container",
) as XUL.Box; ) as XUL.Box;
addon.hooks.onInitWorkspace(addon.data.workspace.window.container); addon.hooks.onInitWorkspace(addon.data.workspace.window.container);
win.addEventListener("message", messageHandler, false); win.addEventListener("message", messageHandler, false);

View File

@ -17,7 +17,7 @@ async function parseAnnotationJSON(annotationItem: Zotero.Item) {
const annotationJSON = await Zotero.Annotations.toJSON(annotationItem); const annotationJSON = await Zotero.Annotations.toJSON(annotationItem);
const annotationObj = Object.assign( const annotationObj = Object.assign(
{}, {},
annotationJSON annotationJSON,
) as CustomAnnotationJSON; ) as CustomAnnotationJSON;
annotationObj.id = annotationItem.key; annotationObj.id = annotationItem.key;
annotationObj.attachmentItemID = annotationItem.parentItem?.id; annotationObj.attachmentItemID = annotationItem.parentItem?.id;
@ -38,12 +38,12 @@ async function parseAnnotationJSON(annotationItem: Zotero.Item) {
function serializeAnnotations( function serializeAnnotations(
annotations: Required<CustomAnnotationJSON>[], annotations: Required<CustomAnnotationJSON>[],
skipEmbeddingItemData: boolean = false, skipEmbeddingItemData: boolean = false,
skipCitation: boolean = false skipCitation: boolean = false,
) { ) {
let storedCitationItems = []; const storedCitationItems = [];
let html = ""; let html = "";
for (let annotation of annotations) { for (const annotation of annotations) {
let attachmentItem = Zotero.Items.get(annotation.attachmentItemID); const attachmentItem = Zotero.Items.get(annotation.attachmentItemID);
if (!attachmentItem) { if (!attachmentItem) {
continue; continue;
} }
@ -64,7 +64,7 @@ function serializeAnnotations(
let quotedHighlightHTML = ""; let quotedHighlightHTML = "";
let commentHTML = ""; let commentHTML = "";
let storedAnnotation: any = { const storedAnnotation: any = {
attachmentURI: Zotero.URI.getItemURI(attachmentItem), attachmentURI: Zotero.URI.getItemURI(attachmentItem),
annotationKey: annotation.id, annotationKey: annotation.id,
color: annotation.color, color: annotation.color,
@ -73,12 +73,12 @@ function serializeAnnotations(
}; };
// Citation // Citation
let parentItem = skipCitation const parentItem = skipCitation
? undefined ? undefined
: attachmentItem.parentID && Zotero.Items.get(attachmentItem.parentID); : attachmentItem.parentID && Zotero.Items.get(attachmentItem.parentID);
if (parentItem) { if (parentItem) {
let uris = [Zotero.URI.getItemURI(parentItem)]; const uris = [Zotero.URI.getItemURI(parentItem)];
let citationItem: any = { const citationItem: any = {
uris, uris,
locator: annotation.pageLabel, locator: annotation.pageLabel,
}; };
@ -86,48 +86,48 @@ function serializeAnnotations(
// Note: integration.js` uses `Zotero.Cite.System.prototype.retrieveItem`, // Note: integration.js` uses `Zotero.Cite.System.prototype.retrieveItem`,
// which produces a little bit different CSL JSON // which produces a little bit different CSL JSON
// @ts-ignore // @ts-ignore
let itemData = Zotero.Utilities.Item.itemToCSLJSON(parentItem); const itemData = Zotero.Utilities.Item.itemToCSLJSON(parentItem);
if (!skipEmbeddingItemData) { if (!skipEmbeddingItemData) {
citationItem.itemData = itemData; citationItem.itemData = itemData;
} }
let item = storedCitationItems.find((item) => const item = storedCitationItems.find((item) =>
item.uris.some((uri) => uris.includes(uri)) item.uris.some((uri) => uris.includes(uri)),
); );
if (!item) { if (!item) {
storedCitationItems.push({ uris, itemData }); storedCitationItems.push({ uris, itemData });
} }
storedAnnotation.citationItem = citationItem; storedAnnotation.citationItem = citationItem;
let citation = { const citation = {
citationItems: [citationItem], citationItems: [citationItem],
properties: {}, properties: {},
}; };
let citationWithData = JSON.parse(JSON.stringify(citation)); const citationWithData = JSON.parse(JSON.stringify(citation));
citationWithData.citationItems[0].itemData = itemData; citationWithData.citationItems[0].itemData = itemData;
let formatted = const formatted =
Zotero.EditorInstanceUtilities.formatCitation(citationWithData); Zotero.EditorInstanceUtilities.formatCitation(citationWithData);
citationHTML = `<span class="citation" data-citation="${encodeURIComponent( citationHTML = `<span class="citation" data-citation="${encodeURIComponent(
JSON.stringify(citation) JSON.stringify(citation),
)}">${formatted}</span>`; )}">${formatted}</span>`;
} }
// Image // Image
if (annotation.imageAttachmentKey) { if (annotation.imageAttachmentKey) {
// Normalize image dimensions to 1.25 of the print size // Normalize image dimensions to 1.25 of the print size
let rect = annotation.position.rects[0]; const rect = annotation.position.rects[0];
let rectWidth = rect[2] - rect[0]; const rectWidth = rect[2] - rect[0];
let rectHeight = rect[3] - rect[1]; const rectHeight = rect[3] - rect[1];
// Constants from pdf.js // Constants from pdf.js
const CSS_UNITS = 96.0 / 72.0; const CSS_UNITS = 96.0 / 72.0;
const PDFJS_DEFAULT_SCALE = 1.25; const PDFJS_DEFAULT_SCALE = 1.25;
let width = Math.round(rectWidth * CSS_UNITS * PDFJS_DEFAULT_SCALE); const width = Math.round(rectWidth * CSS_UNITS * PDFJS_DEFAULT_SCALE);
let height = Math.round((rectHeight * width) / rectWidth); const height = Math.round((rectHeight * width) / rectWidth);
imageHTML = `<img data-attachment-key="${ imageHTML = `<img data-attachment-key="${
annotation.imageAttachmentKey annotation.imageAttachmentKey
}" width="${width}" height="${height}" data-annotation="${encodeURIComponent( }" width="${width}" height="${height}" data-annotation="${encodeURIComponent(
JSON.stringify(storedAnnotation) JSON.stringify(storedAnnotation),
)}"/>`; )}"/>`;
} }
@ -138,17 +138,17 @@ function serializeAnnotations(
// Text // Text
if (annotation.text) { if (annotation.text) {
let text = Zotero.EditorInstanceUtilities._transformTextToHTML.call( const text = Zotero.EditorInstanceUtilities._transformTextToHTML.call(
Zotero.EditorInstanceUtilities, Zotero.EditorInstanceUtilities,
annotation.text.trim() annotation.text.trim(),
); );
highlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent( highlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(
JSON.stringify(storedAnnotation) JSON.stringify(storedAnnotation),
)}">${text}</span>`; )}">${text}</span>`;
quotedHighlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent( quotedHighlightHTML = `<span class="highlight" data-annotation="${encodeURIComponent(
JSON.stringify(storedAnnotation) JSON.stringify(storedAnnotation),
)}">${Zotero.getString( )}">${Zotero.getString(
"punctuation.openingQMark" "punctuation.openingQMark",
)}${text}${Zotero.getString("punctuation.closingQMark")}</span>`; )}${text}${Zotero.getString("punctuation.closingQMark")}</span>`;
} }
@ -156,14 +156,14 @@ function serializeAnnotations(
if (annotation.comment) { if (annotation.comment) {
commentHTML = Zotero.EditorInstanceUtilities._transformTextToHTML.call( commentHTML = Zotero.EditorInstanceUtilities._transformTextToHTML.call(
Zotero.EditorInstanceUtilities, Zotero.EditorInstanceUtilities,
annotation.comment.trim() annotation.comment.trim(),
); );
} }
let template: string = ""; let template: string = "";
if (annotation.type === "highlight") { if (annotation.type === "highlight") {
template = Zotero.Prefs.get( template = Zotero.Prefs.get(
"annotations.noteTemplates.highlight" "annotations.noteTemplates.highlight",
) as string; ) as string;
} else if (annotation.type === "note") { } else if (annotation.type === "note") {
template = Zotero.Prefs.get("annotations.noteTemplates.note") as string; template = Zotero.Prefs.get("annotations.noteTemplates.note") as string;
@ -176,10 +176,10 @@ function serializeAnnotations(
template = template.replace( template = template.replace(
/(<blockquote>[^<>]*?)({{highlight}})([\s\S]*?<\/blockquote>)/g, /(<blockquote>[^<>]*?)({{highlight}})([\s\S]*?<\/blockquote>)/g,
(match, p1, p2, p3) => p1 + "{{highlight quotes='false'}}" + p3 (match, p1, p2, p3) => p1 + "{{highlight quotes='false'}}" + p3,
); );
let vars = { const vars = {
color: annotation.color || "", color: annotation.color || "",
// Include quotation marks by default, but allow to disable with `quotes='false'` // Include quotation marks by default, but allow to disable with `quotes='false'`
highlight: (attrs: any) => highlight: (attrs: any) =>
@ -196,7 +196,7 @@ function serializeAnnotations(
let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate( let templateHTML = Zotero.Utilities.Internal.generateHTMLFromTemplate(
template, template,
vars vars,
); );
// Remove some spaces at the end of paragraph // Remove some spaces at the end of paragraph
templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, "$2"); templateHTML = templateHTML.replace(/([\s]*)(<\/p)/g, "$2");
@ -209,9 +209,9 @@ function serializeAnnotations(
export async function importAnnotationImagesToNote( export async function importAnnotationImagesToNote(
note: Zotero.Item | undefined, note: Zotero.Item | undefined,
annotations: CustomAnnotationJSON[] annotations: CustomAnnotationJSON[],
) { ) {
for (let annotation of annotations) { for (const annotation of annotations) {
if (annotation.image && note) { if (annotation.image && note) {
annotation.imageAttachmentKey = annotation.imageAttachmentKey =
(await importImageToNote(note, annotation.image)) || ""; (await importImageToNote(note, annotation.image)) || "";
@ -226,9 +226,9 @@ export async function parseAnnotationHTML(
noteItem?: Zotero.Item; // If you are sure there are no image annotations, note is not required. noteItem?: Zotero.Item; // If you are sure there are no image annotations, note is not required.
ignoreComment?: boolean; ignoreComment?: boolean;
skipCitation?: boolean; skipCitation?: boolean;
} = {} } = {},
) { ) {
let annotationJSONList: CustomAnnotationJSON[] = []; const annotationJSONList: CustomAnnotationJSON[] = [];
for (const annot of annotations) { for (const annot of annotations) {
const annotJson = await parseAnnotationJSON(annot); const annotJson = await parseAnnotationJSON(annot);
if (options.ignoreComment && annotJson?.comment) { if (options.ignoreComment && annotJson?.comment) {
@ -241,7 +241,7 @@ export async function parseAnnotationHTML(
const html = serializeAnnotations( const html = serializeAnnotations(
annotationJSONList as Required<CustomAnnotationJSON>[], annotationJSONList as Required<CustomAnnotationJSON>[],
false, false,
options.skipCitation options.skipCitation,
).html; ).html;
return html; return html;
} }

View File

@ -1,7 +1,7 @@
export async function parseCitationHTML(citationIds: number[]) { export async function parseCitationHTML(citationIds: number[]) {
let html = ""; let html = "";
let items = await Zotero.Items.getAsync(citationIds); const items = await Zotero.Items.getAsync(citationIds);
for (let item of items) { for (const item of items) {
if ( if (
item.isNote() && item.isNote() &&
!(await Zotero.Notes.ensureEmbeddedImagesAreAvailable(item)) && !(await Zotero.Notes.ensureEmbeddedImagesAreAvailable(item)) &&
@ -11,11 +11,11 @@ export async function parseCitationHTML(citationIds: number[]) {
} }
} }
for (let item of items) { for (const item of items) {
if (item.isRegularItem()) { if (item.isRegularItem()) {
// @ts-ignore // @ts-ignore
let itemData = Zotero.Utilities.Item.itemToCSLJSON(item); const itemData = Zotero.Utilities.Item.itemToCSLJSON(item);
let citation = { const citation = {
citationItems: [ citationItems: [
{ {
uris: [Zotero.URI.getItemURI(item)], uris: [Zotero.URI.getItemURI(item)],
@ -24,9 +24,9 @@ export async function parseCitationHTML(citationIds: number[]) {
], ],
properties: {}, properties: {},
}; };
let formatted = Zotero.EditorInstanceUtilities.formatCitation(citation); const formatted = Zotero.EditorInstanceUtilities.formatCitation(citation);
html += `<p><span class="citation" data-citation="${encodeURIComponent( html += `<p><span class="citation" data-citation="${encodeURIComponent(
JSON.stringify(citation) JSON.stringify(citation),
)}">${formatted}</span></p>`; )}">${formatted}</span></p>`;
} }
} }

View File

@ -27,7 +27,7 @@ function insert(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
content: string = "", content: string = "",
position: number | "end" | "start" | "cursor" = "cursor", position: number | "end" | "start" | "cursor" = "cursor",
select?: boolean select?: boolean,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
@ -45,7 +45,7 @@ function insert(
EditorAPI.refocusEditor(() => { EditorAPI.refocusEditor(() => {
EditorAPI.setSelection( EditorAPI.setSelection(
(position as number) + slice.content.size, (position as number) + slice.content.size,
position as number position as number,
)(core.view.state, core.view.dispatch); )(core.view.state, core.view.dispatch);
}); });
} }
@ -61,7 +61,7 @@ function move(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
from: number, from: number,
to: number, to: number,
delta: number delta: number,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
@ -106,7 +106,7 @@ function replace(
| "link" | "link"
| "code", | "code",
markAttrs: Record<string, any>, markAttrs: Record<string, any>,
select?: boolean select?: boolean,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
@ -119,7 +119,7 @@ function replace(
JSON.stringify(nodeAttrs), JSON.stringify(nodeAttrs),
schema.marks[markTypeName], schema.marks[markTypeName],
JSON.stringify(markAttrs), JSON.stringify(markAttrs),
select select,
)(core.view.state, core.view.dispatch); )(core.view.state, core.view.dispatch);
} }
@ -133,7 +133,7 @@ function scroll(editor: Zotero.EditorInstance, lineIndex: number) {
function getEditorInstance(noteId: number) { function getEditorInstance(noteId: number) {
const editor = Zotero.Notes._editorInstances.find( const editor = Zotero.Notes._editorInstances.find(
(e) => (e) =>
e._item.id === noteId && !Components.utils.isDeadWrapper(e._iframeWindow) e._item.id === noteId && !Components.utils.isDeadWrapper(e._iframeWindow),
); );
return editor; return editor;
} }
@ -179,7 +179,7 @@ function getLineAtCursor(editor: Zotero.EditorInstance) {
function getDOMAtLine( function getDOMAtLine(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
lineIndex: number lineIndex: number,
): HTMLElement { ): HTMLElement {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const lineNodeDesc = const lineNodeDesc =
@ -192,7 +192,7 @@ function getDOMAtLine(
function getPositionAtLine( function getPositionAtLine(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
lineIndex: number, lineIndex: number,
type: "start" | "end" = "end" type: "start" | "end" = "end",
): number { ): number {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const lineNodeDesc = const lineNodeDesc =
@ -204,8 +204,8 @@ function getPositionAtLine(
0, 0,
Math.min( Math.min(
type === "end" ? linePos + lineNodeDesc.size - 1 : linePos - 1, type === "end" ? linePos + lineNodeDesc.size - 1 : linePos - 1,
core.view.state.tr.doc.content.size core.view.state.tr.doc.content.size,
) ),
); );
} }
@ -217,12 +217,12 @@ function getURLAtCursor(editor: Zotero.EditorInstance) {
function updateURLAtCursor( function updateURLAtCursor(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
text: string | undefined, text: string | undefined,
url: string url: string,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
let from = core.view.state.selection.from; const from = core.view.state.selection.from;
let to = core.view.state.selection.to; const to = core.view.state.selection.to;
const schema = core.view.state.schema; const schema = core.view.state.schema;
if (!url) { if (!url) {
return; return;
@ -232,13 +232,13 @@ function updateURLAtCursor(
text, text,
schema.marks.link, schema.marks.link,
JSON.stringify({ href: url }), JSON.stringify({ href: url }),
schema.marks.link schema.marks.link,
)(core.view.state, core.view.dispatch); )(core.view.state, core.view.dispatch);
EditorAPI.refocusEditor(() => { EditorAPI.refocusEditor(() => {
core.view.dispatch( core.view.dispatch(
core.view.state.tr.setSelection( core.view.state.tr.setSelection(
TextSelection.create(core.view.state.tr.doc, from, to) TextSelection.create(core.view.state.tr.doc, from, to),
) ),
); );
}); });
} }
@ -246,7 +246,7 @@ function updateURLAtCursor(
function updateHeadingTextAtLine( function updateHeadingTextAtLine(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
lineIndex: number, lineIndex: number,
text: string text: string,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const schema = core.view.state.schema; const schema = core.view.state.schema;
@ -260,13 +260,13 @@ function updateHeadingTextAtLine(
to, to,
text, text,
schema.nodes.heading, schema.nodes.heading,
JSON.stringify({ level }) JSON.stringify({ level }),
)(core.view.state, core.view.dispatch); )(core.view.state, core.view.dispatch);
EditorAPI.refocusEditor(() => { EditorAPI.refocusEditor(() => {
core.view.dispatch( core.view.dispatch(
core.view.state.tr.setSelection( core.view.state.tr.setSelection(
TextSelection.create(core.view.state.tr.doc, from, from + text.length) TextSelection.create(core.view.state.tr.doc, from, from + text.length),
) ),
); );
}); });
} }
@ -280,7 +280,7 @@ function isImageAtCursor(editor: Zotero.EditorInstance) {
function updateImageDimensionsAtCursor( function updateImageDimensionsAtCursor(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
width: number width: number,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
@ -290,7 +290,7 @@ function updateImageDimensionsAtCursor(
width, width,
undefined, undefined,
core.view.state, core.view.state,
core.view.dispatch core.view.dispatch,
); );
} }
@ -298,7 +298,7 @@ function moveLines(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
fromIndex: number, fromIndex: number,
toIndex: number, toIndex: number,
targetIndex: number targetIndex: number,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
@ -317,8 +317,12 @@ function moveLines(
EditorAPI.refocusEditor(() => { EditorAPI.refocusEditor(() => {
core.view.dispatch( core.view.dispatch(
core.view.state.tr.setSelection( core.view.state.tr.setSelection(
TextSelection.create(core.view.state.tr.doc, target, target + to - from) TextSelection.create(
) core.view.state.tr.doc,
target,
target + to - from,
),
),
); );
}); });
} }
@ -327,7 +331,7 @@ function moveHeading(
editor: Zotero.EditorInstance | undefined, editor: Zotero.EditorInstance | undefined,
currentNode: TreeModel.Node<NoteNodeData>, currentNode: TreeModel.Node<NoteNodeData>,
targetNode: TreeModel.Node<NoteNodeData>, targetNode: TreeModel.Node<NoteNodeData>,
as: "child" | "before" | "after" as: "child" | "before" | "after",
) { ) {
if (!editor || targetNode.getPath().indexOf(currentNode) >= 0) { if (!editor || targetNode.getPath().indexOf(currentNode) >= 0) {
return; return;
@ -359,13 +363,13 @@ function moveHeading(
const fromIndex = currentNode.model.lineIndex; const fromIndex = currentNode.model.lineIndex;
const toIndex = currentNode.model.endIndex; const toIndex = currentNode.model.endIndex;
let levelChange = targetLevel - currentNode.model.level; const levelChange = targetLevel - currentNode.model.level;
const core = getEditorCore(editor); const core = getEditorCore(editor);
const EditorAPI = getEditorAPI(editor); const EditorAPI = getEditorAPI(editor);
EditorAPI.updateHeadingsInRange( EditorAPI.updateHeadingsInRange(
getPositionAtLine(editor, fromIndex, "start"), getPositionAtLine(editor, fromIndex, "start"),
getPositionAtLine(editor, toIndex, "end"), getPositionAtLine(editor, toIndex, "end"),
levelChange levelChange,
)(core.view.state, core.view.dispatch); )(core.view.state, core.view.dispatch);
moveLines(editor, fromIndex, toIndex, targetIndex); moveLines(editor, fromIndex, toIndex, targetIndex);
} }
@ -373,7 +377,7 @@ function moveHeading(
function getTextBetween( function getTextBetween(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
from: number, from: number,
to: number to: number,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
return core.view.state.doc.textBetween(from, to); return core.view.state.doc.textBetween(from, to);
@ -382,7 +386,7 @@ function getTextBetween(
function getTextBetweenLines( function getTextBetweenLines(
editor: Zotero.EditorInstance, editor: Zotero.EditorInstance,
fromIndex: number, fromIndex: number,
toIndex: number toIndex: number,
) { ) {
const core = getEditorCore(editor); const core = getEditorCore(editor);
const from = getPositionAtLine(editor, fromIndex, "start"); const from = getPositionAtLine(editor, fromIndex, "start");

View File

@ -10,7 +10,7 @@ function showHint(text: string) {
async function showHintWithLink( async function showHintWithLink(
text: string, text: string,
linkText: string, linkText: string,
linkCallback: (ev: MouseEvent) => any linkCallback: (ev: MouseEvent) => any,
) { ) {
const progress = new ztoolkit.ProgressWindow(PROGRESS_TITLE) const progress = new ztoolkit.ProgressWindow(PROGRESS_TITLE)
.createLine({ text, progress: 100, type: "default" }) .createLine({ text, progress: 100, type: "default" })
@ -20,7 +20,7 @@ async function showHintWithLink(
await waitUtilAsync(() => await waitUtilAsync(() =>
// @ts-ignore // @ts-ignore
Boolean(progress.lines && progress.lines[0]._itemText) Boolean(progress.lines && progress.lines[0]._itemText),
); );
// @ts-ignore // @ts-ignore
progress.lines[0]._hbox.ownerDocument progress.lines[0]._hbox.ownerDocument

View File

@ -12,7 +12,7 @@ export async function itemPicker() {
: "chrome://zotero/content/selectItemsDialog.xul", : "chrome://zotero/content/selectItemsDialog.xul",
"", "",
"chrome,dialog=no,centerscreen,resizable=yes", "chrome,dialog=no,centerscreen,resizable=yes",
io io,
); );
await io.deferred.promise; await io.deferred.promise;

View File

@ -26,7 +26,7 @@ export function getNoteLinkParams(link: string) {
link, link,
libraryID: -1, libraryID: -1,
noteKey: undefined, noteKey: undefined,
noteItem: false as false, noteItem: false as const,
ignore: null, ignore: null,
lineIndex: null, lineIndex: null,
}; };
@ -38,7 +38,7 @@ export function getNoteLink(
options: { options: {
ignore?: boolean | null; ignore?: boolean | null;
lineIndex?: number | null; lineIndex?: number | null;
} = {} } = {},
) { ) {
const libraryID = noteItem.libraryID; const libraryID = noteItem.libraryID;
const library = Zotero.Libraries.get(libraryID); const library = Zotero.Libraries.get(libraryID);
@ -77,7 +77,7 @@ export function getNoteLink(
export function getLinkedNotesRecursively( export function getLinkedNotesRecursively(
link: string, link: string,
ignoreIds: number[] = [] ignoreIds: number[] = [],
) { ) {
const linkParams = getNoteLinkParams(link); const linkParams = getNoteLinkParams(link);
if (!linkParams.noteItem) return []; if (!linkParams.noteItem) return [];
@ -98,6 +98,6 @@ export function getLinkedNotesRecursively(
} }
return acc; return acc;
}, },
[linkParams.noteItem.id] as number[] [linkParams.noteItem.id] as number[],
); );
} }

View File

@ -39,22 +39,20 @@ function initLocale() {
* getString("addon-dynamic-example", { args: { count: 2 } }); // I have 2 apples * getString("addon-dynamic-example", { args: { count: 2 } }); // I have 2 apples
* ``` * ```
*/ */
function getString(localeString: string): string; function getString(localString: string): string;
function getString(localeString: string, branch: string): string; function getString(localString: string, branch: string): string;
function getString( function getString(
localeString: string, localeString: string,
options: { branch?: string | undefined; args?: Record<string, unknown> } options: { branch?: string | undefined; args?: Record<string, unknown> },
): string; ): string;
function getString(...inputs: any[]) { function getString(...inputs: any[]) {
// Old .properties uses . while .ftl uses -
const localeString = (inputs[0] as string).replace(/\./g, "-");
if (inputs.length === 1) { if (inputs.length === 1) {
return _getString(localeString); return _getString(inputs[0]);
} else if (inputs.length === 2) { } else if (inputs.length === 2) {
if (typeof inputs[1] === "string") { if (typeof inputs[1] === "string") {
return _getString(localeString, { branch: inputs[1] }); return _getString(inputs[0], { branch: inputs[1] });
} else { } else {
return _getString(localeString, inputs[1]); return _getString(inputs[0], inputs[1]);
} }
} else { } else {
throw new Error("Invalid arguments"); throw new Error("Invalid arguments");
@ -63,18 +61,30 @@ function getString(...inputs: any[]) {
function _getString( function _getString(
localeString: string, localeString: string,
options: { branch?: string | undefined; args?: Record<string, unknown> } = {} options: { branch?: string | undefined; args?: Record<string, unknown> } = {},
): string { ): string {
switch (localeString) {
case "alt":
return Zotero.isMac ? "⌥" : "Alt";
case "ctrl":
return Zotero.isMac ? "⌘" : "Ctrl";
case "shift":
return Zotero.isMac ? "⇧" : "Shift";
}
const localStringWithPrefix = `${config.addonRef}-${localeString.replace(
/\./g,
"-",
)}`;
const { branch, args } = options; const { branch, args } = options;
const pattern = addon.data.locale?.current.formatMessagesSync([ const pattern = addon.data.locale?.current.formatMessagesSync([
{ id: localeString, args }, { id: localStringWithPrefix, args },
])[0]; ])[0];
if (!pattern) { if (!pattern) {
return localeString; return localStringWithPrefix;
} }
if (branch && pattern.attributes) { if (branch && pattern.attributes) {
return pattern.attributes[branch] || localeString; return pattern.attributes[branch] || localStringWithPrefix;
} else { } else {
return pattern.value || localeString; return pattern.value || localStringWithPrefix;
} }
} }

View File

@ -25,7 +25,7 @@ function parseHTMLLines(html: string): string[] {
html = html.replace(/<div[^>]*data-schema-version[^>]*>/, ""); html = html.replace(/<div[^>]*data-schema-version[^>]*>/, "");
html = html.replace(/<\/div>/, ""); html = html.replace(/<\/div>/, "");
} }
let noteLines = html.split("\n").filter((e) => e); const noteLines = html.split("\n").filter((e) => e);
// A cache for temporarily stored lines // A cache for temporarily stored lines
let previousLineCache = []; let previousLineCache = [];
@ -33,12 +33,12 @@ function parseHTMLLines(html: string): string[] {
const forceInline = ["table", "blockquote", "pre", "ol", "ul"]; const forceInline = ["table", "blockquote", "pre", "ol", "ul"];
const selfInline: string[] = []; const selfInline: string[] = [];
let forceInlineStack = []; const forceInlineStack = [];
let forceInlineFlag = false; let forceInlineFlag = false;
let selfInlineFlag = false; let selfInlineFlag = false;
const parsedLines = []; const parsedLines = [];
for (let line of noteLines) { for (const line of noteLines) {
// restore self inline flag // restore self inline flag
selfInlineFlag = false; selfInlineFlag = false;
@ -88,7 +88,7 @@ function parseHTMLLines(html: string): string[] {
// Append cache to previous line // Append cache to previous line
if (previousLineCache.length) { if (previousLineCache.length) {
parsedLines[parsedLines.length - 1] += `\n${previousLineCache.join( parsedLines[parsedLines.length - 1] += `\n${previousLineCache.join(
"\n" "\n",
)}`; )}`;
previousLineCache = []; previousLineCache = [];
} }
@ -113,7 +113,7 @@ function getLinesInNote(note: Zotero.Item): string[] {
if (!note) { if (!note) {
return []; return [];
} }
let noteText: string = note.getNote(); const noteText: string = note.getNote();
return parseHTMLLines(noteText); return parseHTMLLines(noteText);
} }
@ -121,20 +121,20 @@ async function setLinesToNote(note: Zotero.Item, lines: string[]) {
if (!note) { if (!note) {
return []; return [];
} }
let noteText: string = note.getNote(); const noteText: string = note.getNote();
let containerIndex = noteText.search(/data-schema-version="[0-9]*/g); const containerIndex = noteText.search(/data-schema-version="[0-9]*/g);
if (containerIndex === -1) { if (containerIndex === -1) {
note.setNote( note.setNote(
`<div data-schema-version="${config.dataSchemaVersion}">${lines.join( `<div data-schema-version="${config.dataSchemaVersion}">${lines.join(
"\n" "\n",
)}</div>` )}</div>`,
); );
} else { } else {
let noteHead = noteText.substring(0, containerIndex); const noteHead = noteText.substring(0, containerIndex);
note.setNote( note.setNote(
`${noteHead}data-schema-version="${ `${noteHead}data-schema-version="${
config.dataSchemaVersion config.dataSchemaVersion
}">${lines.join("\n")}</div>` }">${lines.join("\n")}</div>`,
); );
} }
@ -145,12 +145,12 @@ async function addLineToNote(
note: Zotero.Item, note: Zotero.Item,
html: string, html: string,
lineIndex: number = -1, lineIndex: number = -1,
forceMetadata: boolean = false forceMetadata: boolean = false,
) { ) {
if (!note || !html) { if (!note || !html) {
return; return;
} }
let noteLines = getLinesInNote(note); const noteLines = getLinesInNote(note);
if (lineIndex < 0 || lineIndex >= noteLines.length) { if (lineIndex < 0 || lineIndex >= noteLines.length) {
lineIndex = noteLines.length; lineIndex = noteLines.length;
} }
@ -174,12 +174,12 @@ async function addLineToNote(
async function renderNoteHTML( async function renderNoteHTML(
html: string, html: string,
refNotes: Zotero.Item[] refNotes: Zotero.Item[],
): Promise<string>; ): Promise<string>;
async function renderNoteHTML(noteItem: Zotero.Item): Promise<string>; async function renderNoteHTML(noteItem: Zotero.Item): Promise<string>;
async function renderNoteHTML( async function renderNoteHTML(
htmlOrNote: string | Zotero.Item, htmlOrNote: string | Zotero.Item,
refNotes?: Zotero.Item[] refNotes?: Zotero.Item[],
): Promise<string> { ): Promise<string> {
let html: string; let html: string;
if (typeof htmlOrNote === "string") { if (typeof htmlOrNote === "string") {
@ -195,30 +195,32 @@ async function renderNoteHTML(
} }
const parser = ztoolkit.getDOMParser(); const parser = ztoolkit.getDOMParser();
let doc = parser.parseFromString(html, "text/html"); const doc = parser.parseFromString(html, "text/html");
const imageAttachments = refNotes.reduce((acc, note) => { const imageAttachments = refNotes.reduce((acc, note) => {
acc.push(...Zotero.Items.get(note.getAttachments())); acc.push(...Zotero.Items.get(note.getAttachments()));
return acc; return acc;
}, [] as Zotero.Item[]); }, [] as Zotero.Item[]);
for (let attachment of imageAttachments) { for (const attachment of imageAttachments) {
if (await attachment.fileExists()) { if (await attachment.fileExists()) {
let imageNodes = Array.from( const imageNodes = Array.from(
doc.querySelectorAll(`img[data-attachment-key="${attachment.key}"]`) doc.querySelectorAll(`img[data-attachment-key="${attachment.key}"]`),
); );
if (imageNodes.length) { if (imageNodes.length) {
try { try {
const b64 = await getItemDataURL(attachment); const b64 = await getItemDataURL(attachment);
imageNodes.forEach((node) => node.setAttribute("src", b64)); imageNodes.forEach((node) => node.setAttribute("src", b64));
} catch (e) {} } catch (e) {
ztoolkit.log(e);
}
} }
} }
} }
const bgNodes = doc.querySelectorAll( const bgNodes = doc.querySelectorAll(
"span[style]" "span[style]",
) as NodeListOf<HTMLElement>; ) as NodeListOf<HTMLElement>;
for (let node of bgNodes) { for (const node of bgNodes) {
// Browser converts #RRGGBBAA hex color to rgba function, and we convert it to rgb function, // Browser converts #RRGGBBAA hex color to rgba function, and we convert it to rgb function,
// because word processors don't understand colors with alpha channel // because word processors don't understand colors with alpha channel
if ( if (
@ -240,7 +242,7 @@ async function renderNoteHTML(
node.innerHTML.replace(mathDelimiterRegex, ""), node.innerHTML.replace(mathDelimiterRegex, ""),
{ {
throwOnError: false, throwOnError: false,
} },
); );
}); });
return doc.body.innerHTML; return doc.body.innerHTML;
@ -258,12 +260,12 @@ function getNoteType(id: number) {
function getNoteTree( function getNoteTree(
note: Zotero.Item, note: Zotero.Item,
parseLink: boolean = true parseLink: boolean = true,
): TreeModel.Node<NoteNodeData> { ): TreeModel.Node<NoteNodeData> {
const noteLines = getLinesInNote(note); const noteLines = getLinesInNote(note);
const parser = ztoolkit.getDOMParser(); const parser = ztoolkit.getDOMParser();
let tree = new TreeModel(); const tree = new TreeModel();
let root = tree.parse({ const root = tree.parse({
id: -1, id: -1,
level: 0, level: 0,
lineIndex: -1, lineIndex: -1,
@ -273,9 +275,9 @@ function getNoteTree(
let lastNode = root; let lastNode = root;
const headingRegex = new RegExp("^<h([1-6])(.*?)</h[1-6]>"); const headingRegex = new RegExp("^<h([1-6])(.*?)</h[1-6]>");
const linkRegex = new RegExp('href="(zotero://note/[^"]*)"'); const linkRegex = new RegExp('href="(zotero://note/[^"]*)"');
for (let i in noteLines) { for (const i in noteLines) {
let currentLevel = 7; let currentLevel = 7;
let lineElement = noteLines[i]; const lineElement = noteLines[i];
const matchHeadingResult = lineElement.match(headingRegex); const matchHeadingResult = lineElement.match(headingRegex);
const matchLinkResult = parseLink ? lineElement.match(linkRegex) : null; const matchLinkResult = parseLink ? lineElement.match(linkRegex) : null;
const isHeading = Boolean(matchHeadingResult); const isHeading = Boolean(matchHeadingResult);
@ -333,7 +335,7 @@ function getNoteTreeFlattened(
keepRoot?: boolean; keepRoot?: boolean;
keepLink?: boolean; keepLink?: boolean;
customFilter?: (node: TreeModel.Node<NoteNodeData>) => boolean; customFilter?: (node: TreeModel.Node<NoteNodeData>) => boolean;
} = { keepRoot: false, keepLink: false } } = { keepRoot: false, keepLink: false },
): TreeModel.Node<NoteNodeData>[] { ): TreeModel.Node<NoteNodeData>[] {
if (!note) { if (!note) {
return []; return [];
@ -342,14 +344,14 @@ function getNoteTreeFlattened(
(node) => (node) =>
(options.keepRoot || node.model.lineIndex >= 0) && (options.keepRoot || node.model.lineIndex >= 0) &&
(options.keepLink || node.model.level <= 6) && (options.keepLink || node.model.level <= 6) &&
(options.customFilter ? options.customFilter(node) : true) (options.customFilter ? options.customFilter(node) : true),
); );
} }
function getNoteTreeNodeById( function getNoteTreeNodeById(
note: Zotero.Item, note: Zotero.Item,
id: number, id: number,
root: TreeModel.Node<NoteNodeData> | undefined = undefined root: TreeModel.Node<NoteNodeData> | undefined = undefined,
) { ) {
root = root || getNoteTree(note); root = root || getNoteTree(note);
return root.first(function (node) { return root.first(function (node) {
@ -360,7 +362,7 @@ function getNoteTreeNodeById(
function getNoteTreeNodesByLevel( function getNoteTreeNodesByLevel(
note: Zotero.Item, note: Zotero.Item,
level: number, level: number,
root: TreeModel.Node<NoteNodeData> | undefined = undefined root: TreeModel.Node<NoteNodeData> | undefined = undefined,
) { ) {
root = root || getNoteTree(note); root = root || getNoteTree(note);
return root.all(function (node) { return root.all(function (node) {
@ -370,7 +372,7 @@ function getNoteTreeNodesByLevel(
async function copyEmbeddedImagesFromNote( async function copyEmbeddedImagesFromNote(
targetNote: Zotero.Item, targetNote: Zotero.Item,
sourceNotes: Zotero.Item[] sourceNotes: Zotero.Item[],
) { ) {
await Zotero.DB.executeTransaction(async () => { await Zotero.DB.executeTransaction(async () => {
for (const fromNote of sourceNotes) { for (const fromNote of sourceNotes) {
@ -382,9 +384,9 @@ async function copyEmbeddedImagesFromNote(
async function copyEmbeddedImagesInHTML( async function copyEmbeddedImagesInHTML(
html: string, html: string,
targetNote?: Zotero.Item, targetNote?: Zotero.Item,
refNotes: Zotero.Item[] = [] refNotes: Zotero.Item[] = [],
) { ) {
ztoolkit.log("parseEmbeddedImagesInHTML", arguments); ztoolkit.log("parseEmbeddedImagesInHTML", html, targetNote, refNotes);
if (!targetNote) { if (!targetNote) {
return html; return html;
} }
@ -399,13 +401,13 @@ async function copyEmbeddedImagesInHTML(
ztoolkit.log(attachments); ztoolkit.log(attachments);
let doc = ztoolkit.getDOMParser().parseFromString(html, "text/html"); const doc = ztoolkit.getDOMParser().parseFromString(html, "text/html");
// Copy note image attachments and replace keys in the new note // Copy note image attachments and replace keys in the new note
for (let attachment of attachments) { for (const attachment of attachments) {
if (await attachment.fileExists()) { if (await attachment.fileExists()) {
let nodes = Array.from( const nodes = Array.from(
doc.querySelectorAll(`img[data-attachment-key="${attachment.key}"]`) doc.querySelectorAll(`img[data-attachment-key="${attachment.key}"]`),
); );
if (nodes.length) { if (nodes.length) {
let copiedAttachment: Zotero.Item; let copiedAttachment: Zotero.Item;
@ -417,7 +419,7 @@ async function copyEmbeddedImagesInHTML(
}); });
}); });
nodes.forEach((node) => nodes.forEach((node) =>
node.setAttribute("data-attachment-key", copiedAttachment.key) node.setAttribute("data-attachment-key", copiedAttachment.key),
); );
} }
} }
@ -427,16 +429,16 @@ async function copyEmbeddedImagesInHTML(
} }
function dataURLtoBlob(dataurl: string) { function dataURLtoBlob(dataurl: string) {
let parts = dataurl.split(","); const parts = dataurl.split(",");
let matches = parts[0]?.match(/:(.*?);/); const matches = parts[0]?.match(/:(.*?);/);
if (!matches || !matches[1]) { if (!matches || !matches[1]) {
return; return;
} }
let mime = matches[1]; const mime = matches[1];
if (parts[0].indexOf("base64") !== -1) { if (parts[0].indexOf("base64") !== -1) {
let bstr = ztoolkit.getGlobal("atob")(parts[1]); const bstr = ztoolkit.getGlobal("atob")(parts[1]);
let n = bstr.length; let n = bstr.length;
let u8arr = new Uint8Array(n); const u8arr = new Uint8Array(n);
while (n--) { while (n--) {
u8arr[n] = bstr.charCodeAt(n); u8arr[n] = bstr.charCodeAt(n);
} }
@ -451,7 +453,7 @@ function dataURLtoBlob(dataurl: string) {
async function importImageToNote( async function importImageToNote(
note: Zotero.Item, note: Zotero.Item,
src: string, src: string,
type: "b64" | "url" | "file" = "b64" type: "b64" | "url" | "file" = "b64",
): Promise<string | void> { ): Promise<string | void> {
if (!note || !note.isNote()) { if (!note || !note.isNote()) {
return ""; return "";
@ -474,7 +476,7 @@ async function importImageToNote(
} else if (type === "file") { } else if (type === "file") {
src = Zotero.File.normalizeToUnix(src); src = Zotero.File.normalizeToUnix(src);
const noteAttachmentKeys = Zotero.Items.get(note.getAttachments()).map( const noteAttachmentKeys = Zotero.Items.get(note.getAttachments()).map(
(_i) => _i.key (_i) => _i.key,
); );
const filename = src.split("/").pop()?.split(".").shift(); const filename = src.split("/").pop()?.split(".").shift();
// The exported image is KEY.png by default. // The exported image is KEY.png by default.
@ -497,7 +499,7 @@ async function importImageToNote(
return; return;
} }
let attachment = await Zotero.Attachments.importEmbeddedImage({ const attachment = await Zotero.Attachments.importEmbeddedImage({
blob, blob,
parentItemID: note.id, parentItemID: note.id,
saveOptions: {}, saveOptions: {},

View File

@ -10,14 +10,14 @@ export function fill(
options: { char: string; position: "start" | "end" } = { options: { char: string; position: "start" | "end" } = {
char: " ", char: " ",
position: "end", position: "end",
} },
) { ) {
if (str.length >= len) { if (str.length >= len) {
return str; return str;
} }
return str[options.position === "start" ? "padStart" : "padEnd"]( return str[options.position === "start" ? "padStart" : "padEnd"](
len - str.length, len - str.length,
options.char options.char,
); );
} }
@ -53,7 +53,7 @@ export function randomString(len: number, seed?: string, chars?: string) {
len = 8; len = 8;
} }
let str = ""; let str = "";
const random: Function = seedrandom(seed); const random = seedrandom(seed);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const rnum = Math.floor(random() * chars.length); const rnum = Math.floor(random() * chars.length);
str += chars.substring(rnum, rnum + 1); str += chars.substring(rnum, rnum + 1);
@ -62,18 +62,19 @@ export function randomString(len: number, seed?: string, chars?: string) {
} }
function arrayBufferToBase64(buffer: ArrayBufferLike) { function arrayBufferToBase64(buffer: ArrayBufferLike) {
var binary = ""; let binary = "";
var bytes = new Uint8Array(buffer); const bytes = new Uint8Array(buffer);
var len = bytes.byteLength; const len = bytes.byteLength;
for (var i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]); binary += String.fromCharCode(bytes[i]);
} }
return ztoolkit.getGlobal("btoa")(binary); return ztoolkit.getGlobal("btoa")(binary);
} }
export async function getItemDataURL(item: Zotero.Item) { export async function getItemDataURL(item: Zotero.Item) {
let path = (await item.getFilePathAsync()) as string; const path = (await item.getFilePathAsync()) as string;
let buf = new Uint8Array((await OS.File.read(path, {})) as Uint8Array).buffer; const buf = new Uint8Array((await OS.File.read(path, {})) as Uint8Array)
.buffer;
return ( return (
"data:" + item.attachmentContentType + ";base64," + arrayBufferToBase64(buf) "data:" + item.attachmentContentType + ";base64," + arrayBufferToBase64(buf)
); );

View File

@ -2,7 +2,7 @@ export function waitUntil(
condition: () => boolean, condition: () => boolean,
callback: () => void, callback: () => void,
interval = 100, interval = 100,
timeout = 10000 timeout = 10000,
) { ) {
const start = Date.now(); const start = Date.now();
const intervalId = ztoolkit.getGlobal("setInterval")(() => { const intervalId = ztoolkit.getGlobal("setInterval")(() => {
@ -18,7 +18,7 @@ export function waitUntil(
export function waitUtilAsync( export function waitUtilAsync(
condition: () => boolean, condition: () => boolean,
interval = 100, interval = 100,
timeout = 10000 timeout = 10000,
) { ) {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const start = Date.now(); const start = Date.now();

View File

@ -15,7 +15,7 @@ function localeWindow(win: Window) {
const isProp = key in elem; const isProp = key in elem;
try { try {
const localeString = getString( const localeString = getString(
(isProp ? (elem as any)[key] : elem.getAttribute(key)).trim() || "" (isProp ? (elem as any)[key] : elem.getAttribute(key)).trim() || "",
); );
isProp isProp
? ((elem as any)[key] = localeString) ? ((elem as any)[key] = localeString)
@ -26,6 +26,6 @@ function localeWindow(win: Window) {
: elem.setAttribute(key, errorInfo); : elem.setAttribute(key, errorInfo);
} }
}); });
} },
); );
} }

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

@ -0,0 +1,48 @@
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.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

@ -7,13 +7,6 @@
"skipLibCheck": true, "skipLibCheck": true,
"strict": true "strict": true
}, },
"include": [ "include": ["src", "typings", "node_modules/zotero-types"],
"src", "exclude": ["build", "addon"]
"typing", }
"node_modules/zotero-types"
],
"exclude": [
"builds",
"addon"
]
}

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/ban-types */
declare interface EditorCore { declare interface EditorCore {
debouncedUpdate: Function; debouncedUpdate: Function;
disableDrag: boolean; disableDrag: boolean;

View File

@ -7,7 +7,7 @@ declare const _globalThis: {
document: Document; document: Document;
OS: typeof OS; OS: typeof OS;
Blob: typeof Blob; Blob: typeof Blob;
ztoolkit: typeof ztoolkit; ztoolkit: ZToolkit;
addon: typeof addon; addon: typeof addon;
}; };
@ -20,8 +20,11 @@ declare interface Window {
): Window; ): Window;
} }
declare const ztoolkit: import("../src/addon").MyToolkit; declare type ZToolkit = ReturnType<
// declare const ztoolkit: import("zotero-plugin-toolkit").ZoteroToolkit; typeof import("../src/utils/ztoolkit").createZToolkit
>;
declare const ztoolkit: ZToolkit;
declare const rootURI: string; declare const rootURI: string;