@ziteh/yangchun-comment-client 0.1.0 → 0.2.0-alpha.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.
- package/README.md +6 -0
- package/dist/src/api/apiService.d.ts +22 -0
- package/dist/src/api/globalApiService.d.ts +10 -0
- package/dist/src/components/comment-admin.d.ts +25 -0
- package/dist/src/components/comment-dialog.d.ts +18 -0
- package/dist/src/components/comment-info.d.ts +14 -0
- package/dist/src/components/comment-input.d.ts +31 -0
- package/dist/src/components/list/comment-list-item.d.ts +19 -0
- package/dist/src/components/list/comment-list.d.ts +9 -0
- package/dist/src/components/yangchun-comment.d.ts +58 -0
- package/dist/src/components/yangchun-comment.styles.d.ts +2 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/utils/comment.d.ts +6 -0
- package/dist/src/utils/format.d.ts +2 -0
- package/dist/src/utils/i18n.d.ts +46 -0
- package/dist/src/utils/pow.d.ts +9 -0
- package/dist/{utils → src/utils}/pseudonym.d.ts +0 -1
- package/dist/src/utils/sanitize.d.ts +1 -0
- package/dist/test/sanitize.test.d.ts +1 -0
- package/dist/yangchun-comment.js +962 -0
- package/dist/yangchun-comment.js.map +1 -0
- package/dist/yangchun-comment.umd.cjs +962 -0
- package/dist/yangchun-comment.umd.cjs.map +1 -0
- package/package.json +17 -13
- package/dist/element.d.ts +0 -97
- package/dist/element.js +0 -680
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -23
- package/dist/types.d.ts +0 -3
- package/dist/utils/apiService.d.ts +0 -15
- package/dist/utils/apiService.js +0 -118
- package/dist/utils/format.d.ts +0 -1
- package/dist/utils/format.js +0 -14
- package/dist/utils/i18n.d.ts +0 -51
- package/dist/utils/i18n.js +0 -98
- package/dist/utils/pseudonym.js +0 -34
- package/dist/utils/sanitize.d.ts +0 -4
- package/dist/utils/sanitize.js +0 -59
- package/dist/utils/wordBank.js +0 -692
- package/dist/views/comments.d.ts +0 -3
- package/dist/views/comments.js +0 -119
- package/dist/views/preview.d.ts +0 -3
- package/dist/views/preview.js +0 -53
- package/dist/yangchun-comment.css +0 -1
- package/dist/yangchun-comment.es.js +0 -317
- package/dist/yangchun-comment.es.js.map +0 -1
- package/dist/yangchun-comment.umd.js +0 -317
- package/dist/yangchun-comment.umd.js.map +0 -1
- /package/dist/{types.js → src/utils/pow.worker.d.ts} +0 -0
- /package/dist/{utils → src/utils}/wordBank.d.ts +0 -0
package/dist/element.js
DELETED
|
@@ -1,680 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
import snarkdown from 'snarkdown';
|
|
8
|
-
import { LitElement, html, nothing } from 'lit';
|
|
9
|
-
import { property, state } from 'lit/decorators.js';
|
|
10
|
-
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
11
|
-
import { createApiService } from './utils/apiService';
|
|
12
|
-
import { createI18n, en, zhHant } from './utils/i18n';
|
|
13
|
-
import { generatePseudonymAndHash } from './utils/pseudonym';
|
|
14
|
-
import { sanitizeHtml, setupDOMPurifyHooks } from './utils/sanitize';
|
|
15
|
-
import { formatDate } from './utils/format';
|
|
16
|
-
import { createPreviewTemplate } from './views/preview';
|
|
17
|
-
import { createCommentsTemplate as renderCommentsView } from './views/comments';
|
|
18
|
-
import './index.css';
|
|
19
|
-
export class YangchunCommentElement extends LitElement {
|
|
20
|
-
constructor() {
|
|
21
|
-
super(...arguments);
|
|
22
|
-
this.post = '/blog/my-post';
|
|
23
|
-
this.apiUrl = 'http://localhost:8787/';
|
|
24
|
-
this.authorName = undefined;
|
|
25
|
-
this.language = 'en';
|
|
26
|
-
this.apiService = createApiService(this.apiUrl);
|
|
27
|
-
this.i18n = createI18n(en);
|
|
28
|
-
this.commentMap = {};
|
|
29
|
-
this.comments = [];
|
|
30
|
-
this.currentReplyTo = null;
|
|
31
|
-
this.previewText = '';
|
|
32
|
-
this.previewName = '';
|
|
33
|
-
this.previewPseudonym = '';
|
|
34
|
-
this.editingComment = null;
|
|
35
|
-
this.activeTab = 'write';
|
|
36
|
-
this.showMarkdownHelp = false;
|
|
37
|
-
this.showAdminLogin = false;
|
|
38
|
-
}
|
|
39
|
-
static { this.MAX_NAME_LENGTH = 25; }
|
|
40
|
-
static { this.MAX_MESSAGE_LENGTH = 1000; }
|
|
41
|
-
static { this.MY_NAME_HASHES_KEY = 'ycc_my_name_hashes'; }
|
|
42
|
-
createRenderRoot() {
|
|
43
|
-
return this;
|
|
44
|
-
}
|
|
45
|
-
connectedCallback() {
|
|
46
|
-
super.connectedCallback();
|
|
47
|
-
this.syncI18n();
|
|
48
|
-
this.apiService = createApiService(this.apiUrl);
|
|
49
|
-
setupDOMPurifyHooks();
|
|
50
|
-
this.reloadComments();
|
|
51
|
-
}
|
|
52
|
-
disconnectedCallback() {
|
|
53
|
-
document.body.classList.remove('ycc-modal-open');
|
|
54
|
-
super.disconnectedCallback();
|
|
55
|
-
}
|
|
56
|
-
updated(changed) {
|
|
57
|
-
if (changed.has('apiUrl'))
|
|
58
|
-
this.apiService = createApiService(this.apiUrl);
|
|
59
|
-
if (changed.has('language'))
|
|
60
|
-
this.syncI18n();
|
|
61
|
-
if (changed.has('post'))
|
|
62
|
-
this.reloadComments();
|
|
63
|
-
}
|
|
64
|
-
syncI18n() {
|
|
65
|
-
let languageStrings = en;
|
|
66
|
-
const lang = this.language;
|
|
67
|
-
if (typeof lang === 'string')
|
|
68
|
-
languageStrings = lang === 'zh-Hant' ? zhHant : en;
|
|
69
|
-
else if (lang && typeof lang === 'object')
|
|
70
|
-
languageStrings = lang;
|
|
71
|
-
this.i18n = createI18n(languageStrings);
|
|
72
|
-
}
|
|
73
|
-
renderMarkdown(md) {
|
|
74
|
-
return unsafeHTML(sanitizeHtml(snarkdown(md || '')));
|
|
75
|
-
}
|
|
76
|
-
getDisplayName(comment) {
|
|
77
|
-
if (comment?.isAdmin && this.authorName)
|
|
78
|
-
return this.authorName;
|
|
79
|
-
const cleaned = comment?.pseudonym ?? '';
|
|
80
|
-
return cleaned.trim() ? cleaned : this.i18n.t('anonymous');
|
|
81
|
-
}
|
|
82
|
-
canEditComment(commentId) {
|
|
83
|
-
return this.apiService.canEditComment(commentId);
|
|
84
|
-
}
|
|
85
|
-
saveMyNameHash(nameHash) {
|
|
86
|
-
try {
|
|
87
|
-
const arr = this.getMyNameHashes();
|
|
88
|
-
if (nameHash && !arr.includes(nameHash)) {
|
|
89
|
-
arr.push(nameHash);
|
|
90
|
-
localStorage.setItem(YangchunCommentElement.MY_NAME_HASHES_KEY, JSON.stringify(arr));
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
console.warn('Failed to save name hash:', e);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
getMyNameHashes() {
|
|
98
|
-
try {
|
|
99
|
-
const stored = localStorage.getItem(YangchunCommentElement.MY_NAME_HASHES_KEY);
|
|
100
|
-
return stored ? JSON.parse(stored) : [];
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
console.warn('Failed to get name hashes:', e);
|
|
104
|
-
return [];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
isMyComment(comment) {
|
|
108
|
-
if (!comment.nameHash)
|
|
109
|
-
return false;
|
|
110
|
-
return this.getMyNameHashes().includes(comment.nameHash);
|
|
111
|
-
}
|
|
112
|
-
async loadComments() {
|
|
113
|
-
return await this.apiService.getComments(this.post);
|
|
114
|
-
}
|
|
115
|
-
async reloadComments() {
|
|
116
|
-
this.comments = await this.loadComments();
|
|
117
|
-
this.buildCommentMap();
|
|
118
|
-
}
|
|
119
|
-
// moved preview template to views/preview.ts
|
|
120
|
-
switchTab(tab) {
|
|
121
|
-
this.activeTab = tab;
|
|
122
|
-
if (tab === 'preview')
|
|
123
|
-
this.saveCurrentFormInputs();
|
|
124
|
-
}
|
|
125
|
-
saveCurrentFormInputs() {
|
|
126
|
-
const nameInput = document.querySelector('#comment-form input[name="name"]');
|
|
127
|
-
if (nameInput)
|
|
128
|
-
this.previewName = nameInput.value;
|
|
129
|
-
}
|
|
130
|
-
async renderCommentsList() {
|
|
131
|
-
if (this.comments.length === 0)
|
|
132
|
-
await this.reloadComments();
|
|
133
|
-
}
|
|
134
|
-
buildCommentMap() {
|
|
135
|
-
this.commentMap = {};
|
|
136
|
-
this.comments.forEach((c) => {
|
|
137
|
-
this.commentMap[c.id] = c;
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
createCommentsTemplate() {
|
|
141
|
-
return renderCommentsView(this);
|
|
142
|
-
}
|
|
143
|
-
setReplyTo(commentId) {
|
|
144
|
-
if (this.editingComment) {
|
|
145
|
-
this.editingComment = null;
|
|
146
|
-
this.previewText = '';
|
|
147
|
-
this.previewName = '';
|
|
148
|
-
this.previewPseudonym = '';
|
|
149
|
-
}
|
|
150
|
-
this.currentReplyTo = commentId;
|
|
151
|
-
const form = this.querySelector('#comment-form-container');
|
|
152
|
-
if (form)
|
|
153
|
-
form.scrollIntoView({ behavior: 'smooth' });
|
|
154
|
-
}
|
|
155
|
-
cancelReply() {
|
|
156
|
-
this.currentReplyTo = null;
|
|
157
|
-
}
|
|
158
|
-
handleInputChange(e) {
|
|
159
|
-
this.previewText = e.target.value;
|
|
160
|
-
}
|
|
161
|
-
async handleNameInputChange(e) {
|
|
162
|
-
const target = e.target;
|
|
163
|
-
this.previewName = target.value;
|
|
164
|
-
if (target.value.trim()) {
|
|
165
|
-
try {
|
|
166
|
-
const { pseudonym } = await generatePseudonymAndHash(target.value);
|
|
167
|
-
this.previewPseudonym = pseudonym;
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
this.previewPseudonym = '';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
this.previewPseudonym = '';
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
async handleSubmit(e) {
|
|
178
|
-
e.preventDefault();
|
|
179
|
-
const fd = new FormData(e.target);
|
|
180
|
-
const originalName = fd.get('name');
|
|
181
|
-
const message = fd.get('message');
|
|
182
|
-
if (originalName && originalName.length > YangchunCommentElement.MAX_NAME_LENGTH) {
|
|
183
|
-
alert(`${this.i18n.t('nameTooLong')} (${originalName.length}/${YangchunCommentElement.MAX_NAME_LENGTH})`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (message.length > YangchunCommentElement.MAX_MESSAGE_LENGTH) {
|
|
187
|
-
alert(`${this.i18n.t('messageTooLong')} (${message.length}/${YangchunCommentElement.MAX_MESSAGE_LENGTH})`);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
let pseudonym;
|
|
191
|
-
let hash;
|
|
192
|
-
if (this.editingComment) {
|
|
193
|
-
pseudonym = this.editingComment.pseudonym || '';
|
|
194
|
-
hash = this.editingComment.nameHash || '';
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
const r = await generatePseudonymAndHash(originalName || '');
|
|
198
|
-
pseudonym = r.pseudonym;
|
|
199
|
-
hash = r.hash;
|
|
200
|
-
}
|
|
201
|
-
const success = await this.processSubmission(pseudonym, hash, message);
|
|
202
|
-
if (success) {
|
|
203
|
-
this.resetFormState();
|
|
204
|
-
this.comments.length = 0;
|
|
205
|
-
await this.renderCommentsList();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
async processSubmission(pseudonym, nameHash, message) {
|
|
209
|
-
if (this.editingComment) {
|
|
210
|
-
const success = await this.apiService.updateComment(this.post, this.editingComment.id, this.editingComment.pseudonym || '', this.editingComment.nameHash || '', message);
|
|
211
|
-
if (!success)
|
|
212
|
-
alert(this.i18n.t('editFailed'));
|
|
213
|
-
return success;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
const commentId = await this.apiService.addComment(this.post, pseudonym, nameHash, message, this.currentReplyTo);
|
|
217
|
-
if (!commentId) {
|
|
218
|
-
alert(this.i18n.t('submitFailed'));
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
this.saveMyNameHash(nameHash);
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
resetFormState() {
|
|
226
|
-
const form = document.querySelector('#comment-form');
|
|
227
|
-
if (form)
|
|
228
|
-
form.reset();
|
|
229
|
-
this.previewText = '';
|
|
230
|
-
this.previewName = '';
|
|
231
|
-
this.previewPseudonym = '';
|
|
232
|
-
this.editingComment = null;
|
|
233
|
-
this.currentReplyTo = null;
|
|
234
|
-
}
|
|
235
|
-
async handlePreviewSubmit() {
|
|
236
|
-
if (this.previewName && this.previewName.length > YangchunCommentElement.MAX_NAME_LENGTH) {
|
|
237
|
-
alert(`${this.i18n.t('nameTooLong')} (${this.previewName.length}/${YangchunCommentElement.MAX_NAME_LENGTH})`);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (this.previewText.length > YangchunCommentElement.MAX_MESSAGE_LENGTH) {
|
|
241
|
-
alert(`${this.i18n.t('messageTooLong')} (${this.previewText.length}/${YangchunCommentElement.MAX_MESSAGE_LENGTH})`);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
let pseudonym;
|
|
245
|
-
let hash;
|
|
246
|
-
if (this.editingComment) {
|
|
247
|
-
pseudonym = this.editingComment.pseudonym || '';
|
|
248
|
-
hash = this.editingComment.nameHash || '';
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
const g = await generatePseudonymAndHash(this.previewName);
|
|
252
|
-
pseudonym = g.pseudonym;
|
|
253
|
-
hash = g.hash;
|
|
254
|
-
}
|
|
255
|
-
const success = await this.processSubmission(pseudonym, hash, this.previewText);
|
|
256
|
-
if (success) {
|
|
257
|
-
this.resetPreviewState();
|
|
258
|
-
this.comments.length = 0;
|
|
259
|
-
await this.renderCommentsList();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
resetPreviewState() {
|
|
263
|
-
this.previewText = '';
|
|
264
|
-
this.previewName = '';
|
|
265
|
-
this.previewPseudonym = '';
|
|
266
|
-
this.editingComment = null;
|
|
267
|
-
this.currentReplyTo = null;
|
|
268
|
-
this.switchTab('write');
|
|
269
|
-
const form = document.querySelector('#comment-form');
|
|
270
|
-
if (form)
|
|
271
|
-
form.reset();
|
|
272
|
-
}
|
|
273
|
-
async handleDelete(commentId) {
|
|
274
|
-
if (!confirm(this.i18n.t('confirmDelete')))
|
|
275
|
-
return;
|
|
276
|
-
const success = await this.apiService.deleteComment(this.post, commentId);
|
|
277
|
-
if (success) {
|
|
278
|
-
this.comments.length = 0;
|
|
279
|
-
await this.renderCommentsList();
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
alert(this.i18n.t('deleteFailed'));
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
handleEdit(comment) {
|
|
286
|
-
this.clearReplyState();
|
|
287
|
-
this.setEditingState(comment);
|
|
288
|
-
this.populateFormWithComment(comment);
|
|
289
|
-
this.scrollToForm();
|
|
290
|
-
}
|
|
291
|
-
clearReplyState() {
|
|
292
|
-
if (this.currentReplyTo)
|
|
293
|
-
this.currentReplyTo = null;
|
|
294
|
-
}
|
|
295
|
-
setEditingState(comment) {
|
|
296
|
-
this.editingComment = comment;
|
|
297
|
-
this.previewText = comment.msg || '';
|
|
298
|
-
this.previewName = comment.pseudonym || '';
|
|
299
|
-
this.previewPseudonym = comment.pseudonym || '';
|
|
300
|
-
}
|
|
301
|
-
populateFormWithComment(comment) {
|
|
302
|
-
const nameInput = document.querySelector('#comment-form input[name="name"]');
|
|
303
|
-
const messageInput = document.querySelector('#comment-form textarea[name="message"]');
|
|
304
|
-
if (nameInput) {
|
|
305
|
-
nameInput.value = comment.pseudonym || '';
|
|
306
|
-
this.previewName = comment.pseudonym || '';
|
|
307
|
-
this.previewPseudonym = comment.pseudonym || '';
|
|
308
|
-
}
|
|
309
|
-
if (messageInput) {
|
|
310
|
-
messageInput.value = comment.msg || '';
|
|
311
|
-
this.previewText = comment.msg || '';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
scrollToForm() {
|
|
315
|
-
const form = document.querySelector('#comment-form-container');
|
|
316
|
-
if (form)
|
|
317
|
-
form.scrollIntoView({ behavior: 'smooth' });
|
|
318
|
-
}
|
|
319
|
-
cancelEdit() {
|
|
320
|
-
this.editingComment = null;
|
|
321
|
-
this.clearFormAndPreview();
|
|
322
|
-
}
|
|
323
|
-
clearFormAndPreview() {
|
|
324
|
-
const form = document.querySelector('#comment-form');
|
|
325
|
-
if (form)
|
|
326
|
-
form.reset();
|
|
327
|
-
this.previewText = '';
|
|
328
|
-
this.previewName = '';
|
|
329
|
-
this.previewPseudonym = '';
|
|
330
|
-
}
|
|
331
|
-
toggleMarkdownHelp() {
|
|
332
|
-
this.showMarkdownHelp = !this.showMarkdownHelp;
|
|
333
|
-
this.updateBodyScrollLock();
|
|
334
|
-
}
|
|
335
|
-
createMarkdownHelpTemplate() {
|
|
336
|
-
return html ` <div id="markdown-help-modal" class="active">
|
|
337
|
-
<div class="markdown-help-container ycc-flex">
|
|
338
|
-
<div
|
|
339
|
-
class="markdown-help-backdrop ycc-clickable"
|
|
340
|
-
@click=${() => this.toggleMarkdownHelp()}
|
|
341
|
-
></div>
|
|
342
|
-
<div class="markdown-help-content" role="dialog" aria-modal="true">
|
|
343
|
-
<button
|
|
344
|
-
class="markdown-help-close ycc-clickable ycc-reset-button"
|
|
345
|
-
@click=${() => this.toggleMarkdownHelp()}
|
|
346
|
-
aria-label="Close"
|
|
347
|
-
>
|
|
348
|
-
×
|
|
349
|
-
</button>
|
|
350
|
-
<h4>${this.i18n.t('commentSystemTitle')}</h4>
|
|
351
|
-
<p>${this.i18n.t('commentSystemDesc')}</p>
|
|
352
|
-
<p>${this.i18n.t('commentTimeLimit')}</p>
|
|
353
|
-
<p>
|
|
354
|
-
Powered by <a
|
|
355
|
-
href="https://github.com/ziteh/yangchun-comment"
|
|
356
|
-
target="_blank"
|
|
357
|
-
rel="noopener noreferrer"
|
|
358
|
-
>Yang Chun Comment</a
|
|
359
|
-
>
|
|
360
|
-
</p>
|
|
361
|
-
<h4>${this.i18n.t('markdownSyntax')}</h4>
|
|
362
|
-
<p>${this.i18n.t('markdownBasicSupport')}</p>
|
|
363
|
-
<div class="markdown-examples">
|
|
364
|
-
<code>
|
|
365
|
-
<pre>
|
|
366
|
-
${this.i18n.t('markdownLinkExample')}
|
|
367
|
-
|
|
368
|
-
${this.i18n.t('markdownImageExample')}
|
|
369
|
-
|
|
370
|
-
${this.i18n.t('markdownItalicExample')}
|
|
371
|
-
|
|
372
|
-
${this.i18n.t('markdownBoldExample')}
|
|
373
|
-
|
|
374
|
-
${this.i18n.t('markdownListExample')}
|
|
375
|
-
|
|
376
|
-
${this.i18n.t('markdownOrderedListExample')}
|
|
377
|
-
|
|
378
|
-
${this.i18n.t('markdownInlineCodeExample')}
|
|
379
|
-
|
|
380
|
-
${this.i18n.t('markdownCodeBlockExample')}</pre
|
|
381
|
-
>
|
|
382
|
-
</code>
|
|
383
|
-
</div>
|
|
384
|
-
</div>
|
|
385
|
-
</div>
|
|
386
|
-
</div>`;
|
|
387
|
-
}
|
|
388
|
-
createFormTemplate() {
|
|
389
|
-
return html `
|
|
390
|
-
${this.activeTab === 'preview' ? createPreviewTemplate(this) : this.createFormContent()}
|
|
391
|
-
${this.createStatusIndicators()}
|
|
392
|
-
${this.showMarkdownHelp ? this.createMarkdownHelpTemplate() : nothing}
|
|
393
|
-
${this.showAdminLogin ? this.createAdminLoginTemplate() : nothing}
|
|
394
|
-
`;
|
|
395
|
-
}
|
|
396
|
-
createFormContent() {
|
|
397
|
-
return html ` <div class="comment-box">
|
|
398
|
-
<div id="form-content" class="${this.activeTab === 'write' ? 'active' : ''}">
|
|
399
|
-
<form
|
|
400
|
-
id="comment-form"
|
|
401
|
-
class="ycc-reset-form"
|
|
402
|
-
@submit=${(e) => this.handleSubmit(e)}
|
|
403
|
-
>
|
|
404
|
-
<div class="honeypot-field">
|
|
405
|
-
<input type="text" name="website" tabindex="-1" autocomplete="off" aria-hidden="true" />
|
|
406
|
-
</div>
|
|
407
|
-
${this.createTextareaSection()} ${this.createFormFooter()}
|
|
408
|
-
</form>
|
|
409
|
-
</div>
|
|
410
|
-
</div>`;
|
|
411
|
-
}
|
|
412
|
-
createTextareaSection() {
|
|
413
|
-
return html ` <div class="comment-input">
|
|
414
|
-
<textarea
|
|
415
|
-
name="message"
|
|
416
|
-
placeholder="${this.i18n.t('messagePlaceholder')}"
|
|
417
|
-
maxlength="${YangchunCommentElement.MAX_MESSAGE_LENGTH}"
|
|
418
|
-
required
|
|
419
|
-
.value=${this.previewText}
|
|
420
|
-
@input=${(e) => this.handleInputChange(e)}
|
|
421
|
-
></textarea>
|
|
422
|
-
<div class="char-count">
|
|
423
|
-
<span
|
|
424
|
-
id="message-char-count"
|
|
425
|
-
class="${this.previewText.length > YangchunCommentElement.MAX_MESSAGE_LENGTH
|
|
426
|
-
? 'over-limit'
|
|
427
|
-
: ''}"
|
|
428
|
-
>${this.previewText.length}</span
|
|
429
|
-
>/${YangchunCommentElement.MAX_MESSAGE_LENGTH}
|
|
430
|
-
</div>
|
|
431
|
-
</div>`;
|
|
432
|
-
}
|
|
433
|
-
createFormFooter() {
|
|
434
|
-
return html ` <div class="comment-footer ycc-flex ycc-flex-wrap ycc-gap-xs">
|
|
435
|
-
<div class="name-input-container">
|
|
436
|
-
<input
|
|
437
|
-
type="text"
|
|
438
|
-
name="name"
|
|
439
|
-
autocomplete="name"
|
|
440
|
-
placeholder="${this.i18n.t('namePlaceholder')}"
|
|
441
|
-
maxlength="${YangchunCommentElement.MAX_NAME_LENGTH}"
|
|
442
|
-
?disabled=${this.editingComment !== null}
|
|
443
|
-
.value=${this.previewName}
|
|
444
|
-
@input=${(e) => this.handleNameInputChange(e)}
|
|
445
|
-
/>
|
|
446
|
-
<div class="char-count" style="margin-top: 4px;">
|
|
447
|
-
<span
|
|
448
|
-
id="name-char-count"
|
|
449
|
-
class="${this.previewName.length > YangchunCommentElement.MAX_NAME_LENGTH
|
|
450
|
-
? 'over-limit'
|
|
451
|
-
: ''}"
|
|
452
|
-
>${this.previewName.length}</span
|
|
453
|
-
>/${YangchunCommentElement.MAX_NAME_LENGTH}
|
|
454
|
-
</div>
|
|
455
|
-
<div class="pseudonym-notice" style="font-size: 0.8em; color: #666; margin-top: 4px;">
|
|
456
|
-
${this.editingComment
|
|
457
|
-
? this.i18n.t('editingPseudonymNotice')
|
|
458
|
-
: this.i18n.t('pseudonymNotice')}
|
|
459
|
-
</div>
|
|
460
|
-
</div>
|
|
461
|
-
<div class="ycc-flex ycc-gap-xs">${this.createFormButtons()}</div>
|
|
462
|
-
</div>`;
|
|
463
|
-
}
|
|
464
|
-
createFormButtons() {
|
|
465
|
-
return html ` <button
|
|
466
|
-
type="button"
|
|
467
|
-
class="help-btn ycc-clickable ycc-reset-button"
|
|
468
|
-
title="${this.i18n.t('markdownHelp')}"
|
|
469
|
-
@click=${() => this.toggleMarkdownHelp()}
|
|
470
|
-
>
|
|
471
|
-
?
|
|
472
|
-
</button>
|
|
473
|
-
<button
|
|
474
|
-
type="button"
|
|
475
|
-
class="preview-btn ycc-clickable ycc-transition ycc-transparent-bg ycc-reset-button ${this
|
|
476
|
-
.activeTab === 'preview'
|
|
477
|
-
? 'active'
|
|
478
|
-
: ''}"
|
|
479
|
-
@click=${() => this.switchTab(this.activeTab === 'preview' ? 'write' : 'preview')}
|
|
480
|
-
>
|
|
481
|
-
${this.activeTab === 'preview' ? this.i18n.t('write') : this.i18n.t('preview')}
|
|
482
|
-
</button>
|
|
483
|
-
<button type="submit" class="submit-btn ycc-clickable ycc-transition ycc-reset-button">
|
|
484
|
-
${this.editingComment ? this.i18n.t('updateComment') : this.i18n.t('submitComment')}
|
|
485
|
-
</button>`;
|
|
486
|
-
}
|
|
487
|
-
createAdminButton() {
|
|
488
|
-
return html `<button
|
|
489
|
-
type="button"
|
|
490
|
-
class="admin-btn ycc-clickable ycc-reset-button"
|
|
491
|
-
title="Admin"
|
|
492
|
-
@click=${() => this.showAdminModal()}
|
|
493
|
-
>
|
|
494
|
-
⚙
|
|
495
|
-
</button>`;
|
|
496
|
-
}
|
|
497
|
-
showAdminModal() {
|
|
498
|
-
this.showAdminLogin = true;
|
|
499
|
-
this.updateBodyScrollLock();
|
|
500
|
-
}
|
|
501
|
-
hideAdminModal() {
|
|
502
|
-
this.showAdminLogin = false;
|
|
503
|
-
this.updateBodyScrollLock();
|
|
504
|
-
}
|
|
505
|
-
updateBodyScrollLock() {
|
|
506
|
-
const lock = this.showMarkdownHelp || this.showAdminLogin;
|
|
507
|
-
document.body.classList.toggle('ycc-modal-open', lock);
|
|
508
|
-
}
|
|
509
|
-
createAdminLoginTemplate() {
|
|
510
|
-
return html `<div
|
|
511
|
-
class="admin-modal-backdrop ycc-clickable"
|
|
512
|
-
@click=${() => this.hideAdminModal()}
|
|
513
|
-
>
|
|
514
|
-
<div class="admin-modal-content" @click=${(e) => e.stopPropagation()}>
|
|
515
|
-
<button
|
|
516
|
-
class="admin-modal-close ycc-clickable ycc-reset-button"
|
|
517
|
-
@click=${() => this.hideAdminModal()}
|
|
518
|
-
>
|
|
519
|
-
×
|
|
520
|
-
</button>
|
|
521
|
-
<h3>Admin Login</h3>
|
|
522
|
-
<form @submit=${(e) => this.handleAdminLogin(e)}>
|
|
523
|
-
<div class="admin-form-group">
|
|
524
|
-
<label for="admin-username">Username:</label
|
|
525
|
-
><input type="text" id="admin-username" name="username" required autocomplete="off" />
|
|
526
|
-
</div>
|
|
527
|
-
<div class="admin-form-group">
|
|
528
|
-
<label for="admin-password">Password:</label
|
|
529
|
-
><input
|
|
530
|
-
type="password"
|
|
531
|
-
id="admin-password"
|
|
532
|
-
name="password"
|
|
533
|
-
required
|
|
534
|
-
autocomplete="off"
|
|
535
|
-
/>
|
|
536
|
-
</div>
|
|
537
|
-
<button type="submit" class="admin-login-btn ycc-clickable ycc-reset-button">
|
|
538
|
-
Login
|
|
539
|
-
</button>
|
|
540
|
-
</form>
|
|
541
|
-
</div>
|
|
542
|
-
</div>`;
|
|
543
|
-
}
|
|
544
|
-
async handleAdminLogin(e) {
|
|
545
|
-
e.preventDefault();
|
|
546
|
-
const fd = new FormData(e.target);
|
|
547
|
-
const username = fd.get('username');
|
|
548
|
-
const password = fd.get('password');
|
|
549
|
-
try {
|
|
550
|
-
const res = await fetch(`${this.apiUrl}admin/login`, {
|
|
551
|
-
method: 'POST',
|
|
552
|
-
headers: { 'Content-Type': 'application/json' },
|
|
553
|
-
body: JSON.stringify({ username, password }),
|
|
554
|
-
});
|
|
555
|
-
if (res.ok) {
|
|
556
|
-
await res.text();
|
|
557
|
-
alert('Admin login successful!');
|
|
558
|
-
this.hideAdminModal();
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
alert('Admin login failed!');
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
catch (err) {
|
|
565
|
-
console.error('Admin login error:', err);
|
|
566
|
-
alert('Admin login error!');
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
createStatusIndicators() {
|
|
570
|
-
const replyIndicator = this.createReplyIndicator();
|
|
571
|
-
const editIndicator = this.createEditIndicator();
|
|
572
|
-
const hasAny = replyIndicator !== '' || editIndicator !== '';
|
|
573
|
-
return hasAny ? html `${replyIndicator}${editIndicator}` : nothing;
|
|
574
|
-
}
|
|
575
|
-
createReplyIndicator() {
|
|
576
|
-
return this.currentReplyTo && this.commentMap[this.currentReplyTo]
|
|
577
|
-
? html `<div class="info ycc-flex ycc-gap-md">
|
|
578
|
-
${this.i18n.t('replyingTo')}
|
|
579
|
-
${this.getDisplayName(this.commentMap[this.currentReplyTo])}<button
|
|
580
|
-
type="button"
|
|
581
|
-
class="cancel-link ycc-clickable ycc-transition ycc-reset-button"
|
|
582
|
-
@click=${() => this.cancelReply()}
|
|
583
|
-
>
|
|
584
|
-
${this.i18n.t('cancelReply')}
|
|
585
|
-
</button>
|
|
586
|
-
</div>`
|
|
587
|
-
: '';
|
|
588
|
-
}
|
|
589
|
-
createEditIndicator() {
|
|
590
|
-
return this.editingComment
|
|
591
|
-
? html `<div class="info ycc-flex ycc-gap-md">
|
|
592
|
-
${this.i18n.t('editing')} ${this.editingComment.id}<button
|
|
593
|
-
type="button"
|
|
594
|
-
class="cancel-link ycc-clickable ycc-transition ycc-reset-button"
|
|
595
|
-
@click=${() => this.cancelEdit()}
|
|
596
|
-
>
|
|
597
|
-
${this.i18n.t('cancelEdit')}
|
|
598
|
-
</button>
|
|
599
|
-
</div>`
|
|
600
|
-
: '';
|
|
601
|
-
}
|
|
602
|
-
render() {
|
|
603
|
-
return html ` <div class="ycc-container">
|
|
604
|
-
<div class="comment-box-container">
|
|
605
|
-
<div id="comment-form-container" class="form-content">${this.createFormTemplate()}</div>
|
|
606
|
-
<!-- <div class="admin-btn-wrapper">${this.createAdminButton()}</div> -->
|
|
607
|
-
</div>
|
|
608
|
-
<div id="comments-container">${this.createCommentsTemplate()}</div>
|
|
609
|
-
</div>`;
|
|
610
|
-
}
|
|
611
|
-
async refresh() {
|
|
612
|
-
await this.reloadComments();
|
|
613
|
-
}
|
|
614
|
-
get i18n$() {
|
|
615
|
-
return this.i18n;
|
|
616
|
-
}
|
|
617
|
-
get commentMap$() {
|
|
618
|
-
return this.commentMap;
|
|
619
|
-
}
|
|
620
|
-
get currentReplyTo$() {
|
|
621
|
-
return this.currentReplyTo;
|
|
622
|
-
}
|
|
623
|
-
get previewText$() {
|
|
624
|
-
return this.previewText;
|
|
625
|
-
}
|
|
626
|
-
get previewPseudonym$() {
|
|
627
|
-
return this.previewPseudonym;
|
|
628
|
-
}
|
|
629
|
-
get editingComment$() {
|
|
630
|
-
return this.editingComment;
|
|
631
|
-
}
|
|
632
|
-
get comments$() {
|
|
633
|
-
return this.comments;
|
|
634
|
-
}
|
|
635
|
-
formatDate(ts) {
|
|
636
|
-
return formatDate(ts);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
__decorate([
|
|
640
|
-
property({ type: String })
|
|
641
|
-
], YangchunCommentElement.prototype, "post", void 0);
|
|
642
|
-
__decorate([
|
|
643
|
-
property({ type: String })
|
|
644
|
-
], YangchunCommentElement.prototype, "apiUrl", void 0);
|
|
645
|
-
__decorate([
|
|
646
|
-
property({ type: String })
|
|
647
|
-
], YangchunCommentElement.prototype, "authorName", void 0);
|
|
648
|
-
__decorate([
|
|
649
|
-
property({ attribute: false })
|
|
650
|
-
], YangchunCommentElement.prototype, "language", void 0);
|
|
651
|
-
__decorate([
|
|
652
|
-
state()
|
|
653
|
-
], YangchunCommentElement.prototype, "commentMap", void 0);
|
|
654
|
-
__decorate([
|
|
655
|
-
state()
|
|
656
|
-
], YangchunCommentElement.prototype, "comments", void 0);
|
|
657
|
-
__decorate([
|
|
658
|
-
state()
|
|
659
|
-
], YangchunCommentElement.prototype, "currentReplyTo", void 0);
|
|
660
|
-
__decorate([
|
|
661
|
-
state()
|
|
662
|
-
], YangchunCommentElement.prototype, "previewText", void 0);
|
|
663
|
-
__decorate([
|
|
664
|
-
state()
|
|
665
|
-
], YangchunCommentElement.prototype, "previewName", void 0);
|
|
666
|
-
__decorate([
|
|
667
|
-
state()
|
|
668
|
-
], YangchunCommentElement.prototype, "previewPseudonym", void 0);
|
|
669
|
-
__decorate([
|
|
670
|
-
state()
|
|
671
|
-
], YangchunCommentElement.prototype, "editingComment", void 0);
|
|
672
|
-
__decorate([
|
|
673
|
-
state()
|
|
674
|
-
], YangchunCommentElement.prototype, "activeTab", void 0);
|
|
675
|
-
__decorate([
|
|
676
|
-
state()
|
|
677
|
-
], YangchunCommentElement.prototype, "showMarkdownHelp", void 0);
|
|
678
|
-
__decorate([
|
|
679
|
-
state()
|
|
680
|
-
], YangchunCommentElement.prototype, "showAdminLogin", void 0);
|
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { I18nStrings } from './utils/i18n';
|
|
2
|
-
import { YangchunCommentElement } from './element';
|
|
3
|
-
export declare function initYangchunComment(elementId?: string, options?: {
|
|
4
|
-
post?: string;
|
|
5
|
-
apiUrl?: string;
|
|
6
|
-
language?: 'en' | 'zh-Hant' | I18nStrings;
|
|
7
|
-
authorName?: string;
|
|
8
|
-
}): YangchunCommentElement;
|
|
9
|
-
export default initYangchunComment;
|