diff --git a/packages/text-editor/src/components/CollaborativeTextEditor.svelte b/packages/text-editor/src/components/CollaborativeTextEditor.svelte index 7cd3df83de..7c07eb52da 100644 --- a/packages/text-editor/src/components/CollaborativeTextEditor.svelte +++ b/packages/text-editor/src/components/CollaborativeTextEditor.svelte @@ -64,9 +64,9 @@ import { noSelectionRender, renderCursor } from './editor/collaboration' import { defaultEditorAttributes } from './editor/editorProps' import { EmojiExtension } from './extension/emoji' - import { ImageExtension } from './extension/imageExt' + import { ImageUploadExtension } from './extension/imageUploadExt' import { type FileAttachFunction } from './extension/types' - import { FileExtension } from './extension/fileExt' + import { FileUploadExtension } from './extension/fileUploadExt' import { LeftMenuExtension } from './extension/leftMenu' import { InlineCommandsExtension } from './extension/inlineCommands' import { InlinePopupExtension } from './extension/inlinePopup' @@ -279,16 +279,14 @@ if (attachFile !== undefined) { if (canEmbedFiles) { optionalExtensions.push( - FileExtension.configure({ - inline: true, + FileUploadExtension.configure({ attachFile }) ) } if (canEmbedImages) { optionalExtensions.push( - ImageExtension.configure({ - inline: true, + ImageUploadExtension.configure({ attachFile, uploadUrl: getMetadata(presentation.metadata.UploadURL) }) diff --git a/packages/text-editor/src/components/MarkupDiffViewer.svelte b/packages/text-editor/src/components/MarkupDiffViewer.svelte index 5b1efa98a4..c79c94d6c7 100644 --- a/packages/text-editor/src/components/MarkupDiffViewer.svelte +++ b/packages/text-editor/src/components/MarkupDiffViewer.svelte @@ -19,13 +19,10 @@ import { Plugin, PluginKey } from '@tiptap/pm/state' import { DecorationSet } from '@tiptap/pm/view' import { onDestroy, onMount } from 'svelte' - import { getMetadata } from '@hcengineering/platform' - import presentation from '@hcengineering/presentation' import { MarkupNode, ReferenceNode, jsonToPmNode } from '@hcengineering/text' import { calculateDecorations } from './diff/decorations' import { defaultEditorAttributes } from './editor/editorProps' - import { ImageExtension } from './extension/imageExt' import { EditorKit } from '../kits/editor-kit' export let content: MarkupNode @@ -82,14 +79,7 @@ element, content, editable: false, - extensions: [ - EditorKit, - ReferenceNode, - ImageExtension.configure({ - uploadUrl: getMetadata(presentation.metadata.UploadURL) - }), - DecorationExtension - ], + extensions: [EditorKit, ReferenceNode, DecorationExtension], onTransaction: () => { // force re-render so `editor.isActive` works as expected editor = editor diff --git a/packages/text-editor/src/components/StyledTextBox.svelte b/packages/text-editor/src/components/StyledTextBox.svelte index 591f9e3b0f..d18c23c067 100644 --- a/packages/text-editor/src/components/StyledTextBox.svelte +++ b/packages/text-editor/src/components/StyledTextBox.svelte @@ -27,7 +27,7 @@ import { completionConfig, inlineCommandsConfig } from './extensions' import { EmojiExtension } from './extension/emoji' import { FocusExtension } from './extension/focus' - import { ImageExtension } from './extension/imageExt' + import { ImageUploadExtension } from './extension/imageUploadExt' import { InlineCommandsExtension } from './extension/inlineCommands' import { type FileAttachFunction } from './extension/types' import { RefAction } from '../types' @@ -175,9 +175,7 @@ } function configureExtensions (): AnyExtension[] { - const imagePlugin = ImageExtension.configure({ - inline: true, - HTMLAttributes: {}, + const imageUploadPlugin = ImageUploadExtension.configure({ attachFile, uploadUrl: getMetadata(presentation.metadata.UploadURL) }) @@ -194,7 +192,7 @@ extensions.push(completionPlugin) } extensions.push( - imagePlugin, + imageUploadPlugin, FocusExtension.configure({ onCanBlur: (value: boolean) => (canBlur = value), onFocus: handleFocus }) ) if (enableEmojiReplace) { diff --git a/packages/text-editor/src/components/TextEditor.svelte b/packages/text-editor/src/components/TextEditor.svelte index 11a75da239..9678b0990d 100644 --- a/packages/text-editor/src/components/TextEditor.svelte +++ b/packages/text-editor/src/components/TextEditor.svelte @@ -21,6 +21,7 @@ import { AnyExtension, Content, Editor, FocusPosition, mergeAttributes } from '@tiptap/core' import Placeholder from '@tiptap/extension-placeholder' + import { ParseOptions } from '@tiptap/pm/model' import { createEventDispatcher, onDestroy, onMount } from 'svelte' import { deleteAttachment } from '../command/deleteAttachment' @@ -34,8 +35,6 @@ import { InlineStyleToolbarExtension } from './extension/inlineStyleToolbar' import { SubmitExtension } from './extension/submit' import { EditorKit } from '../kits/editor-kit' - import { FileAttachFunction } from './extension/types' - import { ParseOptions } from '@tiptap/pm/model' export let content: Markup = EmptyMarkup export let placeholder: IntlString = textEditorPlugin.string.EditorPlaceholder @@ -45,7 +44,6 @@ export let editorAttributes: Record = {} export let boundary: HTMLElement | undefined = undefined export let autofocus: FocusPosition = false - export let attachFile: FileAttachFunction | undefined = undefined let element: HTMLElement let editor: Editor diff --git a/packages/text-editor/src/components/extension/fileExt.ts b/packages/text-editor/src/components/extension/fileExt.ts index 9b139c1232..51694f1233 100644 --- a/packages/text-editor/src/components/extension/fileExt.ts +++ b/packages/text-editor/src/components/extension/fileExt.ts @@ -16,11 +16,8 @@ import { getFileUrl, PDFViewer } from '@hcengineering/presentation' import { FileNode, type FileOptions as FileNodeOptions } from '@hcengineering/text' import { showPopup } from '@hcengineering/ui' import { nodeInputRule } from '@tiptap/core' -import { type Node as ProseMirrorNode } from '@tiptap/pm/model' import { Plugin, PluginKey } from '@tiptap/pm/state' -import { type EditorView } from '@tiptap/pm/view' import filesize from 'filesize' - import { type FileAttachFunction } from './types' const attachIcon = @@ -33,7 +30,6 @@ const imageIcon = */ export interface FileOptions extends FileNodeOptions { attachFile?: FileAttachFunction - reportNode?: (id: string, node: ProseMirrorNode) => void } /** @@ -84,7 +80,6 @@ export const FileExtension = FileNode.extend({ let href: string = '' if (id != null) { href = getFileUrl(id, 'full', fileName) - this.options.reportNode?.(id, node) } const linkAttributes = { class: 'file-name', @@ -115,74 +110,15 @@ export const FileExtension = FileNode.extend({ }, addProseMirrorPlugins () { - const opt = this.options - function handleDrop ( - view: EditorView, - pos: { pos: number, inside: number } | null, - dataTransfer: DataTransfer - ): any { - let result = false - - const files = dataTransfer?.files - if (files !== undefined && opt.attachFile !== undefined) { - let hasNotImageFile: boolean = false - for (let i = 0; i < files.length; i++) { - const file = files.item(i) - if (file != null) { - if (!file.type.startsWith('image')) { - hasNotImageFile = true - } - } - } - if (!hasNotImageFile) { - return false - } - - for (let i = 0; i < files.length; i++) { - const file = files.item(i) - if (file != null) { - result = true - void opt.attachFile(file).then((id) => { - if (id !== undefined) { - const node = view.state.schema.nodes.file.create({ - 'file-id': id.file, - 'data-file-name': file.name, - 'data-file-type': file.type, - 'data-file-size': file.size - }) - const transaction = view.state.tr.insert(pos?.pos ?? 0, node) - view.dispatch(transaction) - } - }) - } - } - } - return result - } return [ new Plugin({ - key: new PluginKey('handle-file-paste'), + key: new PluginKey('handle-file-open'), props: { - handlePaste (view, event) { - const dataTransfer = event.clipboardData - if (dataTransfer !== null) { - const res = handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer) - if (res === true) { - event.preventDefault() - event.stopPropagation() - } - return res - } - }, - handleDrop (view, event) { - event.preventDefault() - event.stopPropagation() - const dataTransfer = event.dataTransfer - if (dataTransfer !== null) { - return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) - } - }, handleDoubleClickOn (view, pos, node, nodePos, event) { + if (node.type.name !== FileExtension.name) { + return + } + const fileId = node.attrs['file-id'] ?? '' if (fileId === '') return const fileName = node.attrs['data-file-name'] ?? '' diff --git a/packages/text-editor/src/components/extension/fileUploadExt.ts b/packages/text-editor/src/components/extension/fileUploadExt.ts new file mode 100644 index 0000000000..b7e0a5576f --- /dev/null +++ b/packages/text-editor/src/components/extension/fileUploadExt.ts @@ -0,0 +1,106 @@ +// +// Copyright © 2023 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +import { Extension } from '@tiptap/core' +import { Plugin, PluginKey } from '@tiptap/pm/state' +import { type EditorView } from '@tiptap/pm/view' + +import { type FileAttachFunction } from './types' + +/** + * @public + */ +export interface FileUploadOptions { + attachFile?: FileAttachFunction +} + +/** + * @public + */ +export const FileUploadExtension = Extension.create({ + name: 'file-upload-ext', + + addProseMirrorPlugins () { + const opt = this.options + function handleDrop ( + view: EditorView, + pos: { pos: number, inside: number } | null, + dataTransfer: DataTransfer + ): any { + let result = false + + const files = dataTransfer?.files + if (files !== undefined && opt.attachFile !== undefined) { + let hasNotImageFile: boolean = false + for (let i = 0; i < files.length; i++) { + const file = files.item(i) + if (file != null) { + if (!file.type.startsWith('image/')) { + hasNotImageFile = true + } + } + } + if (!hasNotImageFile) { + return false + } + + for (let i = 0; i < files.length; i++) { + const file = files.item(i) + if (file != null && !file.type.startsWith('image/')) { + result = true + void opt.attachFile(file).then((id) => { + if (id !== undefined) { + const node = view.state.schema.nodes.file.create({ + 'file-id': id.file, + 'data-file-name': file.name, + 'data-file-type': file.type, + 'data-file-size': file.size + }) + const transaction = view.state.tr.insert(pos?.pos ?? 0, node) + view.dispatch(transaction) + } + }) + } + } + } + return result + } + return [ + new Plugin({ + key: new PluginKey('handle-file-paste'), + props: { + handlePaste (view, event) { + const dataTransfer = event.clipboardData + if (dataTransfer !== null) { + const res = handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer) + if (res === true) { + event.preventDefault() + event.stopPropagation() + } + return res + } + }, + handleDrop (view, event) { + event.preventDefault() + event.stopPropagation() + const dataTransfer = event.dataTransfer + if (dataTransfer !== null) { + return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) + } + } + } + }) + ] + } +}) diff --git a/packages/text-editor/src/components/extension/imageExt.ts b/packages/text-editor/src/components/extension/imageExt.ts index ed59ddb27e..4cee02473d 100644 --- a/packages/text-editor/src/components/extension/imageExt.ts +++ b/packages/text-editor/src/components/extension/imageExt.ts @@ -12,16 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -import { PDFViewer, getImageSize } from '@hcengineering/presentation' +import { PDFViewer } from '@hcengineering/presentation' +import { showPopup } from '@hcengineering/ui' import { ImageNode, type ImageOptions as ImageNodeOptions } from '@hcengineering/text' -import { type IconSize, getIconSize2x, showPopup } from '@hcengineering/ui' +import { type IconSize, getIconSize2x } from '@hcengineering/ui' import { mergeAttributes, nodeInputRule } from '@tiptap/core' -import { type Node as ProseMirrorNode } from '@tiptap/pm/model' import { Plugin, PluginKey } from '@tiptap/pm/state' -import { type EditorView } from '@tiptap/pm/view' -import { setPlatformStatus, unknownError } from '@hcengineering/platform' - -import { type FileAttachFunction } from './types' /** * @public @@ -32,8 +28,6 @@ export type ImageAlignment = 'center' | 'left' | 'right' * @public */ export interface ImageOptions extends ImageNodeOptions { - attachFile?: FileAttachFunction - reportNode?: (id: string, node: ProseMirrorNode) => void uploadUrl: string } @@ -70,16 +64,8 @@ declare module '@tiptap/core' { */ export const inputRegex = /(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/ -function getType (type: string): 'image' | 'other' { - if (type.startsWith('image/')) { - return 'image' - } - - return 'other' -} - // This is a simplified version of getFileUrl from presentation plugin, which we cannot use -function getFileUrl (fileId: string, size: IconSize = 'full', uploadUrl: string): string { +export function getFileUrl (fileId: string, size: IconSize = 'full', uploadUrl: string): string { return `${uploadUrl}?file=${fileId}&size=${size as string}` } @@ -149,7 +135,6 @@ export const ImageExtension = ImageNode.extend({ } imgAttributes.class = 'text-editor-image' imgAttributes.contentEditable = false - this.options.reportNode?.(id, node) } return ['div', divAttributes, ['img', imgAttributes]] @@ -203,90 +188,12 @@ export const ImageExtension = ImageNode.extend({ }, addProseMirrorPlugins () { - const attachFile = this.options.attachFile - const uploadUrl = this.options.uploadUrl - - function handleDrop ( - view: EditorView, - pos: { pos: number, inside: number } | null, - dataTransfer: DataTransfer - ): any { - const uris = (dataTransfer.getData('text/uri-list') ?? '').split('\r\n').filter((it) => !it.startsWith('#')) - let result = false - for (const uri of uris) { - if (uri !== '') { - const url = new URL(uri) - if (uploadUrl === undefined || !url.href.includes(uploadUrl)) { - continue - } - - const _file = (url.searchParams.get('file') ?? '').split('/').join('') - - if (_file.trim().length === 0) { - continue - } - - const ctype = dataTransfer.getData('application/contentType') - const type = getType(ctype ?? 'other') - - if (type === 'image') { - const node = view.state.schema.nodes.image.create({ - 'file-id': _file, - src: getFileUrl(_file, 'full', uploadUrl) - }) - const transaction = view.state.tr.insert(pos?.pos ?? 0, node) - view.dispatch(transaction) - result = true - } - } - } - if (result) { - return result - } - - const files = dataTransfer?.files - if (files !== undefined && attachFile !== undefined) { - for (let i = 0; i < files.length; i++) { - const file = files.item(i) - if (file != null) { - result = true - void handleImageUpload(file, view, pos, attachFile, uploadUrl) - } - } - } - return result - } - return [ - new Plugin({ - key: new PluginKey('handle-image-paste'), - props: { - handlePaste (view, event) { - const dataTransfer = event.clipboardData - if (dataTransfer !== null) { - const res = handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer) - if (res === true) { - event.preventDefault() - event.stopPropagation() - } - return res - } - }, - handleDrop (view, event) { - event.preventDefault() - event.stopPropagation() - const dataTransfer = event.dataTransfer - if (dataTransfer !== null) { - return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) - } - } - } - }), new Plugin({ key: new PluginKey('handle-image-open'), props: { handleDoubleClickOn (view, pos, node, nodePos, event) { - if (node.type.name !== 'image') { + if (node.type.name !== ImageExtension.name) { return } @@ -310,37 +217,3 @@ export const ImageExtension = ImageNode.extend({ ] } }) - -async function handleImageUpload ( - file: File, - view: EditorView, - pos: { pos: number, inside: number } | null, - attachFile: FileAttachFunction, - uploadUrl: string -): Promise { - const attached = await attachFile(file) - - if (attached === undefined) { - return - } - - if (!attached.type.includes('image')) { - return - } - - try { - const url = getFileUrl(attached.file, 'full', uploadUrl) - const size = await getImageSize(file, url) - const node = view.state.schema.nodes.image.create({ - 'file-id': attached.file, - src: url, - width: Math.round(size.width / size.pixelRatio) - }) - - const transaction = view.state.tr.insert(pos?.pos ?? 0, node) - - view.dispatch(transaction) - } catch (e) { - void setPlatformStatus(unknownError(e)) - } -} diff --git a/packages/text-editor/src/components/extension/imageUploadExt.ts b/packages/text-editor/src/components/extension/imageUploadExt.ts new file mode 100644 index 0000000000..bee4b3963b --- /dev/null +++ b/packages/text-editor/src/components/extension/imageUploadExt.ts @@ -0,0 +1,166 @@ +// +// Copyright © 2023 Hardcore Engineering Inc. +// +// Licensed under the Eclipse Public License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. You may +// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +import { getImageSize } from '@hcengineering/presentation' +import { Extension } from '@tiptap/core' +import { Plugin, PluginKey } from '@tiptap/pm/state' +import { type EditorView } from '@tiptap/pm/view' +import { setPlatformStatus, unknownError } from '@hcengineering/platform' + +import { getFileUrl } from './imageExt' +import { type FileAttachFunction } from './types' + +/** + * @public + */ +export interface ImageUploadOptions { + attachFile?: FileAttachFunction + uploadUrl: string +} + +function getType (type: string): 'image' | 'other' { + if (type.startsWith('image/')) { + return 'image' + } + + return 'other' +} + +/** + * @public + */ +export const ImageUploadExtension = Extension.create({ + addOptions () { + return { + uploadUrl: '' + } + }, + + addProseMirrorPlugins () { + const attachFile = this.options.attachFile + const uploadUrl = this.options.uploadUrl + + function handleDrop ( + view: EditorView, + pos: { pos: number, inside: number } | null, + dataTransfer: DataTransfer + ): any { + const uris = (dataTransfer.getData('text/uri-list') ?? '').split('\r\n').filter((it) => !it.startsWith('#')) + let result = false + for (const uri of uris) { + if (uri !== '') { + const url = new URL(uri) + if (uploadUrl === undefined || !url.href.includes(uploadUrl)) { + continue + } + + const _file = (url.searchParams.get('file') ?? '').split('/').join('') + + if (_file.trim().length === 0) { + continue + } + + const ctype = dataTransfer.getData('application/contentType') + const type = getType(ctype ?? 'other') + + if (type === 'image') { + const node = view.state.schema.nodes.image.create({ + 'file-id': _file, + src: getFileUrl(_file, 'full', uploadUrl) + }) + const transaction = view.state.tr.insert(pos?.pos ?? 0, node) + view.dispatch(transaction) + result = true + } + } + } + if (result) { + return result + } + + const files = dataTransfer?.files + if (files !== undefined && attachFile !== undefined) { + for (let i = 0; i < files.length; i++) { + const file = files.item(i) + if (file != null && file.type.startsWith('image/')) { + result = true + void handleImageUpload(file, view, pos, attachFile, uploadUrl) + } + } + } + return result + } + + return [ + new Plugin({ + key: new PluginKey('handle-image-paste'), + props: { + handlePaste (view, event) { + const dataTransfer = event.clipboardData + if (dataTransfer !== null) { + const res = handleDrop(view, { pos: view.state.selection.$from.pos, inside: 0 }, dataTransfer) + if (res === true) { + event.preventDefault() + event.stopPropagation() + } + return res + } + }, + handleDrop (view, event) { + event.preventDefault() + event.stopPropagation() + const dataTransfer = event.dataTransfer + if (dataTransfer !== null) { + return handleDrop(view, view.posAtCoords({ left: event.x, top: event.y }), dataTransfer) + } + } + } + }) + ] + } +}) + +async function handleImageUpload ( + file: File, + view: EditorView, + pos: { pos: number, inside: number } | null, + attachFile: FileAttachFunction, + uploadUrl: string +): Promise { + const attached = await attachFile(file) + + if (attached === undefined) { + return + } + + if (!attached.type.includes('image')) { + return + } + + try { + const url = getFileUrl(attached.file, 'full', uploadUrl) + const size = await getImageSize(file, url) + const node = view.state.schema.nodes.image.create({ + 'file-id': attached.file, + src: url, + width: Math.round(size.width / size.pixelRatio) + }) + + const transaction = view.state.tr.insert(pos?.pos ?? 0, node) + + view.dispatch(transaction) + } catch (e) { + void setPlatformStatus(unknownError(e)) + } +} diff --git a/packages/text-editor/src/index.ts b/packages/text-editor/src/index.ts index 10af29dd16..f6f6269667 100644 --- a/packages/text-editor/src/index.ts +++ b/packages/text-editor/src/index.ts @@ -73,6 +73,7 @@ export { export { InlinePopupExtension } from './components/extension/inlinePopup' export { InlineStyleToolbarExtension, type InlineStyleToolbarOptions } from './components/extension/inlineStyleToolbar' export { ImageExtension, type ImageOptions } from './components/extension/imageExt' +export { ImageUploadExtension, type ImageUploadOptions } from './components/extension/imageUploadExt' export { TodoItemExtension, TodoListExtension } from './components/extension/todo' export * from './command/deleteAttachment' diff --git a/packages/text-editor/src/kits/editor-kit.ts b/packages/text-editor/src/kits/editor-kit.ts index c3b8243e35..5c8146e1ad 100644 --- a/packages/text-editor/src/kits/editor-kit.ts +++ b/packages/text-editor/src/kits/editor-kit.ts @@ -22,10 +22,14 @@ import TaskList from '@tiptap/extension-task-list' import { DefaultKit, type DefaultKitOptions } from './default-kit' +import { getMetadata } from '@hcengineering/platform' +import presentation from '@hcengineering/presentation' import { CodeBlockExtension, codeBlockOptions } from '@hcengineering/text' import { CodemarkExtension } from '../components/extension/codemark' import { NodeUuidExtension } from '../components/extension/nodeUuid' import { Table, TableCell, TableRow } from '../components/extension/table' +import { type ImageOptions, ImageExtension } from '../components/extension/imageExt' +import { type FileOptions, FileExtension } from '../components/extension/fileExt' const headingLevels: Level[] = [1, 2, 3] @@ -53,6 +57,8 @@ export const taskListExtensions = [ export interface EditorKitOptions extends DefaultKitOptions { history?: false + file?: Partial | false + image?: Partial | false } export const EditorKit = Extension.create({ @@ -86,7 +92,24 @@ export const EditorKit = Extension.create({ ] }), NodeUuidExtension, - ...tableExtensions + ...tableExtensions, + ...(this.options.file !== false + ? [ + FileExtension.configure({ + inline: true, + ...this.options.file + }) + ] + : []), + ...(this.options.image !== false + ? [ + ImageExtension.configure({ + inline: true, + uploadUrl: getMetadata(presentation.metadata.UploadURL) ?? '', + ...this.options.image + }) + ] + : []) // ...taskListExtensions // Disable since tasks are not working properly now. ] } diff --git a/plugins/document-resources/src/components/DocumentEditor.svelte b/plugins/document-resources/src/components/DocumentEditor.svelte index 1c1c74b365..4cc892ddb7 100644 --- a/plugins/document-resources/src/components/DocumentEditor.svelte +++ b/plugins/document-resources/src/components/DocumentEditor.svelte @@ -21,7 +21,7 @@ import { CollaboratorEditor, HeadingsExtension, - ImageOptions, + ImageUploadOptions, SvelteNodeViewRenderer, TodoItemExtension, TodoListExtension @@ -37,7 +37,7 @@ export let object: Document export let readonly = false export let boundary: HTMLElement | undefined = undefined - export let attachFile: ImageOptions['attachFile'] | undefined = undefined + export let attachFile: ImageUploadOptions['attachFile'] | undefined = undefined export let focusIndex = -1 export let overflow: 'auto' | 'none' = 'none' export let editorAttributes: Record = {}