* first pass at updating all Time components each second * fix a couple date variable typos * use class style state management instead of hooks * wip: Separate title from body * address feedback * test: Remove unused test * feat: You in publishing info language fix: Removal of secondary headings * After much deliberation… a migration is needed for this to be reliable * fix: Export to work with new title structure * fix: Untitled * fix: Consistent spacing of first editor node * fix: Emoji in title handling * fix: Time component not updating for new props * chore: Add createdAt case * fix: Conflict after merging new TOC * PR feedback * lint * fix: Heading level adjustment Co-authored-by: Taylor Lapeyre <taylorlapeyre@gmail.com>
81 lines
2.1 KiB
JavaScript
81 lines
2.1 KiB
JavaScript
// @flow
|
|
import fs from 'fs';
|
|
import JSZip from 'jszip';
|
|
import tmp from 'tmp';
|
|
import * as Sentry from '@sentry/node';
|
|
import { Attachment, Collection, Document } from '../models';
|
|
import { getImageByKey } from './s3';
|
|
|
|
async function addToArchive(zip, documents) {
|
|
for (const doc of documents) {
|
|
const document = await Document.findByPk(doc.id);
|
|
let text = document.toMarkdown();
|
|
|
|
const attachments = await Attachment.findAll({
|
|
where: { documentId: document.id },
|
|
});
|
|
|
|
for (const attachment of attachments) {
|
|
await addImageToArchive(zip, attachment.key);
|
|
text = text.replace(attachment.redirectUrl, encodeURI(attachment.key));
|
|
}
|
|
|
|
zip.file(`${document.title || 'Untitled'}.md`, text);
|
|
|
|
if (doc.children && doc.children.length) {
|
|
const folder = zip.folder(document.title);
|
|
await addToArchive(folder, doc.children);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function addImageToArchive(zip, key) {
|
|
try {
|
|
const img = await getImageByKey(key);
|
|
zip.file(key, img, { createFolders: true });
|
|
} catch (err) {
|
|
if (process.env.NODE_ENV === 'production') {
|
|
Sentry.captureException(err);
|
|
} else {
|
|
// error during file retrieval
|
|
console.error(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function archiveToPath(zip) {
|
|
return new Promise((resolve, reject) => {
|
|
tmp.file({ prefix: 'export-', postfix: '.zip' }, (err, path) => {
|
|
if (err) return reject(err);
|
|
|
|
zip
|
|
.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
|
|
.pipe(fs.createWriteStream(path))
|
|
.on('finish', () => resolve(path))
|
|
.on('error', reject);
|
|
});
|
|
});
|
|
}
|
|
|
|
export async function archiveCollection(collection: Collection) {
|
|
const zip = new JSZip();
|
|
|
|
if (collection.documentStructure) {
|
|
await addToArchive(zip, collection.documentStructure);
|
|
}
|
|
|
|
return archiveToPath(zip);
|
|
}
|
|
|
|
export async function archiveCollections(collections: Collection[]) {
|
|
const zip = new JSZip();
|
|
|
|
for (const collection of collections) {
|
|
if (collection.documentStructure) {
|
|
const folder = zip.folder(collection.name);
|
|
await addToArchive(folder, collection.documentStructure);
|
|
}
|
|
}
|
|
return archiveToPath(zip);
|
|
}
|