cwd-widget 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ /**
2
+ * ReplyEditor 回复编辑器组件
3
+ */
4
+
5
+ import { Component } from './Component.js';
6
+ import { renderMarkdown } from '../utils/markdown.js';
7
+
8
+ export class ReplyEditor extends Component {
9
+ /**
10
+ * @param {HTMLElement|string} container - 容器元素或选择器
11
+ * @param {Object} props - 组件属性
12
+ * @param {string} props.replyToAuthor - 被回复的作者名
13
+ * @param {string} props.content - 回复内容
14
+ * @param {string|null} props.error - 错误信息
15
+ * @param {boolean} props.submitting - 是否正在提交
16
+ * @param {Function} props.onUpdate - 内容更新回调
17
+ * @param {Function} props.onSubmit - 提交回调
18
+ * @param {Function} props.onCancel - 取消回调
19
+ * @param {Function} props.onClearError - 清除错误回调
20
+ */
21
+ constructor(container, props = {}) {
22
+ super(container, props);
23
+ const { currentUser } = props;
24
+ this.state = {
25
+ content: props.content || '',
26
+ // 如果没有昵称或邮箱,显示用户信息输入框。且一旦显示,在当前编辑器生命周期内保持显示,避免输入过程中消失
27
+ showUserInfo: !currentUser || !currentUser.name || !currentUser.email,
28
+ showPreview: false,
29
+ };
30
+ }
31
+
32
+ render() {
33
+ const { currentUser } = this.props;
34
+ const { showUserInfo } = this.state;
35
+
36
+ const root = this.createElement('div', {
37
+ className: 'cwd-reply-editor',
38
+ children: [
39
+ // 头部
40
+ this.createElement('div', {
41
+ className: 'cwd-reply-header',
42
+ children: [
43
+ this.createTextElement('span', `回复 @${this.props.replyToAuthor}`, 'cwd-reply-to'),
44
+ this.createElement('button', {
45
+ className: 'cwd-btn-close',
46
+ attributes: {
47
+ type: 'button',
48
+ onClick: () => this.handleCancel()
49
+ },
50
+ text: '✕'
51
+ })
52
+ ]
53
+ }),
54
+
55
+ // 用户信息输入框(当缺少信息时显示)
56
+ ...(showUserInfo ? [
57
+ this.createElement('div', {
58
+ className: 'cwd-form-row',
59
+ attributes: {
60
+ style: 'margin-bottom: 12px;'
61
+ },
62
+ children: [
63
+ this.createFormField('昵称 *', 'text', 'name', currentUser?.name),
64
+ this.createFormField('邮箱 *', 'email', 'email', currentUser?.email),
65
+ this.createFormField('网址', 'url', 'url', currentUser?.url)
66
+ ]
67
+ })
68
+ ] : []),
69
+
70
+ // 文本框
71
+ this.createElement('textarea', {
72
+ className: 'cwd-reply-textarea',
73
+ attributes: {
74
+ rows: 3,
75
+ placeholder: '支持 Markdown 格式',
76
+ disabled: this.props.submitting,
77
+ onInput: (e) => this.handleInput(e),
78
+ },
79
+ }),
80
+
81
+ // 错误提示
82
+ ...(this.props.error ? [
83
+ this.createElement('div', {
84
+ className: 'cwd-error-inline cwd-error-small',
85
+ children: [
86
+ this.createTextElement('span', this.props.error),
87
+ this.createElement('button', {
88
+ className: 'cwd-error-close',
89
+ attributes: {
90
+ type: 'button',
91
+ onClick: () => this.handleClearError()
92
+ },
93
+ text: '✕'
94
+ })
95
+ ]
96
+ })
97
+ ] : []),
98
+
99
+ // 操作按钮
100
+ this.createElement('div', {
101
+ className: 'cwd-reply-actions',
102
+ children: [
103
+ this.createElement('button', {
104
+ className: `cwd-btn cwd-btn-secondary cwd-btn-small cwd-btn-preview ${this.state.showPreview ? 'cwd-btn-active' : ''}`,
105
+ attributes: {
106
+ type: 'button',
107
+ disabled: this.props.submitting || !this.state.content.trim(),
108
+ onClick: () => this.togglePreview(),
109
+ },
110
+ text: this.state.showPreview ? '关闭' : '预览',
111
+ }),
112
+ this.createElement('button', {
113
+ className: 'cwd-btn cwd-btn-primary cwd-btn-small',
114
+ attributes: {
115
+ type: 'button',
116
+ disabled: this.props.submitting || !this.state.content.trim(),
117
+ onClick: () => this.handleSubmit()
118
+ },
119
+ text: this.props.submitting ? '提交中...' : '提交回复'
120
+ }),
121
+ this.createElement('button', {
122
+ className: 'cwd-btn cwd-btn-secondary cwd-btn-small',
123
+ attributes: {
124
+ type: 'button',
125
+ disabled: this.props.submitting,
126
+ onClick: () => this.handleCancel()
127
+ },
128
+ text: '取消'
129
+ })
130
+ ]
131
+ }),
132
+
133
+ // 预览区域
134
+ ...(this.state.showPreview && this.state.content
135
+ ? [
136
+ this.createElement('div', {
137
+ className: 'cwd-preview-container',
138
+ children: [
139
+ this.createElement('div', {
140
+ className: 'cwd-preview-content cwd-comment-content',
141
+ // 直接设置 innerHTML
142
+ html: renderMarkdown(this.state.content),
143
+ }),
144
+ ],
145
+ }),
146
+ ]
147
+ : []),
148
+ ]
149
+ });
150
+
151
+ // 设置文本框内容
152
+ const textarea = root.querySelector('textarea');
153
+ if (textarea) {
154
+ textarea.value = this.state.content;
155
+ }
156
+
157
+ this.elements.root = root;
158
+ this.empty(this.container);
159
+ this.container.appendChild(root);
160
+ }
161
+
162
+ updateProps(prevProps) {
163
+ // 如果外部传入的 content 变化,更新内部状态
164
+ if (this.props.content !== this.state.content && this.props.content !== prevProps?.content) {
165
+ this.state.content = this.props.content;
166
+ this.render();
167
+ return;
168
+ }
169
+
170
+ // 如果用户信息变化,重新渲染
171
+ if (JSON.stringify(this.props.currentUser) !== JSON.stringify(prevProps?.currentUser)) {
172
+ this.render();
173
+ return;
174
+ }
175
+
176
+ // 如果有错误显示/隐藏变化,重新渲染
177
+ if (this.props.error !== prevProps?.error) {
178
+ this.render();
179
+ return;
180
+ }
181
+
182
+ // 如果 submitting 状态变化,重新渲染
183
+ if (this.props.submitting !== prevProps?.submitting) {
184
+ this.render();
185
+ return;
186
+ }
187
+ }
188
+
189
+ togglePreview() {
190
+ this.state.showPreview = !this.state.showPreview;
191
+ this.render();
192
+ }
193
+
194
+ handleInput(e) {
195
+ this.state.content = e.target.value;
196
+
197
+ // 更新提交按钮的禁用状态
198
+ const submitBtn = this.elements.root?.querySelector('.cwd-btn-primary');
199
+ if (submitBtn) {
200
+ submitBtn.disabled = this.props.submitting || !this.state.content.trim();
201
+ }
202
+
203
+ // 更新预览按钮的禁用状态
204
+ const previewBtn = this.elements.root?.querySelector('.cwd-btn-preview');
205
+ if (previewBtn) {
206
+ previewBtn.disabled = this.props.submitting || !this.state.content.trim();
207
+ }
208
+
209
+ if (this.props.onUpdate) {
210
+ this.props.onUpdate(this.state.content);
211
+ }
212
+
213
+ // 实时更新预览内容
214
+ if (this.state.showPreview) {
215
+ this.updatePreviewContent(this.state.content);
216
+ }
217
+ }
218
+
219
+ updatePreviewContent(content) {
220
+ const previewContent = this.elements.root?.querySelector('.cwd-preview-content');
221
+ if (previewContent) {
222
+ previewContent.innerHTML = renderMarkdown(content);
223
+ }
224
+ }
225
+
226
+ handleSubmit() {
227
+ if (this.props.onSubmit) {
228
+ this.props.onSubmit();
229
+ }
230
+ }
231
+
232
+ handleCancel() {
233
+ if (this.props.onCancel) {
234
+ this.props.onCancel();
235
+ }
236
+ }
237
+
238
+ handleClearError() {
239
+ if (this.props.onClearError) {
240
+ this.props.onClearError();
241
+ }
242
+ }
243
+
244
+ /**
245
+ * 设置内容
246
+ * @param {string} content - 新内容
247
+ */
248
+ setContent(content) {
249
+ this.state.content = content;
250
+ const textarea = this.elements.root?.querySelector('textarea');
251
+ if (textarea) {
252
+ textarea.value = content;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * 获取内容
258
+ * @returns {string}
259
+ */
260
+ getContent() {
261
+ return this.state.content;
262
+ }
263
+
264
+ /**
265
+ * 聚焦文本框
266
+ */
267
+ focus() {
268
+ const textarea = this.elements.root?.querySelector('textarea');
269
+ if (textarea) {
270
+ textarea.focus();
271
+ }
272
+ }
273
+
274
+ handleUserInfoChange(field, value) {
275
+ if (this.props.onUpdateUserInfo) {
276
+ this.props.onUpdateUserInfo(field, value);
277
+ }
278
+ }
279
+
280
+ createFormField(placeholder, type, field, value) {
281
+ return this.createElement('div', {
282
+ className: 'cwd-form-field',
283
+ children: [
284
+ this.createElement('input', {
285
+ className: 'cwd-form-input',
286
+ attributes: {
287
+ type,
288
+ placeholder,
289
+ value: value || '',
290
+ disabled: this.props.submitting,
291
+ onInput: (e) => this.handleUserInfoChange(field, e.target.value)
292
+ }
293
+ })
294
+ ]
295
+ });
296
+ }
297
+ }