Skip to content

Commit

Permalink
saving export meta file per directory
Browse files Browse the repository at this point in the history
  • Loading branch information
zadam committed Sep 10, 2023
1 parent c881b39 commit 5b85713
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 50 deletions.
135 changes: 87 additions & 48 deletions src/services/export/zip.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const archiver = require('archiver');
const log = require("../log");
const TaskContext = require("../task_context");
const ValidationError = require("../../errors/validation_error");
const MetaFile = require("../meta/meta_file");
const NoteMeta = require("../meta/note_meta");
const AttachmentMeta = require("../meta/attachment_meta");
const AttributeMeta = require("../meta/attribute_meta");
Expand All @@ -39,6 +40,24 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
/** @type {Object.<string, NoteMeta>} */
const noteIdToMeta = {};

/** @type {Object.<string, NoteMeta>} */
const childNoteIdToParentMeta = {};

/** @type {Object.<string, NoteMeta[]>} */
const parentNoteIdToChildrenMeta = {};

function getNotePath(noteId) {
const notePath = [noteId];

let cur = noteIdToMeta[noteId];
while (cur = childNoteIdToParentMeta[cur.noteId]) {
notePath.push(cur.noteId);
}

notePath.reverse();
return notePath;
}

/**
* @param {Object.<string, int>} existingFileNames
* @param {string} fileName
Expand Down Expand Up @@ -116,13 +135,49 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
return getUniqueFilename(existingFileNames, fileName);
}

/**
* @param {BBranch[]} branches
* @param {Object.<string, int>} existingFileNames
* @returns {MetaFile}
*/
function createMetaFileForDirectory(branches, existingFileNames = {}) {
// namespace is shared by children in the same note
const metas = [];

for (const branch of branches) {
const noteMeta = createNoteMeta(branch, existingFileNames);

// can be undefined if export is disabled for this note
if (noteMeta) {
metas.push(noteMeta);

const parentMeta = noteIdToMeta[branch.parentNoteId];

if (parentMeta) {
childNoteIdToParentMeta[noteMeta.noteId] = parentMeta;
parentNoteIdToChildrenMeta[parentMeta.noteId] = parentNoteIdToChildrenMeta[parentMeta.noteId] || [];
parentNoteIdToChildrenMeta[parentMeta.noteId].push(noteMeta);
}
}
}

if (!metas.length) {
return null;
}

const metaFile = new MetaFile();
metaFile.formatVersion = 3;
metaFile.appVersion = packageInfo.version;
metaFile.files = metas;
return metaFile;
}

/**
* @param {BBranch} branch
* @param {NoteMeta} parentMeta
* @param {Object.<string, int>} existingFileNames
* @returns {NoteMeta|null}
*/
function createNoteMeta(branch, parentMeta, existingFileNames) {
function createNoteMeta(branch, existingFileNames) {
const note = branch.getNote();

if (note.hasOwnedLabel('excludeFromExport')) {
Expand All @@ -137,15 +192,13 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
baseFileName = baseFileName.substr(0, 200);
}

const notePath = parentMeta.notePath.concat([note.noteId]);

if (note.noteId in noteIdToMeta) {
const fileName = getUniqueFilename(existingFileNames, `${baseFileName}.clone.${format === 'html' ? 'html' : 'md'}`);
const fileName = getUniqueFilename(existingFileNames,
`${baseFileName}.clone.${format === 'html' ? 'html' : 'md'}`);

const meta = new NoteMeta();
meta.isClone = true;
meta.noteId = note.noteId;
meta.notePath = notePath;
meta.title = note.getTitleOrProtected();
meta.prefix = branch.prefix;
meta.dataFileName = fileName;
Expand All @@ -157,7 +210,6 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
const meta = new NoteMeta();
meta.isClone = false;
meta.noteId = note.noteId;
meta.notePath = notePath;
meta.title = note.getTitleOrProtected();
meta.notePosition = branch.notePosition;
meta.prefix = branch.prefix;
Expand Down Expand Up @@ -212,19 +264,6 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)

if (childBranches.length > 0) {
meta.dirFileName = getUniqueFilename(existingFileNames, baseFileName);
meta.children = [];

// namespace is shared by children in the same note
const childExistingNames = {};

for (const childBranch of childBranches) {
const note = createNoteMeta(childBranch, meta, childExistingNames);

// can be undefined if export is disabled for this note
if (note) {
meta.children.push(note);
}
}
}

return meta;
Expand All @@ -242,8 +281,8 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
return null;
}

const targetPath = targetMeta.notePath.slice();
const sourcePath = sourceMeta.notePath.slice();
const targetPath = getNotePath(targetMeta.noteId);
const sourcePath = getNotePath(sourceMeta.noteId);

// > 1 for the edge case that targetPath and sourcePath are exact same (a link to itself)
while (targetPath.length > 1 && sourcePath.length > 1 && targetPath[0] === sourcePath[0]) {
Expand Down Expand Up @@ -328,7 +367,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)

if (noteMeta.format === 'html') {
if (!content.substr(0, 100).toLowerCase().includes("<html")) {
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`;
const cssUrl = `${"../".repeat(getNotePath(noteMeta.noteId).length - 1)}style.css`;
const htmlTitle = utils.escapeHtml(title);

// <base> element will make sure external links are openable - https://github.com/zadam/trilium/issues/1289#issuecomment-704066809
Expand Down Expand Up @@ -409,14 +448,17 @@ ${markdownContent}`;
});
}

if (noteMeta.children?.length > 0) {
const directoryPath = filePathPrefix + noteMeta.dirFileName;
const metaFile = createMetaFileForDirectory(note.getChildBranches());
if (metaFile) {
const childFilePathPrefix = `${filePathPrefix}${noteMeta.dirFileName}/`;

// create directory
archive.append('', { name: `${directoryPath}/`, date: dateUtils.parseDateTime(note.utcDateModified) });
archive.append('', { name: childFilePathPrefix, date: dateUtils.parseDateTime(note.utcDateModified) });

metaFile.save(archive, childFilePathPrefix);

for (const childMeta of noteMeta.children) {
saveNote(childMeta, `${directoryPath}/`);
for (const childMeta of metaFile.files) {
saveNote(childMeta, childFilePathPrefix);
}
}
}
Expand All @@ -440,11 +482,13 @@ ${markdownContent}`;
html += escapedTitle;
}

if (meta.children && meta.children.length > 0) {
const childrenMeta = parentNoteIdToChildrenMeta[meta.noteId];

if (childrenMeta?.length > 0) {
html += '<ul>';

for (const child of meta.children) {
html += saveNavigationInner(child);
for (const childMeta of childrenMeta) {
html += saveNavigationInner(childMeta);
}

html += '</ul>'
Expand Down Expand Up @@ -482,8 +526,10 @@ ${markdownContent}`;
firstNonEmptyNote = getNoteTargetUrl(curMeta.noteId, rootMeta);
}

if (curMeta.children && curMeta.children.length > 0) {
curMeta = curMeta.children[0];
const childrenMeta = parentNoteIdToChildrenMeta[curMeta.noteId];

if (childrenMeta?.length > 0) {
curMeta = childrenMeta[0];
}
else {
break;
Expand Down Expand Up @@ -515,14 +561,13 @@ ${markdownContent}`;
archive.append(cssContent, { name: cssMeta.dataFileName });
}

const existingFileNames = format === 'html' ? ['navigation', 'index'] : [];
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
const existingFileNames = format === 'html' ? { 'navigation': 1, 'index': 1 } : {};
const metaFile = createMetaFileForDirectory([branch], existingFileNames);

const metaFile = {
formatVersion: 2,
appVersion: packageInfo.version,
files: [ rootMeta ]
};
if (!metaFile) { // corner case of disabled export for exported note
res.sendStatus(400);
return;
}

let navigationMeta, indexMeta, cssMeta;

Expand Down Expand Up @@ -565,15 +610,9 @@ ${markdownContent}`;
});
}

if (!rootMeta) { // corner case of disabled export for exported note
res.sendStatus(400);
return;
}

const metaFileJson = JSON.stringify(metaFile, null, '\t');

archive.append(metaFileJson, { name: "!!!meta.json" });
metaFile.save(archive);

const rootMeta = metaFile.files[0];
saveNote(rootMeta, '');

if (format === 'html') {
Expand Down
16 changes: 16 additions & 0 deletions src/services/meta/meta_file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class MetaFile {
/** @type {int} */
formatVersion;
/** @type {string} */
appVersion;
/** @type {NoteMeta[]} */
files;

save(archive, filePathPrefix = '') {
const metaFileJson = JSON.stringify(this, null, '\t');

archive.append(metaFileJson, { name: filePathPrefix + "!!!meta.json" });
}
}

module.exports = MetaFile;
10 changes: 8 additions & 2 deletions src/services/meta/note_meta.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
class NoteMeta {
/** @type {string} */
noteId;
/** @type {string} */
/**
* @type {string[]}
* @deprecated unused as of v3
*/
notePath;
/** @type {boolean} */
isClone;
Expand Down Expand Up @@ -29,7 +32,10 @@ class NoteMeta {
attributes;
/** @type {AttachmentMeta[]} */
attachments;
/** @type {NoteMeta[]|undefined} */
/**
* @type {NoteMeta[]|undefined}
* @deprecated unused as of v3, there's meta file for each directory, avoiding the need to use this
*/
children;
}

Expand Down

0 comments on commit 5b85713

Please sign in to comment.