add: export note to docx

This commit is contained in:
xiangyu 2022-11-13 10:53:51 +08:00
parent ec3eaa76ce
commit bcd4af9c05
12 changed files with 241 additions and 257 deletions

View File

@ -53,17 +53,12 @@
</rows>
</groupbox>
<groupbox flex="1">
<caption label="&zotero.__addonRef__.export.richtext.label;"></caption>
<caption label="&zotero.__addonRef__.export.document.label;"></caption>
<rows flex="1">
<row>
<checkbox id="__addonRef__-export-enablecopy" checked="false" />
<label value="&zotero.__addonRef__.export.copy.enable.label;" />
<checkbox id="__addonRef__-export-enabledocx" checked="false" />
<label value="&zotero.__addonRef__.export.docx.enable.label;" />
</row>
</rows>
</groupbox>
<groupbox flex="1">
<caption label="&zotero.__addonRef__.export.pdf.label;"></caption>
<rows flex="1">
<row>
<checkbox id="__addonRef__-export-enablepdf" checked="false" />
<label value="&zotero.__addonRef__.export.pdf.enable.label;" />

View File

@ -40,16 +40,15 @@
<!ENTITY zotero.__addonRef__.export.option.label "General">
<!ENTITY zotero.__addonRef__.export.link.enable.label "Embed Linked Notes">
<!ENTITY zotero.__addonRef__.export.note.enable.label "Export to new note">
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown Settings">
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown">
<!ENTITY zotero.__addonRef__.export.file.enable.label "Export to MarkDown File">
<!ENTITY zotero.__addonRef__.export.singlefile.enable.label "Export Linked Notes to MarkDown File">
<!ENTITY zotero.__addonRef__.export.enableautosync.enable.label "Auto Sync to Export Path">
<!ENTITY zotero.__addonRef__.export.highlight.enable.label "Show Highlight">
<!ENTITY zotero.__addonRef__.export.convertsquare.enable.label "Convert Square Brackets[]">
<!ENTITY zotero.__addonRef__.export.richtext.label "RichText(MS Word) Settings">
<!ENTITY zotero.__addonRef__.export.copy.enable.label "Export to clipboard">
<!ENTITY zotero.__addonRef__.export.pdf.label "PDF Settings">
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "Export to PDF">
<!ENTITY zotero.__addonRef__.export.document.label "Document Settings">
<!ENTITY zotero.__addonRef__.export.docx.enable.label "Export to MS Word Document">
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "Export to PDF Document">
<!ENTITY zotero.__addonRef__.sync.title "Sync Status">
<!ENTITY zotero.__addonRef__.sync.export.label "Export to...">

View File

@ -40,16 +40,15 @@
<!ENTITY zotero.__addonRef__.export.option.label "通用">
<!ENTITY zotero.__addonRef__.export.link.enable.label "嵌入链接的子笔记">
<!ENTITY zotero.__addonRef__.export.note.enable.label "导出到新笔记">
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown设置">
<!ENTITY zotero.__addonRef__.export.markdown.label "MarkDown">
<!ENTITY zotero.__addonRef__.export.file.enable.label "导出为MarkDown文件">
<!ENTITY zotero.__addonRef__.export.singlefile.enable.label "导出链接的子笔记为MarkDown文件">
<!ENTITY zotero.__addonRef__.export.enableautosync.enable.label "修改时自动同步到导出路径">
<!ENTITY zotero.__addonRef__.export.highlight.enable.label "显示高亮">
<!ENTITY zotero.__addonRef__.export.convertsquare.enable.label "转换方括号[]">
<!ENTITY zotero.__addonRef__.export.richtext.label "富文本(MS Word)设置">
<!ENTITY zotero.__addonRef__.export.copy.enable.label "导出到剪贴板">
<!ENTITY zotero.__addonRef__.export.pdf.label "PDF设置">
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "导出到PDF">
<!ENTITY zotero.__addonRef__.export.document.label "文档">
<!ENTITY zotero.__addonRef__.export.docx.enable.label "导出到Word文档">
<!ENTITY zotero.__addonRef__.export.pdf.enable.label "导出到PDF文档">
<!ENTITY zotero.__addonRef__.sync.title "同步状态">
<!ENTITY zotero.__addonRef__.sync.export.label "导出为...">

View File

@ -9,7 +9,7 @@ pref("extensions.zotero.Knowledge4Zotero.exportHighlight", true);
pref("extensions.zotero.Knowledge4Zotero.convertSquare", true);
pref("extensions.zotero.Knowledge4Zotero.embedLink", true);
pref("extensions.zotero.Knowledge4Zotero.exportNote", false);
pref("extensions.zotero.Knowledge4Zotero.exportCopy", false);
pref("extensions.zotero.Knowledge4Zotero.exportDocx", false);
pref("extensions.zotero.Knowledge4Zotero.exportPDF", false);
pref("extensions.zotero.Knowledge4Zotero.OCREngine", "bing");
pref("extensions.zotero.Knowledge4Zotero.OCRMathpix.Appid", "");

View File

@ -32,6 +32,7 @@
"compressing": "^1.5.1",
"crypto-js": "^4.1.1",
"esbuild": "^0.14.34",
"html-docx-js-typescript": "^0.1.5",
"prosemirror-transform": "^1.7.0",
"replace-in-file": "^6.3.2",
"seedrandom": "^3.0.5",

View File

@ -33,6 +33,20 @@ class EditorController extends AddonBase {
messageScript.innerHTML = `__placeholder:editorScript.js__`;
_window.document.head.append(messageScript);
}
_window.addEventListener("BNMessage", (e: CustomEvent) => {
console.log("BN: note editor event", e.detail);
switch (e.detail.type) {
case "exportPDFDone":
this._Addon.NoteExport._pdfPrintPromise.resolve();
break;
case "exportDocxDone":
this._Addon.NoteExport._docxBlob = e.detail.docxBlob;
this._Addon.NoteExport._docxPromise.resolve();
break;
default:
break;
}
});
}
recordEditor(instance: Zotero.EditorInstance) {

View File

@ -1,6 +1,7 @@
// TODO: Move this somewhere else
// DO NOT USE BACKTICK IN THIS FILE
const { Fragment, Slice } = require("prosemirror-model");
const { Step, StepResult } = require("prosemirror-transform");
import { asBlob } from "html-docx-js-typescript";
class SetAttrsStep extends Step {
// :: (number, Object | null)
@ -69,67 +70,156 @@ window.updateImageDimensions = function (
window.addEventListener(
"message",
async (e) => {
if (e.data.type === "exportPDF") {
console.log("exportPDF");
const container = document.getElementById(
"editor-container"
) as HTMLElement;
container.style.display = "none";
const fullPageStyle = document.createElement("style");
fullPageStyle.innerHTML =
"@page { margin: 0; } @media print{ body { height : auto; -webkit-print-color-adjust: exact; color-adjust: exact; }}";
document.body.append(fullPageStyle);
let t = 0;
let imageFlag = false;
while (!imageFlag && t < 500) {
await new Promise(function (resolve) {
setTimeout(resolve, 10);
});
imageFlag = !Array.prototype.find.call(
document.querySelectorAll("img"),
(e) => !e.getAttribute("src") || e.style.display === "none"
);
t += 1;
}
const editNode = document.querySelector(".primary-editor") as HTMLElement;
const printNode = editNode.cloneNode(true) as HTMLElement;
printNode.style.padding = "20px";
document.body.append(printNode);
let printFlag = false;
window.onafterprint = (e) => {
console.log("Print Dialog Closed..");
printFlag = true;
document.title = "Printed";
};
window.onmouseover = (e) => {
if (printFlag) {
document.title = "Printed";
printNode.remove();
container.style.removeProperty("display");
}
};
document.title = (printNode?.firstChild as HTMLElement).innerText;
console.log(document.title);
window.print();
} else if (e.data.type === "resizeImage") {
console.log("resizeImage");
// @ts-ignore
window.updateImageDimensions(
// @ts-ignore
_currentEditorInstance._editorCore.view.state.selection.node.attrs
.nodeID,
e.data.width,
undefined,
// @ts-ignore
_currentEditorInstance._editorCore.view.state,
// @ts-ignore
_currentEditorInstance._editorCore.view.dispatch
console.log(e);
const editNode = document.querySelector(".primary-editor") as HTMLElement;
let t = 0;
let imageFlag = false;
while (!imageFlag && t < 500) {
await new Promise(function (resolve) {
setTimeout(resolve, 10);
});
imageFlag = !Array.prototype.find.call(
document.querySelectorAll("img"),
(e) => !e.getAttribute("src") || e.style.display === "none"
);
t += 1;
}
switch (e.data.type) {
case "exportPDF":
console.log("exportPDF");
const container = document.getElementById(
"editor-container"
) as HTMLElement;
container.style.display = "none";
const fullPageStyle = document.createElement("style");
fullPageStyle.innerHTML =
"@page { margin: 0; } @media print{ body { height : auto; -webkit-print-color-adjust: exact; color-adjust: exact; }}";
document.body.append(fullPageStyle);
const printNode = editNode.cloneNode(true) as HTMLElement;
printNode.style.padding = "20px";
document.body.append(printNode);
let printFlag = false;
window.onafterprint = (_e) => {
console.log("Print Dialog Closed..");
printFlag = true;
// document.title = "Printed";
window.dispatchEvent(
new CustomEvent("BNMessage", {
detail: {
type: "exportPDFDone",
},
})
);
};
window.onmouseover = (e) => {
if (printFlag) {
document.title = "Printed";
printNode.remove();
container.style.removeProperty("display");
}
};
document.title = (printNode?.firstChild as HTMLElement).innerText;
console.log(document.title);
window.print();
break;
case "exportDocx":
// @ts-ignore
const docxBlob: Blob = await window.getDocx(editNode);
console.log(docxBlob);
window.dispatchEvent(
new CustomEvent("BNMessage", {
detail: {
type: "exportDocxDone",
docxBlob: docxBlob,
},
})
);
break;
case "resizeImage":
console.log("resizeImage");
// @ts-ignore
window.updateImageDimensions(
// @ts-ignore
_currentEditorInstance._editorCore.view.state.selection.node.attrs
.nodeID,
e.data.width,
undefined,
// @ts-ignore
_currentEditorInstance._editorCore.view.state,
// @ts-ignore
_currentEditorInstance._editorCore.view.dispatch
);
break;
default:
break;
}
},
false
);
// @ts-ignore
window.getDocx = async (
dom: HTMLElement,
config: object,
{ title = document.title, width = undefined } = {}
) => {
if (!dom) return;
config = config || {};
let copyDom = document.createElement("span");
// const styleDom = document.querySelectorAll('style, link, meta')
const titleDom = document.createElement("title");
titleDom.innerText = title;
copyDom.appendChild(titleDom);
// Array.from(styleDom).forEach(item => {
// copyDom.appendChild(item.cloneNode(true))
// })
const cloneDom = dom.cloneNode(true) as HTMLElement;
if (width) {
const domTables = cloneDom.getElementsByTagName("table");
if (domTables.length) {
for (const table of domTables) {
table.style.width = width + "px";
}
}
}
copyDom.appendChild(cloneDom);
const htmlTemp = copyDom.innerHTML;
copyDom = null;
const iframeDom = document.createElement("iframe");
const attrObj = {
height: 0,
width: 0,
border: 0,
wmode: "Opaque",
};
const styleObj = {
position: "absolute",
top: "-999px",
left: "-999px",
};
Object.entries(attrObj).forEach(([key, value]) => {
iframeDom.setAttribute(key, String(value));
});
Object.entries(styleObj).forEach(([key, value]) => {
iframeDom.style[key] = value;
});
document.body.insertBefore(iframeDom, document.body.children[0]);
const iframeWin = iframeDom.contentWindow;
const iframeDocs = iframeWin.document;
iframeDocs.write("<!doctype html>");
iframeDocs.write(htmlTemp);
let htmlDoc =
'<!DOCTYPE html>\n<html lang="en"><head><meta charset="UTF-8"></head>\n';
htmlDoc += iframeDocs.documentElement.innerHTML;
htmlDoc += "\n</html>";
var converted = await asBlob(htmlDoc, config);
console.log(converted);
document.body.removeChild(iframeDom);
return converted;
};

View File

@ -33,7 +33,6 @@ class EditorViews extends AddonBase {
const isMainNote = noteItem.id === mainNote.id;
const isPreviewNote =
noteItem.id === this._Addon.WorkspaceWindow.previewItemID;
const isPrint = this._Addon.NoteExport._pdfNoteId === noteItem.id;
const _window = instance._iframeWindow;
@ -284,9 +283,9 @@ class EditorViews extends AddonBase {
);
// Title style only for normal window
if (!isPrint) {
const style = _window.document.createElement("style");
style.innerHTML = `
const style = _window.document.createElement("style");
style.id = "bn-headings";
style.innerHTML = `
.primary-editor h1::before {
margin-left: -64px !important;
padding-left: 40px !important;
@ -321,8 +320,7 @@ class EditorViews extends AddonBase {
max-width: unset
}
`;
_window.document.body.append(style);
}
_window.document.body.append(style);
this._Addon.EditorController.injectScripts(_window);

View File

@ -1,113 +0,0 @@
/*
* This file realizes note tools.
*/
import Knowledge4Zotero from "../addon";
import AddonBase from "../module";
class NoteController extends AddonBase {
public currentLine: any;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.currentLine = [];
}
public async onSelectionChange(editor: Zotero.EditorInstance) {
// Update current line index
const _window = editor._iframeWindow;
const selection = _window.document.getSelection();
if (!selection || !selection.focusNode) {
return;
}
const realElement = selection.focusNode.parentElement;
let focusNode = selection.focusNode as XUL.Element;
if (!focusNode || !realElement) {
return;
}
function getChildIndex(node) {
return Array.prototype.indexOf.call(node.parentNode.childNodes, node);
}
// Make sure this is a direct child node of editor
try {
while (
focusNode.parentElement &&
(!focusNode.parentElement.className ||
focusNode.parentElement.className.indexOf("primary-editor") === -1)
) {
focusNode = focusNode.parentNode as XUL.Element;
}
} catch (e) {
return;
}
if (!focusNode.parentElement) {
return;
}
let currentLineIndex = getChildIndex(focusNode);
// Parse list
const diveTagNames = ["OL", "UL", "LI"];
// Find list elements before current line
const listElements = Array.prototype.filter.call(
Array.prototype.slice.call(
focusNode.parentElement.childNodes,
0,
currentLineIndex
),
(e) => diveTagNames.includes(e.tagName)
);
for (const e of listElements) {
currentLineIndex += this._Addon.NoteParse.parseListElements(e).length - 1;
}
// Find list index if current line is inside a list
if (diveTagNames.includes(focusNode.tagName)) {
const eleList = this._Addon.NoteParse.parseListElements(focusNode);
for (const i in eleList) {
if (realElement.parentElement === eleList[i]) {
currentLineIndex += Number(i);
break;
}
}
}
Zotero.debug(`Knowledge4Zotero: line ${currentLineIndex} selected.`);
console.log(currentLineIndex);
Zotero.debug(
`Current Element: ${focusNode.outerHTML}; Real Element: ${realElement.outerHTML}`
);
this.currentLine[editor._item.id] = currentLineIndex;
if (realElement.tagName === "A") {
let link = (realElement as HTMLLinkElement).href;
let linkedNote = (await this._Addon.NoteUtils.getNoteFromLink(link)).item;
if (linkedNote) {
let t = 0;
let linkPopup = _window.document.querySelector(".link-popup");
while (
!(
linkPopup &&
(linkPopup.querySelector("a") as unknown as HTMLLinkElement)
.href === link
) &&
t < 100
) {
t += 1;
linkPopup = _window.document.querySelector(".link-popup");
await Zotero.Promise.delay(30);
}
await this._Addon.EditorViews.updateEditorPopupButtons(editor, link);
} else {
await this._Addon.EditorViews.updateEditorPopupButtons(
editor,
undefined
);
}
}
}
}
export default NoteController;

View File

@ -14,12 +14,12 @@ class NoteExport extends AddonBase {
note: Zotero.Item;
filename: string;
}>;
_pdfNoteId: number;
_pdfPrintPromise: ZoteroPromise;
_docxPromise: ZoteroPromise;
_docxBlob: Blob;
constructor(parent: Knowledge4Zotero) {
super(parent);
this._pdfNoteId = -1;
this._exportFileInfo = [];
}
@ -28,10 +28,10 @@ class NoteExport extends AddonBase {
convertNoteLinks: boolean = true,
saveMD: boolean = true,
saveNote: boolean = false,
doCopy: boolean = false,
saveDocx: boolean = false,
savePDF: boolean = false
) {
if (!saveMD && !saveNote && !doCopy && !savePDF) {
if (!saveMD && !saveNote && !saveDocx && !savePDF) {
return;
}
this._exportFileInfo = [];
@ -57,6 +57,8 @@ class NoteExport extends AddonBase {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
@ -73,35 +75,30 @@ class NoteExport extends AddonBase {
Zotero.File.pathToFile(filename).parent.path + "/attachments";
// Convert to unix format
this._exportPath = this._exportPath.replace(/\\/g, "/");
await this._export(newNote, filename, false);
await this._exportMD(newNote, filename, false);
}
}
if (doCopy) {
if (!convertNoteLinks) {
Zotero_File_Interface.exportItemsToClipboard(
[newNote],
Zotero.Translators.TRANSLATOR_ID_MARKDOWN_AND_RICH_TEXT
);
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
"Note Copied"
);
} else {
alert(
"Select all in the new note window and copy-paste to other applications."
);
ZoteroPane.openNoteWindow(newNote.id);
alert(
"Waiting for paste finish...\nImages may not be copied correctly if OK is pressed before paste."
);
if (saveDocx) {
const instance: Zotero.EditorInstance =
this._Addon.WorkspaceWindow.getEditorInstance(newNote);
this._docxPromise = Zotero.Promise.defer();
instance._iframeWindow.postMessage({ type: "exportDocx" }, "*");
await this._docxPromise.promise;
console.log(this._docxBlob);
const filename = await pick(
Zotero.getString("fileInterface.export"),
"save",
[["MS Word Document(*.docx)", "*.docx"]],
`${newNote.getNoteTitle()}.docx`
);
if (filename) {
await this._exportDocx(filename);
}
}
if (savePDF) {
console.log(newNote);
let _w: Window;
let t = 0;
this._pdfNoteId = newNote.id;
this._pdfPrintPromise = Zotero.Promise.defer();
ZoteroPane.selectItem(note.id);
do {
ZoteroPane.openNoteWindow(newNote.id);
@ -112,19 +109,23 @@ class NoteExport extends AddonBase {
} while (!_w && t < 500);
ZoteroPane.selectItem(note.id);
_w.resizeTo(900, 650);
const checkPrint = () => {
try {
const editor: any = _w.document.querySelector("#zotero-note-editor");
const instance: Zotero.EditorInstance = editor.getCurrentInstance();
console.log(instance._iframeWindow.document.title);
if (instance._iframeWindow.document.title === "Printed") {
this._pdfPrintPromise.resolve();
return;
}
} catch (e) {}
setTimeout(checkPrint, 300);
};
checkPrint();
const editor: any = _w.document.querySelector("#zotero-note-editor");
t = 0;
while (
!(
editor.getCurrentInstance &&
editor.getCurrentInstance() &&
editor.getCurrentInstance()._knowledgeSelectionInitialized
) &&
t < 500
) {
t += 1;
await Zotero.Promise.delay(10);
}
const instance: Zotero.EditorInstance = editor.getCurrentInstance();
instance._iframeWindow.document.querySelector("#bn-headings")?.remove();
this._pdfPrintPromise = Zotero.Promise.defer();
instance._iframeWindow.postMessage({ type: "exportPDF" }, "*");
await this._pdfPrintPromise.promise;
console.log("print finish detected");
const closeFlag = _w.confirm(
@ -205,7 +206,7 @@ class NoteExport extends AddonBase {
}/${await this._getFileName(note)}`;
filename = filename.replace(/\\/g, "/");
await this._export(newNote, filename, newNote.id !== note.id);
await this._exportMD(newNote, filename, newNote.id !== note.id);
}
} else {
// Export every linked note as a markdown file
@ -248,7 +249,7 @@ class NoteExport extends AddonBase {
let exportPath = `${Zotero.File.pathToFile(filepath).path}/${
noteInfo.filename
}`;
await this._export(noteInfo.note, exportPath, false);
await this._exportMD(noteInfo.note, exportPath, false);
if (useSync) {
this._Addon.SyncController.updateNoteSyncStatus(
noteInfo.note,
@ -305,12 +306,20 @@ class NoteExport extends AddonBase {
let exportPath = `${decodeURIComponent(
syncInfo.path
)}/${decodeURIComponent(syncInfo.filename)}`;
await this._export(note, exportPath, false);
await this._exportMD(note, exportPath, false);
this._Addon.SyncController.updateNoteSyncStatus(note);
}
}
private async _export(
private async _exportDocx(filename: string) {
await Zotero.File.putContentsAsync(filename, this._docxBlob);
this._Addon.ZoteroViews.showProgressWindow(
"Better Notes",
`Note Saved to ${filename}`
);
}
private async _exportMD(
note: Zotero.Item,
filename: string,
deleteAfterExport: boolean

View File

@ -84,13 +84,13 @@ class NoteExportWindow extends AddonBase {
) as XUL.Checkbox
).checked = exportNote;
}
let exportCopy = Zotero.Prefs.get("Knowledge4Zotero.exportCopy") as boolean;
if (typeof exportCopy !== "undefined") {
let exportDocx = Zotero.Prefs.get("Knowledge4Zotero.exportDocx") as boolean;
if (typeof exportDocx !== "undefined") {
(
this._window.document.getElementById(
"Knowledge4Zotero-export-enablecopy"
"Knowledge4Zotero-export-enabledocx"
) as XUL.Checkbox
).checked = exportCopy;
).checked = exportDocx;
}
let exportPDF = Zotero.Prefs.get("Knowledge4Zotero.exportPDF") as boolean;
if (typeof exportPDF !== "undefined") {
@ -195,9 +195,9 @@ class NoteExportWindow extends AddonBase {
"Knowledge4Zotero-export-enablenote"
) as XUL.Checkbox
).checked as boolean;
let exportCopy = (
let exportDocx = (
this._window.document.getElementById(
"Knowledge4Zotero-export-enablecopy"
"Knowledge4Zotero-export-enabledocx"
) as XUL.Checkbox
).checked as boolean;
let exportPDF = (
@ -212,7 +212,7 @@ class NoteExportWindow extends AddonBase {
Zotero.Prefs.set("Knowledge4Zotero.convertSquare", convertSquare);
Zotero.Prefs.set("Knowledge4Zotero.embedLink", embedLink);
Zotero.Prefs.set("Knowledge4Zotero.exportNote", exportNote);
Zotero.Prefs.set("Knowledge4Zotero.exportCopy", exportCopy);
Zotero.Prefs.set("Knowledge4Zotero.exportDocx", exportDocx);
Zotero.Prefs.set("Knowledge4Zotero.exportPDF", exportPDF);
Zotero.debug(this.io);
Zotero.debug(this.io.dataOut);
@ -223,7 +223,7 @@ class NoteExportWindow extends AddonBase {
exportHighlight: exportHighlight,
embedLink: embedLink,
exportNote: exportNote,
exportCopy: exportCopy,
exportDocx: exportDocx,
exportPDF: exportPDF,
};
}

View File

@ -307,7 +307,6 @@ class ZoteroEvents extends AddonBase {
instance._knowledgeUIInitialized = false;
const noteItem = instance._item;
const isPrint = this._Addon.NoteExport._pdfNoteId === noteItem.id;
// item.getNote may not be initialized yet
if (Zotero.ItemTypes.getID("note") !== noteItem.itemTypeID) {
@ -328,13 +327,6 @@ class ZoteroEvents extends AddonBase {
instance._knowledgeSelectionInitialized = true;
}
// Check if this is a window for print
if (isPrint) {
instance._iframeWindow.postMessage({ type: "exportPDF" }, "*");
this._Addon.NoteExport._pdfNoteId = -1;
return;
}
instance._popup.setAttribute(
"onpopupshowing",
"Zotero.Knowledge4Zotero.EditorViews.updatePopupMenu()"
@ -1019,7 +1011,7 @@ class ZoteroEvents extends AddonBase {
options.embedLink,
options.exportFile,
options.exportNote,
options.exportCopy,
options.exportDocx,
options.exportPDF
);
}