fix: Capture drop events in clickable padding below editor (#3376)

* fix: Capture drop events in clickable padding below editor

* fix: Inconsistency in drop handling
This commit is contained in:
Tom Moor
2022-04-15 09:03:25 -07:00
committed by GitHub
parent 0ecfa95efc
commit cf58d8e3e1
4 changed files with 82 additions and 23 deletions

View File

@@ -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
/>
</React.Suspense>
) : (

View File

@@ -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<SharedEditor>) {
function Editor(props: Props, ref: React.RefObject<SharedEditor>) {
const { id, shareId } = props;
const { documents } = useStores();
const { showToast } = useToasts();
@@ -159,6 +164,58 @@ function Editor(props: Props, ref: React.Ref<SharedEditor>) {
[shareId]
);
const focusAtEnd = React.useCallback(() => {
ref.current?.focusAtEnd();
}, [ref]);
const handleDrop = React.useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
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<HTMLDivElement>) => {
event.stopPropagation();
event.preventDefault();
},
[]
);
return (
<ErrorBoundary reloadOnChunkMissing>
<>
@@ -175,6 +232,14 @@ function Editor(props: Props, ref: React.Ref<SharedEditor>) {
placeholder={props.placeholder || ""}
defaultValue={props.defaultValue || ""}
/>
{props.grow && !props.readOnly && (
<ClickablePadding
onClick={focusAtEnd}
onDrop={handleDrop}
onDragOver={handleDragOver}
grow
/>
)}
{activeLinkEvent && !shareId && (
<HoverPreview
node={activeLinkEvent.target as HTMLAnchorElement}

View File

@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import { useRouteMatch } from "react-router-dom";
import fullPackage from "@shared/editor/packages/full";
import Document from "~/models/Document";
import ClickablePadding from "~/components/ClickablePadding";
import { RefHandle } from "~/components/ContentEditable";
import DocumentMetaWithViews from "~/components/DocumentMetaWithViews";
import Editor, { Props as EditorProps } from "~/components/Editor";
@@ -40,6 +39,17 @@ function DocumentEditor(props: Props, ref: React.RefObject<any>) {
const titleRef = React.useRef<RefHandle>(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<any>) {
}
}, [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<any>) {
[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<any>) {
grow
{...rest}
/>
{!readOnly && <ClickablePadding onClick={focusAtEnd} grow />}
{children}
</Flex>
);

View File

@@ -21,7 +21,10 @@ export type Options = {
const insertFiles = function (
view: EditorView,
event: Event | React.ChangeEvent<HTMLInputElement>,
event:
| Event
| React.ChangeEvent<HTMLInputElement>
| React.DragEvent<HTMLDivElement>,
pos: number,
files: File[],
options: Options