webapp/src/components/Editor/Editor.tsx

156 lines
4.5 KiB
TypeScript
Raw Normal View History

import { createEffect } from 'solid-js'
2023-03-23 17:15:50 +00:00
import { createTiptapEditor, useEditorHTML } from 'solid-tiptap'
2023-03-08 16:35:13 +00:00
import { useLocalize } from '../../context/localize'
import { Blockquote } from '@tiptap/extension-blockquote'
import { Bold } from '@tiptap/extension-bold'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { Dropcursor } from '@tiptap/extension-dropcursor'
import { Italic } from '@tiptap/extension-italic'
import { Strike } from '@tiptap/extension-strike'
import { HorizontalRule } from '@tiptap/extension-horizontal-rule'
import { Underline } from '@tiptap/extension-underline'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import { BulletList } from '@tiptap/extension-bullet-list'
import { OrderedList } from '@tiptap/extension-ordered-list'
import { ListItem } from '@tiptap/extension-list-item'
import { CharacterCount } from '@tiptap/extension-character-count'
import { Placeholder } from '@tiptap/extension-placeholder'
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'
import { Link } from '@tiptap/extension-link'
import { Youtube } from '@tiptap/extension-youtube'
import { Document } from '@tiptap/extension-document'
import { Text } from '@tiptap/extension-text'
import { Image } from '@tiptap/extension-image'
import { Paragraph } from '@tiptap/extension-paragraph'
import Focus from '@tiptap/extension-focus'
import { TrailingNode } from './extensions/TrailingNode'
2023-03-25 04:42:45 +00:00
import { EditorBubbleMenu } from './EditorBubbleMenu/EditorBubbleMenu'
2023-03-08 16:35:13 +00:00
import { EditorFloatingMenu } from './EditorFloatingMenu'
2023-03-29 08:51:27 +00:00
import * as Y from 'yjs'
import { WebrtcProvider } from 'y-webrtc'
import { CollaborationCursor } from '@tiptap/extension-collaboration-cursor'
import { Collaboration } from '@tiptap/extension-collaboration'
2023-03-22 07:47:51 +00:00
import './Prosemirror.scss'
2023-03-29 08:51:27 +00:00
import { IndexeddbPersistence } from 'y-indexeddb'
import { useSession } from '../../context/session'
2023-03-29 10:22:03 +00:00
import uniqolor from 'uniqolor'
2023-03-29 15:36:12 +00:00
import { HocuspocusProvider } from '@hocuspocus/provider'
2023-03-08 16:35:13 +00:00
type EditorProps = {
2023-03-29 08:51:27 +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-03-29 08:51:27 +00:00
const yDoc = new Y.Doc()
2023-03-29 10:14:39 +00:00
const persisters: Record<string, IndexeddbPersistence> = {}
2023-03-29 15:36:12 +00:00
const providers: Record<string, HocuspocusProvider> = {}
2023-03-08 16:35:13 +00:00
export const Editor = (props: EditorProps) => {
const { t } = useLocalize()
2023-03-29 08:51:27 +00:00
const { user } = useSession()
const docName = `shout-${props.shoutId}`
2023-03-29 15:36:12 +00:00
if (!persisters[docName]) {
persisters[docName] = new IndexeddbPersistence(docName, yDoc)
}
2023-03-29 08:51:27 +00:00
2023-03-29 10:14:39 +00:00
if (!providers[docName]) {
2023-03-29 15:36:12 +00:00
providers[docName] = new HocuspocusProvider({
2023-03-29 17:00:09 +00:00
url: 'wss://v2.discours.io:4242',
2023-03-29 15:36:12 +00:00
name: docName,
document: yDoc
})
2023-03-29 10:14:39 +00:00
}
2023-03-08 16:35:13 +00:00
const editorElRef: {
current: HTMLDivElement
} = {
current: null
}
const bubbleMenuRef: {
current: HTMLDivElement
} = {
current: null
}
const floatingMenuRef: {
current: HTMLDivElement
} = {
current: null
}
const editor = createTiptapEditor(() => ({
element: editorElRef.current,
extensions: [
Document,
Text,
Paragraph,
Dropcursor,
Blockquote,
Bold,
Italic,
Strike,
HorizontalRule,
Underline,
2023-03-20 09:19:14 +00:00
Link.configure({
openOnClick: false
}),
2023-03-22 07:47:51 +00:00
Heading.configure({
levels: [1, 2, 3]
}),
2023-03-08 16:35:13 +00:00
BubbleMenu.configure({
element: bubbleMenuRef.current
}),
FloatingMenu.configure({
tippyOptions: {
placement: 'left'
},
element: floatingMenuRef.current
}),
BulletList,
OrderedList,
ListItem,
CharacterCount,
2023-03-29 08:51:27 +00:00
Collaboration.configure({
document: yDoc
}),
2023-03-29 10:14:39 +00:00
CollaborationCursor.configure({
provider: providers[docName],
user: {
name: user().name,
2023-03-29 10:28:40 +00:00
color: uniqolor(user().slug).color
2023-03-29 10:14:39 +00:00
}
}),
2023-03-08 16:35:13 +00:00
Placeholder.configure({
placeholder: t('Short opening')
}),
Focus,
Gapcursor,
HardBreak,
Highlight,
Image,
Youtube,
TrailingNode
]
}))
2023-03-23 17:15:50 +00:00
const html = useEditorHTML(() => editor())
createEffect(() => {
props.onChange(html())
})
2023-03-08 16:35:13 +00:00
return (
<>
<div ref={(el) => (editorElRef.current = el)} />
2023-03-08 16:35:13 +00:00
<EditorBubbleMenu editor={editor()} ref={(el) => (bubbleMenuRef.current = el)} />
<EditorFloatingMenu editor={editor()} ref={(el) => (floatingMenuRef.current = el)} />
</>
2023-03-08 16:35:13 +00:00
)
}