diff --git a/cwd-comments-admin/public/cwd-comments.js b/cwd-comments-admin/public/cwd-comments.js index 733dee8..504ae44 100644 --- a/cwd-comments-admin/public/cwd-comments.js +++ b/cwd-comments-admin/public/cwd-comments.js @@ -1 +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,o=20){const n=new URLSearchParams({post_slug:t.postSlug,page:r.toString(),limit:o.toString(),nested:"true"}),i=await fetch(`${e}/api/comments?${n}`);if(!i.ok)throw new Error(`获取评论失败: ${i.status} ${i.statusText}`);return console.log("[API] fetchComments response:",i),i.json()},submitComment:async function(r){const o=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})});if(!o.ok)throw new Error(`提交评论失败: ${o.status} ${o.statusText}`);return o.json()}}}const r="cwd_user_info";class o{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 n(t,e,n){const i=function(){try{const t=localStorage.getItem(r);if(t){const e=JSON.parse(t);return{name:e.name||"",email:e.email||"",url:e.url||""}}}catch(t){console.error("读取用户信息失败:",t)}return{name:"",email:"",url:""}}(),s=new o({comments:[],loading:!0,error:null,pagination:{page:1,limit:t.pageSize||20,total:0,totalCount:0},form:{name:i.name||"",email:i.email||"",url:i.url||"",content:""},formErrors:{},submitting:!1,replyingTo:null,replyContent:"",replyError:null});async function a(t=1){s.setState({loading:!0,error:null});try{const r=await e(t,s.getState().pagination.limit);console.log("[Store] loadComments response:",r),console.log("[Store] comments data:",r.data),s.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){s.setState({error:r instanceof Error?r.message:"加载评论失败",loading:!1})}}return s.subscribe(t=>{(t.form.name||t.form.email||t.form.url)&&function(t,e,o){try{const n={name:t,email:e,url:o};localStorage.setItem(r,JSON.stringify(n))}catch(n){console.error("保存用户信息失败:",n)}}(t.form.name,t.form.email,t.form.url)}),{store:s,getTotalPages:()=>s.getState().pagination.total,loadComments:a,submitNewComment:async function(){const t=s.getState(),e=t.form,{validateCommentForm:r}=await Promise.resolve().then(()=>w),o=r(e);if(!o.valid)return s.setState({formErrors:o.errors}),!1;s.setState({formErrors:{},submitting:!0,error:null});try{return await n({name:e.name,email:e.email,url:e.url,content:e.content}),s.setState({form:{...e,content:""},submitting:!1}),await a(t.pagination.page),!0}catch(i){return s.setState({error:i instanceof Error?i.message:"提交评论失败",submitting:!1}),!1}},submitReply:async function(t){const e=s.getState();if(!e.replyContent.trim())return!1;const{validateReplyUserInfo:r}=await Promise.resolve().then(()=>w),o=r(e.form);if(!o.valid){const t=Object.values(o.errors).join(";");return s.setState({replyError:t}),!1}s.setState({formErrors:{},submitting:!0,replyError:null});try{return await n({name:e.form.name,email:e.form.email,url:e.form.url,content:e.replyContent,parentId:t}),s.setState({replyContent:"",replyingTo:null,submitting:!1}),await a(e.pagination.page),!0}catch(i){return s.setState({error:i instanceof Error?i.message:"提交回复失败",submitting:!1}),!1}},startReply:function(t){console.log("[Store] startReply called with commentId:",t),s.setState({replyingTo:t,replyContent:"",replyError:null}),console.log("[Store] New state:",s.getState())},cancelReply:function(){s.setState({replyingTo:null,replyContent:"",replyError:null})},updateFormField:function(t,e){const r={...s.getState().form};r[t]=e,s.setState({form:r})},updateReplyContent:function(t){s.setState({replyContent:t})},clearReplyError:function(){s.setState({replyError:null})},clearError:function(){s.setState({error:null})},goToPage:function(t){const e=s.getState().pagination.total;t>=1&&t<=e&&a(t)}}}class i{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 o=t.slice(2).toLowerCase();r.addEventListener(o,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 s extends i{constructor(t,e={}){super(t,e),this.state={localForm:{...e.form}}}render(){const{formErrors:t,submitting:e}=this.props,{localForm:r}=this.state,o=r.name.trim()&&r.email.trim()&&r.content.trim(),n=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.createFormField("邮箱 *","email","email",r.email,t.email),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||!o},text:e?"提交中...":"提交评论"})]})]});this.setInputValues(n,r),this.elements.root=n,this.empty(this.container),this.container.appendChild(n)}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||"",o=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:o},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,o=r.name.trim()&&r.email.trim()&&r.content.trim(),n=this.elements.root.querySelector('button[type="submit"]');n&&(n.disabled=e||!o,n.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 o=this.elements.root.querySelector('input[name="url"]');this.updateFieldError(o,null==t?void 0:t.url);const n=this.elements.root.querySelector("textarea");this.updateFieldError(n,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 o=r.querySelector(".cwd-error-text");e?(o||(o=document.createElement("span"),o.className="cwd-error-text",r.appendChild(o)),o.textContent=e):o&&o.remove()}createFormField(t,e,r,o,n,i=""){return this.createElement("div",{className:"cwd-form-field",children:[this.createTextElement("label",t,"cwd-form-label"),this.createElement("input",{className:"cwd-form-input "+(n?"cwd-input-error":""),attributes:{type:e,name:r,value:o||"",disabled:this.props.submitting,onInput:t=>this.handleFieldChange(r,t.target.value)}}),...n?[this.createTextElement("span",n,"cwd-error-text")]:[]]})}setInputValues(t,e){const r=t.querySelector('input[name="name"]'),o=t.querySelector('input[name="email"]'),n=t.querySelector('input[name="url"]'),i=t.querySelector("textarea");r&&(r.value=e.name||""),o&&(o.value=e.email||""),n&&(n.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)}}class a extends i{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 l(t){const e=new Date(t),r=(new Date).getTime()-e.getTime(),o=Math.floor(r/1e3),n=Math.floor(o/60),i=Math.floor(n/60),s=Math.floor(i/24);return s<3?s>0?1===s?"昨天":`${s}天前`:i>0?`${i}小时前`:n>0?`${n}分钟前`:"刚刚":function(t){const e=new Date(t),r=e.getFullYear(),o=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0"),i=String(e.getHours()).padStart(2,"0"),s=String(e.getMinutes()).padStart(2,"0"),a=String(e.getSeconds()).padStart(2,"0");return`${r}/${o}/${n} ${i}:${s}:${a}`}(t)}class c extends i{constructor(t,e={}){super(t,e),this.replyEditor=null,this.childCommentItems=[]}render(){const{comment:t,isReply:e,adminEmail:r,adminBadge:o}=this.props,n=this.props.replyingTo===t.id,i=r&&t.email===r,s=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",`${o}`,"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",l(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"})]:[]]})]}),d=s.querySelector(".cwd-comment-content");if(d&&(d.innerHTML=t.contentHtml),n){const e=s.querySelector(".cwd-reply-editor-container");e&&(this.replyEditor=new a(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=s.querySelector(".cwd-replies");e&&t.replies.forEach(t=>{const r=new c(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=s,this.container.contains(s)?this.container.replaceChild(s,this.elements.root):this.container.appendChild(s)}updateProps(t){var e;const{comment:r}=this.props,o=t.replyingTo===r.id,n=this.props.replyingTo===r.id;if(this.props.comment===t.comment){if(n!==o){const t=null==(e=this.elements.root)?void 0:e.querySelector(":scope > .cwd-comment-body > .cwd-reply-editor-container");n&&t?(this.replyEditor=new a(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()):!n&&t&&(t.innerHTML="",this.replyEditor=null)}else n&&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 d extends i{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 p extends i{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=[],o=Math.max(1,t-Math.floor(2.5)),n=Math.min(e,o+5-1);for(let i=n-5+1;i<=n;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 m extends i{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:o,totalPages:n}=this.props;if(this.empty(this.container),e&&0===t.length)return this.loadingComponent=new d(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 o=new c(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()});o.render(),this.commentItems.set(t.id,o)}),i.appendChild(e)}else{const t=this.createElement("div",{className:"cwd-empty",children:[this.createTextElement("p","暂无评论,快来抢沙发吧!","cwd-empty-text")]});i.appendChild(t)}if(n>1){const t=this.createElement("div");i.appendChild(t),this.paginationComponent=new p(t,{currentPage:o,totalPages:n,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 h{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 o=await r.json();return{adminEmail:o.adminEmail||"",adminBadge:o.adminBadge||"",adminEnabled:!!o.adminEnabled,avatarPrefix:o.avatarPrefix||""}}catch(t){return console.warn("[CWDComments] 加载服务端评论配置失败:",t),{}}}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:hover{background:var(--cwd-bg-hover, #f6f8fa)}.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)}',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;t.avatarPrefix&&(this.config.avatarPrefix=t.avatarPrefix),t.adminEnabled&&t.adminEmail&&(this.config.adminEmail=t.adminEmail),t.adminEnabled&&t.adminBadge&&(this.config.adminBadge=t.adminBadge);const r=e(this.config);this.store=n(this.config,r.fetchComments.bind(r),r.submitComment.bind(r)),this.unsubscribe=this.store.store.subscribe(t=>{this._onStateChange(t)}),this._render(),this.store.loadComments()})(),this._mounted=!0}unmount(){if(this._mounted){if(this.commentForm&&(this.commentForm.destroy(),this.commentForm=null),this.commentList&&(this.commentList.destroy(),this.commentList=null),this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null),this.hostElement)for(;this.hostElement.firstChild;)this.hostElement.removeChild(this.hostElement.firstChild);this.shadowRoot=null,this.mountPoint=null,this.store=null,this._mounted=!1,console.log("[CWDComments] 组件已卸载")}}_render(){if(!this.mountPoint)return;const t=this.store.store.getState();this.commentForm||(this.commentForm=new s(this.mountPoint,{form:t.form,formErrors:t.formErrors,submitting:t.submitting,onSubmit:()=>this._handleSubmit(),onFieldChange:(t,e)=>this.store.updateFormField(t,e)}),this.commentForm.render());const e=this.mountPoint.querySelector(".cwd-error-inline");if(t.error){if(!e){const e=document.createElement("div");e.className="cwd-error-inline",e.innerHTML=`\n ${t.error}\n \n `,e.querySelector('[data-action="clear-error"]').addEventListener("click",()=>{this.store.clearError()}),this.mountPoint.insertBefore(e,this.mountPoint.firstChild)}}else e&&e.remove();let r=this.mountPoint.querySelector(".cwd-comments-header");r||(r=document.createElement("div"),r.className="cwd-comments-header",r.innerHTML='\n

\n 共 0 条评论\n

\n ',this.mountPoint.appendChild(r));const o=r.querySelector(".cwd-comments-count-number");if(o&&(o.textContent=t.pagination.totalCount),!this.commentList){const e=document.createElement("div");this.mountPoint.appendChild(e),this.commentList=new m(e,{comments:t.comments,loading:t.loading,error:null,currentPage:t.pagination.page,totalPages:this.store.getTotalPages(),replyingTo:t.replyingTo,replyContent:t.replyContent,replyError:t.replyError,submitting:t.submitting,adminEmail:this.config.adminEmail,adminBadge:this.config.adminBadge||"博主",onRetry:()=>this.store.loadComments(),onReply:t=>this.store.startReply(t),onSubmitReply:t=>this.store.submitReply(t),onCancelReply:()=>this.store.cancelReply(),onUpdateReplyContent:t=>this.store.updateReplyContent(t),onClearReplyError:()=>this.store.clearReplyError(),onPrevPage:()=>this.store.goToPage(t.pagination.page-1),onNextPage:()=>this.store.goToPage(t.pagination.page+1),onGoToPage:t=>this.store.goToPage(t)}),this.commentList.render()}}_onStateChange(t,e){var r,o,n,i,s;if(!this._mounted)return;if(null==(o=null==(r=this.commentForm)?void 0:r.elements)?void 0:o.root){const e=this.commentForm.elements.root;null!==t.replyingTo?e.style.display="none":e.style.display=""}this.commentForm&&this.commentForm.setProps({form:t.form,formErrors:t.formErrors,submitting:t.submitting});const a=null==(n=this.mountPoint)?void 0:n.querySelector(".cwd-error-inline");if(t.error){if(!a){const e=document.createElement("div");e.className="cwd-error-inline",e.innerHTML=`\n ${t.error}\n \n `,e.querySelector('[data-action="clear-error"]').addEventListener("click",()=>{this.store.clearError()}),null==(i=this.mountPoint)||i.insertBefore(e,this.mountPoint.firstChild)}}else a&&a.remove();const l=null==(s=this.mountPoint)?void 0:s.querySelector(".cwd-comments-header"),c=null==l?void 0:l.querySelector(".cwd-comments-count-number");c&&(c.textContent=t.pagination.totalCount),this.commentList&&this.commentList.setProps({comments:t.comments,loading:t.loading,currentPage:t.pagination.page,totalPages:this.store.getTotalPages(),replyingTo:t.replyingTo,replyContent:t.replyContent,replyError:t.replyError,submitting:t.submitting})}async _handleSubmit(){await this.store.submitNewComment()&&this.commentForm&&(this.commentForm.state.localForm={...this.store.store.getState().form})}updateConfig(t){const r={...this.config};Object.assign(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),t.theme&&this.mountPoint&&this.mountPoint.setAttribute("data-theme",t.theme);if(this.config.apiBaseUrl!==r.apiBaseUrl||this.config.pageSize!==r.pageSize||this.config.postSlug!==r.postSlug){const t=e(this.config);this.unsubscribe&&this.unsubscribe(),this.store=n(this.config,t.fetchComments.bind(t),t.submitComment.bind(t)),this.unsubscribe=this.store.store.subscribe(t=>{this._onStateChange(t)}),this.store.loadComments()}}getConfig(){return{...this.config}}}function u(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}function g(t){if(!t)return!0;try{return new URL(t),!0}catch{return!1}}function f(t){return t&&0!==t.trim().length?t.length>1e3?{valid:!1,error:"评论内容不能超过 1000 字"}:{valid:!0}:{valid:!1,error:"请输入评论内容"}}"undefined"!=typeof window&&(window.CWDComments=h);const w=Object.freeze(Object.defineProperty({__proto__:null,isValidEmail:u,isValidUrl:g,validateCommentContent:f,validateCommentForm:function(t){const e={};t.name&&0!==t.name.trim().length?t.name.length>50&&(e.name="昵称不能超过 50 字"):e.name="请输入昵称",t.email&&0!==t.email.trim().length?u(t.email)||(e.email="邮箱格式不正确"):e.email="请输入邮箱",t.url&&!g(t.url)&&(e.url="网址格式不正确");const r=f(t.content);return r.valid||(e.content=r.error),{valid:0===Object.keys(e).length,errors:e}},validateReplyUserInfo:function(t){const e={};return t.name&&0!==t.name.trim().length?t.name.length>50&&(e.name="昵称不能超过 50 字"):e.name="请输入昵称",t.email&&0!==t.email.trim().length?u(t.email)||(e.email="邮箱格式不正确"):e.email="请输入邮箱",t.url&&!g(t.url)&&(e.url="网址格式不正确"),{valid:0===Object.keys(e).length,errors:e}}},Symbol.toStringTag,{value:"Module"}));t.CWDComments=h,t.default=h,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); +!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,o=20){const n=new URLSearchParams({post_slug:t.postSlug,page:r.toString(),limit:o.toString(),nested:"true"});t.avatarPrefix&&n.set("avatar_prefix",t.avatarPrefix);const i=await fetch(`${e}/api/comments?${n}`);if(!i.ok)throw new Error(`获取评论失败: ${i.status} ${i.statusText}`);return i.json()},submitComment:async function(r){const o=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})});if(!o.ok)throw new Error(`提交评论失败: ${o.status} ${o.statusText}`);return o.json()}}}const r="cwd_user_info";class o{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 n(t,e,n){const i=function(){try{const t=localStorage.getItem(r);if(t){const e=JSON.parse(t);return{name:e.name||"",email:e.email||"",url:e.url||""}}}catch(t){}return{name:"",email:"",url:""}}(),a=new o({comments:[],loading:!0,error:null,pagination:{page:1,limit:t.pageSize||20,total:0,totalCount:0},form:{name:i.name||"",email:i.email||"",url:i.url||"",content:""},formErrors:{},submitting:!1,replyingTo:null,replyContent:"",replyError:null});async function s(t=1){a.setState({loading:!0,error:null});try{const r=await e(t,a.getState().pagination.limit);a.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){a.setState({error:r instanceof Error?r.message:"加载评论失败",loading:!1})}}return a.subscribe(t=>{(t.form.name||t.form.email||t.form.url)&&function(t,e,o){try{const n={name:t,email:e,url:o};localStorage.setItem(r,JSON.stringify(n))}catch(n){}}(t.form.name,t.form.email,t.form.url)}),{store:a,getTotalPages:()=>a.getState().pagination.total,loadComments:s,submitNewComment:async function(){const t=a.getState(),e=t.form,{validateCommentForm:r}=await Promise.resolve().then(()=>b),o=r(e);if(!o.valid)return a.setState({formErrors:o.errors}),!1;a.setState({formErrors:{},submitting:!0,error:null});try{return await n({name:e.name,email:e.email,url:e.url,content:e.content}),a.setState({form:{...e,content:""},submitting:!1}),await s(t.pagination.page),!0}catch(i){return a.setState({error:i instanceof Error?i.message:"提交评论失败",submitting:!1}),!1}},submitReply:async function(t){const e=a.getState();if(!e.replyContent.trim())return!1;const{validateReplyUserInfo:r}=await Promise.resolve().then(()=>b),o=r(e.form);if(!o.valid){const t=Object.values(o.errors).join(";");return a.setState({replyError:t}),!1}a.setState({formErrors:{},submitting:!0,replyError:null});try{return await n({name:e.form.name,email:e.form.email,url:e.form.url,content:e.replyContent,parentId:t}),a.setState({replyContent:"",replyingTo:null,submitting:!1}),await s(e.pagination.page),!0}catch(i){return a.setState({error:i instanceof Error?i.message:"提交回复失败",submitting:!1}),!1}},startReply:function(t){a.setState({replyingTo:t,replyContent:"",replyError:null})},cancelReply:function(){a.setState({replyingTo:null,replyContent:"",replyError:null})},updateFormField:function(t,e){const r={...a.getState().form};r[t]=e,a.setState({form:r})},updateReplyContent:function(t){a.setState({replyContent:t})},clearReplyError:function(){a.setState({replyError:null})},clearError:function(){a.setState({error:null})},goToPage:function(t){const e=a.getState().pagination.total;t>=1&&t<=e&&s(t)}}}class i{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 o=t.slice(2).toLowerCase();r.addEventListener(o,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 a extends i{constructor(t,e={}){super(t,e),this.state={localForm:{...e.form}}}render(){const{formErrors:t,submitting:e}=this.props,{localForm:r}=this.state,o=r.name.trim()&&r.email.trim()&&r.content.trim(),n=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.createFormField("邮箱 *","email","email",r.email,t.email),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||!o},text:e?"提交中...":"提交评论"})]})]});this.setInputValues(n,r),this.elements.root=n,this.empty(this.container),this.container.appendChild(n)}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||"",o=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:o},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,o=r.name.trim()&&r.email.trim()&&r.content.trim(),n=this.elements.root.querySelector('button[type="submit"]');n&&(n.disabled=e||!o,n.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 o=this.elements.root.querySelector('input[name="url"]');this.updateFieldError(o,null==t?void 0:t.url);const n=this.elements.root.querySelector("textarea");this.updateFieldError(n,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 o=r.querySelector(".cwd-error-text");e?(o||(o=document.createElement("span"),o.className="cwd-error-text",r.appendChild(o)),o.textContent=e):o&&o.remove()}createFormField(t,e,r,o,n,i=""){return this.createElement("div",{className:"cwd-form-field",children:[this.createTextElement("label",t,"cwd-form-label"),this.createElement("input",{className:"cwd-form-input "+(n?"cwd-input-error":""),attributes:{type:e,name:r,value:o||"",disabled:this.props.submitting,onInput:t=>this.handleFieldChange(r,t.target.value)}}),...n?[this.createTextElement("span",n,"cwd-error-text")]:[]]})}setInputValues(t,e){const r=t.querySelector('input[name="name"]'),o=t.querySelector('input[name="email"]'),n=t.querySelector('input[name="url"]'),i=t.querySelector("textarea");r&&(r.value=e.name||""),o&&(o.value=e.email||""),n&&(n.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)}}class s extends i{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 l(t){const e=new Date(t),r=(new Date).getTime()-e.getTime(),o=Math.floor(r/1e3),n=Math.floor(o/60),i=Math.floor(n/60),a=Math.floor(i/24);return a<3?a>0?1===a?"昨天":`${a}天前`:i>0?`${i}小时前`:n>0?`${n}分钟前`:"刚刚":function(t){const e=new Date(t),r=e.getFullYear(),o=String(e.getMonth()+1).padStart(2,"0"),n=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}/${o}/${n} ${i}:${a}:${s}`}(t)}class c extends i{constructor(t,e={}){super(t,e),this.replyEditor=null,this.childCommentItems=[]}render(){const{comment:t,isReply:e,adminEmail:r,adminBadge:o}=this.props,n=this.props.replyingTo===t.id,i=r&&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",`${o}`,"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",l(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"})]:[]]})]}),d=a.querySelector(".cwd-comment-content");if(d&&(d.innerHTML=t.contentHtml),n){const e=a.querySelector(".cwd-reply-editor-container");e&&(this.replyEditor=new s(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 c(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,o=t.replyingTo===r.id,n=this.props.replyingTo===r.id;if(this.props.comment===t.comment){if(n!==o){const t=null==(e=this.elements.root)?void 0:e.querySelector(":scope > .cwd-comment-body > .cwd-reply-editor-container");n&&t?(this.replyEditor=new s(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()):!n&&t&&(t.innerHTML="",this.replyEditor=null)}else n&&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 d extends i{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 p extends i{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=[],o=Math.max(1,t-Math.floor(2.5)),n=Math.min(e,o+5-1);for(let i=n-5+1;i<=n;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 m extends i{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:o,totalPages:n}=this.props;if(this.empty(this.container),e&&0===t.length)return this.loadingComponent=new d(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 o=new c(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()});o.render(),this.commentItems.set(t.id,o)}),i.appendChild(e)}else{const t=this.createElement("div",{className:"cwd-empty",children:[this.createTextElement("p","暂无评论,快来抢沙发吧!","cwd-empty-text")]});i.appendChild(t)}if(n>1){const t=this.createElement("div");i.appendChild(t),this.paginationComponent=new p(t,{currentPage:o,totalPages:n,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 h{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 o=await r.json();return{adminEmail:o.adminEmail||"",adminBadge:o.adminBadge||"",adminEnabled:!!o.adminEnabled,avatarPrefix:o.avatarPrefix||""}}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:hover{background:var(--cwd-bg-hover, #f6f8fa)}.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)}',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;t.avatarPrefix&&(this.config.avatarPrefix=t.avatarPrefix),t.adminEnabled&&t.adminEmail&&(this.config.adminEmail=t.adminEmail),t.adminEnabled&&t.adminBadge&&(this.config.adminBadge=t.adminBadge);const r=e(this.config);this.store=n(this.config,r.fetchComments.bind(r),r.submitComment.bind(r)),this.unsubscribe=this.store.store.subscribe(t=>{this._onStateChange(t)}),this._render(),this.store.loadComments()})(),this._mounted=!0}unmount(){if(this._mounted){if(this.commentForm&&(this.commentForm.destroy(),this.commentForm=null),this.commentList&&(this.commentList.destroy(),this.commentList=null),this.unsubscribe&&(this.unsubscribe(),this.unsubscribe=null),this.hostElement)for(;this.hostElement.firstChild;)this.hostElement.removeChild(this.hostElement.firstChild);this.shadowRoot=null,this.mountPoint=null,this.store=null,this._mounted=!1}}_render(){if(!this.mountPoint)return;const t=this.store.store.getState();this.commentForm||(this.commentForm=new a(this.mountPoint,{form:t.form,formErrors:t.formErrors,submitting:t.submitting,onSubmit:()=>this._handleSubmit(),onFieldChange:(t,e)=>this.store.updateFormField(t,e)}),this.commentForm.render());const e=this.mountPoint.querySelector(".cwd-error-inline");if(t.error){if(!e){const e=document.createElement("div");e.className="cwd-error-inline",e.innerHTML=`\n ${t.error}\n \n `,e.querySelector('[data-action="clear-error"]').addEventListener("click",()=>{this.store.clearError()}),this.mountPoint.insertBefore(e,this.mountPoint.firstChild)}}else e&&e.remove();let r=this.mountPoint.querySelector(".cwd-comments-header");r||(r=document.createElement("div"),r.className="cwd-comments-header",r.innerHTML='\n

\n 共 0 条评论\n

\n ',this.mountPoint.appendChild(r));const o=r.querySelector(".cwd-comments-count-number");if(o&&(o.textContent=t.pagination.totalCount),!this.commentList){const e=document.createElement("div");this.mountPoint.appendChild(e),this.commentList=new m(e,{comments:t.comments,loading:t.loading,error:null,currentPage:t.pagination.page,totalPages:this.store.getTotalPages(),replyingTo:t.replyingTo,replyContent:t.replyContent,replyError:t.replyError,submitting:t.submitting,adminEmail:this.config.adminEmail,adminBadge:this.config.adminBadge||"博主",onRetry:()=>this.store.loadComments(),onReply:t=>this.store.startReply(t),onSubmitReply:t=>this.store.submitReply(t),onCancelReply:()=>this.store.cancelReply(),onUpdateReplyContent:t=>this.store.updateReplyContent(t),onClearReplyError:()=>this.store.clearReplyError(),onPrevPage:()=>this.store.goToPage(t.pagination.page-1),onNextPage:()=>this.store.goToPage(t.pagination.page+1),onGoToPage:t=>this.store.goToPage(t)}),this.commentList.render()}}_onStateChange(t,e){var r,o,n,i,a;if(!this._mounted)return;if(null==(o=null==(r=this.commentForm)?void 0:r.elements)?void 0:o.root){const e=this.commentForm.elements.root;null!==t.replyingTo?e.style.display="none":e.style.display=""}this.commentForm&&this.commentForm.setProps({form:t.form,formErrors:t.formErrors,submitting:t.submitting});const s=null==(n=this.mountPoint)?void 0:n.querySelector(".cwd-error-inline");if(t.error){if(!s){const e=document.createElement("div");e.className="cwd-error-inline",e.innerHTML=`\n ${t.error}\n \n `,e.querySelector('[data-action="clear-error"]').addEventListener("click",()=>{this.store.clearError()}),null==(i=this.mountPoint)||i.insertBefore(e,this.mountPoint.firstChild)}}else s&&s.remove();const l=null==(a=this.mountPoint)?void 0:a.querySelector(".cwd-comments-header"),c=null==l?void 0:l.querySelector(".cwd-comments-count-number");c&&(c.textContent=t.pagination.totalCount),this.commentList&&this.commentList.setProps({comments:t.comments,loading:t.loading,currentPage:t.pagination.page,totalPages:this.store.getTotalPages(),replyingTo:t.replyingTo,replyContent:t.replyContent,replyError:t.replyError,submitting:t.submitting})}async _handleSubmit(){await this.store.submitNewComment()&&this.commentForm&&(this.commentForm.state.localForm={...this.store.store.getState().form})}updateConfig(t){const r={...this.config};Object.assign(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),t.theme&&this.mountPoint&&this.mountPoint.setAttribute("data-theme",t.theme);if(this.config.apiBaseUrl!==r.apiBaseUrl||this.config.pageSize!==r.pageSize||this.config.postSlug!==r.postSlug){const t=e(this.config);this.unsubscribe&&this.unsubscribe(),this.store=n(this.config,t.fetchComments.bind(t),t.submitComment.bind(t)),this.unsubscribe=this.store.store.subscribe(t=>{this._onStateChange(t)}),this.store.loadComments()}}getConfig(){return{...this.config}}}function u(t){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t)}function g(t){if(!t)return!0;try{return new URL(t),!0}catch{return!1}}function f(t){return t&&0!==t.trim().length?t.length>1e3?{valid:!1,error:"评论内容不能超过 1000 字"}:{valid:!0}:{valid:!1,error:"请输入评论内容"}}"undefined"!=typeof window&&(window.CWDComments=h);const b=Object.freeze(Object.defineProperty({__proto__:null,isValidEmail:u,isValidUrl:g,validateCommentContent:f,validateCommentForm:function(t){const e={};t.name&&0!==t.name.trim().length?t.name.length>50&&(e.name="昵称不能超过 50 字"):e.name="请输入昵称",t.email&&0!==t.email.trim().length?u(t.email)||(e.email="邮箱格式不正确"):e.email="请输入邮箱",t.url&&!g(t.url)&&(e.url="网址格式不正确");const r=f(t.content);return r.valid||(e.content=r.error),{valid:0===Object.keys(e).length,errors:e}},validateReplyUserInfo:function(t){const e={};return t.name&&0!==t.name.trim().length?t.name.length>50&&(e.name="昵称不能超过 50 字"):e.name="请输入昵称",t.email&&0!==t.email.trim().length?u(t.email)||(e.email="邮箱格式不正确"):e.email="请输入邮箱",t.url&&!g(t.url)&&(e.url="网址格式不正确"),{valid:0===Object.keys(e).length,errors:e}}},Symbol.toStringTag,{value:"Module"}));t.CWDComments=h,t.default=h,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}); diff --git a/widget/src/core/CWDComments.js b/widget/src/core/CWDComments.js index d60730c..3f4613c 100644 --- a/widget/src/core/CWDComments.js +++ b/widget/src/core/CWDComments.js @@ -13,417 +13,405 @@ import styles from '@/styles/main.css?inline'; * CWDComments 评论组件主类 */ export class CWDComments { - /** - * @param {Object} config - 配置对象 - * @param {string|HTMLElement} config.el - 挂载元素选择器或 DOM 元素 - * @param {string} config.apiBaseUrl - API 基础地址 - * @param {'light'|'dark'} [config.theme] - 主题(可选) - * @param {number} [config.pageSize] - 每页评论数(可选,默认 20) - * - * 以下字段由组件自动推导或从后端读取,无需通过 config 传入: - * - postSlug:window.location.origin + window.location.pathname - * - postTitle:document.title 或 postSlug - * - postUrl:window.location.href - * - avatarPrefix/adminEmail/adminBadge:通过 /api/config/comments 接口获取 - */ - constructor(config) { - this.config = { ...config }; - if (typeof window !== 'undefined') { - this.config.postSlug = window.location.origin + window.location.pathname; - } - if (typeof document !== 'undefined') { - this.config.postTitle = document.title || this.config.postSlug; - } - if (typeof window !== 'undefined') { - this.config.postUrl = window.location.href; - } - this.hostElement = this._resolveElement(config.el); - this.shadowRoot = null; - this.mountPoint = null; - this.commentForm = null; - this.commentList = null; - this.store = null; - this.unsubscribe = null; + /** + * @param {Object} config - 配置对象 + * @param {string|HTMLElement} config.el - 挂载元素选择器或 DOM 元素 + * @param {string} config.apiBaseUrl - API 基础地址 + * @param {'light'|'dark'} [config.theme] - 主题(可选) + * @param {number} [config.pageSize] - 每页评论数(可选,默认 20) + * + * 以下字段由组件自动推导或从后端读取,无需通过 config 传入: + * - postSlug:window.location.origin + window.location.pathname + * - postTitle:document.title 或 postSlug + * - postUrl:window.location.href + * - avatarPrefix/adminEmail/adminBadge:通过 /api/config/comments 接口获取 + */ + constructor(config) { + this.config = { ...config }; + if (typeof window !== 'undefined') { + this.config.postSlug = window.location.origin + window.location.pathname; + } + if (typeof document !== 'undefined') { + this.config.postTitle = document.title || this.config.postSlug; + } + if (typeof window !== 'undefined') { + this.config.postUrl = window.location.href; + } + this.hostElement = this._resolveElement(config.el); + this.shadowRoot = null; + this.mountPoint = null; + this.commentForm = null; + this.commentList = null; + this.store = null; + this.unsubscribe = null; - // 初始加载标志 - this._mounted = false; - } + // 初始加载标志 + this._mounted = false; + } - /** - * 解析挂载元素 - * @private - */ - _resolveElement(el) { - if (typeof el === 'string') { - const element = document.querySelector(el); - if (!element) { - throw new Error(`元素未找到: ${el}`); - } - if (!(element instanceof HTMLElement)) { - throw new Error(`目标不是 HTMLElement: ${el}`); - } - return element; - } - return el; - } + /** + * 解析挂载元素 + * @private + */ + _resolveElement(el) { + if (typeof el === 'string') { + const element = document.querySelector(el); + if (!element) { + throw new Error(`元素未找到: ${el}`); + } + if (!(element instanceof HTMLElement)) { + throw new Error(`目标不是 HTMLElement: ${el}`); + } + return element; + } + return el; + } - async _loadServerConfig() { - try { - const base = this.config.apiBaseUrl; - if (!base) { - return {}; - } - const apiBaseUrl = base.replace(/\/$/, ''); - const res = await fetch(`${apiBaseUrl}/api/config/comments`); - if (!res.ok) { - return {}; - } - const data = await res.json(); - return { - adminEmail: data.adminEmail || '', - adminBadge: data.adminBadge || '', - adminEnabled: !!data.adminEnabled, - avatarPrefix: data.avatarPrefix || '' - }; - } catch (e) { - console.warn('[CWDComments] 加载服务端评论配置失败:', e); - return {}; - } - } + async _loadServerConfig() { + try { + const base = this.config.apiBaseUrl; + if (!base) { + return {}; + } + const apiBaseUrl = base.replace(/\/$/, ''); + const res = await fetch(`${apiBaseUrl}/api/config/comments`); + if (!res.ok) { + return {}; + } + const data = await res.json(); + return { + adminEmail: data.adminEmail || '', + adminBadge: data.adminBadge || '', + adminEnabled: !!data.adminEnabled, + avatarPrefix: data.avatarPrefix || '', + }; + } catch (e) { + return {}; + } + } - /** - * 挂载组件 - */ - mount() { - if (this._mounted) { - return; - } + /** + * 挂载组件 + */ + mount() { + if (this._mounted) { + return; + } - // 创建 Shadow DOM - this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' }); + // 创建 Shadow DOM + this.shadowRoot = this.hostElement.attachShadow({ mode: 'open' }); - // 注入样式 - const styleElement = document.createElement('style'); - if (typeof styles === 'string') { - styleElement.textContent = styles; - } else if (styles && typeof styles === 'object' && 'default' in styles) { - styleElement.textContent = styles.default; - } - this.shadowRoot.appendChild(styleElement); + // 注入样式 + const styleElement = document.createElement('style'); + if (typeof styles === 'string') { + styleElement.textContent = styles; + } else if (styles && typeof styles === 'object' && 'default' in styles) { + styleElement.textContent = styles.default; + } + this.shadowRoot.appendChild(styleElement); - // 创建容器 - this.mountPoint = document.createElement('div'); - this.mountPoint.className = 'cwd-comments-container'; - this.shadowRoot.appendChild(this.mountPoint); + // 创建容器 + this.mountPoint = document.createElement('div'); + this.mountPoint.className = 'cwd-comments-container'; + this.shadowRoot.appendChild(this.mountPoint); - // 设置主题 - if (this.config.theme) { - this.mountPoint.setAttribute('data-theme', this.config.theme); - } + // 设置主题 + if (this.config.theme) { + this.mountPoint.setAttribute('data-theme', this.config.theme); + } - (async () => { - const serverConfig = await this._loadServerConfig(); - if (!this._mounted) { - return; - } - if (serverConfig.avatarPrefix) { - this.config.avatarPrefix = serverConfig.avatarPrefix; - } - if (serverConfig.adminEnabled && serverConfig.adminEmail) { - this.config.adminEmail = serverConfig.adminEmail; - } - if (serverConfig.adminEnabled && serverConfig.adminBadge) { - this.config.adminBadge = serverConfig.adminBadge; - } + (async () => { + const serverConfig = await this._loadServerConfig(); + if (!this._mounted) { + return; + } + if (serverConfig.avatarPrefix) { + this.config.avatarPrefix = serverConfig.avatarPrefix; + } + if (serverConfig.adminEnabled && serverConfig.adminEmail) { + this.config.adminEmail = serverConfig.adminEmail; + } + if (serverConfig.adminEnabled && serverConfig.adminBadge) { + this.config.adminBadge = serverConfig.adminBadge; + } - const api = createApiClient(this.config); - this.store = createCommentStore( - this.config, - api.fetchComments.bind(api), - api.submitComment.bind(api) - ); + const api = createApiClient(this.config); + this.store = createCommentStore(this.config, api.fetchComments.bind(api), api.submitComment.bind(api)); - this.unsubscribe = this.store.store.subscribe((state) => { - this._onStateChange(state); - }); + this.unsubscribe = this.store.store.subscribe((state) => { + this._onStateChange(state); + }); - this._render(); - this.store.loadComments(); - })(); + this._render(); + this.store.loadComments(); + })(); - this._mounted = true; - } + this._mounted = true; + } - /** - * 卸载组件 - */ - unmount() { - if (!this._mounted) { - return; - } + /** + * 卸载组件 + */ + unmount() { + if (!this._mounted) { + return; + } - // 销毁组件 - if (this.commentForm) { - this.commentForm.destroy(); - this.commentForm = null; - } + // 销毁组件 + if (this.commentForm) { + this.commentForm.destroy(); + this.commentForm = null; + } - if (this.commentList) { - this.commentList.destroy(); - this.commentList = null; - } + if (this.commentList) { + this.commentList.destroy(); + this.commentList = null; + } - // 取消订阅 - if (this.unsubscribe) { - this.unsubscribe(); - this.unsubscribe = null; - } + // 取消订阅 + if (this.unsubscribe) { + this.unsubscribe(); + this.unsubscribe = null; + } - // 移除 Shadow DOM - 通过替换所有子节点 - if (this.hostElement) { - // Shadow DOM 会在清空子节点时自动移除 - while (this.hostElement.firstChild) { - this.hostElement.removeChild(this.hostElement.firstChild); - } - } + // 移除 Shadow DOM - 通过替换所有子节点 + if (this.hostElement) { + // Shadow DOM 会在清空子节点时自动移除 + while (this.hostElement.firstChild) { + this.hostElement.removeChild(this.hostElement.firstChild); + } + } - this.shadowRoot = null; - this.mountPoint = null; - this.store = null; - this._mounted = false; + this.shadowRoot = null; + this.mountPoint = null; + this.store = null; + this._mounted = false; + } - console.log('[CWDComments] 组件已卸载'); - } + /** + * 渲染组件 + * @private + */ + _render() { + if (!this.mountPoint) { + return; + } - /** - * 渲染组件 - * @private - */ - _render() { - if (!this.mountPoint) { - return; - } + const state = this.store.store.getState(); - const state = this.store.store.getState(); + // 创建评论表单 + if (!this.commentForm) { + this.commentForm = new CommentForm(this.mountPoint, { + form: state.form, + formErrors: state.formErrors, + submitting: state.submitting, + onSubmit: () => this._handleSubmit(), + onFieldChange: (field, value) => this.store.updateFormField(field, value), + }); + this.commentForm.render(); + } - // 创建评论表单 - if (!this.commentForm) { - this.commentForm = new CommentForm(this.mountPoint, { - form: state.form, - formErrors: state.formErrors, - submitting: state.submitting, - onSubmit: () => this._handleSubmit(), - onFieldChange: (field, value) => this.store.updateFormField(field, value) - }); - this.commentForm.render(); - } - - // 创建错误提示 - const existingError = this.mountPoint.querySelector('.cwd-error-inline'); - if (state.error) { - if (!existingError) { - const errorEl = document.createElement('div'); - errorEl.className = 'cwd-error-inline'; - errorEl.innerHTML = ` + // 创建错误提示 + const existingError = this.mountPoint.querySelector('.cwd-error-inline'); + if (state.error) { + if (!existingError) { + const errorEl = document.createElement('div'); + errorEl.className = 'cwd-error-inline'; + errorEl.innerHTML = ` ${state.error} `; - errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { - this.store.clearError(); - }); - this.mountPoint.insertBefore(errorEl, this.mountPoint.firstChild); - } - } else if (existingError) { - existingError.remove(); - } + errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { + this.store.clearError(); + }); + this.mountPoint.insertBefore(errorEl, this.mountPoint.firstChild); + } + } else if (existingError) { + existingError.remove(); + } - // 创建头部统计 - let header = this.mountPoint.querySelector('.cwd-comments-header'); - if (!header) { - header = document.createElement('div'); - header.className = 'cwd-comments-header'; - header.innerHTML = ` + // 创建头部统计 + let header = this.mountPoint.querySelector('.cwd-comments-header'); + if (!header) { + header = document.createElement('div'); + header.className = 'cwd-comments-header'; + header.innerHTML = `

0 条评论

`; - this.mountPoint.appendChild(header); - } - const countEl = header.querySelector('.cwd-comments-count-number'); - if (countEl) { - countEl.textContent = state.pagination.totalCount; - } + this.mountPoint.appendChild(header); + } + const countEl = header.querySelector('.cwd-comments-count-number'); + if (countEl) { + countEl.textContent = state.pagination.totalCount; + } - // 创建评论列表 - if (!this.commentList) { - const listContainer = document.createElement('div'); - this.mountPoint.appendChild(listContainer); + // 创建评论列表 + if (!this.commentList) { + const listContainer = document.createElement('div'); + this.mountPoint.appendChild(listContainer); - this.commentList = new CommentList(listContainer, { - comments: state.comments, - loading: state.loading, - error: null, - currentPage: state.pagination.page, - totalPages: this.store.getTotalPages(), - replyingTo: state.replyingTo, - replyContent: state.replyContent, - replyError: state.replyError, - submitting: state.submitting, - adminEmail: this.config.adminEmail, - adminBadge: this.config.adminBadge || '博主', - onRetry: () => this.store.loadComments(), - onReply: (commentId) => this.store.startReply(commentId), - onSubmitReply: (commentId) => this.store.submitReply(commentId), - onCancelReply: () => this.store.cancelReply(), - onUpdateReplyContent: (content) => this.store.updateReplyContent(content), - onClearReplyError: () => this.store.clearReplyError(), - onPrevPage: () => this.store.goToPage(state.pagination.page - 1), - onNextPage: () => this.store.goToPage(state.pagination.page + 1), - onGoToPage: (page) => this.store.goToPage(page) - }); - this.commentList.render(); - } - } + this.commentList = new CommentList(listContainer, { + comments: state.comments, + loading: state.loading, + error: null, + currentPage: state.pagination.page, + totalPages: this.store.getTotalPages(), + replyingTo: state.replyingTo, + replyContent: state.replyContent, + replyError: state.replyError, + submitting: state.submitting, + adminEmail: this.config.adminEmail, + adminBadge: this.config.adminBadge || '博主', + onRetry: () => this.store.loadComments(), + onReply: (commentId) => this.store.startReply(commentId), + onSubmitReply: (commentId) => this.store.submitReply(commentId), + onCancelReply: () => this.store.cancelReply(), + onUpdateReplyContent: (content) => this.store.updateReplyContent(content), + onClearReplyError: () => this.store.clearReplyError(), + onPrevPage: () => this.store.goToPage(state.pagination.page - 1), + onNextPage: () => this.store.goToPage(state.pagination.page + 1), + onGoToPage: (page) => this.store.goToPage(page), + }); + this.commentList.render(); + } + } - /** - * 状态变化处理 - * @private - */ - _onStateChange(state, prevState) { - if (!this._mounted) { - return; - } + /** + * 状态变化处理 + * @private + */ + _onStateChange(state, prevState) { + if (!this._mounted) { + return; + } - // 根据回复状态显示/隐藏主评论表单 - if (this.commentForm?.elements?.root) { - const formRoot = this.commentForm.elements.root; - if (state.replyingTo !== null) { - formRoot.style.display = 'none'; - } else { - formRoot.style.display = ''; - } - } + // 根据回复状态显示/隐藏主评论表单 + if (this.commentForm?.elements?.root) { + const formRoot = this.commentForm.elements.root; + if (state.replyingTo !== null) { + formRoot.style.display = 'none'; + } else { + formRoot.style.display = ''; + } + } - // 更新评论表单 - if (this.commentForm) { - this.commentForm.setProps({ - form: state.form, - formErrors: state.formErrors, - submitting: state.submitting - }); - } + // 更新评论表单 + if (this.commentForm) { + this.commentForm.setProps({ + form: state.form, + formErrors: state.formErrors, + submitting: state.submitting, + }); + } - // 更新错误提示 - const existingError = this.mountPoint?.querySelector('.cwd-error-inline'); - if (state.error) { - if (!existingError) { - const errorEl = document.createElement('div'); - errorEl.className = 'cwd-error-inline'; - errorEl.innerHTML = ` + // 更新错误提示 + const existingError = this.mountPoint?.querySelector('.cwd-error-inline'); + if (state.error) { + if (!existingError) { + const errorEl = document.createElement('div'); + errorEl.className = 'cwd-error-inline'; + errorEl.innerHTML = ` ${state.error} `; - errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { - this.store.clearError(); - }); - this.mountPoint?.insertBefore(errorEl, this.mountPoint.firstChild); - } - } else if (existingError) { - existingError.remove(); - } + errorEl.querySelector('[data-action="clear-error"]').addEventListener('click', () => { + this.store.clearError(); + }); + this.mountPoint?.insertBefore(errorEl, this.mountPoint.firstChild); + } + } else if (existingError) { + existingError.remove(); + } - // 更新头部统计 - const header = this.mountPoint?.querySelector('.cwd-comments-header'); - const countEl = header?.querySelector('.cwd-comments-count-number'); - if (countEl) { - countEl.textContent = state.pagination.totalCount; - } + // 更新头部统计 + const header = this.mountPoint?.querySelector('.cwd-comments-header'); + const countEl = header?.querySelector('.cwd-comments-count-number'); + if (countEl) { + countEl.textContent = state.pagination.totalCount; + } - // 更新评论列表 - if (this.commentList) { - this.commentList.setProps({ - comments: state.comments, - loading: state.loading, - currentPage: state.pagination.page, - totalPages: this.store.getTotalPages(), - replyingTo: state.replyingTo, - replyContent: state.replyContent, - replyError: state.replyError, - submitting: state.submitting - }); - } - } + // 更新评论列表 + if (this.commentList) { + this.commentList.setProps({ + comments: state.comments, + loading: state.loading, + currentPage: state.pagination.page, + totalPages: this.store.getTotalPages(), + replyingTo: state.replyingTo, + replyContent: state.replyContent, + replyError: state.replyError, + submitting: state.submitting, + }); + } + } - /** - * 处理评论提交 - * @private - */ - async _handleSubmit() { - const success = await this.store.submitNewComment(); - if (success) { - // 表单内容已在 store 中清空 - // 更新表单组件 - if (this.commentForm) { - this.commentForm.state.localForm = { ...this.store.store.getState().form }; - } - } - } + /** + * 处理评论提交 + * @private + */ + async _handleSubmit() { + const success = await this.store.submitNewComment(); + if (success) { + // 表单内容已在 store 中清空 + // 更新表单组件 + if (this.commentForm) { + this.commentForm.state.localForm = { ...this.store.store.getState().form }; + } + } + } - /** - * 更新配置 - * @param {Object} newConfig - 新配置 - */ - updateConfig(newConfig) { - const prevConfig = { ...this.config }; + /** + * 更新配置 + * @param {Object} newConfig - 新配置 + */ + updateConfig(newConfig) { + const prevConfig = { ...this.config }; - Object.assign(this.config, newConfig); - if (typeof window !== 'undefined') { - this.config.postSlug = window.location.origin + window.location.pathname; - } - if (typeof document !== 'undefined') { - this.config.postTitle = document.title || this.config.postSlug; - } - if (typeof window !== 'undefined') { - this.config.postUrl = window.location.href; - } + Object.assign(this.config, newConfig); + if (typeof window !== 'undefined') { + this.config.postSlug = window.location.origin + window.location.pathname; + } + if (typeof document !== 'undefined') { + this.config.postTitle = document.title || this.config.postSlug; + } + if (typeof window !== 'undefined') { + this.config.postUrl = window.location.href; + } - // 更新主题 - if (newConfig.theme && this.mountPoint) { - this.mountPoint.setAttribute('data-theme', newConfig.theme); - } + // 更新主题 + if (newConfig.theme && this.mountPoint) { + this.mountPoint.setAttribute('data-theme', newConfig.theme); + } - const shouldReload = - this.config.apiBaseUrl !== prevConfig.apiBaseUrl || - this.config.pageSize !== prevConfig.pageSize || - this.config.postSlug !== prevConfig.postSlug; + const shouldReload = + this.config.apiBaseUrl !== prevConfig.apiBaseUrl || + this.config.pageSize !== prevConfig.pageSize || + this.config.postSlug !== prevConfig.postSlug; - if (shouldReload) { - const api = createApiClient(this.config); + if (shouldReload) { + const api = createApiClient(this.config); - if (this.unsubscribe) { - this.unsubscribe(); - } + if (this.unsubscribe) { + this.unsubscribe(); + } - this.store = createCommentStore( - this.config, - api.fetchComments.bind(api), - api.submitComment.bind(api) - ); + this.store = createCommentStore(this.config, api.fetchComments.bind(api), api.submitComment.bind(api)); - this.unsubscribe = this.store.store.subscribe((state) => { - this._onStateChange(state); - }); + this.unsubscribe = this.store.store.subscribe((state) => { + this._onStateChange(state); + }); - this.store.loadComments(); - } - } - - /** - * 获取当前配置 - * @returns {Object} - */ - getConfig() { - return { ...this.config }; - } + this.store.loadComments(); + } + } + /** + * 获取当前配置 + * @returns {Object} + */ + getConfig() { + return { ...this.config }; + } } diff --git a/widget/src/core/api.js b/widget/src/core/api.js index 2b14fba..e7e5368 100644 --- a/widget/src/core/api.js +++ b/widget/src/core/api.js @@ -11,67 +11,70 @@ * @param {string} config.postUrl - 文章 URL(可选) * @returns {Object} */ -export function createApiClient(config) { - const baseUrl = config.apiBaseUrl.replace(/\/$/, ''); + export function createApiClient(config) { + const baseUrl = config.apiBaseUrl.replace(/\/$/, ''); - /** - * 获取评论列表 - * @param {number} page - 页码 - * @param {number} limit - 每页数量 - * @returns {Promise} - */ - async function fetchComments(page = 1, limit = 20) { - const params = new URLSearchParams({ - post_slug: config.postSlug, - page: page.toString(), - limit: limit.toString(), - nested: 'true' - }); + /** + * 获取评论列表 + * @param {number} page - 页码 + * @param {number} limit - 每页数量 + * @returns {Promise} + */ + async function fetchComments(page = 1, limit = 20) { + const params = new URLSearchParams({ + post_slug: config.postSlug, + page: page.toString(), + limit: limit.toString(), + nested: 'true', + }); - const response = await fetch(`${baseUrl}/api/comments?${params}`); - if (!response.ok) { - throw new Error(`获取评论失败: ${response.status} ${response.statusText}`); - } - console.log('[API] fetchComments response:', response); - return response.json(); - } + if (config.avatarPrefix) { + params.set('avatar_prefix', config.avatarPrefix); + } - /** - * 提交评论 - * @param {Object} data - 评论数据 - * @param {string} data.name - 昵称 - * @param {string} data.email - 邮箱 - * @param {string} data.url - 网址(可选) - * @param {string} data.content - 评论内容 - * @param {number} data.parentId - 父评论 ID(可选,用于回复) - * @returns {Promise} - */ - async function submitComment(data) { - const response = await fetch(`${baseUrl}/api/comments`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - post_slug: config.postSlug, - post_title: config.postTitle, - post_url: config.postUrl, - name: data.name, - email: data.email, - url: data.url || undefined, - content: data.content, - parent_id: data.parentId - }) - }); + const response = await fetch(`${baseUrl}/api/comments?${params}`); + if (!response.ok) { + throw new Error(`获取评论失败: ${response.status} ${response.statusText}`); + } + return response.json(); + } - if (!response.ok) { - throw new Error(`提交评论失败: ${response.status} ${response.statusText}`); - } - return response.json(); - } + /** + * 提交评论 + * @param {Object} data - 评论数据 + * @param {string} data.name - 昵称 + * @param {string} data.email - 邮箱 + * @param {string} data.url - 网址(可选) + * @param {string} data.content - 评论内容 + * @param {number} data.parentId - 父评论 ID(可选,用于回复) + * @returns {Promise} + */ + async function submitComment(data) { + const response = await fetch(`${baseUrl}/api/comments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + post_slug: config.postSlug, + post_title: config.postTitle, + post_url: config.postUrl, + name: data.name, + email: data.email, + url: data.url || undefined, + content: data.content, + parent_id: data.parentId, + }), + }); - return { - fetchComments, - submitComment - }; + if (!response.ok) { + throw new Error(`提交评论失败: ${response.status} ${response.statusText}`); + } + return response.json(); + } + + return { + fetchComments, + submitComment, + }; } diff --git a/widget/src/core/store.js b/widget/src/core/store.js index deb071a..830041b 100644 --- a/widget/src/core/store.js +++ b/widget/src/core/store.js @@ -10,20 +10,18 @@ const STORAGE_KEY = 'cwd_user_info'; * @returns {Object} */ function loadUserInfo() { - try { - const data = localStorage.getItem(STORAGE_KEY); - if (data) { - const parsed = JSON.parse(data); - return { - name: parsed.name || '', - email: parsed.email || '', - url: parsed.url || '' - }; - } - } catch (e) { - console.error('读取用户信息失败:', e); - } - return { name: '', email: '', url: '' }; + try { + const data = localStorage.getItem(STORAGE_KEY); + if (data) { + const parsed = JSON.parse(data); + return { + name: parsed.name || '', + email: parsed.email || '', + url: parsed.url || '', + }; + } + } catch (e) {} + return { name: '', email: '', url: '' }; } /** @@ -33,58 +31,56 @@ function loadUserInfo() { * @param {string} url - 网址 */ function saveUserInfo(name, email, url) { - try { - const data = { name, email, url }; - localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); - } catch (e) { - console.error('保存用户信息失败:', e); - } + try { + const data = { name, email, url }; + localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); + } catch (e) {} } /** * 简单的 Store 类 */ class Store { - constructor(initialState) { - this.state = { ...initialState }; - this.listeners = []; - } + constructor(initialState) { + this.state = { ...initialState }; + this.listeners = []; + } - /** - * 获取当前状态 - * @returns {Object} - */ - getState() { - return { ...this.state }; - } + /** + * 获取当前状态 + * @returns {Object} + */ + getState() { + return { ...this.state }; + } - /** - * 更新状态 - * @param {Object} updates - 要更新的属性 - */ - setState(updates) { - const prevState = { ...this.state }; - this.state = { ...this.state, ...updates }; + /** + * 更新状态 + * @param {Object} updates - 要更新的属性 + */ + setState(updates) { + const prevState = { ...this.state }; + this.state = { ...this.state, ...updates }; - // 通知所有监听器 - this.listeners.forEach(listener => { - listener(this.state, prevState); - }); - } + // 通知所有监听器 + this.listeners.forEach((listener) => { + listener(this.state, prevState); + }); + } - /** - * 订阅状态变化 - * @param {Function} listener - 监听器函数 - * @returns {Function} - 取消订阅的函数 - */ - subscribe(listener) { - this.listeners.push(listener); + /** + * 订阅状态变化 + * @param {Function} listener - 监听器函数 + * @returns {Function} - 取消订阅的函数 + */ + subscribe(listener) { + this.listeners.push(listener); - // 返回取消订阅的函数 - return () => { - this.listeners = this.listeners.filter(l => l !== listener); - }; - } + // 返回取消订阅的函数 + return () => { + this.listeners = this.listeners.filter((l) => l !== listener); + }; + } } /** @@ -95,281 +91,277 @@ class Store { * @returns {Object} */ export function createCommentStore(config, fetchComments, submitComment) { - // 从 localStorage 加载用户信息 - const savedInfo = loadUserInfo(); + // 从 localStorage 加载用户信息 + const savedInfo = loadUserInfo(); - // 创建 store 实例 - const store = new Store({ - // 评论数据 - comments: [], - loading: true, - error: null, + // 创建 store 实例 + const store = new Store({ + // 评论数据 + comments: [], + loading: true, + error: null, - // 分页 - pagination: { - page: 1, - limit: config.pageSize || 20, - total: 0, - totalCount: 0 - }, + // 分页 + pagination: { + page: 1, + limit: config.pageSize || 20, + total: 0, + totalCount: 0, + }, - // 表单数据 - form: { - name: savedInfo.name || '', - email: savedInfo.email || '', - url: savedInfo.url || '', - content: '' - }, - formErrors: {}, - submitting: false, + // 表单数据 + form: { + name: savedInfo.name || '', + email: savedInfo.email || '', + url: savedInfo.url || '', + content: '', + }, + formErrors: {}, + submitting: false, - // 回复状态 - replyingTo: null, - replyContent: '', - replyError: null - }); + // 回复状态 + replyingTo: null, + replyContent: '', + replyError: null, + }); - // 监听用户信息变化,自动保存到 localStorage - store.subscribe((state) => { - if (state.form.name || state.form.email || state.form.url) { - saveUserInfo(state.form.name, state.form.email, state.form.url); - } - }); + // 监听用户信息变化,自动保存到 localStorage + store.subscribe((state) => { + if (state.form.name || state.form.email || state.form.url) { + saveUserInfo(state.form.name, state.form.email, state.form.url); + } + }); - /** - * 加载评论列表 - * @param {number} page - 页码 - */ - async function loadComments(page = 1) { - store.setState({ - loading: true, - error: null - }); + /** + * 加载评论列表 + * @param {number} page - 页码 + */ + async function loadComments(page = 1) { + store.setState({ + loading: true, + error: null, + }); - try { - const response = await fetchComments(page, store.getState().pagination.limit); - console.log('[Store] loadComments response:', response); - console.log('[Store] comments data:', response.data); - store.setState({ - comments: response.data, - pagination: { - page: response.pagination.page, - limit: response.pagination.limit, - total: response.pagination.total, - totalCount: response.pagination.totalCount - }, - loading: false - }); - } catch (e) { - store.setState({ - error: e instanceof Error ? e.message : '加载评论失败', - loading: false - }); - } - } + try { + const response = await fetchComments(page, store.getState().pagination.limit); + store.setState({ + comments: response.data, + pagination: { + page: response.pagination.page, + limit: response.pagination.limit, + total: response.pagination.total, + totalCount: response.pagination.totalCount, + }, + loading: false, + }); + } catch (e) { + store.setState({ + error: e instanceof Error ? e.message : '加载评论失败', + loading: false, + }); + } + } - /** - * 提交评论 - */ - async function submitNewComment() { - const state = store.getState(); - const form = state.form; + /** + * 提交评论 + */ + async function submitNewComment() { + const state = store.getState(); + const form = state.form; - // 验证表单 - const { validateCommentForm } = await import('@/utils/validator.js'); - const validation = validateCommentForm(form); - if (!validation.valid) { - store.setState({ - formErrors: validation.errors - }); - return false; - } + // 验证表单 + const { validateCommentForm } = await import('@/utils/validator.js'); + const validation = validateCommentForm(form); + if (!validation.valid) { + store.setState({ + formErrors: validation.errors, + }); + return false; + } - // 清空错误 - store.setState({ - formErrors: {}, - submitting: true, - error: null - }); + // 清空错误 + store.setState({ + formErrors: {}, + submitting: true, + error: null, + }); - try { - await submitComment({ - name: form.name, - email: form.email, - url: form.url, - content: form.content - }); + try { + await submitComment({ + name: form.name, + email: form.email, + url: form.url, + content: form.content, + }); - // 清空评论内容 - store.setState({ - form: { ...form, content: '' }, - submitting: false - }); + // 清空评论内容 + store.setState({ + form: { ...form, content: '' }, + submitting: false, + }); - // 重新加载评论 - await loadComments(state.pagination.page); - return true; - } catch (e) { - store.setState({ - error: e instanceof Error ? e.message : '提交评论失败', - submitting: false - }); - return false; - } - } + // 重新加载评论 + await loadComments(state.pagination.page); + return true; + } catch (e) { + store.setState({ + error: e instanceof Error ? e.message : '提交评论失败', + submitting: false, + }); + return false; + } + } - /** - * 提交回复 - * @param {number} parentId - 父评论 ID - */ - async function submitReply(parentId) { - const state = store.getState(); + /** + * 提交回复 + * @param {number} parentId - 父评论 ID + */ + async function submitReply(parentId) { + const state = store.getState(); - // 验证回复内容 - if (!state.replyContent.trim()) { - return false; - } + // 验证回复内容 + if (!state.replyContent.trim()) { + return false; + } - // 验证用户信息 - const { validateReplyUserInfo } = await import('@/utils/validator.js'); - const validation = validateReplyUserInfo(state.form); - if (!validation.valid) { - const errorMessages = Object.values(validation.errors).join(';'); - store.setState({ - replyError: errorMessages - }); - return false; - } + // 验证用户信息 + const { validateReplyUserInfo } = await import('@/utils/validator.js'); + const validation = validateReplyUserInfo(state.form); + if (!validation.valid) { + const errorMessages = Object.values(validation.errors).join(';'); + store.setState({ + replyError: errorMessages, + }); + return false; + } - store.setState({ - formErrors: {}, - submitting: true, - replyError: null - }); + store.setState({ + formErrors: {}, + submitting: true, + replyError: null, + }); - try { - await submitComment({ - name: state.form.name, - email: state.form.email, - url: state.form.url, - content: state.replyContent, - parentId - }); + try { + await submitComment({ + name: state.form.name, + email: state.form.email, + url: state.form.url, + content: state.replyContent, + parentId, + }); - // 清空回复内容并关闭回复框 - store.setState({ - replyContent: '', - replyingTo: null, - submitting: false - }); + // 清空回复内容并关闭回复框 + store.setState({ + replyContent: '', + replyingTo: null, + submitting: false, + }); - // 重新加载评论 - await loadComments(state.pagination.page); - return true; - } catch (e) { - store.setState({ - error: e instanceof Error ? e.message : '提交回复失败', - submitting: false - }); - return false; - } - } + // 重新加载评论 + await loadComments(state.pagination.page); + return true; + } catch (e) { + store.setState({ + error: e instanceof Error ? e.message : '提交回复失败', + submitting: false, + }); + return false; + } + } - /** - * 开始回复 - * @param {number} commentId - 评论 ID - */ - function startReply(commentId) { - console.log('[Store] startReply called with commentId:', commentId); - store.setState({ - replyingTo: commentId, - replyContent: '', - replyError: null - }); - console.log('[Store] New state:', store.getState()); - } + /** + * 开始回复 + * @param {number} commentId - 评论 ID + */ + function startReply(commentId) { + store.setState({ + replyingTo: commentId, + replyContent: '', + replyError: null, + }); + } - /** - * 取消回复 - */ - function cancelReply() { - store.setState({ - replyingTo: null, - replyContent: '', - replyError: null - }); - } + /** + * 取消回复 + */ + function cancelReply() { + store.setState({ + replyingTo: null, + replyContent: '', + replyError: null, + }); + } - /** - * 更新表单字段 - * @param {string} field - 字段名 - * @param {string} value - 值 - */ - function updateFormField(field, value) { - const form = { ...store.getState().form }; - form[field] = value; - store.setState({ form }); - } + /** + * 更新表单字段 + * @param {string} field - 字段名 + * @param {string} value - 值 + */ + function updateFormField(field, value) { + const form = { ...store.getState().form }; + form[field] = value; + store.setState({ form }); + } - /** - * 更新回复内容 - * @param {string} content - 回复内容 - */ - function updateReplyContent(content) { - store.setState({ - replyContent: content - }); - } + /** + * 更新回复内容 + * @param {string} content - 回复内容 + */ + function updateReplyContent(content) { + store.setState({ + replyContent: content, + }); + } - /** - * 清除回复错误 - */ - function clearReplyError() { - store.setState({ - replyError: null - }); - } + /** + * 清除回复错误 + */ + function clearReplyError() { + store.setState({ + replyError: null, + }); + } - /** - * 清除错误 - */ - function clearError() { - store.setState({ - error: null - }); - } + /** + * 清除错误 + */ + function clearError() { + store.setState({ + error: null, + }); + } - /** - * 切换页码 - * @param {number} page - 页码 - */ - function goToPage(page) { - const totalPages = store.getState().pagination.total; - if (page >= 1 && page <= totalPages) { - loadComments(page); - } - } + /** + * 切换页码 + * @param {number} page - 页码 + */ + function goToPage(page) { + const totalPages = store.getState().pagination.total; + if (page >= 1 && page <= totalPages) { + loadComments(page); + } + } - return { - // Store 实例 - store, + return { + // Store 实例 + store, - // 计算属性方法 - getTotalPages: () => { - const state = store.getState(); - return state.pagination.total; - }, + // 计算属性方法 + getTotalPages: () => { + const state = store.getState(); + return state.pagination.total; + }, - // 操作方法 - loadComments, - submitNewComment, - submitReply, - startReply, - cancelReply, - updateFormField, - updateReplyContent, - clearReplyError, - clearError, - goToPage - }; + // 操作方法 + loadComments, + submitNewComment, + submitReply, + startReply, + cancelReply, + updateFormField, + updateReplyContent, + clearReplyError, + clearError, + goToPage, + }; } diff --git a/widget/src/dev.js b/widget/src/dev.js index 23717d1..8434e1a 100644 --- a/widget/src/dev.js +++ b/widget/src/dev.js @@ -25,9 +25,7 @@ function loadConfigFromStorage() { if (saved) { return { ...DEFAULT_CONFIG, ...JSON.parse(saved) }; } - } catch (e) { - console.warn('[CWDComments] 读取本地存储失败:', e); - } + } catch (e) {} return DEFAULT_CONFIG; } @@ -37,9 +35,7 @@ function loadConfigFromStorage() { function saveConfigToStorage(config) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(config)); - } catch (e) { - console.warn('[CWDComments] 保存到本地存储失败:', e); - } + } catch (e) {} } /** @@ -94,10 +90,7 @@ async function initWidget() { pageSize: 20, }); widgetInstance.mount(); - console.log('[CWDComments] Widget 初始化成功', config); - } catch (error) { - console.error('[CWDComments] Widget 初始化失败:', error); - } + } catch (error) {} } /** @@ -105,7 +98,6 @@ async function initWidget() { */ function toggleTheme() { if (!widgetInstance) { - console.warn('[CWDComments] 请先初始化 widget'); return; } @@ -123,8 +115,6 @@ function toggleTheme() { const config = getConfigFromInputs(); config.theme = newTheme; saveConfigToStorage(config); - - console.log('[CWDComments] 主题已切换为:', newTheme); } /** @@ -134,10 +124,7 @@ function clearConfig() { try { localStorage.removeItem(STORAGE_KEY); populateInputs(DEFAULT_CONFIG); - console.log('[CWDComments] 配置已重置为默认值'); - } catch (e) { - console.error('[CWDComments] 重置配置失败:', e); - } + } catch (e) {} } // 将函数挂载到 window 对象,使其在 HTML 中可访问 @@ -147,8 +134,6 @@ window.clearConfig = clearConfig; // 页面加载完成后自动初始化 document.addEventListener('DOMContentLoaded', () => { - console.log('[CWDComments] 开发模式 - 页面加载完成,正在初始化...'); - // 从本地存储加载配置并填充到输入框 const savedConfig = loadConfigFromStorage(); populateInputs(savedConfig);