@ziteh/yangchun-comment-client 0.1.0
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/LICENSE +201 -0
- package/dist/element.d.ts +97 -0
- package/dist/element.js +680 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +23 -0
- package/dist/types.d.ts +3 -0
- package/dist/types.js +1 -0
- package/dist/utils/apiService.d.ts +15 -0
- package/dist/utils/apiService.js +118 -0
- package/dist/utils/format.d.ts +1 -0
- package/dist/utils/format.js +14 -0
- package/dist/utils/i18n.d.ts +51 -0
- package/dist/utils/i18n.js +98 -0
- package/dist/utils/pseudonym.d.ts +5 -0
- package/dist/utils/pseudonym.js +34 -0
- package/dist/utils/sanitize.d.ts +4 -0
- package/dist/utils/sanitize.js +59 -0
- package/dist/utils/wordBank.d.ts +2 -0
- package/dist/utils/wordBank.js +692 -0
- package/dist/views/comments.d.ts +3 -0
- package/dist/views/comments.js +119 -0
- package/dist/views/preview.d.ts +3 -0
- package/dist/views/preview.js +53 -0
- package/dist/yangchun-comment.css +1 -0
- package/dist/yangchun-comment.es.js +317 -0
- package/dist/yangchun-comment.es.js.map +1 -0
- package/dist/yangchun-comment.umd.js +317 -0
- package/dist/yangchun-comment.umd.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
function getCommentCssClasses(isRoot) {
|
|
3
|
+
const prefix = isRoot ? 'comment' : 'reply';
|
|
4
|
+
return {
|
|
5
|
+
item: prefix,
|
|
6
|
+
header: `${prefix}-header ycc-flex ycc-flex-wrap`,
|
|
7
|
+
name: `${prefix}-name`,
|
|
8
|
+
time: `${prefix}-time`,
|
|
9
|
+
content: `${prefix}-content`,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function createCommentHeader(ctx, comment, css, replyToName, canEdit) {
|
|
13
|
+
const isMy = ctx.isMyComment(comment);
|
|
14
|
+
const isAdmin = comment.isAdmin;
|
|
15
|
+
return html ` <div class="${css.header}">
|
|
16
|
+
<span class="${css.name}" title="${comment.id}">
|
|
17
|
+
${ctx.getDisplayName(comment)}
|
|
18
|
+
${isAdmin
|
|
19
|
+
? html `<span class="author-badge">${ctx.i18n$.t('author')}</span>`
|
|
20
|
+
: isMy
|
|
21
|
+
? html `<span class="my-comment-badge">Me</span>`
|
|
22
|
+
: ''}
|
|
23
|
+
</span>
|
|
24
|
+
<span
|
|
25
|
+
class="${css.time}"
|
|
26
|
+
title="${comment.modDate ? ctx.formatDate(comment.pubDate) : undefined}"
|
|
27
|
+
>
|
|
28
|
+
${comment.modDate
|
|
29
|
+
? ctx.i18n$.t('modified') + ' ' + ctx.formatDate(comment.modDate)
|
|
30
|
+
: ctx.formatDate(comment.pubDate)}
|
|
31
|
+
</span>
|
|
32
|
+
${replyToName
|
|
33
|
+
? html `<span class="reply-to"
|
|
34
|
+
>${ctx.i18n$.t('replyTo')}<span title="${comment.replyTo ?? ''}"
|
|
35
|
+
>${replyToName}</span
|
|
36
|
+
></span
|
|
37
|
+
>`
|
|
38
|
+
: ''}
|
|
39
|
+
${canEdit
|
|
40
|
+
? html `<span class="comment-controls ycc-flex ycc-gap-xs">
|
|
41
|
+
<button
|
|
42
|
+
class="edit-button ycc-clickable ycc-transition ycc-transparent-bg ycc-reset-button"
|
|
43
|
+
@click=${() => ctx.handleEdit(comment)}
|
|
44
|
+
>
|
|
45
|
+
${ctx.i18n$.t('edit')}
|
|
46
|
+
</button>
|
|
47
|
+
<button
|
|
48
|
+
class="delete-button ycc-clickable ycc-transition ycc-transparent-bg ycc-reset-button"
|
|
49
|
+
@click=${() => ctx.handleDelete(comment.id)}
|
|
50
|
+
>
|
|
51
|
+
${ctx.i18n$.t('delete')}
|
|
52
|
+
</button>
|
|
53
|
+
</span>`
|
|
54
|
+
: ''}
|
|
55
|
+
</div>`;
|
|
56
|
+
}
|
|
57
|
+
function createCommentContent(ctx, comment, contentClass) {
|
|
58
|
+
return html `<div class="${contentClass}">${ctx.renderMarkdown(comment.msg)}</div>`;
|
|
59
|
+
}
|
|
60
|
+
function createCommentActions(ctx, comment) {
|
|
61
|
+
return html `<button
|
|
62
|
+
class="reply-button ycc-clickable ycc-transition ycc-transparent-bg ycc-reset-button"
|
|
63
|
+
@click=${() => ctx.setReplyTo(comment.id)}
|
|
64
|
+
>
|
|
65
|
+
${ctx.i18n$.t('reply')}
|
|
66
|
+
</button>`;
|
|
67
|
+
}
|
|
68
|
+
function createCommentItemTemplate(ctx, comment, isRoot = false, replyToName = null, allReplies = null, commentMap = null) {
|
|
69
|
+
const css = getCommentCssClasses(isRoot);
|
|
70
|
+
const canEdit = ctx.canEditComment(comment.id);
|
|
71
|
+
return html ` <div class="${css.item}" ${isRoot ? `data-id="${comment.id}"` : ''}>
|
|
72
|
+
${createCommentHeader(ctx, comment, css, replyToName, canEdit)}
|
|
73
|
+
${createCommentContent(ctx, comment, css.content)} ${createCommentActions(ctx, comment)}
|
|
74
|
+
${isRoot
|
|
75
|
+
? html `<div class="replies">
|
|
76
|
+
${allReplies
|
|
77
|
+
? allReplies.map((reply) => {
|
|
78
|
+
const replyToComment = reply.replyTo && commentMap ? commentMap[reply.replyTo] : undefined;
|
|
79
|
+
const replyToName = replyToComment ? ctx.getDisplayName(replyToComment) : '';
|
|
80
|
+
return createCommentItemTemplate(ctx, reply, false, replyToName);
|
|
81
|
+
})
|
|
82
|
+
: ''}
|
|
83
|
+
</div>`
|
|
84
|
+
: ''}
|
|
85
|
+
</div>`;
|
|
86
|
+
}
|
|
87
|
+
function processComments(ctx, data, commentMap) {
|
|
88
|
+
const roots = data.filter((c) => !c.replyTo);
|
|
89
|
+
const replyMap = {};
|
|
90
|
+
data.forEach((c) => {
|
|
91
|
+
if (c.replyTo) {
|
|
92
|
+
(replyMap[c.replyTo] ||= []).push(c);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
const getAll = (id) => {
|
|
96
|
+
const all = [];
|
|
97
|
+
const q = [...(replyMap[id] || [])];
|
|
98
|
+
while (q.length) {
|
|
99
|
+
const r = q.shift();
|
|
100
|
+
if (!r)
|
|
101
|
+
break;
|
|
102
|
+
all.push(r);
|
|
103
|
+
const children = replyMap[r.id] || [];
|
|
104
|
+
if (children.length)
|
|
105
|
+
q.push(...children);
|
|
106
|
+
}
|
|
107
|
+
return all;
|
|
108
|
+
};
|
|
109
|
+
return roots.map((root) => createCommentItemTemplate(ctx, root, true, null, getAll(root.id), commentMap));
|
|
110
|
+
}
|
|
111
|
+
export function createCommentsTemplate(ctx) {
|
|
112
|
+
const comments = ctx.comments$;
|
|
113
|
+
if (comments.length === 0) {
|
|
114
|
+
return html `<div id="comments">
|
|
115
|
+
<div class="no-comments-message">${ctx.i18n$.t('noComments')}</div>
|
|
116
|
+
</div>`;
|
|
117
|
+
}
|
|
118
|
+
return html `<div id="comments">${processComments(ctx, comments, ctx.commentMap$)}</div>`;
|
|
119
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
import { formatDate } from '../utils/format';
|
|
3
|
+
export function createPreviewTemplate(ctx) {
|
|
4
|
+
const now = Date.now();
|
|
5
|
+
const userName = ctx.previewPseudonym$ ?? '';
|
|
6
|
+
return html ` <div class="comment-box preview-mode">
|
|
7
|
+
<div id="preview">
|
|
8
|
+
${ctx.previewText$
|
|
9
|
+
? html ` <div class="preview-comment">
|
|
10
|
+
<div class="comment-header">
|
|
11
|
+
<span class="comment-name">${userName || ctx.i18n$.t('anonymous')}</span>
|
|
12
|
+
<span class="comment-time">${formatDate(now)}</span>
|
|
13
|
+
${ctx.currentReplyTo$ && ctx.commentMap$[ctx.currentReplyTo$]
|
|
14
|
+
? html `<span class="reply-to"
|
|
15
|
+
>${ctx.i18n$.t('replyTo')}<span
|
|
16
|
+
>${ctx.getDisplayName(ctx.commentMap$[ctx.currentReplyTo$])}</span
|
|
17
|
+
></span
|
|
18
|
+
>`
|
|
19
|
+
: ''}
|
|
20
|
+
</div>
|
|
21
|
+
<div class="comment-content">${ctx.renderMarkdown(ctx.previewText$)}</div>
|
|
22
|
+
</div>`
|
|
23
|
+
: html `<div class="empty-preview">${ctx.i18n$.t('emptyPreview')}</div>`}
|
|
24
|
+
</div>
|
|
25
|
+
<div class="comment-footer ycc-flex ycc-gap-xs">
|
|
26
|
+
<span style="flex: 1;"></span>
|
|
27
|
+
<div class="ycc-flex ycc-gap-xs">
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
class="help-btn ycc-clickable ycc-reset-button"
|
|
31
|
+
title="${ctx.i18n$.t('markdownHelp')}"
|
|
32
|
+
@click=${() => ctx.toggleMarkdownHelp()}
|
|
33
|
+
>
|
|
34
|
+
?
|
|
35
|
+
</button>
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
class="preview-btn ycc-clickable ycc-transition ycc-transparent-bg active ycc-reset-button"
|
|
39
|
+
@click=${() => ctx.switchTab('write')}
|
|
40
|
+
>
|
|
41
|
+
${ctx.i18n$.t('write')}
|
|
42
|
+
</button>
|
|
43
|
+
<button
|
|
44
|
+
type="button"
|
|
45
|
+
class="submit-btn ycc-clickable ycc-transition ycc-reset-button"
|
|
46
|
+
@click=${() => ctx.handlePreviewSubmit()}
|
|
47
|
+
>
|
|
48
|
+
${ctx.editingComment$ ? ctx.i18n$.t('updateComment') : ctx.i18n$.t('submitComment')}
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>`;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--ycc-text-color: #333;--ycc-background-color: #fff;--ycc-primary-color: #3b82f6;--ycc-font-family: inherit;--ycc-base-font-size: 14px;--ycc-border-radius: 6px;--ycc-small-border-radius: 4px;--ycc-tiny-border-radius: 2px;--ycc-spacing: 16px;--ycc-small-font-size: .8em;--ycc-transition: all .2s ease;--ycc-reply-indent: 12px;--ycc-hover-opacity: .8;--ycc-light: color-mix(in srgb, var(--ycc-text-color) 60%, var(--ycc-background-color));--ycc-lighter: color-mix(in srgb, var(--ycc-text-color) 40%, var(--ycc-background-color));--ycc-subtle: color-mix(in srgb, var(--ycc-text-color) 20%, var(--ycc-background-color));--ycc-minimal: color-mix(in srgb, var(--ycc-text-color) 5%, var(--ycc-background-color));--ycc-primary-soft: color-mix(in srgb, var(--ycc-primary-color) 90%, var(--ycc-background-color));--ycc-overlay: color-mix(in srgb, var(--ycc-text-color) 50%, transparent)}body.ycc-modal-open{overflow:hidden}.ycc-flex{display:flex}.ycc-flex-center{display:flex;align-items:center}.ycc-flex-wrap{flex-wrap:wrap}.ycc-transparent-bg{background-color:transparent}.ycc-clickable{cursor:pointer}.ycc-transition{transition:var(--ycc-transition)}.ycc-reset-button{border:none;padding:0;margin:0;background:none;font-family:var(--ycc-font-family)}.ycc-reset-form{margin:0;padding:0;border:none;box-shadow:none}.ycc-gap-xs{gap:.5rem}.ycc-gap-sm{gap:.75rem}.ycc-gap-md{gap:1rem}.ycc-container{font-family:var(--ycc-font-family);color:var(--ycc-text-color);font-size:var(--ycc-base-font-size)}.comment-box{border:1px solid var(--ycc-subtle);border-radius:var(--ycc-border-radius);overflow:hidden;background-color:var(--ycc-background-color)}.comment-input{padding:1rem 1rem .25rem}.comment-input textarea{width:100%;border:none;resize:none;font-size:16px;min-height:120px;outline:none;font-family:var(--ycc-font-family);color:var(--ycc-text-color);background-color:var(--ycc-background-color);box-sizing:border-box}.comment-input textarea::placeholder{color:var(--ycc-lighter);opacity:1}.char-count{font-size:12px;color:var(--ycc-light);text-align:right;margin-top:4px}.char-count .over-limit{color:#dc3545;font-weight:700}.name-input-container{flex:1;position:relative}.comment-footer{align-items:center;border-top:1px solid var(--ycc-subtle);padding:.5rem 1rem;background-color:var(--ycc-background-color);position:relative}.comment-footer input[type=text]{width:100%;border:none;font-size:14px;outline:none;font-family:var(--ycc-font-family);color:var(--ycc-text-color);background-color:var(--ycc-background-color);border-radius:var(--ycc-tiny-border-radius)}.comment-footer input[type=text]::placeholder{color:var(--ycc-lighter);opacity:1}.comment-footer button{padding:.3rem .8rem;margin-left:.4rem;font-size:14px;border-radius:var(--ycc-small-border-radius);font-family:var(--ycc-font-family)}.help-btn{font-size:16px;color:var(--ycc-lighter);background:none!important;padding:.2rem .5rem!important}.help-btn:hover,.preview-btn{color:var(--ycc-primary-color)}.preview-btn:hover{background-color:var(--ycc-primary-color);color:var(--ycc-background-color)}.submit-btn{background-color:var(--ycc-primary-color);color:var(--ycc-background-color);border:1px solid var(--ycc-primary-color)}.submit-btn:hover{opacity:var(--ycc-hover-opacity)}.info{max-width:600px;margin:.5rem 0;font-size:13px;color:var(--ycc-lighter);align-items:flex-start}.preview-mode{border:1px solid var(--ycc-subtle);border-radius:var(--ycc-border-radius);background-color:var(--ycc-background-color)}#preview{padding:1rem}.empty-preview{color:var(--ycc-lighter);font-style:italic;padding:2rem 0;text-align:center}#comment-form{box-sizing:border-box;width:100%;background:none}#comments{margin-top:var(--ycc-spacing)}.no-comments-message{text-align:center;color:var(--ycc-lighter);font-style:italic;padding:2rem 1rem;margin:1rem 0}.comment{margin-bottom:12px;padding:4px}.comment-header,.reply-header{margin-bottom:8px;align-items:center;flex-wrap:wrap}.comment-name,.reply-name{font-weight:700}.my-comment-badge{background-color:var(--ycc-light);color:var(--ycc-background-color);border-radius:var(--ycc-small-border-radius);padding:2px 3px;margin-left:2px;font-size:.8em}.author-badge{background-color:var(--ycc-primary-color);color:var(--ycc-background-color);border-radius:var(--ycc-small-border-radius);padding:2px 3px;margin-left:2px;font-size:.8em;font-weight:700}.comment-time,.reply-time,.reply-to{font-size:var(--ycc-small-font-size);color:var(--ycc-light);margin-left:8px}.reply-to{color:var(--ycc-lighter);opacity:0;transition:var(--ycc-transition)}.reply:hover .reply-to{opacity:1}.comment-content,.reply-content,.preview-comment{margin-bottom:6px;line-height:1.5;word-break:break-word}.preview-comment{margin:0;padding:0;border:none;box-shadow:none}.comment-content a,.reply-content a,.preview-comment a{color:var(--ycc-primary-color);text-decoration:none;border-bottom:1px solid var(--ycc-primary-color);padding-bottom:1px}.comment-content img,.reply-content img,.preview-comment img{max-width:75%}.comment-content ul,.reply-content ul,.preview-comment ul{padding-left:20px;margin:8px 0;list-style-type:disc}.comment-content ol,.reply-content ol,.preview-comment ol{padding-left:20px;margin:8px 0;list-style-type:decimal}.comment-content li,.reply-content li,.preview-comment li{margin-bottom:4px}.reply-button,.cancel-link,.edit-button,.delete-button{font-family:var(--ycc-font-family)}.reply-button{color:var(--ycc-text-color);background-color:transparent;font-size:12px;padding:0;text-decoration:underline;text-underline-offset:4px}.cancel-link{color:var(--ycc-primary-color);background-color:transparent;font-size:13px;padding:0}.reply-button:hover,.cancel-link:hover{opacity:var(--ycc-hover-opacity)}.edit-button,.delete-button{color:var(--ycc-light);border-radius:calc(var(--ycc-border-radius) / 2);font-size:calc(var(--ycc-base-font-size) - 2px);padding:2px 6px}.edit-button:hover,.delete-button:hover{background-color:var(--ycc-minimal)}.replies{margin-top:4px}.reply{margin-left:var(--ycc-reply-indent);border-left:4px solid var(--ycc-minimal);padding-left:15px;padding-top:15px}.comment-controls{margin-left:auto}#markdown-help-modal{display:none}#markdown-help-modal.active{display:block;position:fixed;z-index:1000;inset:0}.markdown-help-container{width:100%;height:100%;align-items:center;justify-content:center;position:relative}.markdown-help-backdrop{position:absolute;inset:0;background-color:var(--ycc-overlay)}.markdown-help-content{position:relative;background-color:var(--ycc-background-color);color:var(--ycc-text-color);border-radius:var(--ycc-small-border-radius);padding:1.5rem;max-width:500px;width:90%;max-height:90vh;overflow-y:auto;box-shadow:0 2px 8px var(--ycc-light);z-index:1010}.markdown-help-close{position:absolute;top:10px;right:10px;font-size:1.5rem;color:var(--ycc-light)}.markdown-help-button{align-items:center;justify-content:center;width:26px;height:26px;color:var(--ycc-light);background:none;font-weight:700;margin-right:10px;line-height:1;font-size:var(--ycc-base-font-size)}.markdown-examples{padding:10px 15px;border-radius:var(--ycc-border-radius);background-color:var(--ycc-minimal);overflow-x:auto;margin-bottom:1em}.markdown-help-content a{color:var(--ycc-primary-color);text-decoration:none}.markdown-help-content h4{margin-top:1.5em;margin-bottom:.75em;font-size:1.1rem;color:var(--ycc-primary-color)}.markdown-help-content h4:first-child{margin-top:0}.markdown-help-content p{margin-bottom:1em;font-size:.9rem;line-height:1.5}.markdown-help-content code{font-family:monospace;font-size:.9em}.markdown-help-content pre{margin:0;white-space:pre-wrap;line-height:1.6}.honeypot-field{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important;pointer-events:none!important;opacity:0!important}.comment-box-container{display:flex;flex-direction:column}.form-content{flex:1}.admin-btn-wrapper{display:flex;justify-content:flex-end;padding-right:8px}.admin-btn{font-size:16px;color:var(--ycc-lighter);padding:4px 6px;border-radius:var(--ycc-small-border-radius);opacity:0;transition:var(--ycc-transition)}.comment-box-container:hover .admin-btn{opacity:1}.admin-btn:hover{color:var(--ycc-primary-color);background-color:var(--ycc-subtle)!important}.admin-modal-backdrop{position:fixed;inset:0;background-color:var(--ycc-overlay);display:flex;justify-content:center;align-items:center;z-index:1000}.admin-modal-content{background-color:var(--ycc-background-color);border:1px solid var(--ycc-subtle);border-radius:var(--ycc-border-radius);padding:2rem;max-width:400px;width:90%;position:relative;box-shadow:0 4px 12px #00000026}.admin-modal-close{position:absolute;top:8px;right:12px;font-size:24px;color:var(--ycc-light);background:none!important;padding:0!important;border:none!important;line-height:1}.admin-modal-close:hover{color:var(--ycc-text-color)}.admin-modal-content h3{margin:0 0 1rem;color:var(--ycc-text-color);font-family:var(--ycc-font-family);font-size:1.2em}.admin-form-group{margin-bottom:1rem}.admin-form-group label{display:block;margin-bottom:.5rem;color:var(--ycc-text-color);font-family:var(--ycc-font-family);font-size:var(--ycc-base-font-size)}.admin-form-group input{width:100%;padding:.5rem;border:1px solid var(--ycc-subtle);border-radius:var(--ycc-small-border-radius);font-family:var(--ycc-font-family);font-size:var(--ycc-base-font-size);background-color:var(--ycc-background-color);color:var(--ycc-text-color);box-sizing:border-box}.admin-form-group input:focus{outline:none;border-color:var(--ycc-primary-color)}.admin-login-btn{width:100%;padding:.75rem;background-color:var(--ycc-primary-color);color:var(--ycc-background-color);border:none;border-radius:var(--ycc-small-border-radius);font-family:var(--ycc-font-family);font-size:var(--ycc-base-font-size);font-weight:500;transition:var(--ycc-transition)}.admin-login-btn:hover{opacity:var(--ycc-hover-opacity)}@media (max-width: 576px){.ycc-container,.info{margin:0;max-width:100%}.comment-box{border-radius:var(--ycc-border-radius);margin:0 .5rem;border:1px solid var(--ycc-subtle)}.comment-input{padding:.75rem}.comment-input textarea{min-height:100px;font-size:16px}.comment-footer{flex-direction:column;align-items:stretch;padding:.75rem}.comment-footer input[type=text]{width:100%;min-width:unset;flex:none;margin-bottom:0;padding:.5rem;border:1px solid var(--ycc-subtle);border-radius:var(--ycc-small-border-radius);box-sizing:border-box}.name-input-container{margin-bottom:.75rem}.comment-footer .ycc-flex{justify-content:space-between;align-items:center;width:100%}.help-btn,.preview-btn,.submit-btn{font-size:13px;padding:.4rem .8rem}.comment,.reply{padding:8px}.reply{margin-left:8px;padding-left:10px}.comment-header,.reply-header{flex-direction:column;align-items:flex-start}.comment-time,.reply-time,.reply-to{margin-left:0;margin-top:4px}.reply:hover .reply-to{opacity:1}.comment-controls{margin-left:0;margin-top:8px}.info{flex-direction:column;align-items:flex-start;margin:.5rem 0;padding:0 1rem}.preview-mode .comment-footer{flex-direction:row;justify-content:space-between;align-items:center;padding:.75rem}}
|