/** * ReplyEditor 回复编辑器组件 */ import { Component } from './Component.js'; import { renderMarkdown } from '../utils/markdown.js'; export class ReplyEditor extends Component { /** * @param {HTMLElement|string} container - 容器元素或选择器 * @param {Object} props - 组件属性 * @param {string} props.replyToAuthor - 被回复的作者名 * @param {string} props.content - 回复内容 * @param {string|null} props.error - 错误信息 * @param {boolean} props.submitting - 是否正在提交 * @param {Function} props.onUpdate - 内容更新回调 * @param {Function} props.onSubmit - 提交回调 * @param {Function} props.onCancel - 取消回调 * @param {Function} props.onClearError - 清除错误回调 */ constructor(container, props = {}) { super(container, props); const { currentUser } = props; this.state = { content: props.content || '', // 如果没有昵称或邮箱,显示用户信息输入框。且一旦显示,在当前编辑器生命周期内保持显示,避免输入过程中消失 showUserInfo: !currentUser || !currentUser.name || !currentUser.email, showPreview: false, }; } render() { const { currentUser } = this.props; const { showUserInfo } = this.state; const root = this.createElement('div', { className: 'cwd-reply-editor', children: [ // 头部 this.createElement('div', { className: 'cwd-reply-header', children: [ this.createTextElement('span', `回复 @${this.props.replyToAuthor}`, 'cwd-reply-to'), this.createElement('button', { className: 'cwd-btn-close', attributes: { type: 'button', onClick: () => this.handleCancel() }, text: '✕' }) ] }), // 用户信息输入框(当缺少信息时显示) ...(showUserInfo ? [ this.createElement('div', { className: 'cwd-form-row', attributes: { style: 'margin-bottom: 12px;' }, children: [ this.createFormField('昵称 *', 'text', 'name', currentUser?.name), this.createFormField('邮箱 *', 'email', 'email', currentUser?.email), this.createFormField('网址', 'url', 'url', currentUser?.url) ] }) ] : []), // 文本框 this.createElement('textarea', { className: 'cwd-reply-textarea', attributes: { rows: 3, placeholder: '支持 Markdown 格式', disabled: this.props.submitting, onInput: (e) => this.handleInput(e), }, }), // 错误提示 ...(this.props.error ? [ this.createElement('div', { className: 'cwd-error-inline cwd-error-small', children: [ this.createTextElement('span', this.props.error), this.createElement('button', { className: 'cwd-error-close', attributes: { type: 'button', onClick: () => this.handleClearError() }, text: '✕' }) ] }) ] : []), // 操作按钮 this.createElement('div', { className: 'cwd-reply-actions', children: [ this.createElement('button', { className: `cwd-btn cwd-btn-secondary cwd-btn-small cwd-btn-preview ${this.state.showPreview ? 'cwd-btn-active' : ''}`, attributes: { type: 'button', disabled: this.props.submitting || !this.state.content.trim(), onClick: () => this.togglePreview(), }, text: this.state.showPreview ? '关闭' : '预览', }), this.createElement('button', { className: 'cwd-btn cwd-btn-primary cwd-btn-small', attributes: { type: 'button', disabled: this.props.submitting || !this.state.content.trim(), onClick: () => this.handleSubmit() }, text: this.props.submitting ? '提交中...' : '提交回复' }), this.createElement('button', { className: 'cwd-btn cwd-btn-secondary cwd-btn-small', attributes: { type: 'button', disabled: this.props.submitting, onClick: () => this.handleCancel() }, text: '取消' }) ] }), // 预览区域 ...(this.state.showPreview && this.state.content ? [ this.createElement('div', { className: 'cwd-preview-container', children: [ this.createElement('div', { className: 'cwd-preview-content cwd-comment-content', // 直接设置 innerHTML html: renderMarkdown(this.state.content), }), ], }), ] : []), ] }); // 设置文本框内容 const textarea = root.querySelector('textarea'); if (textarea) { textarea.value = this.state.content; } this.elements.root = root; this.empty(this.container); this.container.appendChild(root); } updateProps(prevProps) { // 如果外部传入的 content 变化,更新内部状态 if (this.props.content !== this.state.content && this.props.content !== prevProps?.content) { this.state.content = this.props.content; this.render(); return; } // 如果用户信息变化,重新渲染 if (JSON.stringify(this.props.currentUser) !== JSON.stringify(prevProps?.currentUser)) { this.render(); return; } // 如果有错误显示/隐藏变化,重新渲染 if (this.props.error !== prevProps?.error) { this.render(); return; } // 如果 submitting 状态变化,重新渲染 if (this.props.submitting !== prevProps?.submitting) { this.render(); return; } } togglePreview() { this.state.showPreview = !this.state.showPreview; this.render(); } handleInput(e) { this.state.content = e.target.value; // 更新提交按钮的禁用状态 const submitBtn = this.elements.root?.querySelector('.cwd-btn-primary'); if (submitBtn) { submitBtn.disabled = this.props.submitting || !this.state.content.trim(); } // 更新预览按钮的禁用状态 const previewBtn = this.elements.root?.querySelector('.cwd-btn-preview'); if (previewBtn) { previewBtn.disabled = this.props.submitting || !this.state.content.trim(); } if (this.props.onUpdate) { this.props.onUpdate(this.state.content); } // 实时更新预览内容 if (this.state.showPreview) { this.updatePreviewContent(this.state.content); } } updatePreviewContent(content) { const previewContent = this.elements.root?.querySelector('.cwd-preview-content'); if (previewContent) { previewContent.innerHTML = renderMarkdown(content); } } handleSubmit() { if (this.props.onSubmit) { this.props.onSubmit(); } } handleCancel() { if (this.props.onCancel) { this.props.onCancel(); } } handleClearError() { if (this.props.onClearError) { this.props.onClearError(); } } /** * 设置内容 * @param {string} content - 新内容 */ setContent(content) { this.state.content = content; const textarea = this.elements.root?.querySelector('textarea'); if (textarea) { textarea.value = content; } } /** * 获取内容 * @returns {string} */ getContent() { return this.state.content; } /** * 聚焦文本框 */ focus() { const textarea = this.elements.root?.querySelector('textarea'); if (textarea) { textarea.focus(); } } handleUserInfoChange(field, value) { if (this.props.onUpdateUserInfo) { this.props.onUpdateUserInfo(field, value); } } createFormField(placeholder, type, field, value) { return this.createElement('div', { className: 'cwd-form-field', children: [ this.createElement('input', { className: 'cwd-form-input', attributes: { type, placeholder, value: value || '', disabled: this.props.submitting, onInput: (e) => this.handleUserInfoChange(field, e.target.value) } }) ] }); } }