From db57c125d6e8a008f0bc3e01fe403b1c59941cb5 Mon Sep 17 00:00:00 2001 From: Abdullah Atta Date: Mon, 18 Mar 2024 08:19:28 +0500 Subject: [PATCH] web: use new export utility from @notesnook/common --- apps/web/src/common/export.ts | 103 ++++++++------------ apps/web/src/components/note/index.tsx | 32 +++--- apps/web/src/utils/streams/export-stream.ts | 89 ++++++++--------- 3 files changed, 101 insertions(+), 123 deletions(-) diff --git a/apps/web/src/common/export.ts b/apps/web/src/common/export.ts index 416d9f0a2..3565bd13b 100644 --- a/apps/web/src/common/export.ts +++ b/apps/web/src/common/export.ts @@ -18,17 +18,19 @@ along with this program. If not, see . */ 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 { FilteredSelector } from "@notesnook/core/dist/database/sql-collection"; -import { Note, isDeleted } from "@notesnook/core"; +import { Note } from "@notesnook/core"; import { fromAsyncIterator } from "../utils/stream"; -import { db } from "./db"; -import { ExportOptions } from "@notesnook/core/dist/collections/notes"; -import { showToast } from "../utils/toast"; -import { sanitizeFilename } from "@notesnook/common"; +import { + sanitizeFilename, + exportNotes as _exportNotes, + exportNote as _exportNote, + exportContent +} from "@notesnook/common"; import Vault from "./vault"; +import { ExportStream } from "../utils/streams/export-stream"; export async function exportToPDF( title: string, @@ -82,24 +84,10 @@ export async function exportNotes( title: "Exporting notes", subtitle: "Please wait while your notes are exported.", action: async (report) => { - const total = await notes.count(); - let progress = 0; - - const noteStream = fromAsyncIterator(notes[Symbol.asyncIterator]()); - await noteStream - .pipeThrough( - new TransformStream({ - transform(note, controller) { - controller.enqueue(note); - report({ - total, - current: progress++, - text: `Exporting "${note?.title}"` - }); - } - }) - ) - .pipeThrough(new ExportStream(format, undefined)) + await fromAsyncIterator( + _exportNotes(notes, { format, unlockVault: Vault.unlockVault }) + ) + .pipeThrough(new ExportStream(report)) .pipeThrough(createZipStream()) .pipeTo(await createWriteStream("notes.zip")); return true; @@ -117,46 +105,39 @@ const FORMAT_TO_EXT = { export async function exportNote( note: Note, - options: Omit & { + options: { format: keyof typeof FORMAT_TO_EXT; } ) { - if ( - !db.vault.unlocked && - (await db.vaults.itemExists(note)) && - !(await Vault.unlockVault()) - ) { - showToast("error", `Skipping note "${note.title}" as it is locked.`); - return false; - } - const rawContent = note.contentId - ? await db.content.get(note.contentId) - : undefined; - - const content = - rawContent && - !isDeleted(rawContent) && - (rawContent.locked - ? await db.vault.decryptContent(rawContent, note.id) - : rawContent); - - const exported = await db.notes - .export(note.id, { - ...options, - format: options.format === "pdf" ? "html" : options.format, - contentItem: content || undefined - }) - .catch((e: Error) => { - showToast("error", `Failed to export note "${note.title}": ${e.message}`); - return false as const; + if (options.format === "pdf") { + const content = await exportContent(note, { + format: "pdf", + unlockVault: Vault.unlockVault }); + if (!content) return false; + console.log(content); + return await exportToPDF(note.title, content); + } - if (typeof exported === "boolean" && !exported) return false; - - const filename = sanitizeFilename(note.title, { replacement: "-" }); - const ext = FORMAT_TO_EXT[options.format]; - return { - filename: [filename, ext].join("."), - content: exported - }; + return await TaskManager.startTask({ + type: "modal", + title: `Exporting "${note.title}"`, + subtitle: "Please wait while your note is exported.", + action: async (report) => { + await fromAsyncIterator( + _exportNote(note, { + format: options.format, + unlockVault: Vault.unlockVault + }) + ) + .pipeThrough(new ExportStream(report)) + .pipeThrough(createZipStream()) + .pipeTo( + await createWriteStream( + `${sanitizeFilename(note.title, { replacement: "-" })}.zip` + ) + ); + return true; + } + }); } diff --git a/apps/web/src/components/note/index.tsx b/apps/web/src/components/note/index.tsx index 4030f6f49..b18ad11fb 100644 --- a/apps/web/src/components/note/index.tsx +++ b/apps/web/src/components/note/index.tsx @@ -71,7 +71,7 @@ import { showToast } from "../../utils/toast"; import { navigate } from "../../navigation"; import { showPublishView } from "../publish-view"; import IconTag from "../icon-tag"; -import { exportNote, exportNotes, exportToPDF } from "../../common/export"; +import { exportNote, exportNotes } from "../../common/export"; import { Multiselect } from "../../common/multi-select"; import { store as selectionStore } from "../../stores/selection-store"; import { @@ -80,6 +80,7 @@ import { } from "@notesnook/core/dist/collections/reminders"; import { NoteResolvedData, + exportContent, getFormattedReminderTime, pluralize } from "@notesnook/common"; @@ -93,8 +94,8 @@ import { import { MenuItem } from "@notesnook/ui"; import { Context } from "../list-container/types"; import { SchemeColors } from "@notesnook/theme"; -import FileSaver from "file-saver"; import { writeToClipboard } from "../../utils/clipboard"; +import Vault from "../../common/vault"; type NoteProps = NoteResolvedData & { item: NoteType; @@ -422,9 +423,9 @@ const menuItems: ( //isDisabled: !isSynced, icon: Print.path, onClick: async () => { - const result = await exportNote(note, { format: "pdf" }); - if (!result) return; - await exportToPDF(note.title, result.content); + await exportNote(note, { + format: "pdf" + }); } }, { @@ -458,18 +459,13 @@ const menuItems: ( isDisabled: format.type === "pdf" && ids.length > 1, // ? "Multiple notes cannot be exported as PDF." // : false, + multiSelect: true, isPro: format.type !== "txt", onClick: async () => { if (ids.length === 1) { - const result = await exportNote(note, { format: format.type }); - if (!result) return; - if (format.type === "pdf") - return exportToPDF(note.title, result.content); - - return FileSaver.saveAs( - new Blob([new TextEncoder().encode(result.content)]), - result.filename - ); + return await exportNote(note, { + format: format.type + }); } await exportNotes( @@ -791,10 +787,14 @@ async function copyNote(noteId: string, format: "md" | "txt") { const note = await db.notes?.note(noteId); if (!note) throw new Error("No note with this id exists."); - const result = await exportNote(note, { format, disableTemplate: true }); + const result = await exportContent(note, { + format, + disableTemplate: true, + unlockVault: Vault.unlockVault + }); if (!result) throw new Error(`Could not convert note to ${format}.`); - await navigator.clipboard.writeText(result.content); + await navigator.clipboard.writeText(result); showToast("success", "Copied!"); } catch (e) { if (e instanceof Error) diff --git a/apps/web/src/utils/streams/export-stream.ts b/apps/web/src/utils/streams/export-stream.ts index f8fee1279..6f7df5fe6 100644 --- a/apps/web/src/utils/streams/export-stream.ts +++ b/apps/web/src/utils/streams/export-stream.ts @@ -17,57 +17,54 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +import { ExportableItem } from "@notesnook/common"; import { db } from "../../common/db"; -import { exportNote } from "../../common/export"; -import { makeUniqueFilename } from "./utils"; +import { showToast } from "../toast"; import { ZipFile } from "./zip-stream"; -import { Note } from "@notesnook/core"; - -export class ExportStream extends TransformStream { - constructor( - format: "pdf" | "md" | "txt" | "html" | "md-frontmatter", - signal?: AbortSignal - ) { - const counters: Record = {}; +import { lazify } from "../lazify"; +export class ExportStream extends TransformStream< + ExportableItem | Error, + ZipFile +> { + constructor(report: (progress: { text: string; current?: number }) => void) { + let progress = 0; super({ - async transform(note, controller) { - try { - if (signal?.aborted) { - controller.terminate(); - return; - } - - if (format === "pdf") return; - - const result = await exportNote(note, { format }); - if (!result) return; - - const { filename, content } = result; - - const notebooks = await db.relations - .to({ id: note.id, type: "note" }, "notebook") - .get(); - - const filePaths: string[] = []; - for (const { fromId: notebookId } of notebooks) { - const crumbs = (await db.notebooks.breadcrumbs(notebookId)).map( - (n) => n.title - ); - filePaths.push([...crumbs, filename].join("/")); - } - if (filePaths.length <= 0) filePaths.push(filename); - - filePaths.forEach((filePath) => { - controller.enqueue({ - path: makeUniqueFilename(filePath, counters), - data: content, - mtime: new Date(note.dateEdited), - ctime: new Date(note.dateCreated) - }); + async transform(item, controller) { + if (item instanceof Error) { + showToast("error", item.message); + return; + } + if (item.type === "attachment") { + report({ text: `Downloading attachment: ${item.path}` }); + await db + .fs() + .downloadFile("exports", item.data.hash, item.data.chunkSize); + const key = await db.attachments.decryptKey(item.data.key); + if (!key) return; + const stream = await lazify( + import("../../interfaces/fs"), + ({ streamingDecryptFile }) => + streamingDecryptFile(item.data.hash, { + key, + iv: item.data.iv, + name: item.data.filename, + type: item.data.mimeType, + isUploaded: !!item.data.dateUploaded + }) + ); + if (!stream) return; + controller.enqueue({ ...item, data: stream }); + report({ + current: progress++, + text: `Saving attachment: ${item.path}` + }); + } else { + controller.enqueue(item); + report({ + current: progress++, + text: `Exporting note: ${item.path}` }); - } catch (e) { - controller.error(e); } } });