mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
export single note as markdown, #166
This commit is contained in:
parent
467ad79129
commit
9bdd4437f2
@ -34,6 +34,7 @@ class Branch extends Entity {
|
|||||||
this.origParentNoteId = this.parentNoteId;
|
this.origParentNoteId = this.parentNoteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Note|null} */
|
||||||
async getNote() {
|
async getNote() {
|
||||||
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
@ -482,6 +482,13 @@ class Note extends Entity {
|
|||||||
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} - true if note has children
|
||||||
|
*/
|
||||||
|
async hasChildren() {
|
||||||
|
return (await this.getChildNotes()).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<Note[]>} child notes of this note
|
* @returns {Promise<Note[]>} child notes of this note
|
||||||
*/
|
*/
|
||||||
|
2
src/public/javascripts/services/bootstrap.js
vendored
2
src/public/javascripts/services/bootstrap.js
vendored
@ -98,6 +98,8 @@ if (utils.isElectron()) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$("#export-note-to-markdown-button").click(() => exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single'));
|
||||||
|
|
||||||
treeService.showTree();
|
treeService.showTree();
|
||||||
|
|
||||||
entrypoints.registerEntrypoints();
|
entrypoints.registerEntrypoints();
|
||||||
|
@ -168,13 +168,13 @@ const contextMenuOptions = {
|
|||||||
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
treeChangesService.deleteNodes(treeService.getSelectedNodes(true));
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "exportSubtreeToTar") {
|
else if (ui.cmd === "exportSubtreeToTar") {
|
||||||
exportService.exportSubtree(node.data.noteId, 'tar');
|
exportService.exportSubtree(node.data.branchId, 'tar');
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "exportSubtreeToOpml") {
|
else if (ui.cmd === "exportSubtreeToOpml") {
|
||||||
exportService.exportSubtree(node.data.noteId, 'opml');
|
exportService.exportSubtree(node.data.branchId, 'opml');
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "exportSubtreeToMarkdown") {
|
else if (ui.cmd === "exportSubtreeToMarkdown") {
|
||||||
exportService.exportSubtree(node.data.noteId, 'markdown');
|
exportService.exportSubtree(node.data.branchId, 'markdown');
|
||||||
}
|
}
|
||||||
else if (ui.cmd === "importIntoNote") {
|
else if (ui.cmd === "importIntoNote") {
|
||||||
exportService.importIntoNote(node.data.noteId);
|
exportService.importIntoNote(node.data.noteId);
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('../../services/sql');
|
|
||||||
const html = require('html');
|
const html = require('html');
|
||||||
const tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
const repository = require("../../services/repository");
|
const repository = require("../../services/repository");
|
||||||
const utils = require('../../services/utils');
|
const utils = require('../../services/utils');
|
||||||
const commonmark = require('commonmark');
|
|
||||||
const TurndownService = require('turndown');
|
const TurndownService = require('turndown');
|
||||||
|
|
||||||
async function exportNote(req, res) {
|
async function exportNote(req, res) {
|
||||||
const noteId = req.params.noteId;
|
// entityId maybe either noteId or branchId depending on format
|
||||||
|
const entityId = req.params.entityId;
|
||||||
const format = req.params.format;
|
const format = req.params.format;
|
||||||
|
|
||||||
const branchId = await sql.getValue('SELECT branchId FROM branches WHERE noteId = ?', [noteId]);
|
|
||||||
|
|
||||||
if (format === 'tar') {
|
if (format === 'tar') {
|
||||||
await exportToTar(branchId, res);
|
await exportToTar(await repository.getBranch(entityId), res);
|
||||||
}
|
}
|
||||||
else if (format === 'opml') {
|
else if (format === 'opml') {
|
||||||
await exportToOpml(branchId, res);
|
await exportToOpml(await repository.getBranch(entityId), res);
|
||||||
}
|
}
|
||||||
else if (format === 'markdown') {
|
else if (format === 'markdown') {
|
||||||
await exportToMarkdown(branchId, res);
|
await exportToMarkdown(await repository.getBranch(entityId), res);
|
||||||
|
}
|
||||||
|
// export single note without subtree
|
||||||
|
else if (format === 'markdown-single') {
|
||||||
|
await exportSingleMarkdown(await repository.getNote(entityId), res);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return [404, "Unrecognized export format " + format];
|
return [404, "Unrecognized export format " + format];
|
||||||
@ -48,8 +49,7 @@ function prepareText(text) {
|
|||||||
return escaped.replace(/\n/g, ' ');
|
return escaped.replace(/\n/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportToOpml(branchId, res) {
|
async function exportToOpml(branch, res) {
|
||||||
const branch = await repository.getBranch(branchId);
|
|
||||||
const note = await branch.getNote();
|
const note = await branch.getNote();
|
||||||
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title;
|
||||||
const sanitizedTitle = sanitize(title);
|
const sanitizedTitle = sanitize(title);
|
||||||
@ -81,18 +81,18 @@ async function exportToOpml(branchId, res) {
|
|||||||
</head>
|
</head>
|
||||||
<body>`);
|
<body>`);
|
||||||
|
|
||||||
await exportNoteInner(branchId);
|
await exportNoteInner(branch.branchId);
|
||||||
|
|
||||||
res.write(`</body>
|
res.write(`</body>
|
||||||
</opml>`);
|
</opml>`);
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportToTar(branchId, res) {
|
async function exportToTar(branch, res) {
|
||||||
const pack = tar.pack();
|
const pack = tar.pack();
|
||||||
|
|
||||||
const exportedNoteIds = [];
|
const exportedNoteIds = [];
|
||||||
const name = await exportNoteInner(branchId, '');
|
const name = await exportNoteInner(branch.branchId, '');
|
||||||
|
|
||||||
async function exportNoteInner(branchId, directory) {
|
async function exportNoteInner(branchId, directory) {
|
||||||
const branch = await repository.getBranch(branchId);
|
const branch = await repository.getBranch(branchId);
|
||||||
@ -165,14 +165,20 @@ async function exportToTar(branchId, res) {
|
|||||||
pack.pipe(res);
|
pack.pipe(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportToMarkdown(branchId, res) {
|
async function exportToMarkdown(branch, res) {
|
||||||
|
const note = await branch.getNote();
|
||||||
|
|
||||||
|
if (!await note.hasChildren()) {
|
||||||
|
await exportSingleMarkdown(note, res);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const turndownService = new TurndownService();
|
const turndownService = new TurndownService();
|
||||||
const pack = tar.pack();
|
const pack = tar.pack();
|
||||||
const name = await exportNoteInner(branchId, '');
|
const name = await exportNoteInner(note, '');
|
||||||
|
|
||||||
async function exportNoteInner(branchId, directory) {
|
async function exportNoteInner(note, directory) {
|
||||||
const branch = await repository.getBranch(branchId);
|
|
||||||
const note = await branch.getNote();
|
|
||||||
const childFileName = directory + sanitize(note.title);
|
const childFileName = directory + sanitize(note.title);
|
||||||
|
|
||||||
if (await note.hasLabel('excludeFromExport')) {
|
if (await note.hasLabel('excludeFromExport')) {
|
||||||
@ -181,8 +187,8 @@ async function exportToMarkdown(branchId, res) {
|
|||||||
|
|
||||||
saveDataFile(childFileName, note);
|
saveDataFile(childFileName, note);
|
||||||
|
|
||||||
for (const child of await note.getChildBranches()) {
|
for (const childNote of await note.getChildNotes()) {
|
||||||
await exportNoteInner(child.branchId, childFileName + "/");
|
await exportNoteInner(childNote, childFileName + "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
return childFileName;
|
return childFileName;
|
||||||
@ -221,6 +227,17 @@ async function exportToMarkdown(branchId, res) {
|
|||||||
pack.pipe(res);
|
pack.pipe(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function exportSingleMarkdown(note, res) {
|
||||||
|
const turndownService = new TurndownService();
|
||||||
|
const markdown = turndownService.turndown(note.content);
|
||||||
|
const name = sanitize(note.title);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"');
|
||||||
|
res.setHeader('Content-Type', 'text/markdown; charset=UTF-8');
|
||||||
|
|
||||||
|
res.send(markdown);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
exportNote
|
exportNote
|
||||||
};
|
};
|
@ -8,6 +8,7 @@ const tar = require('tar-stream');
|
|||||||
const stream = require('stream');
|
const stream = require('stream');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const parseString = require('xml2js').parseString;
|
const parseString = require('xml2js').parseString;
|
||||||
|
const commonmark = require('commonmark');
|
||||||
|
|
||||||
async function importToBranch(req) {
|
async function importToBranch(req) {
|
||||||
const parentNoteId = req.params.parentNoteId;
|
const parentNoteId = req.params.parentNoteId;
|
||||||
@ -178,11 +179,11 @@ async function parseImportFile(file) {
|
|||||||
|
|
||||||
async function importNotes(files, parentNoteId, noteIdMap, attributes) {
|
async function importNotes(files, parentNoteId, noteIdMap, attributes) {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (file.meta.version !== 1) {
|
if (file.meta && file.meta.version !== 1) {
|
||||||
throw new Error("Can't read meta data version " + file.meta.version);
|
throw new Error("Can't read meta data version " + file.meta.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.meta.clone) {
|
if (file.meta && file.meta.clone) {
|
||||||
await new Branch({
|
await new Branch({
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
noteId: noteIdMap[file.meta.noteId],
|
noteId: noteIdMap[file.meta.noteId],
|
||||||
@ -192,7 +193,7 @@ async function importNotes(files, parentNoteId, noteIdMap, attributes) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.meta.type !== 'file') {
|
if (!file.meta || file.meta.type !== 'file') {
|
||||||
file.data = file.data.toString("UTF-8");
|
file.data = file.data.toString("UTF-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ function register(app) {
|
|||||||
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
|
apiRoute(PUT, '/api/notes/:noteId/clone-to/:parentNoteId', cloningApiRoute.cloneNoteToParent);
|
||||||
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter);
|
||||||
|
|
||||||
route(GET, '/api/notes/:noteId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
|
route(GET, '/api/notes/:entityId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
|
||||||
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
|
route(POST, '/api/notes/:parentNoteId/import', [auth.checkApiAuthOrElectron, uploadMiddleware], importRoute.importToBranch, apiResultHandler);
|
||||||
|
|
||||||
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
|
route(POST, '/api/notes/:parentNoteId/upload', [auth.checkApiAuthOrElectron, uploadMiddleware],
|
||||||
|
@ -36,22 +36,27 @@ async function getEntity(query, params = []) {
|
|||||||
return entityConstructor.createEntityFromRow(row);
|
return entityConstructor.createEntityFromRow(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Note|null} */
|
||||||
async function getNote(noteId) {
|
async function getNote(noteId) {
|
||||||
return await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
return await getEntity("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Branch|null} */
|
||||||
async function getBranch(branchId) {
|
async function getBranch(branchId) {
|
||||||
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
return await getEntity("SELECT * FROM branches WHERE branchId = ?", [branchId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Image|null} */
|
||||||
async function getImage(imageId) {
|
async function getImage(imageId) {
|
||||||
return await getEntity("SELECT * FROM images WHERE imageId = ?", [imageId]);
|
return await getEntity("SELECT * FROM images WHERE imageId = ?", [imageId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Attribute|null} */
|
||||||
async function getAttribute(attributeId) {
|
async function getAttribute(attributeId) {
|
||||||
return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]);
|
return await getEntity("SELECT * FROM attributes WHERE attributeId = ?", [attributeId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns {Option|null} */
|
||||||
async function getOption(name) {
|
async function getOption(name) {
|
||||||
return await getEntity("SELECT * FROM options WHERE name = ?", [name]);
|
return await getEntity("SELECT * FROM options WHERE name = ?", [name]);
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,7 @@
|
|||||||
<li><a class="show-attributes-button"><kbd>Alt+A</kbd> Attributes</a></li>
|
<li><a class="show-attributes-button"><kbd>Alt+A</kbd> Attributes</a></li>
|
||||||
<li><a id="show-source-button">HTML source</a></li>
|
<li><a id="show-source-button">HTML source</a></li>
|
||||||
<li><a id="upload-file-button">Upload file</a></li>
|
<li><a id="upload-file-button">Upload file</a></li>
|
||||||
|
<li><a id="export-note-to-markdown-button">Export as markdown</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user