2023-07-31 21:43:41 +00:00
|
|
|
import type { Doc } from 'yjs/dist/src/utils/Doc'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
|
|
|
import { HocuspocusProvider } from '@hocuspocus/provider'
|
|
|
|
import { isTextSelection } from '@tiptap/core'
|
2023-03-08 16:35:13 +00:00
|
|
|
import { Bold } from '@tiptap/extension-bold'
|
|
|
|
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
|
|
|
|
import { BulletList } from '@tiptap/extension-bullet-list'
|
|
|
|
import { CharacterCount } from '@tiptap/extension-character-count'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Collaboration } from '@tiptap/extension-collaboration'
|
|
|
|
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
|
|
|
|
import { Document } from '@tiptap/extension-document'
|
|
|
|
import { Dropcursor } from '@tiptap/extension-dropcursor'
|
|
|
|
import { FloatingMenu } from '@tiptap/extension-floating-menu'
|
|
|
|
import Focus from '@tiptap/extension-focus'
|
2023-03-08 16:35:13 +00:00
|
|
|
import { Gapcursor } from '@tiptap/extension-gapcursor'
|
|
|
|
import { HardBreak } from '@tiptap/extension-hard-break'
|
|
|
|
import { Heading } from '@tiptap/extension-heading'
|
|
|
|
import { Highlight } from '@tiptap/extension-highlight'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
|
|
|
|
import { Image } from '@tiptap/extension-image'
|
|
|
|
import { Italic } from '@tiptap/extension-italic'
|
2023-03-08 16:35:13 +00:00
|
|
|
import { Link } from '@tiptap/extension-link'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { ListItem } from '@tiptap/extension-list-item'
|
|
|
|
import { OrderedList } from '@tiptap/extension-ordered-list'
|
2023-03-08 16:35:13 +00:00
|
|
|
import { Paragraph } from '@tiptap/extension-paragraph'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Placeholder } from '@tiptap/extension-placeholder'
|
|
|
|
import { Strike } from '@tiptap/extension-strike'
|
|
|
|
import { Text } from '@tiptap/extension-text'
|
|
|
|
import { Underline } from '@tiptap/extension-underline'
|
|
|
|
import { createEffect, createSignal, onCleanup } from 'solid-js'
|
|
|
|
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
|
|
|
|
import uniqolor from 'uniqolor'
|
|
|
|
import * as Y from 'yjs'
|
|
|
|
|
|
|
|
import { useEditorContext } from '../../context/editor'
|
|
|
|
import { useLocalize } from '../../context/localize'
|
|
|
|
import { useSession } from '../../context/session'
|
|
|
|
import { useSnackbar } from '../../context/snackbar'
|
|
|
|
import { handleImageUpload } from '../../utils/handleImageUpload'
|
|
|
|
|
2024-02-04 11:25:21 +00:00
|
|
|
import { BlockquoteBubbleMenu, FigureBubbleMenu, IncutBubbleMenu } from './BubbleMenu'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { EditorFloatingMenu } from './EditorFloatingMenu'
|
2024-02-04 11:25:21 +00:00
|
|
|
import { TextBubbleMenu } from './TextBubbleMenu'
|
2023-11-14 15:10:00 +00:00
|
|
|
import Article from './extensions/Article'
|
2023-07-31 21:43:41 +00:00
|
|
|
import { CustomBlockquote } from './extensions/CustomBlockquote'
|
2023-11-14 15:10:00 +00:00
|
|
|
import { Figcaption } from './extensions/Figcaption'
|
|
|
|
import { Figure } from './extensions/Figure'
|
|
|
|
import { Footnote } from './extensions/Footnote'
|
2024-01-16 09:13:23 +00:00
|
|
|
import { Iframe } from './extensions/Iframe'
|
2024-01-22 10:45:46 +00:00
|
|
|
import { Span } from './extensions/Span'
|
|
|
|
import { ToggleTextWrap } from './extensions/ToggleTextWrap'
|
2023-07-31 21:43:41 +00:00
|
|
|
import { TrailingNode } from './extensions/TrailingNode'
|
2023-11-14 15:10:00 +00:00
|
|
|
|
2023-05-07 13:47:10 +00:00
|
|
|
import './Prosemirror.scss'
|
2023-03-08 16:35:13 +00:00
|
|
|
|
2023-07-24 14:09:04 +00:00
|
|
|
type Props = {
|
2023-05-04 04:43:52 +00:00
|
|
|
shoutId: number
|
2023-03-08 16:35:13 +00:00
|
|
|
initialContent?: string
|
2023-03-23 17:15:50 +00:00
|
|
|
onChange: (text: string) => void
|
2023-03-08 16:35:13 +00:00
|
|
|
}
|
|
|
|
|
2023-10-09 05:14:58 +00:00
|
|
|
const allowedImageTypes = new Set([
|
|
|
|
'image/bmp',
|
|
|
|
'image/gif',
|
|
|
|
'image/jpeg',
|
|
|
|
'image/jpg',
|
|
|
|
'image/png',
|
|
|
|
'image/tiff',
|
|
|
|
'image/webp',
|
2023-11-14 15:10:00 +00:00
|
|
|
'image/x-icon',
|
2023-10-09 05:14:58 +00:00
|
|
|
])
|
|
|
|
|
2023-05-05 20:05:50 +00:00
|
|
|
const yDocs: Record<string, Doc> = {}
|
2023-03-29 15:36:12 +00:00
|
|
|
const providers: Record<string, HocuspocusProvider> = {}
|
2023-03-08 16:35:13 +00:00
|
|
|
|
2023-07-24 14:09:04 +00:00
|
|
|
export const Editor = (props: Props) => {
|
2023-03-08 16:35:13 +00:00
|
|
|
const { t } = useLocalize()
|
2023-11-30 08:07:31 +00:00
|
|
|
const { author } = useSession()
|
2023-08-14 15:50:03 +00:00
|
|
|
|
2023-05-11 11:43:14 +00:00
|
|
|
const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
|
2023-09-05 05:49:19 +00:00
|
|
|
const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
|
2023-03-29 08:51:27 +00:00
|
|
|
|
2024-02-04 17:40:15 +00:00
|
|
|
const { showSnackbar } = useSnackbar()
|
2023-10-09 05:14:58 +00:00
|
|
|
|
2023-05-04 04:43:52 +00:00
|
|
|
const docName = `shout-${props.shoutId}`
|
2023-03-29 08:51:27 +00:00
|
|
|
|
2023-05-05 20:05:50 +00:00
|
|
|
if (!yDocs[docName]) {
|
|
|
|
yDocs[docName] = new Y.Doc()
|
|
|
|
}
|
|
|
|
|
2023-03-29 10:14:39 +00:00
|
|
|
if (!providers[docName]) {
|
2023-03-29 15:36:12 +00:00
|
|
|
providers[docName] = new HocuspocusProvider({
|
2023-04-11 13:57:48 +00:00
|
|
|
url: 'wss://hocuspocus.discours.io',
|
2023-03-29 15:36:12 +00:00
|
|
|
name: docName,
|
2023-11-14 15:10:00 +00:00
|
|
|
document: yDocs[docName],
|
2023-03-29 15:36:12 +00:00
|
|
|
})
|
2023-03-29 10:14:39 +00:00
|
|
|
}
|
2023-03-08 16:35:13 +00:00
|
|
|
|
|
|
|
const editorElRef: {
|
|
|
|
current: HTMLDivElement
|
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-03-08 16:35:13 +00:00
|
|
|
}
|
|
|
|
|
2023-05-04 12:16:39 +00:00
|
|
|
const textBubbleMenuRef: {
|
|
|
|
current: HTMLDivElement
|
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-05-04 12:16:39 +00:00
|
|
|
}
|
|
|
|
|
2023-05-29 10:09:44 +00:00
|
|
|
const incutBubbleMenuRef: {
|
|
|
|
current: HTMLElement
|
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-05-29 10:09:44 +00:00
|
|
|
}
|
|
|
|
const figureBubbleMenuRef: {
|
|
|
|
current: HTMLElement
|
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-05-29 10:09:44 +00:00
|
|
|
}
|
|
|
|
const blockquoteBubbleMenuRef: {
|
|
|
|
current: HTMLElement
|
2023-03-08 16:35:13 +00:00
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-03-08 16:35:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const floatingMenuRef: {
|
|
|
|
current: HTMLDivElement
|
|
|
|
} = {
|
2023-11-14 15:10:00 +00:00
|
|
|
current: null,
|
2023-03-08 16:35:13 +00:00
|
|
|
}
|
2023-08-15 09:38:49 +00:00
|
|
|
|
2023-10-09 05:14:58 +00:00
|
|
|
const handleClipboardPaste = async () => {
|
|
|
|
try {
|
|
|
|
const clipboardItems = await navigator.clipboard.read()
|
|
|
|
|
|
|
|
if (clipboardItems.length === 0) return
|
|
|
|
const [clipboardItem] = clipboardItems
|
|
|
|
const { types } = clipboardItem
|
|
|
|
const imageType = types.find((type) => allowedImageTypes.has(type))
|
|
|
|
|
|
|
|
if (!imageType) return
|
|
|
|
const blob = await clipboardItem.getType(imageType)
|
|
|
|
const extension = imageType.split('/')[1]
|
|
|
|
const file = new File([blob], `clipboardImage.${extension}`)
|
|
|
|
|
|
|
|
const uplFile = {
|
|
|
|
source: blob.toString(),
|
|
|
|
name: file.name,
|
|
|
|
size: file.size,
|
2023-11-14 15:10:00 +00:00
|
|
|
file,
|
2023-10-09 05:14:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
showSnackbar({ body: t('Uploading image') })
|
2023-10-27 18:50:13 +00:00
|
|
|
const result = await handleImageUpload(uplFile)
|
2023-10-09 05:14:58 +00:00
|
|
|
|
|
|
|
editor()
|
|
|
|
.chain()
|
|
|
|
.focus()
|
|
|
|
.insertContent({
|
2024-01-16 09:13:23 +00:00
|
|
|
type: 'figure',
|
|
|
|
attrs: { 'data-type': 'image' },
|
2023-10-09 05:14:58 +00:00
|
|
|
content: [
|
|
|
|
{
|
2024-01-16 09:13:23 +00:00
|
|
|
type: 'image',
|
|
|
|
attrs: { src: result.url },
|
2023-10-09 05:14:58 +00:00
|
|
|
},
|
|
|
|
{
|
2024-01-16 09:13:23 +00:00
|
|
|
type: 'figcaption',
|
|
|
|
content: [{ type: 'text', text: result.originalFilename }],
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
|
|
|
],
|
2023-10-09 05:14:58 +00:00
|
|
|
})
|
|
|
|
.run()
|
|
|
|
} catch (error) {
|
2023-10-09 09:14:41 +00:00
|
|
|
console.error('[Paste Image Error]:', error)
|
2023-10-09 05:14:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-27 17:38:38 +00:00
|
|
|
const { initialContent } = props
|
2023-10-09 05:14:58 +00:00
|
|
|
|
2023-03-08 16:35:13 +00:00
|
|
|
const editor = createTiptapEditor(() => ({
|
|
|
|
element: editorElRef.current,
|
2023-07-24 14:09:04 +00:00
|
|
|
editorProps: {
|
|
|
|
attributes: {
|
2023-11-14 15:10:00 +00:00
|
|
|
class: 'articleEditor',
|
2023-10-09 05:14:58 +00:00
|
|
|
},
|
|
|
|
transformPastedHTML(html) {
|
|
|
|
return html.replaceAll(/<img.*?>/g, '')
|
|
|
|
},
|
|
|
|
handlePaste: () => {
|
|
|
|
handleClipboardPaste()
|
|
|
|
return false
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
2023-07-24 14:09:04 +00:00
|
|
|
},
|
2023-03-08 16:35:13 +00:00
|
|
|
extensions: [
|
|
|
|
Document,
|
|
|
|
Text,
|
|
|
|
Paragraph,
|
|
|
|
Dropcursor,
|
2023-05-29 10:09:44 +00:00
|
|
|
CustomBlockquote,
|
2023-03-08 16:35:13 +00:00
|
|
|
Bold,
|
|
|
|
Italic,
|
2024-01-22 10:45:46 +00:00
|
|
|
Span,
|
|
|
|
ToggleTextWrap,
|
2023-03-08 16:35:13 +00:00
|
|
|
Strike,
|
2023-05-04 17:38:50 +00:00
|
|
|
HorizontalRule.configure({
|
|
|
|
HTMLAttributes: {
|
2023-11-14 15:10:00 +00:00
|
|
|
class: 'horizontalRule',
|
|
|
|
},
|
2023-05-04 17:38:50 +00:00
|
|
|
}),
|
2023-03-08 16:35:13 +00:00
|
|
|
Underline,
|
2024-01-22 10:45:46 +00:00
|
|
|
Link.extend({
|
|
|
|
inclusive: false,
|
|
|
|
}).configure({
|
|
|
|
autolink: true,
|
2023-11-14 15:10:00 +00:00
|
|
|
openOnClick: false,
|
2023-03-20 09:19:14 +00:00
|
|
|
}),
|
2023-03-22 07:47:51 +00:00
|
|
|
Heading.configure({
|
2023-11-14 15:10:00 +00:00
|
|
|
levels: [2, 3, 4],
|
2023-03-22 07:47:51 +00:00
|
|
|
}),
|
2023-03-08 16:35:13 +00:00
|
|
|
BulletList,
|
|
|
|
OrderedList,
|
|
|
|
ListItem,
|
2023-03-29 08:51:27 +00:00
|
|
|
Collaboration.configure({
|
2023-11-14 15:10:00 +00:00
|
|
|
document: yDocs[docName],
|
2023-03-29 08:51:27 +00:00
|
|
|
}),
|
2023-03-29 10:14:39 +00:00
|
|
|
CollaborationCursor.configure({
|
|
|
|
provider: providers[docName],
|
|
|
|
user: {
|
2023-11-30 08:07:31 +00:00
|
|
|
name: author().name,
|
|
|
|
color: uniqolor(author().slug).color,
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
2023-03-29 10:14:39 +00:00
|
|
|
}),
|
2023-03-08 16:35:13 +00:00
|
|
|
Placeholder.configure({
|
2023-11-14 15:10:00 +00:00
|
|
|
placeholder: t('Add a link or click plus to embed media'),
|
2023-03-08 16:35:13 +00:00
|
|
|
}),
|
|
|
|
Focus,
|
|
|
|
Gapcursor,
|
|
|
|
HardBreak,
|
2023-05-09 17:31:28 +00:00
|
|
|
Highlight.configure({
|
|
|
|
multicolor: true,
|
|
|
|
HTMLAttributes: {
|
2023-11-14 15:10:00 +00:00
|
|
|
class: 'highlight',
|
|
|
|
},
|
2023-05-09 17:31:28 +00:00
|
|
|
}),
|
2023-08-15 09:38:49 +00:00
|
|
|
Image,
|
2024-01-16 09:13:23 +00:00
|
|
|
Iframe,
|
|
|
|
Figure,
|
2023-08-15 09:38:49 +00:00
|
|
|
Figcaption,
|
2023-08-28 11:48:54 +00:00
|
|
|
Footnote,
|
2024-01-22 10:45:46 +00:00
|
|
|
ToggleTextWrap,
|
2023-08-22 13:37:54 +00:00
|
|
|
CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689
|
2023-04-20 13:58:56 +00:00
|
|
|
BubbleMenu.configure({
|
2023-05-04 12:16:39 +00:00
|
|
|
pluginKey: 'textBubbleMenu',
|
|
|
|
element: textBubbleMenuRef.current,
|
2023-05-07 13:47:10 +00:00
|
|
|
shouldShow: ({ editor: e, view, state, from, to }) => {
|
2023-05-04 15:09:09 +00:00
|
|
|
const { doc, selection } = state
|
|
|
|
const { empty } = selection
|
|
|
|
const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
|
2024-01-22 10:45:46 +00:00
|
|
|
if (isEmptyTextBlock) {
|
|
|
|
e.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
|
|
|
|
}
|
2023-08-15 09:38:49 +00:00
|
|
|
setIsCommonMarkup(e.isActive('figcaption'))
|
2023-09-05 05:49:19 +00:00
|
|
|
const result =
|
2024-01-16 09:13:23 +00:00
|
|
|
(view.hasFocus() &&
|
|
|
|
!empty &&
|
|
|
|
!isEmptyTextBlock &&
|
|
|
|
!e.isActive('image') &&
|
|
|
|
!e.isActive('figure')) ||
|
|
|
|
e.isActive('footnote') ||
|
2024-01-18 07:02:37 +00:00
|
|
|
(e.isActive('figcaption') && !empty)
|
2023-09-05 05:49:19 +00:00
|
|
|
setShouldShowTextBubbleMenu(result)
|
|
|
|
return result
|
2023-07-18 11:21:55 +00:00
|
|
|
},
|
|
|
|
tippyOptions: {
|
2023-11-14 15:10:00 +00:00
|
|
|
sticky: true,
|
|
|
|
},
|
2023-05-29 10:09:44 +00:00
|
|
|
}),
|
|
|
|
BubbleMenu.configure({
|
|
|
|
pluginKey: 'blockquoteBubbleMenu',
|
|
|
|
element: blockquoteBubbleMenuRef.current,
|
2023-05-29 17:14:58 +00:00
|
|
|
shouldShow: ({ editor: e, state }) => {
|
|
|
|
const { selection } = state
|
|
|
|
const { empty } = selection
|
|
|
|
return empty && e.isActive('blockquote')
|
2023-09-25 10:16:06 +00:00
|
|
|
},
|
|
|
|
tippyOptions: {
|
|
|
|
offset: [0, 0],
|
|
|
|
placement: 'top',
|
|
|
|
getReferenceClientRect: () => {
|
|
|
|
const selectedElement = editor().view.dom.querySelector('.has-focus')
|
|
|
|
if (selectedElement) {
|
|
|
|
return selectedElement.getBoundingClientRect()
|
|
|
|
}
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
|
|
|
},
|
2023-05-29 10:09:44 +00:00
|
|
|
}),
|
|
|
|
BubbleMenu.configure({
|
|
|
|
pluginKey: 'incutBubbleMenu',
|
|
|
|
element: incutBubbleMenuRef.current,
|
2023-05-29 17:14:58 +00:00
|
|
|
shouldShow: ({ editor: e, state }) => {
|
|
|
|
const { selection } = state
|
|
|
|
const { empty } = selection
|
|
|
|
return empty && e.isActive('article')
|
2023-09-25 10:16:06 +00:00
|
|
|
},
|
|
|
|
tippyOptions: {
|
|
|
|
offset: [0, -16],
|
|
|
|
placement: 'top',
|
|
|
|
getReferenceClientRect: () => {
|
|
|
|
const selectedElement = editor().view.dom.querySelector('.has-focus')
|
|
|
|
if (selectedElement) {
|
|
|
|
return selectedElement.getBoundingClientRect()
|
|
|
|
}
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
|
|
|
},
|
2023-05-04 12:16:39 +00:00
|
|
|
}),
|
|
|
|
BubbleMenu.configure({
|
|
|
|
pluginKey: 'imageBubbleMenu',
|
2023-05-29 10:09:44 +00:00
|
|
|
element: figureBubbleMenuRef.current,
|
2023-05-07 13:47:10 +00:00
|
|
|
shouldShow: ({ editor: e, view }) => {
|
2023-05-04 15:09:09 +00:00
|
|
|
return view.hasFocus() && e.isActive('image')
|
2023-11-14 15:10:00 +00:00
|
|
|
},
|
2023-04-20 13:58:56 +00:00
|
|
|
}),
|
|
|
|
FloatingMenu.configure({
|
|
|
|
tippyOptions: {
|
2023-11-14 15:10:00 +00:00
|
|
|
placement: 'left',
|
2023-04-20 13:58:56 +00:00
|
|
|
},
|
2023-11-14 15:10:00 +00:00
|
|
|
element: floatingMenuRef.current,
|
2023-05-08 17:21:06 +00:00
|
|
|
}),
|
2023-05-29 10:09:44 +00:00
|
|
|
TrailingNode,
|
2023-11-14 15:10:00 +00:00
|
|
|
Article,
|
2023-07-24 10:49:25 +00:00
|
|
|
],
|
2023-10-09 05:14:58 +00:00
|
|
|
enablePasteRules: [Link],
|
2023-11-14 15:10:00 +00:00
|
|
|
content: initialContent ?? null,
|
2023-03-08 16:35:13 +00:00
|
|
|
}))
|
|
|
|
|
2024-02-04 17:40:15 +00:00
|
|
|
const { countWords, setEditor } = useEditorContext()
|
2023-05-12 13:03:46 +00:00
|
|
|
setEditor(editor)
|
|
|
|
|
|
|
|
const html = useEditorHTML(() => editor())
|
|
|
|
|
2023-04-26 02:37:29 +00:00
|
|
|
createEffect(() => {
|
|
|
|
props.onChange(html())
|
|
|
|
if (html()) {
|
|
|
|
countWords({
|
|
|
|
characters: editor().storage.characterCount.characters(),
|
2023-11-14 15:10:00 +00:00
|
|
|
words: editor().storage.characterCount.words(),
|
2023-04-26 02:37:29 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-09-05 05:49:19 +00:00
|
|
|
onCleanup(() => {
|
2024-01-25 19:16:38 +00:00
|
|
|
editor()?.destroy()
|
2023-09-05 05:49:19 +00:00
|
|
|
})
|
|
|
|
|
2023-03-08 16:35:13 +00:00
|
|
|
return (
|
2023-08-01 10:26:45 +00:00
|
|
|
<>
|
2023-08-23 22:31:39 +00:00
|
|
|
<div class="row">
|
2023-08-31 09:11:32 +00:00
|
|
|
<div class="col-md-5" />
|
2023-08-23 22:31:39 +00:00
|
|
|
<div class="col-md-12">
|
|
|
|
<div ref={(el) => (editorElRef.current = el)} id="editorBody" />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
2023-05-11 11:43:14 +00:00
|
|
|
<TextBubbleMenu
|
2023-09-05 05:49:19 +00:00
|
|
|
shouldShow={shouldShowTextBubbleMenu()}
|
2023-05-11 11:43:14 +00:00
|
|
|
isCommonMarkup={isCommonMarkup()}
|
|
|
|
editor={editor()}
|
|
|
|
ref={(el) => (textBubbleMenuRef.current = el)}
|
|
|
|
/>
|
2023-05-29 10:09:44 +00:00
|
|
|
<BlockquoteBubbleMenu
|
|
|
|
ref={(el) => {
|
|
|
|
blockquoteBubbleMenuRef.current = el
|
|
|
|
}}
|
|
|
|
editor={editor()}
|
|
|
|
/>
|
|
|
|
<FigureBubbleMenu
|
|
|
|
editor={editor()}
|
|
|
|
ref={(el) => {
|
|
|
|
figureBubbleMenuRef.current = el
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<IncutBubbleMenu
|
|
|
|
editor={editor()}
|
|
|
|
ref={(el) => {
|
|
|
|
incutBubbleMenuRef.current = el
|
|
|
|
}}
|
|
|
|
/>
|
2023-03-08 16:35:13 +00:00
|
|
|
<EditorFloatingMenu editor={editor()} ref={(el) => (floatingMenuRef.current = el)} />
|
2023-08-01 10:26:45 +00:00
|
|
|
</>
|
2023-03-08 16:35:13 +00:00
|
|
|
)
|
|
|
|
}
|