feat(editor): route slash-command image upload through coordinator

- slash-command.ts: SlashCommandOptions 加 onInsertUploading option
- slash-command.ts: 上传图片命令优先走 onInsertUploading(coordinator 占位符+上传),
  退回 uploadFn 直接上传(无占位符)
- index.ts: SlashCommand.configure 注入 onInsertUploading,
  闭包延迟读取 this.coordinator(在 editor 创建后才实例化)
This commit is contained in:
xfy 2026-06-22 15:30:39 +08:00
parent 5d527778a5
commit 34c8b47436
2 changed files with 20 additions and 8 deletions

View File

@ -87,8 +87,12 @@ class TiptapEditorInstance {
TaskList, TaskList,
TaskItem.configure({ nested: true }), TaskItem.configure({ nested: true }),
// 把宿主注入的图片上传回调透传给斜杠命令扩展,使 /上传图片 命令可用。 // 把宿主注入的图片上传回调透传给斜杠命令扩展,使 /上传图片 命令可用。
// 注意:闭包延迟读取 this.coordinator它在 editor 创建后才实例化)。
SlashCommand.configure({ SlashCommand.configure({
onImageUpload: this.options.onImageUpload, onImageUpload: this.options.onImageUpload,
onInsertUploading: this.options.onImageUpload
? (file) => this.coordinator?.insertUploading(file)
: undefined,
}), }),
FileHandler.configure({ FileHandler.configure({
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'], allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],

View File

@ -18,6 +18,8 @@ interface CommandItem {
*/ */
export interface SlashCommandOptions { export interface SlashCommandOptions {
onImageUpload?: (file: File) => Promise<string> onImageUpload?: (file: File) => Promise<string>
/** 由 index.ts 注入:直接调 coordinator.insertUploading走占位符 + 上传)。 */
onInsertUploading?: (file: File) => void
} }
const SlashCommandPluginKey = new PluginKey('slashCommand') const SlashCommandPluginKey = new PluginKey('slashCommand')
@ -33,6 +35,7 @@ export const SlashCommand = Extension.create<SlashCommandOptions>({
addOptions() { addOptions() {
return { return {
onImageUpload: undefined, onImageUpload: undefined,
onInsertUploading: undefined,
} }
}, },
@ -137,14 +140,19 @@ export const SlashCommand = Extension.create<SlashCommandOptions>({
input.addEventListener('change', () => { input.addEventListener('change', () => {
const file = input.files?.[0] const file = input.files?.[0]
if (!file) return if (!file) return
uploadFn(file) // 优先走 coordinator占位符 + 上传),否则退回直接上传(无占位符)
.then((url) => { if (this.options.onInsertUploading) {
editor.chain().focus().setImage({ src: url }).run() this.options.onInsertUploading(file)
}) } else if (uploadFn) {
.catch((err) => { uploadFn(file)
const msg = err instanceof Error ? err.message : String(err) .then((url) => {
console.error('[SlashCommand] Upload failed:', msg) editor.chain().focus().setImage({ src: url }).run()
}) })
.catch((err) => {
const msg = err instanceof Error ? err.message : String(err)
console.error('[SlashCommand] Upload failed:', msg)
})
}
}) })
// click() 会立即触发原生文件选择器;回调在用户选择文件后异步执行。 // click() 会立即触发原生文件选择器;回调在用户选择文件后异步执行。
input.click() input.click()