I’m running Tiptap with Vue.js. 🎉
You can also teach the editor new things. For example to recognize hex colors and add a color
81 |swatch on the fly: #FFF, #0D0D0D, #616161, #A975FF, #FB5151, #FD9170, #FFCB6B, #68CEF8, #80cbc4, #9DEF8F
82 | ` 83 | } 84 | } 85 | case 'heroTitle': 86 | return { 87 | id, 88 | type: 'heroTitle', 89 | label: '标题', 90 | props: { 91 | content: '标题' 92 | } 93 | } 94 | case 'image': 95 | return { 96 | id, 97 | type: 'image', 98 | label: '图片', 99 | props: { 100 | url: 'https://images.pexels.com/photos/2577274/pexels-photo-2577274.jpeg?auto=compress&cs=tinysrgb&w=1600' 101 | } 102 | } 103 | case 'view': 104 | return { 105 | id, 106 | type: 'view', 107 | label: '视图', 108 | props: { 109 | fields: { 110 | id: { 111 | type: 'text' 112 | } 113 | }, 114 | fieldProps: [], 115 | data: [] 116 | } 117 | } 118 | case 'chart': 119 | return { 120 | id, 121 | type: 'chart', 122 | label: '图表', 123 | props: { 124 | chartType: 'echarts' 125 | } 126 | } 127 | case 'button': 128 | return { 129 | id, 130 | type: 'button', 131 | label: '按钮', 132 | props: { 133 | content: '按钮' 134 | } 135 | } 136 | case 'form': 137 | return { 138 | id, 139 | type: 'form', 140 | label: '表单', 141 | props: { 142 | fields: [] 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/hooks/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue" 2 | 3 | import { useAppEditorStore } from "@/stores/appEditor" 4 | 5 | export const useClickOutside = (domRef: RefI’m running Tiptap with Vue.js. 🎉
You can also teach the editor new things. For example to recognize hex colors and add a color
29 |swatch on the fly: #FFF, #0D0D0D, #616161, #A975FF, #FB5151, #FD9170, #FFCB6B, #68CEF8, #80cbc4, #9DEF8F
30 | ` 31 | } 32 | }, 33 | { 34 | id: '4', 35 | type: 'heroTitle', 36 | label: '标题', 37 | props: { 38 | content: '标题' 39 | } 40 | }, 41 | { 42 | id: '5', 43 | type: 'image', 44 | label: '图片', 45 | props: { 46 | url: 'https://images.pexels.com/photos/2577274/pexels-photo-2577274.jpeg?auto=compress&cs=tinysrgb&w=1600' 47 | } 48 | }, 49 | { 50 | id: '6', 51 | type: 'view', 52 | label: '视图', 53 | props: { 54 | fields: { 55 | 'id': { 56 | type: 'text', 57 | }, 58 | }, 59 | fieldProps: [], 60 | data: [], 61 | 62 | } 63 | }, 64 | { 65 | id: '7', 66 | type: 'button', 67 | label: '按钮', 68 | props: { 69 | content: '按钮' 70 | } 71 | }, 72 | { 73 | id: '8', 74 | type: 'form', 75 | label: '表单', 76 | props: { 77 | fields: [] 78 | } 79 | } 80 | ] 81 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { defineAsyncComponent } from 'vue' 2 | import { createMemoryHistory, createRouter, createWebHistory } from 'vue-router' 3 | 4 | import AppView from '../views/AppView.vue' 5 | 6 | const router = createRouter({ 7 | history: createWebHistory(import.meta.env.BASE_URL), 8 | routes: [ 9 | { 10 | path: '/app', 11 | name: 'home', 12 | component: AppView, 13 | children: [ 14 | { 15 | path: 'dataSource', 16 | name: 'dataSource', 17 | component: defineAsyncComponent(() => import('../views/DataSourceView.vue')), 18 | children: [ 19 | { 20 | path: ':id', 21 | component: defineAsyncComponent( 22 | () => import('../views/DataSourceContent/DataSourceContent.vue') 23 | ) 24 | }, 25 | { 26 | path: '', 27 | redirect: '/app/dataSource/1' 28 | } 29 | ] 30 | }, 31 | { 32 | path: 'layout', 33 | name: 'layout', 34 | component: defineAsyncComponent(() => import('../views/PageLayoutView.vue')) 35 | }, 36 | { 37 | path: 'actions', 38 | name: 'actions', 39 | component: defineAsyncComponent(() => import('../views/ActionsView.vue')) 40 | } 41 | ] 42 | }, 43 | { 44 | path: '/runner', 45 | name: 'runner', 46 | component: defineAsyncComponent(() => import('../views/RunnerView.vue')) 47 | }, 48 | { 49 | path: '/about', 50 | name: 'about', 51 | // route level code-splitting 52 | // this generates a separate chunk (About.[hash].js) for this route 53 | // which is lazy-loaded when the route is visited. 54 | component: defineAsyncComponent(() => import('../views/AboutView.vue')) 55 | }, 56 | { 57 | path: '/', 58 | redirect: '/app/layout' 59 | } 60 | ] 61 | }) 62 | 63 | // export const innerRouter = createRouter({ 64 | // history: createMemoryHistory(import.meta.env.BASE_URL), 65 | // routes: [ 66 | // { 67 | // path: '/', 68 | // name: 'home', 69 | // component: HomeView 70 | // } 71 | // ] 72 | // }) 73 | 74 | export default router 75 | -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | import type { App } from 'vue' 2 | 3 | import ChartBlock from '@/blocks/basic/ChartBlock.vue' 4 | import HeroTitleBlock from '@/blocks/basic/HeroTitleBlock.vue' 5 | import ImageBlock from '@/blocks/basic/ImageBlock.vue' 6 | import QuoteBlock from '@/blocks/basic/QuoteBlock.vue' 7 | import ViewBlock from '@/blocks/basic/ViewBlock.vue' 8 | import ButtonBlock from '@/blocks/external/ButtonBlock.vue' 9 | import FormBlock from '@/blocks/external/FormBlock.vue' 10 | import NotesBlock from '@/blocks/external/NotesBlock.vue' 11 | import type { BlockType } from '@/types/block' 12 | 13 | const baseBlocks = [ 14 | { 15 | type: 'quote', 16 | material: QuoteBlock 17 | }, 18 | { 19 | type: 'heroTitle', 20 | material: HeroTitleBlock 21 | }, 22 | { 23 | type: 'view', 24 | material: ViewBlock 25 | }, 26 | { 27 | type: 'chart', 28 | material: ChartBlock 29 | }, 30 | { 31 | type: 'image', 32 | material: ImageBlock 33 | } 34 | ] 35 | // 因为我们后面会考虑插件市场,所以我们需要一个类来管理所有的 block 36 | // 只有你安装了对应的外部插件,你才能在页面中使用 37 | class BlockSuite { 38 | private blocks = baseBlocks 39 | constructor() {} 40 | getBlocksMap() { 41 | return Object.fromEntries(this.blocks.map((block) => [block.type, block])) 42 | } 43 | getBlocks() { 44 | return this.blocks 45 | } 46 | addBlock(block: any) { 47 | this.blocks.push(block) 48 | } 49 | hasBlock(type: BlockType) { 50 | return !!this.getBlocksMap()[type] 51 | } 52 | } 53 | 54 | const blockSuite = new BlockSuite() 55 | 56 | console.log( 57 | '🚀 ~ file: BlockRenderer.vue:55 ~ blockSuite.hasBlock(button):', 58 | blockSuite.hasBlock('button') 59 | ) 60 | 61 | blockSuite.addBlock({ 62 | type: 'button', 63 | material: ButtonBlock 64 | }) 65 | blockSuite.addBlock({ 66 | type: 'form', 67 | material: FormBlock 68 | }) 69 | blockSuite.addBlock({ 70 | type: 'notes', 71 | material: NotesBlock 72 | }) 73 | console.log( 74 | '🚀 ~ file: BlockRenderer.vue:68 ~ blockSuite.hasBlock(button):', 75 | blockSuite.hasBlock('button') 76 | ) 77 | const blocksMap = blockSuite.getBlocksMap() 78 | 79 | export const blocksMapSymbol = Symbol('blocksMap') 80 | 81 | export const setup = (app: App