elk/composables/tiptap.ts

139 lines
3.7 KiB
TypeScript
Raw Normal View History

import type { Editor } from '@tiptap/vue-3'
2022-12-13 13:02:43 +00:00
import { Extension, useEditor } from '@tiptap/vue-3'
import Placeholder from '@tiptap/extension-placeholder'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import Mention from '@tiptap/extension-mention'
2022-11-26 02:01:50 +00:00
import HardBreak from '@tiptap/extension-hard-break'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import Code from '@tiptap/extension-code'
2023-01-10 21:16:56 +00:00
import History from '@tiptap/extension-history'
import { Plugin } from 'prosemirror-state'
import type { Ref } from 'vue'
2023-01-16 11:40:47 +00:00
import { TiptapEmojiSuggestion, TiptapHashtagSuggestion, TiptapMentionSuggestion } from './tiptap/suggestion'
import { TiptapPluginCodeBlockShiki } from './tiptap/shiki'
2023-01-16 11:40:47 +00:00
import { TiptapPluginCustomEmoji } from './tiptap/custom-emoji'
import { TiptapPluginEmoji } from './tiptap/emoji'
export interface UseTiptapOptions {
2023-01-04 10:21:18 +00:00
content: Ref<string>
2022-11-29 22:47:24 +00:00
placeholder: Ref<string | undefined>
2022-11-25 14:07:31 +00:00
onSubmit: () => void
onFocus: () => void
onPaste: (event: ClipboardEvent) => void
autofocus: boolean
}
export function useTiptap(options: UseTiptapOptions) {
2024-02-24 16:46:14 +00:00
if (import.meta.server)
return { editor: ref<Editor | undefined>() }
const {
autofocus,
content,
placeholder,
} = options
const editor = useEditor({
content: content.value,
extensions: [
Document,
Paragraph,
2022-11-26 02:01:50 +00:00
HardBreak,
Bold,
Italic,
Code,
Text,
2023-01-16 11:40:47 +00:00
TiptapPluginEmoji,
TiptapPluginCustomEmoji.configure({
inline: true,
HTMLAttributes: {
class: 'custom-emoji',
},
}),
Mention.configure({
renderHTML({ options, node }) {
return ['span', { 'data-type': 'mention', 'data-id': node.attrs.id }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
},
2023-01-16 11:40:47 +00:00
suggestion: TiptapMentionSuggestion,
}),
Mention
.extend({ name: 'hashtag' })
.configure({
renderHTML({ options, node }) {
return ['span', { 'data-type': 'hashtag', 'data-id': node.attrs.id }, `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`]
},
2023-01-16 11:40:47 +00:00
suggestion: TiptapHashtagSuggestion,
}),
Mention
.extend({ name: 'emoji' })
.configure({
2023-01-16 11:40:47 +00:00
suggestion: TiptapEmojiSuggestion,
}),
Placeholder.configure({
placeholder: () => placeholder.value!,
}),
TiptapPluginCodeBlockShiki,
2023-01-10 21:16:56 +00:00
History.configure({
depth: 10,
}),
Extension.create({
name: 'api',
addKeyboardShortcuts() {
return {
'Mod-Enter': () => {
2022-11-25 14:07:31 +00:00
options.onSubmit()
return true
},
}
},
onFocus() {
options.onFocus()
},
addProseMirrorPlugins() {
return [
new Plugin({
props: {
handleDOMEvents: {
paste(view, event) {
options.onPaste(event)
},
},
},
}),
]
},
}),
],
onUpdate({ editor }) {
content.value = editor.getHTML()
},
editorProps: {
attributes: {
class: 'content-editor content-rich',
},
},
parseOptions: {
preserveWhitespace: 'full',
},
autofocus,
editable: true,
})
2022-11-25 18:10:17 +00:00
watch(content, (value) => {
if (editor.value?.getHTML() === value)
return
editor.value?.commands.setContent(value || '', false)
})
watch(placeholder, () => {
editor.value?.view.dispatch(editor.value?.state.tr)
})
2022-11-25 18:10:17 +00:00
return {
editor,
}
}