mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
Compare commits
22 Commits
17f3158a3b
...
editor-279
| Author | SHA1 | Date | |
|---|---|---|---|
| 1adbf97701 | |||
| 4ff67bb962 | |||
| 2e392da61a | |||
| 12e9cae65c | |||
| 477c8ace52 | |||
| d8ec0472ef | |||
| c2b41d72bf | |||
| 963ab5d7cb | |||
| bdde4a7178 | |||
| 2c35d2b3f4 | |||
| 7681894953 | |||
| bfaef88429 | |||
| c6bbb57406 | |||
| c599b6a9c1 | |||
| 4b99d89e55 | |||
| aabdc7264d | |||
| 4ebcbb71da | |||
| c07f348b38 | |||
| a5360ad341 | |||
| 795b79c2a2 | |||
| 6b2f8542c4 | |||
| 9f38c61882 |
@@ -341,6 +341,7 @@
|
|||||||
"Insert horizontal rule divider": "Insert horizontal rule divider",
|
"Insert horizontal rule divider": "Insert horizontal rule divider",
|
||||||
"Upload any image from your device.": "Upload any image from your device.",
|
"Upload any image from your device.": "Upload any image from your device.",
|
||||||
"Upload any video from your device.": "Upload any video from your device.",
|
"Upload any video from your device.": "Upload any video from your device.",
|
||||||
|
"Upload any audio from your device.": "Upload any audio from your device.",
|
||||||
"Upload any file from your device.": "Upload any file from your device.",
|
"Upload any file from your device.": "Upload any file from your device.",
|
||||||
"Uploading {{name}}": "Uploading {{name}}",
|
"Uploading {{name}}": "Uploading {{name}}",
|
||||||
"Uploading file": "Uploading file",
|
"Uploading file": "Uploading file",
|
||||||
@@ -351,6 +352,12 @@
|
|||||||
"Divider": "Divider.",
|
"Divider": "Divider.",
|
||||||
"Quote": "Quote.",
|
"Quote": "Quote.",
|
||||||
"Image": "Image.",
|
"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.",
|
"File attachment": "File attachment.",
|
||||||
"Toggle block": "Toggle block.",
|
"Toggle block": "Toggle block.",
|
||||||
"Callout": "Callout.",
|
"Callout": "Callout.",
|
||||||
@@ -723,5 +730,7 @@
|
|||||||
"Publish": "Publish.",
|
"Publish": "Publish.",
|
||||||
"Security": "Security.",
|
"Security": "Security.",
|
||||||
"Enforce SSO": "Enforce SSO.",
|
"Enforce SSO": "Enforce SSO.",
|
||||||
"Once enforced, members will not be able to login with email and password.": "Once enforced, members will not be able to log in with email and password."
|
"Once enforced, members will not be able to login with email and password.": "Once enforced, members will not be able to log in with email and password.",
|
||||||
|
"Uploading {{name}}": "Uploading {{name}}",
|
||||||
|
"Uploading file": "Uploading file"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ export function PageShareModal({ readOnly }: PageShareModalProps) {
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
variant="default"
|
variant="default"
|
||||||
onClick={open}
|
onClick={() => {
|
||||||
|
setActiveTab(isPubliclyShared ? "publish" : hasPagePermissions ? "access" : "publish");
|
||||||
|
open();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("Share")}
|
{t("Share")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,17 +1,43 @@
|
|||||||
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
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 { 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 { useHover } from "@mantine/hooks";
|
||||||
import { formatBytes } from "@/lib";
|
import { formatBytes } from "@/lib";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
export default function AttachmentView(props: NodeViewProps) {
|
export default function AttachmentView(props: NodeViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { node, selected } = props;
|
const { editor, node, getPos, selected } = props;
|
||||||
const { url, name, size } = node.attrs;
|
const { url, name, size, mime, attachmentId } = node.attrs;
|
||||||
const { hovered, ref } = useHover();
|
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 (
|
return (
|
||||||
<NodeViewWrapper>
|
<NodeViewWrapper>
|
||||||
<Paper withBorder p="4px" ref={ref} data-drag-handle>
|
<Paper withBorder p="4px" ref={ref} data-drag-handle>
|
||||||
@@ -39,11 +65,20 @@ export default function AttachmentView(props: NodeViewProps) {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
{url && (selected || hovered) && (
|
{url && (selected || hovered) && (
|
||||||
<a href={getFileUrl(url)} target="_blank">
|
<Group gap={4} wrap="nowrap" style={{ flexShrink: 0 }}>
|
||||||
<ActionIcon variant="default" aria-label="download file">
|
{isPdf && editor.isEditable && (
|
||||||
<IconDownload size={18} />
|
<Tooltip label={t("Embed as PDF")} position="top" withinPortal={false}>
|
||||||
</ActionIcon>
|
<ActionIcon variant="default" aria-label={t("Embed as PDF")} onClick={handleEmbedAsPdf}>
|
||||||
</a>
|
<IconFileTypePdf size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<a href={getFileUrl(url)} target="_blank">
|
||||||
|
<ActionIcon variant="default" aria-label="download file">
|
||||||
|
<IconDownload size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</a>
|
||||||
|
</Group>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
|
import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
|
import {
|
||||||
|
EditorMenuProps,
|
||||||
|
ShouldShowProps,
|
||||||
|
} from "@/features/editor/components/table/types/types.ts";
|
||||||
|
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconDownload,
|
||||||
|
IconTrash,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { getFileUrl } from "@/lib/config.ts";
|
||||||
|
import classes from "../common/toolbar-menu.module.css";
|
||||||
|
|
||||||
|
export function AudioMenu({ editor }: EditorMenuProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const editorState = useEditorState({
|
||||||
|
editor,
|
||||||
|
selector: (ctx) => {
|
||||||
|
if (!ctx.editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioAttrs = ctx.editor.getAttributes("audio");
|
||||||
|
|
||||||
|
return {
|
||||||
|
isAudio: ctx.editor.isActive("audio"),
|
||||||
|
src: audioAttrs?.src || null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldShow = useCallback(
|
||||||
|
({ state }: ShouldShowProps) => {
|
||||||
|
if (!state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return editor.isActive("audio") && editor.getAttributes("audio").src;
|
||||||
|
},
|
||||||
|
[editor],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getReferencedVirtualElement = useCallback(() => {
|
||||||
|
if (!editor) return;
|
||||||
|
const { selection } = editor.state;
|
||||||
|
const predicate = (node: PMNode) => node.type.name === "audio";
|
||||||
|
const parent = findParentNode(predicate)(selection);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement;
|
||||||
|
const domRect = dom.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => domRect,
|
||||||
|
getClientRects: () => [domRect],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const domRect = posToDOMRect(editor.view, selection.from, selection.to);
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => domRect,
|
||||||
|
getClientRects: () => [domRect],
|
||||||
|
};
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
const handleDownload = useCallback(() => {
|
||||||
|
if (!editorState?.src) return;
|
||||||
|
const url = getFileUrl(editorState.src);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = "";
|
||||||
|
a.click();
|
||||||
|
}, [editorState?.src]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
editor.commands.deleteSelection();
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseBubbleMenu
|
||||||
|
editor={editor}
|
||||||
|
pluginKey={`audio-menu`}
|
||||||
|
updateDelay={0}
|
||||||
|
getReferencedVirtualElement={getReferencedVirtualElement}
|
||||||
|
options={{
|
||||||
|
placement: "top",
|
||||||
|
offset: 8,
|
||||||
|
flip: false,
|
||||||
|
}}
|
||||||
|
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("Delete")} withinPortal={false}>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleDelete}
|
||||||
|
size="lg"
|
||||||
|
aria-label={t("Delete")}
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</BaseBubbleMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AudioMenu;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
.audioWrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
animation: pulse 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-gray-3) 0%, var(--mantine-color-gray-1) 50%, var(--mantine-color-gray-3) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-dark-6) 0%, var(--mantine-color-dark-5) 50%, var(--mantine-color-dark-6) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -135% 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
export default function AudioView(props: NodeViewProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
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 || {};
|
||||||
|
|
||||||
|
if (placeholder?.id) {
|
||||||
|
return editor.storage.shared.audioPreviews[placeholder.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [placeholder, editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeViewWrapper data-drag-handle>
|
||||||
|
<div className={`${classes.audioWrapper} ${!safeSrc ? classes.skeleton : ''}`}>
|
||||||
|
{safeSrc && (
|
||||||
|
<audio
|
||||||
|
className={classes.audio}
|
||||||
|
preload="metadata"
|
||||||
|
controls
|
||||||
|
src={safeSrc}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!safeSrc && previewSrc && (
|
||||||
|
<Group pos="relative" w="100%">
|
||||||
|
<audio
|
||||||
|
className={classes.audio}
|
||||||
|
preload="metadata"
|
||||||
|
controls
|
||||||
|
src={previewSrc}
|
||||||
|
/>
|
||||||
|
<Loader size={20} pos="absolute" top={6} right={6} />
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
{!safeSrc && !previewSrc && (
|
||||||
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md" h={54}>
|
||||||
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
|
<Text component="span" size="sm" truncate="end">
|
||||||
|
{placeholder?.name
|
||||||
|
? t("Uploading {{name}}", { name: placeholder.name })
|
||||||
|
: t("Uploading file")}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { handleAudioUpload } from "@docmost/editor-ext";
|
||||||
|
import { uploadFile } from "@/features/page/services/page-service.ts";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { getFileUploadSizeLimit } from "@/lib/config.ts";
|
||||||
|
import { formatBytes } from "@/lib";
|
||||||
|
import i18n from "@/i18n.ts";
|
||||||
|
|
||||||
|
export const uploadAudioAction = handleAudioUpload({
|
||||||
|
onUpload: async (file: File, pageId: string): Promise<any> => {
|
||||||
|
try {
|
||||||
|
return await uploadFile(file, pageId);
|
||||||
|
} catch (err) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
message: err?.response.data.message,
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateFn: (file) => {
|
||||||
|
if (!file.type.includes("audio/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > getFileUploadSizeLimit()) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
message: i18n.t("File exceeds the {{limit}} attachment limit", {
|
||||||
|
limit: formatBytes(getFileUploadSizeLimit()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
|
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
|
||||||
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
|
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
|
||||||
import { uploadAttachmentAction } from "../attachment/upload-attachment-action";
|
import { uploadAttachmentAction } from "../attachment/upload-attachment-action";
|
||||||
|
import { uploadPdfAction } from "../pdf/upload-pdf-action";
|
||||||
import { createMentionAction } from "@/features/editor/components/link/internal-link-paste.ts";
|
import { createMentionAction } from "@/features/editor/components/link/internal-link-paste.ts";
|
||||||
import { INTERNAL_LINK_REGEX } from "@/lib/constants.ts";
|
import { INTERNAL_LINK_REGEX } from "@/lib/constants.ts";
|
||||||
import { Editor } from "@tiptap/core";
|
import { Editor } from "@tiptap/core";
|
||||||
@@ -12,6 +13,8 @@ import {
|
|||||||
const ATTACHMENT_NODE_TYPES = [
|
const ATTACHMENT_NODE_TYPES = [
|
||||||
"image",
|
"image",
|
||||||
"video",
|
"video",
|
||||||
|
"audio",
|
||||||
|
"pdf",
|
||||||
"attachment",
|
"attachment",
|
||||||
"excalidraw",
|
"excalidraw",
|
||||||
"drawio",
|
"drawio",
|
||||||
@@ -63,6 +66,7 @@ export const handlePaste = (
|
|||||||
const pos = editor.state.selection.from;
|
const pos = editor.state.selection.from;
|
||||||
uploadImageAction(file, editor, pos, pageId);
|
uploadImageAction(file, editor, pos, pageId);
|
||||||
uploadVideoAction(file, editor, pos, pageId);
|
uploadVideoAction(file, editor, pos, pageId);
|
||||||
|
uploadPdfAction(file, editor, pos, pageId);
|
||||||
uploadAttachmentAction(file, editor, pos, pageId);
|
uploadAttachmentAction(file, editor, pos, pageId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -229,6 +233,7 @@ export const handleFileDrop = (
|
|||||||
|
|
||||||
uploadImageAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
uploadImageAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
||||||
uploadVideoAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
uploadVideoAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
||||||
|
uploadPdfAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
||||||
uploadAttachmentAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
uploadAttachmentAction(file, editor, coordinates?.pos ?? 0 - 1, pageId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ResizableNodeViewDirection } from "@tiptap/core";
|
|
||||||
import classes from "./node-resize.module.css";
|
import classes from "./node-resize.module.css";
|
||||||
|
import { ResizableNodeViewDirection } from "@docmost/editor-ext";
|
||||||
|
|
||||||
export function createResizeHandle(
|
export function createResizeHandle(
|
||||||
direction: ResizableNodeViewDirection,
|
direction: ResizableNodeViewDirection,
|
||||||
|
|||||||
@@ -20,8 +20,8 @@
|
|||||||
|
|
||||||
.cornerHandle {
|
.cornerHandle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 36px;
|
width: 24px;
|
||||||
height: 36px;
|
height: 24px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.2s ease;
|
transition: opacity 0.2s ease;
|
||||||
@@ -42,13 +42,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
width: 28px;
|
width: 20px;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 28px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover::before,
|
&:hover::before,
|
||||||
|
|||||||
@@ -74,6 +74,15 @@ export const ResizableWrapper: React.FC<ResizableWrapperProps> = ({
|
|||||||
const constraintsRef = useRef({ minWidth, maxWidth, minHeight, maxHeight });
|
const constraintsRef = useRef({ minWidth, maxWidth, minHeight, maxHeight });
|
||||||
constraintsRef.current = { minWidth, maxWidth, minHeight, maxHeight };
|
constraintsRef.current = { minWidth, maxWidth, minHeight, maxHeight };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!dragRef.current && wrapperRef.current) {
|
||||||
|
widthRef.current = initialWidth;
|
||||||
|
heightRef.current = initialHeight;
|
||||||
|
wrapperRef.current.style.width = `${initialWidth}px`;
|
||||||
|
wrapperRef.current.style.height = `${initialHeight}px`;
|
||||||
|
}
|
||||||
|
}, [initialWidth, initialHeight]);
|
||||||
|
|
||||||
const handleMouseMove = useRef((e: MouseEvent) => {
|
const handleMouseMove = useRef((e: MouseEvent) => {
|
||||||
const drag = dragRef.current;
|
const drag = dragRef.current;
|
||||||
if (!drag || !wrapperRef.current) return;
|
if (!drag || !wrapperRef.current) return;
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ export default function EmbedView(props: NodeViewProps) {
|
|||||||
{embedUrl ? (
|
{embedUrl ? (
|
||||||
<div className={classes.embedContainer}>
|
<div className={classes.embedContainer}>
|
||||||
<ResizableWrapper
|
<ResizableWrapper
|
||||||
initialWidth={nodeWidth || 640}
|
initialWidth={nodeWidth || 800}
|
||||||
initialHeight={nodeHeight || 480}
|
initialHeight={nodeHeight || 600}
|
||||||
minWidth={200}
|
minWidth={200}
|
||||||
maxWidth={1200}
|
maxWidth={1200}
|
||||||
minHeight={200}
|
minHeight={200}
|
||||||
@@ -102,8 +102,9 @@ export default function EmbedView(props: NodeViewProps) {
|
|||||||
<iframe
|
<iframe
|
||||||
className={classes.embedIframe}
|
className={classes.embedIframe}
|
||||||
src={sanitizeUrl(embedUrl)}
|
src={sanitizeUrl(embedUrl)}
|
||||||
allow="encrypted-media"
|
allow="encrypted-media; clipboard-read; clipboard-write; picture-in-picture;"
|
||||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
loading="lazy"
|
||||||
|
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-downloads"
|
||||||
allowFullScreen
|
allowFullScreen
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
animation: pulse 1.2s ease-in-out infinite;
|
animation: pulse 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export default function ImageView(props: NodeViewProps) {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
selected && "ProseMirror-selectednode",
|
selected && "ProseMirror-selectednode",
|
||||||
classes.imageWrapper,
|
classes.imageWrapper,
|
||||||
|
!src && classes.skeleton,
|
||||||
alignClass,
|
alignClass,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import { BubbleMenu as BaseBubbleMenu } from "@tiptap/react/menus";
|
||||||
|
import { findParentNode, posToDOMRect, useEditorState } from "@tiptap/react";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { Node as PMNode } from "@tiptap/pm/model";
|
||||||
|
import {
|
||||||
|
EditorMenuProps,
|
||||||
|
ShouldShowProps,
|
||||||
|
} from "@/features/editor/components/table/types/types.ts";
|
||||||
|
import { ActionIcon, Tooltip } from "@mantine/core";
|
||||||
|
import {
|
||||||
|
IconPaperclip,
|
||||||
|
IconTrash,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import classes from "../common/toolbar-menu.module.css";
|
||||||
|
|
||||||
|
export function PdfMenu({ editor }: EditorMenuProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const editorState = useEditorState({
|
||||||
|
editor,
|
||||||
|
selector: (ctx) => {
|
||||||
|
if (!ctx.editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfAttrs = ctx.editor.getAttributes("pdf");
|
||||||
|
|
||||||
|
return {
|
||||||
|
isPdf: ctx.editor.isActive("pdf"),
|
||||||
|
src: pdfAttrs?.src || null,
|
||||||
|
name: pdfAttrs?.name || null,
|
||||||
|
attachmentId: pdfAttrs?.attachmentId || null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const shouldShow = useCallback(
|
||||||
|
({ state }: ShouldShowProps) => {
|
||||||
|
if (!state || !editor.isActive("pdf")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selection } = state;
|
||||||
|
const dom = editor.view.nodeDOM(selection.from) as HTMLElement | null;
|
||||||
|
if (!dom) return false;
|
||||||
|
|
||||||
|
return !!dom.querySelector("[data-pdf-error]");
|
||||||
|
},
|
||||||
|
[editor],
|
||||||
|
);
|
||||||
|
|
||||||
|
const getReferencedVirtualElement = useCallback(() => {
|
||||||
|
if (!editor) return;
|
||||||
|
const { selection } = editor.state;
|
||||||
|
const predicate = (node: PMNode) => node.type.name === "pdf";
|
||||||
|
const parent = findParentNode(predicate)(selection);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
const dom = editor.view.nodeDOM(parent?.pos) as HTMLElement;
|
||||||
|
const domRect = dom.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => domRect,
|
||||||
|
getClientRects: () => [domRect],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const domRect = posToDOMRect(editor.view, selection.from, selection.to);
|
||||||
|
return {
|
||||||
|
getBoundingClientRect: () => domRect,
|
||||||
|
getClientRects: () => [domRect],
|
||||||
|
};
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
const handleConvertToAttachment = useCallback(() => {
|
||||||
|
if (!editorState?.src) return;
|
||||||
|
|
||||||
|
const { selection } = editor.state;
|
||||||
|
const { from } = selection;
|
||||||
|
const node = editor.state.doc.nodeAt(from);
|
||||||
|
if (!node || node.type.name !== "pdf") return;
|
||||||
|
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.insertContentAt(
|
||||||
|
{ from, to: from + node.nodeSize },
|
||||||
|
{
|
||||||
|
type: "attachment",
|
||||||
|
attrs: {
|
||||||
|
url: node.attrs.src,
|
||||||
|
name: node.attrs.name,
|
||||||
|
attachmentId: node.attrs.attachmentId,
|
||||||
|
size: node.attrs.size,
|
||||||
|
mime: "application/pdf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}, [editor, editorState]);
|
||||||
|
|
||||||
|
const handleDelete = useCallback(() => {
|
||||||
|
editor.commands.deleteSelection();
|
||||||
|
}, [editor]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseBubbleMenu
|
||||||
|
editor={editor}
|
||||||
|
pluginKey={`pdf-menu`}
|
||||||
|
updateDelay={0}
|
||||||
|
getReferencedVirtualElement={getReferencedVirtualElement}
|
||||||
|
options={{
|
||||||
|
placement: "top",
|
||||||
|
offset: 8,
|
||||||
|
flip: false,
|
||||||
|
}}
|
||||||
|
shouldShow={shouldShow}
|
||||||
|
>
|
||||||
|
<div className={classes.toolbar}>
|
||||||
|
<Tooltip position="top" label={t("Convert to attachment")} withinPortal={false}>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleConvertToAttachment}
|
||||||
|
size="lg"
|
||||||
|
aria-label={t("Convert to attachment")}
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<IconPaperclip size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip position="top" label={t("Delete")} withinPortal={false}>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={handleDelete}
|
||||||
|
size="lg"
|
||||||
|
aria-label={t("Delete")}
|
||||||
|
variant="subtle"
|
||||||
|
>
|
||||||
|
<IconTrash size={18} />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</BaseBubbleMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PdfMenu;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
.pdfWrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
|
animation: pulse 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-gray-3) 0%, var(--mantine-color-gray-1) 50%, var(--mantine-color-gray-3) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-dark-6) 0%, var(--mantine-color-dark-5) 50%, var(--mantine-color-dark-6) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -135% 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfContainer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfResizeWrapper {
|
||||||
|
@mixin light {
|
||||||
|
background-color: var(--mantine-color-gray-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdfIframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hoverMenu {
|
||||||
|
position: absolute;
|
||||||
|
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 {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 32px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
background-color: var(--mantine-color-gray-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background-color: var(--mantine-color-dark-7);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
|
||||||
|
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";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import classes from "./pdf-view.module.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { isInternalFileUrl } from "@docmost/editor-ext";
|
||||||
|
import {
|
||||||
|
IconFileTypePdf,
|
||||||
|
IconPaperclip,
|
||||||
|
IconTrash,
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
|
||||||
|
export default function PdfView(props: NodeViewProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { editor, node, getPos, selected, updateAttributes } = props;
|
||||||
|
const { src, placeholder, width: nodeWidth, height: nodeHeight } = node.attrs;
|
||||||
|
const [hasError, setHasError] = useState(false);
|
||||||
|
|
||||||
|
const safeSrc = useMemo(() => {
|
||||||
|
if (!src || !isInternalFileUrl(src)) return null;
|
||||||
|
return getFileUrl(src);
|
||||||
|
}, [src]);
|
||||||
|
|
||||||
|
const handleSelect = useCallback(() => {
|
||||||
|
const pos = getPos();
|
||||||
|
if (pos !== undefined) {
|
||||||
|
editor.commands.setNodeSelection(pos);
|
||||||
|
}
|
||||||
|
}, [editor, getPos]);
|
||||||
|
|
||||||
|
const handleResize = useCallback(
|
||||||
|
(newWidth: number, newHeight: number) => {
|
||||||
|
updateAttributes({ width: newWidth, height: newHeight });
|
||||||
|
},
|
||||||
|
[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>
|
||||||
|
<div className={`${classes.pdfWrapper} ${classes.skeleton}`} style={{ height: 600 }}>
|
||||||
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
||||||
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
|
<Text component="span" size="sm" truncate="end">
|
||||||
|
{placeholder?.name
|
||||||
|
? t("Uploading {{name}}", { name: placeholder.name })
|
||||||
|
: t("Uploading file")}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
return (
|
||||||
|
<NodeViewWrapper data-drag-handle>
|
||||||
|
<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")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</NodeViewWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NodeViewWrapper data-drag-handle className={classes.pdfNodeView}>
|
||||||
|
<div className={classes.pdfContainer}>
|
||||||
|
<ResizableWrapper
|
||||||
|
initialWidth={nodeWidth || 800}
|
||||||
|
initialHeight={nodeHeight || 600}
|
||||||
|
minWidth={200}
|
||||||
|
maxWidth={1200}
|
||||||
|
minHeight={200}
|
||||||
|
maxHeight={1200}
|
||||||
|
onResize={handleResize}
|
||||||
|
isEditable={editor.isEditable}
|
||||||
|
selected={selected}
|
||||||
|
className={clsx(classes.pdfResizeWrapper, {
|
||||||
|
"ProseMirror-selectednode": selected,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
className={classes.pdfIframe}
|
||||||
|
src={safeSrc}
|
||||||
|
loading="lazy"
|
||||||
|
frameBorder="0"
|
||||||
|
onError={() => setHasError(true)}
|
||||||
|
onLoad={(e) => {
|
||||||
|
try {
|
||||||
|
const iframe = e.currentTarget;
|
||||||
|
const status = iframe.contentDocument?.querySelector("pre")?.textContent;
|
||||||
|
if (status && status.includes('"statusCode":404')) {
|
||||||
|
setHasError(true);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// cross-origin - can't inspect, assume OK
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { handlePdfUpload } from "@docmost/editor-ext";
|
||||||
|
import { uploadFile } from "@/features/page/services/page-service.ts";
|
||||||
|
import { notifications } from "@mantine/notifications";
|
||||||
|
import { getFileUploadSizeLimit } from "@/lib/config.ts";
|
||||||
|
import { formatBytes } from "@/lib";
|
||||||
|
import i18n from "@/i18n.ts";
|
||||||
|
|
||||||
|
export const uploadPdfAction = handlePdfUpload({
|
||||||
|
onUpload: async (file: File, pageId: string): Promise<any> => {
|
||||||
|
try {
|
||||||
|
return await uploadFile(file, pageId);
|
||||||
|
} catch (err) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
message: err?.response.data.message,
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateFn: (file) => {
|
||||||
|
if (file.type !== "application/pdf") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > getFileUploadSizeLimit()) {
|
||||||
|
notifications.show({
|
||||||
|
color: "red",
|
||||||
|
message: i18n.t("File exceeds the {{limit}} attachment limit", {
|
||||||
|
limit: formatBytes(getFileUploadSizeLimit()),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
IconMath,
|
IconMath,
|
||||||
IconMathFunction,
|
IconMathFunction,
|
||||||
IconMovie,
|
IconMovie,
|
||||||
|
IconMusic,
|
||||||
IconPaperclip,
|
IconPaperclip,
|
||||||
|
IconFileTypePdf,
|
||||||
IconPhoto,
|
IconPhoto,
|
||||||
IconTable,
|
IconTable,
|
||||||
IconTypography,
|
IconTypography,
|
||||||
@@ -30,7 +32,9 @@ import {
|
|||||||
} from "@/features/editor/components/slash-menu/types";
|
} from "@/features/editor/components/slash-menu/types";
|
||||||
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
|
import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx";
|
||||||
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
|
import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx";
|
||||||
|
import { uploadAudioAction } from "@/features/editor/components/audio/upload-audio-action.tsx";
|
||||||
import { uploadAttachmentAction } from "@/features/editor/components/attachment/upload-attachment-action.tsx";
|
import { uploadAttachmentAction } from "@/features/editor/components/attachment/upload-attachment-action.tsx";
|
||||||
|
import { uploadPdfAction } from "@/features/editor/components/pdf/upload-pdf-action.tsx";
|
||||||
import IconExcalidraw from "@/components/icons/icon-excalidraw";
|
import IconExcalidraw from "@/components/icons/icon-excalidraw";
|
||||||
import IconMermaid from "@/components/icons/icon-mermaid";
|
import IconMermaid from "@/components/icons/icon-mermaid";
|
||||||
import IconDrawio from "@/components/icons/icon-drawio";
|
import IconDrawio from "@/components/icons/icon-drawio";
|
||||||
@@ -161,7 +165,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
{
|
{
|
||||||
title: "Image",
|
title: "Image",
|
||||||
description: "Upload any image from your device.",
|
description: "Upload any image from your device.",
|
||||||
searchTerms: ["photo", "picture", "media"],
|
searchTerms: ["photo", "picture", "media", "file", "attachment"],
|
||||||
icon: IconPhoto,
|
icon: IconPhoto,
|
||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
@@ -194,7 +198,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
{
|
{
|
||||||
title: "Video",
|
title: "Video",
|
||||||
description: "Upload any video from your device.",
|
description: "Upload any video from your device.",
|
||||||
searchTerms: ["video", "mp4", "media"],
|
searchTerms: ["video", "mp4", "media", "file", "attachment"],
|
||||||
icon: IconMovie,
|
icon: IconMovie,
|
||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
@@ -224,10 +228,74 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
input.click();
|
input.click();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Audio",
|
||||||
|
description: "Upload any audio from your device.",
|
||||||
|
searchTerms: ["audio", "music", "sound", "mp3", "media", "file", "attachment"],
|
||||||
|
icon: IconMusic,
|
||||||
|
command: ({ editor, range }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const pageId = editor.storage?.pageId;
|
||||||
|
if (!pageId) return;
|
||||||
|
|
||||||
|
// upload audio
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = "audio/*";
|
||||||
|
input.multiple = true;
|
||||||
|
input.style.display = "none";
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.onchange = async () => {
|
||||||
|
if (input.files?.length) {
|
||||||
|
for (const file of input.files) {
|
||||||
|
const pos = editor.view.state.selection.from;
|
||||||
|
|
||||||
|
uploadAudioAction(file, editor, pos, pageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.remove();
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Embed PDF",
|
||||||
|
description: "Upload and embed a PDF file.",
|
||||||
|
searchTerms: ["pdf", "document", "embed"],
|
||||||
|
icon: IconFileTypePdf,
|
||||||
|
command: ({ editor, range }) => {
|
||||||
|
editor.chain().focus().deleteRange(range).run();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const pageId = editor.storage?.pageId;
|
||||||
|
if (!pageId) return;
|
||||||
|
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.type = "file";
|
||||||
|
input.accept = "application/pdf";
|
||||||
|
input.style.display = "none";
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.onchange = async () => {
|
||||||
|
if (input.files?.length) {
|
||||||
|
for (const file of input.files) {
|
||||||
|
const pos = editor.view.state.selection.from;
|
||||||
|
|
||||||
|
uploadPdfAction(file, editor, pos, pageId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.remove();
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "File attachment",
|
title: "File attachment",
|
||||||
description: "Upload any file from your device.",
|
description: "Upload any file from your device.",
|
||||||
searchTerms: ["file", "attachment", "upload", "pdf", "csv", "zip"],
|
searchTerms: ["file", "attachment", "upload", "csv", "zip"],
|
||||||
icon: IconPaperclip,
|
icon: IconPaperclip,
|
||||||
command: ({ editor, range }) => {
|
command: ({ editor, range }) => {
|
||||||
editor.chain().focus().deleteRange(range).run();
|
editor.chain().focus().deleteRange(range).run();
|
||||||
@@ -359,7 +427,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
editor.chain().focus().deleteRange(range).setDrawio().run(),
|
editor.chain().focus().deleteRange(range).setDrawio().run(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Excalidraw diagram",
|
title: "Excalidraw (Whiteboard)",
|
||||||
description: "Draw and sketch excalidraw diagrams",
|
description: "Draw and sketch excalidraw diagrams",
|
||||||
searchTerms: ["diagrams", "draw", "sketch", "whiteboard"],
|
searchTerms: ["diagrams", "draw", "sketch", "whiteboard"],
|
||||||
icon: IconExcalidraw,
|
icon: IconExcalidraw,
|
||||||
@@ -548,7 +616,7 @@ const CommandGroups: SlashMenuGroupedItemsType = {
|
|||||||
{
|
{
|
||||||
title: "YouTube",
|
title: "YouTube",
|
||||||
description: "Embed YouTube video",
|
description: "Embed YouTube video",
|
||||||
searchTerms: ["youtube", "yt"],
|
searchTerms: ["youtube", "yt", "media", "video"],
|
||||||
icon: YoutubeIcon,
|
icon: YoutubeIcon,
|
||||||
command: ({ editor, range }: CommandProps) => {
|
command: ({ editor, range }: CommandProps) => {
|
||||||
editor
|
editor
|
||||||
@@ -647,7 +715,11 @@ export const getSuggestionItems = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (filteredItems.length) {
|
if (filteredItems.length) {
|
||||||
filteredGroups[group] = filteredItems;
|
filteredGroups[group] = filteredItems.sort((a, b) => {
|
||||||
|
const aTitle = a.title.toLowerCase().includes(search) ? 0 : 1;
|
||||||
|
const bTitle = b.title.toLowerCase().includes(search) ? 0 : 1;
|
||||||
|
return aTitle - bTitle;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton {
|
||||||
animation: pulse 1.2s ease-in-out infinite;
|
animation: pulse 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.video {
|
.video {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export default function VideoView(props: NodeViewProps) {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
selected && "ProseMirror-selectednode",
|
selected && "ProseMirror-selectednode",
|
||||||
classes.videoWrapper,
|
classes.videoWrapper,
|
||||||
|
!src && classes.skeleton,
|
||||||
alignClass,
|
alignClass,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
TiptapImage,
|
TiptapImage,
|
||||||
Callout,
|
Callout,
|
||||||
TiptapVideo,
|
TiptapVideo,
|
||||||
|
TiptapAudio,
|
||||||
LinkExtension,
|
LinkExtension,
|
||||||
Selection,
|
Selection,
|
||||||
Attachment,
|
Attachment,
|
||||||
@@ -37,6 +38,7 @@ import {
|
|||||||
Drawio,
|
Drawio,
|
||||||
Excalidraw,
|
Excalidraw,
|
||||||
Embed,
|
Embed,
|
||||||
|
TiptapPdf,
|
||||||
SearchAndReplace,
|
SearchAndReplace,
|
||||||
Mention,
|
Mention,
|
||||||
TableDndExtension,
|
TableDndExtension,
|
||||||
@@ -68,11 +70,13 @@ import ImageView from "@/features/editor/components/image/image-view.tsx";
|
|||||||
import CalloutView from "@/features/editor/components/callout/callout-view.tsx";
|
import CalloutView from "@/features/editor/components/callout/callout-view.tsx";
|
||||||
import StatusView from "@/features/editor/components/status/status-view.tsx";
|
import StatusView from "@/features/editor/components/status/status-view.tsx";
|
||||||
import VideoView from "@/features/editor/components/video/video-view.tsx";
|
import VideoView from "@/features/editor/components/video/video-view.tsx";
|
||||||
|
import AudioView from "@/features/editor/components/audio/audio-view.tsx";
|
||||||
import AttachmentView from "@/features/editor/components/attachment/attachment-view.tsx";
|
import AttachmentView from "@/features/editor/components/attachment/attachment-view.tsx";
|
||||||
import CodeBlockView from "@/features/editor/components/code-block/code-block-view.tsx";
|
import CodeBlockView from "@/features/editor/components/code-block/code-block-view.tsx";
|
||||||
import DrawioView from "../components/drawio/drawio-view";
|
import DrawioView from "../components/drawio/drawio-view";
|
||||||
import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-view.tsx";
|
import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-view.tsx";
|
||||||
import EmbedView from "@/features/editor/components/embed/embed-view.tsx";
|
import EmbedView from "@/features/editor/components/embed/embed-view.tsx";
|
||||||
|
import PdfView from "@/features/editor/components/pdf/pdf-view.tsx";
|
||||||
import SubpagesView from "@/features/editor/components/subpages/subpages-view.tsx";
|
import SubpagesView from "@/features/editor/components/subpages/subpages-view.tsx";
|
||||||
import { common, createLowlight } from "lowlight";
|
import { common, createLowlight } from "lowlight";
|
||||||
import plaintext from "highlight.js/lib/languages/plaintext";
|
import plaintext from "highlight.js/lib/languages/plaintext";
|
||||||
@@ -269,6 +273,9 @@ export const mainExtensions = [
|
|||||||
className: buildResizeClasses("node-video"),
|
className: buildResizeClasses("node-video"),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
TiptapAudio.configure({
|
||||||
|
view: AudioView,
|
||||||
|
}),
|
||||||
Callout.configure({
|
Callout.configure({
|
||||||
view: CalloutView,
|
view: CalloutView,
|
||||||
}),
|
}),
|
||||||
@@ -313,6 +320,9 @@ export const mainExtensions = [
|
|||||||
Embed.configure({
|
Embed.configure({
|
||||||
view: EmbedView,
|
view: EmbedView,
|
||||||
}),
|
}),
|
||||||
|
TiptapPdf.configure({
|
||||||
|
view: PdfView,
|
||||||
|
}),
|
||||||
Subpages.configure({
|
Subpages.configure({
|
||||||
view: SubpagesView,
|
view: SubpagesView,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import TableMenu from "@/features/editor/components/table/table-menu.tsx";
|
|||||||
import ImageMenu from "@/features/editor/components/image/image-menu.tsx";
|
import ImageMenu from "@/features/editor/components/image/image-menu.tsx";
|
||||||
import CalloutMenu from "@/features/editor/components/callout/callout-menu.tsx";
|
import CalloutMenu from "@/features/editor/components/callout/callout-menu.tsx";
|
||||||
import VideoMenu from "@/features/editor/components/video/video-menu.tsx";
|
import VideoMenu from "@/features/editor/components/video/video-menu.tsx";
|
||||||
|
import PdfMenu from "@/features/editor/components/pdf/pdf-menu.tsx";
|
||||||
import SubpagesMenu from "@/features/editor/components/subpages/subpages-menu.tsx";
|
import SubpagesMenu from "@/features/editor/components/subpages/subpages-menu.tsx";
|
||||||
import {
|
import {
|
||||||
handleFileDrop,
|
handleFileDrop,
|
||||||
@@ -414,6 +415,7 @@ export default function PageEditor({
|
|||||||
<TableCellMenu editor={editor} appendTo={menuContainerRef} />
|
<TableCellMenu editor={editor} appendTo={menuContainerRef} />
|
||||||
<ImageMenu editor={editor} />
|
<ImageMenu editor={editor} />
|
||||||
<VideoMenu editor={editor} />
|
<VideoMenu editor={editor} />
|
||||||
|
<PdfMenu editor={editor} />
|
||||||
<CalloutMenu editor={editor} />
|
<CalloutMenu editor={editor} />
|
||||||
<SubpagesMenu editor={editor} />
|
<SubpagesMenu editor={editor} />
|
||||||
<ExcalidrawMenu editor={editor} />
|
<ExcalidrawMenu editor={editor} />
|
||||||
|
|||||||
@@ -133,10 +133,18 @@
|
|||||||
border-top: 1px solid #68cef8;
|
border-top: 1px solid #68cef8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[contenteditable="false"] hr.ProseMirror-selectednode {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
.ProseMirror-selectednode {
|
.ProseMirror-selectednode {
|
||||||
outline: 2px solid #70cff8;
|
outline: 2px solid #70cff8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[contenteditable="false"] .ProseMirror-selectednode {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
& > .react-renderer {
|
& > .react-renderer {
|
||||||
margin-top: var(--mantine-spacing-sm);
|
margin-top: var(--mantine-spacing-sm);
|
||||||
margin-bottom: var(--mantine-spacing-sm);
|
margin-bottom: var(--mantine-spacing-sm);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-image, .node-video, .node-excalidraw, .node-drawio {
|
.node-image, .node-video, .node-pdf, .node-excalidraw, .node-drawio {
|
||||||
&.ProseMirror-selectednode {
|
&.ProseMirror-selectednode {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@@ -37,5 +37,28 @@
|
|||||||
font-size: var(--mantine-font-size-md);
|
font-size: var(--mantine-font-size-md);
|
||||||
line-height: var(--mantine-line-height-md);
|
line-height: var(--mantine-line-height-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-pulse {
|
||||||
|
animation: media-pulse 1.2s ease-in-out infinite;
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-gray-3) 0%, var(--mantine-color-gray-1) 50%, var(--mantine-color-gray-3) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
background: linear-gradient(-90deg, var(--mantine-color-dark-6) 0%, var(--mantine-color-dark-5) 50%, var(--mantine-color-dark-6) 100%);
|
||||||
|
background-size: 400% 400%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes media-pulse {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 0%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -135% 0%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng: "en-US",
|
fallbackLng: "en-US",
|
||||||
debug: false,
|
debug: false,
|
||||||
|
showSupportNotice: false,
|
||||||
load: 'currentOnly',
|
load: 'currentOnly',
|
||||||
|
|
||||||
interpolation: {
|
interpolation: {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
CustomTable,
|
CustomTable,
|
||||||
TiptapImage,
|
TiptapImage,
|
||||||
TiptapVideo,
|
TiptapVideo,
|
||||||
|
TiptapAudio,
|
||||||
|
TiptapPdf,
|
||||||
TrailingNode,
|
TrailingNode,
|
||||||
Attachment,
|
Attachment,
|
||||||
Drawio,
|
Drawio,
|
||||||
@@ -86,6 +88,8 @@ export const tiptapExtensions = [
|
|||||||
Youtube,
|
Youtube,
|
||||||
TiptapImage,
|
TiptapImage,
|
||||||
TiptapVideo,
|
TiptapVideo,
|
||||||
|
TiptapAudio,
|
||||||
|
TiptapPdf,
|
||||||
Callout,
|
Callout,
|
||||||
Attachment,
|
Attachment,
|
||||||
CustomCodeBlock,
|
CustomCodeBlock,
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ export function isAttachmentNode(nodeType: string) {
|
|||||||
'attachment',
|
'attachment',
|
||||||
'image',
|
'image',
|
||||||
'video',
|
'video',
|
||||||
|
'audio',
|
||||||
|
'pdf',
|
||||||
'excalidraw',
|
'excalidraw',
|
||||||
'drawio',
|
'drawio',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -15,4 +15,9 @@ export const inlineFileExtensions = [
|
|||||||
'.pdf',
|
'.pdf',
|
||||||
'.mp4',
|
'.mp4',
|
||||||
'.mov',
|
'.mov',
|
||||||
|
'.mp3',
|
||||||
|
'.wav',
|
||||||
|
'.ogg',
|
||||||
|
'.m4a',
|
||||||
|
'.webm',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -457,6 +457,10 @@ export class AttachmentController {
|
|||||||
const rangeHeader = req.headers.range;
|
const rangeHeader = req.headers.range;
|
||||||
|
|
||||||
res.header('Accept-Ranges', 'bytes');
|
res.header('Accept-Ranges', 'bytes');
|
||||||
|
res.header(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
"base-uri 'none'; object-src 'self'; default-src 'self';",
|
||||||
|
);
|
||||||
|
|
||||||
if (!inlineFileExtensions.includes(attachment.fileExt)) {
|
if (!inlineFileExtensions.includes(attachment.fileExt)) {
|
||||||
res.header(
|
res.header(
|
||||||
|
|||||||
@@ -190,13 +190,32 @@ export class ImportAttachmentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a map from resolved archive path → real filename from Confluence
|
||||||
|
// metadata. Confluence Server archives often store files under numeric IDs
|
||||||
|
// (e.g. "attachments/65601/65602") instead of the original filename.
|
||||||
|
const pageDir = path.dirname(pageRelativePath);
|
||||||
|
const attachmentNameByRelPath = new Map<string, string>();
|
||||||
|
for (const attachment of pageAttachments) {
|
||||||
|
const relPath = resolveRelativeAttachmentPath(
|
||||||
|
attachment.href,
|
||||||
|
pageDir,
|
||||||
|
attachmentCandidates,
|
||||||
|
);
|
||||||
|
if (relPath && attachment.fileName) {
|
||||||
|
attachmentNameByRelPath.set(relPath, attachment.fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const uploadOnce = (relPath: string) => {
|
const uploadOnce = (relPath: string) => {
|
||||||
const abs = attachmentCandidates.get(relPath)!;
|
const abs = attachmentCandidates.get(relPath)!;
|
||||||
const attachmentId = v7();
|
const attachmentId = v7();
|
||||||
const ext = path.extname(abs);
|
|
||||||
|
const realName = attachmentNameByRelPath.get(relPath);
|
||||||
|
const baseName = realName || path.basename(abs);
|
||||||
|
const ext = path.extname(baseName);
|
||||||
|
|
||||||
const fileNameWithExt =
|
const fileNameWithExt =
|
||||||
sanitizeFileName(path.basename(abs, ext)) + ext.toLowerCase();
|
sanitizeFileName(path.basename(baseName, ext)) + ext.toLowerCase();
|
||||||
|
|
||||||
const storageFilePath = `${getAttachmentFolderPath(
|
const storageFilePath = `${getAttachmentFolderPath(
|
||||||
AttachmentType.File,
|
AttachmentType.File,
|
||||||
@@ -240,7 +259,6 @@ export class ImportAttachmentService {
|
|||||||
return fresh;
|
return fresh;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pageDir = path.dirname(pageRelativePath);
|
|
||||||
const $ = load(html);
|
const $ = load(html);
|
||||||
|
|
||||||
// image
|
// image
|
||||||
@@ -335,6 +353,28 @@ export class ImportAttachmentService {
|
|||||||
unwrapFromParagraph($, $vid);
|
unwrapFromParagraph($, $vid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// audio
|
||||||
|
for (const audEl of $('audio').toArray()) {
|
||||||
|
const $aud = $(audEl);
|
||||||
|
const src = cleanUrlString($aud.attr('src') ?? '')!;
|
||||||
|
if (!src || src.startsWith('http')) continue;
|
||||||
|
|
||||||
|
const relPath = resolveRelativeAttachmentPath(
|
||||||
|
src,
|
||||||
|
pageDir,
|
||||||
|
attachmentCandidates,
|
||||||
|
);
|
||||||
|
if (!relPath) continue;
|
||||||
|
|
||||||
|
const { attachmentId, apiFilePath } = processFile(relPath);
|
||||||
|
|
||||||
|
$aud
|
||||||
|
.attr('src', apiFilePath)
|
||||||
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
|
||||||
|
unwrapFromParagraph($, $aud);
|
||||||
|
}
|
||||||
|
|
||||||
// <div data-type="attachment">
|
// <div data-type="attachment">
|
||||||
for (const el of $('div[data-type="attachment"]').toArray()) {
|
for (const el of $('div[data-type="attachment"]').toArray()) {
|
||||||
const $oldDiv = $(el);
|
const $oldDiv = $(el);
|
||||||
@@ -401,7 +441,18 @@ export class ImportAttachmentService {
|
|||||||
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
const { attachmentId, apiFilePath, abs } = processFile(relPath);
|
||||||
const ext = path.extname(relPath).toLowerCase();
|
const ext = path.extname(relPath).toLowerCase();
|
||||||
|
|
||||||
if (ext === '.mp4') {
|
const audioExtensions = new Set(['.mp3', '.wav', '.ogg', '.m4a', '.webm', '.flac', '.aac']);
|
||||||
|
|
||||||
|
if (ext === '.pdf') {
|
||||||
|
const $pdf = $('<div>')
|
||||||
|
.attr('data-type', 'pdf')
|
||||||
|
.attr('src', apiFilePath)
|
||||||
|
.attr('data-attachment-id', attachmentId)
|
||||||
|
.attr('width', '800')
|
||||||
|
.attr('height', '600');
|
||||||
|
$a.replaceWith($pdf);
|
||||||
|
unwrapFromParagraph($, $pdf);
|
||||||
|
} else if (ext === '.mp4') {
|
||||||
const $video = $('<video>')
|
const $video = $('<video>')
|
||||||
.attr('src', apiFilePath)
|
.attr('src', apiFilePath)
|
||||||
.attr('data-attachment-id', attachmentId)
|
.attr('data-attachment-id', attachmentId)
|
||||||
@@ -409,6 +460,12 @@ export class ImportAttachmentService {
|
|||||||
.attr('data-align', 'center');
|
.attr('data-align', 'center');
|
||||||
$a.replaceWith($video);
|
$a.replaceWith($video);
|
||||||
unwrapFromParagraph($, $video);
|
unwrapFromParagraph($, $video);
|
||||||
|
} else if (audioExtensions.has(ext)) {
|
||||||
|
const $audio = $('<audio>')
|
||||||
|
.attr('src', apiFilePath)
|
||||||
|
.attr('data-attachment-id', attachmentId);
|
||||||
|
$a.replaceWith($audio);
|
||||||
|
unwrapFromParagraph($, $audio);
|
||||||
} else {
|
} else {
|
||||||
const confAliasName = $a.attr('data-linked-resource-default-alias');
|
const confAliasName = $a.attr('data-linked-resource-default-alias');
|
||||||
let attachmentName = path.basename(abs);
|
let attachmentName = path.basename(abs);
|
||||||
@@ -555,7 +612,7 @@ export class ImportAttachmentService {
|
|||||||
// Post-process DOM elements to add file sizes after uploads complete
|
// Post-process DOM elements to add file sizes after uploads complete
|
||||||
// This avoids blocking file operations during initial DOM processing
|
// This avoids blocking file operations during initial DOM processing
|
||||||
const elementsNeedingSize = $(
|
const elementsNeedingSize = $(
|
||||||
'[data-attachment-id]:not([data-attachment-size])',
|
'[data-attachment-id]:not([data-attachment-size]):not([data-size])',
|
||||||
);
|
);
|
||||||
for (const element of elementsNeedingSize.toArray()) {
|
for (const element of elementsNeedingSize.toArray()) {
|
||||||
const $el = $(element);
|
const $el = $(element);
|
||||||
@@ -570,7 +627,14 @@ export class ImportAttachmentService {
|
|||||||
if (processedEntry) {
|
if (processedEntry) {
|
||||||
try {
|
try {
|
||||||
const stat = await fs.stat(processedEntry.abs);
|
const stat = await fs.stat(processedEntry.abs);
|
||||||
$el.attr('data-attachment-size', stat.size.toString());
|
const sizeStr = stat.size.toString();
|
||||||
|
const tagName = $el.prop('tagName')?.toLowerCase();
|
||||||
|
// audio and pdf nodes use data-size, attachment nodes use data-attachment-size
|
||||||
|
if (tagName === 'audio' || $el.attr('data-type') === 'pdf') {
|
||||||
|
$el.attr('data-size', sizeStr);
|
||||||
|
} else {
|
||||||
|
$el.attr('data-attachment-size', sizeStr);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Could not get size for ${processedEntry.abs}:`,
|
`Could not get size for ${processedEntry.abs}:`,
|
||||||
|
|||||||
@@ -41,6 +41,15 @@ export function resolveRelativeAttachmentPath(
|
|||||||
'ImportUtils',
|
'ImportUtils',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Confluence Server uses "/download/attachments/..." in HTML but the ZIP
|
||||||
|
// stores files under "attachments/...". Strip the "download/" prefix so
|
||||||
|
// the path can match candidates from the archive.
|
||||||
|
const confluenceStripped = mainRel.replace(
|
||||||
|
/^download\/attachments\//,
|
||||||
|
'attachments/',
|
||||||
|
);
|
||||||
|
|
||||||
const fallback = path
|
const fallback = path
|
||||||
.normalize(path.join(pageDir, mainRel))
|
.normalize(path.join(pageDir, mainRel))
|
||||||
.split(path.sep)
|
.split(path.sep)
|
||||||
@@ -49,9 +58,13 @@ export function resolveRelativeAttachmentPath(
|
|||||||
if (attachmentCandidates.has(mainRel)) {
|
if (attachmentCandidates.has(mainRel)) {
|
||||||
return mainRel;
|
return mainRel;
|
||||||
}
|
}
|
||||||
|
if (confluenceStripped !== mainRel && attachmentCandidates.has(confluenceStripped)) {
|
||||||
|
return confluenceStripped;
|
||||||
|
}
|
||||||
if (attachmentCandidates.has(fallback)) {
|
if (attachmentCandidates.has(fallback)) {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,25 +66,25 @@ export class LocalDriver implements StorageDriver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async readStream(filePath: string): Promise<Readable> {
|
async readStream(filePath: string): Promise<Readable> {
|
||||||
try {
|
const fullPath = this._fullPath(filePath);
|
||||||
return createReadStream(this._fullPath(filePath));
|
if (!(await fs.pathExists(fullPath))) {
|
||||||
} catch (err) {
|
throw new Error(`File not found: ${filePath}`);
|
||||||
throw new Error(`Failed to read file: ${(err as Error).message}`);
|
|
||||||
}
|
}
|
||||||
|
return createReadStream(fullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async readRangeStream(
|
async readRangeStream(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
range: { start: number; end: number },
|
range: { start: number; end: number },
|
||||||
): Promise<Readable> {
|
): Promise<Readable> {
|
||||||
try {
|
const fullPath = this._fullPath(filePath);
|
||||||
return createReadStream(this._fullPath(filePath), {
|
if (!(await fs.pathExists(fullPath))) {
|
||||||
start: range.start,
|
throw new Error(`File not found: ${filePath}`);
|
||||||
end: range.end,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`Failed to read file: ${(err as Error).message}`);
|
|
||||||
}
|
}
|
||||||
|
return createReadStream(fullPath, {
|
||||||
|
start: range.start,
|
||||||
|
end: range.end,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists(filePath: string): Promise<boolean> {
|
async exists(filePath: string): Promise<boolean> {
|
||||||
|
|||||||
+2
-2
@@ -30,6 +30,7 @@
|
|||||||
"@joplin/turndown-plugin-gfm": "^1.0.64",
|
"@joplin/turndown-plugin-gfm": "^1.0.64",
|
||||||
"@sindresorhus/slugify": "3.0.0",
|
"@sindresorhus/slugify": "3.0.0",
|
||||||
"@tiptap/core": "3.20.4",
|
"@tiptap/core": "3.20.4",
|
||||||
|
"@tiptap/extension-audio": "3.20.4",
|
||||||
"@tiptap/extension-code-block": "3.20.4",
|
"@tiptap/extension-code-block": "3.20.4",
|
||||||
"@tiptap/extension-collaboration": "3.20.4",
|
"@tiptap/extension-collaboration": "3.20.4",
|
||||||
"@tiptap/extension-collaboration-caret": "3.20.4",
|
"@tiptap/extension-collaboration-caret": "3.20.4",
|
||||||
@@ -94,8 +95,7 @@
|
|||||||
"packageManager": "pnpm@10.4.0",
|
"packageManager": "pnpm@10.4.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"react-arborist@3.4.0": "patches/react-arborist@3.4.0.patch",
|
"react-arborist@3.4.0": "patches/react-arborist@3.4.0.patch"
|
||||||
"@tiptap/core": "patches/@tiptap__core.patch"
|
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"prosemirror-changeset": "2.4.0",
|
"prosemirror-changeset": "2.4.0",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export * from "./lib/media-utils";
|
|||||||
export * from "./lib/link";
|
export * from "./lib/link";
|
||||||
export * from "./lib/selection";
|
export * from "./lib/selection";
|
||||||
export * from "./lib/attachment";
|
export * from "./lib/attachment";
|
||||||
|
export * from "./lib/audio";
|
||||||
export * from "./lib/custom-code-block";
|
export * from "./lib/custom-code-block";
|
||||||
export * from "./lib/drawio";
|
export * from "./lib/drawio";
|
||||||
export * from "./lib/excalidraw";
|
export * from "./lib/excalidraw";
|
||||||
@@ -27,3 +28,6 @@ export * from "./lib/shared-storage";
|
|||||||
export * from "./lib/recreate-transform";
|
export * from "./lib/recreate-transform";
|
||||||
export * from "./lib/columns";
|
export * from "./lib/columns";
|
||||||
export * from "./lib/status";
|
export * from "./lib/status";
|
||||||
|
export * from "./lib/pdf";
|
||||||
|
export * from "./lib/resizable-nodeview";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||||
|
import { IAttachment } from "../types";
|
||||||
|
import { generateNodeId } from "../utils";
|
||||||
|
import { Node } from "@tiptap/pm/model";
|
||||||
|
import { Command } from "@tiptap/core";
|
||||||
|
|
||||||
|
const findAudioNodeByPlaceholderId = (
|
||||||
|
doc: Node,
|
||||||
|
placeholderId: string,
|
||||||
|
): { node: Node; pos: number } | null => {
|
||||||
|
let result: { node: Node; pos: number } | null = null;
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (result) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
node.type.name === "audio" &&
|
||||||
|
node.attrs.placeholder?.id === placeholderId
|
||||||
|
) {
|
||||||
|
result = { node, pos };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAudioUpload =
|
||||||
|
({ validateFn, onUpload }: MediaUploadOptions): UploadFn =>
|
||||||
|
async (file, editor, pos, pageId) => {
|
||||||
|
const validated = validateFn?.(file);
|
||||||
|
// @ts-ignore
|
||||||
|
if (!validated) return;
|
||||||
|
|
||||||
|
const objectUrl = URL.createObjectURL(file);
|
||||||
|
const placeholderId = generateNodeId();
|
||||||
|
|
||||||
|
let placeholderInserted = false;
|
||||||
|
|
||||||
|
editor.storage.shared.audioPreviews =
|
||||||
|
editor.storage.shared.audioPreviews || {};
|
||||||
|
editor.storage.shared.audioPreviews[placeholderId] = objectUrl;
|
||||||
|
|
||||||
|
const insertPlaceholder = (): Command => {
|
||||||
|
return ({ tr, state }) => {
|
||||||
|
const initialPlaceholderNode = state.schema.nodes.audio?.create({
|
||||||
|
placeholder: {
|
||||||
|
id: placeholderId,
|
||||||
|
name: file.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!initialPlaceholderNode) return false;
|
||||||
|
|
||||||
|
const { parent } = tr.doc.resolve(pos);
|
||||||
|
const isEmptyTextBlock = parent.isTextblock && !parent.childCount;
|
||||||
|
|
||||||
|
if (isEmptyTextBlock) {
|
||||||
|
tr.replaceRangeWith(pos - 1, pos + 1, initialPlaceholderNode);
|
||||||
|
} else {
|
||||||
|
tr.insert(pos, initialPlaceholderNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const replacePlaceholderWithAudio = (attachment: IAttachment): Command => {
|
||||||
|
return ({ tr }) => {
|
||||||
|
const { pos: currentPos = null } =
|
||||||
|
findAudioNodeByPlaceholderId(tr.doc, placeholderId) || {};
|
||||||
|
|
||||||
|
if (currentPos === null || !attachment) return;
|
||||||
|
|
||||||
|
tr.setNodeMarkup(currentPos, undefined, {
|
||||||
|
src: `/api/files/${attachment.id}/${attachment.fileName}`,
|
||||||
|
attachmentId: attachment.id,
|
||||||
|
size: attachment.fileSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePlaceholder = (): Command => {
|
||||||
|
return ({ tr }) => {
|
||||||
|
const { pos: currentPos = null } =
|
||||||
|
findAudioNodeByPlaceholderId(tr.doc, placeholderId) || {};
|
||||||
|
|
||||||
|
if (currentPos === null) return false;
|
||||||
|
|
||||||
|
tr.delete(currentPos, currentPos + 2);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertPlaceholderTimeout = setTimeout(() => {
|
||||||
|
editor.commands.command(insertPlaceholder());
|
||||||
|
placeholderInserted = true;
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
const disposePreviewFile = () => {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
|
||||||
|
if (editor.storage.shared.audioPreviews) {
|
||||||
|
delete editor.storage.shared.audioPreviews[placeholderId];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const attachment: IAttachment = await onUpload(file, pageId);
|
||||||
|
|
||||||
|
clearTimeout(insertPlaceholderTimeout);
|
||||||
|
|
||||||
|
if (placeholderInserted) {
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.commands.command(replacePlaceholderWithAudio(attachment));
|
||||||
|
disposePreviewFile();
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.command(insertPlaceholder())
|
||||||
|
.command(replacePlaceholderWithAudio(attachment))
|
||||||
|
.run();
|
||||||
|
disposePreviewFile();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(insertPlaceholderTimeout);
|
||||||
|
|
||||||
|
editor.commands.command(removePlaceholder());
|
||||||
|
disposePreviewFile();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { handleAudioUpload };
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { Node, mergeAttributes } from "@tiptap/core";
|
||||||
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
|
import { normalizeFileUrl } from "../media-utils";
|
||||||
|
import { sanitizeUrl, isInternalFileUrl } from "../utils";
|
||||||
|
|
||||||
|
export interface AudioOptions {
|
||||||
|
view: any;
|
||||||
|
HTMLAttributes: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AudioAttributes {
|
||||||
|
src?: string;
|
||||||
|
attachmentId?: string;
|
||||||
|
size?: number;
|
||||||
|
placeholder?: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "@tiptap/core" {
|
||||||
|
interface Commands<ReturnType> {
|
||||||
|
audioBlock: {
|
||||||
|
setAudio: (attributes: AudioAttributes) => ReturnType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TiptapAudio = Node.create<AudioOptions>({
|
||||||
|
name: "audio",
|
||||||
|
|
||||||
|
group: "block",
|
||||||
|
isolating: true,
|
||||||
|
atom: true,
|
||||||
|
defining: true,
|
||||||
|
draggable: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
view: null,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
src: {
|
||||||
|
default: "",
|
||||||
|
parseHTML: (element) => {
|
||||||
|
const src = element.getAttribute("src");
|
||||||
|
const sanitized = sanitizeUrl(src);
|
||||||
|
return isInternalFileUrl(sanitized) ? sanitized : "";
|
||||||
|
},
|
||||||
|
renderHTML: (attributes) => ({
|
||||||
|
src: isInternalFileUrl(attributes.src)
|
||||||
|
? sanitizeUrl(attributes.src)
|
||||||
|
: "",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
attachmentId: {
|
||||||
|
default: undefined,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-attachment-id"),
|
||||||
|
renderHTML: (attributes: AudioAttributes) => ({
|
||||||
|
"data-attachment-id": attributes.attachmentId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-size"),
|
||||||
|
renderHTML: (attributes: AudioAttributes) => ({
|
||||||
|
"data-size": attributes.size,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: null,
|
||||||
|
rendered: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: "audio",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
"audio",
|
||||||
|
mergeAttributes(
|
||||||
|
{ controls: "true", preload: "metadata" },
|
||||||
|
this.options.HTMLAttributes,
|
||||||
|
HTMLAttributes,
|
||||||
|
),
|
||||||
|
["source", { src: HTMLAttributes.src }],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setAudio:
|
||||||
|
(attrs: AudioAttributes) =>
|
||||||
|
({ commands }) => {
|
||||||
|
return commands.insertContent({
|
||||||
|
type: "audio",
|
||||||
|
attrs: attrs,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
if (this.options.view) {
|
||||||
|
this.editor.isInitialized = true;
|
||||||
|
return ReactNodeViewRenderer(this.options.view);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ({ node, HTMLAttributes }) => {
|
||||||
|
const dom = document.createElement("div");
|
||||||
|
const audio = document.createElement("audio");
|
||||||
|
const src = node.attrs.src;
|
||||||
|
if (src && isInternalFileUrl(src)) {
|
||||||
|
audio.src = normalizeFileUrl(src);
|
||||||
|
}
|
||||||
|
audio.controls = true;
|
||||||
|
audio.preload = "metadata";
|
||||||
|
audio.style.width = "100%";
|
||||||
|
dom.append(audio);
|
||||||
|
return { dom };
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { TiptapAudio } from "./audio";
|
||||||
|
export * from "./audio-upload";
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
|
import { Node, mergeAttributes } from "@tiptap/core";
|
||||||
import type { ResizableNodeViewDirection } from "@tiptap/core";
|
import { ResizableNodeView } from "./resizable-nodeview";
|
||||||
|
import type { ResizableNodeViewDirection } from "./resizable-nodeview";
|
||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
import { normalizeFileUrl } from "./media-utils";
|
import { normalizeFileUrl } from "./media-utils";
|
||||||
|
|
||||||
@@ -320,12 +321,11 @@ export const Drawio = Node.create<DrawioOptions>({
|
|||||||
|
|
||||||
// Show skeleton background while image loads from server
|
// Show skeleton background while image loads from server
|
||||||
dom.style.pointerEvents = "none";
|
dom.style.pointerEvents = "none";
|
||||||
dom.style.background =
|
el.classList.add("media-pulse");
|
||||||
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
|
|
||||||
|
|
||||||
el.onload = () => {
|
el.onload = () => {
|
||||||
dom.style.pointerEvents = "";
|
dom.style.pointerEvents = "";
|
||||||
dom.style.background = "";
|
el.classList.remove("media-pulse");
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeView;
|
return nodeView;
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ export const Embed = Node.create<EmbedOptions>({
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
width: {
|
width: {
|
||||||
default: 640,
|
default: 800,
|
||||||
parseHTML: (element) => element.getAttribute("data-width"),
|
parseHTML: (element) => element.getAttribute("data-width"),
|
||||||
renderHTML: (attributes: EmbedAttributes) => ({
|
renderHTML: (attributes: EmbedAttributes) => ({
|
||||||
"data-width": attributes.width,
|
"data-width": attributes.width,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
height: {
|
height: {
|
||||||
default: 480,
|
default: 600,
|
||||||
parseHTML: (element) => element.getAttribute("data-height"),
|
parseHTML: (element) => element.getAttribute("data-height"),
|
||||||
renderHTML: (attributes: EmbedAttributes) => ({
|
renderHTML: (attributes: EmbedAttributes) => ({
|
||||||
"data-height": attributes.height,
|
"data-height": attributes.height,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
|
import { Node, mergeAttributes } from "@tiptap/core";
|
||||||
import type { ResizableNodeViewDirection } from "@tiptap/core";
|
import { ResizableNodeView } from "./resizable-nodeview";
|
||||||
|
import type { ResizableNodeViewDirection } from "./resizable-nodeview";
|
||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
import { normalizeFileUrl } from "./media-utils";
|
import { normalizeFileUrl } from "./media-utils";
|
||||||
|
|
||||||
@@ -320,12 +321,11 @@ export const Excalidraw = Node.create<ExcalidrawOptions>({
|
|||||||
|
|
||||||
// Show skeleton background while image loads from server
|
// Show skeleton background while image loads from server
|
||||||
dom.style.pointerEvents = "none";
|
dom.style.pointerEvents = "none";
|
||||||
dom.style.background =
|
el.classList.add("media-pulse");
|
||||||
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
|
|
||||||
|
|
||||||
el.onload = () => {
|
el.onload = () => {
|
||||||
dom.style.pointerEvents = "";
|
dom.style.pointerEvents = "";
|
||||||
dom.style.background = "";
|
el.classList.remove("media-pulse");
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeView;
|
return nodeView;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { ReactNodeViewRenderer } from "@tiptap/react";
|
|||||||
import {
|
import {
|
||||||
mergeAttributes,
|
mergeAttributes,
|
||||||
Range,
|
Range,
|
||||||
ResizableNodeView,
|
|
||||||
} from "@tiptap/core";
|
} from "@tiptap/core";
|
||||||
|
import { ResizableNodeView } from "../resizable-nodeview";
|
||||||
|
import type { ResizableNodeViewDirection } from "../resizable-nodeview";
|
||||||
import { normalizeFileUrl } from "../media-utils";
|
import { normalizeFileUrl } from "../media-utils";
|
||||||
import type { ResizableNodeViewDirection } from "@tiptap/core";
|
|
||||||
|
|
||||||
export type ImageResizeOptions = {
|
export type ImageResizeOptions = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -362,12 +362,11 @@ export const TiptapImage = Image.extend<ImageOptions>({
|
|||||||
|
|
||||||
// Show skeleton background while image loads from server
|
// Show skeleton background while image loads from server
|
||||||
dom.style.pointerEvents = "none";
|
dom.style.pointerEvents = "none";
|
||||||
dom.style.background =
|
el.classList.add("media-pulse");
|
||||||
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
|
|
||||||
|
|
||||||
el.onload = () => {
|
el.onload = () => {
|
||||||
dom.style.pointerEvents = "";
|
dom.style.pointerEvents = "";
|
||||||
dom.style.background = "";
|
el.classList.remove("media-pulse");
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeView;
|
return nodeView;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { TiptapPdf } from "./pdf";
|
||||||
|
export * from "./pdf-upload";
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import { MediaUploadOptions, UploadFn } from "../media-utils";
|
||||||
|
import { IAttachment } from "../types";
|
||||||
|
import { generateNodeId } from "../utils";
|
||||||
|
import { Node } from "@tiptap/pm/model";
|
||||||
|
import { Command } from "@tiptap/core";
|
||||||
|
|
||||||
|
const findPdfNodeByPlaceholderId = (
|
||||||
|
doc: Node,
|
||||||
|
placeholderId: string,
|
||||||
|
): { node: Node; pos: number } | null => {
|
||||||
|
let result: { node: Node; pos: number } | null = null;
|
||||||
|
|
||||||
|
doc.descendants((node, pos) => {
|
||||||
|
if (result) return false;
|
||||||
|
|
||||||
|
if (
|
||||||
|
node.type.name === "pdf" &&
|
||||||
|
node.attrs.placeholder?.id === placeholderId
|
||||||
|
) {
|
||||||
|
result = { node, pos };
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePdfUpload =
|
||||||
|
({ validateFn, onUpload }: MediaUploadOptions): UploadFn =>
|
||||||
|
async (file, editor, pos, pageId) => {
|
||||||
|
const validated = validateFn?.(file);
|
||||||
|
// @ts-ignore
|
||||||
|
if (!validated) return;
|
||||||
|
|
||||||
|
const placeholderId = generateNodeId();
|
||||||
|
|
||||||
|
let placeholderInserted = false;
|
||||||
|
|
||||||
|
const insertPlaceholder = (): Command => {
|
||||||
|
return ({ tr, state }) => {
|
||||||
|
const initialPlaceholderNode = state.schema.nodes.pdf?.create({
|
||||||
|
placeholder: {
|
||||||
|
id: placeholderId,
|
||||||
|
name: file.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!initialPlaceholderNode) return false;
|
||||||
|
|
||||||
|
const { parent } = tr.doc.resolve(pos);
|
||||||
|
const isEmptyTextBlock = parent.isTextblock && !parent.childCount;
|
||||||
|
|
||||||
|
if (isEmptyTextBlock) {
|
||||||
|
tr.replaceRangeWith(pos - 1, pos + 1, initialPlaceholderNode);
|
||||||
|
} else {
|
||||||
|
tr.insert(pos, initialPlaceholderNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const replacePlaceholderWithPdf = (attachment: IAttachment): Command => {
|
||||||
|
return ({ tr }) => {
|
||||||
|
const { pos: currentPos = null } =
|
||||||
|
findPdfNodeByPlaceholderId(tr.doc, placeholderId) || {};
|
||||||
|
|
||||||
|
if (currentPos === null || !attachment) return;
|
||||||
|
|
||||||
|
tr.setNodeMarkup(currentPos, undefined, {
|
||||||
|
src: `/api/files/${attachment.id}/${attachment.fileName}`,
|
||||||
|
name: attachment.fileName,
|
||||||
|
attachmentId: attachment.id,
|
||||||
|
size: attachment.fileSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const removePlaceholder = (): Command => {
|
||||||
|
return ({ tr }) => {
|
||||||
|
const { pos: currentPos = null } =
|
||||||
|
findPdfNodeByPlaceholderId(tr.doc, placeholderId) || {};
|
||||||
|
|
||||||
|
if (currentPos === null) return false;
|
||||||
|
|
||||||
|
tr.delete(currentPos, currentPos + 2);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertPlaceholderTimeout = setTimeout(() => {
|
||||||
|
editor.commands.command(insertPlaceholder());
|
||||||
|
placeholderInserted = true;
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const attachment: IAttachment = await onUpload(file, pageId);
|
||||||
|
|
||||||
|
clearTimeout(insertPlaceholderTimeout);
|
||||||
|
|
||||||
|
if (placeholderInserted) {
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.commands.command(replacePlaceholderWithPdf(attachment));
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
editor
|
||||||
|
.chain()
|
||||||
|
.command(insertPlaceholder())
|
||||||
|
.command(replacePlaceholderWithPdf(attachment))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clearTimeout(insertPlaceholderTimeout);
|
||||||
|
editor.commands.command(removePlaceholder());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { handlePdfUpload };
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
|
import { Node, mergeAttributes } from "@tiptap/core";
|
||||||
|
import { sanitizeUrl, isInternalFileUrl } from "../utils";
|
||||||
|
|
||||||
|
export type PdfOptions = {
|
||||||
|
view: any;
|
||||||
|
HTMLAttributes: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PdfAttributes = {
|
||||||
|
src?: string;
|
||||||
|
name?: string;
|
||||||
|
attachmentId?: string;
|
||||||
|
size?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
placeholder?: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
declare module "@tiptap/core" {
|
||||||
|
interface Commands<ReturnType> {
|
||||||
|
pdfBlock: {
|
||||||
|
setPdf: (attributes: PdfAttributes) => ReturnType;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TiptapPdf = Node.create<PdfOptions>({
|
||||||
|
name: "pdf",
|
||||||
|
|
||||||
|
group: "block",
|
||||||
|
isolating: true,
|
||||||
|
atom: true,
|
||||||
|
defining: true,
|
||||||
|
draggable: true,
|
||||||
|
|
||||||
|
addOptions() {
|
||||||
|
return {
|
||||||
|
view: null,
|
||||||
|
HTMLAttributes: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addAttributes() {
|
||||||
|
return {
|
||||||
|
src: {
|
||||||
|
default: "",
|
||||||
|
parseHTML: (element) => {
|
||||||
|
const src = element.getAttribute("src");
|
||||||
|
const sanitized = sanitizeUrl(src);
|
||||||
|
return isInternalFileUrl(sanitized) ? sanitized : "";
|
||||||
|
},
|
||||||
|
renderHTML: (attributes) => ({
|
||||||
|
src: isInternalFileUrl(attributes.src) ? sanitizeUrl(attributes.src) : "",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
default: undefined,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-name"),
|
||||||
|
renderHTML: (attributes: PdfAttributes) => ({
|
||||||
|
"data-name": attributes.name,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
attachmentId: {
|
||||||
|
default: undefined,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-attachment-id"),
|
||||||
|
renderHTML: (attributes: PdfAttributes) => ({
|
||||||
|
"data-attachment-id": attributes.attachmentId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: null,
|
||||||
|
parseHTML: (element) => element.getAttribute("data-size"),
|
||||||
|
renderHTML: (attributes: PdfAttributes) => ({
|
||||||
|
"data-size": attributes.size,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
default: 800,
|
||||||
|
parseHTML: (element) => {
|
||||||
|
const raw = element.getAttribute("width");
|
||||||
|
if (!raw) return null;
|
||||||
|
const num = parseFloat(raw);
|
||||||
|
return isNaN(num) ? null : num;
|
||||||
|
},
|
||||||
|
renderHTML: (attributes: PdfAttributes) => ({
|
||||||
|
width: attributes.width,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
default: 600,
|
||||||
|
parseHTML: (element) => {
|
||||||
|
const raw = element.getAttribute("height");
|
||||||
|
if (!raw) return null;
|
||||||
|
const num = parseFloat(raw);
|
||||||
|
return isNaN(num) ? null : num;
|
||||||
|
},
|
||||||
|
renderHTML: (attributes: PdfAttributes) => ({
|
||||||
|
height: attributes.height,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: null,
|
||||||
|
rendered: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
parseHTML() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
tag: `div[data-type="${this.name}"]`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
renderHTML({ HTMLAttributes }) {
|
||||||
|
return [
|
||||||
|
"div",
|
||||||
|
mergeAttributes(
|
||||||
|
{ "data-type": this.name },
|
||||||
|
this.options.HTMLAttributes,
|
||||||
|
HTMLAttributes,
|
||||||
|
),
|
||||||
|
[
|
||||||
|
"iframe",
|
||||||
|
{
|
||||||
|
src: isInternalFileUrl(HTMLAttributes.src) ? sanitizeUrl(HTMLAttributes.src) : "",
|
||||||
|
width: HTMLAttributes.width || 800,
|
||||||
|
height: HTMLAttributes.height || 600,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
},
|
||||||
|
|
||||||
|
addCommands() {
|
||||||
|
return {
|
||||||
|
setPdf:
|
||||||
|
(attrs: PdfAttributes) =>
|
||||||
|
({ commands }) => {
|
||||||
|
return commands.insertContent({
|
||||||
|
type: "pdf",
|
||||||
|
attrs,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
addNodeView() {
|
||||||
|
this.editor.isInitialized = true;
|
||||||
|
return ReactNodeViewRenderer(this.options.view);
|
||||||
|
},
|
||||||
|
});
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -382,6 +382,12 @@ export function sanitizeUrl(url: string | undefined): string {
|
|||||||
return sanitized === "about:blank" ? "" : sanitized;
|
return sanitized === "about:blank" ? "" : sanitized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isInternalFileUrl(url: string | undefined): boolean {
|
||||||
|
if (!url) return false;
|
||||||
|
const normalized = url.trim();
|
||||||
|
return normalized.startsWith("/api/files/") || normalized.startsWith("/files/");
|
||||||
|
}
|
||||||
|
|
||||||
const alphabet = "abcdefghijklmnopqrstuvwxyz";
|
const alphabet = "abcdefghijklmnopqrstuvwxyz";
|
||||||
export const generateNodeId = customAlphabet(alphabet, 12);
|
export const generateNodeId = customAlphabet(alphabet, 12);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { ReactNodeViewRenderer } from "@tiptap/react";
|
import { ReactNodeViewRenderer } from "@tiptap/react";
|
||||||
import { Range, Node, mergeAttributes, ResizableNodeView } from "@tiptap/core";
|
import { Range, Node, mergeAttributes } from "@tiptap/core";
|
||||||
|
import { ResizableNodeView } from "../resizable-nodeview";
|
||||||
|
import type { ResizableNodeViewDirection } from "../resizable-nodeview";
|
||||||
import { normalizeFileUrl } from "../media-utils";
|
import { normalizeFileUrl } from "../media-utils";
|
||||||
import type { ResizableNodeViewDirection } from "@tiptap/core";
|
|
||||||
|
|
||||||
export type VideoResizeOptions = {
|
export type VideoResizeOptions = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -328,12 +329,11 @@ export const TiptapVideo = Node.create<VideoOptions>({
|
|||||||
|
|
||||||
// Show skeleton background while video loads from server
|
// Show skeleton background while video loads from server
|
||||||
dom.style.pointerEvents = "none";
|
dom.style.pointerEvents = "none";
|
||||||
dom.style.background =
|
el.classList.add("media-pulse");
|
||||||
"light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-6))";
|
|
||||||
|
|
||||||
el.onloadedmetadata = () => {
|
el.onloadedmetadata = () => {
|
||||||
dom.style.pointerEvents = "";
|
dom.style.pointerEvents = "";
|
||||||
dom.style.background = "";
|
el.classList.remove("media-pulse");
|
||||||
};
|
};
|
||||||
|
|
||||||
return nodeView;
|
return nodeView;
|
||||||
|
|||||||
Generated
+150
-141
@@ -34,9 +34,6 @@ overrides:
|
|||||||
yaml@>=2.0.0 <2.8.3: 2.8.3
|
yaml@>=2.0.0 <2.8.3: 2.8.3
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
'@tiptap/core':
|
|
||||||
hash: efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00
|
|
||||||
path: patches/@tiptap__core.patch
|
|
||||||
react-arborist@3.4.0:
|
react-arborist@3.4.0:
|
||||||
hash: 419b3b02e24afe928cc006a006f6e906666aff19aa6fd7daaa788ccc2202678a
|
hash: 419b3b02e24afe928cc006a006f6e906666aff19aa6fd7daaa788ccc2202678a
|
||||||
path: patches/react-arborist@3.4.0.patch
|
path: patches/react-arborist@3.4.0.patch
|
||||||
@@ -65,7 +62,7 @@ importers:
|
|||||||
version: 3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
version: 3.4.4(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
||||||
'@hocuspocus/transformer':
|
'@hocuspocus/transformer':
|
||||||
specifier: 3.4.4
|
specifier: 3.4.4
|
||||||
version: 3.4.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)
|
version: 3.4.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)
|
||||||
'@joplin/turndown':
|
'@joplin/turndown':
|
||||||
specifier: ^4.0.82
|
specifier: ^4.0.82
|
||||||
version: 4.0.82
|
version: 4.0.82
|
||||||
@@ -77,85 +74,88 @@ importers:
|
|||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
'@tiptap/core':
|
'@tiptap/core':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
'@tiptap/extension-audio':
|
||||||
|
specifier: 3.20.4
|
||||||
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-code-block':
|
'@tiptap/extension-code-block':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-collaboration':
|
'@tiptap/extension-collaboration':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)
|
||||||
'@tiptap/extension-collaboration-caret':
|
'@tiptap/extension-collaboration-caret':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))
|
||||||
'@tiptap/extension-color':
|
'@tiptap/extension-color':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)))
|
version: 3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)))
|
||||||
'@tiptap/extension-document':
|
'@tiptap/extension-document':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-heading':
|
'@tiptap/extension-heading':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-highlight':
|
'@tiptap/extension-highlight':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-history':
|
'@tiptap/extension-history':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-image':
|
'@tiptap/extension-image':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-link':
|
'@tiptap/extension-link':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-list':
|
'@tiptap/extension-list':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-placeholder':
|
'@tiptap/extension-placeholder':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-subscript':
|
'@tiptap/extension-subscript':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-superscript':
|
'@tiptap/extension-superscript':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-table':
|
'@tiptap/extension-table':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-text':
|
'@tiptap/extension-text':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-text-align':
|
'@tiptap/extension-text-align':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-text-style':
|
'@tiptap/extension-text-style':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-typography':
|
'@tiptap/extension-typography':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-unique-id':
|
'@tiptap/extension-unique-id':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-youtube':
|
'@tiptap/extension-youtube':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/html':
|
'@tiptap/html':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)
|
||||||
'@tiptap/pm':
|
'@tiptap/pm':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4
|
version: 3.20.4
|
||||||
'@tiptap/react':
|
'@tiptap/react':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@tiptap/starter-kit':
|
'@tiptap/starter-kit':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4
|
version: 3.20.4
|
||||||
'@tiptap/suggestion':
|
'@tiptap/suggestion':
|
||||||
specifier: 3.20.4
|
specifier: 3.20.4
|
||||||
version: 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
version: 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/y-tiptap':
|
'@tiptap/y-tiptap':
|
||||||
specifier: 3.0.2
|
specifier: 3.0.2
|
||||||
version: 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
version: 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
||||||
@@ -4662,6 +4662,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tiptap/pm': ^3.20.4
|
'@tiptap/pm': ^3.20.4
|
||||||
|
|
||||||
|
'@tiptap/extension-audio@3.20.4':
|
||||||
|
resolution: {integrity: sha512-zX90pxpEYpV5jSrwtQw8Nmh2uK4WC+xwSG5MXVh4VLG8SnSE/vg/vCCqFiSHjXNfw68dctd6HJ0MJigwnuS0lw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@tiptap/core': ^3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-blockquote@3.20.4':
|
'@tiptap/extension-blockquote@3.20.4':
|
||||||
resolution: {integrity: sha512-9sskyyhYj2oKat//lyZVXCp9YrPt4oJAZnGHYWXS0xlskjsLElrfKKlM4vpbhGss3VrhQRoEGqWLnIaJYPF1zw==}
|
resolution: {integrity: sha512-9sskyyhYj2oKat//lyZVXCp9YrPt4oJAZnGHYWXS0xlskjsLElrfKKlM4vpbhGss3VrhQRoEGqWLnIaJYPF1zw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -12707,9 +12712,9 @@ snapshots:
|
|||||||
- bufferutil
|
- bufferutil
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
'@hocuspocus/transformer@3.4.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)':
|
'@hocuspocus/transformer@3.4.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(y-prosemirror@1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
'@tiptap/starter-kit': 3.20.4
|
'@tiptap/starter-kit': 3.20.4
|
||||||
y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
y-prosemirror: 1.3.7(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
||||||
@@ -15281,191 +15286,195 @@ snapshots:
|
|||||||
'@tanstack/query-core': 5.90.17
|
'@tanstack/query-core': 5.90.17
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
'@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)':
|
'@tiptap/core@3.20.4(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-blockquote@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-audio@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-bold@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-blockquote@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-bubble-menu@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-bold@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
|
dependencies:
|
||||||
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
|
'@tiptap/extension-bubble-menu@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/dom': 1.7.4
|
'@floating-ui/dom': 1.7.4
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tiptap/extension-bullet-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-bullet-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-code-block@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-code-block@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-code@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-code@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-collaboration-caret@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))':
|
'@tiptap/extension-collaboration-caret@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
'@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
'@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
||||||
|
|
||||||
'@tiptap/extension-collaboration@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)':
|
'@tiptap/extension-collaboration@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30))(yjs@13.6.30)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
'@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
'@tiptap/y-tiptap': 3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)
|
||||||
yjs: 13.6.30
|
yjs: 13.6.30
|
||||||
|
|
||||||
'@tiptap/extension-color@3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)))':
|
'@tiptap/extension-color@3.20.4(@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4)))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extension-text-style': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-text-style': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
|
|
||||||
'@tiptap/extension-document@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-document@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-dropcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-dropcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-floating-menu@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-floating-menu@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/dom': 1.7.3
|
'@floating-ui/dom': 1.7.3
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@tiptap/extension-gapcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-gapcursor@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-hard-break@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-hard-break@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-heading@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-heading@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-highlight@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-highlight@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-history@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-history@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-horizontal-rule@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-horizontal-rule@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-image@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-image@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-italic@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-italic@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-link@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-link@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
linkifyjs: 4.3.2
|
linkifyjs: 4.3.2
|
||||||
|
|
||||||
'@tiptap/extension-list-item@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-list-item@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-list-keymap@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-list-keymap@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-ordered-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-ordered-list@3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-paragraph@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-paragraph@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-placeholder@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-placeholder@3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-strike@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-strike@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-subscript@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-subscript@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-superscript@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-superscript@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-table@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-table@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/extension-text-align@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-text-align@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-text-style@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-text@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-text@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-typography@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-typography@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-underline@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-underline@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extension-unique-id@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extension-unique-id@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
uuid: 10.0.0
|
uuid: 10.0.0
|
||||||
|
|
||||||
'@tiptap/extension-youtube@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))':
|
'@tiptap/extension-youtube@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
|
|
||||||
'@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/html@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)':
|
'@tiptap/html@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(happy-dom@20.8.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
happy-dom: 20.8.4
|
happy-dom: 20.8.4
|
||||||
|
|
||||||
@@ -15490,9 +15499,9 @@ snapshots:
|
|||||||
prosemirror-transform: 1.10.4
|
prosemirror-transform: 1.10.4
|
||||||
prosemirror-view: 1.40.0
|
prosemirror-view: 1.40.0
|
||||||
|
|
||||||
'@tiptap/react@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@tiptap/react@3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
'@types/react': 18.3.12
|
'@types/react': 18.3.12
|
||||||
'@types/react-dom': 18.3.1
|
'@types/react-dom': 18.3.1
|
||||||
@@ -15502,41 +15511,41 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
use-sync-external-store: 1.6.0(react@18.3.1)
|
use-sync-external-store: 1.6.0(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@tiptap/extension-bubble-menu': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-bubble-menu': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-floating-menu': 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-floating-menu': 3.20.4(@floating-ui/dom@1.7.3)(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@floating-ui/dom'
|
- '@floating-ui/dom'
|
||||||
|
|
||||||
'@tiptap/starter-kit@3.20.4':
|
'@tiptap/starter-kit@3.20.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-blockquote': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-blockquote': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-bold': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-bold': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-bullet-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-bullet-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-code': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-code': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-code-block': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-code-block': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-document': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-document': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-dropcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-dropcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-gapcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-gapcursor': 3.20.4(@tiptap/extensions@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-hard-break': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-hard-break': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-heading': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-heading': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-horizontal-rule': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-horizontal-rule': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-italic': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-italic': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-link': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-link': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extension-list': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/extension-list-item': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-list-item': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-list-keymap': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-list-keymap': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-ordered-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
'@tiptap/extension-ordered-list': 3.20.4(@tiptap/extension-list@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-paragraph': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-paragraph': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-strike': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-strike': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-text': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-text': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extension-underline': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))
|
'@tiptap/extension-underline': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))
|
||||||
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
'@tiptap/extensions': 3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/suggestion@3.20.4(@tiptap/core@3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
'@tiptap/suggestion@3.20.4(@tiptap/core@3.20.4(@tiptap/pm@3.20.4))(@tiptap/pm@3.20.4)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/core': 3.20.4(patch_hash=efe36d923d71b90e115d2d468ea1ddaf04a78c2e43c811a1a4b667989c39ea00)(@tiptap/pm@3.20.4)
|
'@tiptap/core': 3.20.4(@tiptap/pm@3.20.4)
|
||||||
'@tiptap/pm': 3.20.4
|
'@tiptap/pm': 3.20.4
|
||||||
|
|
||||||
'@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)':
|
'@tiptap/y-tiptap@3.0.2(prosemirror-model@1.25.1)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.30))(yjs@13.6.30)':
|
||||||
|
|||||||
Reference in New Issue
Block a user