Awesome
Vuetify Pro Tiptap
A WYSIWYG rich-text editor using tiptap and vuetify for Vue.js
English | 中文
Demo
👉https://yikoyu.github.io/vuetify-pro-tiptap/
Features
- 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
Vuetify 2.x
For Vuetify 2.x please use the latest version of vuetify-pro-tiptap@1.X.X
Installation
NPM
pnpm add vuetify-pro-tiptap
# or
yarn add vuetify-pro-tiptap
# or
npm i vuetify-pro-tiptap -S
Install plugin
<details> <summary>tiptap.ts</summary>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
]
})
</details>
<details>
<summary>main.ts</summary>
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')
</details>
Global Settings
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
})
]
})
<span id="extensions">Extensions</span>
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
Custom theme
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>
Custom extensions
<details> <summary>PreviewActionButton.vue</summary><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>
</details>
<details>
<summary>preview.ts</summary>
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: {}
})
}
}
})
</details>
I18n
Setting language
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
Use unavailable language
Loading unavailable language, use setMessage
for Settings
import { locale } from 'vuetify-pro-tiptap'
locale.setMessage('zhHant', {
// i18n text
})
locale.setLang('zhHant')
Usage
<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>
Props
VuetifyTiptap
Props
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 |
Slots
Name | Description |
---|---|
editor | Slot to customize editor |
bottom | Slot to customize editor bottom |
Event
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 |
VuetifyViewer
Props
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 |
Slots
Name | Description |
---|---|
before | Add content at the before |
after | Add content at the after |
🏗 Contributing
- 🍴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