add: export linked notes to markdown

This commit is contained in:
xiangyu 2022-06-10 21:03:04 +08:00
parent 4bb748cc85
commit 2db441128a
7 changed files with 191 additions and 72 deletions

View File

@ -31,9 +31,13 @@
<caption label="&zotero.__addonRef__.export.markdown.label;"></caption>
<rows flex="1">
<row>
<checkbox id="__addonRef__-export-enablefile" checked="true" />
<checkbox id="__addonRef__-export-enablefile" checked="true" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.file.enable.label;" />
</row>
<row>
<checkbox id="__addonRef__-export-enablesingle" checked="false" oncommand="Zotero.Knowledge4Zotero.export.doUpdate(event)" />
<label value="&zotero.__addonRef__.export.singlefile.enable.label;" />
</row>
</rows>
</groupbox>
<groupbox flex="1">

View File

@ -28,6 +28,7 @@
<!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 MarkDown File">
<!ENTITY zotero.__addonRef__.export.singlefile.enable.label "Export Linked Notes 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

@ -28,6 +28,7 @@
<!ENTITY zotero.__addonRef__.export.note.enable.label "导出到新笔记">
<!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.richtext.label "富文本(MS Word)设置">
<!ENTITY zotero.__addonRef__.export.copy.enable.label "导出到剪贴板">

View File

@ -774,6 +774,22 @@ let bundle;
var href = node.getAttribute("href");
var title = cleanAttribute(node.getAttribute("title"));
if (title) title = ' "' + title + '"';
if (href.search(/zotero:\/\/note\/\w+\/\w+\//g) !== -1) {
// A note link should be converted if it is in the _exportFileDict
var _Zotero = Components.classes[
"@zotero.org/Zotero;1"
].getService(
Components.interfaces.nsISupports
).wrappedJSObject;
const noteInfo =
_Zotero.Knowledge4Zotero.knowledge._exportFileDict &&
_Zotero.Knowledge4Zotero.knowledge._exportFileDict.find(
(i) => href.includes(i.link)
);
if (noteInfo) {
href = `./${noteInfo.filename}`;
}
}
return "[" + content + "](" + href + title + ")";
},
};
@ -1800,7 +1816,7 @@ let bundle;
);
async function doExport() {
const _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
var _Zotero = Components.classes["@zotero.org/Zotero;1"].getService(
Components.interfaces.nsISupports
).wrappedJSObject;
Zotero.setCharacterSet("utf-8");

View File

@ -126,7 +126,6 @@ class AddonEvents extends AddonBase {
if (state) {
const noteTab = state.tabs.find((t) => t.type === "betternotes");
Zotero.debug(noteTab);
this._Addon.views.showProgressWindow("1", noteTab.select);
if (noteTab) {
let t = 0;
while (t < 5) {
@ -1177,18 +1176,20 @@ class AddonEvents extends AddonBase {
await io.deferred.promise;
const options = io.dataOut;
await this._Addon.knowledge.exportNoteToFile(
item,
options.embedLink,
options.exportFile,
options.exportNote,
options.exportCopy
);
if (options.exportFile && options.exportSingleFile) {
await this._Addon.knowledge.exportNotesToFile([item], false);
} else {
await this._Addon.knowledge.exportNoteToFile(
item,
options.embedLink,
options.exportFile,
options.exportNote,
options.exportCopy
);
}
} else if (message.type === "exportNotes") {
/*
message.content = {
editorInstance
}
message.content = {}
*/
const items = ZoteroPane.getSelectedItems();
const noteItems = [];
@ -1210,7 +1211,11 @@ class AddonEvents extends AddonBase {
new EditorMessage("export", { params: { item: noteItems[0] } })
);
} else {
await this._Addon.knowledge.exportNotesToFile(noteItems);
const useSingleFile = confirm("Export linked notes to markdown files?");
await this._Addon.knowledge.exportNotesToFile(
noteItems,
!useSingleFile
);
}
} else if (message.type === "openAttachment") {
/*

View File

@ -23,6 +23,16 @@ class AddonExport extends AddonBase {
) as XUL.Checkbox
).checked = exportFile;
}
let exportSingleFile = Zotero.Prefs.get(
"Knowledge4Zotero.exportSingleFile"
);
if (typeof exportSingleFile !== "undefined") {
(
this._window.document.getElementById(
"Knowledge4Zotero-export-enablesingle"
) as XUL.Checkbox
).checked = exportSingleFile;
}
let embedLink = Zotero.Prefs.get("Knowledge4Zotero.embedLink");
if (typeof embedLink !== "undefined") {
(
@ -48,6 +58,25 @@ class AddonExport extends AddonBase {
).checked = exportCopy;
}
}
doUpdate(event: XULEvent) {
if (
event.target.getAttribute("id") === "Knowledge4Zotero-export-enablesingle"
) {
(
this._window.document.getElementById(
"Knowledge4Zotero-export-embedLink"
) as XUL.Checkbox
).disabled = (event.target as XUL.Checkbox).checked;
} else if (
event.target.getAttribute("id") === "Knowledge4Zotero-export-enablefile"
) {
(
this._window.document.getElementById(
"Knowledge4Zotero-export-enablesingle"
) as XUL.Checkbox
).disabled = !(event.target as XUL.Checkbox).checked;
}
}
doUnload() {
this.io.deferred && this.io.deferred.resolve();
}
@ -58,6 +87,11 @@ class AddonExport extends AddonBase {
"Knowledge4Zotero-export-enablefile"
) as XUL.Checkbox
).checked;
let exportSingleFile = (
this._window.document.getElementById(
"Knowledge4Zotero-export-enablesingle"
) as XUL.Checkbox
).checked;
let embedLink = (
this._window.document.getElementById(
"Knowledge4Zotero-export-embedLink"
@ -74,6 +108,7 @@ class AddonExport extends AddonBase {
) as XUL.Checkbox
).checked;
Zotero.Prefs.set("Knowledge4Zotero.exportFile", exportFile);
Zotero.Prefs.set("Knowledge4Zotero.exportSingleFile", exportSingleFile);
Zotero.Prefs.set("Knowledge4Zotero.embedLink", embedLink);
Zotero.Prefs.set("Knowledge4Zotero.exportNote", exportNote);
Zotero.Prefs.set("Knowledge4Zotero.exportCopy", exportCopy);
@ -81,6 +116,7 @@ class AddonExport extends AddonBase {
Zotero.debug(this.io.dataOut);
this.io.dataOut = {
exportFile: exportFile,
exportSingleFile: exportSingleFile,
embedLink: embedLink,
exportNote: exportNote,
exportCopy: exportCopy,

View File

@ -11,6 +11,7 @@ class Knowledge extends AddonBase {
workspaceTabId: string;
_exportNote: ZoteroItem;
_exportPath: string;
_exportFileDict: object;
constructor(parent: Knowledge4Zotero) {
super(parent);
this.currentLine = -1;
@ -712,6 +713,8 @@ class Knowledge extends AddonBase {
if (!saveFile && !saveNote && !saveCopy) {
return;
}
this._exportFileDict = [];
note = note || this.getWorkspaceNote();
let newNote: ZoteroItem;
if (convertNoteLinks || saveNote) {
@ -804,8 +807,9 @@ class Knowledge extends AddonBase {
}
}
async exportNotesToFile(notes: ZoteroItem[]) {
async exportNotesToFile(notes: ZoteroItem[], useEmbed: boolean) {
Components.utils.import("resource://gre/modules/osfile.jsm");
this._exportFileDict = [];
const filepath = await pick(
Zotero.getString("fileInterface.export"),
"folder"
@ -821,68 +825,120 @@ class Knowledge extends AddonBase {
// Convert to unix format
this._exportPath = this._exportPath.replace(/\\/g, "/");
let attachmentCreated = false;
if (useEmbed) {
for (const note of notes) {
let newNote: ZoteroItem;
if (this.getLinkFromText(note.getNote())) {
const noteID = await ZoteroPane_Local.newNote();
newNote = Zotero.Items.get(noteID);
const rootNoteIds = [note.id];
for (const note of notes) {
let newNote: ZoteroItem;
if (this.getLinkFromText(note.getNote())) {
const noteID = await ZoteroPane_Local.newNote();
newNote = Zotero.Items.get(noteID);
const rootNoteIds = [note.id];
const convertResult = await this.convertNoteLines(
note,
rootNoteIds,
true
);
const convertResult = await this.convertNoteLines(
note,
rootNoteIds,
true
);
this.setLinesToNote(newNote, convertResult.lines);
Zotero.debug(convertResult.subNotes);
this.setLinesToNote(newNote, convertResult.lines);
Zotero.debug(convertResult.subNotes);
await Zotero.DB.executeTransaction(async () => {
await Zotero.Notes.copyEmbeddedImages(note, newNote);
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
this._exportNote = newNote;
const hasImage = newNote.getNote().includes("<img");
if (hasImage && !attachmentCreated) {
await Zotero.File.createDirectoryIfMissingAsync(
OS.Path.join(...this._exportPath.split(/\//))
);
attachmentCreated = true;
}
let filename = `${Zotero.File.pathToFile(filepath).path}/${
newNote.getNoteTitle
? newNote.getNoteTitle().replace(/[/\\?%*:|"<>]/g, "-") + "-"
: ""
}${note.key}.md`;
filename = filename.replace(/\\/g, "/");
const translator = new Zotero.Translate.Export();
translator.setItems([newNote]);
translator.setLocation(
Zotero.File.pathToFile(OS.Path.join(...filename.split(/\//)))
);
translator.setTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
translator.translate();
this._Addon.views.showProgressWindow(
"Better Notes",
`Note Saved to ${filename}`
);
if (newNote.id !== note.id) {
const _w: Window = ZoteroPane.findNoteWindow(newNote.id);
if (_w) {
_w.close();
await Zotero.DB.executeTransaction(async () => {
await Zotero.Notes.copyEmbeddedImages(note, newNote);
for (const subNote of convertResult.subNotes) {
await Zotero.Notes.copyEmbeddedImages(subNote, newNote);
}
});
} else {
newNote = note;
}
await Zotero.Items.erase(newNote.id);
this._exportNote = newNote;
let filename = `${Zotero.File.pathToFile(filepath).path}/${
newNote.getNoteTitle
? newNote.getNoteTitle().replace(/[/\\?%*:|"<>]/g, "-") + "-"
: ""
}${note.key}.md`;
filename = filename.replace(/\\/g, "/");
this._export(newNote, filename, newNote.id !== note.id);
}
} else {
// Export every linked note as a markdown file
// Find all linked notes that need to be exported
let allNoteIds: number[] = [].concat(notes.map((n) => n.id));
for (const note of notes) {
const subNoteIds = (
await Promise.all(
note
.getNote()
.match(/zotero:\/\/note\/\w+\/\w+\//g)
.map(async (link) => this.getNoteFromLink(link))
)
)
.filter((res) => res.item)
.map((res) => res.item.id);
allNoteIds = allNoteIds.concat(subNoteIds);
}
allNoteIds = new Array(...new Set(allNoteIds));
// console.log(allNoteIds);
const allNoteItems: ZoteroItem[] = Zotero.Items.get(allNoteIds);
const noteLinkDict = allNoteItems.map((_note) => {
return {
link: this.getNoteLink(_note),
id: _note.id,
note: _note,
filename:
(_note.getNoteTitle
? _note.getNoteTitle().replace(/[/\\?%*:|"<> ]/g, "-") + "-"
: "") +
_note.key +
".md",
};
});
this._exportFileDict = noteLinkDict;
for (const noteInfo of noteLinkDict) {
this._exportNote = noteInfo.note;
this._export(
noteInfo.note,
`${Zotero.File.pathToFile(filepath).path}/${noteInfo.filename}`,
false
);
}
}
}
async _export(
note: ZoteroItem,
filename: string,
deleteAfterExport: boolean
) {
const hasImage = note.getNote().includes("<img");
if (hasImage) {
await Zotero.File.createDirectoryIfMissingAsync(
OS.Path.join(...this._exportPath.split(/\//))
);
}
filename = filename.replace(/\\/g, "/");
const translator = new Zotero.Translate.Export();
translator.setItems([note]);
translator.setLocation(
Zotero.File.pathToFile(OS.Path.join(...filename.split(/\//)))
);
translator.setTranslator(TRANSLATOR_ID_BETTER_MARKDOWN);
translator.translate();
this._Addon.views.showProgressWindow(
"Better Notes",
`Note Saved to ${filename}`
);
if (deleteAfterExport) {
const _w: Window = ZoteroPane.findNoteWindow(note.id);
if (_w) {
_w.close();
}
await Zotero.Items.erase(note.id);
}
}