172 lines
4.3 KiB
Vue
172 lines
4.3 KiB
Vue
|
|
<script setup>
|
||
|
|
import { Placeholder } from '@tiptap/extension-placeholder'
|
||
|
|
import { TextAlign } from '@tiptap/extension-text-align'
|
||
|
|
import { Underline } from '@tiptap/extension-underline'
|
||
|
|
import { StarterKit } from '@tiptap/starter-kit'
|
||
|
|
import {
|
||
|
|
EditorContent,
|
||
|
|
useEditor,
|
||
|
|
} from '@tiptap/vue-3'
|
||
|
|
|
||
|
|
const props = defineProps({
|
||
|
|
modelValue: {
|
||
|
|
type: String,
|
||
|
|
required: true,
|
||
|
|
},
|
||
|
|
placeholder: {
|
||
|
|
type: String,
|
||
|
|
required: false,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
const emit = defineEmits(['update:modelValue'])
|
||
|
|
|
||
|
|
const editorRef = ref()
|
||
|
|
|
||
|
|
const editor = useEditor({
|
||
|
|
content: props.modelValue,
|
||
|
|
extensions: [
|
||
|
|
StarterKit,
|
||
|
|
TextAlign.configure({
|
||
|
|
types: [
|
||
|
|
'heading',
|
||
|
|
'paragraph',
|
||
|
|
],
|
||
|
|
}),
|
||
|
|
Placeholder.configure({ placeholder: props.placeholder ?? 'Write something here...' }),
|
||
|
|
Underline,
|
||
|
|
],
|
||
|
|
onUpdate() {
|
||
|
|
if (!editor.value)
|
||
|
|
return
|
||
|
|
emit('update:modelValue', editor.value.getHTML())
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
watch(() => props.modelValue, () => {
|
||
|
|
const isSame = editor.value?.getHTML() === props.modelValue
|
||
|
|
if (isSame)
|
||
|
|
return
|
||
|
|
editor.value?.commands.setContent(props.modelValue)
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div>
|
||
|
|
<div
|
||
|
|
v-if="editor"
|
||
|
|
class="d-flex gap-2 py-2 px-6 flex-wrap align-center editor"
|
||
|
|
>
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive('bold') ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive('bold') ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().toggleBold().run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-bold" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive('underline') ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive('underline') ? 'primary' : 'default'"
|
||
|
|
@click="editor.commands.toggleUnderline()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-underline" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive('italic') ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive('italic') ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().toggleItalic().run()"
|
||
|
|
>
|
||
|
|
<VIcon
|
||
|
|
icon="tabler-italic"
|
||
|
|
class="font-weight-medium"
|
||
|
|
/>
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive('strike') ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive('strike') ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().toggleStrike().run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-strikethrough" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive({ textAlign: 'left' }) ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive({ textAlign: 'left' }) ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().setTextAlign('left').run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-align-left" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:color="editor.isActive({ textAlign: 'center' }) ? 'primary' : 'default'"
|
||
|
|
:variant="editor.isActive({ textAlign: 'center' }) ? 'tonal' : 'text'"
|
||
|
|
@click="editor.chain().focus().setTextAlign('center').run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-align-center" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive({ textAlign: 'right' }) ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive({ textAlign: 'right' }) ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().setTextAlign('right').run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-align-right" />
|
||
|
|
</IconBtn>
|
||
|
|
|
||
|
|
<IconBtn
|
||
|
|
size="small"
|
||
|
|
rounded
|
||
|
|
:variant="editor.isActive({ textAlign: 'justify' }) ? 'tonal' : 'text'"
|
||
|
|
:color="editor.isActive({ textAlign: 'justify' }) ? 'primary' : 'default'"
|
||
|
|
@click="editor.chain().focus().setTextAlign('justify').run()"
|
||
|
|
>
|
||
|
|
<VIcon icon="tabler-align-justified" />
|
||
|
|
</IconBtn>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<VDivider />
|
||
|
|
|
||
|
|
<EditorContent
|
||
|
|
ref="editorRef"
|
||
|
|
:editor="editor"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<style lang="scss">
|
||
|
|
.ProseMirror {
|
||
|
|
padding: 0.5rem;
|
||
|
|
min-block-size: 15vh;
|
||
|
|
outline: none;
|
||
|
|
|
||
|
|
p {
|
||
|
|
margin-block-end: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
p.is-editor-empty:first-child::before {
|
||
|
|
block-size: 0;
|
||
|
|
color: #adb5bd;
|
||
|
|
content: attr(data-placeholder);
|
||
|
|
float: inline-start;
|
||
|
|
pointer-events: none;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|