mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
web: fix pdf exports
This commit is contained in:
committed by
Abdullah Atta
parent
a20a683b17
commit
05cc947cb4
@@ -21,6 +21,7 @@ import { TaskManager } from "./task-manager";
|
||||
import { ExportStream } from "../utils/streams/export-stream";
|
||||
import { createZipStream } from "../utils/streams/zip-stream";
|
||||
import { createWriteStream } from "../utils/stream-saver";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
export async function exportToPDF(
|
||||
title: string,
|
||||
@@ -74,12 +75,17 @@ export async function exportNotes(
|
||||
title: "Exporting notes",
|
||||
subtitle: "Please wait while your notes are exported.",
|
||||
action: async (report) => {
|
||||
await new ExportStream(noteIds, format, undefined, (c, text) =>
|
||||
report({ total: noteIds.length, current: c, text })
|
||||
)
|
||||
.pipeThrough(createZipStream())
|
||||
.pipeTo(await createWriteStream("notes.zip"));
|
||||
return true;
|
||||
try {
|
||||
await new ExportStream(noteIds, format, undefined, (c, text) =>
|
||||
report({ total: noteIds.length, current: c, text })
|
||||
)
|
||||
.pipeThrough(createZipStream())
|
||||
.pipeTo(await createWriteStream("notes.zip"));
|
||||
return true;
|
||||
} catch (e) {
|
||||
if (e instanceof Error) showToast("error", e.message);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ export class ExportStream extends ReadableStream<ZipFile> {
|
||||
signal?: AbortSignal,
|
||||
onProgress?: (current: number, text: string) => void
|
||||
) {
|
||||
const textEncoder = new TextEncoder();
|
||||
let index = 0;
|
||||
const counters: Record<string, number> = {};
|
||||
let vaultUnlocked = false;
|
||||
@@ -60,103 +59,99 @@ export class ExportStream extends ReadableStream<ZipFile> {
|
||||
}
|
||||
},
|
||||
async pull(controller) {
|
||||
if (signal?.aborted) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (signal?.aborted) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const note = db.notes?.note(noteIds[index++]);
|
||||
if (!note) return;
|
||||
if (!vaultUnlocked && note.data.locked) return;
|
||||
const note = db.notes?.note(noteIds[index++]);
|
||||
if (!note) return;
|
||||
if (!vaultUnlocked && note.data.locked) return;
|
||||
|
||||
onProgress && onProgress(index, `Exporting "${note.title}"...`);
|
||||
onProgress && onProgress(index, `Exporting "${note.title}"...`);
|
||||
|
||||
const rawContent = await db.content?.raw(note.data.contentId);
|
||||
const content = note.data.locked
|
||||
? await db.vault?.decryptContent(rawContent)
|
||||
: rawContent;
|
||||
const rawContent = await db.content?.raw(note.data.contentId);
|
||||
const content = note.data.locked
|
||||
? await db.vault?.decryptContent(rawContent)
|
||||
: rawContent;
|
||||
|
||||
const exported = await note
|
||||
.export(format === "pdf" ? "html" : format, content)
|
||||
.catch((e: Error) => {
|
||||
console.error(note.data, e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to export note "${note.title}": ${e.message}`
|
||||
);
|
||||
const exported = await note
|
||||
.export(format === "pdf" ? "html" : format, content)
|
||||
.catch((e: Error) => {
|
||||
console.error(note.data, e);
|
||||
showToast(
|
||||
"error",
|
||||
`Failed to export note "${note.title}": ${e.message}`
|
||||
);
|
||||
});
|
||||
|
||||
if (typeof exported !== "string") {
|
||||
showToast("error", `Failed to export note "${note.title}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === "pdf") {
|
||||
await exportToPDF(note.title, exported);
|
||||
controller.error("PDF export.");
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = sanitizeFilename(note.title, { replacement: "-" });
|
||||
const ext = FORMAT_TO_EXT[format];
|
||||
const filenameWithExtension = [filename, ext].join(".");
|
||||
|
||||
const notebooks = [
|
||||
...(db.relations?.to({ id: note.id, type: "note" }, "notebook") ||
|
||||
[]),
|
||||
...(note.notebooks || []).map(
|
||||
(ref: { id: string; topics: string[] }) => {
|
||||
const notebook = db.notebooks?.notebook(ref.id);
|
||||
const topics: any[] = notebook?.topics.all || [];
|
||||
|
||||
return {
|
||||
title: notebook?.title,
|
||||
topics: ref.topics
|
||||
.map((topicId: string) =>
|
||||
topics.find((topic) => topic.id === topicId)
|
||||
)
|
||||
.filter(Boolean)
|
||||
};
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
const filePaths: Array<string> =
|
||||
notebooks.length > 0
|
||||
? notebooks
|
||||
.map((notebook) => {
|
||||
if (notebook.topics.length > 0)
|
||||
return notebook.topics.map((topic: { title: string }) =>
|
||||
[
|
||||
notebook.title,
|
||||
topic.title,
|
||||
filenameWithExtension
|
||||
].join("/")
|
||||
);
|
||||
return [notebook.title, filenameWithExtension].join("/");
|
||||
})
|
||||
.flat()
|
||||
: [filenameWithExtension];
|
||||
|
||||
filePaths.forEach((filePath) => {
|
||||
controller.enqueue({
|
||||
path: makeUniqueFilename(filePath, counters),
|
||||
data: exported,
|
||||
mtime: new Date(note.data.dateEdited),
|
||||
ctime: new Date(note.data.dateCreated)
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof exported !== "string") {
|
||||
showToast("error", `Failed to export note "${note.title}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === "pdf") {
|
||||
await exportToPDF(note.title, exported);
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = sanitizeFilename(note.title, { replacement: "-" });
|
||||
const ext = FORMAT_TO_EXT[format];
|
||||
const notebooks = db.relations
|
||||
?.to({ id: note.id, type: "note" }, "notebook")
|
||||
.map((notebook) => {
|
||||
return { title: notebook.title, topics: Array<string> };
|
||||
});
|
||||
const notebooksWithTopics: Array<{
|
||||
id: string;
|
||||
topics: Array<string>;
|
||||
}> = note?.notebooks;
|
||||
|
||||
if (notebooksWithTopics)
|
||||
notebooks?.push(
|
||||
...notebooksWithTopics.map((_notebook) => {
|
||||
const notebook = db.notebooks?.notebook(_notebook.id);
|
||||
const _topics = notebook?.topics.all;
|
||||
let topics: any;
|
||||
|
||||
_notebook.topics.map((topicId: string) => {
|
||||
topics = _topics?.filter((topic) => {
|
||||
return topic.id === topicId;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
title: notebook?.title,
|
||||
topics: topics?.map((topic) => topic.title)
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const filenameWithExtension = [filename, ext].join(".").toLowerCase();
|
||||
const filePaths: Array<string> = [];
|
||||
|
||||
if (notebooks && notebooks.length > 0) {
|
||||
notebooks.forEach((notebook) => {
|
||||
if (notebook.topics.length > 0)
|
||||
notebook.topics.forEach((topic) => {
|
||||
filePaths.push(
|
||||
`/${notebook.title}/${topic}/${filenameWithExtension}`
|
||||
);
|
||||
});
|
||||
else filePaths.push(`/${notebook.title}/${filenameWithExtension}`);
|
||||
});
|
||||
} else {
|
||||
filePaths.push(filenameWithExtension);
|
||||
}
|
||||
|
||||
filePaths.forEach((filePath) => {
|
||||
controller.enqueue({
|
||||
path: makeUniqueFilename(filePath, counters),
|
||||
data: textEncoder.encode(exported),
|
||||
mtime: new Date(note.data.dateEdited),
|
||||
ctime: new Date(note.data.dateCreated)
|
||||
});
|
||||
});
|
||||
|
||||
if (index === noteIds.length) {
|
||||
controller.close();
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
} finally {
|
||||
if (index === noteIds.length) {
|
||||
controller.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -17,15 +17,19 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { path } from "@notesnook-importer/core/dist/src/utils/path";
|
||||
|
||||
export function makeUniqueFilename(
|
||||
filePath: string,
|
||||
counters: Record<string, number>
|
||||
) {
|
||||
counters[filePath] = (counters[filePath] || 0) + 1;
|
||||
if (counters[filePath] === 1) return filePath;
|
||||
const matchablePath = filePath.toLowerCase();
|
||||
const count = (counters[matchablePath] = (counters[matchablePath] || 0) + 1);
|
||||
if (count === 1) return filePath;
|
||||
|
||||
const parts = filePath.split(".");
|
||||
return `${parts.slice(0, -1).join(".")}-${counters[filePath]}.${
|
||||
parts[parts.length - 1]
|
||||
}`;
|
||||
const ext = path.extname(filePath);
|
||||
const basename = ext
|
||||
? `${path.basename(filePath, ext)}-${count}${ext}`
|
||||
: `${path.basename(filePath)}-${count}`;
|
||||
return path.join(path.dirname(filePath), basename);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Deflate, Inflate } from "./fflate-shim";
|
||||
import { Uint8ArrayReader, ZipWriter, configure } from "@zip.js/zip.js";
|
||||
import {
|
||||
Uint8ArrayReader,
|
||||
TextReader,
|
||||
ZipWriter,
|
||||
configure
|
||||
} from "@zip.js/zip.js";
|
||||
|
||||
configure({ Deflate, Inflate });
|
||||
|
||||
export type ZipFile = {
|
||||
path: string;
|
||||
data: Uint8Array;
|
||||
data: string | Uint8Array;
|
||||
mtime?: Date;
|
||||
ctime?: Date;
|
||||
};
|
||||
@@ -43,10 +48,16 @@ export function createZipStream(signal?: AbortSignal) {
|
||||
if (written.has(chunk.path)) return;
|
||||
|
||||
await writer
|
||||
.add(chunk.path, new Uint8ArrayReader(chunk.data), {
|
||||
creationDate: chunk.ctime,
|
||||
lastModDate: chunk.mtime
|
||||
})
|
||||
.add(
|
||||
chunk.path,
|
||||
typeof chunk.data === "string"
|
||||
? new TextReader(chunk.data)
|
||||
: new Uint8ArrayReader(chunk.data),
|
||||
{
|
||||
creationDate: chunk.ctime,
|
||||
lastModDate: chunk.mtime
|
||||
}
|
||||
)
|
||||
.catch(async (e) => {
|
||||
await ts.writable.abort(e);
|
||||
await ts.readable.cancel(e);
|
||||
|
||||
@@ -75,7 +75,7 @@ function sanitize(input: string, replacement: string) {
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
|
||||
return sanitized.slice(0, 254).toLowerCase();
|
||||
return sanitized.slice(0, 254);
|
||||
}
|
||||
|
||||
export function sanitizeFilename(
|
||||
|
||||
@@ -83,6 +83,8 @@ export default class Content extends Collection {
|
||||
}
|
||||
|
||||
async raw(id) {
|
||||
if (!id) return;
|
||||
|
||||
const content = await this._collection.getItem(id);
|
||||
if (!content) return;
|
||||
return content;
|
||||
@@ -118,6 +120,7 @@ export default class Content extends Collection {
|
||||
async downloadMedia(groupId, contentItem, notify = true) {
|
||||
if (!contentItem) return contentItem;
|
||||
const content = getContentFromData(contentItem.type, contentItem.data);
|
||||
if (!content) console.log(contentItem);
|
||||
contentItem.data = await content.insertMedia(async (hashes) => {
|
||||
const attachments = hashes.map((h) => this._db.attachments.attachment(h));
|
||||
await this._db.fs.queueDownloads(
|
||||
|
||||
Reference in New Issue
Block a user