UNPKG

vuetify-pro-tiptap

Version:
496 lines (419 loc) 16.5 kB
# Vuetify Pro Tiptap 一个 Vue.js 的基于 [tiptap](https://github.com/scrumpy/tiptap) 和 [vuetify](https://github.com/vuetifyjs/vuetify) 的 「所见即所得」 富文本编辑器 [![download](https://img.shields.io/npm/dm/vuetify-pro-tiptap.svg)](https://npmcharts.com/compare/vuetify-pro-tiptap?minimal=true) [![version](https://img.shields.io/npm/v/vuetify-pro-tiptap.svg)](https://www.npmjs.org/package/vuetify-pro-tiptap) [![gzip](https://img.badgesize.io/https://unpkg.com/vuetify-pro-tiptap/lib/vuetify-pro-tiptap.js?compression=gzip&label=gzip)](https://img.badgesize.io/https://unpkg.com/vuetify-pro-tiptap/lib/vuetify-pro-tiptap.js?compression=gzip&label=gzip) [![Test](https://github.com/yikoyu/vuetify-pro-tiptap/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/yikoyu/vuetify-pro-tiptap/actions/workflows/test.yml) ![LICENSE](https://img.shields.io/badge/License-MIT-yellow.svg) [![semantic-release: vue](https://img.shields.io/badge/semantic--release-vue-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release) [English](./README.md) | 中文 ## Demo 👉[https://yikoyu.github.io/vuetify-pro-tiptap/master/](https://yikoyu.github.io/vuetify-pro-tiptap/master/) 👉[https://yikoyu.github.io/vuetify-pro-tiptap/develop/](https://yikoyu.github.io/vuetify-pro-tiptap/develop/) 👾[Stackblitz](https://stackblitz.com/~/github.com/yikoyu/vuetify-pro-tiptap-demo/tree/main) 👾[Nuxt3 Stackblitz](https://stackblitz.com/~/github.com/yikoyu/vuetify-pro-tiptap-demo/tree/nuxt) ## 特色 - 使用 [vuetify](https://github.com/vuetifyjs/vuetify) 组件 - 许多开箱即用的 [extension](#extensions) (欢迎提交 issue 为新的 feature 留下建议) - 支持 markdown 语法 - TypeScript 支持 - 支持 i18n (`en`, `zhHans`, `nl`, `tr`, `de`, `it`) - Vuetify 3.x 和 Vue 3.x 支持 ## Vuetify 2.x Vuetify 2.x 请使用最新版本的 [vuetify-pro-tiptap@1.X.X](https://github.com/yikoyu/vuetify-pro-tiptap/tree/1.x) ## 安装 ### NPM安装 ```shell pnpm add vuetify-pro-tiptap # 或者 yarn add vuetify-pro-tiptap # 或者 npm i vuetify-pro-tiptap -S ``` ### 安装插件 <details> <summary>tiptap.ts</summary> ```TypeScript 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> ```typescript 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> ## 全局设置 ```TypeScript 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> 你可以只使用需要的 extension,对应的菜单按钮将会按照你声明的顺序被添加。 所有可用的 extensions: - [`BaseKit`](./src/extensions/base-kit.ts) - [`Bold`](./src/extensions/bold.ts) - [`Italic`](./src/extensions/italic.ts) - [`Underline`](./src/extensions/underline.ts) - [`Strike`](./src/extensions/strike.ts) - [`Color`](./src/extensions/color.ts) - [`Highlight`](./src/extensions/highlight.ts) - [`Heading`](./src/extensions/heading.ts) - [`TextAlign`](./src/extensions/text-align.ts) - [`FontFamily`](./src/extensions/font-family.ts) - [`FontSize`](./src/extensions/font-size.ts) - [`SubAndSuperScript`](./src/extensions/subscript.ts) - [`BulletList`](./src/extensions/bullet-list.ts) - [`OrderedList`](./src/extensions/ordered-list.ts) - [`TaskList`](./src/extensions/task-list.ts) - [`Indent`](./src/extensions/indent.ts) - [`Link`](./src/extensions/link.ts) - [`MarkdownTheme`](./src/extensions/markdown-theme.ts) - [`Image`](./src/extensions/image.ts) - [`Video`](./src/extensions/video.ts) - [`Table`](./src/extensions/table.ts) - [`Blockquote`](./src/extensions/blockquote.ts) - [`HorizontalRule`](./src/extensions/horizontal-rule.ts) - [`Code`](./src/extensions/code.ts) - [`CodeBlock`](./src/extensions/code-block.ts) - [`Clear`](./src/extensions/clear.ts) - [`Fullscreen`](./src/extensions/fullscreen.ts) - [`History`](./src/extensions/history.ts) ## 自定义主题 创建 github.scss ```scss $value: 'github'; .vuetify-pro-tiptap-editor__content.markdown-theme-#{$value} { // 自定义样式 &.__dark { // dark 模式下的自定义样式 } } ``` 在 ts 中导入 github.scss ```typescript // import 'vuetify-pro-tiptap/style.css' // 导入全部(editor 和 markdown)样式 import 'vuetify-pro-tiptap/styles/editor.css' // 只使用 editor 样式,不使用 markdown 样式 import './styles/markdown/github.scss' ``` 在组件中使用 ```vue <template> <VuetifyTiptap v-model="content" markdown-theme="github" /> <VuetifyViewer :value="content" markdown-theme="github" /> </template> ``` ## 自定义 extension <details> <summary>PreviewActionButton.vue</summary> ```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> ``` </details> <details> <summary>preview.ts</summary> ```typescript 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> ## 国际化 ### 设置语言 你可以在安装插件的时候声明 ```TypeScript import { createVuetifyProTipTap } from 'vuetify-pro-tiptap' const VuetifyProTipTap = createVuetifyProTipTap({ lang: 'zhHans' }) ``` 或者调用方法动态修改 ```TypeScript import { locale } from 'vuetify-pro-tiptap' locale.setLang('en') ``` 可用的语言: - en (默认) - zhHans - nl - tr - de - it ### 加载新语言 当前语言暂未提供时,可以使用 `setMessage` 进行设置 ```TypeScript import { locale } from 'vuetify-pro-tiptap' locale.setMessage('zhHant', { // 国际化文本 }) locale.setLang('zhHant') ``` ## 用法 ```vue <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 | 名称 | 类型 | 默认值 | 说明 | | -------------------- | ------------------------------------------ | --------- | ---------------------------- | | modelValue | string | '' | 输入的值 | | markdownTheme | string \| false | 'default' | markdown主题 | | output | 'html' \| 'json' \| 'text' | 'html' | 输出格式 | | dark | boolean | false | 是否为深色主题 | | dense | boolean | false | 是否为紧凑模式 | | outlined | boolean | true | 将轮廓样式应用于输入 | | flat | boolean | true | 移除卡片的 elevation | | disabled | boolean | false | 禁用输入 | | label | string | undefined | 设置输入标签 | | hideToolbar | boolean | false | 隐藏工具栏 | | disableToolbar | boolean | false | 禁用工具栏 | | hideBubble | boolean | false | 隐藏气泡菜单 | | removeDefaultWrapper | boolean | false | 删除编辑器为空时默认的包装器 | | maxWidth | string \| number | undefined | 输入框最大宽度 | | minHeight | string \| number | undefined | 输入框最小高度 | | maxHeight | string \| number | undefined | 输入框最大高度 | | extensions | AnyExtension[] | [] | tiptap插件 | | editorClass | string \| string[] \| Record\<string, any> | undefined | 编辑器class | #### Slots | 名称 | 说明 | | ------ | ---------------------- | | editor | 自定义编辑器的插槽 | | bottom | 自定义编辑器底部的插槽 | #### Event | 名称 | 类型 | 说明 | | -------------------- | ------------------------------------------------- | ------------------------ | | update:modelValue | string \| JSONContent | 编辑器 onUpdate 时处触发 | | update:markdownTheme | string | 切换主题时触发 | | change | { editor: Editor, output: string \| JSONContent } | 编辑器 onUpdate 时处触发 | | enter | | 键盘输入回车时触发 | ### VuetifyViewer #### Props | 名称 | 类型 | 默认值 | 说明 | | ------------- | --------------------- | ------------ | --------------- | | value | string \| JSONContent | '' | 预览的值 | | dark | boolean | false | 是否为深色主题 | | dense | boolean | false | 是否为紧凑模式 | | markdownTheme | string \| false | 'default' | markdown主题 | | xss | boolean | true | 是否开启xss过滤 | | xssOptions | xss.IWhiteList | 内置默认规则 | xss过滤规则配置 | | maxWidth | string \| number | undefined | 预览最大宽度 | | extensions | AnyExtension[] | [] | tiptap插件 | #### Slots | 名称 | 说明 | | ------ | -------------- | | before | 在顶部添加内容 | | after | 在底部添加内容 | ## 🏗 贡献代码 1. 🍴Fork it 2. 🔀Create your branch: `git checkout -b your-branch` 3. 🎨Make your changes 4. 📝Commit your changes with [Semantic Commit Messages (recommended)](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) 5. 🚀Push to the branch: `git push origin your-branch` 6. 🎉Submit a PR to `develop` branch ## 📄 许可证 [MIT](./LICENSE) ## 🌹 鸣谢 - [vuetify](https://github.com/vuetifyjs/vuetify) - [tiptap](https://github.com/scrumpy/tiptap) - [element-tiptap](https://github.com/Leecason/element-tiptap)