change: using plugin toolkit for migration zo 6->7

This commit is contained in:
xiangyu 2022-12-28 22:41:58 +08:00
parent c2f63fcd11
commit b7b3c66749
18 changed files with 352 additions and 396 deletions

View File

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

View File

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

View File

@ -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);

View File

@ -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
}</a></p>`;
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
}</a></p>`;
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<void>((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 = `<div class="icon"><svg t="1668689809930" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="8872" width="16" height="16"><path d="M978.346667 149.333333c0 11.776-9.557333 21.333333-21.333333 21.333333L167.68 170.666667l0 448c0 35.285333 28.714667 64 64 64l554.666667 0c35.285333 0 64-28.714667 64-42.666667l0-154.496L850.346667 356.309333 850.346667 277.930667c0-11.776 9.557333-21.333333 21.333333-21.333333s21.333333 9.557333 21.333333 21.333333l0 78.378667 0 129.194667L893.013333 640c0 37.504-47.829333 85.333333-106.666667 85.333333l-205.568 0 169.557333 169.514667c8.32 8.32 8.32 21.845333 0 30.165333-8.362667 8.362667-21.845333 8.362667-30.208 0L520.405333 725.333333l-16.810667 0-199.722667 199.68c-8.32 8.362667-21.845333 8.362667-30.165333 0-8.32-8.32-8.32-21.845333 0-30.165333L443.306667 725.333333 231.68 725.333333c-58.837333 0-106.666667-47.829333-106.666667-106.666667L125.013333 170.666667l-64 0c-11.776 0-21.333333-9.557333-21.333333-21.333333S49.194667 128 61.013333 128l405.333333 0L466.346667 85.333333l85.333333 0 0 42.666667 405.333333 0C968.789333 128 978.346667 137.557333 978.346667 149.333333zM618.666667 405.333333l-194.986667-128 0 256L618.666667 405.333333z" p-id="8873"></path></svg>Preview</div>`;
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 = `<p><a href="${linkText}" rel="noopener noreferrer nofollow">${
noteItem.getNoteTitle().trim()
@ -967,7 +1028,7 @@ class EditorViews extends AddonBase {
: linkText
}</a></p>`;
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
}</a></p>`;
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
);

View File

@ -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}`]],

View File

@ -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",

View File

@ -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"]]

View File

@ -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")}</div>`;
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 = `<div data-schema-version="8">${noteText}\n</div>`;
}
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 = `<div data-schema-version="8">${line}</div>`;

View File

@ -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);

View File

@ -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";

View File

@ -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(

View File

@ -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(

View File

@ -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<string> {
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,

View File

@ -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);

View File

@ -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"]],

View File

@ -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",

View File

@ -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;

20
typing/global.d.ts vendored
View File

@ -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<XULElementOptions>;
}
declare interface SyncStatus {
path: string;
filename: string;