diff --git a/apps/client/src/features/editor/components/common/file-upload-handler.tsx b/apps/client/src/features/editor/components/common/file-upload-handler.tsx new file mode 100644 index 00000000..87c84bc2 --- /dev/null +++ b/apps/client/src/features/editor/components/common/file-upload-handler.tsx @@ -0,0 +1,45 @@ +import type { EditorView } from "@tiptap/pm/view"; +import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx"; +import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx"; + +export const handleFilePaste = ( + view: EditorView, + event: ClipboardEvent, + pageId: string, +) => { + if (event.clipboardData?.files.length) { + event.preventDefault(); + const [file] = Array.from(event.clipboardData.files); + const pos = view.state.selection.from; + + if (file) { + uploadImageAction(file, view, pos, pageId); + uploadVideoAction(file, view, pos, pageId); + } + return true; + } + return false; +}; + +export const handleFileDrop = ( + view: EditorView, + event: DragEvent, + moved: boolean, + pageId: string, +) => { + if (!moved && event.dataTransfer?.files.length) { + event.preventDefault(); + const [file] = Array.from(event.dataTransfer.files); + const coordinates = view.posAtCoords({ + left: event.clientX, + top: event.clientY, + }); + // here we deduct 1 from the pos or else the image will create an extra node + if (file) { + uploadImageAction(file, view, coordinates?.pos ?? 0 - 1, pageId); + uploadVideoAction(file, view, coordinates?.pos ?? 0 - 1, pageId); + } + return true; + } + return false; +}; diff --git a/apps/client/src/features/editor/components/image/image-view.tsx b/apps/client/src/features/editor/components/image/image-view.tsx index 6d4051a2..378fa64b 100644 --- a/apps/client/src/features/editor/components/image/image-view.tsx +++ b/apps/client/src/features/editor/components/image/image-view.tsx @@ -1,7 +1,7 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; import { useMemo } from "react"; import { Image } from "@mantine/core"; -import { getBackendUrl } from "@/lib/config.ts"; +import { getFileUrl } from "@/lib/config.ts"; export default function ImageView(props: NodeViewProps) { const { node, selected } = props; @@ -23,9 +23,9 @@ export default function ImageView(props: NodeViewProps) { > diff --git a/apps/client/src/features/editor/components/image/upload-image-action.tsx b/apps/client/src/features/editor/components/image/upload-image-action.tsx index d0a4e1f4..a23f2c17 100644 --- a/apps/client/src/features/editor/components/image/upload-image-action.tsx +++ b/apps/client/src/features/editor/components/image/upload-image-action.tsx @@ -4,7 +4,6 @@ import { uploadFile } from "@/features/page/services/page-service.ts"; export const uploadImageAction = handleImageUpload({ onUpload: async (file: File, pageId: string): Promise => { try { - console.log("dont upload"); return await uploadFile(file, pageId); } catch (err) { console.error("failed to upload image", err); diff --git a/apps/client/src/features/editor/components/video/video-view.tsx b/apps/client/src/features/editor/components/video/video-view.tsx index 1c8db942..b5feeb7e 100644 --- a/apps/client/src/features/editor/components/video/video-view.tsx +++ b/apps/client/src/features/editor/components/video/video-view.tsx @@ -1,6 +1,6 @@ import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; import { useMemo } from "react"; -import { getBackendUrl } from "@/lib/config.ts"; +import { getFileUrl } from "@/lib/config.ts"; export default function VideoView(props: NodeViewProps) { const { node, selected } = props; @@ -24,7 +24,7 @@ export default function VideoView(props: NodeViewProps) { preload="metadata" width={width} controls - src={getBackendUrl() + src} + src={getFileUrl(src)} className={selected && "ProseMirror-selectednode"} /> diff --git a/apps/client/src/features/editor/page-editor.tsx b/apps/client/src/features/editor/page-editor.tsx index c96369ff..0403682e 100644 --- a/apps/client/src/features/editor/page-editor.tsx +++ b/apps/client/src/features/editor/page-editor.tsx @@ -29,12 +29,15 @@ import EditorSkeleton from "@/features/editor/components/editor-skeleton"; import { EditorBubbleMenu } from "@/features/editor/components/bubble-menu/bubble-menu"; import TableCellMenu from "@/features/editor/components/table/table-cell-menu.tsx"; import TableMenu from "@/features/editor/components/table/table-menu.tsx"; -import { handleMediaDrop, handleMediaPaste } from "@docmost/editor-ext"; import ImageMenu from "@/features/editor/components/image/image-menu.tsx"; import CalloutMenu from "@/features/editor/components/callout/callout-menu.tsx"; import { uploadImageAction } from "@/features/editor/components/image/upload-image-action.tsx"; import { uploadVideoAction } from "@/features/editor/components/video/upload-video-action.tsx"; import VideoMenu from "@/features/editor/components/video/video-menu.tsx"; +import { + handleFileDrop, + handleFilePaste, +} from "@/features/editor/components/common/file-upload-handler.tsx"; interface PageEditorProps { pageId: string; @@ -112,14 +115,9 @@ export default function PageEditor({ pageId, editable }: PageEditorProps) { } }, }, - handlePaste: (view, event) => { - handleMediaPaste(view, event, uploadImageAction, pageId); - handleMediaPaste(view, event, uploadVideoAction, pageId); - }, - handleDrop: (view, event, _slice, moved) => { - handleMediaDrop(view, event, moved, uploadImageAction, pageId); - handleMediaDrop(view, event, moved, uploadVideoAction, pageId); - }, + handlePaste: (view, event) => handleFilePaste(view, event, pageId), + handleDrop: (view, event, _slice, moved) => + handleFileDrop(view, event, moved, pageId), }, onCreate({ editor }) { if (editor) { diff --git a/apps/client/src/features/editor/styles/media.css b/apps/client/src/features/editor/styles/media.css index ef616d74..f288e3b0 100644 --- a/apps/client/src/features/editor/styles/media.css +++ b/apps/client/src/features/editor/styles/media.css @@ -5,6 +5,9 @@ } .node-image, .node-video { + margin-top: 8px; + margin-bottom: 8px; + &.ProseMirror-selectednode { outline: none; } diff --git a/apps/client/src/lib/config.ts b/apps/client/src/lib/config.ts index 8383aa64..c7ff5448 100644 --- a/apps/client/src/lib/config.ts +++ b/apps/client/src/lib/config.ts @@ -42,3 +42,7 @@ export function getAvatarUrl(avatarUrl: string) { export function getSpaceUrl(spaceSlug: string) { return "/s/" + spaceSlug; } + +export function getFileUrl(src: string) { + return src.startsWith("/files/") ? getBackendUrl() + src : src; +} diff --git a/packages/editor-ext/src/lib/image/image-upload.ts b/packages/editor-ext/src/lib/image/image-upload.ts index fef472af..2c197e3a 100644 --- a/packages/editor-ext/src/lib/image/image-upload.ts +++ b/packages/editor-ext/src/lib/image/image-upload.ts @@ -6,9 +6,9 @@ import { MediaUploadOptions, UploadFn } from "../media-utils"; const uploadKey = new PluginKey("image-upload"); export const ImageUploadPlugin = ({ - placeHolderClass, + placeholderClass, }: { - placeHolderClass: string; + placeholderClass: string; }) => new Plugin({ key: uploadKey, @@ -27,7 +27,7 @@ export const ImageUploadPlugin = ({ const placeholder = document.createElement("div"); placeholder.setAttribute("class", "img-placeholder"); const image = document.createElement("img"); - image.setAttribute("class", placeHolderClass); + image.setAttribute("class", placeholderClass); image.src = src; placeholder.appendChild(image); const deco = Decoration.widget(pos + 1, placeholder, { diff --git a/packages/editor-ext/src/lib/image/image.ts b/packages/editor-ext/src/lib/image/image.ts index 810ea376..63b0f1a0 100644 --- a/packages/editor-ext/src/lib/image/image.ts +++ b/packages/editor-ext/src/lib/image/image.ts @@ -141,7 +141,7 @@ export const TiptapImage = Image.extend({ addProseMirrorPlugins() { return [ ImageUploadPlugin({ - placeHolderClass: "image-upload", + placeholderClass: "image-upload", }), ]; }, diff --git a/packages/editor-ext/src/lib/media-utils.ts b/packages/editor-ext/src/lib/media-utils.ts index 039dd406..82d57994 100644 --- a/packages/editor-ext/src/lib/media-utils.ts +++ b/packages/editor-ext/src/lib/media-utils.ts @@ -7,44 +7,6 @@ export type UploadFn = ( pageId: string, ) => void; -export const handleMediaPaste = ( - view: EditorView, - event: ClipboardEvent, - uploadFn: UploadFn, - pageId: string, -) => { - if (event.clipboardData?.files.length) { - event.preventDefault(); - const [file] = Array.from(event.clipboardData.files); - const pos = view.state.selection.from; - - if (file) uploadFn(file, view, pos, pageId); - return true; - } - return false; -}; - -export const handleMediaDrop = ( - view: EditorView, - event: DragEvent, - moved: boolean, - uploadFn: UploadFn, - pageId: string, -) => { - if (!moved && event.dataTransfer?.files.length) { - event.preventDefault(); - const [file] = Array.from(event.dataTransfer.files); - const coordinates = view.posAtCoords({ - left: event.clientX, - top: event.clientY, - }); - // here we deduct 1 from the pos or else the image will create an extra node - if (file) uploadFn(file, view, coordinates?.pos ?? 0 - 1, pageId); - return true; - } - return false; -}; - export interface MediaUploadOptions { validateFn?: (file: File) => void; onUpload: (file: File, pageId: string) => Promise; diff --git a/packages/editor-ext/src/lib/video/video-upload.ts b/packages/editor-ext/src/lib/video/video-upload.ts index 168ce5e2..60e8bed2 100644 --- a/packages/editor-ext/src/lib/video/video-upload.ts +++ b/packages/editor-ext/src/lib/video/video-upload.ts @@ -6,9 +6,9 @@ import { MediaUploadOptions, UploadFn } from "../media-utils"; const uploadKey = new PluginKey("video-upload"); export const VideoUploadPlugin = ({ - placeHolderClass, + placeholderClass, }: { - placeHolderClass: string; + placeholderClass: string; }) => new Plugin({ key: uploadKey, @@ -27,7 +27,7 @@ export const VideoUploadPlugin = ({ const placeholder = document.createElement("div"); placeholder.setAttribute("class", "video-placeholder"); const video = document.createElement("video"); - video.setAttribute("class", placeHolderClass); + video.setAttribute("class", placeholderClass); video.src = src; placeholder.appendChild(video); const deco = Decoration.widget(pos + 1, placeholder, { diff --git a/packages/editor-ext/src/lib/video/video.ts b/packages/editor-ext/src/lib/video/video.ts index 2599c45d..f3d543cd 100644 --- a/packages/editor-ext/src/lib/video/video.ts +++ b/packages/editor-ext/src/lib/video/video.ts @@ -28,8 +28,6 @@ declare module "@tiptap/core" { } } -const VIDEO_INPUT_REGEX = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/; - export const TiptapVideo = Node.create({ name: "video", @@ -123,23 +121,10 @@ export const TiptapVideo = Node.create({ return ReactNodeViewRenderer(this.options.view); }, - addInputRules() { - return [ - nodeInputRule({ - find: VIDEO_INPUT_REGEX, - type: this.type, - getAttributes: (match) => { - const [, , src] = match; - return { src }; - }, - }), - ]; - }, - addProseMirrorPlugins() { return [ VideoUploadPlugin({ - placeHolderClass: "video-upload", + placeholderClass: "video-upload", }), ]; },