mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
single file export working, tar WIP
This commit is contained in:
parent
ee23bcc783
commit
e09b61d1ac
17
package-lock.json
generated
17
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "trilium",
|
"name": "trilium",
|
||||||
"version": "0.24.3-beta",
|
"version": "0.24.4-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6417,11 +6417,18 @@
|
|||||||
"integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
|
"integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw=="
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mime-types": {
|
||||||
"version": "2.1.20",
|
"version": "2.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
|
||||||
"integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
|
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"mime-db": "~1.36.0"
|
"mime-db": "~1.37.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.37.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
||||||
|
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mimic-fn": {
|
"mimic-fn": {
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
"imagemin-pngquant": "6.0.0",
|
"imagemin-pngquant": "6.0.0",
|
||||||
"ini": "1.3.5",
|
"ini": "1.3.5",
|
||||||
"jimp": "0.5.6",
|
"jimp": "0.5.6",
|
||||||
|
"mime-types": "^2.1.21",
|
||||||
"moment": "2.22.2",
|
"moment": "2.22.2",
|
||||||
"multer": "1.4.1",
|
"multer": "1.4.1",
|
||||||
"open": "0.0.5",
|
"open": "0.0.5",
|
||||||
|
@ -34,9 +34,19 @@ async function showDialog(defaultType) {
|
|||||||
$form.submit(() => {
|
$form.submit(() => {
|
||||||
const exportType = $dialog.find("input[name='export-type']:checked").val();
|
const exportType = $dialog.find("input[name='export-type']:checked").val();
|
||||||
|
|
||||||
|
if (!exportType) {
|
||||||
|
// this shouldn't happen as we always choose default export type
|
||||||
|
alert("Choose export type first please");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportFormat = exportType === 'subtree'
|
||||||
|
? $("input[name=export-subtree-format]:checked").val()
|
||||||
|
: $("input[name=export-single-format]:checked").val();
|
||||||
|
|
||||||
const currentNode = treeService.getCurrentNode();
|
const currentNode = treeService.getCurrentNode();
|
||||||
|
|
||||||
exportService.exportNote(currentNode.data.branchId, exportType);
|
exportService.exportBranch(currentNode.data.branchId, exportType, exportFormat);
|
||||||
|
|
||||||
$dialog.modal('hide');
|
$dialog.modal('hide');
|
||||||
|
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import treeService from './tree.js';
|
import treeService from './tree.js';
|
||||||
import infoService from './info.js';
|
|
||||||
import protectedSessionHolder from './protected_session_holder.js';
|
import protectedSessionHolder from './protected_session_holder.js';
|
||||||
import utils from './utils.js';
|
import utils from './utils.js';
|
||||||
import server from './server.js';
|
import server from './server.js';
|
||||||
|
|
||||||
function exportNote(noteId, format) {
|
function exportBranch(branchId, type, format) {
|
||||||
const url = utils.getHost() + "/api/notes/" + noteId + "/export/" + format +
|
const url = utils.getHost() + `/api/notes/${branchId}/export/${type}/${format}?protectedSessionId=` + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
|
||||||
"?protectedSessionId=" + encodeURIComponent(protectedSessionHolder.getProtectedSessionId());
|
|
||||||
|
console.log(url);
|
||||||
|
|
||||||
utils.download(url);
|
utils.download(url);
|
||||||
|
|
||||||
infoService.showMessage("Export to file has been finished.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let importNoteId;
|
let importNoteId;
|
||||||
@ -47,6 +45,6 @@ $("#import-upload").change(async function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
exportNote,
|
exportBranch,
|
||||||
importIntoNote
|
importIntoNote
|
||||||
};
|
};
|
@ -1,29 +1,22 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const nativeTarExportService = require('../../services/export/native_tar');
|
const tarExportService = require('../../services/export/tar');
|
||||||
const markdownTarExportService = require('../../services/export/markdown_tar');
|
const singleExportService = require('../../services/export/single');
|
||||||
const markdownSingleExportService = require('../../services/export/markdown_single');
|
|
||||||
const opmlExportService = require('../../services/export/opml');
|
const opmlExportService = require('../../services/export/opml');
|
||||||
const repository = require("../../services/repository");
|
const repository = require("../../services/repository");
|
||||||
|
|
||||||
async function exportNote(req, res) {
|
async function exportBranch(req, res) {
|
||||||
// entityId maybe either noteId or branchId depending on format
|
const {branchId, type, format} = req.params;
|
||||||
const entityId = req.params.entityId;
|
const branch = await repository.getBranch(branchId);
|
||||||
const type = req.params.type;
|
|
||||||
const format = req.params.format;
|
|
||||||
|
|
||||||
if (type === 'tar') {
|
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
|
||||||
await nativeTarExportService.exportToTar(await repository.getBranch(entityId), format, res);
|
await tarExportService.exportToTar(branch, format, res);
|
||||||
}
|
}
|
||||||
// else if (format === 'tar') {
|
else if (type === 'single') {
|
||||||
// await markdownTarExportService.exportToMarkdown(await repository.getBranch(entityId), res);
|
await singleExportService.exportSingleNote(branch, format, res);
|
||||||
// }
|
|
||||||
// export single note without subtree
|
|
||||||
else if (format === 'markdown-single') {
|
|
||||||
await markdownSingleExportService.exportSingleMarkdown(await repository.getNote(entityId), res);
|
|
||||||
}
|
}
|
||||||
else if (format === 'opml') {
|
else if (format === 'opml') {
|
||||||
await opmlExportService.exportToOpml(await repository.getBranch(entityId), res);
|
await opmlExportService.exportToOpml(branch, res);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return [404, "Unrecognized export format " + format];
|
return [404, "Unrecognized export format " + format];
|
||||||
@ -31,5 +24,5 @@ async function exportNote(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
exportNote
|
exportBranch
|
||||||
};
|
};
|
@ -128,7 +128,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/:entityId/export/:format', [auth.checkApiAuthOrElectron], exportRoute.exportNote);
|
route(GET, '/api/notes/:branchId/export/:type/:format', [auth.checkApiAuthOrElectron], exportRoute.exportBranch);
|
||||||
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],
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const sanitize = require("sanitize-filename");
|
|
||||||
const TurndownService = require('turndown');
|
|
||||||
|
|
||||||
async function exportSingleMarkdown(note, res) {
|
|
||||||
if (note.type !== 'text' && note.type !== 'code') {
|
|
||||||
return [400, `Note type ${note.type} cannot be exported as single markdown file.`];
|
|
||||||
}
|
|
||||||
|
|
||||||
let markdown;
|
|
||||||
|
|
||||||
if (note.type === 'code') {
|
|
||||||
markdown = '```\n' + note.content + "\n```";
|
|
||||||
}
|
|
||||||
else if (note.type === 'text') {
|
|
||||||
const turndownService = new TurndownService();
|
|
||||||
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 = {
|
|
||||||
exportSingleMarkdown
|
|
||||||
};
|
|
@ -1,91 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const tar = require('tar-stream');
|
|
||||||
const TurndownService = require('turndown');
|
|
||||||
const sanitize = require("sanitize-filename");
|
|
||||||
const markdownSingleExportService = require('../../services/export/markdown_single');
|
|
||||||
|
|
||||||
async function exportToMarkdown(branch, res) {
|
|
||||||
const note = await branch.getNote();
|
|
||||||
|
|
||||||
if (!await note.hasChildren()) {
|
|
||||||
await markdownSingleExportService.exportSingleMarkdown(note, res);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const turndownService = new TurndownService();
|
|
||||||
const pack = tar.pack();
|
|
||||||
const name = await exportNoteInner(note, '');
|
|
||||||
|
|
||||||
async function exportNoteInner(note, directory) {
|
|
||||||
const childFileName = directory + sanitize(note.title);
|
|
||||||
|
|
||||||
if (await note.hasLabel('excludeFromExport')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveNote(childFileName, note);
|
|
||||||
|
|
||||||
const childNotes = await note.getChildNotes();
|
|
||||||
|
|
||||||
if (childNotes.length > 0) {
|
|
||||||
saveDirectory(childFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const childNote of childNotes) {
|
|
||||||
await exportNoteInner(childNote, childFileName + "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
return childFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveTextNote(childFileName, note) {
|
|
||||||
if (note.content.trim().length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let markdown;
|
|
||||||
|
|
||||||
if (note.type === 'code') {
|
|
||||||
markdown = '```\n' + note.content + "\n```";
|
|
||||||
}
|
|
||||||
else if (note.type === 'text') {
|
|
||||||
markdown = turndownService.turndown(note.content);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// other note types are not supported
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pack.entry({name: childFileName + ".md", size: markdown.length}, markdown);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFileNote(childFileName, note) {
|
|
||||||
pack.entry({name: childFileName, size: note.content.length}, note.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveNote(childFileName, note) {
|
|
||||||
if (note.type === 'text' || note.type === 'code') {
|
|
||||||
saveTextNote(childFileName, note);
|
|
||||||
}
|
|
||||||
else if (note.type === 'image' || note.type === 'file') {
|
|
||||||
saveFileNote(childFileName, note);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDirectory(childFileName) {
|
|
||||||
pack.entry({name: childFileName, type: 'directory'});
|
|
||||||
}
|
|
||||||
|
|
||||||
pack.finalize();
|
|
||||||
|
|
||||||
res.setHeader('Content-Disposition', 'file; filename="' + name + '.tar"');
|
|
||||||
res.setHeader('Content-Type', 'application/tar');
|
|
||||||
|
|
||||||
pack.pipe(res);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
exportToMarkdown
|
|
||||||
};
|
|
57
src/services/export/single.js
Normal file
57
src/services/export/single.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const sanitize = require("sanitize-filename");
|
||||||
|
const TurndownService = require('turndown');
|
||||||
|
const mimeTypes = require('mime-types');
|
||||||
|
const html = require('html');
|
||||||
|
|
||||||
|
async function exportSingleNote(branch, format, res) {
|
||||||
|
const note = await branch.getNote();
|
||||||
|
|
||||||
|
if (note.type === 'image' || note.type === 'file') {
|
||||||
|
return [400, `Note type ${note.type} cannot be exported as single file.`];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format !== 'html' && format !== 'markdown') {
|
||||||
|
return [400, 'Unrecognized format ' + format];
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload, extension, mime;
|
||||||
|
|
||||||
|
if (note.type === 'text') {
|
||||||
|
if (format === 'html') {
|
||||||
|
payload = html.prettyPrint(note.content, {indent_size: 2});
|
||||||
|
extension = 'html';
|
||||||
|
mime = 'text/html';
|
||||||
|
}
|
||||||
|
else if (format === 'markdown') {
|
||||||
|
const turndownService = new TurndownService();
|
||||||
|
payload = turndownService.turndown(note.content);
|
||||||
|
extension = 'md';
|
||||||
|
mime = 'text/markdown'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (note.type === 'code') {
|
||||||
|
payload = note.content;
|
||||||
|
extension = mimeTypes.extension(note.mime) || 'code';
|
||||||
|
mime = note.mime;
|
||||||
|
}
|
||||||
|
else if (note.type === 'relation-map' || note.type === 'search') {
|
||||||
|
payload = note.content;
|
||||||
|
extension = 'json';
|
||||||
|
mime = 'application/json';
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = sanitize(note.title);
|
||||||
|
|
||||||
|
console.log(name, extension, mime);
|
||||||
|
|
||||||
|
res.setHeader('Content-Disposition', `file; filename="${name}.${extension}"`);
|
||||||
|
res.setHeader('Content-Type', mime + '; charset=UTF-8');
|
||||||
|
|
||||||
|
res.send(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
exportSingleNote
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const html = require('html');
|
const html = require('html');
|
||||||
const native_tar = require('tar-stream');
|
const tar = require('tar-stream');
|
||||||
const sanitize = require("sanitize-filename");
|
const sanitize = require("sanitize-filename");
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
const TurndownService = require('turndown');
|
const TurndownService = require('turndown');
|
||||||
@ -12,7 +12,7 @@ const TurndownService = require('turndown');
|
|||||||
async function exportToTar(branch, format, res) {
|
async function exportToTar(branch, format, res) {
|
||||||
const turndownService = new TurndownService();
|
const turndownService = new TurndownService();
|
||||||
|
|
||||||
const pack = native_tar.pack();
|
const pack = tar.pack();
|
||||||
|
|
||||||
const exportedNoteIds = [];
|
const exportedNoteIds = [];
|
||||||
const name = await exportNoteInner(branch, '');
|
const name = await exportNoteInner(branch, '');
|
||||||
@ -79,7 +79,7 @@ async function exportToTar(branch, format, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const childBranch of childBranches) {
|
for (const childBranch of childBranches) {
|
||||||
await exportNoteInner(childBranch, childFileName + "/");
|
await exportNoteInner(await childBranch.getNote(), childBranch, childFileName + "/");
|
||||||
}
|
}
|
||||||
|
|
||||||
return childFileName;
|
return childFileName;
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-markdown"
|
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-markdown"
|
||||||
value="markdown-tar">
|
value="markdown">
|
||||||
<label class="form-check-label" for="export-subtree-format-markdown">
|
<label class="form-check-label" for="export-subtree-format-markdown">
|
||||||
Markdown - this preserves most of the formatting.
|
Markdown - this preserves most of the formatting.
|
||||||
</label>
|
</label>
|
||||||
@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-markdown"
|
<input class="form-check-input" type="radio" name="export-single-format" id="export-single-format-markdown"
|
||||||
value="markdown-tar">
|
value="markdown">
|
||||||
<label class="form-check-label" for="export-single-format-markdown">
|
<label class="form-check-label" for="export-single-format-markdown">
|
||||||
Markdown - this preserves most of the formatting.
|
Markdown - this preserves most of the formatting.
|
||||||
</label>
|
</label>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user