Files
cwd/docs/widget/src/components/ReplyEditor.js
anghunk 3f1ac28e34 refactor(widget): 移除评论预览中的重复标题文本
移除 CommentForm 和 ReplyEditor 组件预览区域中硬编码的“预览效果:”标题,使预览区域更加简洁,避免与外部容器标题重复。
2026-01-27 17:44:22 +08:00

298 lines
8.6 KiB
JavaScript

/**
* 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)
}
})
]
});
}
}