refactor(tiptap): move coordinator to editor.storage, drop dead Markdown html option

This commit is contained in:
xfy 2026-06-23 18:39:38 +08:00
parent 6b44a9c036
commit d099db4140
3 changed files with 17 additions and 22 deletions

View File

@ -5,8 +5,8 @@ import { TableKit } from '@tiptap/extension-table'
import { TaskList, TaskItem } from '@tiptap/extension-list' import { TaskList, TaskItem } from '@tiptap/extension-list'
import { FileHandler } from '@tiptap/extension-file-handler' import { FileHandler } from '@tiptap/extension-file-handler'
import { SlashCommand } from './slash-command' import { SlashCommand } from './slash-command'
import { UploadCoordinator, type UploadEvent } from './upload-coordinator' import { UploadCoordinator, UPLOAD_COORDINATOR_STORAGE_KEY, type UploadEvent } from './upload-coordinator'
import { UploadImage, setUploadCoordinator } from './upload-image' import { UploadImage } from './upload-image'
import './style.css' import './style.css'
export interface EditorOptions { export interface EditorOptions {
@ -82,9 +82,7 @@ class TiptapEditorInstance {
HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' }, HTMLAttributes: { rel: 'noopener noreferrer', target: '_blank' },
}, },
}), }),
Markdown.configure({ Markdown,
html: false,
}),
TableKit, TableKit,
UploadImage, UploadImage,
TaskList, TaskList,
@ -131,15 +129,16 @@ class TiptapEditorInstance {
}, },
}) })
// 创建上传协调器,注入给 NodeView 的 onRetry/onRemove // 创建上传协调器,挂到 editor.storage 供 NodeView 按实例读取(支持多编辑器实例)。
// onUploadEvent 透传给 coordinator.emit未提供时空操作兜底 // onUploadEvent 透传给 coordinator.emit未提供时空操作兜底
if (this.options.onImageUpload) { if (this.options.onImageUpload) {
this.coordinator = new UploadCoordinator( this.coordinator = new UploadCoordinator(
this.editor, this.editor,
this.options.onImageUpload, this.options.onImageUpload,
this.options.onUploadEvent ?? (() => {}), this.options.onUploadEvent ?? (() => {}),
) )
setUploadCoordinator(this.coordinator) // editor.storage 是开放式索引签名;自定义 key 需绕过严格检查Tiptap 官方扩展 storage 模式)。
;(this.editor.storage as unknown as Record<string, unknown>)[UPLOAD_COORDINATOR_STORAGE_KEY] = this.coordinator
} }
// 通知宿主编辑器已就绪(替代 window.__tiptap_ready 轮询) // 通知宿主编辑器已就绪(替代 window.__tiptap_ready 轮询)
@ -245,9 +244,8 @@ class TiptapEditorInstance {
destroy(): void { destroy(): void {
this.editor?.destroy() this.editor?.destroy()
this.editor = null this.editor = null
// coordinator 通过 editor.storage 访问,随 editor 实例一同回收,无需显式清除引用。
this.coordinator = null this.coordinator = null
// 清除 NodeView 的 coordinator 引用,避免指向已销毁的实例
setUploadCoordinator(null)
// 清理源码模式相关引用(容器 innerHTML 已清空DOM 会随之移除) // 清理源码模式相关引用(容器 innerHTML 已清空DOM 会随之移除)
this.sourceTextarea = null this.sourceTextarea = null
this.toggleButton = null this.toggleButton = null

View File

@ -1,5 +1,8 @@
import type { Editor } from '@tiptap/core' import type { Editor } from '@tiptap/core'
/** editor.storage 上挂载 coordinator 的 key。NodeView 通过此 key 拿到当前编辑器实例的 coordinator。 */
export const UPLOAD_COORDINATOR_STORAGE_KEY = 'uploadCoordinator'
/** pending 上传条目:保留 File 供重试blobUrl 供本地预览state 跟踪当前态。 */ /** pending 上传条目:保留 File 供重试blobUrl 供本地预览state 跟踪当前态。 */
interface UploadEntry { interface UploadEntry {
file: File file: File

View File

@ -1,6 +1,6 @@
import { Image } from '@tiptap/extension-image' import { Image } from '@tiptap/extension-image'
import type { Node as PMNode } from '@tiptap/pm/model' import type { Node as PMNode } from '@tiptap/pm/model'
import type { UploadCoordinator } from './upload-coordinator' import { UPLOAD_COORDINATOR_STORAGE_KEY, type UploadCoordinator } from './upload-coordinator'
/** NodeView 按钮点击 / destroy 回调注入接口。 */ /** NodeView 按钮点击 / destroy 回调注入接口。 */
export interface UploadNodeViewCallbacks { export interface UploadNodeViewCallbacks {
@ -146,14 +146,6 @@ class UploadImageNodeView {
} }
} }
/** coordinator 引用(由 index.ts 在创建 editor 前注入)。 */
let coordinatorRef: Pick<UploadCoordinator, 'retryUpload' | 'removeUpload'> | null = null
/** index.ts 注入 coordinator供 NodeView 的 onRetry/onRemove 调用。 */
export function setUploadCoordinator(c: typeof coordinatorRef): void {
coordinatorRef = c
}
/** /**
* Image NodeView * Image NodeView
*/ */
@ -189,13 +181,15 @@ export const UploadImage = Image.configure({ allowBase64: true }).extend({
}, },
addNodeView() { addNodeView() {
return ({ node, HTMLAttributes }) => { return ({ node, HTMLAttributes, editor }) => {
const coordinator = (editor.storage as unknown as Record<string, unknown>)[UPLOAD_COORDINATOR_STORAGE_KEY] as UploadCoordinator | undefined
return new UploadImageNodeView({ return new UploadImageNodeView({
node, node,
HTMLAttributes, HTMLAttributes,
callbacks: { callbacks: {
onRetry: (id) => coordinatorRef?.retryUpload(id), onRetry: (id) => coordinator?.retryUpload(id),
onRemove: (id) => coordinatorRef?.removeUpload(id), onRemove: (id) => coordinator?.removeUpload(id),
onDestroyed: (id) => coordinator?.handleNodeDestroyed(id),
}, },
}) })
} }