This commit is contained in:
Philipinho
2026-03-28 16:58:09 +00:00
parent d8ec0472ef
commit 477c8ace52
4 changed files with 97 additions and 33 deletions
@@ -8,13 +8,10 @@ import {
} from "@/features/editor/components/table/types/types.ts";
import { ActionIcon, Tooltip } from "@mantine/core";
import {
IconDownload,
IconPaperclip,
IconTrash,
} from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import { getFileUrl } from "@/lib/config.ts";
import { isInternalFileUrl } from "@docmost/editor-ext";
import classes from "../common/toolbar-menu.module.css";
export function PdfMenu({ editor }: EditorMenuProps) {
@@ -40,11 +37,15 @@ export function PdfMenu({ editor }: EditorMenuProps) {
const shouldShow = useCallback(
({ state }: ShouldShowProps) => {
if (!state) {
if (!state || !editor.isActive("pdf")) {
return false;
}
return editor.isActive("pdf") && editor.getAttributes("pdf").src;
const { selection } = state;
const dom = editor.view.nodeDOM(selection.from) as HTMLElement | null;
if (!dom) return false;
return !!dom.querySelector("[data-pdf-error]");
},
[editor],
);
@@ -71,15 +72,6 @@ export function PdfMenu({ editor }: EditorMenuProps) {
};
}, [editor]);
const handleDownload = useCallback(() => {
if (!editorState?.src || !isInternalFileUrl(editorState.src)) return;
const url = getFileUrl(editorState.src);
const a = document.createElement("a");
a.href = url;
a.download = "";
a.click();
}, [editorState?.src]);
const handleConvertToAttachment = useCallback(() => {
if (!editorState?.src) return;
@@ -124,17 +116,6 @@ export function PdfMenu({ editor }: EditorMenuProps) {
shouldShow={shouldShow}
>
<div className={classes.toolbar}>
<Tooltip position="top" label={t("Download")} withinPortal={false}>
<ActionIcon
onClick={handleDownload}
size="lg"
aria-label={t("Download")}
variant="subtle"
>
<IconDownload size={18} />
</ActionIcon>
</Tooltip>
<Tooltip position="top" label={t("Convert to attachment")} withinPortal={false}>
<ActionIcon
onClick={handleConvertToAttachment}
@@ -52,10 +52,32 @@
border-radius: 8px;
}
.clickOverlay {
.hoverMenu {
position: absolute;
inset: 0;
cursor: pointer;
top: 56px;
right: 8px;
z-index: 2;
display: flex;
gap: 4px;
padding: 4px;
border-radius: 6px;
opacity: 0;
transition: opacity 0.15s ease;
background-color: rgba(0, 0, 0, 0.5);
}
.hoverMenu::before {
content: "";
position: absolute;
inset: -12px;
}
.hoverMenu:hover {
opacity: 1;
}
.pdfResizeWrapper:hover .hoverMenu {
opacity: 1;
}
.pdfError {
@@ -1,5 +1,5 @@
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
import { Group, Loader, Text } from "@mantine/core";
import { ActionIcon, Group, Loader, Text, Tooltip } from "@mantine/core";
import { useCallback, useMemo, useState } from "react";
import { getFileUrl } from "@/lib/config.ts";
import { ResizableWrapper } from "../common/resizable-wrapper";
@@ -7,7 +7,11 @@ import clsx from "clsx";
import classes from "./pdf-view.module.css";
import { useTranslation } from "react-i18next";
import { isInternalFileUrl } from "@docmost/editor-ext";
import { IconFileTypePdf } from "@tabler/icons-react";
import {
IconFileTypePdf,
IconPaperclip,
IconTrash,
} from "@tabler/icons-react";
export default function PdfView(props: NodeViewProps) {
const { t } = useTranslation();
@@ -34,6 +38,38 @@ export default function PdfView(props: NodeViewProps) {
[updateAttributes],
);
const handleConvertToAttachment = useCallback(() => {
if (!src) return;
const pos = getPos();
if (pos === undefined) return;
const currentNode = editor.state.doc.nodeAt(pos);
if (!currentNode || currentNode.type.name !== "pdf") return;
editor
.chain()
.insertContentAt(
{ from: pos, to: pos + currentNode.nodeSize },
{
type: "attachment",
attrs: {
url: currentNode.attrs.src,
name: currentNode.attrs.name,
attachmentId: currentNode.attrs.attachmentId,
size: currentNode.attrs.size,
mime: "application/pdf",
},
},
)
.run();
}, [editor, src, getPos]);
const handleDelete = useCallback(() => {
const pos = getPos();
if (pos === undefined) return;
editor.commands.setNodeSelection(pos);
editor.commands.deleteSelection();
}, [editor, getPos]);
if (!src || !safeSrc) {
return (
<NodeViewWrapper data-drag-handle>
@@ -54,7 +90,7 @@ export default function PdfView(props: NodeViewProps) {
if (hasError) {
return (
<NodeViewWrapper data-drag-handle>
<div className={classes.pdfError} onClick={handleSelect}>
<div data-pdf-error className={clsx(classes.pdfError, { "ProseMirror-selectednode": selected })} onClick={handleSelect}>
<IconFileTypePdf size={32} stroke={1.5} />
<Text size="sm" c="dimmed">
{t("Failed to load PDF")}
@@ -99,7 +135,32 @@ export default function PdfView(props: NodeViewProps) {
}
}}
/>
{!selected && <div className={classes.clickOverlay} onClick={handleSelect} />}
{editor.isEditable && (
<div className={classes.hoverMenu}>
<Tooltip position="top" label={t("Convert to attachment")} withinPortal>
<ActionIcon
size="sm"
variant="filled"
color="dark"
onClick={handleConvertToAttachment}
aria-label={t("Convert to attachment")}
>
<IconPaperclip size={14} />
</ActionIcon>
</Tooltip>
<Tooltip position="top" label={t("Delete")} withinPortal>
<ActionIcon
size="sm"
variant="filled"
color="dark"
onClick={handleDelete}
aria-label={t("Delete")}
>
<IconTrash size={14} />
</ActionIcon>
</Tooltip>
</div>
)}
</ResizableWrapper>
</div>
</NodeViewWrapper>
@@ -427,7 +427,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
editor.chain().focus().deleteRange(range).setDrawio().run(),
},
{
title: "Excalidraw diagram (Whiteboard)",
title: "Excalidraw (Whiteboard)",
description: "Draw and sketch excalidraw diagrams",
searchTerms: ["diagrams", "draw", "sketch", "whiteboard"],
icon: IconExcalidraw,