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 = `
Preview
`; 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;