update: rewrite the markdown export translator

fix: #28 Markdown convert bug
This commit is contained in:
xiangyu 2022-06-01 22:51:25 +08:00
parent 992923c3f9
commit 71b961512d
10 changed files with 1955 additions and 157 deletions

View File

@ -34,10 +34,6 @@
<checkbox id="__addonRef__-export-enablefile" checked="true" />
<label value="&zotero.__addonRef__.export.file.enable.label;" />
</row>
<row>
<checkbox id="__addonRef__-export-embedImage" checked="true" />
<label value="&zotero.__addonRef__.export.image.enable.label;" />
</row>
</rows>
</groupbox>
<groupbox flex="1">

View File

@ -23,8 +23,7 @@
<!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.file.enable.label "Export to file(MarkDown/HTML/RDF)">
<!ENTITY zotero.__addonRef__.export.image.enable.label "Embed Images in MarkDown format">
<!ENTITY zotero.__addonRef__.export.file.enable.label "Export to MarkDown File">
<!ENTITY zotero.__addonRef__.export.richtext.label "RichText(MS Word) Settings">
<!ENTITY zotero.__addonRef__.export.copy.enable.label "Export to clipboard">

View File

@ -23,8 +23,7 @@
<!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.file.enable.label "导出为文件(MarkDown/HTML/RDF)">
<!ENTITY zotero.__addonRef__.export.image.enable.label "嵌入MarkDown格式图片">
<!ENTITY zotero.__addonRef__.export.file.enable.label "导出为MarkDown文件">
<!ENTITY zotero.__addonRef__.export.richtext.label "富文本(MS Word)设置">
<!ENTITY zotero.__addonRef__.export.copy.enable.label "导出到剪贴板">

View File

@ -157,6 +157,11 @@ copyFileSync(
path.join(buildDir, "addon/components/zotero-protocol-handler.js")
);
copyFileSync(
"src/Better Note Markdown.js",
path.join(buildDir, "addon/chrome/content/translators/Better Note Markdown.js")
);
compressing.zip.compressDir(
path.join(buildDir, "addon"),
path.join(buildDir, `${name}.xpi`),

1809
src/Better Note Markdown.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -45,6 +45,9 @@ class AddonEvents extends AddonBase {
public async onInit() {
Zotero.debug("Knowledge4Zotero: init called");
await Zotero.uiReadyPromise;
// Init translator
// await loadTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
// Init UI
this._Addon.views.addOpenWorkspaceButton();
this._Addon.views.addNewKnowledgeButton();
this.addEditorInstanceListener();
@ -1039,7 +1042,6 @@ class AddonEvents extends AddonBase {
await this._Addon.knowledge.exportNoteToFile(
message.content.editorInstance._item,
options.embedLink,
options.embedImage,
options.exportFile,
options.exportNote,
options.exportCopy

View File

@ -31,14 +31,6 @@ class AddonExport extends AddonBase {
) as XUL.Checkbox
).checked = embedLink;
}
let embedImage = Zotero.Prefs.get("Knowledge4Zotero.embedImage");
if (typeof embedImage !== "undefined") {
(
this._window.document.getElementById(
"Knowledge4Zotero-export-embedImage"
) as XUL.Checkbox
).checked = embedImage;
}
let exportNote = Zotero.Prefs.get("Knowledge4Zotero.exportNote");
if (typeof exportNote !== "undefined") {
(
@ -71,11 +63,6 @@ class AddonExport extends AddonBase {
"Knowledge4Zotero-export-embedLink"
) as XUL.Checkbox
).checked;
let embedImage = (
this._window.document.getElementById(
"Knowledge4Zotero-export-embedImage"
) as XUL.Checkbox
).checked;
let exportNote = (
this._window.document.getElementById(
"Knowledge4Zotero-export-enablenote"
@ -88,7 +75,6 @@ class AddonExport extends AddonBase {
).checked;
Zotero.Prefs.set("Knowledge4Zotero.exportFile", exportFile);
Zotero.Prefs.set("Knowledge4Zotero.embedLink", embedLink);
Zotero.Prefs.set("Knowledge4Zotero.embedImage", embedImage);
Zotero.Prefs.set("Knowledge4Zotero.exportNote", exportNote);
Zotero.Prefs.set("Knowledge4Zotero.exportCopy", exportCopy);
Zotero.debug(this.io);
@ -96,7 +82,6 @@ class AddonExport extends AddonBase {
this.io.dataOut = {
exportFile: exportFile,
embedLink: embedLink,
embedImage: embedImage,
exportNote: exportNote,
exportCopy: exportCopy,
};

34
src/exportMD.ts Normal file
View File

@ -0,0 +1,34 @@
const TRANSLATOR_ID_BETTER_MARKDOWN = "1412e9e2-51e1-42ec-aa35-e036a895534c";
const configs = {};
configs[TRANSLATOR_ID_BETTER_MARKDOWN] = {
translatorID: TRANSLATOR_ID_BETTER_MARKDOWN,
label: "Better Note Markdown",
creator: "Martynas Bagdonas; Winding",
target: "md",
minVersion: "5.0.97",
maxVersion: "",
priority: 50,
configOptions: {
noteTranslator: true,
},
displayOptions: {
includeAppLinks: true,
},
inRepository: true,
translatorType: 2,
lastUpdated: "2022-06-01 10:26:46",
_codePath:
"chrome://Knowledge4Zotero/content/translators/Better Note Markdown.js",
};
async function loadTranslator(id) {
const config = configs[id];
const code = (await Zotero.File.getContentsAsync(config._codePath)).response;
Zotero.debug(code);
await Zotero.Translators.save(config, code);
await Zotero.Translators.reinit();
}
export { TRANSLATOR_ID_BETTER_MARKDOWN, loadTranslator };

33
src/file_picker.ts Normal file
View File

@ -0,0 +1,33 @@
export 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 Zotero.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
}
})
})
}

View File

@ -1,4 +1,6 @@
import { AddonBase, EditorMessage, OutlineType } from "./base";
import { loadTranslator, TRANSLATOR_ID_BETTER_MARKDOWN } from "./exportMD";
import { pick } from "./file_picker";
const TreeModel = require("./treemodel");
@ -6,6 +8,8 @@ class Knowledge extends AddonBase {
currentLine: number;
currentNodeID: number;
workspaceWindow: Window;
_exportNote: ZoteroItem;
_exportPath: string;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.currentLine = -1;
@ -623,7 +627,6 @@ class Knowledge extends AddonBase {
async exportNoteToFile(
note: ZoteroItem,
convertNoteLinks: boolean = true,
convertNoteImages: boolean = true,
saveFile: boolean = true,
saveNote: boolean = false,
saveCopy: boolean = false
@ -632,29 +635,67 @@ class Knowledge extends AddonBase {
return;
}
note = note || this.getWorkspaceNote();
const noteID = await ZoteroPane_Local.newNote();
const newNote = Zotero.Items.get(noteID);
const rootNoteIds = [note.id];
let newNote: ZoteroItem;
if (convertNoteLinks || saveNote) {
const noteID = await ZoteroPane_Local.newNote();
newNote = Zotero.Items.get(noteID);
const rootNoteIds = [note.id];
const convertResult = await this.convertNoteLines(
note,
rootNoteIds,
convertNoteLinks,
convertNoteImages
);
const convertResult = await this.convertNoteLines(
note,
rootNoteIds,
convertNoteLinks
);
this.setLinesToNote(newNote, convertResult.lines);
Zotero.debug(convertResult.subNotes);
this.setLinesToNote(newNote, convertResult.lines);
Zotero.debug(convertResult.subNotes);
await Zotero.DB.executeTransaction(async () => {
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
await Zotero.DB.executeTransaction(async () => {
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
if (saveFile) {
const exporter = new Zotero_File_Exporter();
exporter.items = [newNote];
await exporter.save();
if (
(await new Zotero.Translate.Export().getTranslators()).filter(
(e) => e.translatorID === TRANSLATOR_ID_BETTER_MARKDOWN
)
) {
await loadTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
}
const filename = await pick(
Zotero.getString("fileInterface.export"),
"save",
[["MarkDown File(*.md)", "*.md"]],
`${newNote.getNoteTitle()}.md`
);
if (!filename) {
return;
}
this._exportNote = newNote;
this._exportPath =
Zotero.File.pathToFile(filename).parent.path + "\\attachments";
const hasImage = newNote.getNote().includes("<img");
if (hasImage) {
await Zotero.File.createDirectoryIfMissingAsync(this._exportPath);
}
const translator = new Zotero.Translate.Export();
translator.setItems([newNote]);
translator.setLocation(Zotero.File.pathToFile(filename));
translator.setTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
translator.translate();
this._Addon.views.showProgressWindow(
"Better Notes",
`Note Saved to ${filename}`
);
}
if (saveCopy) {
if (!convertNoteLinks) {
@ -674,115 +715,22 @@ class Knowledge extends AddonBase {
}
}
if (!saveNote) {
const _w: Window = ZoteroPane.findNoteWindow(newNote.id);
if (_w) {
_w.close();
if (newNote.id !== note.id) {
const _w: Window = ZoteroPane.findNoteWindow(newNote.id);
if (_w) {
_w.close();
}
await Zotero.Items.erase(newNote.id);
}
await Zotero.Items.erase(newNote.id);
} else {
ZoteroPane.openNoteWindow(newNote.id);
}
}
async convertImage(line: string, newLines: string[], sourceNote: ZoteroItem) {
const imageReg = new RegExp("<img");
const imageBrReg = new RegExp("<br>");
const imageKeyReg = new RegExp(`data-attachment-key="`);
const imageAnnotationReg = new RegExp(`data-annotation="`);
const imageIndex = line.search(imageReg);
if (imageIndex !== -1) {
const lineStart = line.slice(0, imageIndex);
const imageLine = line.slice(imageIndex);
const lineEnd = imageLine.slice(imageLine.search(imageBrReg));
const attachmentKeyIndex = imageLine.search(imageKeyReg);
const annotationIndex = imageLine.search(imageAnnotationReg);
if (attachmentKeyIndex !== -1) {
let attachmentKey = imageLine.slice(
attachmentKeyIndex + imageKeyReg.source.length
);
attachmentKey = attachmentKey.slice(0, attachmentKey.search(/"/g));
const attachmentItem = await Zotero.Items.getByLibraryAndKeyAsync(
sourceNote.libraryID,
attachmentKey
);
let attachmentURL = await attachmentItem.getFilePathAsync();
if (attachmentURL) {
Zotero.debug("convert image");
// const imageData = await editorInstance._getDataURL(
// attachmentItem
// );
Zotero.debug(line);
Zotero.debug(lineStart);
Zotero.debug(lineEnd);
if (Zotero.isMac) {
attachmentURL = "file://" + attachmentURL;
}
newLines.push(`<p>!<a href="${attachmentURL}">image</a></p>`);
// Export annotation link
if (annotationIndex !== -1) {
let annotationContentRaw = imageLine.slice(
annotationIndex + imageAnnotationReg.source.length
);
annotationContentRaw = annotationContentRaw.slice(
0,
annotationContentRaw.search('"')
);
if (annotationContentRaw) {
Zotero.debug("convert image annotation");
Zotero.debug(annotationContentRaw);
try {
let annotation = JSON.parse(
decodeURIComponent(annotationContentRaw)
);
if (annotation) {
// annotation.uri was used before note-editor v4
let uri = annotation.attachmentURI || annotation.uri;
let position = annotation.position;
if (typeof uri === "string" && typeof position === "object") {
let annotationURL;
let uriParts = uri.split("/");
let libraryType = uriParts[3];
let key = uriParts[6];
if (libraryType === "users") {
annotationURL = "zotero://open-pdf/library/items/" + key;
}
// groups
else {
let groupID = uriParts[4];
annotationURL =
"zotero://open-pdf/groups/" + groupID + "/items/" + key;
}
annotationURL +=
"?page=" +
(position.pageIndex + 1) +
(annotation.annotationKey
? "&annotation=" + annotation.annotationKey
: "");
newLines.push(`<p><a href="${annotationURL}">pdf</a></p>`);
}
}
} catch (e) {
Zotero.debug(e);
}
}
}
newLines.push(`${lineStart}${lineEnd}`);
return true;
}
}
}
return false;
}
async convertNoteLines(
currentNote: ZoteroItem,
rootNoteIds: number[],
convertNoteLinks: boolean = true,
convertNoteImages: boolean = true
convertNoteLinks: boolean = true
): Promise<{ lines: string[]; subNotes: ZoteroItem[] }> {
Zotero.debug(`convert note ${currentNote.id}`);
@ -792,17 +740,6 @@ class Knowledge extends AddonBase {
let newLines = [];
const noteLines = this.getLinesInNote(currentNote);
for (let i in noteLines) {
// Embed Image
if (convertNoteImages) {
const hasImage = await this.convertImage(
noteLines[i],
newLines,
currentNote
);
if (hasImage) {
continue;
}
}
newLines.push(noteLines[i]);
// Convert Link
if (convertNoteLinks) {
@ -816,8 +753,7 @@ class Knowledge extends AddonBase {
const convertResult = await this.convertNoteLines(
subNote,
_rootNoteIds,
convertNoteLinks,
convertNoteImages
convertNoteLinks
);
const subNoteLines = convertResult.lines;
let _newLine: string = "";