diff --git a/.gitignore b/.gitignore index 766621b..5bc46b2 100644 --- a/.gitignore +++ b/.gitignore @@ -105,7 +105,6 @@ out # Nuxt.js build / generate output .nuxt -dist # Gatsby files @@ -172,3 +171,5 @@ package-lock.json wrangler.jsonc .claude + +cwd-comments-admin/dist \ No newline at end of file diff --git a/widget/dist/cwd-comments.js b/widget/dist/cwd-comments.js new file mode 100644 index 0000000..e3e8b75 --- /dev/null +++ b/widget/dist/cwd-comments.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).CWDComments={})}(this,function(t){"use strict";function e(t){const e=t.apiBaseUrl.replace(/\/$/,"");return{fetchComments:async function(r=1,n=20){const o=new URLSearchParams({post_slug:t.postSlug,page:r.toString(),limit:n.toString(),nested:"true"});t.avatarPrefix&&o.set("avatar_prefix",t.avatarPrefix);const i=await fetch(`${e}/api/comments?${o}`);if(!i.ok)throw new Error(`获取评论失败:${i.status} ${i.statusText}`);return i.json()},submitComment:async function(r){const n=await fetch(`${e}/api/comments`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({post_slug:t.postSlug,post_title:t.postTitle,post_url:t.postUrl,name:r.name,email:r.email,url:r.url||void 0,content:r.content,parent_id:r.parentId,adminToken:r.adminToken})});if(!n.ok){let t=n.statusText;try{const e=await n.json();e.message&&(t=e.message)}catch(o){}throw new Error(t)}return n.json()},verifyAdminKey:async function(t){const r=await fetch(`${e}/api/verify-admin`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({adminToken:t})});if(!r.ok){let t=r.statusText;try{const e=await r.json();e.message&&(t=e.message)}catch(n){}throw new Error(t)}return r.json()}}}const r="cwd_admin_auth",n="cwd-salt";function o(t){try{const e=t=>t.split("").map(t=>t.charCodeAt(0)),r=t=>("0"+Number(t).toString(16)).substr(-2),o=t=>e(n).reduce((t,e)=>t^e,t);return t.split("").map(e).map(o).map(r).join("")}catch(e){return btoa(t)}}const i={saveToken(t){const e={adminToken:o(t),timestamp:Date.now()};localStorage.setItem(r,JSON.stringify(e))},getToken(){try{const t=localStorage.getItem(r);if(!t)return null;const e=JSON.parse(t);return Date.now()-e.timestamp>432e5?(this.clearToken(),null):function(t){try{const e=t=>t.split("").map(t=>t.charCodeAt(0)),r=t=>e(n).reduce((t,e)=>t^e,t);return t.match(/.{1,2}/g).map(t=>parseInt(t,16)).map(r).map(t=>String.fromCharCode(t)).join("")}catch(e){return atob(t)}}(e.adminToken)}catch(t){return this.clearToken(),null}},clearToken(){localStorage.removeItem(r)},hasToken(){return!!this.getToken()}},a="cwd_user_info";class s{constructor(t){this.state={...t},this.listeners=[]}getState(){return{...this.state}}setState(t){const e={...this.state};this.state={...this.state,...t},this.listeners.forEach(t=>{t(this.state,e)})}subscribe(t){return this.listeners.push(t),()=>{this.listeners=this.listeners.filter(e=>e!==t)}}}function d(t,e,r){const n=function(){try{const t=localStorage.getItem(a);if(t){const e=JSON.parse(t);return{name:e.name||"",email:e.email||"",url:e.url||""}}}catch(t){}return{name:"",email:"",url:""}}(),o=new s({comments:[],loading:!0,error:null,pagination:{page:1,limit:t.pageSize||20,total:0,totalCount:0},form:{name:n.name||"",email:n.email||"",url:n.url||"",content:""},formErrors:{},submitting:!1,replyingTo:null,replyContent:"",replyError:null});async function d(t=1){o.setState({loading:!0,error:null});try{const r=await e(t,o.getState().pagination.limit);o.setState({comments:r.data,pagination:{page:r.pagination.page,limit:r.pagination.limit,total:r.pagination.total,totalCount:r.pagination.totalCount},loading:!1})}catch(r){o.setState({error:r instanceof Error?r.message:"加载评论失败",loading:!1})}}return o.subscribe(t=>{(t.form.name||t.form.email||t.form.url)&&function(t,e,r){try{const n={name:t,email:e,url:r};localStorage.setItem(a,JSON.stringify(n))}catch(n){}}(t.form.name,t.form.email,t.form.url)}),{store:o,getTotalPages:()=>o.getState().pagination.total,loadComments:d,submitNewComment:async function(){const t=o.getState(),e=t.form,{validateCommentForm:n}=await Promise.resolve().then(()=>E),a=n(e);if(!a.valid)return o.setState({formErrors:a.errors}),!1;o.setState({formErrors:{},submitting:!0,error:null});try{return await r({name:e.name,email:e.email,url:e.url,content:e.content,adminToken:i.getToken()}),o.setState({form:{...e,content:""},submitting:!1}),await d(t.pagination.page),!0}catch(s){return o.setState({error:s instanceof Error?s.message:"提交评论失败",submitting:!1}),!1}},submitReply:async function(t){const e=o.getState();if(!e.replyContent.trim())return!1;const{validateReplyUserInfo:n}=await Promise.resolve().then(()=>E),a=n(e.form);if(!a.valid){const t=Object.values(a.errors).join(";");return o.setState({replyError:t}),!1}o.setState({formErrors:{},submitting:!0,replyError:null});try{return await r({name:e.form.name,email:e.form.email,url:e.form.url,content:e.replyContent,parentId:t,adminToken:i.getToken()}),o.setState({replyContent:"",replyingTo:null,submitting:!1}),await d(e.pagination.page),!0}catch(s){return o.setState({error:s instanceof Error?s.message:"提交回复失败",submitting:!1}),!1}},startReply:function(t){o.setState({replyingTo:t,replyContent:"",replyError:null})},cancelReply:function(){o.setState({replyingTo:null,replyContent:"",replyError:null})},updateFormField:function(t,e){const r={...o.getState().form};r[t]=e,o.setState({form:r})},updateReplyContent:function(t){o.setState({replyContent:t})},clearReplyError:function(){o.setState({replyError:null})},clearError:function(){o.setState({error:null})},goToPage:function(t){const e=o.getState().pagination.total;t>=1&&t<=e&&d(t)}}}class l{constructor(t,e={}){this.container="string"==typeof t?document.querySelector(t):t,this.props=e,this.state={},this.elements={},this.destroyed=!1}setState(t){if(this.destroyed)return;const e={...this.state};this.state="function"==typeof t?{...this.state,...t(e)}:{...this.state,...t},this.update(e)}setProps(t){if(this.destroyed)return;const e={...this.props};this.props={...this.props,...t},this.updateProps(e)}render(){}update(t){this.render()}updateProps(t){}destroy(){this.destroyed=!0,this.container&&this.elements.root&&this.elements.root.parentNode&&this.elements.root.parentNode.removeChild(this.elements.root),this.elements={}}createElement(t,e={}){const r=document.createElement(t);if(e.className&&(r.className=e.className),e.attributes&&Object.entries(e.attributes).forEach(([t,e])=>{if(t.startsWith("on")){const n=t.slice(2).toLowerCase();r.addEventListener(n,e)}else"dataset"===t?Object.entries(e).forEach(([t,e])=>{r.dataset[t]=e}):["disabled","checked","readonly","required","autofocus"].includes(t)?e&&r.setAttribute(t,""):r.setAttribute(t,e)}),void 0!==e.text&&(r.textContent=e.text),void 0!==e.html&&(r.innerHTML=e.html),e.children){(Array.isArray(e.children)?e.children:[e.children]).forEach(t=>{t instanceof HTMLElement&&r.appendChild(t)})}return r}createTextElement(t,e,r=""){return this.createElement(t,{className:r,text:e})}empty(t){for(;t.firstChild;)t.removeChild(t.firstChild)}}class c extends l{constructor(t,e={}){super(t,e),this.state={key:"",error:"",loading:!1},this.elements={input:null,error:null,submitBtn:null}}render(){const{key:t,error:e,loading:r}=this.state,n=this.createElement("div",{className:"cwd-modal-overlay",children:[this.createElement("div",{className:"cwd-modal",children:[this.createTextElement("h3","管理员身份验证","cwd-modal-title"),this.createElement("div",{className:"cwd-modal-body",children:[this.createTextElement("p","检测到管理员邮箱,请输入密钥以继续。","cwd-modal-desc"),this.createElement("input",{className:"cwd-form-input "+(e?"cwd-input-error":""),attributes:{type:"password",placeholder:"请输入管理员密钥",value:t,disabled:r,onInput:t=>this.setState({key:t.target.value,error:""}),onKeydown:t=>{"Enter"===t.key&&this.handleSubmit()}}}),e?this.createTextElement("div",e,"cwd-error-text"):null]}),this.createElement("div",{className:"cwd-modal-actions",children:[this.createElement("button",{className:"cwd-btn cwd-btn-secondary",text:"取消",attributes:{type:"button",disabled:r,onClick:()=>this.props.onCancel&&this.props.onCancel()}}),this.createElement("button",{className:"cwd-btn cwd-btn-primary",text:r?"验证中...":"验证",attributes:{type:"button",disabled:r||!t,onClick:()=>this.handleSubmit()}})]})]})]});this.empty(this.container),this.container.appendChild(n),this.elements.input=n.querySelector("input"),this.elements.error=n.querySelector(".cwd-error-text"),this.elements.submitBtn=n.querySelector(".cwd-btn-primary"),this.update();const o=this.elements.input;o&&setTimeout(()=>o.focus(),50)}setState(t){this.state={...this.state,...t},this.update()}update(){const{key:t,error:e,loading:r}=this.state;this.elements.input&&(this.elements.input.value=t,this.elements.input.disabled=r,e?this.elements.input.classList.add("cwd-input-error"):this.elements.input.classList.remove("cwd-input-error")),this.elements.error&&(e?(this.elements.error.textContent=e,this.elements.error.style.display=""):(this.elements.error.textContent="",this.elements.error.style.display="none")),this.elements.submitBtn&&(this.elements.submitBtn.disabled=r||!t,this.elements.submitBtn.textContent=r?"验证中...":"验证")}handleSubmit(){this.state.key&&this.props.onSubmit&&(this.setState({loading:!0}),this.props.onSubmit(this.state.key).catch(t=>{this.setState({error:t.message||"验证失败",loading:!1})}))}destroy(){this.empty(this.container)}}class m extends l{constructor(t,e={}){super(t,e),this.state={localForm:{...e.form}},this.modal=null}render(){const{formErrors:t,submitting:e}=this.props,{localForm:r}=this.state,n=r.name.trim()&&r.email.trim()&&r.content.trim(),o=this.props.adminEmail&&r.email.trim()===this.props.adminEmail&&i.hasToken(),a=this.createElement("form",{className:"cwd-comment-form",attributes:{novalidate:!0,onSubmit:t=>this.handleSubmit(t)},children:[this.createTextElement("h3","发表评论","cwd-form-title"),this.createElement("div",{className:"cwd-form-fields",children:[this.createElement("div",{className:"cwd-form-row",children:[this.createFormField("昵称 *","text","name",r.name,t.name),this.createElement("div",{className:"cwd-form-field-wrapper",children:[this.createFormField("邮箱 *","email","email",r.email,t.email),o?this.createElement("div",{className:"cwd-admin-controls",children:[this.createElement("button",{className:"cwd-btn-text",text:"退出验证",attributes:{type:"button",title:"清除管理员凭证",onClick:()=>{i.clearToken(),this.render()}}})]}):null]}),this.createFormField("网址","url","url",r.url,t.url)]}),this.createElement("div",{className:"cwd-form-field",children:[this.createTextElement("label","写下你的评论...","cwd-form-label"),this.createElement("textarea",{className:"cwd-form-textarea "+(t.content?"cwd-input-error":""),attributes:{placeholder:"",rows:4,disabled:e,onInput:t=>this.handleFieldChange("content",t.target.value)}}),...t.content?[this.createTextElement("span",t.content,"cwd-error-text")]:[]]})]}),this.createElement("div",{className:"cwd-form-actions",children:[this.createElement("button",{className:"cwd-btn cwd-btn-primary",attributes:{type:"submit",disabled:e||!n},text:e?"提交中...":"提交评论"})]})]});this.setInputValues(a,r),this.elements.root=a,this.empty(this.container),this.container.appendChild(a)}updateProps(t){if(!this.props.submitting&&this.props.form!==t.form){const t=this.state.localForm.name||"",e=this.state.localForm.email||"",r=this.state.localForm.url||"",n=this.state.localForm.content||"";this.state.localForm={name:this.props.form.name||t,email:this.props.form.email||e,url:this.props.form.url||r,content:void 0!==this.props.form.content?this.props.form.content:n},this.elements.root&&this.setInputValues(this.elements.root,this.state.localForm)}this.elements.root&&this.updateFormState()}updateFormState(){const{formErrors:t,submitting:e}=this.props,{localForm:r}=this.state,n=r.name.trim()&&r.email.trim()&&r.content.trim(),o=this.elements.root.querySelector('button[type="submit"]');o&&(o.disabled=e||!n,o.textContent=e?"提交中...":"提交评论");this.elements.root.querySelectorAll("input, textarea").forEach(t=>{t.disabled=e}),this.updateErrors(t)}updateErrors(t){if(!this.elements.root)return;const e=this.elements.root.querySelector('input[name="name"]');this.updateFieldError(e,null==t?void 0:t.name);const r=this.elements.root.querySelector('input[name="email"]');this.updateFieldError(r,null==t?void 0:t.email);const n=this.elements.root.querySelector('input[name="url"]');this.updateFieldError(n,null==t?void 0:t.url);const o=this.elements.root.querySelector("textarea");this.updateFieldError(o,null==t?void 0:t.content)}updateFieldError(t,e){if(!t)return;e?t.classList.add("cwd-input-error"):t.classList.remove("cwd-input-error");const r=t.parentElement;let n=r.querySelector(".cwd-error-text");e?(n||(n=document.createElement("span"),n.className="cwd-error-text",r.appendChild(n)),n.textContent=e):n&&n.remove()}createFormField(t,e,r,n,o,i=""){return this.createElement("div",{className:"cwd-form-field",children:[this.createTextElement("label",t,"cwd-form-label"),this.createElement("input",{className:"cwd-form-input "+(o?"cwd-input-error":""),attributes:{type:e,name:r,value:n||"",disabled:this.props.submitting,onInput:t=>this.handleFieldChange(r,t.target.value),onBlur:t=>{"email"===r&&this.handleEmailBlur(t.target.value)}}}),...o?[this.createTextElement("span",o,"cwd-error-text")]:[]]})}setInputValues(t,e){const r=t.querySelector('input[name="name"]'),n=t.querySelector('input[name="email"]'),o=t.querySelector('input[name="url"]'),i=t.querySelector("textarea");r&&(r.value=e.name||""),n&&(n.value=e.email||""),o&&(o.value=e.url||""),i&&(i.value=e.content||"")}handleFieldChange(t,e){this.state.localForm[t]=e,this.props.onFieldChange&&this.props.onFieldChange(t,e),this.elements.root&&this.updateFormState()}handleSubmit(t){t.preventDefault(),this.props.onSubmit&&this.props.onSubmit(this.state.localForm)}async handleEmailBlur(t){if(t&&this.props.adminEmail&&t.trim()===this.props.adminEmail){if(i.hasToken())return;this.showAuthModal()}}showAuthModal(){let t=this.elements.root.querySelector(".cwd-modal-container");t||(t=document.createElement("div"),t.className="cwd-modal-container",this.elements.root.appendChild(t)),this.modal=new c(t,{onCancel:()=>{this.modal.destroy(),this.modal=null},onSubmit:async t=>{this.props.onVerifyAdmin&&(await this.props.onVerifyAdmin(t),i.saveToken(t),this.modal.destroy(),this.modal=null)}}),this.modal.render()}}class p extends l{constructor(t,e={}){super(t,e),this.state={content:e.content||""}}render(){const t=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:"✕"})]}),this.createElement("textarea",{className:"cwd-reply-textarea",attributes:{rows:3,placeholder:"写下你的回复...",disabled:this.props.submitting,onInput:t=>this.handleInput(t)}}),...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-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:"取消"})]})]}),e=t.querySelector("textarea");e&&(e.value=this.state.content),this.elements.root=t,this.empty(this.container),this.container.appendChild(t)}updateProps(t){if(this.props.content!==this.state.content&&this.props.content!==(null==t?void 0:t.content))return this.state.content=this.props.content,void this.render();this.props.error===(null==t?void 0:t.error)&&this.props.submitting===(null==t?void 0:t.submitting)||this.render()}handleInput(t){var e;this.state.content=t.target.value;const r=null==(e=this.elements.root)?void 0:e.querySelector(".cwd-btn-primary");r&&(r.disabled=this.props.submitting||!this.state.content.trim()),this.props.onUpdate&&this.props.onUpdate(this.state.content)}handleSubmit(){this.props.onSubmit&&this.props.onSubmit()}handleCancel(){this.props.onCancel&&this.props.onCancel()}handleClearError(){this.props.onClearError&&this.props.onClearError()}setContent(t){var e;this.state.content=t;const r=null==(e=this.elements.root)?void 0:e.querySelector("textarea");r&&(r.value=t)}getContent(){return this.state.content}focus(){var t;const e=null==(t=this.elements.root)?void 0:t.querySelector("textarea");e&&e.focus()}}function h(t){const e=new Date(t),r=(new Date).getTime()-e.getTime(),n=Math.floor(r/1e3),o=Math.floor(n/60),i=Math.floor(o/60),a=Math.floor(i/24);return a<3?a>0?1===a?"昨天":`${a}天前`:i>0?`${i}小时前`:o>0?`${o}分钟前`:"刚刚":function(t){const e=new Date(t),r=e.getFullYear(),n=String(e.getMonth()+1).padStart(2,"0"),o=String(e.getDate()).padStart(2,"0"),i=String(e.getHours()).padStart(2,"0"),a=String(e.getMinutes()).padStart(2,"0"),s=String(e.getSeconds()).padStart(2,"0");return`${r}/${n}/${o} ${i}:${a}:${s}`}(t)}class u extends l{constructor(t,e={}){super(t,e),this.replyEditor=null,this.childCommentItems=[]}render(){const{comment:t,isReply:e,adminEmail:r,adminBadge:n}=this.props,o=this.props.replyingTo===t.id,i=r&&n&&t.email===r,a=this.createElement("div",{className:"cwd-comment-item "+(e?"cwd-comment-reply":""),children:[this.createElement("div",{className:"cwd-comment-avatar",children:[this.createElement("img",{attributes:{src:t.avatar,alt:t.name,loading:"lazy"}})]}),this.createElement("div",{className:"cwd-comment-body",children:[this.createElement("div",{className:"cwd-comment-header",children:[this.createElement("div",{className:"cwd-comment-author",children:[t.url?this.createElement("span",{className:"cwd-author-name",children:[this.createElement("a",{attributes:{href:t.url,target:"_blank",rel:"noopener noreferrer"},text:t.name})]}):this.createTextElement("span",t.name,"cwd-author-name"),...i?[this.createTextElement("span",`${n}`,"cwd-admin-badge")]:[],...t.replyToAuthor?[this.createTextElement("span"," 回复 ","cwd-reply-to-separator"),this.createTextElement("span",t.replyToAuthor,"cwd-reply-to-author")]:[]]}),this.createElement("div",{className:"cwd-comment-actions",children:[this.createElement("span",{className:"cwd-action-btn",attributes:{onClick:()=>this.handleReply()},text:"回复"}),this.createTextElement("span",h(t.created),"cwd-comment-time")]})]}),this.createElement("div",{className:"cwd-comment-content"}),this.createElement("div",{className:"cwd-reply-editor-container"}),...t.replies&&t.replies.length>0?[this.createElement("div",{className:"cwd-replies"})]:[]]})]}),s=a.querySelector(".cwd-comment-content");if(s&&(s.innerHTML=t.contentHtml),o){const e=a.querySelector(".cwd-reply-editor-container");e&&(this.replyEditor=new p(e,{replyToAuthor:t.name,content:this.props.replyContent,error:this.props.replyError,submitting:this.props.submitting,onUpdate:t=>this.handleUpdateReplyContent(t),onSubmit:()=>this.handleSubmitReply(),onCancel:()=>this.handleCancelReply(),onClearError:()=>this.handleClearReplyError()}),this.replyEditor.render(),this.replyEditor.focus())}else this.replyEditor=null;if(this.childCommentItems=[],t.replies&&t.replies.length>0){const e=a.querySelector(".cwd-replies");e&&t.replies.forEach(t=>{const r=new u(e,{comment:t,isReply:!0,replyingTo:this.props.replyingTo,replyContent:this.props.replyContent,replyError:this.props.replyError,submitting:this.props.submitting,adminEmail:this.props.adminEmail,adminBadge:this.props.adminBadge,onReply:this.props.onReply,onSubmitReply:this.props.onSubmitReply,onCancelReply:this.props.onCancelReply,onUpdateReplyContent:this.props.onUpdateReplyContent,onClearReplyError:this.props.onClearReplyError});r.render(),this.childCommentItems.push(r)})}this.elements.root=a,this.container.contains(a)?this.container.replaceChild(a,this.elements.root):this.container.appendChild(a)}updateProps(t){var e;const{comment:r}=this.props,n=t.replyingTo===r.id,o=this.props.replyingTo===r.id;if(this.props.comment===t.comment){if(o!==n){const t=null==(e=this.elements.root)?void 0:e.querySelector(":scope > .cwd-comment-body > .cwd-reply-editor-container");o&&t?(this.replyEditor=new p(t,{replyToAuthor:r.name,content:this.props.replyContent,error:this.props.replyError,submitting:this.props.submitting,onUpdate:t=>this.handleUpdateReplyContent(t),onSubmit:()=>this.handleSubmitReply(),onCancel:()=>this.handleCancelReply(),onClearError:()=>this.handleClearReplyError()}),this.replyEditor.render(),this.replyEditor.focus()):!o&&t&&(t.innerHTML="",this.replyEditor=null)}else o&&this.replyEditor&&this.replyEditor.setProps({content:this.props.replyContent,error:this.props.replyError,submitting:this.props.submitting});this.childCommentItems&&this.childCommentItems.length>0&&this.childCommentItems.forEach(t=>{t.setProps({replyingTo:this.props.replyingTo,replyContent:this.props.replyContent,replyError:this.props.replyError,submitting:this.props.submitting})})}else this.render()}handleReply(){this.props.onReply&&this.props.onReply(this.props.comment.id)}handleSubmitReply(){this.props.onSubmitReply&&this.props.onSubmitReply(this.props.comment.id)}handleCancelReply(){this.props.onCancelReply&&this.props.onCancelReply()}handleUpdateReplyContent(t){this.props.onUpdateReplyContent&&this.props.onUpdateReplyContent(t)}handleClearReplyError(){this.props.onClearReplyError&&this.props.onClearReplyError()}}class f extends l{constructor(t,e={}){super(t,{text:"加载中...",...e})}render(){const t=this.createElement("div",{className:"cwd-loading",children:[this.createElement("div",{className:"cwd-spinner"}),this.createTextElement("span",this.props.text,"cwd-loading-text")]});this.elements.root=t,this.empty(this.container),this.container.appendChild(t)}setText(t){this.props.text=t;const e=this.elements.root.querySelector(".cwd-loading-text");e&&(e.textContent=t)}}class g extends l{constructor(t,e={}){super(t,e),this.state={currentPage:e.currentPage||1,totalPages:e.totalPages||1}}getDisplayedPages(){const{currentPage:t,totalPages:e}=this.state,r=[],n=Math.max(1,t-Math.floor(2.5)),o=Math.min(e,n+5-1);for(let i=o-5+1;i<=o;i++)i>=1&&r.push(i);return r.slice(0,5)}render(){if(this.state.totalPages<=1)return void this.empty(this.container);const t=this.getDisplayedPages(),e=this.createElement("div",{className:"cwd-pagination",children:[this.createElement("button",{className:"cwd-page-btn",attributes:{type:"button",disabled:1===this.state.currentPage,onClick:()=>this.handlePrev()},text:"上一页"}),this.createElement("div",{className:"cwd-page-numbers",children:t.map(t=>this.createElement("button",{className:"cwd-page-num "+(t===this.state.currentPage?"cwd-page-num-active":""),attributes:{type:"button",onClick:()=>this.handleGoTo(t)},text:t.toString()}))}),this.createElement("button",{className:"cwd-page-btn",attributes:{type:"button",disabled:this.state.currentPage===this.state.totalPages,onClick:()=>this.handleNext()},text:"下一页"})]});this.elements.root=e,this.empty(this.container),this.container.appendChild(e)}updateProps(){this.state.currentPage=this.props.currentPage,this.state.totalPages=this.props.totalPages,this.render()}handlePrev(){this.props.onPrev&&this.props.onPrev()}handleNext(){this.props.onNext&&this.props.onNext()}handleGoTo(t){this.props.onGoTo&&this.props.onGoTo(t)}}class w extends l{constructor(t,e={}){super(t,e),this.loadingComponent=null,this.paginationComponent=null,this.commentItems=new Map}render(){const{comments:t,loading:e,error:r,currentPage:n,totalPages:o}=this.props;if(this.empty(this.container),e&&0===t.length)return this.loadingComponent=new f(this.container,{text:"加载评论中..."}),this.loadingComponent.render(),void(this.elements.root=this.loadingComponent.elements.root);if(r&&0===t.length){const t=this.createElement("div",{className:"cwd-error",children:[this.createTextElement("span",r),this.createElement("button",{className:"cwd-error-retry",attributes:{type:"button",onClick:()=>this.handleRetry()},text:"重试"})]});return this.elements.root=t,void this.container.appendChild(t)}const i=this.createElement("div",{className:"cwd-comment-list"});if(t.length>0){const e=this.createElement("div",{className:"cwd-comments"});this.commentItems.clear(),t.forEach((t,r)=>{const n=new u(e,{comment:t,replyingTo:this.props.replyingTo,replyContent:this.props.replyContent,replyError:this.props.replyError,submitting:this.props.submitting,adminEmail:this.props.adminEmail,adminBadge:this.props.adminBadge,onReply:t=>this.handleReply(t),onSubmitReply:t=>this.handleSubmitReply(t),onCancelReply:()=>this.handleCancelReply(),onUpdateReplyContent:t=>this.handleUpdateReplyContent(t),onClearReplyError:()=>this.handleClearReplyError()});n.render(),this.commentItems.set(t.id,n)}),i.appendChild(e)}else{const t=this.createElement("div",{className:"cwd-empty",children:[this.createTextElement("p","暂无评论,快来抢沙发吧!","cwd-empty-text")]});i.appendChild(t)}if(o>1){const t=this.createElement("div");i.appendChild(t),this.paginationComponent=new g(t,{currentPage:n,totalPages:o,onPrev:()=>this.handlePrevPage(),onNext:()=>this.handleNextPage(),onGoTo:t=>this.handleGoToPage(t)}),this.paginationComponent.render()}else this.paginationComponent=null;this.elements.root=i,this.container.appendChild(i)}updateProps(t){if(this.props.loading===t.loading||this.props.loading)if(this.props.comments===t.comments)if(this.props.replyingTo===t.replyingTo&&this.props.replyError===t.replyError&&this.props.submitting===t.submitting){if(this.paginationComponent){(this.props.currentPage!==t.currentPage||this.props.totalPages!==t.totalPages)&&(this.paginationComponent.props.currentPage=this.props.currentPage,this.paginationComponent.props.totalPages=this.props.totalPages,this.paginationComponent.updateProps())}}else this.commentItems.forEach(t=>{t.setProps({replyingTo:this.props.replyingTo,replyContent:this.props.replyContent,replyError:this.props.replyError,submitting:this.props.submitting})});else this.render();else this.render()}handleRetry(){this.props.onRetry&&this.props.onRetry()}handleReply(t){this.props.onReply&&this.props.onReply(t)}handleSubmitReply(t){this.props.onSubmitReply&&this.props.onSubmitReply(t)}handleCancelReply(){this.props.onCancelReply&&this.props.onCancelReply()}handleUpdateReplyContent(t){this.props.onUpdateReplyContent&&this.props.onUpdateReplyContent(t)}handleClearReplyError(){this.props.onClearReplyError&&this.props.onClearReplyError()}handlePrevPage(){this.props.onPrevPage&&this.props.onPrevPage()}handleNextPage(){this.props.onNextPage&&this.props.onNextPage()}handleGoToPage(t){this.props.onGoToPage&&this.props.onGoToPage(t)}}class b{constructor(t){this.config={...t},"undefined"!=typeof window&&(this.config.postSlug=window.location.origin+window.location.pathname),"undefined"!=typeof document&&(this.config.postTitle=document.title||this.config.postSlug),"undefined"!=typeof window&&(this.config.postUrl=window.location.href),this.hostElement=this._resolveElement(t.el),this.shadowRoot=null,this.mountPoint=null,this.commentForm=null,this.commentList=null,this.store=null,this.unsubscribe=null,this._mounted=!1}_resolveElement(t){if("string"==typeof t){const e=document.querySelector(t);if(!e)throw new Error(`元素未找到: ${t}`);if(!(e instanceof HTMLElement))throw new Error(`目标不是 HTMLElement: ${t}`);return e}return t}async _loadServerConfig(){try{const t=this.config.apiBaseUrl;if(!t)return{};const e=t.replace(/\/$/,""),r=await fetch(`${e}/api/config/comments`);if(!r.ok)return{};const n=await r.json();return{adminEmail:n.adminEmail||"",adminBadge:n.adminBadge||"",adminEnabled:!!n.adminEnabled,avatarPrefix:n.avatarPrefix||"",allowedDomains:Array.isArray(n.allowedDomains)?n.allowedDomains:[]}}catch(t){return{}}}mount(){if(this._mounted)return;this.shadowRoot=this.hostElement.attachShadow({mode:"open"});const t=document.createElement("style");t.textContent=':root,[data-theme=light]{--cwd-primary: #0969da;--cwd-primary-hover: #0864ca;--cwd-text: #24292f;--cwd-text-secondary: #6e7781;--cwd-bg: #ffffff;--cwd-bg-input: #ffffff;--cwd-bg-secondary: #f6f8fa;--cwd-bg-reply: #f6f8fa;--cwd-bg-hover: #f6f8fa;--cwd-bg-disabled: #f6f8fa;--cwd-bg-avatar: #f6f8fa;--cwd-border: #d0d7de;--cwd-border-light: #eaeef2;--cwd-error: #cf222e;--cwd-bg-error: #ffebe9;--cwd-border-error: #fd8c73;--cwd-radius: 6px}[data-theme=dark]{--cwd-primary: #58a6ff;--cwd-primary-hover: #4094ff;--cwd-text: #c9d1d9;--cwd-text-secondary: #8b949e;--cwd-bg: #0d1117;--cwd-bg-input: #0d1117;--cwd-bg-secondary: #161b22;--cwd-bg-reply: #161b22;--cwd-bg-hover: #161b22;--cwd-bg-disabled: #161b22;--cwd-bg-avatar: #161b22;--cwd-border: #30363d;--cwd-border-light: #21262d;--cwd-error: #f85149;--cwd-bg-error: #3d1614;--cwd-border-error: #f85149}*{box-sizing:border-box;margin:0;padding:0}.cwd-comments-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";font-size:14px;line-height:1.5;color:var(--cwd-text);background:var(--cwd-bg);word-wrap:break-word}.cwd-comments-header{display:flex;align-items:center;justify-content:space-between;padding:16px 0;margin-bottom:16px;border-bottom:1px solid var(--cwd-border)}.cwd-comments-count{font-size:16px;font-weight:600;color:var(--cwd-text)}.cwd-loading{display:flex;align-items:center;justify-content:center;gap:12px;padding:40px 20px;color:var(--cwd-text-secondary, #6e7781)}.cwd-spinner{width:20px;height:20px;border:2px solid var(--cwd-border, #d0d7de);border-top-color:var(--cwd-primary, #0969da);border-radius:50%;animation:cwd-spin .6s linear infinite}@keyframes cwd-spin{to{transform:rotate(360deg)}}.cwd-loading-text{font-size:14px}.cwd-comment-form{background:var(--cwd-bg, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);padding:20px;margin-bottom:24px}.cwd-form-title{margin:0 0 16px;font-size:16px;font-weight:600;color:var(--cwd-text, #24292f)}.cwd-form-fields{display:flex;flex-direction:column;gap:12px}.cwd-form-row{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}@media(max-width:640px){.cwd-form-row{grid-template-columns:1fr}}.cwd-form-field{display:flex;flex-direction:column;gap:4px}.cwd-form-label{font-size:13px;font-weight:500;color:var(--cwd-text, #24292f)}.cwd-form-field input,.cwd-form-field textarea{width:100%;padding:8px 12px;font-size:14px;line-height:1.5;color:var(--cwd-text, #24292f);background:var(--cwd-bg-input, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);transition:border-color .2s,box-shadow .2s;font-family:inherit}.cwd-form-field input:focus,.cwd-form-field textarea:focus{outline:none;border-color:var(--cwd-primary, #0969da);box-shadow:0 0 0 3px #0969da1a}.cwd-form-field input:disabled,.cwd-form-field textarea:disabled{background:var(--cwd-bg-disabled, #f6f8fa);cursor:not-allowed}.cwd-form-field input.cwd-input-error,.cwd-form-field textarea.cwd-input-error{border-color:var(--cwd-error, #cf222e)}.cwd-form-field textarea{resize:vertical;min-height:80px}.cwd-error-text{font-size:12px;color:var(--cwd-error, #cf222e)}.cwd-form-actions{display:flex;justify-content:flex-end;margin-top:16px}.cwd-btn{padding:8px 16px;font-size:14px;font-weight:500;line-height:1.5;border:none;border-radius:var(--cwd-radius, 6px);cursor:pointer;transition:background-color .2s;font-family:inherit}.cwd-btn-primary{color:#fff;background:var(--cwd-primary, #0969da)}.cwd-btn-primary:hover:not(:disabled){background:var(--cwd-primary-hover, #0864ca)}.cwd-btn:disabled{opacity:.6;cursor:not-allowed}.cwd-reply-editor{margin-top:12px;padding:12px;background:var(--cwd-bg-reply, #f6f8fa);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px)}.cwd-reply-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}.cwd-reply-to{font-size:13px;font-weight:500;color:var(--cwd-text-secondary, #6e7781)}.cwd-btn-close{width:20px;height:20px;padding:0;font-size:14px;color:var(--cwd-text-secondary, #6e7781);background:transparent;border:none;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .2s}.cwd-btn-close:hover{background:var(--cwd-bg-hover, rgba(0, 0, 0, .05))}.cwd-reply-textarea{width:100%;padding:8px 12px;font-size:14px;line-height:1.5;color:var(--cwd-text, #24292f);background:var(--cwd-bg-input, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);resize:vertical;font-family:inherit;margin-bottom:8px}.cwd-reply-textarea:focus{outline:none;border-color:var(--cwd-primary, #0969da);box-shadow:0 0 0 3px #0969da1a}.cwd-reply-actions{display:flex;gap:8px;justify-content:flex-end}.cwd-btn-small{padding:4px 10px;font-size:12px}.cwd-btn-secondary{color:var(--cwd-text, #24292f);background:var(--cwd-bg-secondary, #f6f8fa);border:1px solid var(--cwd-border, #d0d7de)}.cwd-btn-secondary:hover:not(:disabled){background:var(--cwd-bg-hover, #eaeef2)}.cwd-comment-item{display:flex;gap:12px;padding:20px 0;border-bottom:1px solid var(--cwd-border-light, #eaeef2)}.cwd-comment-body:hover .cwd-action-btn{display:inline-flex!important}.cwd-comment-item:last-child{border-bottom:none}.cwd-comment-reply{padding-top:12px;padding-bottom:12px}.cwd-comment-avatar{flex-shrink:0;width:40px;height:40px;border-radius:5px;border:1px solid var(--cwd-border, #d0d7de);overflow:hidden;background:var(--cwd-bg-avatar, #f6f8fa)}.cwd-comment-avatar img{width:100%;height:100%;object-fit:cover}.cwd-comment-reply .cwd-comment-avatar{width:32px;height:32px}.cwd-comment-body{flex:1;min-width:0}.cwd-comment-header{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px}.cwd-author-name{font-size:16px;font-weight:600;color:var(--cwd-text, #24292f)}.cwd-author-name a{color:inherit;text-decoration:none}.cwd-author-name a:hover{color:var(--cwd-primary, #0969da);text-decoration:underline}.cwd-comment-author{display:inline-flex;align-items:center}.cwd-admin-badge{background:#db850d;color:#fff;border-radius:3px;padding:1px 4px;font-size:12px;margin:0 4px}.cwd-reply-to-separator{margin:0 4px}.cwd-reply-to-author{font-style:italic}.cwd-comment-time{font-size:12px;color:var(--cwd-text-secondary, #6e7781)}.cwd-comment-content{font-size:14px;line-height:1.6;color:var(--cwd-text, #24292f);word-wrap:break-word;margin-top:10px}.cwd-comment-content p{margin:0 0 8px}.cwd-comment-content p:last-child{margin-bottom:0}.cwd-comment-content a{color:var(--cwd-primary, #0969da);text-decoration:none}.cwd-comment-content a:hover{text-decoration:underline}.cwd-comment-content h1,.cwd-comment-content h2,.cwd-comment-content h3,.cwd-comment-content h4,.cwd-comment-content h5,.cwd-comment-content h6{margin-top:16px;margin-bottom:8px;font-weight:600;line-height:1.25}.cwd-comment-content h1{font-size:1.5em}.cwd-comment-content h2{font-size:1.3em}.cwd-comment-content h3{font-size:1.1em}.cwd-comment-content ul,.cwd-comment-content ol{padding-left:1.7em;margin:0 0 8px}.cwd-comment-content li+li{margin-top:4px}.cwd-comment-content code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.9em;background:var(--cwd-bg-secondary, #f6f8fa);padding:.2em .4em;border-radius:4px}.cwd-comment-content pre{padding:12px;margin:12px 0;overflow-x:auto;background:var(--cwd-bg-secondary, #f6f8fa);border-radius:6px;font-size:.9em}.cwd-comment-content pre code{padding:0;background:transparent;border-radius:0;display:block}.cwd-comment-content blockquote{margin:8px 0;padding:0 12px;border-left:4px solid var(--cwd-border, #d0d7de);color:var(--cwd-text-secondary, #6e7781)}.cwd-comment-content hr{border:0;border-top:1px solid var(--cwd-border-light, #eaeef2);margin:16px 0}.cwd-comment-content table{border-collapse:collapse;border-spacing:0;width:100%;font-size:13px;margin:12px 0}.cwd-comment-content th,.cwd-comment-content td{border:1px solid var(--cwd-border, #d0d7de);padding:6px 12px;text-align:left}.cwd-comment-content th{background:var(--cwd-bg-secondary, #f6f8fa)}.cwd-comment-content img{max-width:100%;height:auto}.cwd-comment-actions{display:flex;align-items:center;gap:12px}.cwd-action-btn{display:inline-flex;align-items:center;font-size:12px;color:var(--cwd-text-secondary, #6e7781);background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s;font-family:inherit;display:none}.cwd-action-btn:hover{color:var(--cwd-primary, #0969da)}.cwd-replies{margin-top:12px;padding-left:12px;border-left:2px solid var(--cwd-border-light, #eaeef2)}.cwd-comment-list{min-height:100px}.cwd-comments{display:flex;flex-direction:column}.cwd-error{padding:20px;text-align:center;color:var(--cwd-error, #cf222e);background:var(--cwd-bg-error, #ffebe9);border:1px solid var(--cwd-border-error, #fd8c73);border-radius:var(--cwd-radius, 6px)}.cwd-error-retry{margin-left:12px;padding:4px 12px;font-size:13px;color:#fff;background:var(--cwd-primary, #0969da);border:none;border-radius:4px;cursor:pointer;font-family:inherit}.cwd-error-retry:hover{background:var(--cwd-primary-hover, #0864ca)}.cwd-empty{padding:40px 20px;text-align:center}.cwd-empty-icon{font-size:48px;margin-bottom:12px}.cwd-empty-text{margin:0;font-size:14px;color:var(--cwd-text-secondary, #6e7781)}.cwd-pagination{display:flex;align-items:center;justify-content:center;gap:8px;padding:20px 0}.cwd-page-btn{padding:6px 12px;font-size:13px;color:var(--cwd-text, #24292f);background:var(--cwd-bg, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);cursor:pointer;transition:background-color .2s;font-family:inherit}.cwd-page-btn:hover:not(:disabled){background:var(--cwd-bg-hover, #f6f8fa)}.cwd-page-btn:disabled{opacity:.5;cursor:not-allowed}.cwd-page-numbers{display:flex;gap:4px}.cwd-page-num{min-width:32px;padding:6px 8px;font-size:13px;color:var(--cwd-text, #24292f);background:var(--cwd-bg, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);cursor:pointer;transition:background-color .2s;font-family:inherit}.cwd-page-num-active{color:#fff;background:var(--cwd-primary, #0969da);border-color:var(--cwd-primary, #0969da)}.cwd-error-inline{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;margin-bottom:16px;font-size:14px;color:var(--cwd-error, #cf222e);background:var(--cwd-bg-error, #ffebe9);border:1px solid var(--cwd-border-error, #fd8c73);border-radius:var(--cwd-radius, 6px)}.cwd-error-close{width:20px;height:20px;padding:0;font-size:14px;color:inherit;background:transparent;border:none;border-radius:4px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background-color .2s}.cwd-error-close:hover{background:#0000001a}.cwd-load-more{display:block;width:100%;padding:12px;margin:16px 0;font-size:14px;color:var(--cwd-primary);background:var(--cwd-bg-secondary);border:1px solid var(--cwd-border);border-radius:var(--cwd-radius);cursor:pointer;transition:background-color .2s;font-family:inherit}.cwd-load-more:hover{background:var(--cwd-bg-hover)}.fade-enter-active,.fade-leave-active{transition:opacity .2s ease}.fade-enter-from,.fade-leave-to{opacity:0}.cwd-comments-container::-webkit-scrollbar{width:8px;height:8px}.cwd-comments-container::-webkit-scrollbar-track{background:transparent}.cwd-comments-container::-webkit-scrollbar-thumb{background:var(--cwd-border);border-radius:4px}.cwd-comments-container::-webkit-scrollbar-thumb:hover{background:var(--cwd-text-secondary)}.cwd-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:10000;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.cwd-modal{background:var(--cwd-bg, #ffffff);border-radius:var(--cwd-radius, 6px);width:90%;max-width:400px;box-shadow:0 8px 24px #0003;border:1px solid var(--cwd-border, #d0d7de);overflow:hidden;animation:cwd-modal-in .2s ease-out}@keyframes cwd-modal-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}.cwd-modal-title{margin:0;padding:16px 20px;border-bottom:1px solid var(--cwd-border, #d0d7de);font-size:16px;font-weight:600;color:var(--cwd-text, #24292f)}.cwd-modal-body{padding:20px}.cwd-modal-desc{margin:0 0 16px;font-size:14px;color:var(--cwd-text-secondary, #6e7781)}.cwd-form-input{width:100%;padding:8px 12px;font-size:14px;line-height:1.5;color:var(--cwd-text, #24292f);background:var(--cwd-bg-input, #ffffff);border:1px solid var(--cwd-border, #d0d7de);border-radius:var(--cwd-radius, 6px);transition:border-color .2s,box-shadow .2s;font-family:inherit}.cwd-modal-actions{padding:16px 20px;background:var(--cwd-bg-secondary, #f6f8fa);border-top:1px solid var(--cwd-border, #d0d7de);display:flex;justify-content:flex-end;gap:12px}.cwd-form-field-wrapper{display:flex;flex-direction:column;gap:4px;position:relative}.cwd-admin-controls{position:absolute;top:0;right:0;display:flex;align-items:center;gap:8px;font-size:12px}.cwd-admin-verified{color:#1a7f37;font-weight:500}.cwd-btn-text{background:none;border:none;padding:0;color:var(--cwd-text-secondary);font-size:12px;cursor:pointer;text-decoration:underline}.cwd-btn-text:hover{color:var(--cwd-primary)}',this.shadowRoot.appendChild(t),this.mountPoint=document.createElement("div"),this.mountPoint.className="cwd-comments-container",this.shadowRoot.appendChild(this.mountPoint),this.config.theme&&this.mountPoint.setAttribute("data-theme",this.config.theme),(async()=>{const t=await this._loadServerConfig();if(!this._mounted)return;if(t.allowedDomains&&t.allowedDomains.length>0&&"undefined"!=typeof window){const e=window.location.hostname;if(!t.allowedDomains.some(t=>e===t||e.endsWith("."+t)))return void(this.mountPoint.innerHTML=`\n