diff --git a/apps/web/src/common/attachments.js b/apps/web/src/common/attachments.ts similarity index 59% rename from apps/web/src/common/attachments.js rename to apps/web/src/common/attachments.ts index 50add2ed4..e0f54cc7f 100644 --- a/apps/web/src/common/attachments.js +++ b/apps/web/src/common/attachments.ts @@ -20,8 +20,8 @@ along with this program. If not, see . import FS from "../interfaces/fs"; import { db } from "./db"; -export async function downloadAttachment(hash) { - const attachment = db.attachments.attachment(hash); +async function download(hash: string) { + const attachment = db.attachments?.attachment(hash); if (!attachment) return; const downloadResult = await db.fs.downloadFile( attachment.metadata.hash, @@ -31,9 +31,17 @@ export async function downloadAttachment(hash) { ); if (!downloadResult) throw new Error("Failed to download file."); - const key = await db.attachments.decryptKey(attachment.key); + const key = await db.attachments?.decryptKey(attachment.key); if (!key) throw new Error("Invalid key for attachment."); + return { key, attachment }; +} + +export async function saveAttachment(hash: string) { + const response = await download(hash); + if (!response) return; + + const { attachment, key } = response; await FS.saveFile(attachment.metadata.hash, { key, iv: attachment.iv, @@ -43,46 +51,51 @@ export async function downloadAttachment(hash) { }); } -export async function downloadAttachmentForPreview(hash) { - const attachment = db.attachments.attachment(hash); - if (!attachment) return; - const downloadResult = await db.fs.downloadFile( - attachment.metadata.hash, - attachment.metadata.hash, - attachment.chunkSize, - attachment.metadata - ); - if (!downloadResult) throw new Error("Failed to download file."); +type OutputTypeToReturnType = { + blob: Blob; + base64: string; + text: string; +}; +export async function downloadAttachment< + TType extends "blob" | "base64" | "text", + TOutputType = OutputTypeToReturnType[TType] +>(hash: string, type: TType): Promise { + const response = await download(hash); + if (!response) return; + const { attachment, key } = response; - const key = await db.attachments.decryptKey(attachment.key); - if (!key) throw new Error("Invalid key for attachment."); + if (type === "base64" || type === "text") + return (await db.attachments?.read(hash, type)) as TOutputType; - return await FS.decryptFile(attachment.metadata.hash, { + const blob = await FS.decryptFile(attachment.metadata.hash, { key, iv: attachment.iv, name: attachment.metadata.filename, type: attachment.metadata.type, isUploaded: !!attachment.dateUploaded }); + + if (!blob) return; + return blob as TOutputType; } -export async function checkAttachment(hash) { - const attachment = db.attachments.attachment(hash); +export async function checkAttachment(hash: string) { + const attachment = db.attachments?.attachment(hash); if (!attachment) return { failed: "Attachment not found." }; try { const size = await FS.getUploadedFileSize(hash); if (size <= 0) return { failed: "File length is 0." }; } catch (e) { - return { failed: e.message }; + return { failed: e instanceof Error ? e.message : "Unknown error." }; } return { success: true }; } const ABYTES = 17; -export function getTotalSize(attachments) { +export function getTotalSize(attachments: any[]) { let size = 0; - for (let attachment of attachments) { + for (const attachment of attachments) { size += attachment.length + ABYTES; } return size; diff --git a/apps/web/src/components/attachment/index.tsx b/apps/web/src/components/attachment/index.tsx index e0ac818d9..5e63f6df0 100644 --- a/apps/web/src/components/attachment/index.tsx +++ b/apps/web/src/components/attachment/index.tsx @@ -47,7 +47,7 @@ import { } from "../../common/dialog-controller"; import { store } from "../../stores/attachment-store"; import { db } from "../../common/db"; -import { downloadAttachment } from "../../common/attachments"; +import { saveAttachment } from "../../common/attachments"; import { reuploadAttachment } from "../editor/picker"; import { Multiselect } from "../../common/multi-select"; import { Menu } from "../../hooks/use-menu"; @@ -303,7 +303,7 @@ const AttachmentMenuItems: MenuItem[] = [ const isDownloading = status?.type === "download"; if (isDownloading) { await db.fs.cancel(attachment.metadata.hash, "download"); - } else await downloadAttachment(attachment.metadata.hash); + } else await saveAttachment(attachment.metadata.hash); } }, { diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx index 843b4d371..890f5f37f 100644 --- a/apps/web/src/components/editor/index.tsx +++ b/apps/web/src/components/editor/index.tsx @@ -41,10 +41,7 @@ import Header from "./header"; import { Attachment } from "../icons"; import { useEditorInstance } from "./context"; import { attachFile, AttachmentProgress, insertAttachment } from "./picker"; -import { - downloadAttachment, - downloadAttachmentForPreview -} from "../../common/attachments"; +import { saveAttachment, downloadAttachment } from "../../common/attachments"; import { EV, EVENTS } from "@notesnook/core/common"; import { db } from "../../common/db"; import useMobile from "../../hooks/use-mobile"; @@ -57,6 +54,7 @@ import { Lightbox } from "../lightbox"; import ThemeProviderWrapper from "../theme-provider"; import { Allotment } from "allotment"; import { PdfPreview } from "../pdf-preview"; +import { showToast } from "../../utils/toast"; type PreviewSession = { content: { data: string; type: string }; @@ -408,14 +406,17 @@ export function Editor(props: EditorProps) { }} onContentChange={onContentChange} onChange={onEditorChange} - onDownloadAttachment={(attachment) => - downloadAttachment(attachment.hash) - } + onDownloadAttachment={(attachment) => saveAttachment(attachment.hash)} onPreviewAttachment={async ({ hash, dataurl }) => { const attachment = db.attachments?.attachment(hash); if (attachment && attachment.metadata.type.startsWith("image/")) { const container = document.getElementById("dialogContainer"); if (!(container instanceof HTMLElement)) return; + + dataurl = dataurl || (await downloadAttachment(hash, "base64")); + if (!dataurl) + return showToast("error", "This image cannot be previewed."); + ReactDOM.render( ({ }, hash: getDataAttribute("hash"), filename: getDataAttribute("filename"), - type: getDataAttribute("mime"), + mime: getDataAttribute("mime"), size: getDataAttribute("size") }; }, diff --git a/packages/editor/src/extensions/image/component.tsx b/packages/editor/src/extensions/image/component.tsx index 03e149fff..521446a45 100644 --- a/packages/editor/src/extensions/image/component.tsx +++ b/packages/editor/src/extensions/image/component.tsx @@ -83,7 +83,7 @@ export function ImageComponent( setSource(url); editor.current?.commands.updateImage( { src }, - { src: await toDataURL(blob), size, type } + { src: await toDataURL(blob), size, mime: type } ); } } catch (e) { diff --git a/packages/editor/src/extensions/image/image.ts b/packages/editor/src/extensions/image/image.ts index 0af9f1182..24e2ffa4e 100644 --- a/packages/editor/src/extensions/image/image.ts +++ b/packages/editor/src/extensions/image/image.ts @@ -138,7 +138,7 @@ export const ImageNode = Node.create({ hash: getDataAttribute("hash"), filename: getDataAttribute("filename"), - type: getDataAttribute("mime"), + mime: getDataAttribute("mime"), size: getDataAttribute("size"), aspectRatio: { default: undefined, @@ -209,6 +209,7 @@ export const ImageNode = Node.create({ insertImage: (options) => ({ commands }) => { + console.log(options); return commands.insertContent({ type: this.name, attrs: options diff --git a/packages/editor/src/toolbar/popups/image-upload.tsx b/packages/editor/src/toolbar/popups/image-upload.tsx index 9fd2d0032..9b7800b3c 100644 --- a/packages/editor/src/toolbar/popups/image-upload.tsx +++ b/packages/editor/src/toolbar/popups/image-upload.tsx @@ -53,7 +53,7 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) { url, downloadOptions ); - onInsert({ src: await toDataURL(blob), size, type }); + onInsert({ src: await toDataURL(blob), size, mime: type }); } catch (e) { if (e instanceof Error) setError(e.message); } finally { diff --git a/packages/editor/src/toolbar/tools/attachment.tsx b/packages/editor/src/toolbar/tools/attachment.tsx index c09de5154..68d066f19 100644 --- a/packages/editor/src/toolbar/tools/attachment.tsx +++ b/packages/editor/src/toolbar/tools/attachment.tsx @@ -61,15 +61,7 @@ export function DownloadAttachment(props: ToolProps) { /> ); } -const previewableFileExtensions = ["pdf"]; -function canPreviewAttachment(attachment: Attachment) { - if (!attachment) return false; - const extension = attachment.filename?.split(".").pop(); - if (!extension) return false; - - return previewableFileExtensions.indexOf(extension) > -1; -} export function PreviewAttachment(props: ToolProps) { const { editor } = props; const attachmentNode = @@ -105,3 +97,17 @@ export function RemoveAttachment(props: ToolProps) { /> ); } + +const previewableFileExtensions = ["pdf"]; +const previewableMimeTypes = ["application/pdf"]; + +function canPreviewAttachment(attachment: Attachment) { + if (!attachment) return false; + if (previewableMimeTypes.some((mime) => attachment.mime.startsWith(mime))) + return true; + + const extension = attachment.filename?.split(".").pop(); + if (!extension) return false; + + return previewableFileExtensions.indexOf(extension) > -1; +}