Merge pull request #13142 from overleaf/mj-figure-modal-paste

[cm6] Add image paste handler to rich text

GitOrigin-RevId: 3c814bf64438b387b7b08b8bf89b917347371492
This commit is contained in:
Mathias Jakobsen 2023-05-19 09:35:37 +01:00 committed by Copybot
parent e895208665
commit a06118b96d
5 changed files with 72 additions and 10 deletions

View file

@ -1,4 +1,5 @@
import { FC, createContext, useContext, useReducer } from 'react' import { FC, createContext, useContext, useReducer } from 'react'
import { PastedImageData } from '../../extensions/figure-modal'
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
export enum FigureModalSource { export enum FigureModalSource {
@ -20,6 +21,7 @@ type FigureModalState = {
includeCaption: boolean includeCaption: boolean
includeLabel: boolean includeLabel: boolean
error?: string error?: string
pastedImageData?: PastedImageData
} }
type FigureModalStateUpdate = Partial<FigureModalState> type FigureModalStateUpdate = Partial<FigureModalState>
@ -55,6 +57,7 @@ const reducer = (prev: FigureModalState, action: Partial<FigureModalState>) => {
sourcePickerShown: false, sourcePickerShown: false,
getPath: undefined, getPath: undefined,
error: undefined, error: undefined,
pastedImageData: undefined,
...action, ...action,
} }
} }

View file

@ -14,11 +14,13 @@ import { ChangeSpec } from '@codemirror/state'
import SplitTestBadge from '../../../../shared/components/split-test-badge' import SplitTestBadge from '../../../../shared/components/split-test-badge'
import { import {
FigureData, FigureData,
PastedImageData,
editFigureData, editFigureData,
editFigureDataEffect, editFigureDataEffect,
} from '../../extensions/figure-modal' } from '../../extensions/figure-modal'
import { ensureEmptyLine } from '../../extensions/toolbar/commands' import { ensureEmptyLine } from '../../extensions/toolbar/commands'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useEventListener from '../../../../shared/hooks/use-event-listener'
export const FigureModal = memo(function FigureModal() { export const FigureModal = memo(function FigureModal() {
return ( return (
@ -89,8 +91,9 @@ const FigureModalContent = () => {
view.focus() view.focus()
}, [dispatch, view]) }, [dispatch, view])
useEffect(() => { useEventListener(
const listener = () => { 'figure-modal:open-modal',
useCallback(() => {
const figure = view.state.field<FigureData>(editFigureData, false) const figure = view.state.field<FigureData>(editFigureData, false)
if (!figure) { if (!figure) {
return return
@ -106,14 +109,21 @@ const FigureModalContent = () => {
includeCaption: figure.caption !== null, includeCaption: figure.caption !== null,
includeLabel: figure.label !== null, includeLabel: figure.label !== null,
}) })
}
window.addEventListener('figure-modal:open-modal', listener)
return () => {
window.removeEventListener('figure-modal:open-modal', listener)
}
}, [view, dispatch, updateExistingFigure]) }, [view, dispatch, updateExistingFigure])
)
useEventListener(
'figure-modal:paste-image',
useCallback(
(image: CustomEvent<PastedImageData>) => {
dispatch({
source: FigureModalSource.FILE_UPLOAD,
pastedImageData: image.detail,
})
},
[dispatch]
)
)
const insert = useCallback(async () => { const insert = useCallback(async () => {
const figure = view.state.field<FigureData>(editFigureData, false) const figure = view.state.field<FigureData>(editFigureData, false)

View file

@ -31,7 +31,7 @@ export enum FileUploadStatus {
export const FigureModalUploadFileSource: FC = () => { export const FigureModalUploadFileSource: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const view = useCodeMirrorViewContext() const view = useCodeMirrorViewContext()
const { dispatch } = useFigureModalContext() const { dispatch, pastedImageData } = useFigureModalContext()
const { _id: projectId } = useProjectContext() const { _id: projectId } = useProjectContext()
const [, rootFolder] = useCurrentProjectFolders() const [, rootFolder] = useCurrentProjectFolders()
const [folder, setFolder] = useState<File | null>(null) const [folder, setFolder] = useState<File | null>(null)
@ -188,6 +188,12 @@ export const FigureModalUploadFileSource: FC = () => {
dispatch, dispatch,
]) ])
useEffect(() => {
if (pastedImageData) {
uppy.addFile(pastedImageData)
}
}, [uppy, pastedImageData])
return ( return (
<> <>
<div className="figure-modal-upload"> <div className="figure-modal-upload">

View file

@ -7,6 +7,7 @@ import {
import { EditorView } from '@codemirror/view' import { EditorView } from '@codemirror/view'
import { addEffectListener, removeEffectListener } from './effect-listeners' import { addEffectListener, removeEffectListener } from './effect-listeners'
import { setMetadataEffect } from './language' import { setMetadataEffect } from './language'
import getMeta from '../../../utils/meta'
type NestedReadonly<T> = { type NestedReadonly<T> = {
readonly [P in keyof T]: NestedReadonly<T[P]> readonly [P in keyof T]: NestedReadonly<T[P]>
@ -158,3 +159,43 @@ export function waitForFileTreeUpdate(view: EditorView) {
promise, promise,
} }
} }
const ALLOWED_MIME_TYPES = new Set([
'image/jpeg',
'image/png',
'application/pdf',
])
export type PastedImageData = {
name: string
type: string
data: Blob
}
export const figureModalPasteHandler = (): Extension => {
const splitTestVariants = getMeta('ol-splitTestVariants', {})
const figureModalEnabled = splitTestVariants['figure-modal'] === 'enabled'
if (!figureModalEnabled) {
return []
}
return EditorView.domEventHandlers({
paste: evt => {
if (!evt.clipboardData || evt.clipboardData.files.length === 0) {
return
}
const file = evt.clipboardData.files[0]
if (!ALLOWED_MIME_TYPES.has(file.type)) {
return
}
window.dispatchEvent(
new CustomEvent<PastedImageData>('figure-modal:paste-image', {
detail: {
name: file.name,
type: file.type,
data: file,
},
})
)
},
})
}

View file

@ -21,6 +21,7 @@ import { toolbarPanel } from '../toolbar/toolbar-panel'
import { CurrentDoc } from '../../../../../../types/current-doc' import { CurrentDoc } from '../../../../../../types/current-doc'
import isValidTeXFile from '../../../../main/is-valid-tex-file' import isValidTeXFile from '../../../../main/is-valid-tex-file'
import { listItemMarker } from './list-item-marker' import { listItemMarker } from './list-item-marker'
import { figureModalPasteHandler } from '../figure-modal'
type Options = { type Options = {
visual: boolean visual: boolean
@ -191,4 +192,5 @@ const extension = (options: Options) => [
toolbarPanel(), toolbarPanel(),
scrollJumpAdjuster, scrollJumpAdjuster,
showContentWhenParsed, showContentWhenParsed,
figureModalPasteHandler(),
] ]