xfy f6d60520eb 集成 Tiptap Markdown 编辑器到文章撰写页面
- 新增 libs/tiptap-editor/ 打包子项目(Tiptap Core + StarterKit + Markdown)
- 构建产物输出到 public/tiptap/
- 替换原有的 textarea + pulldown_cmark 预览为 WYSIWYG 编辑器
- Makefile 新增 build-editor target
- Dioxus.toml 引入 editor.js 和 editor.css

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:46:04 +08:00

117 lines
2.6 KiB
TypeScript

import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import { Markdown } from '@tiptap/markdown'
import './style.css'
export interface EditorOptions {
content?: string
placeholder?: string
onUpdate?: (markdown: string) => void
onFocus?: () => void
onBlur?: () => void
editable?: boolean
}
class TiptapEditorInstance {
private editor: Editor | null = null
private container: HTMLElement
private options: EditorOptions
constructor(container: HTMLElement, options: EditorOptions = {}) {
this.container = container
this.options = options
this.init()
}
private init() {
const el = document.createElement('div')
el.className = 'tiptap-editor'
this.container.appendChild(el)
this.editor = new Editor({
element: el,
extensions: [
StarterKit.configure({
heading: {
levels: [1, 2, 3],
},
}),
Markdown.configure({
html: false,
}),
],
content: this.options.content || '',
editable: this.options.editable !== false,
autofocus: false,
onUpdate: ({ editor }) => {
if (this.options.onUpdate) {
this.options.onUpdate(editor.getMarkdown())
}
},
onFocus: () => {
if (this.options.onFocus) {
this.options.onFocus()
}
},
onBlur: () => {
if (this.options.onBlur) {
this.options.onBlur()
}
},
})
}
getMarkdown(): string {
return this.editor?.getMarkdown() || ''
}
setMarkdown(content: string): void {
this.editor?.commands.setContent(content, false, { contentType: 'markdown' })
}
getHTML(): string {
return this.editor?.getHTML() || ''
}
focus(): void {
this.editor?.commands.focus()
}
blur(): void {
this.editor?.commands.blur()
}
isEmpty(): boolean {
return this.editor?.isEmpty ?? true
}
destroy(): void {
this.editor?.destroy()
this.editor = null
this.container.innerHTML = ''
}
}
const TiptapEditor = {
_instances: new Map<string, TiptapEditorInstance>(),
create(containerId: string, options: EditorOptions = {}): TiptapEditorInstance | null {
const container = document.getElementById(containerId)
if (!container) {
console.error(`[TiptapEditor] Container not found: #${containerId}`)
return null
}
const existing = this._instances.get(containerId)
if (existing) {
existing.destroy()
}
const instance = new TiptapEditorInstance(container, options)
this._instances.set(containerId, instance)
return instance
},
}
export default TiptapEditor