自訂指南
自訂 extension
加 extension
用 :extensions 傳入任何 tiptap extension。要注意 editor 的 schema 在建立當下就固定了、之後沒辦法再加,所以 node / mark 這類 extension 一定要一開始就放進去:
<script setup lang="ts">import CharacterCount from '@tiptap/extension-character-count'import { AdminEditorToolbar, defaultEditorItems } from '@/registry/admin/ui/editor-toolbar'import { AdminRichEditor } from '@/registry/admin/ui/rich-editor'</script><template> <admin-rich-editor v-slot="{ editor, handlers }" v-model="value" :extensions="[CharacterCount.configure({ limit: 500 })]" > <admin-editor-toolbar :editor :handlers="handlers" :items="defaultEditorItems" /> </admin-rich-editor></template>擴充套件
admin-rich-editor 預設只帶 StarterKit,Image、TextAlign、Placeholder 這些要另外裝。自行安裝對應的官方 @tiptap/extension-* 套件,.configure({}) 設定好再傳進 :extensions:
<script setup lang="ts">import { Image } from '@tiptap/extension-image'import TextAlign from '@tiptap/extension-text-align'</script><template> <admin-rich-editor :extensions="[TextAlign.configure({ types: ['heading', 'paragraph'] }), Image.configure({})]" /></template>StarterKit
配置內建行為
<admin-rich-editor :starter-kit="{ heading: { levels: [1, 2] }, codeBlock: false }" />替換內建 extension
StarterKit 內建了 Link 等 extension。想換成自己的版本,得先用 :starter-kit 把內建的那個關掉,再從 :extensions 傳入,否則兩個同名 extension 會同時生效。完整可替換清單與限制見 tiptap StarterKit 官方文件。
<admin-rich-editor :starter-kit="{ link: false }" :extensions="[MyLink]" />工具列
AdminEditorToolbar 靠 items 這個陣列決定要畫哪些按鈕,型別是 EditorToolbarItem[][],外層陣列用來分組。每個 item 可以是:
{ kind: '...' }:走內建或:handlers註冊的 handler{ items: [...] }:dropdown(巢狀){ type: 'label' | 'separator' }:純標示{ onClick }:無 kind 的純自訂按鈕{ slot: 'name' }:改由具名 slot 渲染
有狀態的按鈕(用 :handlers)
如果按鈕的反白(active)、禁用(disabled)要跟著編輯狀態即時變化(按下會 toggle、游標移到粗體文字上時按鈕要亮起來),就得用 :handlers 註冊一個 kind。handler 裡給 canExecute、execute、isActive 三個 function,toolbar 會拿它們自動算出當下要不要反白、要不要禁用:
<script setup lang="ts">import type { EditorToolbarItem } from '@/registry/admin/ui/editor-toolbar'const handlers = { highlightLine: { canExecute: (editor) => editor.can().toggleNode?.('paragraph', 'paragraph') ?? true, execute: (editor) => editor.chain().focus().toggleMark('bold'), isActive: (editor) => editor.isActive('bold'), },}// satisfies 掛在自訂這組,自訂 kind 才有型別檢查:// 打錯或用了沒註冊的 kind 會在編譯期就報錯const items = [ ...defaultEditorItems, [{ kind: 'highlightLine', label: '強調', tooltip: { text: '強調' } }] satisfies EditorToolbarItem< typeof handlers >[],]</script><template> <admin-rich-editor v-slot="{ editor, handlers: mergedHandlers }" v-model="value" :handlers="handlers"> <admin-editor-toolbar :editor :handlers="mergedHandlers" :items="items" /> </admin-rich-editor></template>toolbar 收的是外層 v-slot 吐出的 mergedHandlers(你的自訂 kind + StarterKit 內建 kind 已合併),這樣預設按鈕和自訂按鈕才會同時有 handler 可用。
無狀態的按鈕(用 onClick)
純觸發、不需要 active 同步的按鈕(插入內容、開外部視窗…)直接用 onClick,免註冊 kind。onClick 只收得到 MouseEvent、拿不到 editor,要操作 editor 就從 rich-editor 的 exposed ref 取:
<script setup lang="ts">import { IconSeparator } from '@tabler/icons-vue'import { useTemplateRef } from 'vue'import { AdminEditorToolbar, defaultEditorItems } from '@/registry/admin/ui/editor-toolbar'import { AdminRichEditor } from '@/registry/admin/ui/rich-editor'const richEditor = useTemplateRef<InstanceType<typeof AdminRichEditor>>('richEditor')const items = [ ...defaultEditorItems, [{ label: '插入分隔', icon: IconSeparator, onClick: () => richEditor.value?.editor?.commands.setHorizontalRule(), }],]</script><template> <admin-rich-editor ref="richEditor" v-slot="{ editor, handlers }" v-model="value"> <admin-editor-toolbar :editor :handlers="handlers" :items="items" /> </admin-rich-editor></template>- 要按鈕反白 / disabled 跟著編輯狀態變 →
:handlers註冊 kind(onClick沒有狀態同步)。 - 只是按一下做件事、長相固定 →
onClick即可。 - item 同時有
kind與onClick時,kind 優先、onClick 忽略。
完全自訂渲染(用 slot)
toolbar 開了兩種 slot,不用改 toolbar 內部即可塞 button / dropdown / 元件:
- 通用
#item:攔下每一顆 item,全部自己畫 - 具名 slot:在 item 上寫
slot: 'xxx',對應的<template #xxx>就會用你的元件取代那顆標準按鈕
兩種 slot 拿到的 props 都是 { item, index, isActive, isDisabled, onClick }(對齊 Nuxt UI)。這裡的 item 是原始資料,狀態要自己用 isActive(item)、isDisabled(item) 算,點擊呼叫 onClick(e, item)。props 裡沒有 editor,需要時從外層 <AdminRichEditor v-slot="{ editor }"> 拿。
<template> <admin-rich-editor v-slot="{ editor, handlers }" v-model="value"> <admin-editor-toolbar :editor :handlers="handlers" :items="items"> <!-- slot props 無 editor;從外層 v-slot 的 editor 取 --> <template #myPicker> <my-custom-button :editor="editor" /> </template> </admin-editor-toolbar> </admin-rich-editor></template>slot 內容定義在 consumer 作用域,取不到 <AdminRichEditor> provide 的 context。需要 editor 時用外層 <AdminRichEditor v-slot="{ editor }"> 的 editor,不要靠 inject。
範例:接一排對齊按鈕
文字對齊要三步:裝上官方 TextAlign extension、用 :handlers 註冊左/中/右三個 kind、再把對應按鈕加進 toolbar。按鈕的反白由 handler 的 isActive 自動同步:
<script setup lang="ts">import type { Editor, JSONContent } from '@tiptap/core'import type { EditorToolbarItem } from '@/components/admin/editor-toolbar'import { IconAlignCenter, IconAlignLeft, IconAlignRight } from '@tabler/icons-vue'import TextAlign from '@tiptap/extension-text-align'import { ref } from 'vue'import { cn } from '@/lib/utils'import { baseProseClass } from '@/components/admin/editor'import { AdminEditorToolbar, defaultEditorItems } from '@/components/admin/editor-toolbar'import { AdminRichEditor, starterKitProseClass } from '@/components/admin/rich-editor'const value = ref<JSONContent | string>( '<h2 style="text-align: center">置中標題</h2><p style="text-align: right">這段靠右對齊。</p><p>這段預設靠左。</p>')// 1. 裝上官方 TextAlign extensionconst extensions = [TextAlign.configure({ types: ['heading', 'paragraph'] })]// 2. 每個對齊方向註冊一個 kind:handler 提供 canExecute / execute / isActivefunction alignHandler(align: 'left' | 'center' | 'right') { return { canExecute: (editor: Editor) => editor.can().setTextAlign(align), execute: (editor: Editor) => editor.chain().focus().setTextAlign(align), isActive: (editor: Editor) => editor.isActive({ textAlign: align }) }}const handlers = { alignLeft: alignHandler('left'), alignCenter: alignHandler('center'), alignRight: alignHandler('right')}// 3. 用 kind 掛上按鈕,toolbar 會據 handler 自動算 active / disabled// satisfies 掛在自訂這組:打錯或用了沒註冊的 kind 會在編譯期就報錯const items = [ ...defaultEditorItems, [ { kind: 'alignLeft', icon: IconAlignLeft, tooltip: { text: '靠左' } }, { kind: 'alignCenter', icon: IconAlignCenter, tooltip: { text: '置中' } }, { kind: 'alignRight', icon: IconAlignRight, tooltip: { text: '靠右' } } ] satisfies EditorToolbarItem<typeof handlers>[]]</script><template> <admin-rich-editor v-slot="{ editor, handlers: mergedHandlers }" v-model="value" :extensions="extensions" :handlers="handlers" :ui="{ base: cn(baseProseClass, starterKitProseClass) }" > <admin-editor-toolbar :editor :handlers="mergedHandlers" :items="items" /> </admin-rich-editor></template>不想自己接的話,文字顏色、螢光筆、對齊這類功能本系統都有封裝好的擴充元件可直接用,見 Text Color、Highlight、Text Align。