diff --git a/package.json b/package.json
index 70beec8..0591887 100644
--- a/package.json
+++ b/package.json
@@ -51,14 +51,15 @@
"unified": "^10.1.2",
"unist-util-visit": "^4.1.1",
"unist-util-visit-parents": "^5.1.1",
- "yamljs": "^0.3.0"
+ "yamljs": "^0.3.0",
+ "zotero-plugin-toolkit": "^0.0.9"
},
"devDependencies": {
"@types/diff": "^5.0.2",
"@types/jquery": "^3.5.14",
"@types/node": "^17.0.31",
- "esbuild": "^0.14.34",
"compressing": "^1.5.1",
+ "esbuild": "^0.14.34",
"release-it": "^14.14.0",
"zotero-types": "^0.1.2"
}
diff --git a/src/addon.ts b/src/addon.ts
index 3c8ed02..4181d44 100644
--- a/src/addon.ts
+++ b/src/addon.ts
@@ -24,6 +24,7 @@ import EditorController from "./editor/editorController";
import EditorImageViewer from "./editor/imageViewerWindow";
import TemplateWindow from "./template/templateWindow";
import { SyncUtils } from "./sync/syncUtils";
+import ZoteroToolkit from "zotero-plugin-toolkit";
class Knowledge4Zotero {
public ZoteroEvents: ZoteroEvents;
@@ -58,6 +59,8 @@ class Knowledge4Zotero {
public EditorController: EditorController;
public EditorImageViewer: EditorImageViewer;
+ public toolkit: ZoteroToolkit;
+
constructor() {
this.ZoteroEvents = new ZoteroEvents(this);
this.ZoteroViews = new ZoteroViews(this);
@@ -82,6 +85,10 @@ class Knowledge4Zotero {
this.NoteExportWindow = new NoteExportWindow(this);
this.NoteParse = new NoteParse(this);
this.knowledge = new TemplateAPI(this);
+
+ this.toolkit = new ZoteroToolkit();
+ // Disable since we are still using overlay
+ this.toolkit.UI.enableElementRecordGlobal = false
}
}
diff --git a/src/editor/editorController.ts b/src/editor/editorController.ts
index 15bc658..a48ffc9 100644
--- a/src/editor/editorController.ts
+++ b/src/editor/editorController.ts
@@ -28,7 +28,10 @@ class EditorController extends AddonBase {
injectScripts(_window: Window) {
if (!_window.document.getElementById("betternotes-script")) {
- const messageScript = _window.document.createElement("script");
+ const messageScript = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "script"
+ ) as HTMLScriptElement;
messageScript.id = "betternotes-script";
messageScript.innerHTML = `__placeholder:editorScript.js__`;
_window.document.head.append(messageScript);
diff --git a/src/editor/editorViews.ts b/src/editor/editorViews.ts
index 5704824..5492a23 100644
--- a/src/editor/editorViews.ts
+++ b/src/editor/editorViews.ts
@@ -3,7 +3,7 @@
*/
import Knowledge4Zotero from "../addon";
-import { CopyHelper, EditorMessage } from "../utils";
+import { EditorMessage } from "../utils";
import AddonBase from "../module";
class EditorViews extends AddonBase {
@@ -95,13 +95,19 @@ class EditorViews extends AddonBase {
setMainNoteDropDown,
"left"
);
- const titleNode = _window.document.createElement("div");
+ const titleNode = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "div"
+ ) as HTMLDivElement;
titleNode.innerHTML = "Set Recent Main Notes";
titleNode.title = "Click item to set it main note";
titleNode.style.textAlign = "center";
popup.childNodes[0].before(
titleNode,
- _window.document.createElement("hr")
+ this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "hr"
+ ) as HTMLHRElement
);
setMainNoteDropDown.addEventListener("mouseleave", (e) => {
popup.remove();
@@ -125,7 +131,10 @@ class EditorViews extends AddonBase {
if (isMainNote) {
// This is a main knowledge, hide all buttons except the export button and add title
addLinkDropDown.innerHTML = "";
- const header = _window.document.createElement("div");
+ const header = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "div"
+ ) as HTMLDivElement;
header.setAttribute("title", "This is a Main Note");
header.innerHTML = "Main Note";
header.setAttribute("style", "font-size: medium");
@@ -290,7 +299,10 @@ class EditorViews extends AddonBase {
);
// Title style only for normal window
- const style = _window.document.createElement("style");
+ const style = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "style"
+ ) as HTMLStyleElement;
style.id = "bn-headings";
style.innerHTML = `
.primary-editor h1::before {
@@ -344,7 +356,10 @@ class EditorViews extends AddonBase {
) {
const dropdownPopup = moreDropdown.querySelector(".popup");
if (dropdownPopup) {
- const refreshButton = _window.document.createElement("button");
+ const refreshButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
refreshButton.classList.add("option");
refreshButton.innerText = "Refresh Editor";
refreshButton.addEventListener("click", (e) => {
@@ -366,7 +381,10 @@ class EditorViews extends AddonBase {
state: instance._state,
});
});
- const previewButton = _window.document.createElement("button");
+ const previewButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
previewButton.classList.add("option");
previewButton.innerText = "Preview in Workspace";
previewButton.addEventListener("click", async (e) => {
@@ -376,7 +394,10 @@ class EditorViews extends AddonBase {
instance._item
);
});
- const copyLinkButton = _window.document.createElement("button");
+ const copyLinkButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
copyLinkButton.classList.add("option");
copyLinkButton.innerText = "Copy Note Link";
copyLinkButton.addEventListener("click", (e) => {
@@ -387,7 +408,7 @@ class EditorViews extends AddonBase {
: linkText
}
`;
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(linkText, "text/unicode")
.addText(linkHTML, "text/html")
.copy();
@@ -397,8 +418,10 @@ class EditorViews extends AddonBase {
);
});
- const copyLinkAtLineButton =
- _window.document.createElement("button");
+ const copyLinkAtLineButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
copyLinkAtLineButton.classList.add("option");
copyLinkAtLineButton.innerText = "Copy Note Link of Current Line";
copyLinkAtLineButton.addEventListener("click", (e) => {
@@ -410,7 +433,7 @@ class EditorViews extends AddonBase {
? noteItem.getNoteTitle().trim()
: linkText
}`;
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(linkText, "text/unicode")
.addText(linkHTML, "text/html")
.copy();
@@ -421,7 +444,10 @@ class EditorViews extends AddonBase {
} Copied`
);
});
- const importButton = _window.document.createElement("button");
+ const importButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
importButton.classList.add("option");
importButton.innerText = "Import from MarkDown";
importButton.addEventListener("click", async (e) => {
@@ -457,16 +483,28 @@ class EditorViews extends AddonBase {
await new Promise((resolve, reject) => {
const _document = editorInstances._iframeWindow.document;
- const knowledgeToolBar = _document.createElement("div");
+ const knowledgeToolBar = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
knowledgeToolBar.setAttribute("id", "knowledge-tools");
knowledgeToolBar.setAttribute("class", "toolbar");
- const start = _document.createElement("div");
+ const start = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
start.setAttribute("id", "knowledge-tools-start");
start.setAttribute("class", "start");
- const middle = _document.createElement("div");
+ const middle = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
middle.setAttribute("id", "knowledge-tools-middle");
middle.setAttribute("class", "middle");
- const end = _document.createElement("div");
+ const end = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
end.setAttribute("id", "knowledge-tools-end");
end.setAttribute("class", "end");
knowledgeToolBar.append(start, middle, end);
@@ -506,10 +544,16 @@ class EditorViews extends AddonBase {
(e) => e.getAttribute("id") !== `knowledge-tools-${position}`
);
}
- const dropdown = _document.createElement("div");
+ const dropdown = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
dropdown.setAttribute("class", "dropdown");
dropdown.setAttribute("id", id);
- const button = _document.createElement("button");
+ const button = this._Addon.toolkit.UI.createElement(
+ _document,
+ "button"
+ ) as HTMLButtonElement;
button.setAttribute("class", "toolbar-button");
button.setAttribute("title", title);
button.setAttribute("eventType", eventType);
@@ -544,11 +588,17 @@ class EditorViews extends AddonBase {
if (!knowledgeToolBar) {
await this.addEditorToolBar(editorInstances);
}
- const popup = _document.createElement("div");
+ const popup = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
popup.setAttribute("class", "popup");
popup.setAttribute("id", id);
for (let buttonParam of buttons) {
- const button = _document.createElement("button");
+ const button = this._Addon.toolkit.UI.createElement(
+ _document,
+ "button"
+ ) as HTMLButtonElement;
button.setAttribute("class", "option");
button.setAttribute(
"style",
@@ -604,7 +654,10 @@ class EditorViews extends AddonBase {
if (insertButton) {
insertButton.remove();
}
- insertButton = _window.document.createElement("button");
+ insertButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
insertButton.setAttribute("id", insertButtonId);
insertButton.setAttribute(
"title",
@@ -700,7 +753,10 @@ class EditorViews extends AddonBase {
if (updateButton) {
updateButton.remove();
}
- updateButton = _window.document.createElement("button");
+ updateButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
updateButton.setAttribute("id", updateButtonId);
updateButton.setAttribute(
"title",
@@ -765,7 +821,10 @@ class EditorViews extends AddonBase {
if (openInWindowButton) {
openInWindowButton.remove();
}
- openInWindowButton = _window.document.createElement("button");
+ openInWindowButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
openInWindowButton.setAttribute("id", openInWindowButtonId);
openInWindowButton.setAttribute("title", "Open In New Window");
openInWindowButton.innerHTML = this.icons["openInNewWindow"];
@@ -788,10 +847,10 @@ class EditorViews extends AddonBase {
if (previewContainer) {
previewContainer.remove();
}
- previewContainer = _window.document.createElementNS(
- "http://www.w3.org/1999/xhtml",
+ previewContainer = this._Addon.toolkit.UI.createElement(
+ _window.document,
"div"
- );
+ ) as HTMLDivElement;
previewContainer.id = "note-link-preview";
previewContainer.className = "ProseMirror primary-editor";
previewContainer.innerHTML =
@@ -840,7 +899,10 @@ class EditorViews extends AddonBase {
const _window = instance._iframeWindow;
_window.document.querySelector("#bn-image-preview")?.remove();
- const previewButton = _window.document.createElement("button");
+ const previewButton = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "button"
+ ) as HTMLButtonElement;
previewButton.innerHTML = ``;
previewButton.id = "bn-image-preview";
previewButton.addEventListener("click", (e) => {
@@ -904,7 +966,7 @@ class EditorViews extends AddonBase {
);
};
- const elementOptions: XULElementOptions = {
+ const elementOptions = {
tag: "fragment",
subElementOptions: [
{
@@ -918,12 +980,12 @@ class EditorViews extends AddonBase {
id: "menupopup-resizeImage",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [["label", "Resize Image"]],
+ attributes: { label: "Resize Image" },
customCheck: checkImageSelected,
listeners: [
- [
- "command",
- (e) => {
+ {
+ type: "command",
+ listener: (e: Event) => {
const newWidth = parseFloat(
prompt(
"Enter new width (px):",
@@ -939,8 +1001,7 @@ class EditorViews extends AddonBase {
);
}
},
- undefined,
- ],
+ },
],
},
{
@@ -955,11 +1016,11 @@ class EditorViews extends AddonBase {
id: "menupopup-copylink",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [["label", "Copy Note Link"]],
+ attributes: { label: "Copy Note Link" },
listeners: [
- [
- "command",
- (e) => {
+ {
+ type: "command",
+ listener: (e: Event) => {
const linkText = this._Addon.NoteUtils.getNoteLink(noteItem);
const linkHTML = `${
noteItem.getNoteTitle().trim()
@@ -967,7 +1028,7 @@ class EditorViews extends AddonBase {
: linkText
}
`;
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(linkText, "text/unicode")
.addText(linkHTML, "text/html")
.copy();
@@ -976,8 +1037,7 @@ class EditorViews extends AddonBase {
"Note Link Copied"
);
},
- undefined,
- ],
+ },
],
},
{
@@ -985,11 +1045,11 @@ class EditorViews extends AddonBase {
id: "menupopup-copylinkline",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [["label", `Copy Note Link of Line ${lineIndex + 1}`]],
+ attributes: { label: `Copy Note Link of Line ${lineIndex + 1}` },
listeners: [
- [
- "command",
- (e) => {
+ {
+ type: "command",
+ listener: ((e: Event) => {
const linkText = this._Addon.NoteUtils.getNoteLink(noteItem, {
withLine: true,
});
@@ -998,7 +1058,7 @@ class EditorViews extends AddonBase {
? noteItem.getNoteTitle().trim()
: linkText
}`;
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(linkText, "text/unicode")
.addText(linkHTML, "text/html")
.copy();
@@ -1006,9 +1066,8 @@ class EditorViews extends AddonBase {
"Better Notes",
`Note Link of Line ${lineIndex + 1} Copied`
);
- },
- undefined,
- ],
+ }) as EventListener,
+ },
],
},
{
@@ -1016,18 +1075,15 @@ class EditorViews extends AddonBase {
id: "menupopup-insertTextTemplateMenu",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [["label", "Insert Template (Text)"]],
+ attributes: { label: "Insert Template (Text)" },
subElementOptions: [
{
tag: "menupopup",
id: `menu_insert${instance._item.id}TextTemplatePopup`,
ignoreIfExists: true,
- attributes: [
- [
- "onpopupshowing",
- `Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Text', Zotero.Knowledge4Zotero.EditorController.activeEditor._popup.ownerDocument, '${instance._item.id}', false);`,
- ],
- ],
+ attributes: {
+ onpopupshowing: `Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Text', Zotero.Knowledge4Zotero.EditorController.activeEditor._popup.ownerDocument, '${instance._item.id}', false);`,
+ },
},
],
},
@@ -1036,18 +1092,15 @@ class EditorViews extends AddonBase {
id: "menupopup-insertItemTemplate",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [["label", "Insert Template (Item)"]],
+ attributes: { label: "Insert Template (Item)" },
subElementOptions: [
{
tag: "menupopup",
id: `menu_insert${instance._item.id}ItemTemplatePopup`,
ignoreIfExists: true,
- attributes: [
- [
- "onpopupshowing",
- `Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Item', Zotero.Knowledge4Zotero.EditorController.activeEditor._popup.ownerDocument, '${instance._item.id}', false);`,
- ],
- ],
+ attributes: {
+ onpopupshowing: `Zotero.Knowledge4Zotero.ZoteroViews.updateTemplateMenu('Item', Zotero.Knowledge4Zotero.EditorController.activeEditor._popup.ownerDocument, '${instance._item.id}', false);`,
+ },
},
],
},
@@ -1062,49 +1115,42 @@ class EditorViews extends AddonBase {
id: "menupopup-lineprevious",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [
- [
- "label",
- `L${lineIndex + 1}:${shorten(
- lineElements[lineIndex].innerText,
- 25
- )}`,
- ],
- ["disabled", true],
- ],
+ attributes: {
+ label: `L${lineIndex + 1}:${shorten(
+ lineElements[lineIndex].innerText,
+ 25
+ )}`,
+
+ disabled: true,
+ },
},
{
tag: "menuitem",
id: "menupopup-insertposition",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [
- ["label", "<--- Insert Anchor"],
- ["disabled", true],
- ],
+ attributes: { label: "<--- Insert Anchor", disabled: true },
},
{
tag: "menuitem",
id: "menupopup-linenext",
checkExistanceParent: instance._popup,
ignoreIfExists: true,
- attributes: [
- [
- "label",
+ attributes: {
+ label:
lineIndex + 1 >= lineElements.length
? "End of Note"
: `L${lineIndex + 2}:${shorten(
lineElements[lineIndex + 1].innerText,
25
)}`,
- ],
- ["disabled", true],
- ],
+ disabled: true,
+ },
},
],
};
- const fragment = this._Addon.ZoteroViews.createXULElement(
+ const fragment = this._Addon.toolkit.UI.creatElementsFromJSON(
instance._popup.ownerDocument,
elementOptions
);
diff --git a/src/editor/imageViewerWindow.ts b/src/editor/imageViewerWindow.ts
index dde626b..9a74da9 100644
--- a/src/editor/imageViewerWindow.ts
+++ b/src/editor/imageViewerWindow.ts
@@ -4,7 +4,6 @@
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
-import { CopyHelper, pick } from "../utils";
class EditorImageViewer extends AddonBase {
_window: Window;
@@ -87,7 +86,9 @@ class EditorImageViewer extends AddonBase {
this._window.document
.querySelector("#copy")
.addEventListener("click", (e) => {
- new CopyHelper().addImage(this.srcList[this.idx]).copy();
+ this._Addon.toolkit.Tool.getCopyHelper()
+ .addImage(this.srcList[this.idx])
+ .copy();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"Image Copied."
@@ -108,7 +109,7 @@ class EditorImageViewer extends AddonBase {
u8arr[n] = bstr.charCodeAt(n);
}
let ext = Zotero.MIME.getPrimaryExtension(mime, "");
- const filename = await pick(
+ const filename = await this._Addon.toolkit.Tool.openFilePicker(
Zotero.getString("noteEditor.saveImageAs"),
"save",
[[`Image(*.${ext})`, `*.${ext}`]],
diff --git a/src/note/noteExportController.ts b/src/note/noteExportController.ts
index 62700dc..b245471 100644
--- a/src/note/noteExportController.ts
+++ b/src/note/noteExportController.ts
@@ -3,7 +3,6 @@
*/
import Knowledge4Zotero from "../addon";
-import { pick } from "../utils";
import AddonBase from "../module";
class NoteExport extends AddonBase {
@@ -84,7 +83,7 @@ class NoteExport extends AddonBase {
}
if (options.exportMD) {
- const filename = await pick(
+ const filename = await this._Addon.toolkit.Tool.openFilePicker(
`${Zotero.getString("fileInterface.export")} MarkDown Document`,
"save",
[["MarkDown File(*.md)", "*.md"]],
@@ -109,7 +108,7 @@ class NoteExport extends AddonBase {
instance._iframeWindow.postMessage({ type: "exportDocx" }, "*");
await this._docxPromise.promise;
console.log(this._docxBlob);
- const filename = await pick(
+ const filename = await this._Addon.toolkit.Tool.openFilePicker(
`${Zotero.getString("fileInterface.export")} MS Word Document`,
"save",
[["MS Word Document(*.docx)", "*.docx"]],
@@ -160,7 +159,7 @@ class NoteExport extends AddonBase {
}
}
if (options.exportFreeMind) {
- const filename = await pick(
+ const filename = await this._Addon.toolkit.Tool.openFilePicker(
`${Zotero.getString("fileInterface.export")} FreeMind`,
"save",
[["FreeMind(*.mm)", "*.mm"]],
@@ -196,7 +195,7 @@ class NoteExport extends AddonBase {
this._exportFileInfo = [];
let filedir =
options.filedir ||
- (await pick(
+ (await this._Addon.toolkit.Tool.openFilePicker(
Zotero.getString(
options.useSync ? "sync.sync" : "fileInterface.export"
) + " MarkDown",
diff --git a/src/note/noteImportController.ts b/src/note/noteImportController.ts
index c078b70..ebcbd0e 100644
--- a/src/note/noteImportController.ts
+++ b/src/note/noteImportController.ts
@@ -4,7 +4,6 @@
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
-import { pick } from "../utils";
class NoteImport extends AddonBase {
constructor(parent: Knowledge4Zotero) {
@@ -18,7 +17,7 @@ class NoteImport extends AddonBase {
append?: boolean;
} = {}
) {
- const filepath = await pick(
+ const filepath = await this._Addon.toolkit.Tool.openFilePicker(
`${Zotero.getString("fileInterface.import")} MarkDown Document`,
"open",
[["MarkDown File(*.md)", "*.md"]]
diff --git a/src/note/noteParse.ts b/src/note/noteParse.ts
index b547a77..29e14b4 100644
--- a/src/note/noteParse.ts
+++ b/src/note/noteParse.ts
@@ -7,7 +7,6 @@ const asciidoctor = require("asciidoctor")();
import YAML = require("yamljs");
import AddonBase from "../module";
import Knowledge4Zotero from "../addon";
-import { getDOMParser } from "../utils";
import { NodeMode } from "../sync/syncUtils";
class NoteParse extends AddonBase {
@@ -486,7 +485,7 @@ class NoteParse extends AddonBase {
.join("\n")}`;
console.log(this.parseHTMLLines(item.getNote()).slice(0, lineCount));
- let parser = getDOMParser();
+ const parser = this._Addon.toolkit.Compat.getDOMParser();
let doc = parser.parseFromString(note, "text/html");
// Make sure this is the new note
@@ -599,7 +598,7 @@ class NoteParse extends AddonBase {
if (noteText.search(/data-schema-version/g) === -1) {
noteText = `${noteText}\n
`;
}
- let parser = getDOMParser();
+ const parser = this._Addon.toolkit.Compat.getDOMParser();
let doc = parser.parseFromString(noteText, "text/html");
let metadataContainer: HTMLElement = doc.querySelector(
@@ -609,7 +608,7 @@ class NoteParse extends AddonBase {
}
parseLineText(line: string): string {
- const parser = getDOMParser();
+ const parser = this._Addon.toolkit.Compat.getDOMParser();
try {
if (line.search(/data-schema-version/g) === -1) {
line = `${line}
`;
diff --git a/src/note/noteUtils.ts b/src/note/noteUtils.ts
index c76fd0a..1f94d93 100644
--- a/src/note/noteUtils.ts
+++ b/src/note/noteUtils.ts
@@ -73,7 +73,10 @@ class NoteUtils extends AddonBase {
lineIndex
);
const frag = _document.createDocumentFragment();
- const temp = _document.createElement("div");
+ const temp = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
temp.innerHTML = text;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
@@ -365,7 +368,10 @@ class NoteUtils extends AddonBase {
lineIndex
);
const frag = _document.createDocumentFragment();
- const temp = _document.createElement("div");
+ const temp = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
temp.innerHTML = noteLines[lineIndex];
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
diff --git a/src/reader/readerViews.ts b/src/reader/readerViews.ts
index 1d25887..c618f21 100644
--- a/src/reader/readerViews.ts
+++ b/src/reader/readerViews.ts
@@ -31,7 +31,10 @@ class ReaderViews extends AddonBase {
continue;
}
moreButton.setAttribute("knowledgeinit", "true");
- const createNoteButton = _document.createElement("div");
+ const createNoteButton = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
createNoteButton.setAttribute("style", "margin: 5px;");
createNoteButton.title = "Quick Note";
createNoteButton.innerHTML = this.icons["createNote"];
@@ -66,7 +69,10 @@ class ReaderViews extends AddonBase {
moreButton.before(createNoteButton);
if (annotationItem.annotationType === "image") {
// Image OCR
- const ocrButton = _document.createElement("div");
+ const ocrButton = this._Addon.toolkit.UI.createElement(
+ _document,
+ "div"
+ ) as HTMLDivElement;
ocrButton.setAttribute("style", "margin: 5px;");
ocrButton.innerHTML = this.icons["ocrTex"];
ocrButton.title = "OCR LaTex";
diff --git a/src/sync/syncListWindow.ts b/src/sync/syncListWindow.ts
index 178da4c..a1ebc73 100644
--- a/src/sync/syncListWindow.ts
+++ b/src/sync/syncListWindow.ts
@@ -45,16 +45,26 @@ class SyncListWindow extends AddonBase {
}
for (const note of notes) {
const syncInfo = this._Addon.SyncUtils.getSyncStatus(note);
- const listitem = this._window.document.createElement(
- "listitem"
+ const listitem = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listitem",
+ "xul"
) as XUL.ListItem;
listitem.setAttribute("id", note.id);
- const icon = this._window.document.createElement("listcell");
+ const icon = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listcell",
+ "xul"
+ ) as XUL.Element;
icon.setAttribute("class", "listcell-iconic");
icon.setAttribute("image", "chrome://zotero/skin/treeitem-note.png");
- const name = this._window.document.createElement("listcell");
+ const name = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listcell",
+ "xul"
+ ) as XUL.Element;
name.setAttribute("label", `${note.getNoteTitle()}-${note.key}`);
let lastSyncString: string;
@@ -71,10 +81,18 @@ class SyncListWindow extends AddonBase {
} else {
lastSyncString = new Date(lastSyncTime).toLocaleString();
}
- const lastSync = this._window.document.createElement("listcell");
+ const lastSync = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listcell",
+ "xul"
+ ) as XUL.Element;
lastSync.setAttribute("label", lastSyncString);
- const syncPath = this._window.document.createElement("listcell");
+ const syncPath = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listcell",
+ "xul"
+ ) as XUL.Element;
syncPath.setAttribute(
"label",
`${decodeURIComponent(syncInfo.path)}/${decodeURIComponent(
diff --git a/src/template/templateWindow.ts b/src/template/templateWindow.ts
index fce9412..859a1d7 100644
--- a/src/template/templateWindow.ts
+++ b/src/template/templateWindow.ts
@@ -51,9 +51,17 @@ class TemplateWindow extends AddonBase {
e.parentElement.removeChild(e);
}
for (const template of templates) {
- const listitem = this._window.document.createElement("listitem");
+ const listitem = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listitem",
+ "xul"
+ ) as XUL.ListItem;
listitem.setAttribute("id", template.name);
- const name = this._window.document.createElement("listcell");
+ const name = this._Addon.toolkit.UI.createElement(
+ this._window.document,
+ "listcell",
+ "xul"
+ ) as XUL.Element;
name.setAttribute("label", template.name);
if (
this._Addon.TemplateController._systemTemplateNames.includes(
diff --git a/src/utils.ts b/src/utils.ts
index b96b9b1..29ef546 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -27,104 +27,6 @@ class NoteTemplate {
text?: string;
}
-class CopyHelper {
- transferable: any;
- clipboardService: any;
-
- constructor() {
- this.transferable = Components.classes[
- "@mozilla.org/widget/transferable;1"
- ].createInstance(Components.interfaces.nsITransferable);
- this.clipboardService = Components.classes[
- "@mozilla.org/widget/clipboard;1"
- ].getService(Components.interfaces.nsIClipboard);
- this.transferable.init(null);
- }
-
- public addText(source: string, type: "text/html" | "text/unicode") {
- const str = Components.classes[
- "@mozilla.org/supports-string;1"
- ].createInstance(Components.interfaces.nsISupportsString);
- str.data = source;
- this.transferable.addDataFlavor(type);
- this.transferable.setTransferData(type, str, source.length * 2);
- return this;
- }
-
- public addImage(source: string) {
- let parts = source.split(",");
- if (!parts[0].includes("base64")) {
- return;
- }
- let mime = parts[0].match(/:(.*?);/)[1];
- let bstr = atob(parts[1]);
- let n = bstr.length;
- let u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- let imgTools = Components.classes["@mozilla.org/image/tools;1"].getService(
- Components.interfaces.imgITools
- );
- let imgPtr = Components.classes[
- "@mozilla.org/supports-interface-pointer;1"
- ].createInstance(Components.interfaces.nsISupportsInterfacePointer);
- imgPtr.data = imgTools.decodeImageFromArrayBuffer(u8arr.buffer, mime);
- this.transferable.addDataFlavor(mime);
- this.transferable.setTransferData(mime, imgPtr, 0);
- return this;
- }
-
- public copy() {
- this.clipboardService.setData(
- this.transferable,
- null,
- Components.interfaces.nsIClipboard.kGlobalClipboard
- );
- }
-}
-
-async function pick(
- title: string,
- mode: "open" | "save" | "folder",
- filters?: [string, string][],
- suggestion?: string
-): Promise {
- const fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(
- Components.interfaces.nsIFilePicker
- );
-
- if (suggestion) fp.defaultString = suggestion;
-
- mode = {
- open: Components.interfaces.nsIFilePicker.modeOpen,
- save: Components.interfaces.nsIFilePicker.modeSave,
- folder: Components.interfaces.nsIFilePicker.modeGetFolder,
- }[mode];
-
- fp.init(window, title, mode);
-
- for (const [label, ext] of filters || []) {
- fp.appendFilter(label, ext);
- }
-
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
- return new Promise((resolve) => {
- fp.open((userChoice) => {
- switch (userChoice) {
- case Components.interfaces.nsIFilePicker.returnOK:
- case Components.interfaces.nsIFilePicker.returnReplace:
- resolve(fp.file.path);
- break;
-
- default: // aka returnCancel
- resolve("");
- break;
- }
- });
- });
-}
-
enum SyncCode {
UpToDate = 0,
NoteAhead,
@@ -153,8 +55,6 @@ export {
EditorMessage,
OutlineType,
NoteTemplate,
- CopyHelper,
- pick,
SyncCode,
NodeMode,
getDOMParser,
diff --git a/src/workspace/workspaceOutline.ts b/src/workspace/workspaceOutline.ts
index e83eb16..a6e1688 100644
--- a/src/workspace/workspaceOutline.ts
+++ b/src/workspace/workspaceOutline.ts
@@ -48,10 +48,11 @@ class WorkspaceOutline extends AddonBase {
"chrome://Knowledge4Zotero/content/mindMap.html",
"chrome://Knowledge4Zotero/content/bubbleMap.html",
];
- const iframe =
- this._Addon.WorkspaceWindow.workspaceWindow.document.createElement(
- "iframe"
- );
+ const iframe = this._Addon.toolkit.UI.createElement(
+ this._Addon.WorkspaceWindow.workspaceWindow.document,
+ "iframe",
+ "xul"
+ ) as XUL.Element;
iframe.setAttribute("id", "mindmapIframe");
iframe.setAttribute("src", srcList[this.currentOutline]);
mindmap.append(iframe);
diff --git a/src/workspace/workspaceWindow.ts b/src/workspace/workspaceWindow.ts
index 965a2bf..4311acf 100644
--- a/src/workspace/workspaceWindow.ts
+++ b/src/workspace/workspaceWindow.ts
@@ -3,7 +3,7 @@
*/
import Knowledge4Zotero from "../addon";
-import { EditorMessage, OutlineType, pick } from "../utils";
+import { EditorMessage, OutlineType } from "../utils";
import AddonBase from "../module";
class WorkspaceWindow extends AddonBase {
@@ -83,7 +83,11 @@ class WorkspaceWindow extends AddonBase {
onClose: () => (this.workspaceTabId = ""),
});
this.workspaceTabId = id;
- const _iframe = window.document.createElement("browser");
+ const _iframe = this._Addon.toolkit.UI.createElement(
+ document,
+ "browser",
+ "xul"
+ ) as XUL.Element;
_iframe.setAttribute("class", "reader");
_iframe.setAttribute("flex", "1");
_iframe.setAttribute("type", "content");
@@ -198,7 +202,7 @@ class WorkspaceWindow extends AddonBase {
);
} else if (e.data.type === "saveSVGReturn") {
console.log(e.data.image);
- const filename = await pick(
+ const filename = await this._Addon.toolkit.Tool.openFilePicker(
`${Zotero.getString("fileInterface.export")} SVG Image`,
"save",
[["SVG File(*.svg)", "*.svg"]],
diff --git a/src/zotero/events.ts b/src/zotero/events.ts
index f89cd59..224dbb0 100644
--- a/src/zotero/events.ts
+++ b/src/zotero/events.ts
@@ -4,7 +4,7 @@
import TreeModel = require("tree-model");
import Knowledge4Zotero from "../addon";
-import { CopyHelper, EditorMessage } from "../utils";
+import { EditorMessage } from "../utils";
import AddonBase from "../module";
class ZoteroEvents extends AddonBase {
@@ -377,7 +377,7 @@ class ZoteroEvents extends AddonBase {
}
},
copyLink: async () => {
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(link, "text/unicode")
.addText(
(e.target as HTMLLinkElement).outerHTML,
@@ -767,10 +767,10 @@ class ZoteroEvents extends AddonBase {
if (currentLine >= 0) {
// Compute annotation lines length
- const temp = document.createElementNS(
- "http://www.w3.org/1999/xhtml",
+ const temp = this._Addon.toolkit.UI.createElement(
+ document,
"div"
- );
+ ) as HTMLDivElement;
temp.innerHTML = html;
const elementList = this._Addon.NoteParse.parseHTMLElements(temp);
// Move cursor foward
@@ -930,7 +930,7 @@ class ZoteroEvents extends AddonBase {
const html = newLines.join("\n");
if (!targetItem) {
console.log(html);
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(html, "text/html")
.addText(
await this._Addon.NoteParse.parseHTMLToMD(html),
@@ -1049,7 +1049,7 @@ class ZoteroEvents extends AddonBase {
if (!targetItem) {
console.log(html);
- new CopyHelper()
+ this._Addon.toolkit.Tool.getCopyHelper()
.addText(html, "text/html")
.addText(
await this._Addon.NoteParse.parseHTMLToMD(html),
@@ -1283,7 +1283,9 @@ class ZoteroEvents extends AddonBase {
}
const html = await this._Addon.NoteParse.parseMDToHTML(source);
console.log(source, html);
- new CopyHelper().addText(html, "text/html").copy();
+ this._Addon.toolkit.Tool.getCopyHelper()
+ .addText(html, "text/html")
+ .copy();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
@@ -1303,7 +1305,9 @@ class ZoteroEvents extends AddonBase {
}
const html = this._Addon.NoteParse.parseAsciiDocToHTML(source);
console.log(source, html);
- new CopyHelper().addText(html, "text/html").copy();
+ this._Addon.toolkit.Tool.getCopyHelper()
+ .addText(html, "text/html")
+ .copy();
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
diff --git a/src/zotero/views.ts b/src/zotero/views.ts
index b3bb876..5b206e0 100644
--- a/src/zotero/views.ts
+++ b/src/zotero/views.ts
@@ -68,51 +68,45 @@ class ZoteroViews extends AddonBase {
let addNoteItem = document
.getElementById("zotero-tb-note-add")
.getElementsByTagName("menuitem")[1];
- let buttons = this.createXULElement(document, {
+ let buttons = this._Addon.toolkit.UI.creatElementsFromJSON(document, {
tag: "fragment",
subElementOptions: [
{
tag: "menuitem",
id: "zotero-tb-knowledge-create-mainnote",
- attributes: [
- ["label", "New Main Note"],
- ["class", "menuitem-iconic"],
- [
- "style",
+ attributes: {
+ label: "New Main Note",
+ class: "menuitem-iconic",
+ style:
"list-style-image: url('chrome://Knowledge4Zotero/skin/favicon.png');",
- ],
- ],
+ },
listeners: [
- [
- "click",
- (e) => {
+ {
+ type: "click",
+ listener: (e) => {
this._Addon.ZoteroEvents.onEditorEvent(
new EditorMessage("createWorkspace", {})
);
},
- false,
- ],
+ },
],
},
{
tag: "menuitem",
id: "zotero-tb-knowledge-import-md",
- attributes: [
- ["label", "Import MarkDown as Note"],
- ["class", "menuitem-iconic"],
- [
- "style",
+ attributes: {
+ label: "Import MarkDown as Note",
+ class: "menuitem-iconic",
+ style:
"list-style-image: url('chrome://Knowledge4Zotero/skin/favicon.png');",
- ],
- ],
+ },
listeners: [
- [
- "click",
- async (e) => {
+ {
+ type: "click",
+ listener: async (e) => {
await this._Addon.NoteImport.doImport();
},
- false,
- ],
+ },
],
},
],
@@ -122,62 +116,95 @@ class ZoteroViews extends AddonBase {
public addOpenWorkspaceButton() {
// Left collection tree view button
- const treeRow = document.createElement("html:div");
- treeRow.setAttribute("class", "row");
- treeRow.setAttribute(
- "style",
- "height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
- );
- const span1 = document.createElement("span");
- span1.setAttribute("class", "cell label primary");
- const span2 = document.createElement("span");
- span2.setAttribute("class", "icon icon-twisty twisty open");
- span2.innerHTML = this.icons["openWorkspaceCollectionView"];
- const span3 = document.createElement("span");
- span3.setAttribute("class", "icon icon-bg cell-icon");
- span3.setAttribute(
- "style",
- "background-image:url(chrome://Knowledge4Zotero/skin/favicon.png)"
- );
- const span4 = document.createElement("span");
- span4.setAttribute("class", "cell-text");
- span4.setAttribute("style", "margin-left: 6px;");
- span4.innerHTML = Zotero.locale.includes("zh")
- ? "打开工作区"
- : "Open Workspace";
- span1.append(span2, span3, span4);
- treeRow.append(span1);
- treeRow.addEventListener("click", async (e) => {
- if (e.shiftKey) {
- await this._Addon.WorkspaceWindow.openWorkspaceWindow("window", true);
- } else {
- await this._Addon.WorkspaceWindow.openWorkspaceWindow();
- }
- });
- treeRow.addEventListener("mouseover", (e: XUL.XULEvent) => {
- treeRow.setAttribute(
- "style",
- "height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; background-color: grey;"
- );
- });
- treeRow.addEventListener("mouseleave", (e: XUL.XULEvent) => {
- treeRow.setAttribute(
- "style",
- "height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
- );
- });
- treeRow.addEventListener("mousedown", (e: XUL.XULEvent) => {
- treeRow.setAttribute(
- "style",
- "height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; color: #FFFFFF;"
- );
- });
- treeRow.addEventListener("mouseup", (e: XUL.XULEvent) => {
- treeRow.setAttribute(
- "style",
- "height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
- );
- });
+ const treeRow = this._Addon.toolkit.UI.creatElementsFromJSON(document, {
+ tag: "html:div",
+ attributes: {
+ class: "row",
+ style: "max-height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;",
+ },
+ listeners: [
+ {
+ type: "click",
+ listener: async (e: MouseEvent) => {
+ if (e.shiftKey) {
+ await this._Addon.WorkspaceWindow.openWorkspaceWindow(
+ "window",
+ true
+ );
+ } else {
+ await this._Addon.WorkspaceWindow.openWorkspaceWindow();
+ }
+ },
+ },
+ {
+ type: "mouseover",
+ listener: (e) => {
+ treeRow.setAttribute(
+ "style",
+ "max-height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; background-color: grey;"
+ );
+ },
+ },
+ {
+ type: "mouseleave",
+ listener: (e) => {
+ treeRow.setAttribute(
+ "style",
+ "max-height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
+ );
+ },
+ },
+ {
+ type: "mousedown",
+ listener: (e) => {
+ treeRow.setAttribute(
+ "style",
+ "max-height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px; color: #FFFFFF;"
+ );
+ },
+ },
+ {
+ type: "mouseup",
+ listener: (e) => {
+ treeRow.setAttribute(
+ "style",
+ "max-height: 22px; margin: 0 0 0 0; padding: 0 6px 0 6px;"
+ );
+ },
+ },
+ ],
+ subElementOptions: [
+ {
+ tag: "div",
+ attributes: {
+ class: "icon icon-twisty twisty open",
+ },
+ directAttributes: {
+ innerHTML: this.icons["openWorkspaceCollectionView"],
+ },
+ },
+ {
+ tag: "div",
+ attributes: {
+ class: "icon icon-bg cell-icon",
+ style:
+ "background-image:url(chrome://Knowledge4Zotero/skin/favicon.png)",
+ },
+ },
+ {
+ tag: "div",
+ attributes: {
+ class: "cell-text",
+ style: "margin-left: 6px;",
+ },
+ directAttributes: {
+ innerHTML: Zotero.locale.includes("zh")
+ ? "打开工作区"
+ : "Open Workspace",
+ },
+ },
+ ],
+ }) as HTMLDivElement;
document
.getElementById("zotero-collections-tree-container")
.children[0].before(treeRow);
@@ -223,7 +250,11 @@ class ZoteroViews extends AddonBase {
];
}
for (const template of templates) {
- const menuitem = _document.createElement("menuitem");
+ const menuitem = this._Addon.toolkit.UI.createElement(
+ _document,
+ "menuitem",
+ "xul"
+ ) as XUL.MenuItem;
// menuitem.setAttribute("id", template.name);
menuitem.setAttribute("label", template.name);
menuitem.setAttribute(
@@ -244,6 +275,7 @@ class ZoteroViews extends AddonBase {
}
}
+ // To deprecate
public updateCitationStyleMenu() {
const _window = this._Addon.WorkspaceMenu.getWorkspaceMenuWindow();
Zotero.debug(`updateCitationStyleMenu`);
@@ -255,14 +287,18 @@ class ZoteroViews extends AddonBase {
// add styles to list
const styles = Zotero.Styles.getVisible();
- styles.forEach(function (style) {
+ styles.forEach((style) => {
const val = JSON.stringify({
mode: "bibliography",
contentType: "html",
id: style.styleID,
locale: "",
});
- const itemNode = document.createElement("menuitem") as XUL.MenuItem;
+ const itemNode = this._Addon.toolkit.UI.createElement(
+ _window.document,
+ "menuitem",
+ "xul"
+ ) as XUL.MenuItem;
itemNode.setAttribute("value", val);
itemNode.setAttribute("label", style.title);
itemNode.setAttribute("type", "checkbox");
@@ -413,68 +449,6 @@ class ZoteroViews extends AddonBase {
await this.waitProgressWindow(progressWindow);
return progressWindow.progress._hbox.ownerDocument;
}
-
- public createXULElement(doc: Document, options: XULElementOptions) {
- const createElement: () => XUL.Element =
- options.tag === "fragment"
- ? () => doc.createDocumentFragment()
- : Zotero.platformMajorVersion <= 60
- ? () =>
- doc.createElementNS(
- "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
- options.tag
- )
- : // @ts-ignore
- () => doc.createXULElement(options.tag);
- if (
- options.id &&
- (options.checkExistanceParent
- ? options.checkExistanceParent
- : doc
- ).querySelector(`#${options.id}`)
- ) {
- if (options.ignoreIfExists) {
- return undefined;
- }
- if (options.removeIfExists) {
- doc.querySelector(`#${options.id}`).remove();
- }
- }
- if (options.customCheck && !options.customCheck()) {
- return undefined;
- }
- const element = createElement();
- if (options.id) {
- element.id = options.id;
- }
- if (options.styles?.length) {
- options.styles.forEach(([k, v]) => {
- element.style[k] = v;
- });
- }
- if (options.directAttributes?.length) {
- options.directAttributes.forEach(([k, v]) => {
- element[k] = v;
- });
- }
- if (options.attributes?.length) {
- options.attributes.forEach(([k, v]) => {
- element.setAttribute(k, v);
- });
- }
- if (options.listeners?.length) {
- options.listeners.forEach(([type, cbk, option]) => {
- element.addEventListener(type, cbk, option);
- });
- }
- if (options.subElementOptions?.length) {
- const subElements = options.subElementOptions
- .map((options) => this.createXULElement(doc, options))
- .filter((e) => e);
- element.append(...subElements);
- }
- return element;
- }
}
export default ZoteroViews;
diff --git a/typing/global.d.ts b/typing/global.d.ts
index 3943e85..6ec5284 100644
--- a/typing/global.d.ts
+++ b/typing/global.d.ts
@@ -1,23 +1,3 @@
-declare interface XULElementOptions {
- tag: string;
- id?: string;
- styles?: Array<[string, string]>;
- directAttributes?: Array<[string, string | boolean | number]>;
- attributes?: Array<[string, string | boolean | number]>;
- listeners?: Array<
- [
- string,
- EventListenerOrEventListenerObject,
- boolean | AddEventListenerOptions
- ]
- >;
- checkExistanceParent?: HTMLElement;
- ignoreIfExists?: boolean;
- removeIfExists?: boolean;
- customCheck?: () => boolean;
- subElementOptions?: Array;
-}
-
declare interface SyncStatus {
path: string;
filename: string;