The previous article covered the basic use of Titap in VUE
Now add images, table functionality, highlight, and v-Model binding rich text components based on last time
Install the images and tables module
yarn add @tiptap/extension-highlight @tiptap/extension-image
@tiptap/extension-table @tiptap/extension-table-cell
@tiptap/extension-table-header @tiptap/extension-table-row
Copy the code
Modify items in menubar. vue
export default {
components: {
MenuItem
},
props: {
editor: {
type: Object.required: true}},setup(props) {
const items = reactive([
{
icon: 'bold'.title: 'bold'.action: () = > props.editor.chain().focus().toggleBold().run(),
isActive: () = > props.editor.isActive('bold')}, {icon: 'italic'.title: 'italics'.action: () = > props.editor.chain().focus().toggleItalic().run(),
isActive: () = > props.editor.isActive('italic')}, {icon: 'strikethrough'.title: 'Text line'.action: () = > props.editor.chain().focus().toggleStrike().run(),
isActive: () = > props.editor.isActive('strike')}, {icon: 'code-view'.title: 'the code'.action: () = > props.editor.chain().focus().toggleCode().run(),
isActive: () = > props.editor.isActive('code')}, {icon: 'mark-pen-line'.title: 'highlighted'.action: () = > props.editor.chain().focus().toggleHighlight().run(),
isActive: () = > props.editor.isActive('highlight')}, {type: 'divider'
},
{
icon: 'h-1'.title: 'heading 1'.action: () = > props.editor.chain().focus().toggleHeading({ level: 1 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 1})}, {icon: 'h-2'.title: 'title 2'.action: () = > props.editor.chain().focus().toggleHeading({ level: 2 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 2})}, {icon: 'h-3'.title: 'title'.action: () = > props.editor.chain().focus().toggleHeading({ level: 3 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 3})}, {icon: 'h-4'.title: 'title 4'.action: () = > props.editor.chain().focus().toggleHeading({ level: 4 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 4})}, {icon: 'h-5'.title: 'title 5'.action: () = > props.editor.chain().focus().toggleHeading({ level: 5 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 5})}, {icon: 'h-6'.title: 'title 6'.action: () = > props.editor.chain().focus().toggleHeading({ level: 6 }).run(),
isActive: () = > props.editor.isActive('heading', { level: 6})}, {icon: 'paragraph'.title: 'section'.action: () = > props.editor.chain().focus().setParagraph().run(),
isActive: () = > props.editor.isActive('paragraph')}, {icon: 'list-unordered'.title: 'No need list'.action: () = > props.editor.chain().focus().toggleBulletList().run(),
isActive: () = > props.editor.isActive('bulletList')}, {icon: 'list-ordered'.title: 'Required list'.action: () = > props.editor.chain().focus().toggleOrderedList().run(),
isActive: () = > props.editor.isActive('orderedList')}, {type: 'divider'
},
{
icon: 'double-quotes-l'.title: 'block'.action: () = > props.editor.chain().focus().toggleBlockquote().run(),
isActive: () = > props.editor.isActive('blockquote')}, {icon: 'separator'.title: 'horizontal'.action: () = > props.editor.chain().focus().setHorizontalRule().run()
},
{
type: 'divider'
},
{
icon: 'format-clear'.title: 'Clear style'.action: () = > props.editor.chain()
.focus()
.clearNodes()
.unsetAllMarks()
.run()
},
{
type: 'divider'
},
{
icon: 'image-line'.title: 'Insert picture'.action: () = > {
const url = window.prompt('URL')
if (url) {
props.editor.chain().focus().setImage({ src: url }).run()
}
}
},
{
type: 'divider'
},
{
icon: 'arrow-go-back-line'.title: 'undo'.action: () = > props.editor.chain().focus().undo().run()
},
{
icon: 'arrow-go-forward-line'.title: 'Cancel cancel'.action: () = > props.editor.chain().focus().redo().run()
},
{
type: 'divider'
},
{
icon: 'table-2'.title: 'Insert table'.action: () = > props.editor.chain().focus()
.insertTable({ rows: 3.cols: 3.withHeaderRow: true }).run()
},
{
icon: 'delete-bin-6-line'.title: 'Delete Table'.action: () = > props.editor.chain().focus().deleteTable().run()
},
{
icon: 'merge-cells-horizontal'.title: 'Merge split cells'.action: () = > props.editor.chain().focus().mergeOrSplit().run()
},
{
icon: 'insert-row-top'.title: 'Add a line above'.action: () = > props.editor.chain().focus().addRowBefore().run()
},
{
icon: 'insert-row-bottom'.title: 'Let's add a line.'.action: () = > props.editor.chain().focus().addRowAfter().run()
},
{
icon: 'delete-row'.title: 'Delete row'.action: () = > props.editor.chain().focus().deleteRow().run()
},
{
icon: 'insert-column-left'.title: 'Add a column to the left'.action: () = > props.editor.chain().focus().addColumnBefore().run()
},
{
icon: 'insert-column-right'.title: 'Add a column to the right'.action: () = > props.editor.chain().focus().addColumnAfter().run()
},
{
icon: 'delete-column'.title: 'Delete row'.action: () = > props.editor.chain().focus().deleteColumn().run()
},
{
icon: 'sip-line'.title: 'Cell background color'.action: () = > props.editor.chain().focus().toggleHeaderCell().run()
},
{
type: 'divider'}])return {
items
}
}
}
Copy the code
Modify the Editor. Vue
import { useEditor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import { defineComponent, onBeforeUnmount } from 'vue'
import MenuBar from './MenuBar.vue'
import Highlight from '@tiptap/extension-highlight'
import Image from '@tiptap/extension-image'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
const CustomTableCell = TableCell.extend({
addAttributes() {
return {
// Expand existing attributes,? . Is an optional chain operator that can be searched by itself.. this.parent? . (),// Add new attributes
backgroundColor: {
default: null.parseHTML: (element) = > element.getAttribute('data-background-color'),
renderHTML: (attributes) = > ({
'data-background-color': attributes.backgroundColor,
style: `background-color: ${attributes.backgroundColor}`})}}}})export default defineComponent({
components: {
EditorContent,
MenuBar
},
props: {
width: {
type: String.default: '800px'
},
height: {
type: String.default: '300px'}},setup(props, { emit }) {
const editor = useEditor({
content: "< p > tiptap editor demo. 🎉 < / p >".extensions: [
StarterKit,
Image,
Highlight.configure({ multicolor: true }),
Table.configure({
resizable: true
}),
TableRow,
TableHeader,
CustomTableCell
],
})
return {
editor
}
}
})
Copy the code
The method you use is as simple as calling the Editor’S API. Now we have added the ability to select highlights, images, and tables. The highlighted background color is based on the CSS of the Mark element
The effect
Note that it is better to use CSS to manually set the width of the image, otherwise the large image will directly spread over the whole rich text box.
How to send rich text data to the back end
export default defineComponent({
components: {
EditorContent,
MenuBar
},
props: {
html: {
type: String.default: ' '
},
/ * json: {type: Object, default: () = > ({type: "doc", the content: / /... })}, * /
width: {
type: String.default: '800px'
},
height: {
type: String.default: '300px'}},setup(props, { emit }) {
const editor = useEditor({
content: props.html,
extensions: [
StarterKit,
Image,
Highlight.configure({ multicolor: true }),
Table.configure({
resizable: true
}),
TableRow,
TableHeader,
CustomTableCell
],
onUpdate: () = > {
emit('update:html', editor.value.getHTML())
//emit('update:json', editor.value.getJSON())}})return {
editor
}
}
})
Copy the code
use
<script setup>
import Editor from "./Editor.vue"
const editorHtml = ref('< p > tiptap editor demo. 🎉 < / p >')
</script>
<Editor v-model:html="editorHtml" />
Copy the code
The default value must be {type: ‘doc’,content: []}, otherwise an error will be reported (no type specified).