diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index fc9e0717..68bef0f2 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -353,6 +353,11 @@ "Quote": "Quote.", "Image": "Image.", "Audio": "Audio.", + "Embed PDF": "Embed PDF", + "Upload and embed a PDF file.": "Upload and embed a PDF file.", + "Embed as PDF": "Embed as PDF", + "Failed to load PDF": "Failed to load PDF", + "Convert to attachment": "Convert to attachment", "File attachment": "File attachment.", "Toggle block": "Toggle block.", "Callout": "Callout.", diff --git a/apps/client/src/features/editor/components/attachment/attachment-view.tsx b/apps/client/src/features/editor/components/attachment/attachment-view.tsx index e3281e64..f6c13c80 100644 --- a/apps/client/src/features/editor/components/attachment/attachment-view.tsx +++ b/apps/client/src/features/editor/components/attachment/attachment-view.tsx @@ -1,17 +1,43 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; -import { Group, Text, Paper, ActionIcon, Loader } from "@mantine/core"; +import { Group, Text, Paper, ActionIcon, Loader, Tooltip } from "@mantine/core"; import { getFileUrl } from "@/lib/config.ts"; -import { IconDownload, IconPaperclip } from "@tabler/icons-react"; +import { IconDownload, IconFileTypePdf, IconPaperclip } from "@tabler/icons-react"; import { useHover } from "@mantine/hooks"; import { formatBytes } from "@/lib"; import { useTranslation } from "react-i18next"; +import { useCallback } from "react"; export default function AttachmentView(props: NodeViewProps) { const { t } = useTranslation(); - const { node, selected } = props; - const { url, name, size } = node.attrs; + const { editor, node, getPos, selected } = props; + const { url, name, size, mime, attachmentId } = node.attrs; const { hovered, ref } = useHover(); + const isPdf = mime === "application/pdf" || name?.toLowerCase().endsWith(".pdf"); + + const handleEmbedAsPdf = useCallback(() => { + const pos = getPos(); + if (pos === undefined || !url) return; + + const nodeSize = node.nodeSize; + + editor + .chain() + .insertContentAt( + { from: pos, to: pos + nodeSize }, + { + type: "pdf", + attrs: { + src: url, + name, + attachmentId, + size, + }, + }, + ) + .run(); + }, [editor, getPos, node, url, name, attachmentId]); + return ( @@ -39,11 +65,20 @@ export default function AttachmentView(props: NodeViewProps) { {url && (selected || hovered) && ( - - - - - + + {isPdf && editor.isEditable && ( + + + + + + )} + + + + + + )} diff --git a/apps/client/src/features/editor/components/audio/audio-view.tsx b/apps/client/src/features/editor/components/audio/audio-view.tsx index da08ccaa..a353ce45 100644 --- a/apps/client/src/features/editor/components/audio/audio-view.tsx +++ b/apps/client/src/features/editor/components/audio/audio-view.tsx @@ -2,6 +2,7 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; import { Group, Loader, Text } from "@mantine/core"; import { useMemo } from "react"; import { getFileUrl } from "@/lib/config.ts"; +import { isInternalFileUrl } from "@docmost/editor-ext"; import classes from "./audio-view.module.css"; import { useTranslation } from "react-i18next"; @@ -10,6 +11,11 @@ export default function AudioView(props: NodeViewProps) { const { editor, node } = props; const { src, placeholder } = node.attrs; + const safeSrc = useMemo(() => { + if (!src || !isInternalFileUrl(src)) return null; + return getFileUrl(src); + }, [src]); + const previewSrc = useMemo(() => { editor.storage.shared.audioPreviews = editor.storage.shared.audioPreviews || {}; @@ -23,16 +29,16 @@ export default function AudioView(props: NodeViewProps) { return ( -
- {src && ( +
+ {safeSrc && (