AioEditor
安裝
pnpm dlx shadcn-vue@latest add @admin/aio-editor安裝即取得 UI 與邏輯核心(tiptap extensions、預設 config、useAioEditor),核心置於元件內 core/ 子目錄,無需另裝其他單元。
用法
預設 config 即包含完整功能:36 個 extensions、35 顆 toolbar 控制項、7 種情境 bubble menu 與 drag handle。
<script setup lang="ts">import { ref } from 'vue'import { AdminAioEditor } from '@/components/admin/aio-editor'const content = ref(` <h2>AioEditor</h2> <p>config 驅動的富文本編輯器,預設 toolbar 即包含<strong>格式</strong>、<em>樣式</em>、清單與區塊工具。</p> <ul> <li>項目清單</li> <li>支援 <code>行內程式碼</code> 與 <mark>螢光標記</mark></li> </ul> <blockquote><p>引用區塊</p></blockquote>`)</script><template> <admin-aio-editor v-model="content" /></template>場景
自訂 toolbar 與 Custom 按鈕
config.features 以宣告陣列決定按鈕集合與順序(同時決定 extension 載入),Divider 分組;Custom 功能可帶 icon / slot / 自帶 extensions。bubble 跟隨宣告自動掛載,dragHandle 可停用。
<script setup lang="ts">import type { AioEditorConfig } from '@/components/admin/aio-editor'import { IconSparkles } from '@tabler/icons-vue'import { ref } from 'vue'import { AdminAioEditor, AioEditorFeature } from '@/components/admin/aio-editor'const content = ref('<p>精簡 toolbar:只放常用按鈕,並示範 Custom 按鈕。</p>')const config: AioEditorConfig = { features: [ AioEditorFeature.FontBold, AioEditorFeature.FontItalic, AioEditorFeature.Highlight, AioEditorFeature.Divider, AioEditorFeature.BulletList, AioEditorFeature.Blockquote, AioEditorFeature.Divider, { kind: AioEditorFeature.Custom, label: '插入問候', icon: IconSparkles, run: (editor) => editor.chain().focus().insertContent('👋 你好!').run() } ], // bubble 跟隨 features 宣告自動掛載(此處僅 BulletList 會帶 List bubble);drag handle 另關 dragHandle: false}</script><template> <admin-aio-editor v-model="content" :config="config" /></template>表格
Table 功能載入 TableKit(Table / TableRow / TableCell / TableHeader)。游標進入表格時依位置掛載 Table / TableColumn / TableRow 三種 bubble,欄寬可拖曳框線調整。
<script setup lang="ts">import type { AioEditorConfig } from '@/components/admin/aio-editor'import { ref } from 'vue'import { AdminAioEditor, AioEditorFeature } from '@/components/admin/aio-editor'const content = ref(` <p>將游標移入表格即出現欄 / 列控制 bubble:可增刪欄列、合併與拆分儲存格。</p> <table> <tbody> <tr> <th>項目</th> <th>說明</th> </tr> <tr> <td>欄寬</td> <td>拖曳框線調整(resizable)</td> </tr> <tr> <td>控制項</td> <td>Table / TableColumn / TableRow 三種 bubble 跟隨游標位置</td> </tr> </tbody> </table>`)const config: AioEditorConfig = { features: [ AioEditorFeature.Undo, AioEditorFeature.Redo, AioEditorFeature.Divider, AioEditorFeature.FontBold, AioEditorFeature.Divider, AioEditorFeature.Table ]}</script><template> <admin-aio-editor v-model="content" :config="config" /></template>圖片與 requestUrl
Image 功能之插入來源走 config.toolbarOptions.requestUrl(Link / Video 共用同一入口,以 kind 區分);未提供時 fallback 為 window.prompt。實務上在此接上傳對話框或素材庫,回傳 null 即取消。點選圖片出現 Image bubble,可切換對齊與寬度。
<script setup lang="ts">import type { AioEditorConfig } from '@/components/admin/aio-editor'import { ref } from 'vue'import { AdminAioEditor, AioEditorFeature } from '@/components/admin/aio-editor'const content = ref(` <p>點選圖片即出現 Image bubble:可切換對齊、調整寬度或移除。</p> <img src="/demo/sample.gif" alt="範例圖片" />`)// requestUrl 為 Link / Image / Video 之 URL 輸入來源;未提供時 fallback 為 window.prompt。// 實務上這裡接上傳對話框或素材庫,回傳 null 代表取消。const config: AioEditorConfig = { features: [ AioEditorFeature.Undo, AioEditorFeature.Redo, AioEditorFeature.Divider, AioEditorFeature.AlignLeft, AioEditorFeature.AlignCenter, AioEditorFeature.Divider, AioEditorFeature.Image ], toolbarOptions: { requestUrl: ({ kind, previous }) => { if (kind !== 'image') { return previous ?? null } return '/demo/sample.gif' } }}</script><template> <admin-aio-editor v-model="content" :config="config" /></template>前台渲染輸出 HTML
編輯器內容樣式已拆為共通 prose 樣式 adminAioEditorProseClass:前台(或任何唯讀情境)渲染編輯器產出的 HTML 時,把它套在容器上即可對齊編輯時外觀,不需掛編輯器。
<script setup lang="ts">import { adminAioEditorProseClass } from '@/components/admin/aio-editor'import { cn } from '@/lib/utils'</script><template> <div :class="cn(adminAioEditorProseClass)" v-html="htmlFromEditor"></div></template><script setup lang="ts">import { ref } from 'vue'import { cn } from '@/lib/utils'import { AdminAioEditor, adminAioEditorProseClass } from '@/components/admin/aio-editor'const content = ref(` <h3>發布預覽</h3> <p>左側編輯,右側即為<strong>前台渲染</strong>結果——同一份 prose 樣式。</p> <ul> <li>清單與 <code>行內程式碼</code></li> </ul> <blockquote><p>引用區塊</p></blockquote>`)</script><template> <div :class="` grid w-full gap-4 lg:grid-cols-2 `" > <admin-aio-editor v-model="content" /> <div :class="cn(`rounded-[6px] border border-dashed border-text-30 p-3`, adminAioEditorProseClass)" v-html="content" ></div> </div></template>Mention(opt-in)
Mention / HashTag 不在預設集合——需經 config.include 啟用,並以 mentionSuggestion 的 runtime hooks 提供選單渲染實作(選單 UI 由 consumer 全權決定)。
<script setup lang="ts">import type { SuggestionProps } from '@tiptap/suggestion'import type { AioEditorConfig } from '@/components/admin/aio-editor'import { ref } from 'vue'import { cn } from '@/lib/utils'import { AdminAioEditor, AioEditorCoreExtension, AioEditorFeature, mentionSuggestion} from '@/components/admin/aio-editor'const content = ref('<p>輸入 @ 觸發 mention 選單。</p>')const MEMBERS = ['王小明', '陳美麗', '林大同', '張雅婷']// mention 選單為 consumer 自行實作:以 runtime hooks 掛一個最小清單 UIfunction renderMenu(mountEl: HTMLElement, props: SuggestionProps) { mountEl.innerHTML = '' const items = MEMBERS.filter((name) => name.includes(props.query)) for (const name of items) { const button = document.createElement('button') button.type = 'button' button.textContent = name button.className = 'aio-mention-option' button.addEventListener('mousedown', (event) => { event.preventDefault() props.command({ id: name, label: name }) }) mountEl.appendChild(button) } return mountEl}const config: AioEditorConfig = { include: [AioEditorCoreExtension.Mention], extensionsConfig: { Mention: { suggestion: { ...mentionSuggestion({ onRender: (props, mountEl) => renderMenu(mountEl!, props as SuggestionProps), onUpdate: (props, componentEl) => { if (componentEl) { renderMenu(componentEl, props as SuggestionProps) } } }), items: ({ query }) => MEMBERS.filter((name) => name.includes(query)) } } }, features: [AioEditorFeature.FontBold, AioEditorFeature.FontItalic], dragHandle: false}</script><template> <admin-aio-editor v-model="content" :class=" cn(` [&_.aio-mention-option]:block [&_.aio-mention-option]:w-full [&_.aio-mention-option]:rounded-[4px] [&_.aio-mention-option]:px-3 [&_.aio-mention-option]:py-1 [&_.aio-mention-option]:text-left [&_.aio-mention-option]:hover:bg-bg-70 [&_.editor-mention]:text-primary-40 [&_.editor-suggestion]:z-10 [&_.editor-suggestion]:rounded-[6px] [&_.editor-suggestion]:border [&_.editor-suggestion]:border-text-30 [&_.editor-suggestion]:bg-bg-90 [&_.editor-suggestion]:p-1 [&_.editor-suggestion]:shadow-md `) " :config="config" /></template>API 參考
aio-editor
Props
| 名稱 | 型別 | 預設值 | 說明 |
|---|---|---|---|
config | AioEditorConfig | undefined | — | 編輯器設定:features / extensions / 選項覆寫,未指定欄位取預設值 |
modelValue | string | JSONContent | undefined | — | 內容(v-model):依 contentType 解讀;未指定 contentType 時 string 視為 html、物件視為 json |
contentType | "json" | "html" | undefined | — | 內容型別(顯式優先於自動推斷) |
editable | boolean | undefined | true | 是否可編輯 |
id | string | undefined | — | 元件 id:多編輯器頁面之辨識,寫入 hooks context 與 editor.storage.aio.id;未提供自動產生 |
autofocus | FocusPosition | undefined | false | 掛載後自動聚焦位置(true = 'start';false 不聚焦) |
enableInputRules | boolean | ((names: string[]) => boolean | string[]) | undefined | true | 是否啟用輸入規則;函式形接收最終 extension 名單,回傳 boolean 或要啟用的名稱陣列 |
enablePasteRules | boolean | ((names: string[]) => boolean | string[]) | undefined | true | 是否啟用貼上規則;函式形同 enableInputRules |
toolbar | boolean | undefined | true | 是否顯示工具列(false 隱藏,extensions 照常載入) |
toolbarTarget | string | HTMLElement | undefined | — | 工具列外掛目標:CSS selector 或 HTMLElement(Teleport),未指定渲染於編輯器頂部 |
ui | { base?: string | undefined; content?: string | undefined; toolbar?: string | undefined; bubble?: string | undefined; } | undefined | — | 各部位 class 覆寫 |
class | ClassValue | — | root 之 class |
Events
| 名稱 | Payload | 說明 |
|---|---|---|
blur | any | — |
focus | any | — |
keydown | any | — |
update:modelValue | any | — |
create | any | — |
update | any | — |
selectionUpdate | any | — |
destroy | any | — |
contentError | any | — |
Slots
| 名稱 | Bindings | 說明 |
|---|---|---|
default | { editor: Editor | undefined; editorId: string; } | 預設 slot:置於內容區之後,暴露 editor 實例供進階操作 |
footer | { editor: Editor | undefined; editorId: string; } | 底部列:預設渲染字數統計(設定 CharacterCount limit 時),提供此 slot 可完全自訂 |
Expose
| 名稱 | 簽名 | 說明 |
|---|---|---|
editor | Editor | undefined | tiptap Editor 實例(mounted 前為 undefined) |
config 之完整型別(AioEditorConfig、AioEditorExtensionsConfig、enums 與各 default*Config)由 @admin/aio-editor 匯出,各 extension 的完整選項說明見 Extensions 總覽。
注意事項
- URL 輸入來源:Link / Image / Video 按鈕預設以
window.prompt取得 URL;提供config.toolbarOptions.requestUrl(ctx 帶editor/editorId/target)即可換成自訂 UI。連結編輯已內建於 LinkTips bubble 的 inline 輸入框。 - 不允許的連結網址:網址被 Link extension 的
isAllowedUri拒絕(如javascript:)時不會套用,LinkTips 輸入框還原為原網址;提供config.toolbarOptions.onInvalidLink(ctx 帶url,LinkTips 觸發時target為輸入框)即可顯示自訂提示。選取範圍本身不能加連結(程式碼區塊、行內程式碼)時同樣不會套用,但不屬網址問題,不會呼叫onInvalidLink。 - 色盤:TextColor / Highlight 內建
@admin/color-picker色票(toolbarOptions.textColorSwatches / highlightSwatches自訂調色盤);要自由取色提供requestColor。value: ''為清除項;完全透明之色票(transparent、#0000、rgba(…, 0)等)於浮層中會加上外框以便辨識。 - 工具列版面:
toolbar: false隱藏(extensions 照載);toolbarTarget可把工具列 Teleport 到任意容器(多編輯器共用一條、頂部固定列、浮動容器)。 - 事件:
create / update / selectionUpdate / focus / blur / destroy / contentError / keydown透傳,payload 統一帶{ editor, editorId }——多編輯器頁面以editorId(或idprop)辨識來源。 - 圖片 / 影片上傳:貼上與拖放檔案會觸發
extensionsConfig.Image.pasteAndDropHandle/extensionsConfig.Video.pasteAndDropHandle,上傳邏輯由 consumer 實作後回呼afterImageUpload/afterVideoUpload。未實作時僅於 console 提示。 - Mention / HashTag:無按鈕功能,經
config.include啟用並提供 suggestiononRender實作。UndoRedo / Code 同理——未宣告按鈕時要保留能力就include。 - v-model 型別:綁
string視為 HTML、綁物件視為 JSON(tiptapJSONContent)。不掛編輯器的轉換用editorJson2Html/editorHtml2Json(瀏覽器環境限定)。 - 外部寫入不觸發事件:父層透過 v-model 寫入內容、或切換
editable時,不會 emitupdate:modelValue與update——父層拿到的值維持原樣,不會被換成編輯器正規化後的 HTML,表單載入後也不會被判為已修改。只有使用者編輯(含透過editor實例執行之 command)才會觸發;字數統計則一律隨內容更新。 - exclude 一律生效:
exclude對任何 extension(含 schema 必備之 Document / Text / Paragraph)都會生效、不自動保留。排除後以extraExtensions或 Customextensions提供同名 extension 即視為替換、不警告;未替換且有依賴者受影響時 console 警告;schema 必備項於最終清單缺席時給最強警告(編輯器將無法初始化)。toolbar 按鈕依 extension 啟用狀態自動隱藏。
更新紀錄
- fix: 透明色票之其他寫法也套用清除項外框4831781
- fix: 選取範圍無法加連結時不再誤觸 onInvalidLink92db11a
- refactor: 工具列 Link 改以 can() 判定網址是否允許5e30a51
- fix: 清除色票外框與勾勾僅套用於透明色票f79356c
- fix: 外部載入內容後字數統計未更新d7a90ba
- fix: 父層設回先前的 v-model 值時未同步編輯器內容fe06783
- fix: 修正清除色票在顏色浮層中看不見51d18fe
- fix: exclude 搭配同名替換時不再印出警告e25a859
- feat: 新增 toolbarOptions.onInvalidLink 處理不允許的連結網址6aeeeb9
- fix: 父層同步 v-model 或 editable 時不再回寫 update:modelValue1f67b83
- fix: 修正 active 按鈕懸停時底色反而變淺174feba
- fix: 移除工具列 active 按鈕會隨圓角彎曲的底線48d0f73
- fix: 修正文字顏色與螢光標記按鈕懸停時沒有 tooltip92ed8b3
- feat: 新增 config 驅動的 all-in-one 編輯器系列bc3086e