mirror of
https://github.com/zadam/trilium.git
synced 2024-11-10 17:13:45 +08:00
export subtrees in ZIP instead of TAR format because of more widespread support (especially unicode)
This commit is contained in:
parent
ff426fc1fc
commit
45e2dc96fb
5 changed files with 175 additions and 30 deletions
131
package-lock.json
generated
131
package-lock.json
generated
|
@ -803,6 +803,43 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"archiver-utils": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz",
|
||||
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==",
|
||||
"requires": {
|
||||
"glob": "^7.1.4",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"lazystream": "^1.0.0",
|
||||
"lodash.defaults": "^4.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"lodash.flatten": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.union": "^4.6.0",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"are-we-there-yet": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
|
||||
|
@ -1780,6 +1817,24 @@
|
|||
"integrity": "sha1-AWLsLZNR9d3VmpICy6k1NmpyUIA=",
|
||||
"dev": true
|
||||
},
|
||||
"compress-commons": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-2.1.1.tgz",
|
||||
"integrity": "sha512-eVw6n7CnEMFzc3duyFVrQEuY1BlHR3rYsSztyG32ibGMW722i3C6IizEGMFmfMU+A+fALvBIwxN3czffTcdA+Q==",
|
||||
"requires": {
|
||||
"buffer-crc32": "^0.2.13",
|
||||
"crc32-stream": "^3.0.1",
|
||||
"normalize-path": "^3.0.0",
|
||||
"readable-stream": "^2.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
@ -1898,6 +1953,35 @@
|
|||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"crc": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
|
||||
"integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==",
|
||||
"requires": {
|
||||
"buffer": "^5.1.0"
|
||||
}
|
||||
},
|
||||
"crc32-stream": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-3.0.1.tgz",
|
||||
"integrity": "sha512-mctvpXlbzsvK+6z8kJwSJ5crm7yBwrQMTybJzMw1O4lLGJqjlDCXY2Zw7KheiA6XBEcBmfLx1D88mjRGVJtY9w==",
|
||||
"requires": {
|
||||
"crc": "^3.4.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"create-error-class": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz",
|
||||
|
@ -5614,6 +5698,16 @@
|
|||
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
"integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw="
|
||||
},
|
||||
"lodash.difference": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
|
||||
"integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw="
|
||||
},
|
||||
"lodash.escape": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz",
|
||||
|
@ -5622,6 +5716,11 @@
|
|||
"lodash._root": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
"integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8="
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
|
@ -5648,6 +5747,11 @@
|
|||
"resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
|
||||
"integrity": "sha1-+4m2WpqAKBgz8LdHizpRBPiY67M="
|
||||
},
|
||||
"lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
|
||||
},
|
||||
"lodash.keys": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz",
|
||||
|
@ -5693,6 +5797,11 @@
|
|||
"lodash.escape": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lodash.union": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
|
||||
"integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg="
|
||||
},
|
||||
"log-symbols": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
|
||||
|
@ -10310,6 +10419,28 @@
|
|||
"buffer-crc32": "~0.2.3",
|
||||
"fd-slicer": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"zip-stream": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz",
|
||||
"integrity": "sha512-EkXc2JGcKhO5N5aZ7TmuNo45budRaFGHOmz24wtJR7znbNqDPmdZtUauKX6et8KAVseAMBOyWJqEpXcHTBsh7Q==",
|
||||
"requires": {
|
||||
"archiver-utils": "^2.1.0",
|
||||
"compress-commons": "^2.1.1",
|
||||
"readable-stream": "^3.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"readable-stream": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
|
||||
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@
|
|||
"turndown": "6.0.0",
|
||||
"turndown-plugin-gfm": "1.0.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "7.2.3"
|
||||
"ws": "7.2.3",
|
||||
"zip-stream": "^2.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "9.0.0-beta.9",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use strict";
|
||||
|
||||
const tarExportService = require('../../services/export/tar');
|
||||
const zipExportService = require('../../services/export/tar');
|
||||
const singleExportService = require('../../services/export/single');
|
||||
const opmlExportService = require('../../services/export/opml');
|
||||
const repository = require("../../services/repository");
|
||||
|
@ -15,7 +15,7 @@ async function exportBranch(req, res) {
|
|||
|
||||
try {
|
||||
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
|
||||
await tarExportService.exportToTar(taskContext, branch, format, res);
|
||||
await zipExportService.exportToZip(taskContext, branch, format, res);
|
||||
}
|
||||
else if (type === 'single') {
|
||||
await singleExportService.exportSingleNote(taskContext, branch, format, res);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
const html = require('html');
|
||||
const repository = require('../repository');
|
||||
const dateUtils = require('../date_utils');
|
||||
const tar = require('tar-stream');
|
||||
const zip = require('tar-stream');
|
||||
const path = require('path');
|
||||
const mimeTypes = require('mime-types');
|
||||
const mdService = require('./md');
|
||||
|
@ -13,17 +13,30 @@ const protectedSessionService = require('../protected_session');
|
|||
const sanitize = require("sanitize-filename");
|
||||
const fs = require("fs");
|
||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
||||
const ZipStream = require('zip-stream');
|
||||
|
||||
/**
|
||||
* @param {TaskContext} taskContext
|
||||
* @param {Branch} branch
|
||||
* @param {string} format - 'html' or 'markdown'
|
||||
*/
|
||||
async function exportToTar(taskContext, branch, format, res) {
|
||||
const pack = tar.pack();
|
||||
async function exportToZip(taskContext, branch, format, res) {
|
||||
const packer = new ZipStream();
|
||||
|
||||
const noteIdToMeta = {};
|
||||
|
||||
async function addEntry(source, data) {
|
||||
return new Promise((res, rej) => {
|
||||
packer.entry(source, data, (err, entry) => {
|
||||
if (err) {
|
||||
rej(err);
|
||||
}
|
||||
|
||||
res(entry);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function getUniqueFilename(existingFileNames, fileName) {
|
||||
const lcFileName = fileName.toLowerCase();
|
||||
|
||||
|
@ -265,7 +278,8 @@ ${content}
|
|||
|
||||
content = prepareContent(noteMeta.title, content, noteMeta);
|
||||
|
||||
pack.entry({name: filePathPrefix + noteMeta.dataFileName, size: content.length}, content);
|
||||
await addEntry(content, {name: filePathPrefix + noteMeta.dataFileName});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -276,11 +290,10 @@ ${content}
|
|||
if (noteMeta.dataFileName) {
|
||||
const content = prepareContent(noteMeta.title, await note.getContent(), noteMeta);
|
||||
|
||||
pack.entry({
|
||||
await addEntry(content, {
|
||||
name: filePathPrefix + noteMeta.dataFileName,
|
||||
size: content.length,
|
||||
mtime: dateUtils.parseDateTime(note.utcDateModified)
|
||||
}, content);
|
||||
date: dateUtils.parseDateTime(note.utcDateModified)
|
||||
});
|
||||
}
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
@ -288,10 +301,10 @@ ${content}
|
|||
if (noteMeta.children && noteMeta.children.length > 0) {
|
||||
const directoryPath = filePathPrefix + noteMeta.dirFileName;
|
||||
|
||||
pack.entry({
|
||||
await addEntry(null,{
|
||||
name: directoryPath,
|
||||
type: 'directory',
|
||||
mtime: dateUtils.parseDateTime(note.utcDateModified)
|
||||
date: dateUtils.parseDateTime(note.utcDateModified)
|
||||
});
|
||||
|
||||
for (const childMeta of noteMeta.children) {
|
||||
|
@ -300,7 +313,7 @@ ${content}
|
|||
}
|
||||
}
|
||||
|
||||
function saveNavigation(rootMeta, navigationMeta) {
|
||||
async function saveNavigation(rootMeta, navigationMeta) {
|
||||
function saveNavigationInner(meta) {
|
||||
let html = '<li>';
|
||||
|
||||
|
@ -339,10 +352,10 @@ ${content}
|
|||
</html>`;
|
||||
const prettyHtml = html.prettyPrint(fullHtml, {indent_size: 2});
|
||||
|
||||
pack.entry({name: navigationMeta.dataFileName, size: prettyHtml.length}, prettyHtml);
|
||||
await addEntry(prettyHtml, {name: navigationMeta.dataFileName});
|
||||
}
|
||||
|
||||
function saveIndex(rootMeta, indexMeta) {
|
||||
async function saveIndex(rootMeta, indexMeta) {
|
||||
let firstNonEmptyNote;
|
||||
let curMeta = rootMeta;
|
||||
|
||||
|
@ -370,13 +383,13 @@ ${content}
|
|||
</frameset>
|
||||
</html>`;
|
||||
|
||||
pack.entry({name: indexMeta.dataFileName, size: fullHtml.length}, fullHtml);
|
||||
await addEntry(fullHtml, {name: indexMeta.dataFileName});
|
||||
}
|
||||
|
||||
function saveCss(rootMeta, cssMeta) {
|
||||
async function saveCss(rootMeta, cssMeta) {
|
||||
const cssContent = fs.readFileSync(RESOURCE_DIR + '/libraries/ckeditor/ckeditor-content.css');
|
||||
|
||||
pack.entry({name: cssMeta.dataFileName, size: cssContent.length}, cssContent);
|
||||
await addEntry(cssContent, {name: cssMeta.dataFileName});
|
||||
}
|
||||
|
||||
const existingFileNames = format === 'html' ? ['navigation', 'index'] : [];
|
||||
|
@ -425,29 +438,29 @@ ${content}
|
|||
|
||||
const metaFileJson = JSON.stringify(metaFile, null, '\t');
|
||||
|
||||
pack.entry({name: "!!!meta.json", size: metaFileJson.length}, metaFileJson);
|
||||
await addEntry(metaFileJson, {name: "!!!meta.json"});
|
||||
|
||||
await saveNote(rootMeta, '');
|
||||
|
||||
if (format === 'html') {
|
||||
saveNavigation(rootMeta, navigationMeta);
|
||||
saveIndex(rootMeta, indexMeta);
|
||||
saveCss(rootMeta, cssMeta);
|
||||
await saveNavigation(rootMeta, navigationMeta);
|
||||
await saveIndex(rootMeta, indexMeta);
|
||||
await saveCss(rootMeta, cssMeta);
|
||||
}
|
||||
|
||||
pack.finalize();
|
||||
packer.finalize();
|
||||
|
||||
const note = await branch.getNote();
|
||||
const tarFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".tar";
|
||||
const zipFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".zip";
|
||||
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(tarFileName));
|
||||
res.setHeader('Content-Type', 'application/tar');
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));
|
||||
res.setHeader('Content-Type', 'application/zip');
|
||||
|
||||
pack.pipe(res);
|
||||
packer.pipe(res);
|
||||
|
||||
taskContext.taskSucceeded();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
exportToTar
|
||||
exportToZip
|
||||
};
|
|
@ -18,7 +18,7 @@
|
|||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="export-subtree-format" id="export-subtree-format-html"
|
||||
value="html">
|
||||
<label class="form-check-label" for="export-subtree-format-html">HTML in TAR archive - this is recommended since this preserves all the formatting.</label>
|
||||
<label class="form-check-label" for="export-subtree-format-html">HTML in ZIP archive - this is recommended since this preserves all the formatting.</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
|
|
Loading…
Reference in a new issue