mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
e895208665
commit
a06118b96d
5 changed files with 72 additions and 10 deletions
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue