diff --git a/apps/web/src/app.css b/apps/web/src/app.css index c3ef75d27..1c132f0c5 100644 --- a/apps/web/src/app.css +++ b/apps/web/src/app.css @@ -115,13 +115,13 @@ textarea, } .rpv-core__text-layer-text::selection { - background-color: var(--background-selected) !important; + background-color: var(--accent-selected) !important; color: transparent; } .rpv-core__text-layer-text::-moz-selection { /* Code for Firefox */ - background-color: var(--background-selected) !important; + background-color: var(--accent-selected) !important; color: transparent; } diff --git a/apps/web/src/common/attachments.ts b/apps/web/src/common/attachments.tsx similarity index 82% rename from apps/web/src/common/attachments.ts rename to apps/web/src/common/attachments.tsx index 493c26b26..d27a66859 100644 --- a/apps/web/src/common/attachments.ts +++ b/apps/web/src/common/attachments.tsx @@ -22,6 +22,10 @@ import { logger } from "../utils/logger"; import { showToast } from "../utils/toast"; import { db } from "./db"; import { checkUpload, decryptFile, saveFile } from "../interfaces/fs"; +import { ScopedThemeProvider } from "../components/theme-provider"; +import { Lightbox } from "../components/lightbox"; +import ReactDOM from "react-dom"; +import { Attachment } from "@notesnook/core"; async function download(hash: string, groupId?: string) { const attachment = await db.attachments.attachment(hash); @@ -127,3 +131,29 @@ export async function checkAttachment(hash: string) { } return { success: true }; } + +export async function previewImageAttachment(attachment: Attachment) { + const container = document.getElementById("dialogContainer"); + if (!(container instanceof HTMLElement)) return; + + const dataurl = await downloadAttachment( + attachment.hash, + "base64", + attachment.id + ); + if (!dataurl) { + return showToast("error", strings.imagePreviewFailed()); + } + + ReactDOM.render( + + { + ReactDOM.unmountComponentAtNode(container); + }} + /> + , + container + ); +} diff --git a/apps/web/src/components/attachment/index.tsx b/apps/web/src/components/attachment/index.tsx index 72709ffd0..f7be946e8 100644 --- a/apps/web/src/components/attachment/index.tsx +++ b/apps/web/src/components/attachment/index.tsx @@ -33,6 +33,7 @@ import { FileWebClip, Icon, Loading, + PasswordInvisible, References, Rename, Reupload, @@ -40,7 +41,11 @@ import { } from "../icons"; import { store, useStore } from "../../stores/attachment-store"; import { db } from "../../common/db"; -import { saveAttachment } from "../../common/attachments"; +import { + downloadAttachment, + previewImageAttachment, + saveAttachment +} from "../../common/attachments"; import { reuploadAttachment } from "../editor/picker"; import { Multiselect } from "../../common/multi-select"; import { Menu } from "../../hooks/use-menu"; @@ -59,6 +64,8 @@ import { PromptDialog } from "../../dialogs/prompt"; import { DialogManager } from "../../common/dialog-manager"; import { useStore as useSelectionStore } from "../../stores/selection-store"; import { strings } from "@notesnook/intl"; +import { showToast } from "../../utils/toast"; +import { PdfPreviewDialog } from "../../dialogs/pdf-preview-dialog"; const FILE_ICONS: Record = { "image/": FileImage, @@ -249,6 +256,34 @@ const AttachmentMenuItems: ( status?: AttachmentProgressStatus ) => MenuItem[] = (attachment, status) => { return [ + { + key: "preview-attachment", + type: "button", + title: strings.previewAttachment(), + icon: PasswordInvisible.path, + isHidden: + !attachment.mimeType.startsWith("image/") && + attachment.mimeType !== PDFMimeType, + onClick: async () => { + if (attachment.mimeType.startsWith("image")) { + await previewImageAttachment(attachment); + } else if (attachment.mimeType === PDFMimeType) { + const blob = await downloadAttachment( + attachment.hash, + "blob", + attachment.id + ); + if (!blob) { + return showToast("error", strings.attachmentPreviewFailed()); + } + + PdfPreviewDialog.show({ + url: URL.createObjectURL(blob), + hash: attachment.hash + }); + } + } + }, { key: "notes", type: "button", diff --git a/apps/web/src/components/dialog/index.tsx b/apps/web/src/components/dialog/index.tsx index e4396a038..2216f9b31 100644 --- a/apps/web/src/components/dialog/index.tsx +++ b/apps/web/src/components/dialog/index.tsx @@ -44,6 +44,7 @@ type DialogProps = SxProp & { ) => void; onOpen?: () => void; width?: number | string; + height?: number | string; showCloseButton?: boolean; textAlignment?: "left" | "right" | "center"; buttonsAlignment?: "start" | "center" | "end"; @@ -98,7 +99,7 @@ function BaseDialog(props: React.PropsWithChildren) { display: "flex", flexDirection: "column", width: ["100%", "90%", props.width || "380px"], - maxHeight: ["100%", "80%", "70%"], + maxHeight: ["100%", "80%", props.height || "70%"], height: ["100%", "auto", "auto"], bg: "background", alignSelf: "center", diff --git a/apps/web/src/components/editor/index.tsx b/apps/web/src/components/editor/index.tsx index 20c2ac5eb..1dc6bf684 100644 --- a/apps/web/src/components/editor/index.tsx +++ b/apps/web/src/components/editor/index.tsx @@ -26,7 +26,6 @@ import React, { useLayoutEffect, useCallback } from "react"; -import ReactDOM from "react-dom"; import { Box, Button, Flex, Progress, Text } from "@theme-ui/components"; import Properties from "../properties"; import { @@ -53,13 +52,16 @@ import Header from "./header"; import { Attachment } from "../icons"; import { attachFiles, AttachmentProgress, insertAttachments } from "./picker"; import { useEditorManager } from "./manager"; -import { saveAttachment, downloadAttachment } from "../../common/attachments"; +import { + saveAttachment, + downloadAttachment, + previewImageAttachment +} from "../../common/attachments"; import { EV, EVENTS } from "@notesnook/core"; import { db } from "../../common/db"; import Titlebox, { resizeTextarea } from "./title-box"; import Config from "../../utils/config"; import { ScopedThemeProvider } from "../theme-provider"; -import { Lightbox } from "../lightbox"; import { showToast } from "../../utils/toast"; import { Item, MaybeDeletedItem, isDeleted } from "@notesnook/core"; import { debounce, debounceWithId } from "@notesnook/common"; @@ -571,24 +573,7 @@ export function Editor(props: EditorProps) { const { hash, type } = data; const attachment = await db.attachments.attachment(hash); if (attachment && type === "image") { - const container = document.getElementById("dialogContainer"); - if (!(container instanceof HTMLElement)) return; - - const dataurl = await downloadAttachment(hash, "base64", id); - if (!dataurl) - return showToast("error", strings.imagePreviewFailed()); - - ReactDOM.render( - - { - ReactDOM.unmountComponentAtNode(container); - }} - /> - , - container - ); + await previewImageAttachment(attachment); } else if ( attachment && onPreviewDocument && diff --git a/apps/web/src/dialogs/pdf-preview-dialog.tsx b/apps/web/src/dialogs/pdf-preview-dialog.tsx new file mode 100644 index 000000000..688c95507 --- /dev/null +++ b/apps/web/src/dialogs/pdf-preview-dialog.tsx @@ -0,0 +1,55 @@ +/* +This file is part of the Notesnook project (https://notesnook.com/) + +Copyright (C) 2023 Streetwriters (Private) Limited + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +import React, { Suspense } from "react"; +import { BaseDialogProps, DialogManager } from "../common/dialog-manager"; +import Dialog from "../components/dialog"; +import { Loading } from "../components/icons"; + +const PdfPreview = React.lazy(() => import("../components/pdf-preview")); + +type PdfPreviewDialogProps = BaseDialogProps & { + url: string; + hash: string; +}; + +export const PdfPreviewDialog = DialogManager.register( + function PdfPreviewDialog({ onClose, url, hash }: PdfPreviewDialogProps) { + return ( + onClose(false)} + > + }> + onClose(false)} + /> + + + ); + } +);