A WYSIWYG rich-text editor using tiptap and vuetify for Vue.js
English | 中文
👉https://yikoyu.github.io/vuetify-pro-tiptap/
- Use vuetify components
- Many out of box extension (welcome to submit an issue for feature request)
- Markdown support
- TypeScript support
- I18n support(
en
,zhHans
,nl
) - Vuetify 3.x and Vue 3.x support
For Vuetify 2.x please use the latest version of [email protected]
pnpm add vuetify-pro-tiptap
# or
yarn add vuetify-pro-tiptap
# or
npm i vuetify-pro-tiptap -S
tiptap.ts
import { markRaw } from 'vue'
import { VuetifyTiptap, VuetifyViewer, createVuetifyProTipTap } from 'vuetify-pro-tiptap'
import { BaseKit, Bold, Italic, Underline, Strike, Color, Highlight, Heading, TextAlign, FontFamily, FontSize, SubAndSuperScript, BulletList, OrderedList, TaskList, Indent, Link, Image, Video, Table, Blockquote, HorizontalRule, Code, CodeBlock, Clear, Fullscreen, History } from 'vuetify-pro-tiptap'
import 'vuetify-pro-tiptap/style.css'
import SelectImage from './components/SelectImage.vue'
export const vuetifyProTipTap = createVuetifyProTipTap({
lang: 'zhHans',
components: {
VuetifyTiptap,
VuetifyViewer
},
extensions: [
BaseKit.configure({
placeholder: {
placeholder: 'Enter some text...'
}
}),
Bold,
Italic,
Underline,
Strike,
Code.configure({ divider: true }),
Heading,
TextAlign,
FontFamily,
FontSize,
Color,
Highlight.configure({ divider: true }),
SubAndSuperScript.configure({ divider: true }),
Clear.configure({ divider: true }),
BulletList,
OrderedList,
TaskList,
Indent.configure({ divider: true }),
Link,
Image.configure({
imageTabs: [{ name: 'SELECT', component: markRaw(SelectImage) }],
// hiddenTabs: ['upload'],
upload(file: File) {
const url = URL.createObjectURL(file)
console.log('mock upload api :>> ', url)
return Promise.resolve(url)
}
}),
Video,
Table.configure({ divider: true }),
Blockquote,
HorizontalRule,
CodeBlock.configure({ divider: true }),
History.configure({ divider: true }),
Fullscreen
]
})
main.ts
import { createApp } from 'vue'
import { createVuetify } from 'vuetify'
import App from './App.vue'
import { vuetifyProTipTap } from './tiptap'
import 'vuetify/styles'
const vuetify = createVuetify()
const app = createApp(App)
app.use(vuetify)
app.use(vuetifyProTipTap)
// fix warning injected property "decorationClasses" is a ref and will be auto-unwrapped
// https://github.com/ueberdosis/tiptap/issues/1719
app.config.unwrapInjectedRef = true
app.mount('#app')
import { markRaw } from 'vue'
import { VuetifyTiptap, VuetifyViewer, createVuetifyProTipTap, defaultBubbleList } from 'vuetify-pro-tiptap'
import { BaseKit, Image, Fullscreen } from 'vuetify-pro-tiptap'
import 'vuetify-pro-tiptap/style.css'
import SelectImage from './components/SelectImage.vue'
export const vuetifyProTipTap = createVuetifyProTipTap({
// Set default lang
lang: 'zhHans',
// Set markdown theme
markdownTheme: 'github',
// Global registration app.component
components: {
VuetifyTiptap,
VuetifyViewer
},
// Global registration extensions
extensions: [
BaseKit.configure({
placeholder: {
placeholder: 'Enter some text...'
},
bubble: {
// default config
list: {
image: [ 'float-left', 'float-none', 'float-right', 'divider', 'size-small', 'size-medium', 'size-large', 'divider', 'textAlign', 'divider', 'image', 'image-aspect-ratio', 'remove'],
text: ['bold', 'italic', 'underline', 'strike', 'divider', 'color', 'highlight', 'textAlign', 'divider', 'link'],
video: ['video', 'remove']
},
defaultBubbleList: editor => {
// You can customize the bubble menu here
return defaultBubbleList(editor) // default customize bubble list
}
}
}),
Image.configure({
// Generate a VDivider after the button
divider: true,
// Custom image tabs
imageTabs: [{ name: 'SELECT', component: markRaw(SelectImage) }],
// hidden default tab
hiddenTabs: ['upload'],
// custom upload function
upload(file) {
const url = URL.createObjectURL(file)
console.log('mock upload api :>> ', url)
return Promise.resolve(url)
}
}),
Fullscreen.configure({
// Generate a VSpacer after the button
spacer: true
})
]
})
You can use the necessary extensions. The corresponding command-buttons will be added by declaring the order of the extension.
All available extensions:
BaseKit
Bold
Italic
Underline
Strike
Color
Highlight
Heading
TextAlign
FontFamily
FontSize
SubAndSuperScript
BulletList
OrderedList
TaskList
Indent
Link
MarkdownTheme
Image
Video
Table
Blockquote
HorizontalRule
Code
CodeBlock
Clear
Fullscreen
History
Create github.scss
$value: 'github';
.vuetify-pro-tiptap-editor__content.markdown-theme-#{$value} {
// your custom styles
&.__dark {
// your dark mode custom styles
}
}
Import github.scss in ts
// import 'vuetify-pro-tiptap/style.css' // import all(editor and markdown) styles
import 'vuetify-pro-tiptap/styles/editor.css' // only use editor style, not using markdown style
import './styles/markdown/github.scss'
In the component using
<template>
<VuetifyTiptap v-model="content" markdown-theme="github" />
<VuetifyViewer :value="content" markdown-theme="github" />
</template>
PreviewActionButton.vue
<script setup lang="ts">
import type { Editor } from '@tiptap/vue-3'
import { mdiClose, mdiFileCodeOutline } from '@mdi/js'
import { ref } from 'vue'
import { ActionButton } from 'vuetify-pro-tiptap'
interface Props {
editor: Editor
tooltip?: string
disabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
tooltip: undefined,
disabled: false
})
const dialog = ref(false)
const maxWidth = ref<number>(900)
</script>
<template>
<ActionButton tooltip="全屏" :disabled="disabled">
<VIcon>{{ `svg:${mdiFileCodeOutline}` }}</VIcon>
<VDialog v-model="dialog" fullscreen hide-overlay activator="parent">
<VCard>
<VToolbar dark color="primary">
<VBtn icon dark @click="dialog = false">
<VIcon>{{ `svg:${mdiClose}` }}</VIcon>
</VBtn>
</VToolbar>
<VContainer>
<VSheet class="mx-auto" :max-width="maxWidth">
<VuetifyViewer :value="editor.getHTML()" />
</VSheet>
</VContainer>
</VCard>
</VDialog>
</ActionButton>
</template>
preview.ts
import type { ButtonView, GeneralOptions } from 'vuetify-pro-tiptap'
import { Extension } from '@tiptap/core'
import PreviewActionButton from '../components/PreviewActionButton.vue'
export interface PreviewOptions extends GeneralOptions {
button: ButtonView
}
export default Extension.create<PreviewOptions>({
name: 'preview',
addOptions() {
return {
divider: false,
spacer: false,
button: () => ({
component: PreviewActionButton,
componentProps: {}
})
}
}
})
You can declare when you install the plugin.
import { createVuetifyProTipTap } from 'vuetify-pro-tiptap'
const VuetifyProTipTap = createVuetifyProTipTap({
lang: 'zhHans'
})
Or use setLang
dynamic change
import { locale } from 'vuetify-pro-tiptap'
locale.setLang('en')
Available languages:
- en (default)
- zhHans
- nl
- de
Loading unavailable language, use setMessage
for Settings
import { locale } from 'vuetify-pro-tiptap'
locale.setMessage('zhHant', {
// i18n text
})
locale.setLang('zhHant')
<script setup lang="ts">
import { ref } from 'vue'
import { BaseKit, Bold, Color, Fullscreen, Heading, Highlight, History, Image, Italic, Link, Strike, Table, Underline, Video, VuetifyTiptap, VuetifyViewer } from 'vuetify-pro-tiptap'
import 'vuetify-pro-tiptap/style.css'
const extensions = [
BaseKit.configure({
placeholder: {
placeholder: 'Enter some text...'
}
}),
Bold,
Italic,
Underline,
Strike,
Color,
Highlight,
Heading,
Link,
Image,
Video,
Table,
Fullscreen,
History
]
const content = ref('')
</script>
<template>
<VApp id="app">
<VContainer>
<VuetifyTiptap v-model="content" label="Title" rounded :min-height="200" :max-height="465" :max-width="900" :extensions="extensions" />
<VuetifyViewer :value="content" />
</VContainer>
</VApp>
</template>
Name | Type | Default | Description |
---|---|---|---|
modelValue | string | JSONContent | '' | The input’s value |
markdownTheme | string | false | 'default' | Markdown theme |
output | 'html' | 'json' | 'text' | 'html' | Output format |
dark | boolean | false | Applies the dark theme variant to the component. |
dense | boolean | false | Reduces the input height |
outlined | boolean | true | Applies the outlined style to the input |
flat | boolean | true | Removes the card’s elevation |
disabled | boolean | false | Disable the input |
label | string | undefined | Sets input label |
hideToolbar | boolean | false | Hidden the toolbar |
disableToolbar | boolean | false | Disable the toolbar |
hideBubble | boolean | false | Hidden the bubble menu |
removeDefaultWrapper | boolean | false | Default wrapper when the delete editor is empty |
maxWidth | string | number | undefined | Sets the maximum width for the component. |
minHeight | string | number | undefined | Sets the minimum height for the component. |
maxHeight | string | number | undefined | Sets the maximum height for the component. |
extensions | AnyExtension[] | [] | Tiptap the extensions |
editorClass | string | string[] | Record<string, any> | undefined | Editor class |
Name | Description |
---|---|
editor | Slot to customize editor |
bottom | Slot to customize editor bottom |
Name | Type | Description |
---|---|---|
update:modelValue | string | JSONContent | Emitted when editor onUpdate |
update:markdownTheme | string | Emitted when change theme |
change | { editor: Editor, output: string | JSONContent } | Emitted when editor onUpdate |
enter | Keyboard enter return |
Name | Type | Default | Description |
---|---|---|---|
value | string | JSONContent | '' | The preview’s value |
dark | boolean | false | Applies the dark theme variant to the component. |
dense | boolean | false | Reduces the input height |
markdownTheme | string | false | 'default' | Markdown theme |
xss | boolean | true | Enable xss filter |
xssOptions | xss.IWhiteList | Default rule | Xss filter rule config |
maxWidth | string | number | undefined | Sets the maximum width for the component. |
extensions | AnyExtension[] | [] | Tiptap the extensions |
Name | Description |
---|---|
before | Add content at the before |
after | Add content at the after |
- 🍴Fork it
- 🔀Create your branch:
git checkout -b your-branch
- 🎨Make your changes
- 📝Commit your changes with Semantic Commit Messages (recommended)
- 🚀Push to the branch:
git push origin your-branch
- 🎉Submit a PR to
develop
branch