From cf58d8e3e18135a2f58f1e3cf387ebcea411abfe Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Fri, 15 Apr 2022 09:03:25 -0700 Subject: [PATCH] fix: Capture drop events in clickable padding below editor (#3376) * fix: Capture drop events in clickable padding below editor * fix: Inconsistency in drop handling --- app/components/CollectionDescription.tsx | 3 +- app/components/Editor.tsx | 67 ++++++++++++++++++++++- app/scenes/Document/components/Editor.tsx | 30 ++++------ shared/editor/commands/insertFiles.ts | 5 +- 4 files changed, 82 insertions(+), 23 deletions(-) diff --git a/app/components/CollectionDescription.tsx b/app/components/CollectionDescription.tsx index 278fdb62a..81e34b02e 100644 --- a/app/components/CollectionDescription.tsx +++ b/app/components/CollectionDescription.tsx @@ -66,7 +66,7 @@ function CollectionDescription({ collection }: Props) { throw err; } }, 1000), - [] + [collection, showToast, t] ); const handleChange = React.useCallback( @@ -111,7 +111,6 @@ function CollectionDescription({ collection }: Props) { maxLength={1000} embedsDisabled readOnlyWriteCheckboxes - grow /> ) : ( diff --git a/app/components/Editor.tsx b/app/components/Editor.tsx index 1616c2f9b..db53763c0 100644 --- a/app/components/Editor.tsx +++ b/app/components/Editor.tsx @@ -1,11 +1,16 @@ import { formatDistanceToNow } from "date-fns"; import { deburr, sortBy } from "lodash"; +import { TextSelection } from "prosemirror-state"; import * as React from "react"; import { Optional } from "utility-types"; +import insertFiles from "@shared/editor/commands/insertFiles"; import embeds from "@shared/editor/embeds"; +import { supportedImageMimeTypes } from "@shared/utils/files"; +import getDataTransferFiles from "@shared/utils/getDataTransferFiles"; import parseDocumentSlug from "@shared/utils/parseDocumentSlug"; import { isInternalUrl } from "@shared/utils/urls"; import Document from "~/models/Document"; +import ClickablePadding from "~/components/ClickablePadding"; import ErrorBoundary from "~/components/ErrorBoundary"; import HoverPreview from "~/components/HoverPreview"; import type { Props as EditorProps, Editor as SharedEditor } from "~/editor"; @@ -44,7 +49,7 @@ export type Props = Optional< onPublish?: (event: React.MouseEvent) => any; }; -function Editor(props: Props, ref: React.Ref) { +function Editor(props: Props, ref: React.RefObject) { const { id, shareId } = props; const { documents } = useStores(); const { showToast } = useToasts(); @@ -159,6 +164,58 @@ function Editor(props: Props, ref: React.Ref) { [shareId] ); + const focusAtEnd = React.useCallback(() => { + ref.current?.focusAtEnd(); + }, [ref]); + + const handleDrop = React.useCallback( + (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + const files = getDataTransferFiles(event); + const view = ref.current?.view; + if (!view) { + return; + } + + // Insert all files as attachments if any of the files are not images. + const isAttachment = files.some( + (file) => !supportedImageMimeTypes.includes(file.type) + ); + + // Find a valid position at the end of the document + const pos = TextSelection.near( + view.state.doc.resolve(view.state.doc.nodeSize - 2) + ).from; + + insertFiles(view, event, pos, files, { + uploadFile: onUploadFile, + onFileUploadStart: props.onFileUploadStart, + onFileUploadStop: props.onFileUploadStop, + onShowToast: showToast, + dictionary, + isAttachment, + }); + }, + [ + ref, + props.onFileUploadStart, + props.onFileUploadStop, + dictionary, + onUploadFile, + showToast, + ] + ); + + // see: https://stackoverflow.com/a/50233827/192065 + const handleDragOver = React.useCallback( + (event: React.DragEvent) => { + event.stopPropagation(); + event.preventDefault(); + }, + [] + ); + return ( <> @@ -175,6 +232,14 @@ function Editor(props: Props, ref: React.Ref) { placeholder={props.placeholder || ""} defaultValue={props.defaultValue || ""} /> + {props.grow && !props.readOnly && ( + + )} {activeLinkEvent && !shareId && ( ) { const titleRef = React.useRef(null); const { t } = useTranslation(); const match = useRouteMatch(); + const { + document, + title, + onChangeTitle, + isDraft, + shareId, + readOnly, + children, + multiplayer, + ...rest + } = props; const focusAtStart = React.useCallback(() => { if (ref.current) { @@ -47,12 +57,6 @@ function DocumentEditor(props: Props, ref: React.RefObject) { } }, [ref]); - const focusAtEnd = React.useCallback(() => { - if (ref.current) { - ref.current.focusAtEnd(); - } - }, [ref]); - const handleBlur = React.useCallback(() => { props.onSave({ autosave: true }); }, [props]); @@ -70,17 +74,6 @@ function DocumentEditor(props: Props, ref: React.RefObject) { [focusAtStart, ref] ); - const { - document, - title, - onChangeTitle, - isDraft, - shareId, - readOnly, - children, - multiplayer, - ...rest - } = props; const EditorComponent = multiplayer ? MultiplayerEditor : Editor; return ( @@ -121,7 +114,6 @@ function DocumentEditor(props: Props, ref: React.RefObject) { grow {...rest} /> - {!readOnly && } {children} ); diff --git a/shared/editor/commands/insertFiles.ts b/shared/editor/commands/insertFiles.ts index c32ab0acc..f88ee872f 100644 --- a/shared/editor/commands/insertFiles.ts +++ b/shared/editor/commands/insertFiles.ts @@ -21,7 +21,10 @@ export type Options = { const insertFiles = function ( view: EditorView, - event: Event | React.ChangeEvent, + event: + | Event + | React.ChangeEvent + | React.DragEvent, pos: number, files: File[], options: Options