collabdocchat 2.5.6 → 2.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerPolicy&&(o.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?o.credentials="include":l.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(l){if(l.ep)return;l.ep=!0;const o=n(l);fetch(l.href,o)}})();const Me="http://localhost:3000/api";class Be{async login(e,n){const a=await fetch(`${Me}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:n})});if(!a.ok){const l=await a.json();throw new Error(l.message)}return await a.json()}async register(e,n){const a=await fetch(`${Me}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:n})});if(!a.ok){const l=await a.json();throw new Error(l.message)}return await a.json()}async getCurrentUser(){const e=localStorage.getItem("token"),n=await fetch(`${Me}/auth/me`,{headers:{Authorization:`Bearer ${e}`}});if(!n.ok)throw new Error("获取用户信息失败");return(await n.json()).user}logout(){localStorage.removeItem("token"),window.location.reload()}}class Ct{constructor(){this.ws=null,this.listeners=new Map}connect(e){this.ws=new WebSocket("ws://localhost:3000"),this.ws.onopen=()=>{console.log("✅ WebSocket 连接成功"),this.send({type:"auth",token:e})},this.ws.onmessage=n=>{const a=JSON.parse(n.data);this.notifyListeners(a.type,a)},this.ws.onerror=n=>{console.error("❌ WebSocket 错误:",n)},this.ws.onclose=()=>{console.log("🔌 WebSocket 连接关闭"),setTimeout(()=>this.connect(e),3e3)}}send(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}on(e,n){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(n)}off(e,n){if(this.listeners.has(e)){const a=this.listeners.get(e),l=a.indexOf(n);l>-1&&a.splice(l,1)}}notifyListeners(e,n){this.listeners.has(e)&&this.listeners.get(e).forEach(a=>a(n))}joinGroup(e){this.send({type:"join_group",groupId:e})}sendChatMessage(e,n,a){this.send({type:"chat_message",groupId:e,username:n,content:a})}syncDocument(e,n,a){this.send({type:"document_sync",documentId:e,content:n,cursorPosition:a})}respondToCall(e,n){this.send({type:"call_response",groupId:e,username:n})}sendTyping(e,n,a){this.send({type:"typing",documentId:e,username:n,isTyping:a})}sendWhiteboardDraw(e,n){this.send({type:"whiteboard_draw",groupId:e,...n})}sendWhiteboardClear(e){this.send({type:"whiteboard_clear",groupId:e})}}function zt(t){const e=document.getElementById("app"),n=new Be;e.innerHTML=`
1
+ (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const c of document.querySelectorAll('link[rel="modulepreload"]'))n(c);new MutationObserver(c=>{for(const s of c)if(s.type==="childList")for(const d of s.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&n(d)}).observe(document,{childList:!0,subtree:!0});function o(c){const s={};return c.integrity&&(s.integrity=c.integrity),c.referrerPolicy&&(s.referrerPolicy=c.referrerPolicy),c.crossOrigin==="use-credentials"?s.credentials="include":c.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function n(c){if(c.ep)return;c.ep=!0;const s=o(c);fetch(c.href,s)}})();const ze="http://localhost:3000/api";class Ce{async login(e,o){const n=await fetch(`${ze}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:o})});if(!n.ok){const c=await n.json();throw new Error(c.message)}return await n.json()}async register(e,o){const n=await fetch(`${ze}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:o})});if(!n.ok){const c=await n.json();throw new Error(c.message)}return await n.json()}async getCurrentUser(){const e=localStorage.getItem("token"),o=await fetch(`${ze}/auth/me`,{headers:{Authorization:`Bearer ${e}`}});if(!o.ok)throw new Error("获取用户信息失败");return(await o.json()).user}logout(){localStorage.removeItem("token"),window.location.reload()}}class Dt{constructor(){this.ws=null,this.listeners=new Map}connect(e){this.ws=new WebSocket("ws://localhost:3000"),this.ws.onopen=()=>{console.log("✅ WebSocket 连接成功"),this.send({type:"auth",token:e})},this.ws.onmessage=o=>{const n=JSON.parse(o.data);this.notifyListeners(n.type,n)},this.ws.onerror=o=>{console.error("❌ WebSocket 错误:",o)},this.ws.onclose=()=>{console.log("🔌 WebSocket 连接关闭"),setTimeout(()=>this.connect(e),3e3)}}send(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}on(e,o){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(o)}off(e,o){if(this.listeners.has(e)){const n=this.listeners.get(e),c=n.indexOf(o);c>-1&&n.splice(c,1)}}notifyListeners(e,o){this.listeners.has(e)&&this.listeners.get(e).forEach(n=>n(o))}joinGroup(e){this.send({type:"join_group",groupId:e})}sendChatMessage(e,o,n){this.send({type:"chat_message",groupId:e,username:o,content:n})}syncDocument(e,o,n){this.send({type:"document_sync",documentId:e,content:o,cursorPosition:n})}respondToCall(e,o){this.send({type:"call_response",groupId:e,username:o})}sendTyping(e,o,n){this.send({type:"typing",documentId:e,username:o,isTyping:n})}sendWhiteboardDraw(e,o){this.send({type:"whiteboard_draw",groupId:e,...o})}sendWhiteboardClear(e){this.send({type:"whiteboard_clear",groupId:e})}}function jt(a){const e=document.getElementById("app"),o=new Ce;e.innerHTML=`
2
2
  <div class="login-container">
3
3
  <div class="login-card">
4
4
  <div class="login-header">
@@ -48,10 +48,10 @@
48
48
  </div>
49
49
  </div>
50
50
  </div>
51
- `,document.getElementById("showRegister").addEventListener("click",()=>{document.getElementById("loginSection").classList.add("hidden"),document.getElementById("registerSection").classList.remove("hidden")}),document.getElementById("showLogin").addEventListener("click",()=>{document.getElementById("registerSection").classList.add("hidden"),document.getElementById("loginSection").classList.remove("hidden")}),document.getElementById("loginForm").addEventListener("submit",async a=>{a.preventDefault();const l=new FormData(a.target),o=l.get("username"),i=l.get("password");try{const S=await n.login(o,i);t(S.user,S.token)}catch(S){document.getElementById("loginError").textContent=S.message}}),document.getElementById("registerForm").addEventListener("submit",async a=>{a.preventDefault();const l=new FormData(a.target),o=l.get("username"),i=l.get("password");try{const S=await n.register(o,i);t(S.user,S.token)}catch(S){document.getElementById("registerError").textContent=S.message}})}const Ce="http://localhost:3000/api";class ct{constructor(){this.token=localStorage.getItem("token")}async request(e,n={}){const a=await fetch(`${Ce}${e}`,{...n,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`,...n.headers}});if(!a.ok){const l=await a.json().catch(()=>({message:"请求失败"}));throw console.error("API 错误:",{endpoint:e,status:a.status,error:l}),new Error(l.message||`请求失败: ${a.status}`)}return await a.json()}async getGroups(){return await this.request("/groups")}async getAllGroups(){return await this.request("/groups/all")}async getGroup(e){return await this.request(`/groups/${e}`)}async createGroup(e,n,a){return await this.request("/groups",{method:"POST",body:JSON.stringify({name:e,description:n,members:a})})}async joinGroup(e){return await this.request(`/groups/${e}/join`,{method:"POST"})}async leaveGroup(e){return await this.request(`/groups/${e}/leave`,{method:"POST"})}async addMember(e,n){return await this.request(`/groups/${e}/members`,{method:"POST",body:JSON.stringify({userId:n})})}async removeMember(e,n){return await this.request(`/groups/${e}/members/${n}`,{method:"DELETE"})}async setMuteAll(e,n){return await this.request(`/groups/${e}/mute/all`,{method:"POST",body:JSON.stringify({enabled:n})})}async setUserMute(e,n,a){return await this.request(`/groups/${e}/mute/users/${n}`,{method:"POST",body:JSON.stringify({muted:a})})}async getAllUsers(){return await this.request("/auth/users")}async getGroupMessages(e){return await this.request(`/groups/${e}/messages`)}async randomCall(e,n=1){return await this.request(`/groups/${e}/call`,{method:"POST",body:JSON.stringify({count:n})})}async clearGroupMessages(e){return await this.request(`/groups/${e}/messages`,{method:"DELETE",body:JSON.stringify({deleteAll:!0})})}async getTasks(e){return await this.request(`/tasks/group/${e}`)}async getMyTasks(){return await this.request("/tasks/my")}async createTask(e){return await this.request("/tasks",{method:"POST",body:JSON.stringify(e)})}async updateTaskStatus(e,n){return await this.request(`/tasks/${e}/status`,{method:"PATCH",body:JSON.stringify({status:n})})}async deleteTask(e){return await this.request(`/tasks/${e}`,{method:"DELETE"})}async getDocuments(e){return await this.request(`/documents/group/${e}`)}async getDocument(e){return await this.request(`/documents/${e}`)}async createDocument(e,n,a,l=[]){return await this.request("/documents",{method:"POST",body:JSON.stringify({title:e,content:n,groupId:a,editableMembers:l})})}async updateDocument(e,n){return await this.request(`/documents/${e}`,{method:"PATCH",body:JSON.stringify({content:n})})}async updateDocumentPermissions(e,n){return await this.request(`/documents/${e}/permissions`,{method:"PATCH",body:JSON.stringify({editableMembers:n})})}async getDocumentVersions(e){return await this.request(`/documents/${e}/versions`)}async deleteDocument(e){return await this.request(`/documents/${e}`,{method:"DELETE"})}async getAuditLogs(e={},n={}){const a=new URLSearchParams;Object.keys(e).forEach(o=>{e[o]&&a.append(o,e[o])}),Object.keys(n).forEach(o=>{n[o]&&a.append(o,n[o])});const l=a.toString();return await this.request(`/audit${l?"?"+l:""}`)}async getUserActivityStats(e,n={}){const l=new URLSearchParams(n).toString();return await this.request(`/audit/user-stats/${e}${l?"?"+l:""}`)}async getDocumentEditHistory(e,n=20){return await this.request(`/audit/document-history/${e}?limit=${n}`)}async getGroupAuditLogs(e,n={},a={}){const l=new URLSearchParams;Object.keys(n).forEach(i=>{n[i]&&l.append(i,n[i])}),Object.keys(a).forEach(i=>{a[i]&&l.append(i,a[i])});const o=l.toString();return await this.request(`/audit/group/${e}${o?"?"+o:""}`)}async getAuditSummary(e={}){const a=new URLSearchParams(e).toString();return await this.request(`/audit/stats/summary${a?"?"+a:""}`)}async getAuditLogDetail(e){return await this.request(`/audit/${e}`)}async clearAuditLogs(e={}){return await this.request("/audit",{method:"DELETE",body:JSON.stringify(e)})}async uploadFile(e,n,a=""){const l=new FormData;l.append("file",n),l.append("groupId",e),a&&l.append("description",a);const o=await fetch(`${Ce}/files/upload`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:l});if(!o.ok){const i=await o.json().catch(()=>({message:"上传失败"}));throw new Error(i.message||"上传失败")}return await o.json()}async getGroupFiles(e){return await this.request(`/files/group/${e}`)}async deleteFile(e){return await this.request(`/files/${e}`,{method:"DELETE"})}getFileDownloadUrl(e){return`${Ce}/files/${e}/download?token=${this.token}`}async createPoll(e){return await this.request("/polls",{method:"POST",body:JSON.stringify(e)})}async getGroupPolls(e){return await this.request(`/polls/group/${e}`)}async getPoll(e){return await this.request(`/polls/${e}`)}async vote(e,n){return await this.request(`/polls/${e}/vote`,{method:"POST",body:JSON.stringify({optionIndexes:n})})}async endPoll(e){return await this.request(`/polls/${e}/end`,{method:"PUT"})}async deletePoll(e){return await this.request(`/polls/${e}`,{method:"DELETE"})}}function we(t){if(typeof t!="string"||!t)throw new Error("expected a non-empty string, got: "+t)}function ze(t){if(typeof t!="number")throw new Error("expected a number, got: "+t)}const jt=1,Dt=1,me="emoji",he="keyvalue",Ge="favorites",_t="tokens",pt="tokens",At="unicode",ut="count",Pt="group",Ft="order",gt="group-order",He="eTag",Te="url",Qe="skinTone",be="readonly",We="readwrite",mt="skinUnicodes",Ot="skinUnicodes",Ht="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Nt="en";function Ut(t,e){const n=new Set,a=[];for(const l of t){const o=e(l);n.has(o)||(n.add(o),a.push(l))}return a}function et(t){return Ut(t,e=>e.unicode)}function Rt(t){function e(n,a,l){const o=a?t.createObjectStore(n,{keyPath:a}):t.createObjectStore(n);if(l)for(const[i,[S,N]]of Object.entries(l))o.createIndex(i,S,{multiEntry:N});return o}e(he),e(me,At,{[pt]:[_t,!0],[gt]:[[Pt,Ft]],[mt]:[Ot,!0]}),e(Ge,void 0,{[ut]:[""]})}const Ne={},Le={},Ie={};function yt(t,e,n){n.onerror=()=>e(n.error),n.onblocked=()=>e(new Error("IDB blocked")),n.onsuccess=()=>t(n.result)}async function qt(t){const e=await new Promise((n,a)=>{const l=indexedDB.open(t,jt);Ne[t]=l,l.onupgradeneeded=o=>{o.oldVersion<Dt&&Rt(l.result)},yt(n,a,l)});return e.onclose=()=>Ve(t),e}function Gt(t){return Le[t]||(Le[t]=qt(t)),Le[t]}function pe(t,e,n,a){return new Promise((l,o)=>{const i=t.transaction(e,n,{durability:"relaxed"}),S=typeof e=="string"?i.objectStore(e):e.map($=>i.objectStore($));let N;a(S,i,$=>{N=$}),i.oncomplete=()=>l(N),i.onerror=()=>o(i.error)})}function Ve(t){const e=Ne[t],n=e&&e.result;if(n){n.close();const a=Ie[t];if(a)for(const l of a)l()}delete Ne[t],delete Le[t],delete Ie[t]}function Wt(t){return new Promise((e,n)=>{Ve(t);const a=indexedDB.deleteDatabase(t);yt(e,n,a)})}function Vt(t,e){let n=Ie[t];n||(n=Ie[t]=[]),n.push(e)}const Kt=new Set([":D","XD",":'D","O:)",":X",":P",";P","XP",":L",":Z",":j","8D","XO","8)",":B",":O",":S",":'o","Dx","X(","D:",":C",">0)",":3","</3","<3","\\M/",":E","8#"]);function ye(t){return t.split(/[\s_]+/).map(e=>!e.match(/\w/)||Kt.has(e)?e.toLowerCase():e.replace(/[)(:,]/g,"").replace(/’/g,"'").toLowerCase()).filter(Boolean)}const Yt=2;function ht(t){return t.filter(Boolean).map(e=>e.toLowerCase()).filter(e=>e.length>=Yt)}function Jt(t){return t.map(({annotation:n,emoticon:a,group:l,order:o,shortcodes:i,skins:S,tags:N,emoji:$,version:D})=>{const H=[...new Set(ht([...(i||[]).map(ye).flat(),...(N||[]).map(ye).flat(),...ye(n),a]))].sort(),_={annotation:n,group:l,order:o,tags:N,tokens:H,unicode:$,version:D};if(a&&(_.emoticon=a),i&&(_.shortcodes=i),S){_.skinTones=[],_.skinUnicodes=[],_.skinVersions=[];for(const{tone:R,emoji:ae,version:Z}of S)_.skinTones.push(R),_.skinUnicodes.push(ae),_.skinVersions.push(Z)}return _})}function bt(t,e,n,a){t[e](n).onsuccess=l=>a&&a(l.target.result)}function ge(t,e,n){bt(t,"get",e,n)}function vt(t,e,n){bt(t,"getAll",e,n)}function Ke(t){t.commit&&t.commit()}function Xt(t,e){let n=t[0];for(let a=1;a<t.length;a++){const l=t[a];e(n)>e(l)&&(n=l)}return n}function xt(t,e){const n=Xt(t,l=>l.length),a=[];for(const l of n)t.some(o=>o.findIndex(i=>e(i)===e(l))===-1)||a.push(l);return a}async function Zt(t){return!await Ye(t,he,Te)}async function Qt(t,e,n){const[a,l]=await Promise.all([He,Te].map(o=>Ye(t,he,o)));return a===n&&l===e}async function ea(t,e){return pe(t,me,be,(a,l,o)=>{let i;const S=()=>{a.getAll(i&&IDBKeyRange.lowerBound(i,!0),50).onsuccess=N=>{const $=N.target.result;for(const D of $)if(i=D.unicode,e(D))return o(D);if($.length<50)return o();S()}};S()})}async function ft(t,e,n,a){try{const l=Jt(e);await pe(t,[me,he],We,([o,i],S)=>{let N,$,D=0;function H(){++D===2&&_()}function _(){if(!(N===a&&$===n)){o.clear();for(const R of l)o.put(R);i.put(a,He),i.put(n,Te),Ke(S)}}ge(i,He,R=>{N=R,H()}),ge(i,Te,R=>{$=R,H()})})}finally{}}async function ta(t,e){return pe(t,me,be,(n,a,l)=>{const o=IDBKeyRange.bound([e,0],[e+1,0],!1,!0);vt(n.index(gt),o,l)})}async function wt(t,e){const n=ht(ye(e));return n.length?pe(t,me,be,(a,l,o)=>{const i=[],S=()=>{i.length===n.length&&N()},N=()=>{const $=xt(i,D=>D.unicode);o($.sort((D,H)=>D.order<H.order?-1:1))};for(let $=0;$<n.length;$++){const D=n[$],H=$===n.length-1?IDBKeyRange.bound(D,D+"￿",!1,!0):IDBKeyRange.only(D);vt(a.index(pt),H,_=>{i.push(_),S()})}}):[]}async function aa(t,e){const n=await wt(t,e);return n.length?n.filter(a=>(a.shortcodes||[]).map(o=>o.toLowerCase()).includes(e.toLowerCase()))[0]||null:await ea(t,l=>(l.shortcodes||[]).includes(e.toLowerCase()))||null}async function na(t,e){return pe(t,me,be,(n,a,l)=>ge(n,e,o=>{if(o)return l(o);ge(n.index(mt),e,i=>l(i||null))}))}function Ye(t,e,n){return pe(t,e,be,(a,l,o)=>ge(a,n,o))}function sa(t,e,n,a){return pe(t,e,We,(l,o)=>{l.put(a,n),Ke(o)})}function oa(t,e){return pe(t,Ge,We,(n,a)=>ge(n,e,l=>{n.put((l||0)+1,e),Ke(a)}))}function ia(t,e,n){return n===0?[]:pe(t,[Ge,me],be,([a,l],o,i)=>{const S=[];a.index(ut).openCursor(void 0,"prev").onsuccess=N=>{const $=N.target.result;if(!$)return i(S);function D(R){if(S.push(R),S.length===n)return i(S);$.continue()}const H=$.primaryKey,_=e.byName(H);if(_)return D(_);ge(l,H,R=>{if(R)return D(R);$.continue()})}})}const ke="";function ra(t,e){const n=new Map;for(const l of t){const o=e(l);for(const i of o){let S=n;for(let $=0;$<i.length;$++){const D=i.charAt($);let H=S.get(D);H||(H=new Map,S.set(D,H)),S=H}let N=S.get(ke);N||(N=[],S.set(ke,N)),N.push(l)}}return(l,o)=>{let i=n;for(let $=0;$<l.length;$++){const D=l.charAt($),H=i.get(D);if(H)i=H;else return[]}if(o)return i.get(ke)||[];const S=[],N=[i];for(;N.length;){const D=[...N.shift().entries()].sort((H,_)=>H[0]<_[0]?-1:1);for(const[H,_]of D)H===ke?S.push(..._):N.push(_)}return S}}const da=["name","url"];function la(t){const e=t&&Array.isArray(t),n=e&&t.length&&(!t[0]||da.some(a=>!(a in t[0])));if(!e||n)throw new Error("Custom emojis are in the wrong format")}function tt(t){la(t);const e=(_,R)=>_.name.toLowerCase()<R.name.toLowerCase()?-1:1,n=t.sort(e),l=ra(t,_=>{const R=new Set;if(_.shortcodes)for(const ae of _.shortcodes)for(const Z of ye(ae))R.add(Z);return R}),o=_=>l(_,!0),i=_=>l(_,!1),S=_=>{const R=ye(_),ae=R.map((Z,se)=>(se<R.length-1?o:i)(Z));return xt(ae,Z=>Z.name).sort(e)},N=new Map,$=new Map;for(const _ of t){$.set(_.name.toLowerCase(),_);for(const R of _.shortcodes||[])N.set(R.toLowerCase(),_)}return{all:n,search:S,byShortcode:_=>N.get(_.toLowerCase()),byName:_=>$.get(_.toLowerCase())}}const ca=typeof wrappedJSObject<"u";function xe(t){if(!t)return t;if(ca&&(t=structuredClone(t)),delete t.tokens,t.skinTones){const e=t.skinTones.length;t.skins=Array(e);for(let n=0;n<e;n++)t.skins[n]={tone:t.skinTones[n],unicode:t.skinUnicodes[n],version:t.skinVersions[n]};delete t.skinTones,delete t.skinUnicodes,delete t.skinVersions}return t}function kt(t){t||console.warn("emoji-picker-element is more efficient if the dataSource server exposes an ETag header.")}const pa=["annotation","emoji","group","order","version"];function ua(t){if(!t||!Array.isArray(t)||!t[0]||typeof t[0]!="object"||pa.some(e=>!(e in t[0])))throw new Error("Emoji data is in the wrong format")}function Et(t,e){if(Math.floor(t.status/100)!==2)throw new Error("Failed to fetch: "+e+": "+t.status)}async function ga(t){const e=await fetch(t,{method:"HEAD"});Et(e,t);const n=e.headers.get("etag");return kt(n),n}async function Ue(t){const e=await fetch(t);Et(e,t);const n=e.headers.get("etag");kt(n);const a=await e.json();return ua(a),[n,a]}function ma(t){for(var e="",n=new Uint8Array(t),a=n.byteLength,l=-1;++l<a;)e+=String.fromCharCode(n[l]);return e}function ya(t){for(var e=t.length,n=new ArrayBuffer(e),a=new Uint8Array(n),l=-1;++l<e;)a[l]=t.charCodeAt(l);return n}async function Lt(t){const e=JSON.stringify(t);let n=ya(e);const a=await crypto.subtle.digest("SHA-1",n),l=ma(a);return btoa(l)}async function ha(t,e){let n,a=await ga(e);if(!a){const l=await Ue(e);a=l[0],n=l[1],a||(a=await Lt(n))}await Qt(t,e,a)||(n||(n=(await Ue(e))[1]),await ft(t,n,e,a))}async function ba(t,e){let[n,a]=await Ue(e);n||(n=await Lt(a)),await ft(t,a,e,n)}async function va(t,e){try{await ha(t,e)}catch(n){if(n.name!=="InvalidStateError")throw n}}class xa{constructor({dataSource:e=Ht,locale:n=Nt,customEmoji:a=[]}={}){this.dataSource=e,this.locale=n,this._dbName=`emoji-picker-element-${this.locale}`,this._db=void 0,this._lazyUpdate=void 0,this._custom=tt(a),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){const e=this._db=await Gt(this._dbName);Vt(this._dbName,this._clear);const n=this.dataSource;await Zt(e)?await ba(e,n):this._lazyUpdate=va(e,n)}async ready(){const e=async()=>(this._ready||(this._ready=this._init()),this._ready);await e(),this._db||await e()}async getEmojiByGroup(e){return ze(e),await this.ready(),et(await ta(this._db,e)).map(xe)}async getEmojiBySearchQuery(e){we(e),await this.ready();const n=this._custom.search(e),a=et(await wt(this._db,e)).map(xe);return[...n,...a]}async getEmojiByShortcode(e){we(e),await this.ready();const n=this._custom.byShortcode(e);return n||xe(await aa(this._db,e))}async getEmojiByUnicodeOrName(e){we(e),await this.ready();const n=this._custom.byName(e);return n||xe(await na(this._db,e))}async getPreferredSkinTone(){return await this.ready(),await Ye(this._db,he,Qe)||0}async setPreferredSkinTone(e){return ze(e),await this.ready(),sa(this._db,he,Qe,e)}async incrementFavoriteEmojiCount(e){return we(e),await this.ready(),oa(this._db,e)}async getTopFavoriteEmoji(e){return ze(e),await this.ready(),(await ia(this._db,this._custom,e)).map(xe)}set customEmoji(e){this._custom=tt(e)}get customEmoji(){return this._custom.all}async _shutdown(){await this.ready();try{await this._lazyUpdate}catch{}}_clear(){this._db=this._ready=this._lazyUpdate=void 0}async close(){await this._shutdown(),await Ve(this._dbName)}async delete(){await this._shutdown(),await Wt(this._dbName)}}const Re=[[-1,"✨","custom"],[0,"😀","smileys-emotion"],[1,"👋","people-body"],[3,"🐱","animals-nature"],[4,"🍎","food-drink"],[5,"🏠️","travel-places"],[6,"⚽","activities"],[7,"📝","objects"],[8,"⛔️","symbols"],[9,"🏁","flags"]].map(([t,e,n])=>({id:t,emoji:e,name:n})),je=Re.slice(1),fa=2,at=6,$t=typeof requestIdleCallback=="function"?requestIdleCallback:setTimeout;function nt(t){return t.unicode.includes("‍")}const wa={"🫪":17,"🫩":16,"🫨":15.1,"🫠":14,"🥲":13.1,"🥻":12.1,"🥰":11,"🤩":5,"👱‍♀️":4,"🤣":3,"👁️‍🗨️":2,"😀":1,"😐️":.7,"😃":.6},ka=1e3,Ea="🖐️",La=8,$a=["😊","😒","❤️","👍️","😍","😂","😭","☺️","😔","😩","😏","💕","🙌","😘"],Tt='"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif',Ta=(t,e)=>t<e?-1:t>e?1:0,st=(t,e)=>{const n=document.createElement("canvas");n.width=n.height=1;const a=n.getContext("2d",{willReadFrequently:!0});return a.textBaseline="top",a.font=`100px ${Tt}`,a.fillStyle=e,a.scale(.01,.01),a.fillText(t,0,0),a.getImageData(0,0,1,1).data},Ia=(t,e)=>{const n=[...t].join(","),a=[...e].join(",");return n===a&&!n.startsWith("0,0,0,")};function Sa(t){const e=st(t,"#000"),n=st(t,"#fff");return e&&n&&Ia(e,n)}function Ba(){const t=Object.entries(wa);try{for(const[e,n]of t)if(Sa(e))return n}catch{}finally{}return t[0][1]}let De;const _e=()=>(De||(De=new Promise(t=>$t(()=>t(Ba())))),De),qe=new Map,Ma="️",Ca="\uD83C",za="‍",ja=127995,Da=57339;function _a(t,e){if(e===0)return t;const n=t.indexOf(za);return n!==-1?t.substring(0,n)+String.fromCodePoint(ja+e-1)+t.substring(n):(t.endsWith(Ma)&&(t=t.substring(0,t.length-1)),t+Ca+String.fromCodePoint(Da+e-1))}function ce(t){t.preventDefault(),t.stopPropagation()}function Ae(t,e,n){return e+=t?-1:1,e<0?e=n.length-1:e>=n.length&&(e=0),e}function It(t,e){const n=new Set,a=[];for(const l of t){const o=e(l);n.has(o)||(n.add(o),a.push(l))}return a}function Aa(t,e){const n=a=>{const l={};for(const o of a)typeof o.tone=="number"&&o.version<=e&&(l[o.tone]=o.unicode);return l};return t.map(({unicode:a,skins:l,shortcodes:o,url:i,name:S,category:N,annotation:$})=>({unicode:a,name:S,shortcodes:o,url:i,category:N,annotation:$,id:a||S,skins:l&&n(l)}))}const $e=requestAnimationFrame;let Pa=typeof ResizeObserver=="function";function Fa(t,e,n){let a;Pa?(a=new ResizeObserver(n),a.observe(t)):$e(n),e.addEventListener("abort",()=>{a&&a.disconnect()})}function ot(t){{const e=document.createRange();return e.selectNode(t.firstChild),e.getBoundingClientRect().width}}let Pe;function Oa(t,e,n){let a=!0;for(const l of t){const o=n(l);if(!o)continue;const i=ot(o);typeof Pe>"u"&&(Pe=ot(e));const S=i/1.8<Pe;qe.set(l.unicode,S),S||(a=!1)}return a}function Ha(t){return It(t,e=>e)}function Na(t){t&&(t.scrollTop=0)}function fe(t,e,n){let a=t.get(e);return a||(a=n(),t.set(e,a)),a}function it(t){return""+t}function Ua(t){const e=document.createElement("template");return e.innerHTML=t,e}const Ra=new WeakMap,qa=new WeakMap,Ga=Symbol("un-keyed"),Wa="replaceChildren"in Element.prototype;function Va(t,e){Wa?t.replaceChildren(...e):(t.innerHTML="",t.append(...e))}function Ka(t,e){let n=t.firstChild,a=0;for(;n;){if(e[a]!==n)return!0;n=n.nextSibling,a++}return a!==e.length}function Ya(t,e){const{targetNode:n}=e;let{targetParentNode:a}=e,l=!1;a?l=Ka(a,t):(l=!0,e.targetNode=void 0,e.targetParentNode=a=n.parentNode),l&&Va(a,t)}function Ja(t,e){for(const n of e){const{targetNode:a,currentExpression:l,binding:{expressionIndex:o,attributeName:i,attributeValuePre:S,attributeValuePost:N}}=n,$=t[o];if(l!==$)if(n.currentExpression=$,i)if($===null)a.removeAttribute(i);else{const D=S+it($)+N;a.setAttribute(i,D)}else{let D;Array.isArray($)?Ya($,n):$ instanceof Element?(D=$,a.replaceWith(D)):a.nodeValue=it($),D&&(n.targetNode=D)}}}function Xa(t){let e="",n=!1,a=!1,l=-1;const o=new Map,i=[];let S=0;for(let $=0,D=t.length;$<D;$++){const H=t[$];if(e+=H.slice(S),$===D-1)break;for(let U=0;U<H.length;U++)switch(H.charAt(U)){case"<":{H.charAt(U+1)==="/"?i.pop():(n=!0,i.push(++l));break}case">":{n=!1,a=!1;break}case"=":{a=!0;break}}const _=i[i.length-1],R=fe(o,_,()=>[]);let ae,Z,se;if(a){const U=/(\S+)="?([^"=]*)$/.exec(H);ae=U[1],Z=U[2];const V=/^([^">]*)("?)/.exec(t[$+1]);se=V[1],e=e.slice(0,-1*U[0].length),S=V[0].length}else S=0;const le={attributeName:ae,attributeValuePre:Z,attributeValuePost:se,expressionIndex:$};R.push(le),!n&&!a&&(e+=" ")}return{template:Ua(e),elementsToBindings:o}}function rt(t,e,n){for(let a=0;a<t.length;a++){const l=t[a],o=l.attributeName?e:e.firstChild,i={binding:l,targetNode:o,targetParentNode:void 0,currentExpression:void 0};n.push(i)}}function Za(t,e){const n=[];let a;if(e.size===1&&(a=e.get(0)))rt(a,t,n);else{const l=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT);let o=t,i=-1;do{const S=e.get(++i);S&&rt(S,o,n)}while(o=l.nextNode())}return n}function Qa(t){const{template:e,elementsToBindings:n}=fe(Ra,t,()=>Xa(t)),a=e.cloneNode(!0).content.firstElementChild,l=Za(a,n);return function(i){return Ja(i,l),a}}function en(t){const e=fe(qa,t,()=>new Map);let n=Ga;function a(o,...i){const S=fe(e,o,()=>new Map);return fe(S,n,()=>Qa(o))(i)}function l(o,i,S){return o.map((N,$)=>{const D=n;n=S(N);try{return i(N,$)}finally{n=D}})}return{map:l,html:a}}function tn(t,e,n,a,l,o,i,S,N){const{labelWithSkin:$,titleForEmoji:D,unicodeWithSkin:H}=n,{html:_,map:R}=en(e);function ae(U,V,Q){return R(U,(ne,re)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?re===e.activeSearchItem:null}" aria-label="${$(ne,e.currentSkinTone)}" title="${D(ne)}" class="${"emoji"+(V&&re===e.activeSearchItem?" active":"")+(ne.unicode?"":" custom-emoji")}" id="${`${Q}-${ne.id}`}" style="${ne.unicode?null:`--custom-emoji-background: url(${JSON.stringify(ne.url)})`}">${ne.unicode?H(ne,e.currentSkinTone):""}</button>`,ne=>`${Q}-${ne.id}`)}const se=_`<section data-ref="rootElement" class="picker" aria-label="${e.i18n.regionLabel}" style="${e.pickerStyle||""}"><div class="pad-top"></div><div class="search-row"><div class="search-wrapper"><input id="search" class="search" type="search" role="combobox" enterkeyhint="search" placeholder="${e.i18n.searchLabel}" autocapitalize="none" autocomplete="off" spellcheck="true" aria-expanded="${!!(e.searchMode&&e.currentEmojis.length)}" aria-controls="search-results" aria-describedby="search-description" aria-autocomplete="list" aria-activedescendant="${e.activeSearchItemId?`emo-${e.activeSearchItemId}`:null}" data-ref="searchElement" data-on-input="onSearchInput" data-on-keydown="onSearchKeydown"><label class="sr-only" for="search">${e.i18n.searchLabel}</label> <span id="search-description" class="sr-only">${e.i18n.searchDescription}</span></div><div class="skintone-button-wrapper ${e.skinTonePickerExpandedAfterAnimation?"expanded":""}"><button id="skintone-button" class="emoji ${e.skinTonePickerExpanded?"hide-focus":""}" aria-label="${e.skinToneButtonLabel}" title="${e.skinToneButtonLabel}" aria-describedby="skintone-description" aria-haspopup="listbox" aria-expanded="${e.skinTonePickerExpanded}" aria-controls="skintone-list" data-on-click="onClickSkinToneButton">${e.skinToneButtonText||""}</button></div><span id="skintone-description" class="sr-only">${e.i18n.skinToneDescription}</span><div data-ref="skinToneDropdown" id="skintone-list" class="skintone-list hide-focus ${e.skinTonePickerExpanded?"":"hidden no-animate"}" style="transform:translateY(${e.skinTonePickerExpanded?0:"calc(-1 * var(--num-skintones) * var(--total-emoji-size))"})" role="listbox" aria-label="${e.i18n.skinTonesLabel}" aria-activedescendant="skintone-${e.activeSkinTone}" aria-hidden="${!e.skinTonePickerExpanded}" tabIndex="-1" data-on-focusout="onSkinToneOptionsFocusOut" data-on-click="onSkinToneOptionsClick" data-on-keydown="onSkinToneOptionsKeydown" data-on-keyup="onSkinToneOptionsKeyup">${R(e.skinTones,(U,V)=>_`<div id="skintone-${V}" class="emoji ${V===e.activeSkinTone?"active":""}" aria-selected="${V===e.activeSkinTone}" role="option" title="${e.i18n.skinTones[V]}" aria-label="${e.i18n.skinTones[V]}">${U}</div>`,U=>U)}</div></div><div class="nav" role="tablist" style="grid-template-columns:repeat(${e.groups.length},1fr)" aria-label="${e.i18n.categoriesLabel}" data-on-keydown="onNavKeydown" data-on-click="onNavClick">${R(e.groups,U=>_`<button role="tab" class="nav-button" aria-controls="tab-${U.id}" aria-label="${e.i18n.categories[U.name]}" aria-selected="${!e.searchMode&&e.currentGroup.id===U.id}" title="${e.i18n.categories[U.name]}" data-group-id="${U.id}"><div class="nav-emoji emoji">${U.emoji}</div></button>`,U=>U.id)}</div><div class="indicator-wrapper"><div class="indicator" style="transform:translateX(${(e.isRtl?-1:1)*e.currentGroupIndex*100}%)"></div></div><div class="message ${e.message?"":"gone"}" role="alert" aria-live="polite">${e.message||""}</div><div data-ref="tabpanelElement" class="tabpanel ${!e.databaseLoaded||e.message?"gone":""}" role="${e.searchMode?"region":"tabpanel"}" aria-label="${e.searchMode?e.i18n.searchResultsLabel:e.i18n.categories[e.currentGroup.name]}" id="${e.searchMode?null:`tab-${e.currentGroup.id}`}" tabIndex="0" data-on-click="onEmojiClick"><div data-action="calculateEmojiGridStyle">${R(e.currentEmojisWithCategories,(U,V)=>_`<div><div id="menu-label-${V}" class="category ${e.currentEmojisWithCategories.length===1&&e.currentEmojisWithCategories[0].category===""?"gone":""}" aria-hidden="true">${e.searchMode?e.i18n.searchResultsLabel:U.category?U.category:e.currentEmojisWithCategories.length>1?e.i18n.categories.custom:e.i18n.categories[e.currentGroup.name]}</div><div class="emoji-menu ${V!==0&&!e.searchMode&&e.currentGroup.id===-1?"visibility-auto":""}" style="${`--num-rows: ${Math.ceil(U.emojis.length/e.numColumns)}`}" data-action="updateOnIntersection" role="${e.searchMode?"listbox":"menu"}" aria-labelledby="menu-label-${V}" id="${e.searchMode?"search-results":null}">${ae(U.emojis,e.searchMode,"emo")}</div></div>`,U=>U.category)}</div></div><div class="favorites onscreen emoji-menu ${e.message?"gone":""}" role="menu" aria-label="${e.i18n.favoritesLabel}" data-on-click="onEmojiClick">${ae(e.currentFavorites,!1,"fav")}</div><button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">😀</button></section>`,le=(U,V)=>{for(const Q of t.querySelectorAll(`[${U}]`))V(Q,Q.getAttribute(U))};if(N){t.appendChild(se);for(const U of["click","focusout","input","keydown","keyup"])le(`data-on-${U}`,(V,Q)=>{V.addEventListener(U,a[Q])});le("data-ref",(U,V)=>{o[V]=U}),i.addEventListener("abort",()=>{t.removeChild(se)})}le("data-action",(U,V)=>{let Q=S.get(V);Q||S.set(V,Q=new WeakSet),Q.has(U)||(Q.add(U),l[V](U))})}const Se=typeof queueMicrotask=="function"?queueMicrotask:t=>Promise.resolve().then(t);function an(t){let e=!1,n;const a=new Map,l=new Set;let o;const i=()=>{if(e)return;const $=[...l];l.clear();try{for(const D of $)D()}finally{o=!1,l.size&&(o=!0,Se(i))}},S=new Proxy({},{get($,D){if(n){let H=a.get(D);H||(H=new Set,a.set(D,H)),H.add(n)}return $[D]},set($,D,H){if($[D]!==H){$[D]=H;const _=a.get(D);if(_){for(const R of _)l.add(R);o||(o=!0,Se(i))}}return!0}}),N=$=>{const D=()=>{const H=n;n=D;try{return $()}finally{n=H}};return D()};return t.addEventListener("abort",()=>{e=!0}),{state:S,createEffect:N}}function Fe(t,e,n){if(t.length!==e.length)return!1;for(let a=0;a<t.length;a++)if(!n(t[a],e[a]))return!1;return!0}const dt=new WeakMap;function nn(t,e,n){{const a=t.closest(".tabpanel");let l=dt.get(a);l||(l=new IntersectionObserver(n,{root:a,rootMargin:"50% 0px 50% 0px",threshold:0}),dt.set(a,l),e.addEventListener("abort",()=>{l.disconnect()})),l.observe(t)}}const Oe=[],{assign:Ee}=Object;function sn(t,e){const n={},a=new AbortController,l=a.signal,{state:o,createEffect:i}=an(l),S=new Map;Ee(o,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),Ee(o,e),Ee(o,{initialLoad:!0,currentEmojis:[],currentEmojisWithCategories:[],rawSearchText:"",searchText:"",searchMode:!1,activeSearchItem:-1,message:void 0,skinTonePickerExpanded:!1,skinTonePickerExpandedAfterAnimation:!1,currentSkinTone:0,activeSkinTone:0,skinToneButtonText:void 0,pickerStyle:void 0,skinToneButtonLabel:"",skinTones:[],currentFavorites:[],defaultFavoriteEmojis:void 0,numColumns:La,isRtl:!1,currentGroupIndex:0,groups:je,databaseLoaded:!1,activeSearchItemId:void 0}),i(()=>{o.currentGroup!==o.groups[o.currentGroupIndex]&&(o.currentGroup=o.groups[o.currentGroupIndex])});const N=s=>{t.getElementById(s).focus()},$=s=>t.getElementById(`emo-${s.id}`),D=(s,c)=>{n.rootElement.dispatchEvent(new CustomEvent(s,{detail:c,bubbles:!0,composed:!0}))},H=(s,c)=>s.id===c.id,_=(s,c)=>{const{category:v,emojis:w}=s,{category:P,emojis:p}=c;return v!==P?!1:Fe(w,p,H)},R=s=>{Fe(o.currentEmojis,s,H)||(o.currentEmojis=s)},ae=s=>{o.searchMode!==s&&(o.searchMode=s)},Z=s=>{Fe(o.currentEmojisWithCategories,s,_)||(o.currentEmojisWithCategories=s)},se=(s,c)=>c&&s.skins&&s.skins[c]||s.unicode,V={labelWithSkin:(s,c)=>Ha([s.name||se(s,c),s.annotation,...s.shortcodes||Oe].filter(Boolean)).join(", "),titleForEmoji:s=>s.annotation||(s.shortcodes||Oe).join(", "),unicodeWithSkin:se},Q={onClickSkinToneButton:B,onEmojiClick:u,onNavClick:oe,onNavKeydown:K,onSearchKeydown:O,onSkinToneOptionsClick:k,onSkinToneOptionsFocusOut:r,onSkinToneOptionsKeydown:y,onSkinToneOptionsKeyup:g,onSearchInput:d},ne={calculateEmojiGridStyle:E,updateOnIntersection:F};let re=!0;i(()=>{tn(t,o,V,Q,ne,n,l,S,re),re=!1}),o.emojiVersion||_e().then(s=>{s||(o.message=o.i18n.emojiUnsupportedMessage)}),i(()=>{async function s(){let c=!1;const v=setTimeout(()=>{c=!0,o.message=o.i18n.loadingMessage},ka);try{await o.database.ready(),o.databaseLoaded=!0}catch(w){console.error(w),o.message=o.i18n.networkErrorMessage}finally{clearTimeout(v),c&&(c=!1,o.message="")}}o.database&&s()}),i(()=>{o.pickerStyle=`
52
- --num-groups: ${o.groups.length};
53
- --indicator-opacity: ${o.searchMode?0:1};
54
- --num-skintones: ${at};`}),i(()=>{o.customEmoji&&o.database&&T()}),i(()=>{o.customEmoji&&o.customEmoji.length?o.groups!==Re&&(o.groups=Re):o.groups!==je&&(o.currentGroupIndex&&o.currentGroupIndex--,o.groups=je)}),i(()=>{async function s(){o.databaseLoaded&&(o.currentSkinTone=await o.database.getPreferredSkinTone())}s()}),i(()=>{o.skinTones=Array(at).fill().map((s,c)=>_a(o.skinToneEmoji,c))}),i(()=>{o.skinToneButtonText=o.skinTones[o.currentSkinTone]}),i(()=>{o.skinToneButtonLabel=o.i18n.skinToneLabel.replace("{skinTone}",o.i18n.skinTones[o.currentSkinTone])}),i(()=>{async function s(){const{database:c}=o,v=(await Promise.all($a.map(w=>c.getEmojiByUnicodeOrName(w)))).filter(Boolean);o.defaultFavoriteEmojis=v}o.databaseLoaded&&s()});function T(){const{customEmoji:s,database:c}=o,v=s||Oe;c.customEmoji!==v&&(c.customEmoji=v)}i(()=>{async function s(){T();const{database:c,defaultFavoriteEmojis:v,numColumns:w}=o,P=await c.getTopFavoriteEmoji(w),p=await I(It([...P,...v],L=>L.unicode||L.name).slice(0,w));o.currentFavorites=p}o.databaseLoaded&&o.defaultFavoriteEmojis&&s()});function E(s){Fa(s,l,()=>{{const c=getComputedStyle(n.rootElement),v=parseInt(c.getPropertyValue("--num-columns"),10),w=c.getPropertyValue("direction")==="rtl";o.numColumns=v,o.isRtl=w}})}function F(s){nn(s,l,c=>{for(const{target:v,isIntersecting:w}of c)v.classList.toggle("onscreen",w)})}i(()=>{async function s(){const{searchText:c,currentGroup:v,databaseLoaded:w,customEmoji:P}=o;if(!w)o.currentEmojis=[],o.searchMode=!1;else if(c.length>=fa){const p=await W(c);o.searchText===c&&(R(p),ae(!0))}else{const{id:p}=v;if(p!==-1||P&&P.length){const L=await z(p);o.currentGroup.id===p&&(R(L),ae(!1))}}}s()});const x=()=>{$e(()=>Na(n.tabpanelElement))};i(()=>{const{currentEmojis:s,emojiVersion:c}=o,v=s.filter(w=>w.unicode).filter(w=>nt(w)&&!qe.has(w.unicode));if(!c&&v.length)R(s),$e(()=>f(v));else{const w=c?s:s.filter(M);R(w),x()}});function f(s){Oa(s,n.baselineEmoji,$)?x():o.currentEmojis=[...o.currentEmojis]}function M(s){return!s.unicode||!nt(s)||qe.get(s.unicode)}async function A(s){const c=o.emojiVersion||await _e();return s.filter(({version:v})=>!v||v<=c)}async function I(s){return Aa(s,o.emojiVersion||await _e())}async function z(s){const c=s===-1?o.customEmoji:await o.database.getEmojiByGroup(s);return I(await A(c))}async function W(s){return I(await A(await o.database.getEmojiBySearchQuery(s)))}i(()=>{}),i(()=>{function s(){const{searchMode:v,currentEmojis:w}=o;if(v)return[{category:"",emojis:w}];const P=new Map;for(const p of w){const L=p.category||"";let j=P.get(L);j||(j=[],P.set(L,j)),j.push(p)}return[...P.entries()].map(([p,L])=>({category:p,emojis:L})).sort((p,L)=>o.customCategorySorting(p.category,L.category))}const c=s();Z(c)}),i(()=>{o.activeSearchItemId=o.activeSearchItem!==-1&&o.currentEmojis[o.activeSearchItem].id}),i(()=>{const{rawSearchText:s}=o;$t(()=>{o.searchText=(s||"").trim(),o.activeSearchItem=-1})});function O(s){if(!o.searchMode||!o.currentEmojis.length)return;const c=v=>{ce(s),o.activeSearchItem=Ae(v,o.activeSearchItem,o.currentEmojis)};switch(s.key){case"ArrowDown":return c(!1);case"ArrowUp":return c(!0);case"Enter":if(o.activeSearchItem===-1)o.activeSearchItem=0;else return ce(s),m(o.currentEmojis[o.activeSearchItem].id)}}function oe(s){const{target:c}=s,v=c.closest(".nav-button");if(!v)return;const w=parseInt(v.dataset.groupId,10);n.searchElement.value="",o.rawSearchText="",o.searchText="",o.activeSearchItem=-1,o.currentGroupIndex=o.groups.findIndex(P=>P.id===w)}function K(s){const{target:c,key:v}=s,w=P=>{P&&(ce(s),P.focus())};switch(v){case"ArrowLeft":return w(c.previousElementSibling);case"ArrowRight":return w(c.nextElementSibling);case"Home":return w(c.parentElement.firstElementChild);case"End":return w(c.parentElement.lastElementChild)}}async function h(s){const c=await o.database.getEmojiByUnicodeOrName(s),v=[...o.currentEmojis,...o.currentFavorites].find(P=>P.id===s),w=v.unicode&&se(v,o.currentSkinTone);return await o.database.incrementFavoriteEmojiCount(s),{emoji:c,skinTone:o.currentSkinTone,...w&&{unicode:w},...v.name&&{name:v.name}}}async function m(s){const c=h(s);D("emoji-click-sync",c),D("emoji-click",await c)}function u(s){const{target:c}=s;if(!c.classList.contains("emoji"))return;ce(s);const v=c.id.substring(4);m(v)}function b(s){o.currentSkinTone=s,o.skinTonePickerExpanded=!1,N("skintone-button"),D("skin-tone-change",{skinTone:s}),o.database.setPreferredSkinTone(s)}function k(s){const{target:{id:c}}=s,v=c&&c.match(/^skintone-(\d)/);if(!v)return;ce(s);const w=parseInt(v[1],10);b(w)}function B(s){o.skinTonePickerExpanded=!o.skinTonePickerExpanded,o.activeSkinTone=o.currentSkinTone,o.skinTonePickerExpanded&&(ce(s),$e(()=>N("skintone-list")))}i(()=>{o.skinTonePickerExpanded?n.skinToneDropdown.addEventListener("transitionend",()=>{o.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):o.skinTonePickerExpandedAfterAnimation=!1});function y(s){if(!o.skinTonePickerExpanded)return;const c=async v=>{ce(s),o.activeSkinTone=v};switch(s.key){case"ArrowUp":return c(Ae(!0,o.activeSkinTone,o.skinTones));case"ArrowDown":return c(Ae(!1,o.activeSkinTone,o.skinTones));case"Home":return c(0);case"End":return c(o.skinTones.length-1);case"Enter":return ce(s),b(o.activeSkinTone);case"Escape":return ce(s),o.skinTonePickerExpanded=!1,N("skintone-button")}}function g(s){if(o.skinTonePickerExpanded)switch(s.key){case" ":return ce(s),b(o.activeSkinTone)}}async function r(s){const{relatedTarget:c}=s;(!c||c.id!=="skintone-list")&&(o.skinTonePickerExpanded=!1)}function d(s){o.rawSearchText=s.target.value}return{$set(s){Ee(o,s)},$destroy(){a.abort()}}}const on="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",rn="en";var dn={categoriesLabel:"Categories",emojiUnsupportedMessage:"Your browser does not support color emoji.",favoritesLabel:"Favorites",loadingMessage:"Loading…",networkErrorMessage:"Could not load emoji.",regionLabel:"Emoji picker",searchDescription:"When search results are available, press up or down to select and enter to choose.",searchLabel:"Search",searchResultsLabel:"Search results",skinToneDescription:"When expanded, press up or down to select and enter to choose.",skinToneLabel:"Choose a skin tone (currently {skinTone})",skinTonesLabel:"Skin tones",skinTones:["Default","Light","Medium-Light","Medium","Medium-Dark","Dark"],categories:{custom:"Custom","smileys-emotion":"Smileys and emoticons","people-body":"People and body","animals-nature":"Animals and nature","food-drink":"Food and drink","travel-places":"Travel and places",activities:"Activities",objects:"Objects",symbols:"Symbols",flags:"Flags"}},ln=':host{--emoji-size:1.375rem;--emoji-padding:0.5rem;--category-emoji-size:var(--emoji-size);--category-emoji-padding:var(--emoji-padding);--indicator-height:3px;--input-border-radius:0.5rem;--input-border-size:1px;--input-font-size:1rem;--input-line-height:1.5;--input-padding:0.25rem;--num-columns:8;--outline-size:2px;--border-size:1px;--border-radius:0;--skintone-border-radius:1rem;--category-font-size:1rem;display:flex;width:min-content;height:400px}:host,:host(.light){color-scheme:light;--background:#fff;--border-color:#e0e0e0;--indicator-color:#385ac1;--input-border-color:#999;--input-font-color:#111;--input-placeholder-color:#999;--outline-color:#999;--category-font-color:#111;--button-active-background:#e6e6e6;--button-hover-background:#d9d9d9}:host(.dark){color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}@media (prefers-color-scheme:dark){:host{color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}}:host([hidden]){display:none}button{margin:0;padding:0;border:0;background:0 0;box-shadow:none;-webkit-tap-highlight-color:transparent}button::-moz-focus-inner{border:0}input{padding:0;margin:0;line-height:1.15;font-family:inherit}input[type=search]{-webkit-appearance:none}:focus{outline:var(--outline-color) solid var(--outline-size);outline-offset:calc(-1*var(--outline-size))}:host([data-js-focus-visible]) :focus:not([data-focus-visible-added]){outline:0}:focus:not(:focus-visible){outline:0}.hide-focus{outline:0}*{box-sizing:border-box}.picker{contain:content;display:flex;flex-direction:column;background:var(--background);border:var(--border-size) solid var(--border-color);border-radius:var(--border-radius);width:100%;height:100%;overflow:hidden;--total-emoji-size:calc(var(--emoji-size) + (2 * var(--emoji-padding)));--total-category-emoji-size:calc(var(--category-emoji-size) + (2 * var(--category-emoji-padding)))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.hidden{opacity:0;pointer-events:none}.abs-pos{position:absolute;left:0;top:0}.gone{display:none!important}.skintone-button-wrapper,.skintone-list{background:var(--background);z-index:3}.skintone-button-wrapper.expanded{z-index:1}.skintone-list{position:absolute;inset-inline-end:0;top:0;z-index:2;overflow:visible;border-bottom:var(--border-size) solid var(--border-color);border-radius:0 0 var(--skintone-border-radius) var(--skintone-border-radius);will-change:transform;transition:transform .2s ease-in-out;transform-origin:center 0}@media (prefers-reduced-motion:reduce){.skintone-list{transition-duration:.001s}}@supports not (inset-inline-end:0){.skintone-list{right:0}}.skintone-list.no-animate{transition:none}.tabpanel{overflow-y:auto;scrollbar-gutter:stable;-webkit-overflow-scrolling:touch;will-change:transform;min-height:0;flex:1;contain:content}.emoji-menu{display:grid;grid-template-columns:repeat(var(--num-columns),var(--total-emoji-size));justify-content:space-around;align-items:flex-start;width:100%}.emoji-menu.visibility-auto{content-visibility:auto;contain-intrinsic-size:calc(var(--num-columns)*var(--total-emoji-size)) calc(var(--num-rows)*var(--total-emoji-size))}.category{padding:var(--emoji-padding);font-size:var(--category-font-size);color:var(--category-font-color)}.emoji,button.emoji{font-size:var(--emoji-size);display:flex;align-items:center;justify-content:center;border-radius:100%;height:var(--total-emoji-size);width:var(--total-emoji-size);line-height:1;overflow:hidden;font-family:var(--emoji-font-family);cursor:pointer}@media (hover:hover) and (pointer:fine){.emoji:hover,button.emoji:hover{background:var(--button-hover-background)}}.emoji.active,.emoji:active,button.emoji.active,button.emoji:active{background:var(--button-active-background)}.onscreen .custom-emoji::after{content:"";width:var(--emoji-size);height:var(--emoji-size);background-repeat:no-repeat;background-position:center center;background-size:contain;background-image:var(--custom-emoji-background)}.nav,.nav-button{align-items:center}.nav{display:grid;justify-content:space-between;contain:content}.nav-button{display:flex;justify-content:center}.nav-emoji{font-size:var(--category-emoji-size);width:var(--total-category-emoji-size);height:var(--total-category-emoji-size)}.indicator-wrapper{display:flex;border-bottom:1px solid var(--border-color)}.indicator{width:calc(100%/var(--num-groups));height:var(--indicator-height);opacity:var(--indicator-opacity);background-color:var(--indicator-color);will-change:transform,opacity;transition:opacity .1s linear,transform .25s ease-in-out}@media (prefers-reduced-motion:reduce){.indicator{will-change:opacity;transition:opacity .1s linear}}.pad-top,input.search{background:var(--background);width:100%}.pad-top{height:var(--emoji-padding);z-index:3}.search-row{display:flex;align-items:center;position:relative;padding-inline-start:var(--emoji-padding);padding-bottom:var(--emoji-padding)}.search-wrapper{flex:1;min-width:0}input.search{padding:var(--input-padding);border-radius:var(--input-border-radius);border:var(--input-border-size) solid var(--input-border-color);color:var(--input-font-color);font-size:var(--input-font-size);line-height:var(--input-line-height)}input.search::placeholder{color:var(--input-placeholder-color)}.favorites{overflow-y:auto;scrollbar-gutter:stable;display:flex;flex-direction:row;border-top:var(--border-size) solid var(--border-color);contain:content}.message{padding:var(--emoji-padding)}';const St=["customEmoji","customCategorySorting","database","dataSource","i18n","locale","skinToneEmoji","emojiVersion"],cn=`:host{--emoji-font-family:${Tt}}`;class Je extends HTMLElement{constructor(e){super(),this.attachShadow({mode:"open"});const n=document.createElement("style");n.textContent=ln+cn,this.shadowRoot.appendChild(n),this._ctx={locale:rn,dataSource:on,skinToneEmoji:Ea,customCategorySorting:Ta,customEmoji:null,i18n:dn,emojiVersion:null,...e};for(const a of St)a!=="database"&&Object.prototype.hasOwnProperty.call(this,a)&&(this._ctx[a]=this[a],delete this[a]);this._dbFlush()}connectedCallback(){lt(this),this._cmp||(this._cmp=sn(this.shadowRoot,this._ctx))}disconnectedCallback(){lt(this),Se(()=>{if(!this.isConnected&&this._cmp){this._cmp.$destroy(),this._cmp=void 0;const{database:e}=this._ctx;e.close().catch(n=>console.error(n))}})}static get observedAttributes(){return["locale","data-source","skin-tone-emoji","emoji-version"]}attributeChangedCallback(e,n,a){this._set(e.replace(/-([a-z])/g,(l,o)=>o.toUpperCase()),e==="emoji-version"?parseFloat(a):a)}_set(e,n){this._ctx[e]=n,this._cmp&&this._cmp.$set({[e]:n}),["locale","dataSource"].includes(e)&&this._dbFlush()}_dbCreate(){const{locale:e,dataSource:n,database:a}=this._ctx;(!a||a.locale!==e||a.dataSource!==n)&&this._set("database",new xa({locale:e,dataSource:n}))}_dbFlush(){Se(()=>this._dbCreate())}}const Bt={};for(const t of St)Bt[t]={get(){return t==="database"&&this._dbCreate(),this._ctx[t]},set(e){if(t==="database")throw new Error("database is read-only");this._set(t,e)}};Object.defineProperties(Je.prototype,Bt);function lt(t){t instanceof Je||Object.setPrototypeOf(t,customElements.get(t.tagName.toLowerCase()).prototype)}customElements.get("emoji-picker")||customElements.define("emoji-picker",Je);function pn(t,e){const n=document.getElementById("app"),a=new ct,l=new Be,o=t.id||t._id;let i=null,S=[];const N=localStorage.getItem("currentTheme")||"dark";un(N),n.innerHTML=`
51
+ `,document.getElementById("showRegister").addEventListener("click",()=>{document.getElementById("loginSection").classList.add("hidden"),document.getElementById("registerSection").classList.remove("hidden")}),document.getElementById("showLogin").addEventListener("click",()=>{document.getElementById("registerSection").classList.add("hidden"),document.getElementById("loginSection").classList.remove("hidden")}),document.getElementById("loginForm").addEventListener("submit",async n=>{n.preventDefault();const c=new FormData(n.target),s=c.get("username"),d=c.get("password");try{const I=await o.login(s,d);a(I.user,I.token)}catch(I){document.getElementById("loginError").textContent=I.message}}),document.getElementById("registerForm").addEventListener("submit",async n=>{n.preventDefault();const c=new FormData(n.target),s=c.get("username"),d=c.get("password");try{const I=await o.register(s,d);a(I.user,I.token)}catch(I){document.getElementById("registerError").textContent=I.message}})}const De="http://localhost:3000/api";class ut{constructor(){this.token=localStorage.getItem("token")}async request(e,o={}){const n=await fetch(`${De}${e}`,{...o,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`,...o.headers}});if(!n.ok){const c=await n.json().catch(()=>({message:"请求失败"}));throw console.error("API 错误:",{endpoint:e,status:n.status,error:c}),new Error(c.message||`请求失败: ${n.status}`)}return await n.json()}async getGroups(){return await this.request("/groups")}async getAllGroups(){return await this.request("/groups/all")}async getGroup(e){return await this.request(`/groups/${e}`)}async createGroup(e,o,n){return await this.request("/groups",{method:"POST",body:JSON.stringify({name:e,description:o,members:n})})}async joinGroup(e){return await this.request(`/groups/${e}/join`,{method:"POST"})}async leaveGroup(e){return await this.request(`/groups/${e}/leave`,{method:"POST"})}async addMember(e,o){return await this.request(`/groups/${e}/members`,{method:"POST",body:JSON.stringify({userId:o})})}async removeMember(e,o){return await this.request(`/groups/${e}/members/${o}`,{method:"DELETE"})}async setMuteAll(e,o){return await this.request(`/groups/${e}/mute/all`,{method:"POST",body:JSON.stringify({enabled:o})})}async setUserMute(e,o,n){return await this.request(`/groups/${e}/mute/users/${o}`,{method:"POST",body:JSON.stringify({muted:n})})}async getAllUsers(){return await this.request("/auth/users")}async getGroupMessages(e){return await this.request(`/groups/${e}/messages`)}async randomCall(e,o=1){return await this.request(`/groups/${e}/call`,{method:"POST",body:JSON.stringify({count:o})})}async clearGroupMessages(e){return await this.request(`/groups/${e}/messages`,{method:"DELETE",body:JSON.stringify({deleteAll:!0})})}async getTasks(e){return await this.request(`/tasks/group/${e}`)}async getMyTasks(){return await this.request("/tasks/my")}async createTask(e){return await this.request("/tasks",{method:"POST",body:JSON.stringify(e)})}async updateTaskStatus(e,o){return await this.request(`/tasks/${e}/status`,{method:"PATCH",body:JSON.stringify({status:o})})}async deleteTask(e){return await this.request(`/tasks/${e}`,{method:"DELETE"})}async getDocuments(e){return await this.request(`/documents/group/${e}`)}async getDocument(e){return await this.request(`/documents/${e}`)}async createDocument(e,o,n,c=[]){return await this.request("/documents",{method:"POST",body:JSON.stringify({title:e,content:o,groupId:n,editableMembers:c})})}async updateDocument(e,o){return await this.request(`/documents/${e}`,{method:"PATCH",body:JSON.stringify({content:o})})}async updateDocumentPermissions(e,o){return await this.request(`/documents/${e}/permissions`,{method:"PATCH",body:JSON.stringify({editableMembers:o})})}async getDocumentVersions(e){return await this.request(`/documents/${e}/versions`)}async deleteDocument(e){return await this.request(`/documents/${e}`,{method:"DELETE"})}async getAuditLogs(e={},o={}){const n=new URLSearchParams;Object.keys(e).forEach(s=>{e[s]&&n.append(s,e[s])}),Object.keys(o).forEach(s=>{o[s]&&n.append(s,o[s])});const c=n.toString();return await this.request(`/audit${c?"?"+c:""}`)}async getUserActivityStats(e,o={}){const c=new URLSearchParams(o).toString();return await this.request(`/audit/user-stats/${e}${c?"?"+c:""}`)}async getDocumentEditHistory(e,o=20){return await this.request(`/audit/document-history/${e}?limit=${o}`)}async getGroupAuditLogs(e,o={},n={}){const c=new URLSearchParams;Object.keys(o).forEach(d=>{o[d]&&c.append(d,o[d])}),Object.keys(n).forEach(d=>{n[d]&&c.append(d,n[d])});const s=c.toString();return await this.request(`/audit/group/${e}${s?"?"+s:""}`)}async getAuditSummary(e={}){const n=new URLSearchParams(e).toString();return await this.request(`/audit/stats/summary${n?"?"+n:""}`)}async getAuditLogDetail(e){return await this.request(`/audit/${e}`)}async clearAuditLogs(e={}){return await this.request("/audit",{method:"DELETE",body:JSON.stringify(e)})}async uploadFile(e,o,n=""){const c=new FormData;c.append("file",o),c.append("groupId",e),n&&c.append("description",n);const s=await fetch(`${De}/files/upload`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:c});if(!s.ok){const d=await s.json().catch(()=>({message:"上传失败"}));throw new Error(d.message||"上传失败")}return await s.json()}async getGroupFiles(e){return await this.request(`/files/group/${e}`)}async deleteFile(e){return await this.request(`/files/${e}`,{method:"DELETE"})}getFileDownloadUrl(e){return`${De}/files/${e}/download?token=${this.token}`}async createPoll(e){return await this.request("/polls",{method:"POST",body:JSON.stringify(e)})}async getGroupPolls(e){return await this.request(`/polls/group/${e}`)}async getPoll(e){return await this.request(`/polls/${e}`)}async vote(e,o){return await this.request(`/polls/${e}/vote`,{method:"POST",body:JSON.stringify({optionIndexes:o})})}async endPoll(e){return await this.request(`/polls/${e}/end`,{method:"PUT"})}async deletePoll(e){return await this.request(`/polls/${e}`,{method:"DELETE"})}}function Ee(a){if(typeof a!="string"||!a)throw new Error("expected a non-empty string, got: "+a)}function je(a){if(typeof a!="number")throw new Error("expected a number, got: "+a)}const _t=1,At=1,he="emoji",ve="keyvalue",Ve="favorites",Pt="tokens",mt="tokens",Ot="unicode",gt="count",Ft="group",Ht="order",yt="group-order",qe="eTag",Se="url",tt="skinTone",xe="readonly",Ye="readwrite",ht="skinUnicodes",Nt="skinUnicodes",qt="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Ut="en";function Rt(a,e){const o=new Set,n=[];for(const c of a){const s=e(c);o.has(s)||(o.add(s),n.push(c))}return n}function at(a){return Rt(a,e=>e.unicode)}function Gt(a){function e(o,n,c){const s=n?a.createObjectStore(o,{keyPath:n}):a.createObjectStore(o);if(c)for(const[d,[I,N]]of Object.entries(c))s.createIndex(d,I,{multiEntry:N});return s}e(ve),e(he,Ot,{[mt]:[Pt,!0],[yt]:[[Ft,Ht]],[ht]:[Nt,!0]}),e(Ve,void 0,{[gt]:[""]})}const Ue={},Ie={},Be={};function bt(a,e,o){o.onerror=()=>e(o.error),o.onblocked=()=>e(new Error("IDB blocked")),o.onsuccess=()=>a(o.result)}async function Wt(a){const e=await new Promise((o,n)=>{const c=indexedDB.open(a,_t);Ue[a]=c,c.onupgradeneeded=s=>{s.oldVersion<At&&Gt(c.result)},bt(o,n,c)});return e.onclose=()=>Ke(a),e}function Vt(a){return Ie[a]||(Ie[a]=Wt(a)),Ie[a]}function ge(a,e,o,n){return new Promise((c,s)=>{const d=a.transaction(e,o,{durability:"relaxed"}),I=typeof e=="string"?d.objectStore(e):e.map(E=>d.objectStore(E));let N;n(I,d,E=>{N=E}),d.oncomplete=()=>c(N),d.onerror=()=>s(d.error)})}function Ke(a){const e=Ue[a],o=e&&e.result;if(o){o.close();const n=Be[a];if(n)for(const c of n)c()}delete Ue[a],delete Ie[a],delete Be[a]}function Yt(a){return new Promise((e,o)=>{Ke(a);const n=indexedDB.deleteDatabase(a);bt(e,o,n)})}function Kt(a,e){let o=Be[a];o||(o=Be[a]=[]),o.push(e)}const Jt=new Set([":D","XD",":'D","O:)",":X",":P",";P","XP",":L",":Z",":j","8D","XO","8)",":B",":O",":S",":'o","Dx","X(","D:",":C",">0)",":3","</3","<3","\\M/",":E","8#"]);function be(a){return a.split(/[\s_]+/).map(e=>!e.match(/\w/)||Jt.has(e)?e.toLowerCase():e.replace(/[)(:,]/g,"").replace(/’/g,"'").toLowerCase()).filter(Boolean)}const Xt=2;function vt(a){return a.filter(Boolean).map(e=>e.toLowerCase()).filter(e=>e.length>=Xt)}function Zt(a){return a.map(({annotation:o,emoticon:n,group:c,order:s,shortcodes:d,skins:I,tags:N,emoji:E,version:j})=>{const F=[...new Set(vt([...(d||[]).map(be).flat(),...(N||[]).map(be).flat(),...be(o),n]))].sort(),_={annotation:o,group:c,order:s,tags:N,tokens:F,unicode:E,version:j};if(n&&(_.emoticon=n),d&&(_.shortcodes=d),I){_.skinTones=[],_.skinUnicodes=[],_.skinVersions=[];for(const{tone:U,emoji:ne,version:ee}of I)_.skinTones.push(U),_.skinUnicodes.push(ne),_.skinVersions.push(ee)}return _})}function xt(a,e,o,n){a[e](o).onsuccess=c=>n&&n(c.target.result)}function ye(a,e,o){xt(a,"get",e,o)}function ft(a,e,o){xt(a,"getAll",e,o)}function Je(a){a.commit&&a.commit()}function Qt(a,e){let o=a[0];for(let n=1;n<a.length;n++){const c=a[n];e(o)>e(c)&&(o=c)}return o}function wt(a,e){const o=Qt(a,c=>c.length),n=[];for(const c of o)a.some(s=>s.findIndex(d=>e(d)===e(c))===-1)||n.push(c);return n}async function ea(a){return!await Xe(a,ve,Se)}async function ta(a,e,o){const[n,c]=await Promise.all([qe,Se].map(s=>Xe(a,ve,s)));return n===o&&c===e}async function aa(a,e){return ge(a,he,xe,(n,c,s)=>{let d;const I=()=>{n.getAll(d&&IDBKeyRange.lowerBound(d,!0),50).onsuccess=N=>{const E=N.target.result;for(const j of E)if(d=j.unicode,e(j))return s(j);if(E.length<50)return s();I()}};I()})}async function kt(a,e,o,n){try{const c=Zt(e);await ge(a,[he,ve],Ye,([s,d],I)=>{let N,E,j=0;function F(){++j===2&&_()}function _(){if(!(N===n&&E===o)){s.clear();for(const U of c)s.put(U);d.put(n,qe),d.put(o,Se),Je(I)}}ye(d,qe,U=>{N=U,F()}),ye(d,Se,U=>{E=U,F()})})}finally{}}async function na(a,e){return ge(a,he,xe,(o,n,c)=>{const s=IDBKeyRange.bound([e,0],[e+1,0],!1,!0);ft(o.index(yt),s,c)})}async function Et(a,e){const o=vt(be(e));return o.length?ge(a,he,xe,(n,c,s)=>{const d=[],I=()=>{d.length===o.length&&N()},N=()=>{const E=wt(d,j=>j.unicode);s(E.sort((j,F)=>j.order<F.order?-1:1))};for(let E=0;E<o.length;E++){const j=o[E],F=E===o.length-1?IDBKeyRange.bound(j,j+"￿",!1,!0):IDBKeyRange.only(j);ft(n.index(mt),F,_=>{d.push(_),I()})}}):[]}async function oa(a,e){const o=await Et(a,e);return o.length?o.filter(n=>(n.shortcodes||[]).map(s=>s.toLowerCase()).includes(e.toLowerCase()))[0]||null:await aa(a,c=>(c.shortcodes||[]).includes(e.toLowerCase()))||null}async function sa(a,e){return ge(a,he,xe,(o,n,c)=>ye(o,e,s=>{if(s)return c(s);ye(o.index(ht),e,d=>c(d||null))}))}function Xe(a,e,o){return ge(a,e,xe,(n,c,s)=>ye(n,o,s))}function ia(a,e,o,n){return ge(a,e,Ye,(c,s)=>{c.put(n,o),Je(s)})}function ra(a,e){return ge(a,Ve,Ye,(o,n)=>ye(o,e,c=>{o.put((c||0)+1,e),Je(n)}))}function da(a,e,o){return o===0?[]:ge(a,[Ve,he],xe,([n,c],s,d)=>{const I=[];n.index(gt).openCursor(void 0,"prev").onsuccess=N=>{const E=N.target.result;if(!E)return d(I);function j(U){if(I.push(U),I.length===o)return d(I);E.continue()}const F=E.primaryKey,_=e.byName(F);if(_)return j(_);ye(c,F,U=>{if(U)return j(U);E.continue()})}})}const Le="";function la(a,e){const o=new Map;for(const c of a){const s=e(c);for(const d of s){let I=o;for(let E=0;E<d.length;E++){const j=d.charAt(E);let F=I.get(j);F||(F=new Map,I.set(j,F)),I=F}let N=I.get(Le);N||(N=[],I.set(Le,N)),N.push(c)}}return(c,s)=>{let d=o;for(let E=0;E<c.length;E++){const j=c.charAt(E),F=d.get(j);if(F)d=F;else return[]}if(s)return d.get(Le)||[];const I=[],N=[d];for(;N.length;){const j=[...N.shift().entries()].sort((F,_)=>F[0]<_[0]?-1:1);for(const[F,_]of j)F===Le?I.push(..._):N.push(_)}return I}}const ca=["name","url"];function pa(a){const e=a&&Array.isArray(a),o=e&&a.length&&(!a[0]||ca.some(n=>!(n in a[0])));if(!e||o)throw new Error("Custom emojis are in the wrong format")}function nt(a){pa(a);const e=(_,U)=>_.name.toLowerCase()<U.name.toLowerCase()?-1:1,o=a.sort(e),c=la(a,_=>{const U=new Set;if(_.shortcodes)for(const ne of _.shortcodes)for(const ee of be(ne))U.add(ee);return U}),s=_=>c(_,!0),d=_=>c(_,!1),I=_=>{const U=be(_),ne=U.map((ee,oe)=>(oe<U.length-1?s:d)(ee));return wt(ne,ee=>ee.name).sort(e)},N=new Map,E=new Map;for(const _ of a){E.set(_.name.toLowerCase(),_);for(const U of _.shortcodes||[])N.set(U.toLowerCase(),_)}return{all:o,search:I,byShortcode:_=>N.get(_.toLowerCase()),byName:_=>E.get(_.toLowerCase())}}const ua=typeof wrappedJSObject<"u";function we(a){if(!a)return a;if(ua&&(a=structuredClone(a)),delete a.tokens,a.skinTones){const e=a.skinTones.length;a.skins=Array(e);for(let o=0;o<e;o++)a.skins[o]={tone:a.skinTones[o],unicode:a.skinUnicodes[o],version:a.skinVersions[o]};delete a.skinTones,delete a.skinUnicodes,delete a.skinVersions}return a}function Lt(a){a||console.warn("emoji-picker-element is more efficient if the dataSource server exposes an ETag header.")}const ma=["annotation","emoji","group","order","version"];function ga(a){if(!a||!Array.isArray(a)||!a[0]||typeof a[0]!="object"||ma.some(e=>!(e in a[0])))throw new Error("Emoji data is in the wrong format")}function $t(a,e){if(Math.floor(a.status/100)!==2)throw new Error("Failed to fetch: "+e+": "+a.status)}async function ya(a){const e=await fetch(a,{method:"HEAD"});$t(e,a);const o=e.headers.get("etag");return Lt(o),o}async function Re(a){const e=await fetch(a);$t(e,a);const o=e.headers.get("etag");Lt(o);const n=await e.json();return ga(n),[o,n]}function ha(a){for(var e="",o=new Uint8Array(a),n=o.byteLength,c=-1;++c<n;)e+=String.fromCharCode(o[c]);return e}function ba(a){for(var e=a.length,o=new ArrayBuffer(e),n=new Uint8Array(o),c=-1;++c<e;)n[c]=a.charCodeAt(c);return o}async function It(a){const e=JSON.stringify(a);let o=ba(e);const n=await crypto.subtle.digest("SHA-1",o),c=ha(n);return btoa(c)}async function va(a,e){let o,n=await ya(e);if(!n){const c=await Re(e);n=c[0],o=c[1],n||(n=await It(o))}await ta(a,e,n)||(o||(o=(await Re(e))[1]),await kt(a,o,e,n))}async function xa(a,e){let[o,n]=await Re(e);o||(o=await It(n)),await kt(a,n,e,o)}async function fa(a,e){try{await va(a,e)}catch(o){if(o.name!=="InvalidStateError")throw o}}class wa{constructor({dataSource:e=qt,locale:o=Ut,customEmoji:n=[]}={}){this.dataSource=e,this.locale=o,this._dbName=`emoji-picker-element-${this.locale}`,this._db=void 0,this._lazyUpdate=void 0,this._custom=nt(n),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){const e=this._db=await Vt(this._dbName);Kt(this._dbName,this._clear);const o=this.dataSource;await ea(e)?await xa(e,o):this._lazyUpdate=fa(e,o)}async ready(){const e=async()=>(this._ready||(this._ready=this._init()),this._ready);await e(),this._db||await e()}async getEmojiByGroup(e){return je(e),await this.ready(),at(await na(this._db,e)).map(we)}async getEmojiBySearchQuery(e){Ee(e),await this.ready();const o=this._custom.search(e),n=at(await Et(this._db,e)).map(we);return[...o,...n]}async getEmojiByShortcode(e){Ee(e),await this.ready();const o=this._custom.byShortcode(e);return o||we(await oa(this._db,e))}async getEmojiByUnicodeOrName(e){Ee(e),await this.ready();const o=this._custom.byName(e);return o||we(await sa(this._db,e))}async getPreferredSkinTone(){return await this.ready(),await Xe(this._db,ve,tt)||0}async setPreferredSkinTone(e){return je(e),await this.ready(),ia(this._db,ve,tt,e)}async incrementFavoriteEmojiCount(e){return Ee(e),await this.ready(),ra(this._db,e)}async getTopFavoriteEmoji(e){return je(e),await this.ready(),(await da(this._db,this._custom,e)).map(we)}set customEmoji(e){this._custom=nt(e)}get customEmoji(){return this._custom.all}async _shutdown(){await this.ready();try{await this._lazyUpdate}catch{}}_clear(){this._db=this._ready=this._lazyUpdate=void 0}async close(){await this._shutdown(),await Ke(this._dbName)}async delete(){await this._shutdown(),await Yt(this._dbName)}}const Ge=[[-1,"✨","custom"],[0,"😀","smileys-emotion"],[1,"👋","people-body"],[3,"🐱","animals-nature"],[4,"🍎","food-drink"],[5,"🏠️","travel-places"],[6,"⚽","activities"],[7,"📝","objects"],[8,"⛔️","symbols"],[9,"🏁","flags"]].map(([a,e,o])=>({id:a,emoji:e,name:o})),_e=Ge.slice(1),ka=2,ot=6,Tt=typeof requestIdleCallback=="function"?requestIdleCallback:setTimeout;function st(a){return a.unicode.includes("‍")}const Ea={"🫪":17,"🫩":16,"🫨":15.1,"🫠":14,"🥲":13.1,"🥻":12.1,"🥰":11,"🤩":5,"👱‍♀️":4,"🤣":3,"👁️‍🗨️":2,"😀":1,"😐️":.7,"😃":.6},La=1e3,$a="🖐️",Ia=8,Ta=["😊","😒","❤️","👍️","😍","😂","😭","☺️","😔","😩","😏","💕","🙌","😘"],St='"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif',Sa=(a,e)=>a<e?-1:a>e?1:0,it=(a,e)=>{const o=document.createElement("canvas");o.width=o.height=1;const n=o.getContext("2d",{willReadFrequently:!0});return n.textBaseline="top",n.font=`100px ${St}`,n.fillStyle=e,n.scale(.01,.01),n.fillText(a,0,0),n.getImageData(0,0,1,1).data},Ba=(a,e)=>{const o=[...a].join(","),n=[...e].join(",");return o===n&&!o.startsWith("0,0,0,")};function Ma(a){const e=it(a,"#000"),o=it(a,"#fff");return e&&o&&Ba(e,o)}function Ca(){const a=Object.entries(Ea);try{for(const[e,o]of a)if(Ma(e))return o}catch{}finally{}return a[0][1]}let Ae;const Pe=()=>(Ae||(Ae=new Promise(a=>Tt(()=>a(Ca())))),Ae),We=new Map,za="️",Da="\uD83C",ja="‍",_a=127995,Aa=57339;function Pa(a,e){if(e===0)return a;const o=a.indexOf(ja);return o!==-1?a.substring(0,o)+String.fromCodePoint(_a+e-1)+a.substring(o):(a.endsWith(za)&&(a=a.substring(0,a.length-1)),a+Da+String.fromCodePoint(Aa+e-1))}function me(a){a.preventDefault(),a.stopPropagation()}function Oe(a,e,o){return e+=a?-1:1,e<0?e=o.length-1:e>=o.length&&(e=0),e}function Bt(a,e){const o=new Set,n=[];for(const c of a){const s=e(c);o.has(s)||(o.add(s),n.push(c))}return n}function Oa(a,e){const o=n=>{const c={};for(const s of n)typeof s.tone=="number"&&s.version<=e&&(c[s.tone]=s.unicode);return c};return a.map(({unicode:n,skins:c,shortcodes:s,url:d,name:I,category:N,annotation:E})=>({unicode:n,name:I,shortcodes:s,url:d,category:N,annotation:E,id:n||I,skins:c&&o(c)}))}const Te=requestAnimationFrame;let Fa=typeof ResizeObserver=="function";function Ha(a,e,o){let n;Fa?(n=new ResizeObserver(o),n.observe(a)):Te(o),e.addEventListener("abort",()=>{n&&n.disconnect()})}function rt(a){{const e=document.createRange();return e.selectNode(a.firstChild),e.getBoundingClientRect().width}}let Fe;function Na(a,e,o){let n=!0;for(const c of a){const s=o(c);if(!s)continue;const d=rt(s);typeof Fe>"u"&&(Fe=rt(e));const I=d/1.8<Fe;We.set(c.unicode,I),I||(n=!1)}return n}function qa(a){return Bt(a,e=>e)}function Ua(a){a&&(a.scrollTop=0)}function ke(a,e,o){let n=a.get(e);return n||(n=o(),a.set(e,n)),n}function dt(a){return""+a}function Ra(a){const e=document.createElement("template");return e.innerHTML=a,e}const Ga=new WeakMap,Wa=new WeakMap,Va=Symbol("un-keyed"),Ya="replaceChildren"in Element.prototype;function Ka(a,e){Ya?a.replaceChildren(...e):(a.innerHTML="",a.append(...e))}function Ja(a,e){let o=a.firstChild,n=0;for(;o;){if(e[n]!==o)return!0;o=o.nextSibling,n++}return n!==e.length}function Xa(a,e){const{targetNode:o}=e;let{targetParentNode:n}=e,c=!1;n?c=Ja(n,a):(c=!0,e.targetNode=void 0,e.targetParentNode=n=o.parentNode),c&&Ka(n,a)}function Za(a,e){for(const o of e){const{targetNode:n,currentExpression:c,binding:{expressionIndex:s,attributeName:d,attributeValuePre:I,attributeValuePost:N}}=o,E=a[s];if(c!==E)if(o.currentExpression=E,d)if(E===null)n.removeAttribute(d);else{const j=I+dt(E)+N;n.setAttribute(d,j)}else{let j;Array.isArray(E)?Xa(E,o):E instanceof Element?(j=E,n.replaceWith(j)):n.nodeValue=dt(E),j&&(o.targetNode=j)}}}function Qa(a){let e="",o=!1,n=!1,c=-1;const s=new Map,d=[];let I=0;for(let E=0,j=a.length;E<j;E++){const F=a[E];if(e+=F.slice(I),E===j-1)break;for(let q=0;q<F.length;q++)switch(F.charAt(q)){case"<":{F.charAt(q+1)==="/"?d.pop():(o=!0,d.push(++c));break}case">":{o=!1,n=!1;break}case"=":{n=!0;break}}const _=d[d.length-1],U=ke(s,_,()=>[]);let ne,ee,oe;if(n){const q=/(\S+)="?([^"=]*)$/.exec(F);ne=q[1],ee=q[2];const V=/^([^">]*)("?)/.exec(a[E+1]);oe=V[1],e=e.slice(0,-1*q[0].length),I=V[0].length}else I=0;const ce={attributeName:ne,attributeValuePre:ee,attributeValuePost:oe,expressionIndex:E};U.push(ce),!o&&!n&&(e+=" ")}return{template:Ra(e),elementsToBindings:s}}function lt(a,e,o){for(let n=0;n<a.length;n++){const c=a[n],s=c.attributeName?e:e.firstChild,d={binding:c,targetNode:s,targetParentNode:void 0,currentExpression:void 0};o.push(d)}}function en(a,e){const o=[];let n;if(e.size===1&&(n=e.get(0)))lt(n,a,o);else{const c=document.createTreeWalker(a,NodeFilter.SHOW_ELEMENT);let s=a,d=-1;do{const I=e.get(++d);I&&lt(I,s,o)}while(s=c.nextNode())}return o}function tn(a){const{template:e,elementsToBindings:o}=ke(Ga,a,()=>Qa(a)),n=e.cloneNode(!0).content.firstElementChild,c=en(n,o);return function(d){return Za(d,c),n}}function an(a){const e=ke(Wa,a,()=>new Map);let o=Va;function n(s,...d){const I=ke(e,s,()=>new Map);return ke(I,o,()=>tn(s))(d)}function c(s,d,I){return s.map((N,E)=>{const j=o;o=I(N);try{return d(N,E)}finally{o=j}})}return{map:c,html:n}}function nn(a,e,o,n,c,s,d,I,N){const{labelWithSkin:E,titleForEmoji:j,unicodeWithSkin:F}=o,{html:_,map:U}=an(e);function ne(q,V,te){return U(q,(ae,re)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?re===e.activeSearchItem:null}" aria-label="${E(ae,e.currentSkinTone)}" title="${j(ae)}" class="${"emoji"+(V&&re===e.activeSearchItem?" active":"")+(ae.unicode?"":" custom-emoji")}" id="${`${te}-${ae.id}`}" style="${ae.unicode?null:`--custom-emoji-background: url(${JSON.stringify(ae.url)})`}">${ae.unicode?F(ae,e.currentSkinTone):""}</button>`,ae=>`${te}-${ae.id}`)}const oe=_`<section data-ref="rootElement" class="picker" aria-label="${e.i18n.regionLabel}" style="${e.pickerStyle||""}"><div class="pad-top"></div><div class="search-row"><div class="search-wrapper"><input id="search" class="search" type="search" role="combobox" enterkeyhint="search" placeholder="${e.i18n.searchLabel}" autocapitalize="none" autocomplete="off" spellcheck="true" aria-expanded="${!!(e.searchMode&&e.currentEmojis.length)}" aria-controls="search-results" aria-describedby="search-description" aria-autocomplete="list" aria-activedescendant="${e.activeSearchItemId?`emo-${e.activeSearchItemId}`:null}" data-ref="searchElement" data-on-input="onSearchInput" data-on-keydown="onSearchKeydown"><label class="sr-only" for="search">${e.i18n.searchLabel}</label> <span id="search-description" class="sr-only">${e.i18n.searchDescription}</span></div><div class="skintone-button-wrapper ${e.skinTonePickerExpandedAfterAnimation?"expanded":""}"><button id="skintone-button" class="emoji ${e.skinTonePickerExpanded?"hide-focus":""}" aria-label="${e.skinToneButtonLabel}" title="${e.skinToneButtonLabel}" aria-describedby="skintone-description" aria-haspopup="listbox" aria-expanded="${e.skinTonePickerExpanded}" aria-controls="skintone-list" data-on-click="onClickSkinToneButton">${e.skinToneButtonText||""}</button></div><span id="skintone-description" class="sr-only">${e.i18n.skinToneDescription}</span><div data-ref="skinToneDropdown" id="skintone-list" class="skintone-list hide-focus ${e.skinTonePickerExpanded?"":"hidden no-animate"}" style="transform:translateY(${e.skinTonePickerExpanded?0:"calc(-1 * var(--num-skintones) * var(--total-emoji-size))"})" role="listbox" aria-label="${e.i18n.skinTonesLabel}" aria-activedescendant="skintone-${e.activeSkinTone}" aria-hidden="${!e.skinTonePickerExpanded}" tabIndex="-1" data-on-focusout="onSkinToneOptionsFocusOut" data-on-click="onSkinToneOptionsClick" data-on-keydown="onSkinToneOptionsKeydown" data-on-keyup="onSkinToneOptionsKeyup">${U(e.skinTones,(q,V)=>_`<div id="skintone-${V}" class="emoji ${V===e.activeSkinTone?"active":""}" aria-selected="${V===e.activeSkinTone}" role="option" title="${e.i18n.skinTones[V]}" aria-label="${e.i18n.skinTones[V]}">${q}</div>`,q=>q)}</div></div><div class="nav" role="tablist" style="grid-template-columns:repeat(${e.groups.length},1fr)" aria-label="${e.i18n.categoriesLabel}" data-on-keydown="onNavKeydown" data-on-click="onNavClick">${U(e.groups,q=>_`<button role="tab" class="nav-button" aria-controls="tab-${q.id}" aria-label="${e.i18n.categories[q.name]}" aria-selected="${!e.searchMode&&e.currentGroup.id===q.id}" title="${e.i18n.categories[q.name]}" data-group-id="${q.id}"><div class="nav-emoji emoji">${q.emoji}</div></button>`,q=>q.id)}</div><div class="indicator-wrapper"><div class="indicator" style="transform:translateX(${(e.isRtl?-1:1)*e.currentGroupIndex*100}%)"></div></div><div class="message ${e.message?"":"gone"}" role="alert" aria-live="polite">${e.message||""}</div><div data-ref="tabpanelElement" class="tabpanel ${!e.databaseLoaded||e.message?"gone":""}" role="${e.searchMode?"region":"tabpanel"}" aria-label="${e.searchMode?e.i18n.searchResultsLabel:e.i18n.categories[e.currentGroup.name]}" id="${e.searchMode?null:`tab-${e.currentGroup.id}`}" tabIndex="0" data-on-click="onEmojiClick"><div data-action="calculateEmojiGridStyle">${U(e.currentEmojisWithCategories,(q,V)=>_`<div><div id="menu-label-${V}" class="category ${e.currentEmojisWithCategories.length===1&&e.currentEmojisWithCategories[0].category===""?"gone":""}" aria-hidden="true">${e.searchMode?e.i18n.searchResultsLabel:q.category?q.category:e.currentEmojisWithCategories.length>1?e.i18n.categories.custom:e.i18n.categories[e.currentGroup.name]}</div><div class="emoji-menu ${V!==0&&!e.searchMode&&e.currentGroup.id===-1?"visibility-auto":""}" style="${`--num-rows: ${Math.ceil(q.emojis.length/e.numColumns)}`}" data-action="updateOnIntersection" role="${e.searchMode?"listbox":"menu"}" aria-labelledby="menu-label-${V}" id="${e.searchMode?"search-results":null}">${ne(q.emojis,e.searchMode,"emo")}</div></div>`,q=>q.category)}</div></div><div class="favorites onscreen emoji-menu ${e.message?"gone":""}" role="menu" aria-label="${e.i18n.favoritesLabel}" data-on-click="onEmojiClick">${ne(e.currentFavorites,!1,"fav")}</div><button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">😀</button></section>`,ce=(q,V)=>{for(const te of a.querySelectorAll(`[${q}]`))V(te,te.getAttribute(q))};if(N){a.appendChild(oe);for(const q of["click","focusout","input","keydown","keyup"])ce(`data-on-${q}`,(V,te)=>{V.addEventListener(q,n[te])});ce("data-ref",(q,V)=>{s[V]=q}),d.addEventListener("abort",()=>{a.removeChild(oe)})}ce("data-action",(q,V)=>{let te=I.get(V);te||I.set(V,te=new WeakSet),te.has(q)||(te.add(q),c[V](q))})}const Me=typeof queueMicrotask=="function"?queueMicrotask:a=>Promise.resolve().then(a);function on(a){let e=!1,o;const n=new Map,c=new Set;let s;const d=()=>{if(e)return;const E=[...c];c.clear();try{for(const j of E)j()}finally{s=!1,c.size&&(s=!0,Me(d))}},I=new Proxy({},{get(E,j){if(o){let F=n.get(j);F||(F=new Set,n.set(j,F)),F.add(o)}return E[j]},set(E,j,F){if(E[j]!==F){E[j]=F;const _=n.get(j);if(_){for(const U of _)c.add(U);s||(s=!0,Me(d))}}return!0}}),N=E=>{const j=()=>{const F=o;o=j;try{return E()}finally{o=F}};return j()};return a.addEventListener("abort",()=>{e=!0}),{state:I,createEffect:N}}function He(a,e,o){if(a.length!==e.length)return!1;for(let n=0;n<a.length;n++)if(!o(a[n],e[n]))return!1;return!0}const ct=new WeakMap;function sn(a,e,o){{const n=a.closest(".tabpanel");let c=ct.get(n);c||(c=new IntersectionObserver(o,{root:n,rootMargin:"50% 0px 50% 0px",threshold:0}),ct.set(n,c),e.addEventListener("abort",()=>{c.disconnect()})),c.observe(a)}}const Ne=[],{assign:$e}=Object;function rn(a,e){const o={},n=new AbortController,c=n.signal,{state:s,createEffect:d}=on(c),I=new Map;$e(s,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),$e(s,e),$e(s,{initialLoad:!0,currentEmojis:[],currentEmojisWithCategories:[],rawSearchText:"",searchText:"",searchMode:!1,activeSearchItem:-1,message:void 0,skinTonePickerExpanded:!1,skinTonePickerExpandedAfterAnimation:!1,currentSkinTone:0,activeSkinTone:0,skinToneButtonText:void 0,pickerStyle:void 0,skinToneButtonLabel:"",skinTones:[],currentFavorites:[],defaultFavoriteEmojis:void 0,numColumns:Ia,isRtl:!1,currentGroupIndex:0,groups:_e,databaseLoaded:!1,activeSearchItemId:void 0}),d(()=>{s.currentGroup!==s.groups[s.currentGroupIndex]&&(s.currentGroup=s.groups[s.currentGroupIndex])});const N=t=>{a.getElementById(t).focus()},E=t=>a.getElementById(`emo-${t.id}`),j=(t,r)=>{o.rootElement.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0}))},F=(t,r)=>t.id===r.id,_=(t,r)=>{const{category:l,emojis:h}=t,{category:w,emojis:k}=r;return l!==w?!1:He(h,k,F)},U=t=>{He(s.currentEmojis,t,F)||(s.currentEmojis=t)},ne=t=>{s.searchMode!==t&&(s.searchMode=t)},ee=t=>{He(s.currentEmojisWithCategories,t,_)||(s.currentEmojisWithCategories=t)},oe=(t,r)=>r&&t.skins&&t.skins[r]||t.unicode,V={labelWithSkin:(t,r)=>qa([t.name||oe(t,r),t.annotation,...t.shortcodes||Ne].filter(Boolean)).join(", "),titleForEmoji:t=>t.annotation||(t.shortcodes||Ne).join(", "),unicodeWithSkin:oe},te={onClickSkinToneButton:g,onEmojiClick:y,onNavClick:se,onNavKeydown:X,onSearchKeydown:O,onSkinToneOptionsClick:m,onSkinToneOptionsFocusOut:p,onSkinToneOptionsKeydown:x,onSkinToneOptionsKeyup:M,onSearchInput:i},ae={calculateEmojiGridStyle:L,updateOnIntersection:P};let re=!0;d(()=>{nn(a,s,V,te,ae,o,c,I,re),re=!1}),s.emojiVersion||Pe().then(t=>{t||(s.message=s.i18n.emojiUnsupportedMessage)}),d(()=>{async function t(){let r=!1;const l=setTimeout(()=>{r=!0,s.message=s.i18n.loadingMessage},La);try{await s.database.ready(),s.databaseLoaded=!0}catch(h){console.error(h),s.message=s.i18n.networkErrorMessage}finally{clearTimeout(l),r&&(r=!1,s.message="")}}s.database&&t()}),d(()=>{s.pickerStyle=`
52
+ --num-groups: ${s.groups.length};
53
+ --indicator-opacity: ${s.searchMode?0:1};
54
+ --num-skintones: ${ot};`}),d(()=>{s.customEmoji&&s.database&&T()}),d(()=>{s.customEmoji&&s.customEmoji.length?s.groups!==Ge&&(s.groups=Ge):s.groups!==_e&&(s.currentGroupIndex&&s.currentGroupIndex--,s.groups=_e)}),d(()=>{async function t(){s.databaseLoaded&&(s.currentSkinTone=await s.database.getPreferredSkinTone())}t()}),d(()=>{s.skinTones=Array(ot).fill().map((t,r)=>Pa(s.skinToneEmoji,r))}),d(()=>{s.skinToneButtonText=s.skinTones[s.currentSkinTone]}),d(()=>{s.skinToneButtonLabel=s.i18n.skinToneLabel.replace("{skinTone}",s.i18n.skinTones[s.currentSkinTone])}),d(()=>{async function t(){const{database:r}=s,l=(await Promise.all(Ta.map(h=>r.getEmojiByUnicodeOrName(h)))).filter(Boolean);s.defaultFavoriteEmojis=l}s.databaseLoaded&&t()});function T(){const{customEmoji:t,database:r}=s,l=t||Ne;r.customEmoji!==l&&(r.customEmoji=l)}d(()=>{async function t(){T();const{database:r,defaultFavoriteEmojis:l,numColumns:h}=s,w=await r.getTopFavoriteEmoji(h),k=await $(Bt([...w,...l],H=>H.unicode||H.name).slice(0,h));s.currentFavorites=k}s.databaseLoaded&&s.defaultFavoriteEmojis&&t()});function L(t){Ha(t,c,()=>{{const r=getComputedStyle(o.rootElement),l=parseInt(r.getPropertyValue("--num-columns"),10),h=r.getPropertyValue("direction")==="rtl";s.numColumns=l,s.isRtl=h}})}function P(t){sn(t,c,r=>{for(const{target:l,isIntersecting:h}of r)l.classList.toggle("onscreen",h)})}d(()=>{async function t(){const{searchText:r,currentGroup:l,databaseLoaded:h,customEmoji:w}=s;if(!h)s.currentEmojis=[],s.searchMode=!1;else if(r.length>=ka){const k=await W(r);s.searchText===r&&(U(k),ne(!0))}else{const{id:k}=l;if(k!==-1||w&&w.length){const H=await C(k);s.currentGroup.id===k&&(U(H),ne(!1))}}}t()});const v=()=>{Te(()=>Ua(o.tabpanelElement))};d(()=>{const{currentEmojis:t,emojiVersion:r}=s,l=t.filter(h=>h.unicode).filter(h=>st(h)&&!We.has(h.unicode));if(!r&&l.length)U(t),Te(()=>f(l));else{const h=r?t:t.filter(B);U(h),v()}});function f(t){Na(t,o.baselineEmoji,E)?v():s.currentEmojis=[...s.currentEmojis]}function B(t){return!t.unicode||!st(t)||We.get(t.unicode)}async function D(t){const r=s.emojiVersion||await Pe();return t.filter(({version:l})=>!l||l<=r)}async function $(t){return Oa(t,s.emojiVersion||await Pe())}async function C(t){const r=t===-1?s.customEmoji:await s.database.getEmojiByGroup(t);return $(await D(r))}async function W(t){return $(await D(await s.database.getEmojiBySearchQuery(t)))}d(()=>{}),d(()=>{function t(){const{searchMode:l,currentEmojis:h}=s;if(l)return[{category:"",emojis:h}];const w=new Map;for(const k of h){const H=k.category||"";let b=w.get(H);b||(b=[],w.set(H,b)),b.push(k)}return[...w.entries()].map(([k,H])=>({category:k,emojis:H})).sort((k,H)=>s.customCategorySorting(k.category,H.category))}const r=t();ee(r)}),d(()=>{s.activeSearchItemId=s.activeSearchItem!==-1&&s.currentEmojis[s.activeSearchItem].id}),d(()=>{const{rawSearchText:t}=s;Tt(()=>{s.searchText=(t||"").trim(),s.activeSearchItem=-1})});function O(t){if(!s.searchMode||!s.currentEmojis.length)return;const r=l=>{me(t),s.activeSearchItem=Oe(l,s.activeSearchItem,s.currentEmojis)};switch(t.key){case"ArrowDown":return r(!1);case"ArrowUp":return r(!0);case"Enter":if(s.activeSearchItem===-1)s.activeSearchItem=0;else return me(t),pe(s.currentEmojis[s.activeSearchItem].id)}}function se(t){const{target:r}=t,l=r.closest(".nav-button");if(!l)return;const h=parseInt(l.dataset.groupId,10);o.searchElement.value="",s.rawSearchText="",s.searchText="",s.activeSearchItem=-1,s.currentGroupIndex=s.groups.findIndex(w=>w.id===h)}function X(t){const{target:r,key:l}=t,h=w=>{w&&(me(t),w.focus())};switch(l){case"ArrowLeft":return h(r.previousElementSibling);case"ArrowRight":return h(r.nextElementSibling);case"Home":return h(r.parentElement.firstElementChild);case"End":return h(r.parentElement.lastElementChild)}}async function de(t){const r=await s.database.getEmojiByUnicodeOrName(t),l=[...s.currentEmojis,...s.currentFavorites].find(w=>w.id===t),h=l.unicode&&oe(l,s.currentSkinTone);return await s.database.incrementFavoriteEmojiCount(t),{emoji:r,skinTone:s.currentSkinTone,...h&&{unicode:h},...l.name&&{name:l.name}}}async function pe(t){const r=de(t);j("emoji-click-sync",r),j("emoji-click",await r)}function y(t){const{target:r}=t;if(!r.classList.contains("emoji"))return;me(t);const l=r.id.substring(4);pe(l)}function u(t){s.currentSkinTone=t,s.skinTonePickerExpanded=!1,N("skintone-button"),j("skin-tone-change",{skinTone:t}),s.database.setPreferredSkinTone(t)}function m(t){const{target:{id:r}}=t,l=r&&r.match(/^skintone-(\d)/);if(!l)return;me(t);const h=parseInt(l[1],10);u(h)}function g(t){s.skinTonePickerExpanded=!s.skinTonePickerExpanded,s.activeSkinTone=s.currentSkinTone,s.skinTonePickerExpanded&&(me(t),Te(()=>N("skintone-list")))}d(()=>{s.skinTonePickerExpanded?o.skinToneDropdown.addEventListener("transitionend",()=>{s.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):s.skinTonePickerExpandedAfterAnimation=!1});function x(t){if(!s.skinTonePickerExpanded)return;const r=async l=>{me(t),s.activeSkinTone=l};switch(t.key){case"ArrowUp":return r(Oe(!0,s.activeSkinTone,s.skinTones));case"ArrowDown":return r(Oe(!1,s.activeSkinTone,s.skinTones));case"Home":return r(0);case"End":return r(s.skinTones.length-1);case"Enter":return me(t),u(s.activeSkinTone);case"Escape":return me(t),s.skinTonePickerExpanded=!1,N("skintone-button")}}function M(t){if(s.skinTonePickerExpanded)switch(t.key){case" ":return me(t),u(s.activeSkinTone)}}async function p(t){const{relatedTarget:r}=t;(!r||r.id!=="skintone-list")&&(s.skinTonePickerExpanded=!1)}function i(t){s.rawSearchText=t.target.value}return{$set(t){$e(s,t)},$destroy(){n.abort()}}}const dn="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",ln="en";var cn={categoriesLabel:"Categories",emojiUnsupportedMessage:"Your browser does not support color emoji.",favoritesLabel:"Favorites",loadingMessage:"Loading…",networkErrorMessage:"Could not load emoji.",regionLabel:"Emoji picker",searchDescription:"When search results are available, press up or down to select and enter to choose.",searchLabel:"Search",searchResultsLabel:"Search results",skinToneDescription:"When expanded, press up or down to select and enter to choose.",skinToneLabel:"Choose a skin tone (currently {skinTone})",skinTonesLabel:"Skin tones",skinTones:["Default","Light","Medium-Light","Medium","Medium-Dark","Dark"],categories:{custom:"Custom","smileys-emotion":"Smileys and emoticons","people-body":"People and body","animals-nature":"Animals and nature","food-drink":"Food and drink","travel-places":"Travel and places",activities:"Activities",objects:"Objects",symbols:"Symbols",flags:"Flags"}},pn=':host{--emoji-size:1.375rem;--emoji-padding:0.5rem;--category-emoji-size:var(--emoji-size);--category-emoji-padding:var(--emoji-padding);--indicator-height:3px;--input-border-radius:0.5rem;--input-border-size:1px;--input-font-size:1rem;--input-line-height:1.5;--input-padding:0.25rem;--num-columns:8;--outline-size:2px;--border-size:1px;--border-radius:0;--skintone-border-radius:1rem;--category-font-size:1rem;display:flex;width:min-content;height:400px}:host,:host(.light){color-scheme:light;--background:#fff;--border-color:#e0e0e0;--indicator-color:#385ac1;--input-border-color:#999;--input-font-color:#111;--input-placeholder-color:#999;--outline-color:#999;--category-font-color:#111;--button-active-background:#e6e6e6;--button-hover-background:#d9d9d9}:host(.dark){color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}@media (prefers-color-scheme:dark){:host{color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}}:host([hidden]){display:none}button{margin:0;padding:0;border:0;background:0 0;box-shadow:none;-webkit-tap-highlight-color:transparent}button::-moz-focus-inner{border:0}input{padding:0;margin:0;line-height:1.15;font-family:inherit}input[type=search]{-webkit-appearance:none}:focus{outline:var(--outline-color) solid var(--outline-size);outline-offset:calc(-1*var(--outline-size))}:host([data-js-focus-visible]) :focus:not([data-focus-visible-added]){outline:0}:focus:not(:focus-visible){outline:0}.hide-focus{outline:0}*{box-sizing:border-box}.picker{contain:content;display:flex;flex-direction:column;background:var(--background);border:var(--border-size) solid var(--border-color);border-radius:var(--border-radius);width:100%;height:100%;overflow:hidden;--total-emoji-size:calc(var(--emoji-size) + (2 * var(--emoji-padding)));--total-category-emoji-size:calc(var(--category-emoji-size) + (2 * var(--category-emoji-padding)))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.hidden{opacity:0;pointer-events:none}.abs-pos{position:absolute;left:0;top:0}.gone{display:none!important}.skintone-button-wrapper,.skintone-list{background:var(--background);z-index:3}.skintone-button-wrapper.expanded{z-index:1}.skintone-list{position:absolute;inset-inline-end:0;top:0;z-index:2;overflow:visible;border-bottom:var(--border-size) solid var(--border-color);border-radius:0 0 var(--skintone-border-radius) var(--skintone-border-radius);will-change:transform;transition:transform .2s ease-in-out;transform-origin:center 0}@media (prefers-reduced-motion:reduce){.skintone-list{transition-duration:.001s}}@supports not (inset-inline-end:0){.skintone-list{right:0}}.skintone-list.no-animate{transition:none}.tabpanel{overflow-y:auto;scrollbar-gutter:stable;-webkit-overflow-scrolling:touch;will-change:transform;min-height:0;flex:1;contain:content}.emoji-menu{display:grid;grid-template-columns:repeat(var(--num-columns),var(--total-emoji-size));justify-content:space-around;align-items:flex-start;width:100%}.emoji-menu.visibility-auto{content-visibility:auto;contain-intrinsic-size:calc(var(--num-columns)*var(--total-emoji-size)) calc(var(--num-rows)*var(--total-emoji-size))}.category{padding:var(--emoji-padding);font-size:var(--category-font-size);color:var(--category-font-color)}.emoji,button.emoji{font-size:var(--emoji-size);display:flex;align-items:center;justify-content:center;border-radius:100%;height:var(--total-emoji-size);width:var(--total-emoji-size);line-height:1;overflow:hidden;font-family:var(--emoji-font-family);cursor:pointer}@media (hover:hover) and (pointer:fine){.emoji:hover,button.emoji:hover{background:var(--button-hover-background)}}.emoji.active,.emoji:active,button.emoji.active,button.emoji:active{background:var(--button-active-background)}.onscreen .custom-emoji::after{content:"";width:var(--emoji-size);height:var(--emoji-size);background-repeat:no-repeat;background-position:center center;background-size:contain;background-image:var(--custom-emoji-background)}.nav,.nav-button{align-items:center}.nav{display:grid;justify-content:space-between;contain:content}.nav-button{display:flex;justify-content:center}.nav-emoji{font-size:var(--category-emoji-size);width:var(--total-category-emoji-size);height:var(--total-category-emoji-size)}.indicator-wrapper{display:flex;border-bottom:1px solid var(--border-color)}.indicator{width:calc(100%/var(--num-groups));height:var(--indicator-height);opacity:var(--indicator-opacity);background-color:var(--indicator-color);will-change:transform,opacity;transition:opacity .1s linear,transform .25s ease-in-out}@media (prefers-reduced-motion:reduce){.indicator{will-change:opacity;transition:opacity .1s linear}}.pad-top,input.search{background:var(--background);width:100%}.pad-top{height:var(--emoji-padding);z-index:3}.search-row{display:flex;align-items:center;position:relative;padding-inline-start:var(--emoji-padding);padding-bottom:var(--emoji-padding)}.search-wrapper{flex:1;min-width:0}input.search{padding:var(--input-padding);border-radius:var(--input-border-radius);border:var(--input-border-size) solid var(--input-border-color);color:var(--input-font-color);font-size:var(--input-font-size);line-height:var(--input-line-height)}input.search::placeholder{color:var(--input-placeholder-color)}.favorites{overflow-y:auto;scrollbar-gutter:stable;display:flex;flex-direction:row;border-top:var(--border-size) solid var(--border-color);contain:content}.message{padding:var(--emoji-padding)}';const Mt=["customEmoji","customCategorySorting","database","dataSource","i18n","locale","skinToneEmoji","emojiVersion"],un=`:host{--emoji-font-family:${St}}`;class Ze extends HTMLElement{constructor(e){super(),this.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=pn+un,this.shadowRoot.appendChild(o),this._ctx={locale:ln,dataSource:dn,skinToneEmoji:$a,customCategorySorting:Sa,customEmoji:null,i18n:cn,emojiVersion:null,...e};for(const n of Mt)n!=="database"&&Object.prototype.hasOwnProperty.call(this,n)&&(this._ctx[n]=this[n],delete this[n]);this._dbFlush()}connectedCallback(){pt(this),this._cmp||(this._cmp=rn(this.shadowRoot,this._ctx))}disconnectedCallback(){pt(this),Me(()=>{if(!this.isConnected&&this._cmp){this._cmp.$destroy(),this._cmp=void 0;const{database:e}=this._ctx;e.close().catch(o=>console.error(o))}})}static get observedAttributes(){return["locale","data-source","skin-tone-emoji","emoji-version"]}attributeChangedCallback(e,o,n){this._set(e.replace(/-([a-z])/g,(c,s)=>s.toUpperCase()),e==="emoji-version"?parseFloat(n):n)}_set(e,o){this._ctx[e]=o,this._cmp&&this._cmp.$set({[e]:o}),["locale","dataSource"].includes(e)&&this._dbFlush()}_dbCreate(){const{locale:e,dataSource:o,database:n}=this._ctx;(!n||n.locale!==e||n.dataSource!==o)&&this._set("database",new wa({locale:e,dataSource:o}))}_dbFlush(){Me(()=>this._dbCreate())}}const Ct={};for(const a of Mt)Ct[a]={get(){return a==="database"&&this._dbCreate(),this._ctx[a]},set(e){if(a==="database")throw new Error("database is read-only");this._set(a,e)}};Object.defineProperties(Ze.prototype,Ct);function pt(a){a instanceof Ze||Object.setPrototypeOf(a,customElements.get(a.tagName.toLowerCase()).prototype)}customElements.get("emoji-picker")||customElements.define("emoji-picker",Ze);function mn(a,e){const o=document.getElementById("app"),n=new ut,c=new Ce,s=a.id||a._id;let d=null,I=[];const N=localStorage.getItem("currentTheme")||"dark";gn(N),o.innerHTML=`
55
55
  <div class="dashboard">
56
56
  <aside class="sidebar">
57
57
  <div class="sidebar-header">
@@ -60,9 +60,9 @@
60
60
  </div>
61
61
 
62
62
  <div class="user-info">
63
- <div class="avatar">${t.username[0].toUpperCase()}</div>
63
+ <div class="avatar">${a.username[0].toUpperCase()}</div>
64
64
  <div>
65
- <div class="username">${t.username}</div>
65
+ <div class="username">${a.username}</div>
66
66
  <div class="user-role">管理员</div>
67
67
  </div>
68
68
  </div>
@@ -92,6 +92,9 @@
92
92
  <button class="nav-item" data-view="audit">
93
93
  <span class="icon">📊</span> 操作记录
94
94
  </button>
95
+ <button class="nav-item" data-view="polls">
96
+ <span class="icon">🗳️</span> 投票管理
97
+ </button>
95
98
  <button class="nav-item" data-view="knowledge">
96
99
  <span class="icon">📚</span> 知识库
97
100
  </button>
@@ -123,7 +126,7 @@
123
126
  <div id="contentArea"></div>
124
127
  </main>
125
128
  </div>
126
- `,document.querySelectorAll(".nav-item").forEach(h=>{h.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(u=>u.classList.remove("active")),h.classList.add("active");const m=h.dataset.view;K(m)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{l.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{K("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{K("help")});async function $(h){S=(await a.getGroups()).groups,h.innerHTML=`
129
+ `,document.querySelectorAll(".nav-item").forEach(y=>{y.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(m=>m.classList.remove("active")),y.classList.add("active");const u=y.dataset.view;pe(u)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{pe("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{pe("help")});async function E(y){I=(await n.getGroups()).groups,y.innerHTML=`
127
130
  <div class="view-header">
128
131
  <h2>群组管理</h2>
129
132
  <button class="btn-primary" id="createGroupBtn">创建群组</button>
@@ -165,47 +168,47 @@
165
168
  <button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
166
169
  </div>
167
170
  </div>
168
- `;const u=document.getElementById("groupsList");S.forEach(b=>{const k=document.createElement("div");k.className="group-card",k.innerHTML=`
169
- <h3>${b.name}</h3>
170
- <p>${b.description||"暂无描述"}</p>
171
+ `;const m=document.getElementById("groupsList");I.forEach(g=>{const x=document.createElement("div");x.className="group-card",x.innerHTML=`
172
+ <h3>${g.name}</h3>
173
+ <p>${g.description||"暂无描述"}</p>
171
174
  <div class="group-stats">
172
- <span>👥 ${b.members.length} 成员</span>
173
- <span>📄 ${b.documents.length} 文档</span>
175
+ <span>👥 ${g.members.length} 成员</span>
176
+ <span>📄 ${g.documents.length} 文档</span>
174
177
  </div>
175
178
  <div style="display: flex; gap: 10px; margin-top: 10px;">
176
- <button class="btn-select" data-id="${b._id}">选择</button>
177
- <button class="btn-secondary" data-id="${b._id}" data-action="manage">管理成员</button>
179
+ <button class="btn-select" data-id="${g._id}">选择</button>
180
+ <button class="btn-secondary" data-id="${g._id}" data-action="manage">管理成员</button>
178
181
  </div>
179
- `,u.appendChild(k)}),document.querySelectorAll(".btn-select").forEach(b=>{b.addEventListener("click",()=>{i=S.find(k=>k._id===b.dataset.id),e.joinGroup(i._id),alert(`已加入群组: ${i.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(b=>{b.addEventListener("click",async()=>{const k=b.dataset.id;await H(k)})}),document.getElementById("createGroupBtn").addEventListener("click",async()=>{document.getElementById("createGroupModal").classList.remove("hidden"),await D()}),document.getElementById("closeModal").addEventListener("click",()=>{document.getElementById("createGroupModal").classList.add("hidden")}),document.getElementById("closeMembersModal").addEventListener("click",()=>{document.getElementById("manageMembersModal").classList.add("hidden")}),document.getElementById("createGroupForm").addEventListener("submit",async b=>{b.preventDefault();const k=new FormData(b.target),B=Array.from(document.querySelectorAll("#usersList input:checked")).map(y=>y.value);try{const y=await a.createGroup(k.get("name"),k.get("description"),B);alert("群组创建成功!"),await $(h),document.getElementById("createGroupModal").classList.add("hidden")}catch(y){console.error("创建群组错误:",y),alert("创建失败: "+y.message)}})}async function D(){try{const h=await a.getAllUsers(),m=document.getElementById("usersList");m.innerHTML=h.users.map(u=>`
182
+ `,m.appendChild(x)}),document.querySelectorAll(".btn-select").forEach(g=>{g.addEventListener("click",()=>{d=I.find(x=>x._id===g.dataset.id),e.joinGroup(d._id),alert(`已加入群组: ${d.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(g=>{g.addEventListener("click",async()=>{const x=g.dataset.id;await F(x)})}),document.getElementById("createGroupBtn").addEventListener("click",async()=>{document.getElementById("createGroupModal").classList.remove("hidden"),await j()}),document.getElementById("closeModal").addEventListener("click",()=>{document.getElementById("createGroupModal").classList.add("hidden")}),document.getElementById("closeMembersModal").addEventListener("click",()=>{document.getElementById("manageMembersModal").classList.add("hidden")}),document.getElementById("createGroupForm").addEventListener("submit",async g=>{g.preventDefault();const x=new FormData(g.target),M=Array.from(document.querySelectorAll("#usersList input:checked")).map(p=>p.value);try{const p=await n.createGroup(x.get("name"),x.get("description"),M);alert("群组创建成功!"),await E(y),document.getElementById("createGroupModal").classList.add("hidden")}catch(p){console.error("创建群组错误:",p),alert("创建失败: "+p.message)}})}async function j(){try{const y=await n.getAllUsers(),u=document.getElementById("usersList");u.innerHTML=y.users.map(m=>`
180
183
  <label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
181
- <input type="checkbox" value="${u._id}">
182
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${u.username[0].toUpperCase()}</div>
183
- <span>${u.username} (${u.role==="admin"?"管理员":"用户"})</span>
184
+ <input type="checkbox" value="${m._id}">
185
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${m.username[0].toUpperCase()}</div>
186
+ <span>${m.username} (${m.role==="admin"?"管理员":"用户"})</span>
184
187
  </label>
185
- `).join("")}catch(h){console.error("加载用户失败:",h),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function H(h){try{const m=await a.getGroup(h),u=await a.getAllUsers(),b=m.group,k=document.getElementById("currentMembers");k.innerHTML=`
186
- <h4>当前成员 (${b.members.length})</h4>
188
+ `).join("")}catch(y){console.error("加载用户失败:",y),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function F(y){try{const u=await n.getGroup(y),m=await n.getAllUsers(),g=u.group,x=document.getElementById("currentMembers");x.innerHTML=`
189
+ <h4>当前成员 (${g.members.length})</h4>
187
190
  <div style="max-height: 200px; overflow-y: auto;">
188
- ${b.members.map(r=>`
191
+ ${g.members.map(t=>`
189
192
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
190
193
  <div style="display: flex; align-items: center; gap: 10px;">
191
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${r.username[0].toUpperCase()}</div>
192
- <span>${r.username} ${r._id.toString()===b.admin._id.toString()?"(管理员)":""}</span>
194
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${t.username[0].toUpperCase()}</div>
195
+ <span>${t.username} ${t._id.toString()===g.admin._id.toString()?"(管理员)":""}</span>
193
196
  </div>
194
- ${r._id.toString()!==b.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${h}', '${r._id}')">移除</button>`:""}
197
+ ${t._id.toString()!==g.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${y}', '${t._id}')">移除</button>`:""}
195
198
  </div>
196
199
  `).join("")}
197
200
  </div>
198
- `;const B=b.members.map(r=>r._id.toString()),y=u.users.filter(r=>!B.includes(r._id)),g=document.getElementById("availableUsers");y.length===0?g.innerHTML="<p>所有用户都已在群组中</p>":g.innerHTML=y.map(r=>`
201
+ `;const M=g.members.map(t=>t._id.toString()),p=m.users.filter(t=>!M.includes(t._id)),i=document.getElementById("availableUsers");p.length===0?i.innerHTML="<p>所有用户都已在群组中</p>":i.innerHTML=p.map(t=>`
199
202
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
200
203
  <div style="display: flex; align-items: center; gap: 10px;">
201
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${r.username[0].toUpperCase()}</div>
202
- <span>${r.username}</span>
204
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${t.username[0].toUpperCase()}</div>
205
+ <span>${t.username}</span>
203
206
  </div>
204
- <button class="btn-primary btn-sm" onclick="addMember('${h}', '${r._id}')">添加</button>
207
+ <button class="btn-primary btn-sm" onclick="addMember('${y}', '${t._id}')">添加</button>
205
208
  </div>
206
- `).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(m){console.error("加载成员失败:",m),alert("加载失败: "+m.message)}}window.addMember=async(h,m)=>{try{await a.addMember(h,m),alert("成员添加成功!"),await H(h)}catch(u){alert("添加失败: "+u.message)}},window.removeMember=async(h,m)=>{if(confirm("确定要移除该成员吗?"))try{await a.removeMember(h,m),alert("成员移除成功!"),await H(h)}catch(u){alert("移除失败: "+u.message)}};async function _(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const m=await a.getTasks(i._id);h.innerHTML=`
209
+ `).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(u){console.error("加载成员失败:",u),alert("加载失败: "+u.message)}}window.addMember=async(y,u)=>{try{await n.addMember(y,u),alert("成员添加成功!"),await F(y)}catch(m){alert("添加失败: "+m.message)}},window.removeMember=async(y,u)=>{if(confirm("确定要移除该成员吗?"))try{await n.removeMember(y,u),alert("成员移除成功!"),await F(y)}catch(m){alert("移除失败: "+m.message)}};async function _(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const u=await n.getTasks(d._id);y.innerHTML=`
207
210
  <div class="view-header">
208
- <h2>任务管理 - ${i.name}</h2>
211
+ <h2>任务管理 - ${d.name}</h2>
209
212
  <button class="btn-primary" id="createTaskBtn">创建任务</button>
210
213
  </div>
211
214
  <div class="tasks-list" id="tasksList"></div>
@@ -245,30 +248,30 @@
245
248
  </div>
246
249
  </div>
247
250
  </div>
248
- `;const u=document.getElementById("tasksList");m.tasks.length===0?u.innerHTML='<div class="empty-state">暂无任务</div>':(m.tasks.forEach(b=>{const k=document.createElement("div");k.className=`task-card status-${b.status}`,k.innerHTML=`
251
+ `;const m=document.getElementById("tasksList");u.tasks.length===0?m.innerHTML='<div class="empty-state">暂无任务</div>':(u.tasks.forEach(g=>{const x=document.createElement("div");x.className=`task-card status-${g.status}`,x.innerHTML=`
249
252
  <div style="display: flex; justify-content: space-between; align-items: start;">
250
253
  <div style="flex: 1;">
251
- <h3>${b.title}</h3>
252
- <p>${b.description||"无描述"}</p>
254
+ <h3>${g.title}</h3>
255
+ <p>${g.description||"无描述"}</p>
253
256
  <div class="task-meta">
254
- <span class="status-badge">${E(b.status)}</span>
255
- <span>截止: ${b.deadline?new Date(b.deadline).toLocaleDateString():"无"}</span>
257
+ <span class="status-badge">${v(g.status)}</span>
258
+ <span>截止: ${g.deadline?new Date(g.deadline).toLocaleDateString():"无"}</span>
256
259
  </div>
257
260
  </div>
258
261
  <div style="display: flex; gap: 10px;">
259
- <button class="btn-primary btn-sm" data-id="${b._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
260
- <button class="btn-danger btn-sm" data-id="${b._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
262
+ <button class="btn-primary btn-sm" data-id="${g._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
263
+ <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
261
264
  </div>
262
- `,u.appendChild(k)}),document.querySelectorAll('[data-action="delete-task"]').forEach(b=>{b.addEventListener("click",async k=>{k.stopPropagation();const B=b.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await a.deleteTask(B),alert("任务删除成功!"),await _(h)}catch(y){console.error("删除任务错误:",y),alert("删除失败: "+(y.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(b=>{b.addEventListener("click",async k=>{var g;k.stopPropagation();const B=b.dataset.id,y=m.tasks.find(r=>r._id===B);if(y){const r=document.getElementById("taskDetailContent"),d=s=>({pending:"#6366f1","in-progress":"#f59e0b",completed:"#10b981",cancelled:"#ef4444"})[s]||"#6366f1";r.innerHTML=`
265
+ `,m.appendChild(x)}),document.querySelectorAll('[data-action="delete-task"]').forEach(g=>{g.addEventListener("click",async x=>{x.stopPropagation();const M=g.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await n.deleteTask(M),alert("任务删除成功!"),await _(y)}catch(p){console.error("删除任务错误:",p),alert("删除失败: "+(p.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(g=>{g.addEventListener("click",async x=>{var i;x.stopPropagation();const M=g.dataset.id,p=u.tasks.find(t=>t._id===M);if(p){const t=document.getElementById("taskDetailContent"),r=l=>({pending:"#6366f1","in-progress":"#f59e0b",completed:"#10b981",cancelled:"#ef4444"})[l]||"#6366f1";t.innerHTML=`
263
266
  <div class="task-detail" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-radius: 12px; padding: 16px;">
264
267
 
265
268
  <!-- 任务标题 -->
266
- <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${d(y.status)};">
269
+ <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${r(p.status)};">
267
270
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
268
271
  <span style="font-size: 18px;">📌</span>
269
272
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务标题</h4>
270
273
  </div>
271
- <p style="font-size: 18px; font-weight: 700; margin: 0; color: var(--text-primary); line-height: 1.3;">${y.title}</p>
274
+ <p style="font-size: 18px; font-weight: 700; margin: 0; color: var(--text-primary); line-height: 1.3;">${p.title}</p>
272
275
  </div>
273
276
 
274
277
  <!-- 任务描述 -->
@@ -277,7 +280,7 @@
277
280
  <span style="font-size: 18px;">📝</span>
278
281
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务描述</h4>
279
282
  </div>
280
- <p style="margin: 0; line-height: 1.6; color: var(--text-primary); font-size: 14px; white-space: pre-wrap;">${y.description||'<span style="color: var(--text-secondary); font-style: italic;">暂无描述</span>'}</p>
283
+ <p style="margin: 0; line-height: 1.6; color: var(--text-primary); font-size: 14px; white-space: pre-wrap;">${p.description||'<span style="color: var(--text-secondary); font-style: italic;">暂无描述</span>'}</p>
281
284
  </div>
282
285
 
283
286
  <!-- 状态和日期网格 -->
@@ -289,9 +292,9 @@
289
292
  <span style="font-size: 18px;">📊</span>
290
293
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务状态</h4>
291
294
  </div>
292
- <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${d(y.status)}; color: white; font-weight: 600; font-size: 13px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">
293
- ${y.status==="completed"?"✓":y.status==="in-progress"?"⟳":y.status==="cancelled"?"✕":"○"}
294
- ${E(y.status)}
295
+ <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${r(p.status)}; color: white; font-weight: 600; font-size: 13px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">
296
+ ${p.status==="completed"?"✓":p.status==="in-progress"?"⟳":p.status==="cancelled"?"✕":"○"}
297
+ ${v(p.status)}
295
298
  </span>
296
299
  </div>
297
300
 
@@ -302,7 +305,7 @@
302
305
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">截止日期</h4>
303
306
  </div>
304
307
  <p style="margin: 0; font-size: 13px; font-weight: 600; color: var(--text-primary);">
305
- ${y.deadline?new Date(y.deadline).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):'<span style="color: var(--text-secondary); font-style: italic;">无截止日期</span>'}
308
+ ${p.deadline?new Date(p.deadline).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):'<span style="color: var(--text-secondary); font-style: italic;">无截止日期</span>'}
306
309
  </p>
307
310
  </div>
308
311
 
@@ -313,24 +316,24 @@
313
316
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
314
317
  <span style="font-size: 18px;">👥</span>
315
318
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">分配给</h4>
316
- ${y.assignedTo&&y.assignedTo.length>0?`<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">${y.assignedTo.length} 人</span>`:""}
319
+ ${p.assignedTo&&p.assignedTo.length>0?`<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">${p.assignedTo.length} 人</span>`:""}
317
320
  </div>
318
321
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
319
- ${y.assignedTo&&y.assignedTo.length>0?y.assignedTo.map(s=>{var w,P;const c=y.completedBy&&y.completedBy.some(p=>p.user&&p.user._id===s._id),v=c?y.completedBy.find(p=>p.user&&p.user._id===s._id):null;return`
320
- <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${c?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${c?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${c?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
321
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${c?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${c?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((P=(w=s.username)==null?void 0:w[0])==null?void 0:P.toUpperCase())||"?"}</div>
322
+ ${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(l=>{var k,H;const h=p.completedBy&&p.completedBy.some(b=>b.user&&b.user._id===l._id),w=h?p.completedBy.find(b=>b.user&&b.user._id===l._id):null;return`
323
+ <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${h?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${h?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${h?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
324
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${h?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${h?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((H=(k=l.username)==null?void 0:k[0])==null?void 0:H.toUpperCase())||"?"}</div>
322
325
  <div style="display: flex; flex-direction: column; gap: 1px;">
323
- <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${s.username||"未知用户"}</span>
324
- ${c?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${v&&v.completedAt?new Date(v.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
326
+ <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${l.username||"未知用户"}</span>
327
+ ${h?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${w&&w.completedAt?new Date(w.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
325
328
  </div>
326
- ${c?'<span style="position: absolute; top: -4px; right: -4px; background: #10b981; color: white; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span>':""}
329
+ ${h?'<span style="position: absolute; top: -4px; right: -4px; background: #10b981; color: white; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span>':""}
327
330
  </div>
328
331
  `}).join(""):'<p style="margin: 0; color: var(--text-secondary); font-style: italic; font-size: 13px;">未分配给任何人</p>'}
329
332
  </div>
330
333
  </div>
331
334
 
332
335
  <!-- 完成情况统计 -->
333
- ${y.assignedTo&&y.assignedTo.length>0?`
336
+ ${p.assignedTo&&p.assignedTo.length>0?`
334
337
  <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
335
338
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
336
339
  <span style="font-size: 18px;">📈</span>
@@ -338,22 +341,22 @@
338
341
  </div>
339
342
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 10px;">
340
343
  <div style="background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.2); text-align: center;">
341
- <div style="font-size: 22px; font-weight: 700; color: #10b981;">${y.completedBy?y.completedBy.length:0}</div>
344
+ <div style="font-size: 22px; font-weight: 700; color: #10b981;">${p.completedBy?p.completedBy.length:0}</div>
342
345
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">已完成</div>
343
346
  </div>
344
347
  <div style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(245, 158, 11, 0.2); text-align: center;">
345
- <div style="font-size: 22px; font-weight: 700; color: #f59e0b;">${y.assignedTo.length-(y.completedBy?y.completedBy.length:0)}</div>
348
+ <div style="font-size: 22px; font-weight: 700; color: #f59e0b;">${p.assignedTo.length-(p.completedBy?p.completedBy.length:0)}</div>
346
349
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">未完成</div>
347
350
  </div>
348
351
  <div style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(99, 102, 241, 0.2); text-align: center;">
349
- <div style="font-size: 22px; font-weight: 700; color: #6366f1;">${Math.round((y.completedBy?y.completedBy.length:0)/y.assignedTo.length*100)}%</div>
352
+ <div style="font-size: 22px; font-weight: 700; color: #6366f1;">${Math.round((p.completedBy?p.completedBy.length:0)/p.assignedTo.length*100)}%</div>
350
353
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">完成率</div>
351
354
  </div>
352
355
  </div>
353
356
 
354
357
  <!-- 进度条 -->
355
358
  <div style="background: var(--bg-tertiary); height: 8px; border-radius: 4px; overflow: hidden; position: relative;">
356
- <div style="background: linear-gradient(90deg, #10b981 0%, #059669 100%); height: 100%; width: ${(y.completedBy?y.completedBy.length:0)/y.assignedTo.length*100}%; transition: width 0.3s ease; box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);"></div>
359
+ <div style="background: linear-gradient(90deg, #10b981 0%, #059669 100%); height: 100%; width: ${(p.completedBy?p.completedBy.length:0)/p.assignedTo.length*100}%; transition: width 0.3s ease; box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);"></div>
357
360
  </div>
358
361
  </div>
359
362
  `:""}
@@ -368,7 +371,7 @@
368
371
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">创建时间</h4>
369
372
  </div>
370
373
  <p style="margin: 0; font-size: 13px; color: var(--text-primary);">
371
- ${new Date(y.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
374
+ ${new Date(p.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
372
375
  </p>
373
376
  </div>
374
377
 
@@ -379,14 +382,14 @@
379
382
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">更新时间</h4>
380
383
  </div>
381
384
  <p style="margin: 0; font-size: 13px; color: var(--text-primary);">
382
- ${new Date(y.updatedAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
385
+ ${new Date(p.updatedAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
383
386
  </p>
384
387
  </div>
385
388
 
386
389
  </div>
387
390
 
388
391
  <!-- 投票信息 -->
389
- ${y.poll?`
392
+ ${p.poll?`
390
393
  <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-top: 12px;">
391
394
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
392
395
  <span style="font-size: 18px;">📊</span>
@@ -394,43 +397,43 @@
394
397
  </div>
395
398
 
396
399
  <div style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-radius: 8px; padding: 12px; border: 1px solid rgba(99, 102, 241, 0.2);">
397
- <h5 style="margin: 0 0 10px 0; font-size: 14px; font-weight: 600; color: var(--text-primary);">${y.poll.question||"投票"}</h5>
400
+ <h5 style="margin: 0 0 10px 0; font-size: 14px; font-weight: 600; color: var(--text-primary);">${p.poll.question||"投票"}</h5>
398
401
 
399
402
  <!-- 投票统计 -->
400
403
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
401
404
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
402
- <div style="font-size: 20px; font-weight: 700; color: var(--primary);">${y.poll.votedCount||0}</div>
405
+ <div style="font-size: 20px; font-weight: 700; color: var(--primary);">${p.poll.votedCount||0}</div>
403
406
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">已投票</div>
404
407
  </div>
405
408
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
406
- <div style="font-size: 20px; font-weight: 700; color: var(--warning);">${(y.poll.totalMembers||0)-(y.poll.votedCount||0)}</div>
409
+ <div style="font-size: 20px; font-weight: 700; color: var(--warning);">${(p.poll.totalMembers||0)-(p.poll.votedCount||0)}</div>
407
410
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">未投票</div>
408
411
  </div>
409
412
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
410
- <div style="font-size: 20px; font-weight: 700; color: var(--success);">${y.poll.totalVotes||0}</div>
413
+ <div style="font-size: 20px; font-weight: 700; color: var(--success);">${p.poll.totalVotes||0}</div>
411
414
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">总票数</div>
412
415
  </div>
413
416
  </div>
414
417
 
415
418
  <!-- 投票选项 -->
416
419
  <div style="display: flex; flex-direction: column; gap: 8px;">
417
- ${((g=y.poll.options)==null?void 0:g.map(s=>{const c=y.poll.totalVotes>0?Math.round((s.votes||0)/y.poll.totalVotes*100):0,v=s.voters||[];return`
420
+ ${((i=p.poll.options)==null?void 0:i.map(l=>{const h=p.poll.totalVotes>0?Math.round((l.votes||0)/p.poll.totalVotes*100):0,w=l.voters||[];return`
418
421
  <div style="background: var(--bg-tertiary); border-radius: 8px; padding: 10px; border: 1px solid var(--border);">
419
422
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
420
- <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${s.text}</span>
421
- <span style="font-weight: 700; font-size: 13px; color: var(--primary);">${s.votes||0} 票 (${c}%)</span>
423
+ <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${l.text}</span>
424
+ <span style="font-weight: 700; font-size: 13px; color: var(--primary);">${l.votes||0} 票 (${h}%)</span>
422
425
  </div>
423
426
  <div style="height: 6px; background: var(--bg-secondary); border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
424
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${c}%; transition: width 0.3s;"></div>
427
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${h}%; transition: width 0.3s;"></div>
425
428
  </div>
426
- ${v.length>0?`
429
+ ${w.length>0?`
427
430
  <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
428
- <div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${v.length}人):</div>
431
+ <div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${w.length}人):</div>
429
432
  <div style="display: flex; flex-wrap: wrap; gap: 4px;">
430
- ${v.map(w=>{const P=typeof w=="string"?w:w.username;return`
433
+ ${w.map(k=>{const H=typeof k=="string"?k:k.username;return`
431
434
  <span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 10px; font-size: 11px;">
432
- <span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${P?P[0].toUpperCase():"?"}</span>
433
- <span style="font-weight: 600; color: var(--text-primary);">${P||"未知用户"}</span>
435
+ <span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${H?H[0].toUpperCase():"?"}</span>
436
+ <span style="font-weight: 600; color: var(--text-primary);">${H||"未知用户"}</span>
434
437
  </span>
435
438
  `}).join("")}
436
439
  </div>
@@ -443,23 +446,23 @@
443
446
  </div>
444
447
 
445
448
  <!-- 未投票成员 -->
446
- ${y.poll.unvotedMembers&&y.poll.unvotedMembers.length>0?`
449
+ ${p.poll.unvotedMembers&&p.poll.unvotedMembers.length>0?`
447
450
  <div style="margin-top: 12px; padding: 12px; background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(220, 38, 38, 0.05) 100%); border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 10px;">
448
451
  <div style="font-size: 14px; font-weight: 600; color: var(--danger); margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
449
452
  <span>⚠️</span>
450
- <span>未投票成员 (${y.poll.unvotedMembers.length}人):</span>
453
+ <span>未投票成员 (${p.poll.unvotedMembers.length}人):</span>
451
454
  </div>
452
455
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
453
- ${y.poll.unvotedMembers.map(s=>{const c=typeof s=="string"?s:s.username;return`
456
+ ${p.poll.unvotedMembers.map(l=>{const h=typeof l=="string"?l:l.username;return`
454
457
  <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg-tertiary); border: 2px solid rgba(239, 68, 68, 0.3); border-radius: 12px; font-size: 12px;">
455
- <span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${c?c[0].toUpperCase():"?"}</span>
456
- <span style="font-weight: 600; color: var(--text-primary);">${c||"未知用户"}</span>
458
+ <span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${h?h[0].toUpperCase():"?"}</span>
459
+ <span style="font-weight: 600; color: var(--text-primary);">${h||"未知用户"}</span>
457
460
  <span style="font-size: 10px; color: var(--danger); background: rgba(239, 68, 68, 0.1); padding: 2px 6px; border-radius: 8px;">未投票</span>
458
461
  </span>
459
462
  `}).join("")}
460
463
  </div>
461
464
  </div>
462
- `:y.poll.totalMembers>0?`
465
+ `:p.poll.totalMembers>0?`
463
466
  <div style="margin-top: 16px; padding: 12px; background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(5, 150, 105, 0.05) 100%); border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 10px; text-align: center; color: var(--success); font-weight: 600;">
464
467
  ✅ 所有成员已完成投票
465
468
  </div>
@@ -469,9 +472,9 @@
469
472
  `:""}
470
473
 
471
474
  </div>
472
- `,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async b=>{b.preventDefault();const k=new FormData(b.target);try{const y=(await a.getGroup(i._id)).group.members.map(g=>g._id);await a.createTask({title:k.get("title"),description:k.get("description"),groupId:i._id,assignedTo:y,deadline:k.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await _(h),document.getElementById("createTaskModal").classList.add("hidden")}catch(B){console.error("创建任务错误:",B),alert("创建失败: "+B.message)}})}async function R(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const m=await a.getDocuments(i._id),b=(await a.getGroup(i._id)).group;h.innerHTML=`
475
+ `,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async g=>{g.preventDefault();const x=new FormData(g.target);try{const p=(await n.getGroup(d._id)).group.members.map(i=>i._id);await n.createTask({title:x.get("title"),description:x.get("description"),groupId:d._id,assignedTo:p,deadline:x.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await _(y),document.getElementById("createTaskModal").classList.add("hidden")}catch(M){console.error("创建任务错误:",M),alert("创建失败: "+M.message)}})}async function U(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const u=await n.getDocuments(d._id),g=(await n.getGroup(d._id)).group;y.innerHTML=`
473
476
  <div class="view-header">
474
- <h2>📄 共享文档 - ${i.name}</h2>
477
+ <h2>📄 共享文档 - ${d.name}</h2>
475
478
  <button class="btn-primary" id="createDocBtn">➕ 创建文档</button>
476
479
  </div>
477
480
  <div class="documents-list" id="docsList"></div>
@@ -495,13 +498,13 @@
495
498
  <div class="form-group">
496
499
  <label>👥 成员编辑权限</label>
497
500
  <div style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
498
- ${b.members.map(y=>`
501
+ ${g.members.map(p=>`
499
502
  <div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
500
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${y.username[0].toUpperCase()}</div>
501
- <span style="flex: 1;">${y.username}</span>
503
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${p.username[0].toUpperCase()}</div>
504
+ <span style="flex: 1;">${p.username}</span>
502
505
  <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
503
- <input type="checkbox" name="editableMembers" value="${y._id}" ${y._id===b.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
504
- <span style="font-size: 13px;">${y._id===b.admin.toString()?"管理员":"可编辑"}</span>
506
+ <input type="checkbox" name="editableMembers" value="${p._id}" ${p._id===g.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
507
+ <span style="font-size: 13px;">${p._id===g.admin.toString()?"管理员":"可编辑"}</span>
505
508
  </label>
506
509
  </div>
507
510
  `).join("")}
@@ -527,36 +530,36 @@
527
530
  </div>
528
531
  </div>
529
532
  </div>
530
- `;const k=document.getElementById("docsList");m.documents.length===0?k.innerHTML='<div class="empty-state">暂无共享文档</div>':(m.documents.forEach(y=>{var d;const g=document.createElement("div");g.className="document-card";const r=((d=y.editableMembers)==null?void 0:d.length)||0;g.innerHTML=`
533
+ `;const x=document.getElementById("docsList");u.documents.length===0?x.innerHTML='<div class="empty-state">暂无共享文档</div>':(u.documents.forEach(p=>{var r;const i=document.createElement("div");i.className="document-card";const t=((r=p.editableMembers)==null?void 0:r.length)||0;i.innerHTML=`
531
534
  <div style="display: flex; justify-content: space-between; align-items: start;">
532
535
  <div style="flex: 1;">
533
- <h3>📄 ${y.title}</h3>
536
+ <h3>📄 ${p.title}</h3>
534
537
  <div class="doc-meta" style="display: flex; gap: 15px; margin-top: 8px; font-size: 13px; color: var(--text-secondary);">
535
- <span>👤 创建者: ${y.creator.username}</span>
536
- <span>✏️ ${r} 人可编辑</span>
537
- <span>📅 ${new Date(y.createdAt).toLocaleDateString()}</span>
538
+ <span>👤 创建者: ${p.creator.username}</span>
539
+ <span>✏️ ${t} 人可编辑</span>
540
+ <span>📅 ${new Date(p.createdAt).toLocaleDateString()}</span>
538
541
  </div>
539
542
  </div>
540
543
  <div style="display: flex; gap: 10px; align-items: center;">
541
- <button class="btn-secondary btn-sm" data-id="${y._id}" data-action="manage-permission" title="管理权限" style="padding: 8px 16px;">⚙️ 权限</button>
542
- <button class="btn-primary btn-sm" data-id="${y._id}" data-action="edit-doc" title="编辑文档" style="padding: 8px 16px;">✏️ 编辑</button>
543
- <button class="btn-danger btn-sm" data-id="${y._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
544
+ <button class="btn-secondary btn-sm" data-id="${p._id}" data-action="manage-permission" title="管理权限" style="padding: 8px 16px;">⚙️ 权限</button>
545
+ <button class="btn-primary btn-sm" data-id="${p._id}" data-action="edit-doc" title="编辑文档" style="padding: 8px 16px;">✏️ 编辑</button>
546
+ <button class="btn-danger btn-sm" data-id="${p._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
544
547
  </div>
545
548
  </div>
546
- `,k.appendChild(g)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(y=>{y.addEventListener("click",()=>{ae(h,y.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(y=>{y.addEventListener("click",async()=>{const g=y.dataset.id,r=m.documents.find(d=>d._id===g);B(r,b)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(y=>{y.addEventListener("click",async g=>{g.stopPropagation();const r=y.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await a.deleteDocument(r),alert("文档删除成功!"),await R(h)}catch(d){console.error("删除文档错误:",d),alert("删除失败: "+(d.message||"未知错误"))}})})),document.getElementById("createDocBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.remove("hidden")}),document.getElementById("closeDocModal").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")}),document.getElementById("closeDocModalBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")});function B(y,g){const r=document.getElementById("editPermissionModal"),d=document.getElementById("permissionContent");d.innerHTML=`
549
+ `,x.appendChild(i)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(p=>{p.addEventListener("click",()=>{ne(y,p.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(p=>{p.addEventListener("click",async()=>{const i=p.dataset.id,t=u.documents.find(r=>r._id===i);M(t,g)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(p=>{p.addEventListener("click",async i=>{i.stopPropagation();const t=p.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await n.deleteDocument(t),alert("文档删除成功!"),await U(y)}catch(r){console.error("删除文档错误:",r),alert("删除失败: "+(r.message||"未知错误"))}})})),document.getElementById("createDocBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.remove("hidden")}),document.getElementById("closeDocModal").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")}),document.getElementById("closeDocModalBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")});function M(p,i){const t=document.getElementById("editPermissionModal"),r=document.getElementById("permissionContent");r.innerHTML=`
547
550
  <div style="margin-bottom: 16px;">
548
- <h4 style="margin: 0 0 8px 0;">📄 ${y.title}</h4>
551
+ <h4 style="margin: 0 0 8px 0;">📄 ${p.title}</h4>
549
552
  <p style="font-size: 13px; color: var(--text-secondary); margin: 0;">管理哪些成员可以编辑此文档</p>
550
553
  </div>
551
554
  <form id="updatePermissionForm">
552
555
  <div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
553
- ${g.members.map(s=>{var w;const c=((w=y.editableMembers)==null?void 0:w.includes(s._id))||s._id===g.admin.toString(),v=s._id===g.admin.toString();return`
556
+ ${i.members.map(l=>{var k;const h=((k=p.editableMembers)==null?void 0:k.includes(l._id))||l._id===i.admin.toString(),w=l._id===i.admin.toString();return`
554
557
  <div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
555
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${s.username[0].toUpperCase()}</div>
556
- <span style="flex: 1;">${s.username}</span>
558
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${l.username[0].toUpperCase()}</div>
559
+ <span style="flex: 1;">${l.username}</span>
557
560
  <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
558
- <input type="checkbox" name="editableMembers" value="${s._id}" ${c?"checked":""} ${v?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
559
- <span style="font-size: 13px;">${v?"管理员":"可编辑"}</span>
561
+ <input type="checkbox" name="editableMembers" value="${l._id}" ${h?"checked":""} ${w?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
562
+ <span style="font-size: 13px;">${w?"管理员":"可编辑"}</span>
560
563
  </label>
561
564
  </div>
562
565
  `}).join("")}
@@ -567,27 +570,27 @@
567
570
  <button type="button" class="btn-secondary" id="cancelPermissionBtn" style="flex: 1;">取消</button>
568
571
  </div>
569
572
  </form>
570
- `,r.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{r.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async s=>{s.preventDefault();const v=new FormData(s.target).getAll("editableMembers");try{await a.updateDocumentPermissions(y._id,v),alert("权限更新成功!"),r.classList.add("hidden"),await R(h)}catch(w){console.error("更新权限错误:",w),alert("更新失败: "+w.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async y=>{y.preventDefault();const g=new FormData(y.target),r=g.getAll("editableMembers");try{await a.createDocument(g.get("title"),g.get("content"),i._id,r),alert("文档创建成功!"),await R(h),document.getElementById("createDocModal").classList.add("hidden")}catch(d){console.error("创建文档错误:",d),alert("创建失败: "+d.message)}})}async function ae(h,m){const b=(await a.getDocument(m)).document;h.innerHTML=`
573
+ `,t.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{t.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async l=>{l.preventDefault();const w=new FormData(l.target).getAll("editableMembers");try{await n.updateDocumentPermissions(p._id,w),alert("权限更新成功!"),t.classList.add("hidden"),await U(y)}catch(k){console.error("更新权限错误:",k),alert("更新失败: "+k.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async p=>{p.preventDefault();const i=new FormData(p.target),t=i.getAll("editableMembers");try{await n.createDocument(i.get("title"),i.get("content"),d._id,t),alert("文档创建成功!"),await U(y),document.getElementById("createDocModal").classList.add("hidden")}catch(r){console.error("创建文档错误:",r),alert("创建失败: "+r.message)}})}async function ne(y,u){const g=(await n.getDocument(u)).document;y.innerHTML=`
571
574
  <div class="view-header">
572
575
  <button class="btn-back" id="backBtn">← 返回</button>
573
- <h2>${b.title}</h2>
574
- <span class="doc-status">${b.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
576
+ <h2>${g.title}</h2>
577
+ <span class="doc-status">${g.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
575
578
  </div>
576
579
  <div class="editor-container">
577
580
  <div class="editor-toolbar">
578
581
  <div class="online-users" id="onlineUsers">
579
- <span class="user-badge">👤 ${t.username}</span>
582
+ <span class="user-badge">👤 ${a.username}</span>
580
583
  </div>
581
584
  <button class="btn-primary" id="saveBtn">保存</button>
582
585
  </div>
583
586
  <div id="editor"></div>
584
587
  <div class="editor-footer">
585
- <span>最后编辑: ${new Date(b.updatedAt).toLocaleString()}</span>
588
+ <span>最后编辑: ${new Date(g.updatedAt).toLocaleString()}</span>
586
589
  </div>
587
590
  </div>
588
- `;const k=new Quill("#editor",{theme:"snow",modules:{toolbar:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:!1});k.root.innerHTML=b.content||"";let B,y;k.on("text-change",()=>{clearTimeout(B),clearTimeout(y),e.sendTyping(m,t.username,!0),B=setTimeout(()=>{e.sendTyping(m,t.username,!1)},1e3),y=setTimeout(async()=>{const g=k.root.innerHTML;try{await a.updateDocument(m,g)}catch(r){console.error("自动保存失败:",r)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const g=k.root.innerHTML;await a.updateDocument(m,g),alert("保存成功!")}catch(g){alert("保存失败: "+g.message)}}),e.on("document_update",g=>{g.documentId===m&&g.userId!==t.id&&(k.root.innerHTML=g.content)}),e.on("typing",g=>{if(g.documentId===m&&g.userId!==t.id){const r=document.getElementById("onlineUsers");if(g.isTyping)r.innerHTML+=`<span class="user-badge typing" data-user="${g.userId}">✏️ ${g.username}</span>`;else{const d=r.querySelector(`[data-user="${g.userId}"]`);d&&d.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{R(h)})}async function Z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const m=await a.getGroupFiles(i._id);h.innerHTML=`
591
+ `;const x=new Quill("#editor",{theme:"snow",modules:{toolbar:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:!1});x.root.innerHTML=g.content||"";let M,p;x.on("text-change",()=>{clearTimeout(M),clearTimeout(p),e.sendTyping(u,a.username,!0),M=setTimeout(()=>{e.sendTyping(u,a.username,!1)},1e3),p=setTimeout(async()=>{const i=x.root.innerHTML;try{await n.updateDocument(u,i)}catch(t){console.error("自动保存失败:",t)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const i=x.root.innerHTML;await n.updateDocument(u,i),alert("保存成功!")}catch(i){alert("保存失败: "+i.message)}}),e.on("document_update",i=>{i.documentId===u&&i.userId!==a.id&&(x.root.innerHTML=i.content)}),e.on("typing",i=>{if(i.documentId===u&&i.userId!==a.id){const t=document.getElementById("onlineUsers");if(i.isTyping)t.innerHTML+=`<span class="user-badge typing" data-user="${i.userId}">✏️ ${i.username}</span>`;else{const r=t.querySelector(`[data-user="${i.userId}"]`);r&&r.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{U(y)})}async function ee(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=await n.getGroupFiles(d._id);y.innerHTML=`
589
592
  <div class="view-header">
590
- <h2>文件管理 - ${i.name}</h2>
593
+ <h2>文件管理 - ${d.name}</h2>
591
594
  <button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
592
595
  </div>
593
596
  <div class="files-list" id="filesList"></div>
@@ -616,30 +619,31 @@
616
619
  </form>
617
620
  </div>
618
621
  </div>
619
- `;const u=document.getElementById("filesList");!m.files||m.files.length===0?u.innerHTML='<div class="empty-state">暂无文件</div>':(m.files.forEach(b=>{const k=document.createElement("div");k.className="file-card";const B=se(b.mimetype),y=le(b.size);k.innerHTML=`
620
- <div class="file-icon">${B}</div>
622
+ `;const m=document.getElementById("filesList");!u.files||u.files.length===0?m.innerHTML='<div class="empty-state">暂无文件</div>':(u.files.forEach(g=>{const x=document.createElement("div");x.className="file-card";const M=oe(g.mimetype),p=ce(g.size);x.innerHTML=`
623
+ <div class="file-icon">${M}</div>
621
624
  <div class="file-info">
622
- <h4>${b.originalName}</h4>
625
+ <h4>${g.originalName}</h4>
623
626
  <div class="file-meta">
624
- <span>上传者: ${b.uploader.username}</span>
625
- <span>大小: ${y}</span>
626
- <span>时间: ${new Date(b.createdAt).toLocaleString()}</span>
627
+ <span>上传者: ${g.uploader.username}</span>
628
+ <span>大小: ${p}</span>
629
+ <span>时间: ${new Date(g.createdAt).toLocaleString()}</span>
627
630
  </div>
628
- ${b.description?`<p class="file-description">${b.description}</p>`:""}
631
+ ${g.description?`<p class="file-description">${g.description}</p>`:""}
629
632
  </div>
630
633
  <div class="file-actions">
631
- <a href="${a.getFileDownloadUrl(b._id)}" class="btn-primary" download>下载</a>
632
- <button class="btn-danger" data-id="${b._id}" data-action="delete-file">删除</button>
634
+ <a href="${n.getFileDownloadUrl(g._id)}" class="btn-primary" download>下载</a>
635
+ <button class="btn-danger" data-id="${g._id}" data-action="delete-file">删除</button>
633
636
  </div>
634
- `,u.appendChild(k)}),document.querySelectorAll('[data-action="delete-file"]').forEach(b=>{b.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await a.deleteFile(b.dataset.id),alert("文件删除成功!"),await Z(h)}catch(k){alert("删除失败: "+k.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async b=>{b.preventDefault();const k=document.getElementById("fileInput"),B=document.getElementById("fileDescription").value;if(!k.files[0]){alert("请选择文件");return}try{await a.uploadFile(i._id,k.files[0],B),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await Z(h)}catch(y){alert("上传失败: "+y.message)}})}catch(m){console.error("获取文件列表失败:",m),h.innerHTML=`
637
+ `,m.appendChild(x)}),document.querySelectorAll('[data-action="delete-file"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await n.deleteFile(g.dataset.id),alert("文件删除成功!"),await ee(y)}catch(x){alert("删除失败: "+x.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async g=>{g.preventDefault();const x=document.getElementById("fileInput"),M=document.getElementById("fileDescription").value;if(!x.files[0]){alert("请选择文件");return}try{await n.uploadFile(d._id,x.files[0],M),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await ee(y)}catch(p){alert("上传失败: "+p.message)}})}catch(u){console.error("获取文件列表失败:",u),y.innerHTML=`
635
638
  <div class="view-header">
636
639
  <h2>文件管理</h2>
637
640
  </div>
638
- <div class="empty-state">加载文件失败: ${m.message}</div>
639
- `}}function se(h){return h.startsWith("image/")?"🖼️":h==="application/pdf"?"📕":h.includes("word")||h.includes("document")?"📘":h.includes("excel")||h.includes("spreadsheet")?"📗":h.includes("zip")||h.includes("compressed")?"📦":"📄"}function le(h){if(h===0)return"0 Bytes";const m=1024,u=["Bytes","KB","MB","GB"],b=Math.floor(Math.log(h)/Math.log(m));return Math.round(h/Math.pow(m,b)*100)/100+" "+u[b]}async function U(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let u=(await a.getGroup(i._id)).group;h.innerHTML=`
641
+ <div class="empty-state">加载文件失败: ${u.message}</div>
642
+ `}}function oe(y){return y.startsWith("image/")?"🖼️":y==="application/pdf"?"📕":y.includes("word")||y.includes("document")?"📘":y.includes("excel")||y.includes("spreadsheet")?"📗":y.includes("zip")||y.includes("compressed")?"📦":"📄"}function ce(y){if(y===0)return"0 Bytes";const u=1024,m=["Bytes","KB","MB","GB"],g=Math.floor(Math.log(y)/Math.log(u));return Math.round(y/Math.pow(u,g)*100)/100+" "+m[g]}async function q(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let m=(await n.getGroup(d._id)).group;y.innerHTML=`
640
643
  <div class="view-header">
641
- <h2>群聊 - ${i.name}</h2>
644
+ <h2>群聊 - ${d.name}</h2>
642
645
  <div style="display: flex; gap: 10px;">
646
+ <button class="btn-primary" id="createPollBtn" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none;">📊 创建投票</button>
643
647
  <button class="btn-secondary" id="muteAllBtn">全体禁言</button>
644
648
  <button class="btn-secondary" id="manageMuteBtn">个人禁言</button>
645
649
  <button class="btn-danger" id="clearChatBtn" style="background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); color: white; border: none;">🗑️ 清除记录</button>
@@ -664,6 +668,58 @@
664
668
  </div>
665
669
  </div>
666
670
 
671
+ <!-- 创建投票模态框 -->
672
+ <div id="createPollModal" class="modal hidden">
673
+ <div class="modal-content" style="max-width: 600px;">
674
+ <div class="modal-header">
675
+ <h3>📊 创建投票</h3>
676
+ <button class="modal-close" id="closePollModal">&times;</button>
677
+ </div>
678
+ <form id="createPollForm" style="padding: 20px;">
679
+ <div class="form-group">
680
+ <label>投票标题 *</label>
681
+ <input type="text" id="pollTitle" required placeholder="请输入投票标题" style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
682
+ </div>
683
+ <div class="form-group">
684
+ <label>投票描述(可选)</label>
685
+ <textarea id="pollDescription" rows="3" placeholder="请输入投票描述" style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;"></textarea>
686
+ </div>
687
+ <div class="form-group">
688
+ <label>投票选项 *</label>
689
+ <div id="pollOptions">
690
+ <div class="poll-option-input" style="display: flex; gap: 10px; margin-bottom: 10px;">
691
+ <input type="text" class="poll-option" placeholder="选项 1" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
692
+ </div>
693
+ <div class="poll-option-input" style="display: flex; gap: 10px; margin-bottom: 10px;">
694
+ <input type="text" class="poll-option" placeholder="选项 2" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
695
+ </div>
696
+ </div>
697
+ <button type="button" id="addPollOption" class="btn-secondary" style="margin-top: 10px;">+ 添加选项</button>
698
+ </div>
699
+ <div class="form-group">
700
+ <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
701
+ <input type="checkbox" id="allowMultiple" style="width: 18px; height: 18px; cursor: pointer;">
702
+ <span>允许多选</span>
703
+ </label>
704
+ </div>
705
+ <div class="form-group">
706
+ <label style="display: flex; align-items: center; gap: 10px; cursor: pointer;">
707
+ <input type="checkbox" id="anonymous" style="width: 18px; height: 18px; cursor: pointer;">
708
+ <span>匿名投票</span>
709
+ </label>
710
+ </div>
711
+ <div class="form-group">
712
+ <label>截止时间(可选)</label>
713
+ <input type="datetime-local" id="pollEndTime" style="width: 100%; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
714
+ </div>
715
+ <div style="display: flex; gap: 10px; margin-top: 20px;">
716
+ <button type="submit" class="btn-primary" style="flex: 1;">创建投票</button>
717
+ <button type="button" class="btn-secondary" id="cancelPollModal" style="flex: 1;">取消</button>
718
+ </div>
719
+ </form>
720
+ </div>
721
+ </div>
722
+
667
723
  <!-- AI助手模态框 -->
668
724
  <div id="aiModal" class="modal hidden">
669
725
  <div class="modal-content" style="max-width: 950px; height: 88vh; display: flex; flex-direction: column; background: var(--bg-card); border-radius: 16px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.3);">
@@ -779,43 +835,46 @@
779
835
  </div>
780
836
  </div>
781
837
  </div>
782
- `;const b=document.getElementById("messages"),k=document.getElementById("messageInput"),B=document.getElementById("sendBtn"),y=document.getElementById("emojiBtn"),g=document.getElementById("emojiPicker");let r=new Set((u.mutedUsers||[]).map(String)),d=!!u.mutedAll;y.addEventListener("click",()=>{g.classList.toggle("hidden")}),g.addEventListener("emoji-click",p=>{k.value+=p.detail.unicode,k.focus(),g.classList.add("hidden")}),document.addEventListener("click",p=>{!y.contains(p.target)&&!g.contains(p.target)&&g.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const p=await a.getGroupMessages(i._id);p.messages&&(p.messages.forEach(L=>{const j=document.createElement("div");j.className=`message ${L.sender===o?"own":""}`;let C=L.content,G=!1;if(L.content.startsWith("[白板作品]")){G=!0;let q=L.content.replace("[白板作品]","").trim();if(q.includes("/api/files/")&&!q.includes("token=")){const Y=localStorage.getItem("token");q=q.includes("?")?`${q}&token=${Y}`:`${q}?token=${Y}`}console.log("白板作品URL:",q.substring(0,100)),C=`
838
+ `;const g=document.getElementById("messages"),x=document.getElementById("messageInput"),M=document.getElementById("sendBtn"),p=document.getElementById("emojiBtn"),i=document.getElementById("emojiPicker");let t=new Set((m.mutedUsers||[]).map(String)),r=!!m.mutedAll;p.addEventListener("click",()=>{i.classList.toggle("hidden")}),i.addEventListener("emoji-click",b=>{x.value+=b.detail.unicode,x.focus(),i.classList.add("hidden")}),document.addEventListener("click",b=>{!p.contains(b.target)&&!i.contains(b.target)&&i.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const b=await n.getGroupMessages(d._id);b.messages&&(b.messages.forEach(A=>{const z=document.createElement("div");z.className=`message ${A.sender===s?"own":""}`;let S=A.content,R=!1;if(A.content.startsWith("[白板作品]")){R=!0;let G=A.content.replace("[白板作品]","").trim();if(G.includes("/api/files/")&&!G.includes("token=")){const Y=localStorage.getItem("token");G=G.includes("?")?`${G}&token=${Y}`:`${G}?token=${Y}`}console.log("白板作品URL:",G.substring(0,100)),S=`
783
839
  <div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
784
- <img src="${q}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${q.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
840
+ <img src="${G}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${G.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
785
841
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
786
- `}j.innerHTML=`
842
+ `}z.innerHTML=`
787
843
  <div class="message-header">
788
- <span class="message-user">${L.username}</span>
789
- <span class="message-time">${new Date(L.timestamp).toLocaleTimeString()}</span>
844
+ <span class="message-user">${A.username}</span>
845
+ <span class="message-time">${new Date(A.timestamp).toLocaleTimeString()}</span>
790
846
  </div>
791
- <div class="message-content" style="${G?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${C}</div>
792
- `,b.appendChild(j)}),b.scrollTop=b.scrollHeight)}catch(p){console.error("加载历史消息失败:",p)}const s=()=>{const p=document.getElementById("muteAllBtn");p.textContent=d?"取消全体禁言":"全体禁言",p.style.background=d?"var(--danger)":""};s(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
847
+ <div class="message-content" style="${R?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${S}</div>
848
+ `,g.appendChild(z)}),g.scrollTop=g.scrollHeight)}catch(b){console.error("加载历史消息失败:",b)}const l=()=>{const b=document.getElementById("muteAllBtn");b.textContent=r?"取消全体禁言":"全体禁言",b.style.background=r?"var(--danger)":""};l(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
793
849
 
794
850
  确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
795
851
 
796
- 群组名称:`+i.name)!==i.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const j=document.getElementById("clearChatBtn"),C=j.innerHTML;j.innerHTML="⏳ 清除中...",j.disabled=!0;const G=localStorage.getItem("token"),q=await fetch(`http://localhost:3000/api/messages/group/${i._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${G}`,"Content-Type":"application/json"}});if(!q.ok){const X=await q.json();throw new Error(X.message||"清除失败")}const Y=await q.json();b.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Y.deletedCount||0} 条聊天记录!`),j.innerHTML=C,j.disabled=!1}catch(j){console.error("清除聊天记录失败:",j),alert("❌ 清除失败: "+j.message);const C=document.getElementById("clearChatBtn");C.innerHTML="🗑️ 清除记录",C.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const p=!d;d=!!(await a.setMuteAll(i._id,p)).mutedAll,s();const j=document.createElement("div");j.className="notification",j.textContent=d?"已开启全体禁言(成员无法发言)":"已取消全体禁言",b.appendChild(j),b.scrollTop=b.scrollHeight}catch(p){alert("设置失败: "+p.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{u=(await a.getGroup(i._id)).group,r=new Set((u.mutedUsers||[]).map(String));const L=document.getElementById("membersList");L.innerHTML=u.members.filter(j=>j._id.toString()!==o).map(j=>{const C=r.has(j._id.toString());return`
852
+ 群组名称:`+d.name)!==d.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const z=document.getElementById("clearChatBtn"),S=z.innerHTML;z.innerHTML="⏳ 清除中...",z.disabled=!0;const R=localStorage.getItem("token"),G=await fetch(`http://localhost:3000/api/messages/group/${d._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${R}`,"Content-Type":"application/json"}});if(!G.ok){const Z=await G.json();throw new Error(Z.message||"清除失败")}const Y=await G.json();g.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Y.deletedCount||0} 条聊天记录!`),z.innerHTML=S,z.disabled=!1}catch(z){console.error("清除聊天记录失败:",z),alert("❌ 清除失败: "+z.message);const S=document.getElementById("clearChatBtn");S.innerHTML="🗑️ 清除记录",S.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const b=!r;r=!!(await n.setMuteAll(d._id,b)).mutedAll,l();const z=document.createElement("div");z.className="notification",z.textContent=r?"已开启全体禁言(成员无法发言)":"已取消全体禁言",g.appendChild(z),g.scrollTop=g.scrollHeight}catch(b){alert("设置失败: "+b.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{m=(await n.getGroup(d._id)).group,t=new Set((m.mutedUsers||[]).map(String));const A=document.getElementById("membersList");A.innerHTML=m.members.filter(z=>z._id.toString()!==s).map(z=>{const S=t.has(z._id.toString());return`
797
853
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
798
854
  <div style="display: flex; align-items: center; gap: 10px;">
799
- <div class="avatar" style="width: 35px; height: 35px;">${j.username[0].toUpperCase()}</div>
800
- <span>${j.username}</span>
855
+ <div class="avatar" style="width: 35px; height: 35px;">${z.username[0].toUpperCase()}</div>
856
+ <span>${z.username}</span>
801
857
  </div>
802
- <button class="btn-secondary btn-sm" onclick="toggleMute('${j._id}')" id="mute-${j._id}">
803
- ${C?"取消禁言":"禁言"}
858
+ <button class="btn-secondary btn-sm" onclick="toggleMute('${z._id}')" id="mute-${z._id}">
859
+ ${S?"取消禁言":"禁言"}
804
860
  </button>
805
861
  </div>
806
- `}).join(""),document.getElementById("manageMuteModal").classList.remove("hidden")}),document.getElementById("closeMuteModal").addEventListener("click",()=>{document.getElementById("manageMuteModal").classList.add("hidden")}),window.toggleMute=async p=>{try{const L=!r.has(p),j=await a.setUserMute(i._id,p,L);r=new Set((j.mutedUsers||[]).map(String));const C=document.getElementById(`mute-${p}`);C.textContent=r.has(p)?"取消禁言":"禁言",C.style.background=r.has(p)?"var(--danger)":""}catch(L){alert("操作失败: "+L.message)}},e.on("chat_message",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className=`message ${p.userId===o?"own":""}`;let j=p.content,C=!1;if(p.content.startsWith("[白板作品]")){C=!0;let G=p.content.replace("[白板作品]","").trim();if(G.includes("/api/files/")&&!G.includes("token=")){const q=localStorage.getItem("token");G=G.includes("?")?`${G}&token=${q}`:`${G}?token=${q}`}console.log("实时白板作品URL:",G.substring(0,100)),j=`
862
+ `}).join(""),document.getElementById("manageMuteModal").classList.remove("hidden")}),document.getElementById("closeMuteModal").addEventListener("click",()=>{document.getElementById("manageMuteModal").classList.add("hidden")}),document.getElementById("createPollBtn").addEventListener("click",()=>{document.getElementById("createPollModal").classList.remove("hidden")}),document.getElementById("closePollModal").addEventListener("click",()=>{document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}),document.getElementById("cancelPollModal").addEventListener("click",()=>{document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}),document.getElementById("addPollOption").addEventListener("click",()=>{const b=document.getElementById("pollOptions"),A=b.querySelectorAll(".poll-option-input").length+1,z=document.createElement("div");z.className="poll-option-input",z.style.cssText="display: flex; gap: 10px; margin-bottom: 10px;",z.innerHTML=`
863
+ <input type="text" class="poll-option" placeholder="选项 ${A}" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
864
+ <button type="button" class="btn-danger remove-option" style="padding: 10px 15px;">删除</button>
865
+ `,b.appendChild(z),z.querySelector(".remove-option").addEventListener("click",()=>{b.querySelectorAll(".poll-option-input").length>2?z.remove():alert("至少需要2个选项!")})}),document.getElementById("createPollForm").addEventListener("submit",async b=>{b.preventDefault();const A=document.getElementById("pollTitle").value.trim(),z=document.getElementById("pollDescription").value.trim(),S=document.getElementById("allowMultiple").checked,R=document.getElementById("anonymous").checked,G=document.getElementById("pollEndTime").value,Y=document.querySelectorAll(".poll-option"),Z=Array.from(Y).map(J=>J.value.trim()).filter(J=>J);if(Z.length<2){alert("至少需要2个选项!");return}try{const J=localStorage.getItem("token"),ue=await fetch("http://localhost:3000/api/polls",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${J}`},body:JSON.stringify({title:A,description:z,options:Z,groupId:d._id,allowMultiple:S,anonymous:R,endTime:G||null})});if(!ue.ok){const Q=await ue.json();throw new Error(Q.error||"创建投票失败")}const K=(await ue.json()).poll;e.sendChatMessage(d._id,a.username,`[投票]${K._id}`),alert("投票创建成功!"),document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}catch(J){console.error("创建投票失败:",J),alert("创建投票失败: "+J.message)}}),window.toggleMute=async b=>{try{const A=!t.has(b),z=await n.setUserMute(d._id,b,A);t=new Set((z.mutedUsers||[]).map(String));const S=document.getElementById(`mute-${b}`);S.textContent=t.has(b)?"取消禁言":"禁言",S.style.background=t.has(b)?"var(--danger)":""}catch(A){alert("操作失败: "+A.message)}},e.on("chat_message",b=>{if(b.groupId===d._id){const A=document.createElement("div");A.className=`message ${b.userId===s?"own":""}`;let z=b.content,S=!1;if(b.content.startsWith("[白板作品]")){S=!0;let R=b.content.replace("[白板作品]","").trim();if(R.includes("/api/files/")&&!R.includes("token=")){const G=localStorage.getItem("token");R=R.includes("?")?`${R}&token=${G}`:`${R}?token=${G}`}console.log("实时白板作品URL:",R.substring(0,100)),z=`
807
866
  <div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
808
- <img src="${G}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${G.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
867
+ <img src="${R}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${R.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
809
868
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
810
- `}L.innerHTML=`
869
+ `}A.innerHTML=`
811
870
  <div class="message-header">
812
- <span class="message-user">${p.username}</span>
813
- <span class="message-time">${new Date(p.timestamp).toLocaleTimeString()}</span>
871
+ <span class="message-user">${b.username}</span>
872
+ <span class="message-time">${new Date(b.timestamp).toLocaleTimeString()}</span>
814
873
  </div>
815
- <div class="message-content" style="${C?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${j}</div>
816
- `,b.appendChild(L),b.scrollTop=b.scrollHeight}}),e.on("chat_blocked",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className="notification",L.textContent=p.message||"消息发送失败",b.appendChild(L),b.scrollTop=b.scrollHeight}});const c=()=>{const p=k.value.trim();p&&(e.sendChatMessage(i._id,t.username,p),k.value="")};B.addEventListener("click",c),k.addEventListener("keypress",p=>{p.key==="Enter"&&c()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(p=>{p.addEventListener("click",()=>{document.getElementById("aiInputText").value=p.dataset.question,document.getElementById("aiSendBtnModal").click()}),p.addEventListener("mouseenter",L=>{L.target.style.background="var(--primary)",L.target.style.color="white",L.target.style.transform="translateY(-2px)"}),p.addEventListener("mouseleave",L=>{L.target.style.background="var(--bg)",L.target.style.color="inherit",L.target.style.transform="translateY(0)"})});const v=document.getElementById("aiInputText");v.addEventListener("input",()=>{v.style.height="auto",v.style.height=v.scrollHeight+"px"}),v.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),document.getElementById("aiSendBtnModal").click())}),v.addEventListener("focus",()=>{v.style.borderColor="var(--primary)"}),v.addEventListener("blur",()=>{v.style.borderColor="var(--border)"});const w=document.getElementById("aiSendBtnModal");w.addEventListener("mouseenter",()=>{w.style.transform="scale(1.05)"}),w.addEventListener("mouseleave",()=>{w.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const p=document.getElementById("aiInputText"),L=p.value.trim();if(!L)return;const j=document.getElementById("aiChatMessages"),C=document.createElement("div");C.className="ai-message user",C.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",C.textContent=L,j.appendChild(C),p.value="";const G=document.createElement("div");G.className="ai-message ai loading",G.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",G.textContent="思考中...",j.appendChild(G),j.scrollTop=j.scrollHeight;try{const q=localStorage.getItem("token"),X=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${q}`},body:JSON.stringify({question:L,groupId:i==null?void 0:i._id})})).json();G.remove();const te=document.createElement("div");te.className="ai-message ai",te.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",te.textContent=X.answer||"抱歉,我无法回答这个问题。",j.appendChild(te),j.scrollTop=j.scrollHeight}catch(q){G.remove();const Y=document.createElement("div");Y.className="ai-message ai error",Y.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Y.textContent="抱歉,发生了错误: "+q.message,j.appendChild(Y),j.scrollTop=j.scrollHeight}}),window.showImageModal=p=>{const L=document.getElementById("imagePreviewModal"),j=document.getElementById("previewImage"),C=document.getElementById("downloadImageBtn");j.src=p,L.classList.remove("hidden"),C.onclick=async()=>{try{const q=await(await fetch(p)).blob(),Y=window.URL.createObjectURL(q),X=document.createElement("a");X.href=Y,X.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(X),X.click(),document.body.removeChild(X),window.URL.revokeObjectURL(Y)}catch(G){console.error("下载失败:",G),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",p=>{p.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),P()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function P(){const p=document.getElementById("whiteboardCanvas");if(!p)return;const L=p.getContext("2d");let j=!1,C="pen",G="#000000",q=3,Y=0,X=0;document.querySelectorAll(".tool-btn").forEach(J=>{J.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(ee=>{ee.style.background="transparent",ee.style.borderColor="var(--border)",ee.style.color="inherit",ee.classList.remove("active")}),J.style.background="var(--primary)",J.style.borderColor="var(--primary)",J.style.color="white",J.classList.add("active"),C=J.dataset.tool}});const te=document.getElementById("colorPickerCanvas");te&&(te.onchange=J=>{G=J.target.value});const ue=document.getElementById("brushSizeCanvas"),ie=document.getElementById("brushSizeLabel");ue&&ie&&(ue.oninput=J=>{q=J.target.value,ie.textContent=`大小: ${q}`}),p.onmousedown=J=>{j=!0;const ee=p.getBoundingClientRect();Y=J.clientX-ee.left,X=J.clientY-ee.top},p.onmousemove=J=>{if(!j)return;const ee=p.getBoundingClientRect(),de=J.clientX-ee.left,ve=J.clientY-ee.top;L.beginPath(),L.moveTo(Y,X),L.lineTo(de,ve),L.strokeStyle=C==="eraser"?"#ffffff":G,L.lineWidth=q,L.lineCap="round",L.stroke(),Y=de,X=ve},p.onmouseup=()=>{j=!1},p.onmouseleave=()=>{j=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&L.clearRect(0,0,p.width,p.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const J=p.toDataURL("image/png"),ee=document.createElement("a");ee.download=`whiteboard-${Date.now()}.png`,ee.href=J,ee.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const J=p.toDataURL("image/png"),ee=await fetch(J).then(Ze=>Ze.blob()),de=new FormData;de.append("file",ee,`whiteboard-${Date.now()}.png`),de.append("groupId",i._id),de.append("description","协作白板作品");const ve=localStorage.getItem("token"),Xe=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ve}`},body:de});if(Xe.ok){const Mt=`http://localhost:3000/api/files/${(await Xe.json()).file._id}/download?token=${ve}`;e.sendChatMessage(i._id,t.username,`[白板作品]${Mt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(J){console.error("发送白板作品错误:",J),alert("发送失败: "+J.message)}}}}async function V(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
874
+ <div class="message-content" style="${S?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${z}</div>
875
+ `,g.appendChild(A),g.scrollTop=g.scrollHeight}}),e.on("chat_blocked",b=>{if(b.groupId===d._id){const A=document.createElement("div");A.className="notification",A.textContent=b.message||"消息发送失败",g.appendChild(A),g.scrollTop=g.scrollHeight}});const h=()=>{const b=x.value.trim();b&&(e.sendChatMessage(d._id,a.username,b),x.value="")};M.addEventListener("click",h),x.addEventListener("keypress",b=>{b.key==="Enter"&&h()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(b=>{b.addEventListener("click",()=>{document.getElementById("aiInputText").value=b.dataset.question,document.getElementById("aiSendBtnModal").click()}),b.addEventListener("mouseenter",A=>{A.target.style.background="var(--primary)",A.target.style.color="white",A.target.style.transform="translateY(-2px)"}),b.addEventListener("mouseleave",A=>{A.target.style.background="var(--bg)",A.target.style.color="inherit",A.target.style.transform="translateY(0)"})});const w=document.getElementById("aiInputText");w.addEventListener("input",()=>{w.style.height="auto",w.style.height=w.scrollHeight+"px"}),w.addEventListener("keydown",b=>{b.key==="Enter"&&!b.shiftKey&&(b.preventDefault(),document.getElementById("aiSendBtnModal").click())}),w.addEventListener("focus",()=>{w.style.borderColor="var(--primary)"}),w.addEventListener("blur",()=>{w.style.borderColor="var(--border)"});const k=document.getElementById("aiSendBtnModal");k.addEventListener("mouseenter",()=>{k.style.transform="scale(1.05)"}),k.addEventListener("mouseleave",()=>{k.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const b=document.getElementById("aiInputText"),A=b.value.trim();if(!A)return;const z=document.getElementById("aiChatMessages"),S=document.createElement("div");S.className="ai-message user",S.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",S.textContent=A,z.appendChild(S),b.value="";const R=document.createElement("div");R.className="ai-message ai loading",R.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",R.textContent="思考中...",z.appendChild(R),z.scrollTop=z.scrollHeight;try{const G=localStorage.getItem("token"),Z=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${G}`},body:JSON.stringify({question:A,groupId:d==null?void 0:d._id})})).json();R.remove();const J=document.createElement("div");J.className="ai-message ai",J.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",J.textContent=Z.answer||"抱歉,我无法回答这个问题。",z.appendChild(J),z.scrollTop=z.scrollHeight}catch(G){R.remove();const Y=document.createElement("div");Y.className="ai-message ai error",Y.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Y.textContent="抱歉,发生了错误: "+G.message,z.appendChild(Y),z.scrollTop=z.scrollHeight}}),window.showImageModal=b=>{const A=document.getElementById("imagePreviewModal"),z=document.getElementById("previewImage"),S=document.getElementById("downloadImageBtn");z.src=b,A.classList.remove("hidden"),S.onclick=async()=>{try{const G=await(await fetch(b)).blob(),Y=window.URL.createObjectURL(G),Z=document.createElement("a");Z.href=Y,Z.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(Z),Z.click(),document.body.removeChild(Z),window.URL.revokeObjectURL(Y)}catch(R){console.error("下载失败:",R),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",b=>{b.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),H()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function H(){const b=document.getElementById("whiteboardCanvas");if(!b)return;const A=b.getContext("2d");let z=!1,S="pen",R="#000000",G=3,Y=0,Z=0;document.querySelectorAll(".tool-btn").forEach(K=>{K.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(Q=>{Q.style.background="transparent",Q.style.borderColor="var(--border)",Q.style.color="inherit",Q.classList.remove("active")}),K.style.background="var(--primary)",K.style.borderColor="var(--primary)",K.style.color="white",K.classList.add("active"),S=K.dataset.tool}});const J=document.getElementById("colorPickerCanvas");J&&(J.onchange=K=>{R=K.target.value});const ue=document.getElementById("brushSizeCanvas"),ie=document.getElementById("brushSizeLabel");ue&&ie&&(ue.oninput=K=>{G=K.target.value,ie.textContent=`大小: ${G}`}),b.onmousedown=K=>{z=!0;const Q=b.getBoundingClientRect();Y=K.clientX-Q.left,Z=K.clientY-Q.top},b.onmousemove=K=>{if(!z)return;const Q=b.getBoundingClientRect(),le=K.clientX-Q.left,fe=K.clientY-Q.top;A.beginPath(),A.moveTo(Y,Z),A.lineTo(le,fe),A.strokeStyle=S==="eraser"?"#ffffff":R,A.lineWidth=G,A.lineCap="round",A.stroke(),Y=le,Z=fe},b.onmouseup=()=>{z=!1},b.onmouseleave=()=>{z=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&A.clearRect(0,0,b.width,b.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const K=b.toDataURL("image/png"),Q=document.createElement("a");Q.download=`whiteboard-${Date.now()}.png`,Q.href=K,Q.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const K=b.toDataURL("image/png"),Q=await fetch(K).then(et=>et.blob()),le=new FormData;le.append("file",Q,`whiteboard-${Date.now()}.png`),le.append("groupId",d._id),le.append("description","协作白板作品");const fe=localStorage.getItem("token"),Qe=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${fe}`},body:le});if(Qe.ok){const zt=`http://localhost:3000/api/files/${(await Qe.json()).file._id}/download?token=${fe}`;e.sendChatMessage(d._id,a.username,`[白板作品]${zt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(K){console.error("发送白板作品错误:",K),alert("发送失败: "+K.message)}}}}async function V(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}y.innerHTML=`
817
876
  <div class="view-header">
818
- <h2>随机点名 - ${i.name}</h2>
877
+ <h2>随机点名 - ${d.name}</h2>
819
878
  </div>
820
879
  <div class="call-panel">
821
880
  <div class="call-controls">
@@ -825,17 +884,17 @@
825
884
  </div>
826
885
  <div id="callResult" class="call-result"></div>
827
886
  </div>
828
- `,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const m=parseInt(document.getElementById("callCount").value);try{const u=await a.randomCall(i._id,m),b=document.getElementById("callResult");if(b.innerHTML=`
887
+ `,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const u=parseInt(document.getElementById("callCount").value);try{const m=await n.randomCall(d._id,u),g=document.getElementById("callResult");if(g.innerHTML=`
829
888
  <h3>点名结果:</h3>
830
889
  <div class="called-members">
831
- ${u.calledMembers.map(k=>`
890
+ ${m.calledMembers.map(x=>`
832
891
  <div class="member-card">
833
- <div class="avatar">${k.username[0].toUpperCase()}</div>
834
- <div class="member-name">${k.username}</div>
892
+ <div class="avatar">${x.username[0].toUpperCase()}</div>
893
+ <div class="member-name">${x.username}</div>
835
894
  </div>
836
895
  `).join("")}
837
896
  </div>
838
- `,Array.isArray(u.calledMembers)&&u.calledMembers.length>0&&e&&i&&t){const k=u.calledMembers.map(y=>y.username).join("、"),B=`🎲 本次随机点名(${u.calledMembers.length} 人):${k}`;try{e.sendChatMessage(i._id,t.username,B)}catch(y){console.error("发送点名结果到群聊失败:",y)}}}catch(u){alert("点名失败: "+u.message)}})}async function Q(h){h.innerHTML=`
897
+ `,Array.isArray(m.calledMembers)&&m.calledMembers.length>0&&e&&d&&a){const x=m.calledMembers.map(p=>p.username).join("、"),M=`🎲 本次随机点名(${m.calledMembers.length} 人):${x}`;try{e.sendChatMessage(d._id,a.username,M)}catch(p){console.error("发送点名结果到群聊失败:",p)}}}catch(m){alert("点名失败: "+m.message)}})}async function te(y){y.innerHTML=`
839
898
  <div class="view-header">
840
899
  <h2>操作记录</h2>
841
900
  <div style="display: flex; gap: 10px;">
@@ -899,7 +958,7 @@
899
958
  </div>
900
959
  </div>
901
960
  </div>
902
- `;let m=1,u={};try{const r=await a.getGroups(),d=document.getElementById("auditGroupFilter");r.groups.forEach(s=>{const c=document.createElement("option");c.value=s._id,c.textContent=s.name,d.appendChild(c)})}catch(r){console.error("加载群组列表失败:",r)}async function b(r=1,d={}){try{const s=document.getElementById("auditLogs");s.innerHTML='<div class="loading">加载中...</div>';const c={page:r,limit:20},v=await a.getAuditLogs(d,c);if(v.logs.length===0){s.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}s.innerHTML=`
961
+ `;let u=1,m={};try{const t=await n.getGroups(),r=document.getElementById("auditGroupFilter");t.groups.forEach(l=>{const h=document.createElement("option");h.value=l._id,h.textContent=l.name,r.appendChild(h)})}catch(t){console.error("加载群组列表失败:",t)}async function g(t=1,r={}){try{const l=document.getElementById("auditLogs");l.innerHTML='<div class="loading">加载中...</div>';const h={page:t,limit:20},w=await n.getAuditLogs(r,h);if(w.logs.length===0){l.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}l.innerHTML=`
903
962
  <div class="audit-table">
904
963
  <div class="audit-header">
905
964
  <div>时间</div>
@@ -908,26 +967,26 @@
908
967
  <div>资源</div>
909
968
  <div>详情</div>
910
969
  </div>
911
- ${v.logs.map(p=>{var L,j,C,G,q;return`
912
- <div class="audit-row" onclick="showAuditDetail('${p._id}')">
913
- <div class="audit-time">${new Date(p.createdAt).toLocaleString()}</div>
970
+ ${w.logs.map(b=>{var A,z,S,R,G;return`
971
+ <div class="audit-row" onclick="showAuditDetail('${b._id}')">
972
+ <div class="audit-time">${new Date(b.createdAt).toLocaleString()}</div>
914
973
  <div class="audit-user">
915
- <div class="avatar">${((C=(j=(L=p.user)==null?void 0:L.username)==null?void 0:j[0])==null?void 0:C.toUpperCase())||"?"}</div>
916
- <span>${((G=p.user)==null?void 0:G.username)||"未知用户"}</span>
974
+ <div class="avatar">${((S=(z=(A=b.user)==null?void 0:A.username)==null?void 0:z[0])==null?void 0:S.toUpperCase())||"?"}</div>
975
+ <span>${((R=b.user)==null?void 0:R.username)||"未知用户"}</span>
917
976
  </div>
918
977
  <div class="audit-action">
919
- <span class="action-badge action-${p.action}">${T(p.action)}</span>
978
+ <span class="action-badge action-${b.action}">${P(b.action)}</span>
920
979
  </div>
921
- <div class="audit-resource">${p.resourceTitle||p.resourceId}</div>
922
- <div class="audit-description">${((q=p.details)==null?void 0:q.description)||"-"}</div>
980
+ <div class="audit-resource">${b.resourceTitle||b.resourceId}</div>
981
+ <div class="audit-description">${((G=b.details)==null?void 0:G.description)||"-"}</div>
923
982
  </div>
924
983
  `}).join("")}
925
984
  </div>
926
- `;const w=document.getElementById("auditPagination"),P=document.getElementById("pageInfo");P.textContent=`第 ${v.pagination.page} 页,共 ${v.pagination.pages} 页`,document.getElementById("prevPage").disabled=v.pagination.page<=1,document.getElementById("nextPage").disabled=v.pagination.page>=v.pagination.pages,w.style.display=v.pagination.pages>1?"flex":"none"}catch(s){console.error("加载审计日志失败:",s),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+s.message+"</div>"}}async function k(){try{const r=new Date,d=new Date(r.getTime()-7*24*60*60*1e3),s=await a.getAuditSummary({startDate:r.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]}),c=await a.getAuditSummary({startDate:d.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=s.summary.totalLogs,document.getElementById("weekCount").textContent=c.summary.totalLogs,document.getElementById("activeUsers").textContent=c.summary.topUsers.length}catch(r){console.error("加载统计信息失败:",r)}}function B(r){var w,P,p,L,j,C;const d=((w=r.user)==null?void 0:w.username)||"未知用户",s=T(r.action),c=r.resourceTitle||r.resourceId;let v='<strong style="color: #6366f1;">'+d+"</strong> ";switch(r.action){case"document_create":v+='创建了文档 <strong>"'+c+'"</strong>';break;case"document_update":v+='更新了文档 <strong>"'+c+'"</strong>',(P=r.details)!=null&&P.field&&(v+=" 的 <strong>"+y(r.details.field)+"</strong>");break;case"document_delete":v+='删除了文档 <strong>"'+c+'"</strong>';break;case"content_edit":if(v+='编辑了文档 <strong>"'+c+'"</strong> 的内容',r.changes){const G=((p=r.changes.insertions)==null?void 0:p.reduce((Y,X)=>Y+X.length,0))||0,q=((L=r.changes.deletions)==null?void 0:L.reduce((Y,X)=>Y+X.length,0))||0;(G>0||q>0)&&(v+=' (<span style="color: #10b981;">+'+G+'</span> / <span style="color: #ef4444;">-'+q+"</span> 字符)")}break;case"title_edit":v+='修改了文档 <strong>"'+c+'"</strong> 的标题',(j=r.details)!=null&&j.oldValue&&((C=r.details)!=null&&C.newValue)&&(v+=' 从 <strong>"'+r.details.oldValue+'"</strong> 改为 <strong>"'+r.details.newValue+'"</strong>');break;case"document_permission_change":v+='修改了文档 <strong>"'+c+'"</strong> 的权限设置';break;default:v+="执行了 <strong>"+s+"</strong> 操作"}return v}function y(r){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[r]||r}function g(r){if(r==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof r=="object")return JSON.stringify(r,null,2);const d=String(r);return d.length>500?d.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':d.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async r=>{var d,s,c,v,w,P;try{const p=localStorage.getItem("token"),C=(await(await fetch(`http://localhost:3000/api/audit/${r}`,{headers:{Authorization:`Bearer ${p}`}})).json()).log,G=document.getElementById("auditDetailModal"),q=document.getElementById("auditDetailContent"),Y=ue=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ue]||"#6366f1";q.innerHTML=`
985
+ `;const k=document.getElementById("auditPagination"),H=document.getElementById("pageInfo");H.textContent=`第 ${w.pagination.page} 页,共 ${w.pagination.pages} 页`,document.getElementById("prevPage").disabled=w.pagination.page<=1,document.getElementById("nextPage").disabled=w.pagination.page>=w.pagination.pages,k.style.display=w.pagination.pages>1?"flex":"none"}catch(l){console.error("加载审计日志失败:",l),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+l.message+"</div>"}}async function x(){try{const t=new Date,r=new Date(t.getTime()-7*24*60*60*1e3),l=await n.getAuditSummary({startDate:t.toISOString().split("T")[0],endDate:t.toISOString().split("T")[0]}),h=await n.getAuditSummary({startDate:r.toISOString().split("T")[0],endDate:t.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=l.summary.totalLogs,document.getElementById("weekCount").textContent=h.summary.totalLogs,document.getElementById("activeUsers").textContent=h.summary.topUsers.length}catch(t){console.error("加载统计信息失败:",t)}}function M(t){var k,H,b,A,z,S;const r=((k=t.user)==null?void 0:k.username)||"未知用户",l=P(t.action),h=t.resourceTitle||t.resourceId;let w='<strong style="color: #6366f1;">'+r+"</strong> ";switch(t.action){case"document_create":w+='创建了文档 <strong>"'+h+'"</strong>';break;case"document_update":w+='更新了文档 <strong>"'+h+'"</strong>',(H=t.details)!=null&&H.field&&(w+=" 的 <strong>"+p(t.details.field)+"</strong>");break;case"document_delete":w+='删除了文档 <strong>"'+h+'"</strong>';break;case"content_edit":if(w+='编辑了文档 <strong>"'+h+'"</strong> 的内容',t.changes){const R=((b=t.changes.insertions)==null?void 0:b.reduce((Y,Z)=>Y+Z.length,0))||0,G=((A=t.changes.deletions)==null?void 0:A.reduce((Y,Z)=>Y+Z.length,0))||0;(R>0||G>0)&&(w+=' (<span style="color: #10b981;">+'+R+'</span> / <span style="color: #ef4444;">-'+G+"</span> 字符)")}break;case"title_edit":w+='修改了文档 <strong>"'+h+'"</strong> 的标题',(z=t.details)!=null&&z.oldValue&&((S=t.details)!=null&&S.newValue)&&(w+=' 从 <strong>"'+t.details.oldValue+'"</strong> 改为 <strong>"'+t.details.newValue+'"</strong>');break;case"document_permission_change":w+='修改了文档 <strong>"'+h+'"</strong> 的权限设置';break;default:w+="执行了 <strong>"+l+"</strong> 操作"}return w}function p(t){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[t]||t}function i(t){if(t==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof t=="object")return JSON.stringify(t,null,2);const r=String(t);return r.length>500?r.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':r.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async t=>{var r,l,h,w,k,H;try{const b=localStorage.getItem("token"),S=(await(await fetch(`http://localhost:3000/api/audit/${t}`,{headers:{Authorization:`Bearer ${b}`}})).json()).log,R=document.getElementById("auditDetailModal"),G=document.getElementById("auditDetailContent"),Y=ue=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ue]||"#6366f1";G.innerHTML=`
927
986
  <div class="audit-detail" style="padding: 0; background: linear-gradient(135deg, rgba(99, 102, 241, 0.03) 0%, rgba(168, 85, 247, 0.03) 100%);">
928
987
 
929
988
  <!-- 操作信息 -->
930
- <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${Y(C.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
989
+ <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${Y(S.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
931
990
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
932
991
  <span style="font-size: 24px;">📋</span>
933
992
  <h4 style="margin: 0; color: var(--text-primary); font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">操作信息</h4>
@@ -935,14 +994,14 @@
935
994
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
936
995
  <span style="color: var(--text-tertiary); font-weight: 500;">操作类型:</span>
937
996
  <span style="display: inline-flex; align-items: center; gap: 8px;">
938
- <span style="display: inline-block; padding: 4px 10px; background: ${Y(C.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${T(C.action)}</span>
997
+ <span style="display: inline-block; padding: 4px 10px; background: ${Y(S.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${P(S.action)}</span>
939
998
  </span>
940
999
  <span style="color: var(--text-tertiary); font-weight: 500;">操作时间:</span>
941
- <span style="font-weight: 600; color: var(--text-primary);">${new Date(C.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
1000
+ <span style="font-weight: 600; color: var(--text-primary);">${new Date(S.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
942
1001
  <span style="color: var(--text-tertiary); font-weight: 500;">操作用户:</span>
943
1002
  <span style="display: flex; align-items: center; gap: 10px;">
944
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((c=(s=(d=C.user)==null?void 0:d.username)==null?void 0:s[0])==null?void 0:c.toUpperCase())||"?"}</div>
945
- <span style="font-weight: 600; color: var(--text-primary);">${((v=C.user)==null?void 0:v.username)||"未知用户"}</span>
1003
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((h=(l=(r=S.user)==null?void 0:r.username)==null?void 0:l[0])==null?void 0:h.toUpperCase())||"?"}</div>
1004
+ <span style="font-weight: 600; color: var(--text-primary);">${((w=S.user)==null?void 0:w.username)||"未知用户"}</span>
946
1005
  </span>
947
1006
  </div>
948
1007
  </div>
@@ -955,15 +1014,15 @@
955
1014
  </div>
956
1015
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
957
1016
  <span style="color: var(--text-tertiary); font-weight: 500;">资源类型:</span>
958
- <span style="font-weight: 600; color: var(--text-primary);">${C.resourceType||"未知"}</span>
1017
+ <span style="font-weight: 600; color: var(--text-primary);">${S.resourceType||"未知"}</span>
959
1018
  <span style="color: var(--text-tertiary); font-weight: 500;">资源ID:</span>
960
- <span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${C.resourceId}</span>
1019
+ <span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${S.resourceId}</span>
961
1020
  <span style="color: var(--text-tertiary); font-weight: 500;">资源标题:</span>
962
- <span style="font-weight: 600; color: var(--text-primary);">${C.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
1021
+ <span style="font-weight: 600; color: var(--text-primary);">${S.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
963
1022
  </div>
964
1023
  </div>
965
1024
 
966
- ${C.details?`
1025
+ ${S.details?`
967
1026
  <!-- 详细信息 -->
968
1027
  <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
969
1028
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
@@ -1003,13 +1062,145 @@
1003
1062
  </div>
1004
1063
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
1005
1064
  <span style="color: var(--text-tertiary); font-weight: 500;">IP地址:</span>
1006
- <span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${C.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1065
+ <span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${S.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1007
1066
  <span style="color: var(--text-tertiary); font-weight: 500;">用户代理:</span>
1008
- <span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${C.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1067
+ <span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${S.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1009
1068
  </div>
1010
1069
  </div>
1011
1070
  </div>
1012
- `,document.getElementById("auditDescriptionText").innerHTML=B(C);const X=document.getElementById("auditDetailsContent");let te="";if(C.details&&(C.details.field&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',te+='<span style="font-weight: 600; color: var(--text-primary);">'+y(C.details.field)+"</span>"),C.details.oldValue!==void 0&&C.details.oldValue!==null&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">原始值:</span>',te+=`<div style="padding: 8px 12px; background: rgba(239, 68, 68, 0.1); border-radius: 6px; border-left: 3px solid #ef4444; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+g(C.details.oldValue)+"</div>"),C.details.newValue!==void 0&&C.details.newValue!==null&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">新值:</span>',te+=`<div style="padding: 8px 12px; background: rgba(16, 185, 129, 0.1); border-radius: 6px; border-left: 3px solid #10b981; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+g(C.details.newValue)+"</div>")),te?(X.innerHTML=te,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",C.changes&&(((w=C.changes.insertions)==null?void 0:w.length)>0||((P=C.changes.deletions)==null?void 0:P.length)>0)){const ue=document.getElementById("auditChangesContent");let ie="";if(C.changes.insertions&&C.changes.insertions.length>0){const J=C.changes.insertions.reduce((ee,de)=>ee+de.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',ie+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+J+" 个字符</div>",ie+="</div>"}if(C.changes.deletions&&C.changes.deletions.length>0){const J=C.changes.deletions.reduce((ee,de)=>ee+de.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',ie+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+J+" 个字符</div>",ie+="</div>"}ue.innerHTML=ie,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";G.classList.remove("hidden")}catch(p){alert("加载详情失败: "+p.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{u={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(u).forEach(r=>{u[r]||delete u[r]}),m=1,b(m,u)}),document.getElementById("prevPage").addEventListener("click",()=>{m>1&&(m--,b(m,u))}),document.getElementById("nextPage").addEventListener("click",()=>{m++,b(m,u)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const d=Object.keys(u||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(d))try{const s=await a.clearAuditLogs(u||{});alert(`已清除 ${s.deletedCount||0} 条操作记录`),m=1,await k(),await b(m,u)}catch(s){alert("清除失败: "+s.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),k(),b()}async function ne(h){h.innerHTML=`
1071
+ `,document.getElementById("auditDescriptionText").innerHTML=M(S);const Z=document.getElementById("auditDetailsContent");let J="";if(S.details&&(S.details.field&&(J+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',J+='<span style="font-weight: 600; color: var(--text-primary);">'+p(S.details.field)+"</span>"),S.details.oldValue!==void 0&&S.details.oldValue!==null&&(J+='<span style="color: var(--text-tertiary); font-weight: 500;">原始值:</span>',J+=`<div style="padding: 8px 12px; background: rgba(239, 68, 68, 0.1); border-radius: 6px; border-left: 3px solid #ef4444; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+i(S.details.oldValue)+"</div>"),S.details.newValue!==void 0&&S.details.newValue!==null&&(J+='<span style="color: var(--text-tertiary); font-weight: 500;">新值:</span>',J+=`<div style="padding: 8px 12px; background: rgba(16, 185, 129, 0.1); border-radius: 6px; border-left: 3px solid #10b981; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+i(S.details.newValue)+"</div>")),J?(Z.innerHTML=J,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",S.changes&&(((k=S.changes.insertions)==null?void 0:k.length)>0||((H=S.changes.deletions)==null?void 0:H.length)>0)){const ue=document.getElementById("auditChangesContent");let ie="";if(S.changes.insertions&&S.changes.insertions.length>0){const K=S.changes.insertions.reduce((Q,le)=>Q+le.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',ie+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+K+" 个字符</div>",ie+="</div>"}if(S.changes.deletions&&S.changes.deletions.length>0){const K=S.changes.deletions.reduce((Q,le)=>Q+le.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',ie+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+K+" 个字符</div>",ie+="</div>"}ue.innerHTML=ie,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";R.classList.remove("hidden")}catch(b){alert("加载详情失败: "+b.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{m={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(m).forEach(t=>{m[t]||delete m[t]}),u=1,g(u,m)}),document.getElementById("prevPage").addEventListener("click",()=>{u>1&&(u--,g(u,m))}),document.getElementById("nextPage").addEventListener("click",()=>{u++,g(u,m)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const r=Object.keys(m||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(r))try{const l=await n.clearAuditLogs(m||{});alert(`已清除 ${l.deletedCount||0} 条操作记录`),u=1,await x(),await g(u,m)}catch(l){alert("清除失败: "+l.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),x(),g()}async function ae(y){if(!d){y.innerHTML=`
1072
+ <div class="empty-state" style="text-align: center; padding: 60px 20px;">
1073
+ <div style="font-size: 64px; margin-bottom: 20px;">🗳️</div>
1074
+ <h3 style="font-size: 24px; margin-bottom: 12px;">投票管理</h3>
1075
+ <p style="color: var(--text-secondary); margin-bottom: 24px;">请先选择一个群组</p>
1076
+ <button class="btn-primary" onclick="document.querySelector('[data-view=\\"groups\\"]').click()">前往群组管理</button>
1077
+ </div>
1078
+ `;return}try{const u=localStorage.getItem("token"),m=await fetch(`http://localhost:3000/api/polls/group/${d._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!m.ok)throw new Error("获取投票列表失败");const x=(await m.json()).polls||[];y.innerHTML=`
1079
+ <div class="view-header">
1080
+ <h2>🗳️ 投票管理 - ${d.name}</h2>
1081
+ </div>
1082
+ <div class="polls-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; padding: 20px;">
1083
+ ${x.length===0?'<div class="empty-state" style="grid-column: 1/-1;">暂无投票</div>':""}
1084
+ </div>
1085
+ `;const M=y.querySelector(".polls-grid");x.forEach(p=>{const i=p.options.reduce((l,h)=>l+h.votes.length,0),t=p.status==="ended"||p.endTime&&new Date(p.endTime)<new Date,r=document.createElement("div");r.className="poll-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;",r.innerHTML=`
1086
+ <div style="display: flex; align-items: start; justify-content: space-between; margin-bottom: 15px;">
1087
+ <div style="flex: 1;">
1088
+ <h3 style="margin: 0 0 8px 0; font-size: 18px;">${p.title}</h3>
1089
+ ${p.description?`<p style="color: var(--text-secondary); margin: 0 0 12px 0; font-size: 14px;">${p.description}</p>`:""}
1090
+ </div>
1091
+ <span class="status-badge" style="background: ${t?"var(--danger)":"var(--success)"}; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; white-space: nowrap;">${t?"已结束":"进行中"}</span>
1092
+ </div>
1093
+
1094
+ <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 15px;">
1095
+ <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1096
+ ${p.allowMultiple?"✓ 多选":"○ 单选"}
1097
+ </span>
1098
+ <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1099
+ ${p.anonymous?"🔒 匿名":"👤 实名"}
1100
+ </span>
1101
+ <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1102
+ 👥 ${i} 人投票
1103
+ </span>
1104
+ </div>
1105
+
1106
+ <div style="margin-bottom: 15px;">
1107
+ ${p.options.map((l,h)=>{const w=i>0?(l.votes.length/i*100).toFixed(1):0;return`
1108
+ <div style="margin-bottom: 10px;">
1109
+ <div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
1110
+ <span style="font-size: 14px; color: var(--text-primary);">${l.text}</span>
1111
+ <span style="font-size: 14px; font-weight: 600; color: var(--primary);">${l.votes.length} 票 (${w}%)</span>
1112
+ </div>
1113
+ <div style="height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden;">
1114
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${w}%; transition: width 0.3s;"></div>
1115
+ </div>
1116
+ </div>
1117
+ `}).join("")}
1118
+ </div>
1119
+
1120
+ <div style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 15px;">
1121
+ <div>创建时间: ${new Date(p.createdAt).toLocaleString("zh-CN")}</div>
1122
+ ${p.endTime?`<div>截止时间: ${new Date(p.endTime).toLocaleString("zh-CN")}</div>`:""}
1123
+ </div>
1124
+
1125
+ <div style="display: flex; gap: 10px;">
1126
+ <button class="btn-primary btn-sm view-poll-detail" data-poll-id="${p._id}" style="flex: 1;">查看详情</button>
1127
+ ${t?"":`<button class="btn-secondary btn-sm end-poll" data-poll-id="${p._id}">结束投票</button>`}
1128
+ <button class="btn-danger btn-sm delete-poll" data-poll-id="${p._id}">删除</button>
1129
+ </div>
1130
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},M.appendChild(r)}),document.querySelectorAll(".view-poll-detail").forEach(p=>{p.addEventListener("click",async()=>{const i=p.dataset.pollId;await re(i)})}),document.querySelectorAll(".end-poll").forEach(p=>{p.addEventListener("click",async()=>{if(confirm("确定要结束这个投票吗?"))try{const i=localStorage.getItem("token");if(!(await fetch(`http://localhost:3000/api/polls/${p.dataset.pollId}/end`,{method:"PUT",headers:{Authorization:`Bearer ${i}`}})).ok)throw new Error("结束投票失败");alert("投票已结束!"),await ae(y)}catch(i){alert("操作失败: "+i.message)}})}),document.querySelectorAll(".delete-poll").forEach(p=>{p.addEventListener("click",async()=>{if(confirm("确定要删除这个投票吗?"))try{const i=localStorage.getItem("token");if(!(await fetch(`http://localhost:3000/api/polls/${p.dataset.pollId}`,{method:"DELETE",headers:{Authorization:`Bearer ${i}`}})).ok)throw new Error("删除投票失败");alert("投票已删除!"),await ae(y)}catch(i){alert("操作失败: "+i.message)}})})}catch(u){console.error("加载投票列表失败:",u),y.innerHTML=`
1131
+ <div class="view-header">
1132
+ <h2>🗳️ 投票管理</h2>
1133
+ </div>
1134
+ <div class="empty-state">加载失败: ${u.message}</div>
1135
+ `}}async function re(y){try{const u=localStorage.getItem("token"),m=await fetch(`http://localhost:3000/api/polls/${y}`,{headers:{Authorization:`Bearer ${u}`}});if(!m.ok)throw new Error("获取投票详情失败");const x=(await m.json()).poll,M=x.options.reduce((r,l)=>r+l.votes.length,0),p=x.status==="ended"||x.endTime&&new Date(x.endTime)<new Date,i=`
1136
+ <div id="pollDetailModal" class="modal" style="display: flex;">
1137
+ <div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
1138
+ <div class="modal-header">
1139
+ <h3>📊 投票详情</h3>
1140
+ <button class="modal-close" id="closePollDetailModal">&times;</button>
1141
+ </div>
1142
+ <div class="modal-body" style="padding: 24px;">
1143
+ <h2 style="margin: 0 0 12px 0;">${x.title}</h2>
1144
+ ${x.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${x.description}</p>`:""}
1145
+
1146
+ <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
1147
+ <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
1148
+ ${x.allowMultiple?"✓ 多选投票":"○ 单选投票"}
1149
+ </span>
1150
+ <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
1151
+ ${x.anonymous?"🔒 匿名投票":"👤 实名投票"}
1152
+ </span>
1153
+ <span style="font-size: 13px; padding: 6px 12px; background: ${p?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
1154
+ ${p?"已结束":"进行中"}
1155
+ </span>
1156
+ </div>
1157
+
1158
+ <div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; margin-bottom: 20px;">
1159
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
1160
+ <div>
1161
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
1162
+ <div style="font-weight: 600;">👤 ${x.creatorName}</div>
1163
+ </div>
1164
+ <div>
1165
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
1166
+ <div style="font-weight: 600; color: var(--primary);">👥 ${M} 人</div>
1167
+ </div>
1168
+ <div>
1169
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
1170
+ <div style="font-weight: 600;">⏰ ${new Date(x.createdAt).toLocaleString("zh-CN")}</div>
1171
+ </div>
1172
+ ${x.endTime?`
1173
+ <div>
1174
+ <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
1175
+ <div style="font-weight: 600;">⏰ ${new Date(x.endTime).toLocaleString("zh-CN")}</div>
1176
+ </div>
1177
+ `:""}
1178
+ </div>
1179
+ </div>
1180
+
1181
+ <h3 style="margin-bottom: 16px;">投票选项</h3>
1182
+ ${x.options.map((r,l)=>{const h=M>0?(r.votes.length/M*100).toFixed(1):0;return`
1183
+ <div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
1184
+ <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
1185
+ <span style="font-weight: 500; font-size: 16px;">${r.text}</span>
1186
+ <span style="font-weight: 600; color: var(--primary); font-size: 16px;">${r.votes.length} 票 (${h}%)</span>
1187
+ </div>
1188
+ <div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden; margin-bottom: 12px;">
1189
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${h}%; transition: width 0.3s;"></div>
1190
+ </div>
1191
+ ${!x.anonymous&&r.votes.length>0?`
1192
+ <div style="font-size: 13px; color: var(--text-secondary);">
1193
+ <strong>投票者:</strong> ${r.votes.map(w=>w.username).join(", ")}
1194
+ </div>
1195
+ `:""}
1196
+ </div>
1197
+ `}).join("")}
1198
+
1199
+ <button class="btn-secondary" id="closePollDetailBtn" style="width: 100%; margin-top: 20px;">关闭</button>
1200
+ </div>
1201
+ </div>
1202
+ </div>
1203
+ `,t=document.getElementById("pollDetailModal");t&&t.remove(),document.body.insertAdjacentHTML("beforeend",i),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()})}catch(u){console.error("加载投票详情失败:",u),alert("加载投票详情失败: "+u.message)}}async function T(y){y.innerHTML=`
1013
1204
  <div class="view-header">
1014
1205
  <h2>🔍 搜索</h2>
1015
1206
  </div>
@@ -1031,20 +1222,20 @@
1031
1222
  </div>
1032
1223
  <div class="search-results" id="searchResults"></div>
1033
1224
  </div>
1034
- `;const m=document.getElementById("searchInput"),u=document.getElementById("searchBtn"),b=document.getElementById("searchResults"),k=async()=>{const B=m.value.trim();if(!B){b.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const y={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};b.innerHTML='<div class="loading">搜索中...</div>';try{const g=[];if(y.messages&&i)try{const r=await a.getGroupMessages(i._id);r.messages&&r.messages.filter(s=>s.content.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"message",title:`消息 - ${s.username}`,content:s.content,time:s.timestamp,group:i.name})})}catch(r){console.error("搜索消息失败:",r)}if(y.documents)try{if(i){const r=await a.getDocuments(i._id);r.documents&&r.documents.filter(s=>s.title.toLowerCase().includes(B.toLowerCase())||s.content.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"document",title:s.title,content:s.content.substring(0,200),time:s.updatedAt,id:s._id,group:i.name})})}}catch(r){console.error("搜索文档失败:",r)}if(y.tasks&&i)try{const r=await a.getTasks(i._id);r.tasks&&r.tasks.filter(s=>s.title.toLowerCase().includes(B.toLowerCase())||s.description&&s.description.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"task",title:s.title,content:s.description||"",time:s.updatedAt,id:s._id,status:s.status,group:i.name})})}catch(r){console.error("搜索任务失败:",r)}g.length===0?b.innerHTML='<div class="empty-state">未找到相关结果</div>':b.innerHTML=g.map(r=>`
1225
+ `;const u=document.getElementById("searchInput"),m=document.getElementById("searchBtn"),g=document.getElementById("searchResults"),x=async()=>{const M=u.value.trim();if(!M){g.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const p={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};g.innerHTML='<div class="loading">搜索中...</div>';try{const i=[];if(p.messages&&d)try{const t=await n.getGroupMessages(d._id);t.messages&&t.messages.filter(l=>l.content.toLowerCase().includes(M.toLowerCase())).forEach(l=>{i.push({type:"message",title:`消息 - ${l.username}`,content:l.content,time:l.timestamp,group:d.name})})}catch(t){console.error("搜索消息失败:",t)}if(p.documents)try{if(d){const t=await n.getDocuments(d._id);t.documents&&t.documents.filter(l=>l.title.toLowerCase().includes(M.toLowerCase())||l.content.toLowerCase().includes(M.toLowerCase())).forEach(l=>{i.push({type:"document",title:l.title,content:l.content.substring(0,200),time:l.updatedAt,id:l._id,group:d.name})})}}catch(t){console.error("搜索文档失败:",t)}if(p.tasks&&d)try{const t=await n.getTasks(d._id);t.tasks&&t.tasks.filter(l=>l.title.toLowerCase().includes(M.toLowerCase())||l.description&&l.description.toLowerCase().includes(M.toLowerCase())).forEach(l=>{i.push({type:"task",title:l.title,content:l.description||"",time:l.updatedAt,id:l._id,status:l.status,group:d.name})})}catch(t){console.error("搜索任务失败:",t)}i.length===0?g.innerHTML='<div class="empty-state">未找到相关结果</div>':g.innerHTML=i.map(t=>`
1035
1226
  <div class="search-result-item">
1036
1227
  <div class="result-header">
1037
- <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[r.type]} ${r.type==="message"?"消息":r.type==="document"?"文档":"任务"}</span>
1038
- <span class="result-time">${new Date(r.time).toLocaleString()}</span>
1228
+ <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[t.type]} ${t.type==="message"?"消息":t.type==="document"?"文档":"任务"}</span>
1229
+ <span class="result-time">${new Date(t.time).toLocaleString()}</span>
1039
1230
  </div>
1040
- <h4>${re(r.title,B)}</h4>
1041
- <p>${re(r.content,B)}</p>
1042
- ${r.group?`<span class="result-group">群组: ${r.group}</span>`:""}
1043
- ${r.status?`<span class="result-status">状态: ${E(r.status)}</span>`:""}
1231
+ <h4>${L(t.title,M)}</h4>
1232
+ <p>${L(t.content,M)}</p>
1233
+ ${t.group?`<span class="result-group">群组: ${t.group}</span>`:""}
1234
+ ${t.status?`<span class="result-status">状态: ${v(t.status)}</span>`:""}
1044
1235
  </div>
1045
- `).join("")}catch(g){b.innerHTML=`<div class="empty-state">搜索失败: ${g.message}</div>`}};u.addEventListener("click",k),m.addEventListener("keypress",B=>{B.key==="Enter"&&k()})}function re(h,m){if(!m)return h;const u=new RegExp(`(${m})`,"gi");return h.replace(u,"<mark>$1</mark>")}function T(h){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[h]||h}function E(h){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[h]||h}async function F(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),b=await fetch(`http://localhost:3000/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!b.ok)throw new Error(`HTTP ${b.status}: ${b.statusText}`);const k=await b.json();console.log("知识库数据:",k);const B=((m=k.data)==null?void 0:m.knowledgeList)||[];console.log("知识库条目数量:",B.length),h.innerHTML=`
1236
+ `).join("")}catch(i){g.innerHTML=`<div class="empty-state">搜索失败: ${i.message}</div>`}};m.addEventListener("click",x),u.addEventListener("keypress",M=>{M.key==="Enter"&&x()})}function L(y,u){if(!u)return y;const m=new RegExp(`(${u})`,"gi");return y.replace(m,"<mark>$1</mark>")}function P(y){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[y]||y}function v(y){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[y]||y}async function f(y){var u;if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const m=localStorage.getItem("token"),g=await fetch(`http://localhost:3000/api/knowledge/group/${d._id}`,{headers:{Authorization:`Bearer ${m}`}});if(!g.ok)throw new Error(`HTTP ${g.status}: ${g.statusText}`);const x=await g.json();console.log("知识库数据:",x);const M=((u=x.data)==null?void 0:u.knowledgeList)||[];console.log("知识库条目数量:",M.length),y.innerHTML=`
1046
1237
  <div class="view-header">
1047
- <h2>📚 知识库管理 - ${i.name}</h2>
1238
+ <h2>📚 知识库管理 - ${d.name}</h2>
1048
1239
  <button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
1049
1240
  </div>
1050
1241
  <div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -1081,26 +1272,26 @@
1081
1272
  </form>
1082
1273
  </div>
1083
1274
  </div>
1084
- `;const y=document.getElementById("knowledgeList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(B.forEach(g=>{var d;const r=document.createElement("div");r.className="knowledge-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",r.innerHTML=`
1085
- ${g.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
1086
- <h3 style="margin: 0 0 10px 0; font-size: 18px; ${g.isShared?"padding-right: 80px;":""}">${g.title}</h3>
1087
- <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${g.content.substring(0,150)}${g.content.length>150?"...":""}</p>
1275
+ `;const p=document.getElementById("knowledgeList");M.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(M.forEach(i=>{var r;const t=document.createElement("div");t.className="knowledge-card",t.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",t.innerHTML=`
1276
+ ${i.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
1277
+ <h3 style="margin: 0 0 10px 0; font-size: 18px; ${i.isShared?"padding-right: 80px;":""}">${i.title}</h3>
1278
+ <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${i.content.substring(0,150)}${i.content.length>150?"...":""}</p>
1088
1279
  <div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
1089
- <span>👤 ${((d=g.author)==null?void 0:d.username)||"未知"}</span>
1090
- <span style="margin-left: 15px;">📅 ${new Date(g.createdAt).toLocaleDateString()}</span>
1280
+ <span>👤 ${((r=i.author)==null?void 0:r.username)||"未知"}</span>
1281
+ <span style="margin-left: 15px;">📅 ${new Date(i.createdAt).toLocaleDateString()}</span>
1091
1282
  </div>
1092
- ${g.tags&&g.tags.length>0?`
1283
+ ${i.tags&&i.tags.length>0?`
1093
1284
  <div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
1094
- ${g.tags.map(s=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${s}</span>`).join("")}
1285
+ ${i.tags.map(l=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${l}</span>`).join("")}
1095
1286
  </div>
1096
1287
  `:""}
1097
1288
  <div style="display: flex; gap: 10px;">
1098
- <button class="btn-secondary btn-sm" data-id="${g._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
1099
- <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1289
+ <button class="btn-secondary btn-sm" data-id="${i._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
1290
+ <button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1100
1291
  </div>
1101
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="edit"]').forEach(g=>{g.addEventListener("click",async()=>{var d;const r=B.find(s=>s._id===g.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=r.title,document.querySelector('[name="content"]').value=r.content,document.querySelector('[name="tags"]').value=((d=r.tags)==null?void 0:d.join(", "))||"",document.getElementById("isSharedCheckbox").checked=r.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=r._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=await fetch(`http://localhost:3000/api/backup/download/${g.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${u}`}});if(!r.ok)throw new Error("下载失败");const d=await r.blob(),s=window.URL.createObjectURL(d),c=document.createElement("a");c.href=s,c.download=g.dataset.filename,document.body.appendChild(c),c.click(),window.URL.revokeObjectURL(s),document.body.removeChild(c)}catch(r){alert("下载失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await F(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={title:r.get("title"),content:r.get("content"),tags:r.get("tags").split(",").map(s=>s.trim()).filter(s=>s),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const s=g.target.dataset.editId,c=s?`http://localhost:3000/api/knowledge/${s}`:"http://localhost:3000/api/knowledge",w=await fetch(c,{method:s?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)});if(!w.ok){const p=await w.json();throw new Error(p.message||"操作失败")}const P=await w.json();console.log("知识库操作结果:",P),alert(s?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await F(h)}catch(s){console.error("知识库操作错误:",s),alert("操作失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}function x(h){if(!h)return"未设置";if(typeof h=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[h]||h;const m=[];if(h.event){const u={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};m.push(u[h.event]||h.event)}if(h.conditions&&Object.keys(h.conditions).length>0){const u=[];for(const[b,k]of Object.entries(h.conditions)){const y={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[b]||b;u.push(`${y}=${k}`)}u.length>0&&m.push(`(条件: ${u.join(", ")})`)}return h.schedule&&m.push(`⏱️ 定时: ${h.schedule}`),m.length>0?m.join(" "):"自定义触发条件"}async function f(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),B=((m=(await(await fetch(`http://localhost:3000/api/workflows/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.workflows)||[];h.innerHTML=`
1292
+ `,t.onmouseenter=()=>{t.style.transform="translateY(-4px)",t.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},t.onmouseleave=()=>{t.style.transform="translateY(0)",t.style.boxShadow="none"},p.appendChild(t)}),document.querySelectorAll('[data-action="edit"]').forEach(i=>{i.addEventListener("click",async()=>{var r;const t=M.find(l=>l._id===i.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=t.title,document.querySelector('[name="content"]').value=t.content,document.querySelector('[name="tags"]').value=((r=t.tags)==null?void 0:r.join(", "))||"",document.getElementById("isSharedCheckbox").checked=t.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=t._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(i=>{i.addEventListener("click",async()=>{try{const t=await fetch(`http://localhost:3000/api/backup/download/${i.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${m}`}});if(!t.ok)throw new Error("下载失败");const r=await t.blob(),l=window.URL.createObjectURL(r),h=document.createElement("a");h.href=l,h.download=i.dataset.filename,document.body.appendChild(h),h.click(),window.URL.revokeObjectURL(l),document.body.removeChild(h)}catch(t){alert("下载失败: "+t.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(i=>{i.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${i.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${m}`}}),alert("删除成功!"),await f(y)}catch(t){alert("删除失败: "+t.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async i=>{i.preventDefault();const t=new FormData(i.target),r={title:t.get("title"),content:t.get("content"),tags:t.get("tags").split(",").map(l=>l.trim()).filter(l=>l),groupId:d._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const l=i.target.dataset.editId,h=l?`http://localhost:3000/api/knowledge/${l}`:"http://localhost:3000/api/knowledge",k=await fetch(h,{method:l?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${m}`},body:JSON.stringify(r)});if(!k.ok){const b=await k.json();throw new Error(b.message||"操作失败")}const H=await k.json();console.log("知识库操作结果:",H),alert(l?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await f(y)}catch(l){console.error("知识库操作错误:",l),alert("操作失败: "+l.message)}})}catch(m){y.innerHTML=`<div class="empty-state">加载失败: ${m.message}</div>`}}function B(y){if(!y)return"未设置";if(typeof y=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[y]||y;const u=[];if(y.event){const m={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};u.push(m[y.event]||y.event)}if(y.conditions&&Object.keys(y.conditions).length>0){const m=[];for(const[g,x]of Object.entries(y.conditions)){const p={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[g]||g;m.push(`${p}=${x}`)}m.length>0&&u.push(`(条件: ${m.join(", ")})`)}return y.schedule&&u.push(`⏱️ 定时: ${y.schedule}`),u.length>0?u.join(" "):"自定义触发条件"}async function D(y){var u;if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const m=localStorage.getItem("token"),M=((u=(await(await fetch(`http://localhost:3000/api/workflows/group/${d._id}`,{headers:{Authorization:`Bearer ${m}`}})).json()).data)==null?void 0:u.workflows)||[];y.innerHTML=`
1102
1293
  <div class="view-header">
1103
- <h2>⚙️ 工作流管理 - ${i.name}</h2>
1294
+ <h2>⚙️ 工作流管理 - ${d.name}</h2>
1104
1295
  <button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</button>
1105
1296
  </div>
1106
1297
  <div class="workflow-grid" id="workflowList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -1163,43 +1354,43 @@
1163
1354
  </form>
1164
1355
  </div>
1165
1356
  </div>
1166
- `;const y=document.getElementById("workflowList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无工作流</div>':(B.forEach(g=>{const r=document.createElement("div");r.className="workflow-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const d=g.status==="active";r.innerHTML=`
1357
+ `;const p=document.getElementById("workflowList");M.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无工作流</div>':(M.forEach(i=>{const t=document.createElement("div");t.className="workflow-card",t.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const r=i.status==="active";t.innerHTML=`
1167
1358
  <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
1168
- <h3 style="margin: 0; font-size: 18px;">${g.name}</h3>
1169
- <span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${d?"var(--success)":"var(--warning)"}; color: white;">${d?"✅ 活跃":"⏸️ 暂停"}</span>
1359
+ <h3 style="margin: 0; font-size: 18px;">${i.name}</h3>
1360
+ <span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${r?"var(--success)":"var(--warning)"}; color: white;">${r?"✅ 活跃":"⏸️ 暂停"}</span>
1170
1361
  </div>
1171
- <p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${g.description||"无描述"}</p>
1362
+ <p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${i.description||"无描述"}</p>
1172
1363
  <div class="workflow-meta" style="font-size: 12px; color: var(--text-tertiary); margin: 10px 0;">
1173
- <span>🔔 触发条件: ${x(g.trigger)}</span>
1364
+ <span>🔔 触发条件: ${B(i.trigger)}</span>
1174
1365
  </div>
1175
1366
  <div style="display: flex; gap: 10px; margin-top: 15px;">
1176
- <button class="btn-secondary btn-sm" data-id="${g._id}" data-action="toggle" style="flex: 1;">
1177
- ${d?"⏸️ 暂停":"▶️ 启用"}
1367
+ <button class="btn-secondary btn-sm" data-id="${i._id}" data-action="toggle" style="flex: 1;">
1368
+ ${r?"⏸️ 暂停":"▶️ 启用"}
1178
1369
  </button>
1179
- <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1370
+ <button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1180
1371
  </div>
1181
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="toggle"]').forEach(g=>{g.addEventListener("click",async()=>{try{await fetch(`http://localhost:3000/api/workflows/${g.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${u}`}}),await f(h)}catch(r){alert("操作失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:3000/api/workflows/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await f(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={name:r.get("name"),description:r.get("description"),trigger:r.get("trigger"),groupId:i._id,actions:[]};try{await fetch("http://localhost:3000/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await f(h)}catch(s){alert("创建失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function M(h){var m;try{const u=localStorage.getItem("token"),B=((m=(await(await fetch("http://localhost:3000/api/backup/list",{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.backups)||[];h.innerHTML=`
1372
+ `,t.onmouseenter=()=>{t.style.transform="translateY(-4px)",t.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},t.onmouseleave=()=>{t.style.transform="translateY(0)",t.style.boxShadow="none"},p.appendChild(t)}),document.querySelectorAll('[data-action="toggle"]').forEach(i=>{i.addEventListener("click",async()=>{try{await fetch(`http://localhost:3000/api/workflows/${i.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${m}`}}),await D(y)}catch(t){alert("操作失败: "+t.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(i=>{i.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:3000/api/workflows/${i.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${m}`}}),alert("删除成功!"),await D(y)}catch(t){alert("删除失败: "+t.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async i=>{i.preventDefault();const t=new FormData(i.target),r={name:t.get("name"),description:t.get("description"),trigger:t.get("trigger"),groupId:d._id,actions:[]};try{await fetch("http://localhost:3000/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${m}`},body:JSON.stringify(r)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await D(y)}catch(l){alert("创建失败: "+l.message)}})}catch(m){y.innerHTML=`<div class="empty-state">加载失败: ${m.message}</div>`}}async function $(y){var u;try{const m=localStorage.getItem("token"),M=((u=(await(await fetch("http://localhost:3000/api/backup/list",{headers:{Authorization:`Bearer ${m}`}})).json()).data)==null?void 0:u.backups)||[];y.innerHTML=`
1182
1373
  <div class="view-header">
1183
1374
  <h2>💾 备份管理</h2>
1184
1375
  <button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
1185
1376
  </div>
1186
1377
  <div class="backup-grid" id="backupList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
1187
- `;const y=document.getElementById("backupList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无备份</div>':(B.forEach(g=>{const r=document.createElement("div");r.className="backup-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const d=(g.size/1024/1024).toFixed(2);r.innerHTML=`
1378
+ `;const p=document.getElementById("backupList");M.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无备份</div>':(M.forEach(i=>{const t=document.createElement("div");t.className="backup-card",t.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const r=(i.size/1024/1024).toFixed(2);t.innerHTML=`
1188
1379
  <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 15px;">
1189
1380
  <div style="font-size: 48px;">📦</div>
1190
1381
  <div style="flex: 1;">
1191
- <h3 style="margin: 0 0 5px 0; font-size: 16px;">${g.filename}</h3>
1192
- <p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${d} MB</p>
1382
+ <h3 style="margin: 0 0 5px 0; font-size: 16px;">${i.filename}</h3>
1383
+ <p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${r} MB</p>
1193
1384
  </div>
1194
1385
  </div>
1195
1386
  <div class="backup-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 15px;">
1196
- <span>📅 ${new Date(g.createdAt).toLocaleString()}</span>
1387
+ <span>📅 ${new Date(i.createdAt).toLocaleString()}</span>
1197
1388
  </div>
1198
1389
  <div style="display: flex; gap: 10px;">
1199
- <button class="btn-primary btn-sm" data-backup-name="${g.name}" data-filename="${g.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
1200
- <button class="btn-danger btn-sm" data-backup-name="${g.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1390
+ <button class="btn-primary btn-sm" data-backup-name="${i.name}" data-filename="${i.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
1391
+ <button class="btn-danger btn-sm" data-backup-name="${i.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1201
1392
  </div>
1202
- `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=g.dataset.backupName,d=g.dataset.filename,s=localStorage.getItem("token");console.log("开始下载备份:",{backupName:r,filename:d});const c=await fetch(`http://localhost:3000/api/backup/download/${r}`,{method:"GET",headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`下载失败: ${c.status} ${c.statusText}`);const v=await c.blob();console.log("文件下载成功,大小:",v.size,"bytes");const w=window.URL.createObjectURL(v),P=document.createElement("a");P.href=w,P.download=d,P.style.display="none",document.body.appendChild(P),P.click(),setTimeout(()=>{document.body.removeChild(P),window.URL.revokeObjectURL(w)},100);const p=g.textContent;g.textContent="✅ 下载成功",g.disabled=!0,setTimeout(()=>{g.textContent=p,g.disabled=!1},2e3)}catch(r){console.error("下载失败:",r),alert("下载失败: "+r.message),g.textContent="⬇️ 下载",g.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const r=g.dataset.backupName;console.log("删除备份:",r);const d=await fetch(`http://localhost:3000/api/backup/${r}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}});if(!d.ok)throw new Error(`删除失败: ${d.status}`);alert("删除成功!"),await M(h)}catch(r){console.error("删除失败:",r),alert("删除失败: "+r.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const g=document.getElementById("createBackupBtn");g.disabled=!0,g.textContent="⏳ 创建中...";try{await fetch("http://localhost:3000/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${u}`}}),alert("备份创建成功!"),await M(h)}catch(r){alert("创建失败: "+r.message)}finally{g.disabled=!1,g.textContent="➕ 创建备份"}}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function A(h){h.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function I(h){h.innerHTML=`
1393
+ `,t.onmouseenter=()=>{t.style.transform="translateY(-4px)",t.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},t.onmouseleave=()=>{t.style.transform="translateY(0)",t.style.boxShadow="none"},p.appendChild(t)}),document.querySelectorAll('[data-action="download"]').forEach(i=>{i.addEventListener("click",async()=>{try{const t=i.dataset.backupName,r=i.dataset.filename,l=localStorage.getItem("token");console.log("开始下载备份:",{backupName:t,filename:r});const h=await fetch(`http://localhost:3000/api/backup/download/${t}`,{method:"GET",headers:{Authorization:`Bearer ${l}`}});if(!h.ok)throw new Error(`下载失败: ${h.status} ${h.statusText}`);const w=await h.blob();console.log("文件下载成功,大小:",w.size,"bytes");const k=window.URL.createObjectURL(w),H=document.createElement("a");H.href=k,H.download=r,H.style.display="none",document.body.appendChild(H),H.click(),setTimeout(()=>{document.body.removeChild(H),window.URL.revokeObjectURL(k)},100);const b=i.textContent;i.textContent="✅ 下载成功",i.disabled=!0,setTimeout(()=>{i.textContent=b,i.disabled=!1},2e3)}catch(t){console.error("下载失败:",t),alert("下载失败: "+t.message),i.textContent="⬇️ 下载",i.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(i=>{i.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const t=i.dataset.backupName;console.log("删除备份:",t);const r=await fetch(`http://localhost:3000/api/backup/${t}`,{method:"DELETE",headers:{Authorization:`Bearer ${m}`}});if(!r.ok)throw new Error(`删除失败: ${r.status}`);alert("删除成功!"),await $(y)}catch(t){console.error("删除失败:",t),alert("删除失败: "+t.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const i=document.getElementById("createBackupBtn");i.disabled=!0,i.textContent="⏳ 创建中...";try{await fetch("http://localhost:3000/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${m}`}}),alert("备份创建成功!"),await $(y)}catch(t){alert("创建失败: "+t.message)}finally{i.disabled=!1,i.textContent="➕ 创建备份"}}})}catch(m){y.innerHTML=`<div class="empty-state">加载失败: ${m.message}</div>`}}async function C(y){y.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function W(y){y.innerHTML=`
1203
1394
  <div class="view-header">
1204
1395
  <h2>📤 数据导出</h2>
1205
1396
  </div>
@@ -1244,17 +1435,17 @@
1244
1435
  <div id="historyList">加载中...</div>
1245
1436
  </div>
1246
1437
  </div>
1247
- `;try{const m=localStorage.getItem("token"),b=await(await fetch("http://localhost:3000/api/export/history",{headers:{Authorization:`Bearer ${m}`}})).json(),k=document.getElementById("historyList");b.exports&&b.exports.length>0?k.innerHTML=b.exports.map(B=>`
1438
+ `;try{const u=localStorage.getItem("token"),g=await(await fetch("http://localhost:3000/api/export/history",{headers:{Authorization:`Bearer ${u}`}})).json(),x=document.getElementById("historyList");g.exports&&g.exports.length>0?x.innerHTML=g.exports.map(M=>`
1248
1439
  <div class="export-item" style="display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--bg); border-radius: 8px; margin-bottom: 10px;">
1249
1440
  <div>
1250
- <div style="font-weight: 600; margin-bottom: 5px;">📦 ${B.format.toUpperCase()} 导出</div>
1251
- <div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(B.createdAt).toLocaleString()}</div>
1441
+ <div style="font-weight: 600; margin-bottom: 5px;">📦 ${M.format.toUpperCase()} 导出</div>
1442
+ <div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(M.createdAt).toLocaleString()}</div>
1252
1443
  </div>
1253
- <a href="http://localhost:3000/api/export/download/${B.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
1444
+ <a href="http://localhost:3000/api/export/download/${M.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
1254
1445
  </div>
1255
- `).join(""):k.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const m={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},u=document.getElementById("exportBtn");u.disabled=!0,u.textContent="⏳ 导出中...";try{const b=localStorage.getItem("token"),k=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${b}`},body:JSON.stringify(m)});if(k.ok){const B=await k.blob(),y=window.URL.createObjectURL(B),g=document.createElement("a");g.href=y,g.download=`export-${Date.now()}.${m.format}`,g.click(),alert("导出成功!"),await I(h)}else throw new Error("导出失败")}catch(b){alert("导出失败: "+b.message)}finally{u.disabled=!1,u.textContent="🚀 开始导出"}})}async function z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
1446
+ `).join(""):x.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const u={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},m=document.getElementById("exportBtn");m.disabled=!0,m.textContent="⏳ 导出中...";try{const g=localStorage.getItem("token"),x=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`},body:JSON.stringify(u)});if(x.ok){const M=await x.blob(),p=window.URL.createObjectURL(M),i=document.createElement("a");i.href=p,i.download=`export-${Date.now()}.${u.format}`,i.click(),alert("导出成功!"),await W(y)}else throw new Error("导出失败")}catch(g){alert("导出失败: "+g.message)}finally{m.disabled=!1,m.textContent="🚀 开始导出"}})}async function O(y){if(!d){y.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}y.innerHTML=`
1256
1447
  <div class="view-header">
1257
- <h2>🎨 协作白板 - ${i.name}</h2>
1448
+ <h2>🎨 协作白板 - ${d.name}</h2>
1258
1449
  <div style="display: flex; gap: 10px;">
1259
1450
  <button class="btn-secondary" id="clearCanvas">清空画布</button>
1260
1451
  <button class="btn-primary" id="saveCanvas">保存白板</button>
@@ -1271,10 +1462,10 @@
1271
1462
  </div>
1272
1463
  <canvas id="whiteboard" width="1200" height="600" style="border: 1px solid var(--border); background: white; cursor: crosshair;"></canvas>
1273
1464
  <div class="whiteboard-users" id="whiteboardUsers">
1274
- <span class="user-badge">👤 ${t.username}</span>
1465
+ <span class="user-badge">👤 ${a.username}</span>
1275
1466
  </div>
1276
1467
  </div>
1277
- `;const m=document.getElementById("whiteboard"),u=m.getContext("2d");let b=!1,k="pen",B="#000000",y=3;document.querySelectorAll(".tool-btn").forEach(d=>{d.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(s=>s.classList.remove("active")),d.classList.add("active"),k=d.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",d=>{B=d.target.value}),document.getElementById("brushSize").addEventListener("input",d=>{y=d.target.value});let g=0,r=0;m.addEventListener("mousedown",d=>{b=!0;const s=m.getBoundingClientRect();g=d.clientX-s.left,r=d.clientY-s.top}),m.addEventListener("mousemove",d=>{if(!b)return;const s=m.getBoundingClientRect(),c=d.clientX-s.left,v=d.clientY-s.top;u.beginPath(),u.moveTo(g,r),u.lineTo(c,v),u.strokeStyle=k==="eraser"?"#ffffff":B,u.lineWidth=y,u.lineCap="round",u.stroke(),g=c,r=v,e.sendWhiteboardData(i._id,{tool:k,color:B,size:y,from:{x:g,y:r},to:{x:c,y:v}})}),m.addEventListener("mouseup",()=>{b=!1}),m.addEventListener("mouseleave",()=>{b=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&u.clearRect(0,0,m.width,m.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const d=m.toDataURL("image/png"),s=document.createElement("a");s.download=`whiteboard-${Date.now()}.png`,s.href=d,s.click(),alert("白板已保存!")}),e.on("whiteboard_draw",d=>{d.groupId===i._id&&d.userId!==o&&(u.beginPath(),u.moveTo(d.from.x,d.from.y),u.lineTo(d.to.x,d.to.y),u.strokeStyle=d.tool==="eraser"?"#ffffff":d.color,u.lineWidth=d.size,u.lineCap="round",u.stroke())})}async function W(h){h.innerHTML=`
1468
+ `;const u=document.getElementById("whiteboard"),m=u.getContext("2d");let g=!1,x="pen",M="#000000",p=3;document.querySelectorAll(".tool-btn").forEach(r=>{r.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(l=>l.classList.remove("active")),r.classList.add("active"),x=r.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",r=>{M=r.target.value}),document.getElementById("brushSize").addEventListener("input",r=>{p=r.target.value});let i=0,t=0;u.addEventListener("mousedown",r=>{g=!0;const l=u.getBoundingClientRect();i=r.clientX-l.left,t=r.clientY-l.top}),u.addEventListener("mousemove",r=>{if(!g)return;const l=u.getBoundingClientRect(),h=r.clientX-l.left,w=r.clientY-l.top;m.beginPath(),m.moveTo(i,t),m.lineTo(h,w),m.strokeStyle=x==="eraser"?"#ffffff":M,m.lineWidth=p,m.lineCap="round",m.stroke(),i=h,t=w,e.sendWhiteboardData(d._id,{tool:x,color:M,size:p,from:{x:i,y:t},to:{x:h,y:w}})}),u.addEventListener("mouseup",()=>{g=!1}),u.addEventListener("mouseleave",()=>{g=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&m.clearRect(0,0,u.width,u.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const r=u.toDataURL("image/png"),l=document.createElement("a");l.download=`whiteboard-${Date.now()}.png`,l.href=r,l.click(),alert("白板已保存!")}),e.on("whiteboard_draw",r=>{r.groupId===d._id&&r.userId!==s&&(m.beginPath(),m.moveTo(r.from.x,r.from.y),m.lineTo(r.to.x,r.to.y),m.strokeStyle=r.tool==="eraser"?"#ffffff":r.color,m.lineWidth=r.size,m.lineCap="round",m.stroke())})}async function se(y){y.innerHTML=`
1278
1469
  <div class="view-header" style="margin-bottom: 30px;">
1279
1470
  <h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
1280
1471
  <span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚙️ 设置中心</span>
@@ -1290,11 +1481,11 @@
1290
1481
  </div>
1291
1482
  <div class="setting-item" style="margin-bottom: 20px;">
1292
1483
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">👤 用户名</label>
1293
- <input type="text" value="${t.username}" disabled style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--bg-tertiary); cursor: not-allowed;">
1484
+ <input type="text" value="${a.username}" disabled style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--bg-tertiary); cursor: not-allowed;">
1294
1485
  </div>
1295
1486
  <div class="setting-item" style="margin-bottom: 20px;">
1296
1487
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">📧 邮箱</label>
1297
- <input type="email" id="userEmail" value="${t.email||""}" placeholder="请输入邮箱地址" style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; transition: border-color 0.2s;">
1488
+ <input type="email" id="userEmail" value="${a.email||""}" placeholder="请输入邮箱地址" style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; transition: border-color 0.2s;">
1298
1489
  </div>
1299
1490
  <div class="setting-item">
1300
1491
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">🔐 密码</label>
@@ -1370,7 +1561,7 @@
1370
1561
  <button class="btn-primary" id="saveSettingsBtn" style="padding: 12px 32px; border-radius: 10px; font-weight: 600; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; cursor: pointer; transition: transform 0.2s;">💾 保存设置</button>
1371
1562
  </div>
1372
1563
  </div>
1373
- `,document.querySelectorAll(".settings-card").forEach(u=>{u.addEventListener("mouseenter",()=>{u.style.transform="translateY(-4px)",u.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),u.addEventListener("mouseleave",()=>{u.style.transform="translateY(0)",u.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(u=>{u.addEventListener("focus",()=>{u.style.borderColor="var(--primary)"}),u.addEventListener("blur",()=>{u.style.borderColor="var(--border)"})}),document.getElementById("changePasswordBtn").addEventListener("click",()=>{prompt("请输入新密码:")&&alert("密码修改功能开发中...")}),document.getElementById("resetSettingsBtn").addEventListener("click",()=>{confirm("确定要重置所有设置吗?")&&location.reload()}),document.getElementById("saveSettingsBtn").addEventListener("click",()=>{const u={email:document.getElementById("userEmail").value,emailNotifications:document.getElementById("emailNotifications").checked,desktopNotifications:document.getElementById("desktopNotifications").checked,soundNotifications:document.getElementById("soundNotifications").checked,theme:document.getElementById("themeSelect").value,language:document.getElementById("languageSelect").value};localStorage.setItem("userSettings",JSON.stringify(u)),O(u.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",u=>{O(u.target.value)});const m=localStorage.getItem("userSettings");if(m){const u=JSON.parse(m);u.email&&(document.getElementById("userEmail").value=u.email),document.getElementById("emailNotifications").checked=u.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=u.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=u.soundNotifications!==!1,u.theme&&(document.getElementById("themeSelect").value=u.theme,O(u.theme)),u.language&&(document.getElementById("languageSelect").value=u.language)}}function O(h){const m=document.documentElement,u={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},b=u[h]||u.dark;m.style.setProperty("--primary",b.primary),m.style.setProperty("--primary-dark",b.primaryDark),m.style.setProperty("--secondary",b.secondary),m.style.setProperty("--bg-dark",b.bgDark),m.style.setProperty("--bg-card",b.bgCard),m.style.setProperty("--bg-hover",b.bgHover),m.style.setProperty("--text-primary",b.textPrimary),m.style.setProperty("--text-secondary",b.textSecondary),m.style.setProperty("--border",b.border),localStorage.setItem("currentTheme",h)}async function oe(h){h.innerHTML=`
1564
+ `,document.querySelectorAll(".settings-card").forEach(m=>{m.addEventListener("mouseenter",()=>{m.style.transform="translateY(-4px)",m.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),m.addEventListener("mouseleave",()=>{m.style.transform="translateY(0)",m.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(m=>{m.addEventListener("focus",()=>{m.style.borderColor="var(--primary)"}),m.addEventListener("blur",()=>{m.style.borderColor="var(--border)"})}),document.getElementById("changePasswordBtn").addEventListener("click",()=>{prompt("请输入新密码:")&&alert("密码修改功能开发中...")}),document.getElementById("resetSettingsBtn").addEventListener("click",()=>{confirm("确定要重置所有设置吗?")&&location.reload()}),document.getElementById("saveSettingsBtn").addEventListener("click",()=>{const m={email:document.getElementById("userEmail").value,emailNotifications:document.getElementById("emailNotifications").checked,desktopNotifications:document.getElementById("desktopNotifications").checked,soundNotifications:document.getElementById("soundNotifications").checked,theme:document.getElementById("themeSelect").value,language:document.getElementById("languageSelect").value};localStorage.setItem("userSettings",JSON.stringify(m)),X(m.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",m=>{X(m.target.value)});const u=localStorage.getItem("userSettings");if(u){const m=JSON.parse(u);m.email&&(document.getElementById("userEmail").value=m.email),document.getElementById("emailNotifications").checked=m.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=m.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=m.soundNotifications!==!1,m.theme&&(document.getElementById("themeSelect").value=m.theme,X(m.theme)),m.language&&(document.getElementById("languageSelect").value=m.language)}}function X(y){const u=document.documentElement,m={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},g=m[y]||m.dark;u.style.setProperty("--primary",g.primary),u.style.setProperty("--primary-dark",g.primaryDark),u.style.setProperty("--secondary",g.secondary),u.style.setProperty("--bg-dark",g.bgDark),u.style.setProperty("--bg-card",g.bgCard),u.style.setProperty("--bg-hover",g.bgHover),u.style.setProperty("--text-primary",g.textPrimary),u.style.setProperty("--text-secondary",g.textSecondary),u.style.setProperty("--border",g.border),localStorage.setItem("currentTheme",y)}async function de(y){y.innerHTML=`
1374
1565
  <div class="view-header" style="margin-bottom: 30px;">
1375
1566
  <h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
1376
1567
  <span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">❓ 帮助中心</span>
@@ -1550,29 +1741,29 @@
1550
1741
  </div>
1551
1742
  </div>
1552
1743
  </div>
1553
- `,document.querySelectorAll(".help-card").forEach(m=>{m.addEventListener("mouseenter",()=>{m.style.transform="translateY(-4px)",m.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),m.addEventListener("mouseleave",()=>{m.style.transform="translateY(0)";const u=m.style.background.includes("gradient");m.style.boxShadow=u?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function K(h){const m=document.getElementById("contentArea");switch(h){case"groups":await $(m);break;case"tasks":await _(m);break;case"documents":await R(m);break;case"chat":await U(m);break;case"files":await Z(m);break;case"search":await ne(m);break;case"call":await V(m);break;case"audit":await Q(m);break;case"knowledge":await F(m);break;case"workflow":await f(m);break;case"backup":await M(m);break;case"export":await I(m);break;case"ai":await A(m);break;case"export":await I(m);break;case"whiteboard":await z(m);break;case"settings":await W(m);break;case"help":await oe(m);break}}K("groups")}function un(t){const e=document.documentElement,n={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},a=n[t]||n.dark;e.style.setProperty("--primary",a.primary),e.style.setProperty("--primary-dark",a.primaryDark),e.style.setProperty("--secondary",a.secondary),e.style.setProperty("--bg-dark",a.bgDark),e.style.setProperty("--bg-card",a.bgCard),e.style.setProperty("--bg-hover",a.bgHover),e.style.setProperty("--text-primary",a.textPrimary),e.style.setProperty("--text-secondary",a.textSecondary),e.style.setProperty("--border",a.border)}function gn(t,e){const n=document.getElementById("app"),a=new ct,l=new Be,o=t.id||t._id;let i=null,S=[];function N(T){if(T.startsWith("[白板作品]")){const E=T.replace("[白板作品]","").trim(),F=E.includes("/api/files/")&&E.includes("/download");let x=E;if(F&&!E.includes("token=")){const f=localStorage.getItem("token");x=E.includes("?")?`${E}&token=${f}`:`${E}?token=${f}`}return`
1744
+ `,document.querySelectorAll(".help-card").forEach(u=>{u.addEventListener("mouseenter",()=>{u.style.transform="translateY(-4px)",u.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),u.addEventListener("mouseleave",()=>{u.style.transform="translateY(0)";const m=u.style.background.includes("gradient");u.style.boxShadow=m?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function pe(y){const u=document.getElementById("contentArea");switch(y){case"groups":await E(u);break;case"tasks":await _(u);break;case"documents":await U(u);break;case"chat":await q(u);break;case"files":await ee(u);break;case"search":await T(u);break;case"call":await V(u);break;case"audit":await te(u);break;case"polls":await ae(u);break;case"knowledge":await f(u);break;case"workflow":await D(u);break;case"backup":await $(u);break;case"export":await W(u);break;case"ai":await C(u);break;case"export":await W(u);break;case"whiteboard":await O(u);break;case"settings":await se(u);break;case"help":await de(u);break}}pe("groups")}function gn(a){const e=document.documentElement,o={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},n=o[a]||o.dark;e.style.setProperty("--primary",n.primary),e.style.setProperty("--primary-dark",n.primaryDark),e.style.setProperty("--secondary",n.secondary),e.style.setProperty("--bg-dark",n.bgDark),e.style.setProperty("--bg-card",n.bgCard),e.style.setProperty("--bg-hover",n.bgHover),e.style.setProperty("--text-primary",n.textPrimary),e.style.setProperty("--text-secondary",n.textSecondary),e.style.setProperty("--border",n.border)}function yn(a,e){const o=document.getElementById("app"),n=new ut,c=new Ce,s=a.id||a._id;let d=null,I=[];function N(T){if(T.startsWith("[白板作品]")){const L=T.replace("[白板作品]","").trim(),P=L.includes("/api/files/")&&L.includes("/download");let v=L;if(P&&!L.includes("token=")){const f=localStorage.getItem("token");v=L.includes("?")?`${L}&token=${f}`:`${L}?token=${f}`}return`
1554
1745
  <div style="background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);">
1555
1746
  <div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
1556
1747
  <span style="font-size: 18px;">🎨</span>
1557
1748
  <span>白板作品</span>
1558
1749
  </div>
1559
1750
  <div style="position: relative; display: inline-block;">
1560
- <img src="${x}" alt="白板作品"
1751
+ <img src="${v}" alt="白板作品"
1561
1752
  style="max-width: 400px; max-height: 400px; border-radius: 8px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); background: rgba(255,255,255,0.1);"
1562
- onclick="window.open('${x}', '_blank')"
1563
- onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${x}');"
1564
- onload="console.log('白板图片加载成功:', '${x}');">
1753
+ onclick="window.open('${v}', '_blank')"
1754
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${v}');"
1755
+ onload="console.log('白板图片加载成功:', '${v}');">
1565
1756
  <div style="display: none; padding: 20px; background: rgba(255,255,255,0.1); border: 2px dashed rgba(255,255,255,0.3); border-radius: 8px; text-align: center; color: white;">
1566
1757
  <div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
1567
1758
  <div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
1568
1759
  <div style="font-size: 12px;">图片可能已被删除或URL无效</div>
1569
- <button onclick="navigator.clipboard.writeText('${E}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
1760
+ <button onclick="navigator.clipboard.writeText('${L}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
1570
1761
  </div>
1571
1762
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
1572
1763
  </div>
1573
1764
  </div>
1574
- `}if(T.startsWith("[投票]")){const E=T.replace("[投票]","").trim();return`
1575
- <div class="poll-card" data-poll-id="${E}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${E}')">
1765
+ `}if(T.startsWith("[投票]")){const L=T.replace("[投票]","").trim();return`
1766
+ <div class="poll-card" data-poll-id="${L}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${L}')">
1576
1767
  <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
1577
1768
  <span style="font-size: 32px;">📊</span>
1578
1769
  <div style="flex: 1;">
@@ -1584,25 +1775,25 @@
1584
1775
  📋 查看投票详情
1585
1776
  </div>
1586
1777
  </div>
1587
- `}return T}window.viewPollDetail=async T=>{try{const F=(await a.getPoll(T)).poll,x=F.options.reduce((O,oe)=>O+oe.votes.length,0),f=F.options.some(O=>O.votes.includes(o)),M=F.status==="ended"||F.endTime&&new Date(F.endTime)<new Date;let A="";F.options.forEach((O,oe)=>{const K=x>0?(O.votes.length/x*100).toFixed(1):0,h=O.votes.includes(o);A+=`
1588
- <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${h?"var(--primary)":"var(--border)"};">
1778
+ `}return T}window.viewPollDetail=async T=>{try{const P=(await n.getPoll(T)).poll,v=P.options.reduce((O,se)=>O+se.votes.length,0),f=P.options.some(O=>O.votes.includes(s)),B=P.status==="ended"||P.endTime&&new Date(P.endTime)<new Date;let D="";P.options.forEach((O,se)=>{const X=v>0?(O.votes.length/v*100).toFixed(1):0,de=O.votes.includes(s);D+=`
1779
+ <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${de?"var(--primary)":"var(--border)"};">
1589
1780
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1590
1781
  <div style="display: flex; align-items: center; gap: 8px;">
1591
1782
  <span style="font-weight: 500; color: var(--text-primary);">${O.text}</span>
1592
- ${h?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
1783
+ ${de?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
1593
1784
  </div>
1594
- <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${K}%)</span>
1785
+ <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${X}%)</span>
1595
1786
  </div>
1596
1787
  <div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
1597
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${K}%; transition: width 0.3s;"></div>
1788
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${X}%; transition: width 0.3s;"></div>
1598
1789
  </div>
1599
- ${!F.anonymous&&O.votes.length>0?`
1790
+ ${!P.anonymous&&O.votes.length>0?`
1600
1791
  <div style="margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
1601
1792
  投票者: ${O.voterNames?O.voterNames.join(", "):""}
1602
1793
  </div>
1603
1794
  `:""}
1604
1795
  </div>
1605
- `});const I=`
1796
+ `});const $=`
1606
1797
  <div id="pollDetailModal" class="modal" style="display: flex;">
1607
1798
  <div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
1608
1799
  <div class="modal-header">
@@ -1611,37 +1802,37 @@
1611
1802
  </div>
1612
1803
  <div class="modal-body" style="padding: 24px;">
1613
1804
  <div style="margin-bottom: 24px;">
1614
- <h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${F.title}</h2>
1615
- ${F.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${F.description}</p>`:""}
1805
+ <h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${P.title}</h2>
1806
+ ${P.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${P.description}</p>`:""}
1616
1807
 
1617
1808
  <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px;">
1618
1809
  <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1619
- ${F.allowMultiple?"✓ 多选投票":"○ 单选投票"}
1810
+ ${P.allowMultiple?"✓ 多选投票":"○ 单选投票"}
1620
1811
  </span>
1621
1812
  <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1622
- ${F.anonymous?"🔒 匿名投票":"👤 实名投票"}
1813
+ ${P.anonymous?"🔒 匿名投票":"👤 实名投票"}
1623
1814
  </span>
1624
- ${M?'<span style="font-size: 13px; padding: 6px 12px; background: var(--danger); border-radius: 14px; color: white;">已结束</span>':'<span style="font-size: 13px; padding: 6px 12px; background: var(--success); border-radius: 14px; color: white;">进行中</span>'}
1815
+ ${B?'<span style="font-size: 13px; padding: 6px 12px; background: var(--danger); border-radius: 14px; color: white;">已结束</span>':'<span style="font-size: 13px; padding: 6px 12px; background: var(--success); border-radius: 14px; color: white;">进行中</span>'}
1625
1816
  </div>
1626
1817
 
1627
1818
  <div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; border-left: 4px solid var(--primary); margin-bottom: 24px;">
1628
1819
  <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
1629
1820
  <div>
1630
1821
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
1631
- <div style="font-weight: 600; color: var(--text-primary);">👤 ${F.creatorName}</div>
1822
+ <div style="font-weight: 600; color: var(--text-primary);">👤 ${P.creatorName}</div>
1632
1823
  </div>
1633
1824
  <div>
1634
1825
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
1635
- <div style="font-weight: 600; color: var(--primary);">👥 ${x} 人</div>
1826
+ <div style="font-weight: 600; color: var(--primary);">👥 ${v} 人</div>
1636
1827
  </div>
1637
1828
  <div>
1638
1829
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
1639
- <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(F.createdAt).toLocaleString("zh-CN")}</div>
1830
+ <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(P.createdAt).toLocaleString("zh-CN")}</div>
1640
1831
  </div>
1641
- ${F.endTime?`
1832
+ ${P.endTime?`
1642
1833
  <div>
1643
1834
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
1644
- <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(F.endTime).toLocaleString("zh-CN")}</div>
1835
+ <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(P.endTime).toLocaleString("zh-CN")}</div>
1645
1836
  </div>
1646
1837
  `:""}
1647
1838
  </div>
@@ -1650,28 +1841,28 @@
1650
1841
 
1651
1842
  <div style="margin-bottom: 24px;">
1652
1843
  <h3 style="margin-bottom: 16px; color: var(--text-primary);">投票选项</h3>
1653
- ${!M&&!f?`
1844
+ ${!B&&!f?`
1654
1845
  <form id="voteForm">
1655
- ${F.options.map((O,oe)=>{const K=x>0?(O.votes.length/x*100).toFixed(1):0;return`
1846
+ ${P.options.map((O,se)=>{const X=v>0?(O.votes.length/v*100).toFixed(1):0;return`
1656
1847
  <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid var(--border); cursor: pointer;" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'">
1657
1848
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1658
1849
  <div style="display: flex; align-items: center; gap: 8px;">
1659
- <input type="${F.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${oe}" style="width: 18px; height: 18px; cursor: pointer;">
1850
+ <input type="${P.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${se}" style="width: 18px; height: 18px; cursor: pointer;">
1660
1851
  <span style="font-weight: 500; color: var(--text-primary);">${O.text}</span>
1661
1852
  </div>
1662
- <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${K}%)</span>
1853
+ <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${X}%)</span>
1663
1854
  </div>
1664
1855
  <div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
1665
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${K}%; transition: width 0.3s;"></div>
1856
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${X}%; transition: width 0.3s;"></div>
1666
1857
  </div>
1667
1858
  </div>
1668
1859
  `}).join("")}
1669
1860
  <button type="submit" class="btn-primary" style="width: 100%; padding: 12px; margin-top: 16px;">提交投票</button>
1670
1861
  <div style="text-align: center; color: var(--warning); margin-top: 12px; font-size: 13px;">⚠️ 提交后不可修改</div>
1671
1862
  </form>
1672
- `:A}
1863
+ `:D}
1673
1864
  ${f?'<div style="text-align: center; color: var(--success); margin-top: 16px; padding: 12px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; font-weight: 600;">✓ 您已参与投票,投票后不可修改</div>':""}
1674
- ${M?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
1865
+ ${B?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
1675
1866
  </div>
1676
1867
 
1677
1868
  <div style="display: flex; gap: 10px;">
@@ -1680,7 +1871,7 @@
1680
1871
  </div>
1681
1872
  </div>
1682
1873
  </div>
1683
- `,z=document.getElementById("pollDetailModal");z&&z.remove(),document.body.insertAdjacentHTML("beforeend",I),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const W=document.getElementById("voteForm");W&&!M&&!f&&W.addEventListener("submit",async O=>{O.preventDefault();const oe=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(K=>parseInt(K.value));if(oe.length===0){alert("请选择至少一个选项!");return}try{await a.vote(T,oe),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const K=document.querySelector(".nav-item.active");if(K&&K.dataset.view==="tasks"){const h=document.getElementById("contentArea");await _(h)}}catch(K){console.error("投票失败:",K),alert("投票失败:"+K.message)}})}catch(E){console.error("加载投票详情失败:",E),alert("加载投票详情失败:"+E.message)}},n.innerHTML=`
1874
+ `,C=document.getElementById("pollDetailModal");C&&C.remove(),document.body.insertAdjacentHTML("beforeend",$),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const W=document.getElementById("voteForm");W&&!B&&!f&&W.addEventListener("submit",async O=>{O.preventDefault();const se=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(X=>parseInt(X.value));if(se.length===0){alert("请选择至少一个选项!");return}try{await n.vote(T,se),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const X=document.querySelector(".nav-item.active");if(X&&X.dataset.view==="tasks"){const de=document.getElementById("contentArea");await _(de)}}catch(X){console.error("投票失败:",X),alert("投票失败:"+X.message)}})}catch(L){console.error("加载投票详情失败:",L),alert("加载投票详情失败:"+L.message)}},o.innerHTML=`
1684
1875
  <div class="dashboard">
1685
1876
  <aside class="sidebar">
1686
1877
  <div class="sidebar-header">
@@ -1689,9 +1880,9 @@
1689
1880
  </div>
1690
1881
 
1691
1882
  <div class="user-info">
1692
- <div class="avatar">${t.username[0].toUpperCase()}</div>
1883
+ <div class="avatar">${a.username[0].toUpperCase()}</div>
1693
1884
  <div>
1694
- <div class="username">${t.username}</div>
1885
+ <div class="username">${a.username}</div>
1695
1886
  <div class="user-role">普通用户</div>
1696
1887
  </div>
1697
1888
  </div>
@@ -1730,46 +1921,46 @@
1730
1921
  <div id="contentArea"></div>
1731
1922
  </main>
1732
1923
  </div>
1733
- `,document.querySelectorAll(".nav-item").forEach(T=>{T.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(F=>F.classList.remove("active")),T.classList.add("active");const E=T.dataset.view;$(E)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{l.logout()});async function $(T){const E=document.getElementById("contentArea");switch(T){case"groups":await D(E);break;case"allgroups":await H(E);break;case"tasks":await _(E);break;case"documents":await R(E);break;case"files":await Z(E);break;case"chat":await U(E);break;case"search":await V(E);break;case"knowledge":await re(E);break}}async function D(T){S=(await a.getGroups()).groups,T.innerHTML=`
1924
+ `,document.querySelectorAll(".nav-item").forEach(T=>{T.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(P=>P.classList.remove("active")),T.classList.add("active");const L=T.dataset.view;E(L)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()});async function E(T){const L=document.getElementById("contentArea");switch(T){case"groups":await j(L);break;case"allgroups":await F(L);break;case"tasks":await _(L);break;case"documents":await U(L);break;case"files":await ee(L);break;case"chat":await q(L);break;case"search":await V(L);break;case"knowledge":await re(L);break}}async function j(T){I=(await n.getGroups()).groups,T.innerHTML=`
1734
1925
  <div class="view-header">
1735
1926
  <h2>我的群组</h2>
1736
1927
  </div>
1737
1928
  <div class="groups-grid" id="groupsList"></div>
1738
- `;const F=document.getElementById("groupsList");if(S.length===0){F.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}S.forEach(x=>{const f=document.createElement("div");f.className="group-card",f.innerHTML=`
1739
- <h3>${x.name}</h3>
1740
- <p>${x.description||"暂无描述"}</p>
1929
+ `;const P=document.getElementById("groupsList");if(I.length===0){P.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}I.forEach(v=>{const f=document.createElement("div");f.className="group-card",f.innerHTML=`
1930
+ <h3>${v.name}</h3>
1931
+ <p>${v.description||"暂无描述"}</p>
1741
1932
  <div class="group-stats">
1742
- <span>👥 ${x.members.length} 成员</span>
1743
- <span>📄 ${x.documents.length} 文档</span>
1744
- <span>📋 ${x.tasks.length} 任务</span>
1933
+ <span>👥 ${v.members.length} 成员</span>
1934
+ <span>📄 ${v.documents.length} 文档</span>
1935
+ <span>📋 ${v.tasks.length} 任务</span>
1745
1936
  </div>
1746
1937
  <div style="display: flex; gap: 10px; margin-top: 10px;">
1747
- <button class="btn-select" data-id="${x._id}">进入群组</button>
1748
- <button class="btn-secondary" data-id="${x._id}" data-action="leave">退出群组</button>
1938
+ <button class="btn-select" data-id="${v._id}">进入群组</button>
1939
+ <button class="btn-secondary" data-id="${v._id}" data-action="leave">退出群组</button>
1749
1940
  </div>
1750
- `,F.appendChild(f)}),document.querySelectorAll(".btn-select").forEach(x=>{x.addEventListener("click",()=>{i=S.find(f=>f._id===x.dataset.id),e.joinGroup(i._id),alert(`已进入群组: ${i.name}`)})}),document.querySelectorAll('[data-action="leave"]').forEach(x=>{x.addEventListener("click",async()=>{if(confirm("确定要退出该群组吗?"))try{await a.leaveGroup(x.dataset.id),alert("已退出群组"),await D(T)}catch(f){alert("退出失败: "+f.message)}})})}async function H(T){const E=await a.getAllGroups(),x=(await a.getGroups()).groups.map(M=>M._id);T.innerHTML=`
1941
+ `,P.appendChild(f)}),document.querySelectorAll(".btn-select").forEach(v=>{v.addEventListener("click",()=>{d=I.find(f=>f._id===v.dataset.id),e.joinGroup(d._id),alert(`已进入群组: ${d.name}`)})}),document.querySelectorAll('[data-action="leave"]').forEach(v=>{v.addEventListener("click",async()=>{if(confirm("确定要退出该群组吗?"))try{await n.leaveGroup(v.dataset.id),alert("已退出群组"),await j(T)}catch(f){alert("退出失败: "+f.message)}})})}async function F(T){const L=await n.getAllGroups(),v=(await n.getGroups()).groups.map(B=>B._id);T.innerHTML=`
1751
1942
  <div class="view-header">
1752
1943
  <h2>所有群组</h2>
1753
1944
  </div>
1754
1945
  <div class="groups-grid" id="allGroupsList"></div>
1755
- `;const f=document.getElementById("allGroupsList");E.groups.forEach(M=>{const A=x.includes(M._id),I=document.createElement("div");I.className="group-card",I.innerHTML=`
1756
- <h3>${M.name}</h3>
1757
- <p>${M.description||"暂无描述"}</p>
1946
+ `;const f=document.getElementById("allGroupsList");L.groups.forEach(B=>{const D=v.includes(B._id),$=document.createElement("div");$.className="group-card",$.innerHTML=`
1947
+ <h3>${B.name}</h3>
1948
+ <p>${B.description||"暂无描述"}</p>
1758
1949
  <div class="group-stats">
1759
- <span>👥 ${M.members.length} 成员</span>
1760
- <span>📄 ${M.documents.length} 文档</span>
1950
+ <span>👥 ${B.members.length} 成员</span>
1951
+ <span>📄 ${B.documents.length} 文档</span>
1761
1952
  </div>
1762
- ${A?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${M._id}" data-action="join">加入群组</button>`}
1763
- `,f.appendChild(I)}),document.querySelectorAll('[data-action="join"]').forEach(M=>{M.addEventListener("click",async()=>{try{await a.joinGroup(M.dataset.id),alert("加入成功!"),await H(T)}catch(A){alert("加入失败: "+A.message)}})})}async function _(T){try{const E=await a.getMyTasks();let F=[];try{const M=(await a.getGroups()).groups;for(const A of M)try{const I=await a.getGroupPolls(A._id);I.polls&&Array.isArray(I.polls)&&(F=F.concat(I.polls.map(z=>({...z,groupName:A.name}))))}catch(I){console.error(`获取群组 ${A.name} 的投票失败:`,I)}}catch(f){console.error("获取投票失败:",f)}T.innerHTML=`
1953
+ ${D?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${B._id}" data-action="join">加入群组</button>`}
1954
+ `,f.appendChild($)}),document.querySelectorAll('[data-action="join"]').forEach(B=>{B.addEventListener("click",async()=>{try{await n.joinGroup(B.dataset.id),alert("加入成功!"),await F(T)}catch(D){alert("加入失败: "+D.message)}})})}async function _(T){try{const L=await n.getMyTasks();let P=[];try{const B=(await n.getGroups()).groups;for(const D of B)try{const $=await n.getGroupPolls(D._id);$.polls&&Array.isArray($.polls)&&(P=P.concat($.polls.map(C=>({...C,groupName:D.name}))))}catch($){console.error(`获取群组 ${D.name} 的投票失败:`,$)}}catch(f){console.error("获取投票失败:",f)}T.innerHTML=`
1764
1955
  <div class="view-header">
1765
1956
  <h2>我的任务</h2>
1766
1957
  </div>
1767
1958
  <div class="tasks-list" id="tasksList"></div>
1768
- `;const x=document.getElementById("tasksList");if(E.tasks.length===0&&F.length===0){x.innerHTML='<div class="empty-state">暂无任务</div>';return}E.tasks.forEach(f=>{const M=document.createElement("div");M.className=`task-card status-${f.status}`,M.innerHTML=`
1959
+ `;const v=document.getElementById("tasksList");if(L.tasks.length===0&&P.length===0){v.innerHTML='<div class="empty-state">暂无任务</div>';return}L.tasks.forEach(f=>{const B=document.createElement("div");B.className=`task-card status-${f.status}`,B.innerHTML=`
1769
1960
  <h3>${f.title}</h3>
1770
1961
  <p>${f.description}</p>
1771
1962
  <div class="task-meta">
1772
- <span class="status-badge">${ne(f.status)}</span>
1963
+ <span class="status-badge">${ae(f.status)}</span>
1773
1964
  <span>群组: ${f.group.name}</span>
1774
1965
  ${f.deadline?`<span>截止: ${new Date(f.deadline).toLocaleDateString()}</span>`:""}
1775
1966
  </div>
@@ -1778,62 +1969,62 @@
1778
1969
  ${f.status==="pending"?`<button class="btn-primary btn-sm" data-id="${f._id}" data-action="start">开始任务</button>`:""}
1779
1970
  ${f.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${f._id}" data-action="complete">完成任务</button>`:""}
1780
1971
  </div>
1781
- `,x.appendChild(M)}),F.forEach(f=>{const M=f.options.reduce((W,O)=>W+O.votes.length,0),A=f.options.some(W=>W.votes.includes(o)),I=f.status==="ended"||f.endTime&&new Date(f.endTime)<new Date,z=document.createElement("div");z.className="task-card poll-task",z.style.cssText="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-left: 4px solid var(--primary);",z.innerHTML=`
1972
+ `,v.appendChild(B)}),P.forEach(f=>{const B=f.options.reduce((W,O)=>W+O.votes.length,0),D=f.options.some(W=>W.votes.includes(s)),$=f.status==="ended"||f.endTime&&new Date(f.endTime)<new Date,C=document.createElement("div");C.className="task-card poll-task",C.style.cssText="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-left: 4px solid var(--primary);",C.innerHTML=`
1782
1973
  <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
1783
1974
  <span style="font-size: 32px;">📊</span>
1784
1975
  <h3 style="margin: 0;">${f.title}</h3>
1785
1976
  </div>
1786
1977
  <p>${f.description||"暂无描述"}</p>
1787
1978
  <div class="task-meta">
1788
- <span class="status-badge" style="background: ${I?"var(--danger)":"var(--success)"};">${I?"已结束":"进行中"}</span>
1979
+ <span class="status-badge" style="background: ${$?"var(--danger)":"var(--success)"};">${$?"已结束":"进行中"}</span>
1789
1980
  <span>群组: ${f.groupName}</span>
1790
- <span>👥 ${M} 人投票</span>
1791
- ${A?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
1981
+ <span>👥 ${B} 人投票</span>
1982
+ ${D?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
1792
1983
  </div>
1793
1984
  <div class="task-actions">
1794
1985
  <button class="btn-primary btn-sm" data-poll-id="${f._id}" data-action="view-poll">查看详情</button>
1795
1986
  </div>
1796
- `,x.appendChild(z)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(f=>{f.addEventListener("click",async()=>{const M=f.dataset.id,I=f.dataset.action==="start"?"in_progress":"completed";try{await a.updateTaskStatus(M,I),await _(T)}catch(z){alert("操作失败: "+z.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(f=>{f.addEventListener("click",()=>{const M=f.dataset.pollId;window.viewPollDetail(M)})})}catch(E){console.error("获取任务失败:",E),T.innerHTML=`
1987
+ `,v.appendChild(C)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(f=>{f.addEventListener("click",async()=>{const B=f.dataset.id,$=f.dataset.action==="start"?"in_progress":"completed";try{await n.updateTaskStatus(B,$),await _(T)}catch(C){alert("操作失败: "+C.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(f=>{f.addEventListener("click",()=>{const B=f.dataset.pollId;window.viewPollDetail(B)})})}catch(L){console.error("获取任务失败:",L),T.innerHTML=`
1797
1988
  <div class="view-header">
1798
1989
  <h2>我的任务</h2>
1799
1990
  </div>
1800
- <div class="empty-state">加载任务失败: ${E.message}</div>
1801
- `}}async function R(T){if(!i){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const E=await a.getDocuments(i._id);T.innerHTML=`
1991
+ <div class="empty-state">加载任务失败: ${L.message}</div>
1992
+ `}}async function U(T){if(!d){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const L=await n.getDocuments(d._id);T.innerHTML=`
1802
1993
  <div class="view-header">
1803
- <h2>共享文档 - ${i.name}</h2>
1994
+ <h2>共享文档 - ${d.name}</h2>
1804
1995
  </div>
1805
1996
  <div class="documents-list" id="docsList"></div>
1806
- `;const F=document.getElementById("docsList");if(E.documents.length===0){F.innerHTML='<div class="empty-state">暂无文档</div>';return}E.documents.forEach(x=>{const f=document.createElement("div");f.className="document-card",f.innerHTML=`
1807
- <h3>📄 ${x.title}</h3>
1997
+ `;const P=document.getElementById("docsList");if(L.documents.length===0){P.innerHTML='<div class="empty-state">暂无文档</div>';return}L.documents.forEach(v=>{const f=document.createElement("div");f.className="document-card",f.innerHTML=`
1998
+ <h3>📄 ${v.title}</h3>
1808
1999
  <div class="doc-meta">
1809
- <span>创建者: ${x.creator.username}</span>
1810
- <span>${x.permission==="readonly"?"🔒 只读":"✏️ 可编辑"}</span>
1811
- <span>更新: ${new Date(x.updatedAt).toLocaleString()}</span>
2000
+ <span>创建者: ${v.creator.username}</span>
2001
+ <span>${v.permission==="readonly"?"🔒 只读":"✏️ 可编辑"}</span>
2002
+ <span>更新: ${new Date(v.updatedAt).toLocaleString()}</span>
1812
2003
  </div>
1813
- <button class="btn-edit" data-id="${x._id}">
1814
- ${x.permission==="readonly"?"查看":"编辑"}
2004
+ <button class="btn-edit" data-id="${v._id}">
2005
+ ${v.permission==="readonly"?"查看":"编辑"}
1815
2006
  </button>
1816
- `,F.appendChild(f)}),document.querySelectorAll(".btn-edit").forEach(x=>{x.addEventListener("click",()=>{ae(T,x.dataset.id)})})}async function ae(T,E){const x=(await a.getDocument(E)).document;T.innerHTML=`
2007
+ `,P.appendChild(f)}),document.querySelectorAll(".btn-edit").forEach(v=>{v.addEventListener("click",()=>{ne(T,v.dataset.id)})})}async function ne(T,L){const v=(await n.getDocument(L)).document;T.innerHTML=`
1817
2008
  <div class="view-header">
1818
2009
  <button class="btn-back" id="backBtn">← 返回</button>
1819
- <h2>${x.title}</h2>
1820
- <span class="doc-status">${x.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
2010
+ <h2>${v.title}</h2>
2011
+ <span class="doc-status">${v.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
1821
2012
  </div>
1822
2013
  <div class="editor-container">
1823
2014
  <div class="editor-toolbar">
1824
2015
  <div class="online-users" id="onlineUsers">
1825
- <span class="user-badge">👤 ${t.username}</span>
2016
+ <span class="user-badge">👤 ${a.username}</span>
1826
2017
  </div>
1827
- ${x.permission==="editable"?'<button class="btn-primary" id="saveBtn">保存</button>':""}
2018
+ ${v.permission==="editable"?'<button class="btn-primary" id="saveBtn">保存</button>':""}
1828
2019
  </div>
1829
- <div id="editor" ${x.permission==="readonly"?'class="readonly"':""}></div>
2020
+ <div id="editor" ${v.permission==="readonly"?'class="readonly"':""}></div>
1830
2021
  <div class="editor-footer">
1831
- <span>最后编辑: ${new Date(x.updatedAt).toLocaleString()}</span>
2022
+ <span>最后编辑: ${new Date(v.updatedAt).toLocaleString()}</span>
1832
2023
  </div>
1833
2024
  </div>
1834
- `;const f=new Quill("#editor",{theme:"snow",modules:{toolbar:x.permission==="readonly"?!1:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:x.permission==="readonly"});if(f.root.innerHTML=x.content||"",x.permission==="editable"){let M,A;f.on("text-change",()=>{clearTimeout(M),clearTimeout(A),e.sendTyping(E,t.username,!0),M=setTimeout(()=>{e.sendTyping(E,t.username,!1)},1e3),A=setTimeout(async()=>{const I=f.root.innerHTML;try{await a.updateDocument(E,I)}catch(z){console.error("自动保存失败:",z)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const I=f.root.innerHTML;await a.updateDocument(E,I),alert("保存成功!")}catch(I){alert("保存失败: "+I.message)}})}e.on("document_update",M=>{if(M.documentId===E&&M.userId!==t.id){const A=f.getSelection();f.root.innerHTML=M.content,A&&f.setSelection(A)}}),e.on("typing",M=>{if(M.documentId===E&&M.userId!==t.id){const A=document.getElementById("onlineUsers");if(M.isTyping)A.innerHTML+=`<span class="user-badge typing" data-user="${M.userId}">✏️ ${M.username}</span>`;else{const I=A.querySelector(`[data-user="${M.userId}"]`);I&&I.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{R(T)})}async function Z(T){if(!i){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const E=await a.getGroupFiles(i._id);T.innerHTML=`
2025
+ `;const f=new Quill("#editor",{theme:"snow",modules:{toolbar:v.permission==="readonly"?!1:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:v.permission==="readonly"});if(f.root.innerHTML=v.content||"",v.permission==="editable"){let B,D;f.on("text-change",()=>{clearTimeout(B),clearTimeout(D),e.sendTyping(L,a.username,!0),B=setTimeout(()=>{e.sendTyping(L,a.username,!1)},1e3),D=setTimeout(async()=>{const $=f.root.innerHTML;try{await n.updateDocument(L,$)}catch(C){console.error("自动保存失败:",C)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const $=f.root.innerHTML;await n.updateDocument(L,$),alert("保存成功!")}catch($){alert("保存失败: "+$.message)}})}e.on("document_update",B=>{if(B.documentId===L&&B.userId!==a.id){const D=f.getSelection();f.root.innerHTML=B.content,D&&f.setSelection(D)}}),e.on("typing",B=>{if(B.documentId===L&&B.userId!==a.id){const D=document.getElementById("onlineUsers");if(B.isTyping)D.innerHTML+=`<span class="user-badge typing" data-user="${B.userId}">✏️ ${B.username}</span>`;else{const $=D.querySelector(`[data-user="${B.userId}"]`);$&&$.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{U(T)})}async function ee(T){if(!d){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const L=await n.getGroupFiles(d._id);T.innerHTML=`
1835
2026
  <div class="view-header">
1836
- <h2>文件共享 - ${i.name}</h2>
2027
+ <h2>文件共享 - ${d.name}</h2>
1837
2028
  <button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
1838
2029
  </div>
1839
2030
  <div class="files-list" id="filesList"></div>
@@ -1862,27 +2053,27 @@
1862
2053
  </form>
1863
2054
  </div>
1864
2055
  </div>
1865
- `;const F=document.getElementById("filesList");!E.files||E.files.length===0?F.innerHTML='<div class="empty-state">暂无文件</div>':(E.files.forEach(x=>{const f=document.createElement("div");f.className="file-card";const M=se(x.mimetype),A=le(x.size);f.innerHTML=`
1866
- <div class="file-icon">${M}</div>
2056
+ `;const P=document.getElementById("filesList");!L.files||L.files.length===0?P.innerHTML='<div class="empty-state">暂无文件</div>':(L.files.forEach(v=>{const f=document.createElement("div");f.className="file-card";const B=oe(v.mimetype),D=ce(v.size);f.innerHTML=`
2057
+ <div class="file-icon">${B}</div>
1867
2058
  <div class="file-info">
1868
- <h4>${x.originalName}</h4>
2059
+ <h4>${v.originalName}</h4>
1869
2060
  <div class="file-meta">
1870
- <span>上传者: ${x.uploader.username}</span>
1871
- <span>大小: ${A}</span>
1872
- <span>时间: ${new Date(x.createdAt).toLocaleString()}</span>
2061
+ <span>上传者: ${v.uploader.username}</span>
2062
+ <span>大小: ${D}</span>
2063
+ <span>时间: ${new Date(v.createdAt).toLocaleString()}</span>
1873
2064
  </div>
1874
- ${x.description?`<p class="file-description">${x.description}</p>`:""}
2065
+ ${v.description?`<p class="file-description">${v.description}</p>`:""}
1875
2066
  </div>
1876
2067
  <div class="file-actions">
1877
- <a href="${a.getFileDownloadUrl(x._id)}" class="btn-primary" download>下载</a>
1878
- ${String(x.uploader._id)===String(o)?`<button class="btn-danger" data-id="${x._id}" data-action="delete-file">删除</button>`:""}
2068
+ <a href="${n.getFileDownloadUrl(v._id)}" class="btn-primary" download>下载</a>
2069
+ ${String(v.uploader._id)===String(s)?`<button class="btn-danger" data-id="${v._id}" data-action="delete-file">删除</button>`:""}
1879
2070
  </div>
1880
- `,F.appendChild(f)}),document.querySelectorAll('[data-action="delete-file"]').forEach(x=>{x.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await a.deleteFile(x.dataset.id),alert("文件删除成功!"),await Z(T)}catch(f){alert("删除失败: "+f.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async x=>{x.preventDefault();const f=document.getElementById("fileInput"),M=document.getElementById("fileDescription").value;if(!f.files[0]){alert("请选择文件");return}try{await a.uploadFile(i._id,f.files[0],M),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await Z(T)}catch(A){alert("上传失败: "+A.message)}})}catch(E){console.error("获取文件列表失败:",E),T.innerHTML=`
2071
+ `,P.appendChild(f)}),document.querySelectorAll('[data-action="delete-file"]').forEach(v=>{v.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await n.deleteFile(v.dataset.id),alert("文件删除成功!"),await ee(T)}catch(f){alert("删除失败: "+f.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async v=>{v.preventDefault();const f=document.getElementById("fileInput"),B=document.getElementById("fileDescription").value;if(!f.files[0]){alert("请选择文件");return}try{await n.uploadFile(d._id,f.files[0],B),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await ee(T)}catch(D){alert("上传失败: "+D.message)}})}catch(L){console.error("获取文件列表失败:",L),T.innerHTML=`
1881
2072
  <div class="view-header">
1882
2073
  <h2>文件共享</h2>
1883
2074
  </div>
1884
- <div class="empty-state">加载文件失败: ${E.message}</div>
1885
- `}}function se(T){return T.startsWith("image/")?"🖼️":T==="application/pdf"?"📕":T.includes("word")||T.includes("document")?"📘":T.includes("excel")||T.includes("spreadsheet")?"📗":T.includes("zip")||T.includes("compressed")?"📦":"📄"}function le(T){if(T===0)return"0 Bytes";const E=1024,F=["Bytes","KB","MB","GB"],x=Math.floor(Math.log(T)/Math.log(E));return Math.round(T/Math.pow(E,x)*100)/100+" "+F[x]}async function U(T){if(!i){T.innerHTML=`
2075
+ <div class="empty-state">加载文件失败: ${L.message}</div>
2076
+ `}}function oe(T){return T.startsWith("image/")?"🖼️":T==="application/pdf"?"📕":T.includes("word")||T.includes("document")?"📘":T.includes("excel")||T.includes("spreadsheet")?"📗":T.includes("zip")||T.includes("compressed")?"📦":"📄"}function ce(T){if(T===0)return"0 Bytes";const L=1024,P=["Bytes","KB","MB","GB"],v=Math.floor(Math.log(T)/Math.log(L));return Math.round(T/Math.pow(L,v)*100)/100+" "+P[v]}async function q(T){if(!d){T.innerHTML=`
1886
2077
  <div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
1887
2078
  <div style="font-size: 64px; margin-bottom: 20px;">💬</div>
1888
2079
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">群聊</h3>
@@ -1891,15 +2082,15 @@
1891
2082
  前往我的群组
1892
2083
  </button>
1893
2084
  </div>
1894
- `;return}try{let h=function(d,s,c="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(d,{body:s,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},k=function(){const d=document.getElementById("whiteboard");if(!d||d.dataset.initialized)return;d.dataset.initialized="true";const s=d.getContext("2d");d.width=d.offsetWidth,d.height=d.offsetHeight;let c=!1,v="pen",w="#667eea",P=3;document.querySelectorAll(".tool-btn").forEach(p=>{p.addEventListener("click",()=>{v=p.dataset.tool,document.querySelectorAll(".tool-btn").forEach(L=>{L.style.background="var(--bg-secondary)",L.style.color="var(--text-primary)",L.style.border="1px solid var(--border)"}),p.style.background="var(--primary)",p.style.color="white",p.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",p=>{w=p.target.value}),document.getElementById("brushSize").addEventListener("input",p=>{P=p.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(s.clearRect(0,0,d.width,d.height),e.sendWhiteboardClear(i._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const p=d.toDataURL("image/png"),L=document.createElement("canvas");L.width=d.width,L.height=d.height;const j=L.toDataURL("image/png");if(p===j){alert("画布是空的,请先绘制内容!");return}if(p.length*.75/1024/1024<1)e.sendChatMessage(i._id,t.username,`[白板作品]${p}`),alert("白板作品已发送到群聊!");else{const G=await fetch(p).then(te=>te.blob()),q=new FormData;q.append("file",G,`whiteboard-${Date.now()}.png`),q.append("groupId",i._id),q.append("description","协作白板作品");const Y=localStorage.getItem("token"),X=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${Y}`},body:q});if(X.ok){const ie=`http://localhost:3000/api/files/${(await X.json()).file._id}/download?token=${Y}`;e.sendChatMessage(i._id,t.username,`[白板作品]${ie}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(p){console.error("发送白板失败:",p),alert("发送失败,请重试!")}}),d.addEventListener("mousedown",p=>{c=!0;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.beginPath(),s.moveTo(j,C)}),d.addEventListener("mousemove",p=>{if(!c)return;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.lineWidth=P,s.lineCap="round",v==="pen"?(s.strokeStyle=w,s.globalCompositeOperation="source-over"):v==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(j,C),s.stroke(),e.sendWhiteboardDraw(i._id,{tool:v,color:w,size:P,x:j,y:C})}),d.addEventListener("mouseup",()=>{c=!1}),d.addEventListener("mouseleave",()=>{c=!1}),e.on("whiteboard_draw",p=>{p.groupId===i._id&&(s.lineWidth=p.size,s.lineCap="round",p.tool==="pen"?(s.strokeStyle=p.color,s.globalCompositeOperation="source-over"):p.tool==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(p.x,p.y),s.stroke())}),e.on("whiteboard_clear",p=>{p.groupId===i._id&&s.clearRect(0,0,d.width,d.height)})};var E=h,F=k;const f=(await a.getGroup(i._id)).group,M=!!f.mutedAll,A=(f.mutedUsers||[]).map(String).includes(String(o)),I=!M&&!A;T.innerHTML=`
2085
+ `;return}try{let de=function(i,t,r="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(i,{body:t,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},m=function(){const i=document.getElementById("whiteboard");if(!i||i.dataset.initialized)return;i.dataset.initialized="true";const t=i.getContext("2d");i.width=i.offsetWidth,i.height=i.offsetHeight;let r=!1,l="pen",h="#667eea",w=3;document.querySelectorAll(".tool-btn").forEach(k=>{k.addEventListener("click",()=>{l=k.dataset.tool,document.querySelectorAll(".tool-btn").forEach(H=>{H.style.background="var(--bg-secondary)",H.style.color="var(--text-primary)",H.style.border="1px solid var(--border)"}),k.style.background="var(--primary)",k.style.color="white",k.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",k=>{h=k.target.value}),document.getElementById("brushSize").addEventListener("input",k=>{w=k.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(t.clearRect(0,0,i.width,i.height),e.sendWhiteboardClear(d._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const k=i.toDataURL("image/png"),H=document.createElement("canvas");H.width=i.width,H.height=i.height;const b=H.toDataURL("image/png");if(k===b){alert("画布是空的,请先绘制内容!");return}if(k.length*.75/1024/1024<1)e.sendChatMessage(d._id,a.username,`[白板作品]${k}`),alert("白板作品已发送到群聊!");else{const z=await fetch(k).then(Y=>Y.blob()),S=new FormData;S.append("file",z,`whiteboard-${Date.now()}.png`),S.append("groupId",d._id),S.append("description","协作白板作品");const R=localStorage.getItem("token"),G=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${R}`},body:S});if(G.ok){const J=`http://localhost:3000/api/files/${(await G.json()).file._id}/download?token=${R}`;e.sendChatMessage(d._id,a.username,`[白板作品]${J}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(k){console.error("发送白板失败:",k),alert("发送失败,请重试!")}}),i.addEventListener("mousedown",k=>{r=!0;const H=i.getBoundingClientRect(),b=k.clientX-H.left,A=k.clientY-H.top;t.beginPath(),t.moveTo(b,A)}),i.addEventListener("mousemove",k=>{if(!r)return;const H=i.getBoundingClientRect(),b=k.clientX-H.left,A=k.clientY-H.top;t.lineWidth=w,t.lineCap="round",l==="pen"?(t.strokeStyle=h,t.globalCompositeOperation="source-over"):l==="eraser"&&(t.globalCompositeOperation="destination-out"),t.lineTo(b,A),t.stroke(),e.sendWhiteboardDraw(d._id,{tool:l,color:h,size:w,x:b,y:A})}),i.addEventListener("mouseup",()=>{r=!1}),i.addEventListener("mouseleave",()=>{r=!1}),e.on("whiteboard_draw",k=>{k.groupId===d._id&&(t.lineWidth=k.size,t.lineCap="round",k.tool==="pen"?(t.strokeStyle=k.color,t.globalCompositeOperation="source-over"):k.tool==="eraser"&&(t.globalCompositeOperation="destination-out"),t.lineTo(k.x,k.y),t.stroke())}),e.on("whiteboard_clear",k=>{k.groupId===d._id&&t.clearRect(0,0,i.width,i.height)})};var L=de,P=m;const f=(await n.getGroup(d._id)).group,B=!!f.mutedAll,D=(f.mutedUsers||[]).map(String).includes(String(s)),$=!B&&!D;T.innerHTML=`
1895
2086
  <div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
1896
2087
  <h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
1897
2088
  <span style="font-size: 32px;">💬</span>
1898
- <span>群聊 - ${i.name}</span>
2089
+ <span>群聊 - ${d.name}</span>
1899
2090
  </h2>
1900
- ${I?"":`
2091
+ ${$?"":`
1901
2092
  <div style="margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.2); border-radius: 8px; font-size: 14px;">
1902
- ⚠️ ${M?"全体禁言中,无法发言":"你已被禁言"}
2093
+ ⚠️ ${B?"全体禁言中,无法发言":"你已被禁言"}
1903
2094
  </div>
1904
2095
  `}
1905
2096
  </div>
@@ -1922,9 +2113,9 @@
1922
2113
  <div class="chat-container" style="display: flex; flex-direction: column; height: calc(100vh - 350px); background: var(--bg-secondary); border-radius: 12px; overflow: hidden;">
1923
2114
  <div class="messages" id="messages" style="flex: 1; overflow-y: auto; padding: 20px;"></div>
1924
2115
  <div class="chat-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
1925
- <button class="btn-emoji" id="emojiBtn" ${I?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${I?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
1926
- <input type="text" id="messageInput" placeholder="${I?"输入消息...":M?"全体禁言中,无法发言":"你已被禁言"}" ${I?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
1927
- <button class="btn-primary" id="sendBtn" ${I?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${I?"pointer":"not-allowed"};">发送</button>
2116
+ <button class="btn-emoji" id="emojiBtn" ${$?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${$?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
2117
+ <input type="text" id="messageInput" placeholder="${$?"输入消息...":B?"全体禁言中,无法发言":"你已被禁言"}" ${$?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
2118
+ <button class="btn-primary" id="sendBtn" ${$?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${$?"pointer":"not-allowed"};">发送</button>
1928
2119
  </div>
1929
2120
  <emoji-picker id="emojiPicker" class="hidden" style="position: absolute; bottom: 80px; left: 20px; z-index: 1000;"></emoji-picker>
1930
2121
  </div>
@@ -1961,40 +2152,40 @@
1961
2152
  </div>
1962
2153
  </div>
1963
2154
  </div>
1964
- `;const z=document.getElementById("messages"),W=document.getElementById("messageInput"),O=document.getElementById("sendBtn");try{const d=await a.getGroupMessages(i._id);d.messages&&Array.isArray(d.messages)&&(d.messages.length===0?z.innerHTML=`
2155
+ `;const C=document.getElementById("messages"),W=document.getElementById("messageInput"),O=document.getElementById("sendBtn");try{const i=await n.getGroupMessages(d._id);i.messages&&Array.isArray(i.messages)&&(i.messages.length===0?C.innerHTML=`
1965
2156
  <div style="text-align: center; padding: 40px; color: var(--text-tertiary);">
1966
2157
  <div style="font-size: 48px; margin-bottom: 16px;">💬</div>
1967
2158
  <p>还没有消息,开始聊天吧!</p>
1968
2159
  </div>
1969
- `:(d.messages.forEach(s=>{const c=document.createElement("div"),v=String(s.sender)===String(o)||s.username===t.username,w=N(s.content),P=s.content.startsWith("[白板作品]")||s.content.startsWith("[投票]"),p=s.content.startsWith("[白板作品]");c.className=`message ${v?"own":""}`,c.style.cssText=`
2160
+ `:(i.messages.forEach(t=>{const r=document.createElement("div"),l=String(t.sender)===String(s)||t.username===a.username,h=N(t.content),w=t.content.startsWith("[白板作品]")||t.content.startsWith("[投票]"),k=t.content.startsWith("[白板作品]");r.className=`message ${l?"own":""}`,r.style.cssText=`
1970
2161
  margin-bottom: 16px;
1971
2162
  display: flex;
1972
2163
  flex-direction: column;
1973
- align-items: ${v?"flex-end":"flex-start"};
1974
- `,c.innerHTML=`
2164
+ align-items: ${l?"flex-end":"flex-start"};
2165
+ `,r.innerHTML=`
1975
2166
  <div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
1976
- <span class="message-user">${s.username}</span>
1977
- <span class="message-time">${new Date(s.timestamp).toLocaleTimeString("zh-CN")}</span>
2167
+ <span class="message-user">${t.username}</span>
2168
+ <span class="message-time">${new Date(t.timestamp).toLocaleTimeString("zh-CN")}</span>
1978
2169
  </div>
1979
- <div class="message-content" style="background: ${p||P?"transparent":v?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${v&&!P?"white":"var(--text-primary)"}; padding: ${P?"0":"12px 16px"}; border-radius: 16px; max-width: ${P?"90%":"70%"}; word-wrap: break-word; box-shadow: ${v&&!P?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${w}</div>
1980
- `,z.appendChild(c)}),z.scrollTop=z.scrollHeight))}catch(d){console.error("加载历史消息失败:",d),z.innerHTML=`
2170
+ <div class="message-content" style="background: ${k||w?"transparent":l?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${l&&!w?"white":"var(--text-primary)"}; padding: ${w?"0":"12px 16px"}; border-radius: 16px; max-width: ${w?"90%":"70%"}; word-wrap: break-word; box-shadow: ${l&&!w?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${h}</div>
2171
+ `,C.appendChild(r)}),C.scrollTop=C.scrollHeight))}catch(i){console.error("加载历史消息失败:",i),C.innerHTML=`
1981
2172
  <div style="text-align: center; padding: 40px; color: var(--danger);">
1982
2173
  <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
1983
2174
  <p>加载历史消息失败</p>
1984
- <p style="font-size: 14px; color: var(--text-tertiary);">${d.message}</p>
2175
+ <p style="font-size: 14px; color: var(--text-tertiary);">${i.message}</p>
1985
2176
  </div>
1986
- `}const oe=document.getElementById("emojiBtn"),K=document.getElementById("emojiPicker");I&&(oe.addEventListener("click",()=>{K.classList.toggle("hidden")}),K.addEventListener("emoji-click",d=>{W.value+=d.detail.unicode,W.focus(),K.classList.add("hidden")}),document.addEventListener("click",d=>{!oe.contains(d.target)&&!K.contains(d.target)&&K.classList.add("hidden")})),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission(),e.on("chat_message",d=>{if(d.groupId===i._id){const s=document.createElement("div"),c=String(d.userId)===String(o)||d.username===t.username;s.className=`message ${c?"own":""}`,s.style.cssText=`
2177
+ `}const se=document.getElementById("emojiBtn"),X=document.getElementById("emojiPicker");$&&(se.addEventListener("click",()=>{X.classList.toggle("hidden")}),X.addEventListener("emoji-click",i=>{W.value+=i.detail.unicode,W.focus(),X.classList.add("hidden")}),document.addEventListener("click",i=>{!se.contains(i.target)&&!X.contains(i.target)&&X.classList.add("hidden")})),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission(),e.on("chat_message",i=>{if(i.groupId===d._id){const t=document.createElement("div"),r=String(i.userId)===String(s)||i.username===a.username;t.className=`message ${r?"own":""}`,t.style.cssText=`
1987
2178
  margin-bottom: 16px;
1988
2179
  display: flex;
1989
2180
  flex-direction: column;
1990
- align-items: ${c?"flex-end":"flex-start"};
1991
- `;const v=N(d.content),w=d.content.startsWith("[白板作品]")||d.content.startsWith("[投票]"),P=d.content.startsWith("[白板作品]");s.innerHTML=`
2181
+ align-items: ${r?"flex-end":"flex-start"};
2182
+ `;const l=N(i.content),h=i.content.startsWith("[白板作品]")||i.content.startsWith("[投票]"),w=i.content.startsWith("[白板作品]");t.innerHTML=`
1992
2183
  <div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
1993
- <span class="message-user">${d.username}</span>
1994
- <span class="message-time">${new Date(d.timestamp).toLocaleTimeString("zh-CN")}</span>
2184
+ <span class="message-user">${i.username}</span>
2185
+ <span class="message-time">${new Date(i.timestamp).toLocaleTimeString("zh-CN")}</span>
1995
2186
  </div>
1996
- <div class="message-content" style="background: ${P||w?"transparent":c?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${c&&!w?"white":"var(--text-primary)"}; padding: ${w?"0":"12px 16px"}; border-radius: 16px; max-width: ${w?"90%":"70%"}; word-wrap: break-word; box-shadow: ${c&&!w?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${v}</div>
1997
- `,z.appendChild(s),z.scrollTop=z.scrollHeight,c||h(`${d.username} 在 ${i.name}`,d.content.startsWith("[")?"发送了特殊消息":d.content)}}),e.on("chat_blocked",d=>{if(d.groupId===i._id){const s=document.createElement("div");s.style.cssText=`
2187
+ <div class="message-content" style="background: ${w||h?"transparent":r?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${r&&!h?"white":"var(--text-primary)"}; padding: ${h?"0":"12px 16px"}; border-radius: 16px; max-width: ${h?"90%":"70%"}; word-wrap: break-word; box-shadow: ${r&&!h?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${l}</div>
2188
+ `,C.appendChild(t),C.scrollTop=C.scrollHeight,r||de(`${i.username} 在 ${d.name}`,i.content.startsWith("[")?"发送了特殊消息":i.content)}}),e.on("chat_blocked",i=>{if(i.groupId===d._id){const t=document.createElement("div");t.style.cssText=`
1998
2189
  text-align: center;
1999
2190
  padding: 12px;
2000
2191
  margin: 16px auto;
@@ -2002,7 +2193,7 @@
2002
2193
  color: white;
2003
2194
  border-radius: 8px;
2004
2195
  max-width: 80%;
2005
- `,s.textContent=d.message||"消息发送失败",z.appendChild(s),z.scrollTop=z.scrollHeight}}),e.on("call_response",d=>{if(d.groupId===i._id){const s=document.createElement("div");s.style.cssText=`
2196
+ `,t.textContent=i.message||"消息发送失败",C.appendChild(t),C.scrollTop=C.scrollHeight}}),e.on("call_response",i=>{if(i.groupId===d._id){const t=document.createElement("div");t.style.cssText=`
2006
2197
  text-align: center;
2007
2198
  padding: 12px;
2008
2199
  margin: 16px auto;
@@ -2010,7 +2201,7 @@
2010
2201
  color: white;
2011
2202
  border-radius: 8px;
2012
2203
  max-width: 80%;
2013
- `,s.textContent=`${d.username} 已响应点名`,z.appendChild(s),z.scrollTop=z.scrollHeight}});const m=()=>{if(!I){alert(M?"全体禁言中,无法发言":"你已被禁言");return}const d=W.value.trim();if(d)try{e.sendChatMessage(i._id,t.username,d),W.value=""}catch(s){console.error("发送消息失败:",s),alert("发送失败: "+s.message)}};I&&(O.addEventListener("click",m),W.addEventListener("keypress",d=>{d.key==="Enter"&&!d.shiftKey&&(d.preventDefault(),m())}));const u=document.querySelectorAll(".chat-tab"),b=document.querySelectorAll(".tab-content");u.forEach(d=>{d.addEventListener("click",()=>{const s=d.dataset.tab;u.forEach(c=>{c.dataset.tab===s?(c.style.background="var(--primary)",c.style.color="white"):(c.style.background="transparent",c.style.color="var(--text-primary)")}),b.forEach(c=>{c.dataset.content===s?c.style.display="block":c.style.display="none"}),s==="whiteboard"&&k()})});const B=document.getElementById("aiChat"),y=document.getElementById("aiInput"),g=document.getElementById("aiSendBtn"),r=async()=>{const d=y.value.trim();if(!d)return;const s=document.createElement("div");s.style.cssText=`
2204
+ `,t.textContent=`${i.username} 已响应点名`,C.appendChild(t),C.scrollTop=C.scrollHeight}});const pe=()=>{if(!$){alert(B?"全体禁言中,无法发言":"你已被禁言");return}const i=W.value.trim();if(i)try{e.sendChatMessage(d._id,a.username,i),W.value=""}catch(t){console.error("发送消息失败:",t),alert("发送失败: "+t.message)}};$&&(O.addEventListener("click",pe),W.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),pe())}));const y=document.querySelectorAll(".chat-tab"),u=document.querySelectorAll(".tab-content");y.forEach(i=>{i.addEventListener("click",()=>{const t=i.dataset.tab;y.forEach(r=>{r.dataset.tab===t?(r.style.background="var(--primary)",r.style.color="white"):(r.style.background="transparent",r.style.color="var(--text-primary)")}),u.forEach(r=>{r.dataset.content===t?r.style.display="block":r.style.display="none"}),t==="whiteboard"&&m()})});const g=document.getElementById("aiChat"),x=document.getElementById("aiInput"),M=document.getElementById("aiSendBtn"),p=async()=>{const i=x.value.trim();if(!i)return;const t=document.createElement("div");t.style.cssText=`
2014
2205
  background: var(--primary);
2015
2206
  color: white;
2016
2207
  padding: 12px 16px;
@@ -2019,7 +2210,7 @@
2019
2210
  max-width: 80%;
2020
2211
  margin-left: auto;
2021
2212
  word-wrap: break-word;
2022
- `,s.textContent=d,B.appendChild(s),y.value="";const c=document.createElement("div");c.style.cssText=`
2213
+ `,t.textContent=i,g.appendChild(t),x.value="";const r=document.createElement("div");r.style.cssText=`
2023
2214
  background: var(--bg-tertiary);
2024
2215
  color: var(--text-secondary);
2025
2216
  padding: 12px 16px;
@@ -2027,7 +2218,7 @@
2027
2218
  margin-bottom: 16px;
2028
2219
  max-width: 80%;
2029
2220
  font-style: italic;
2030
- `,c.textContent="🤔 思考中...",B.appendChild(c),B.scrollTop=B.scrollHeight;try{const v=localStorage.getItem("token"),P=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`},body:JSON.stringify({question:d,groupId:i==null?void 0:i._id})})).json();c.remove();const p=document.createElement("div");p.style.cssText=`
2221
+ `,r.textContent="🤔 思考中...",g.appendChild(r),g.scrollTop=g.scrollHeight;try{const l=localStorage.getItem("token"),w=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${l}`},body:JSON.stringify({question:i,groupId:d==null?void 0:d._id})})).json();r.remove();const k=document.createElement("div");k.style.cssText=`
2031
2222
  background: var(--bg-tertiary);
2032
2223
  color: var(--text-primary);
2033
2224
  padding: 12px 16px;
@@ -2036,18 +2227,18 @@
2036
2227
  max-width: 80%;
2037
2228
  line-height: 1.6;
2038
2229
  word-wrap: break-word;
2039
- `,p.textContent=P.answer||"抱歉,我无法回答这个问题。",B.appendChild(p),B.scrollTop=B.scrollHeight}catch(v){c.remove();const w=document.createElement("div");w.style.cssText=`
2230
+ `,k.textContent=w.answer||"抱歉,我无法回答这个问题。",g.appendChild(k),g.scrollTop=g.scrollHeight}catch(l){r.remove();const h=document.createElement("div");h.style.cssText=`
2040
2231
  background: var(--danger);
2041
2232
  color: white;
2042
2233
  padding: 12px 16px;
2043
2234
  border-radius: 12px;
2044
2235
  margin-bottom: 16px;
2045
2236
  max-width: 80%;
2046
- `,w.textContent="抱歉,发生了错误: "+v.message,B.appendChild(w),B.scrollTop=B.scrollHeight}};g.addEventListener("click",r),y.addEventListener("keypress",d=>{d.key==="Enter"&&!d.shiftKey&&(d.preventDefault(),r())})}catch(x){console.error("加载群聊失败:",x),T.innerHTML=`
2237
+ `,h.textContent="抱歉,发生了错误: "+l.message,g.appendChild(h),g.scrollTop=g.scrollHeight}};M.addEventListener("click",p),x.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),p())})}catch(v){console.error("加载群聊失败:",v),T.innerHTML=`
2047
2238
  <div class="empty-state" style="text-align: center; padding: 60px 20px;">
2048
2239
  <div style="font-size: 64px; margin-bottom: 20px;">⚠️</div>
2049
2240
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--danger);">加载失败</h3>
2050
- <p style="color: var(--text-secondary); margin-bottom: 24px;">${x.message}</p>
2241
+ <p style="color: var(--text-secondary); margin-bottom: 24px;">${v.message}</p>
2051
2242
  <button class="btn-primary" onclick="location.reload()">重新加载</button>
2052
2243
  </div>
2053
2244
  `}}async function V(T){T.innerHTML=`
@@ -2072,18 +2263,18 @@
2072
2263
  </div>
2073
2264
  <div class="search-results" id="searchResults"></div>
2074
2265
  </div>
2075
- `;const E=document.getElementById("searchInput"),F=document.getElementById("searchBtn"),x=document.getElementById("searchResults"),f=async()=>{const M=E.value.trim();if(!M){x.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const A={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};x.innerHTML='<div class="loading">搜索中...</div>';try{const I=[];if(A.messages&&i)try{const z=await a.getGroupMessages(i._id);z.messages&&z.messages.filter(O=>O.content.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"message",title:`消息 - ${O.username}`,content:O.content,time:O.timestamp,group:i.name})})}catch(z){console.error("搜索消息失败:",z)}if(A.documents)try{if(i){const z=await a.getDocuments(i._id);z.documents&&z.documents.filter(O=>O.title.toLowerCase().includes(M.toLowerCase())||O.content.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"document",title:O.title,content:O.content.substring(0,200),time:O.updatedAt,id:O._id,group:i.name})})}}catch(z){console.error("搜索文档失败:",z)}if(A.tasks)try{const z=await a.getMyTasks();z.tasks&&z.tasks.filter(O=>O.title.toLowerCase().includes(M.toLowerCase())||O.description&&O.description.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"task",title:O.title,content:O.description||"",time:O.updatedAt,id:O._id,status:O.status})})}catch(z){console.error("搜索任务失败:",z)}I.length===0?x.innerHTML='<div class="empty-state">未找到相关结果</div>':x.innerHTML=I.map(z=>`
2266
+ `;const L=document.getElementById("searchInput"),P=document.getElementById("searchBtn"),v=document.getElementById("searchResults"),f=async()=>{const B=L.value.trim();if(!B){v.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const D={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};v.innerHTML='<div class="loading">搜索中...</div>';try{const $=[];if(D.messages&&d)try{const C=await n.getGroupMessages(d._id);C.messages&&C.messages.filter(O=>O.content.toLowerCase().includes(B.toLowerCase())).forEach(O=>{$.push({type:"message",title:`消息 - ${O.username}`,content:O.content,time:O.timestamp,group:d.name})})}catch(C){console.error("搜索消息失败:",C)}if(D.documents)try{if(d){const C=await n.getDocuments(d._id);C.documents&&C.documents.filter(O=>O.title.toLowerCase().includes(B.toLowerCase())||O.content.toLowerCase().includes(B.toLowerCase())).forEach(O=>{$.push({type:"document",title:O.title,content:O.content.substring(0,200),time:O.updatedAt,id:O._id,group:d.name})})}}catch(C){console.error("搜索文档失败:",C)}if(D.tasks)try{const C=await n.getMyTasks();C.tasks&&C.tasks.filter(O=>O.title.toLowerCase().includes(B.toLowerCase())||O.description&&O.description.toLowerCase().includes(B.toLowerCase())).forEach(O=>{$.push({type:"task",title:O.title,content:O.description||"",time:O.updatedAt,id:O._id,status:O.status})})}catch(C){console.error("搜索任务失败:",C)}$.length===0?v.innerHTML='<div class="empty-state">未找到相关结果</div>':v.innerHTML=$.map(C=>`
2076
2267
  <div class="search-result-item">
2077
2268
  <div class="result-header">
2078
- <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[z.type]} ${z.type==="message"?"消息":z.type==="document"?"文档":"任务"}</span>
2079
- <span class="result-time">${new Date(z.time).toLocaleString()}</span>
2269
+ <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[C.type]} ${C.type==="message"?"消息":C.type==="document"?"文档":"任务"}</span>
2270
+ <span class="result-time">${new Date(C.time).toLocaleString()}</span>
2080
2271
  </div>
2081
- <h4>${Q(z.title,M)}</h4>
2082
- <p>${Q(z.content,M)}</p>
2083
- ${z.group?`<span class="result-group">群组: ${z.group}</span>`:""}
2084
- ${z.status?`<span class="result-status">状态: ${ne(z.status)}</span>`:""}
2272
+ <h4>${te(C.title,B)}</h4>
2273
+ <p>${te(C.content,B)}</p>
2274
+ ${C.group?`<span class="result-group">群组: ${C.group}</span>`:""}
2275
+ ${C.status?`<span class="result-status">状态: ${ae(C.status)}</span>`:""}
2085
2276
  </div>
2086
- `).join("")}catch(I){x.innerHTML=`<div class="empty-state">搜索失败: ${I.message}</div>`}};F.addEventListener("click",f),E.addEventListener("keypress",M=>{M.key==="Enter"&&f()})}function Q(T,E){if(!E)return T;const F=new RegExp(`(${E})`,"gi");return T.replace(F,"<mark>$1</mark>")}function ne(T){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[T]||T}async function re(T){if(!i){T.innerHTML=`
2277
+ `).join("")}catch($){v.innerHTML=`<div class="empty-state">搜索失败: ${$.message}</div>`}};P.addEventListener("click",f),L.addEventListener("keypress",B=>{B.key==="Enter"&&f()})}function te(T,L){if(!L)return T;const P=new RegExp(`(${L})`,"gi");return T.replace(P,"<mark>$1</mark>")}function ae(T){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[T]||T}async function re(T){if(!d){T.innerHTML=`
2087
2278
  <div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
2088
2279
  <div style="font-size: 64px; margin-bottom: 20px;">📚</div>
2089
2280
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">知识库</h3>
@@ -2092,9 +2283,9 @@
2092
2283
  前往我的群组
2093
2284
  </button>
2094
2285
  </div>
2095
- `;return}try{const E=localStorage.getItem("token"),F=await fetch(`http://localhost:3000/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${E}`}});if(!F.ok)throw new Error(`HTTP ${F.status}: ${F.statusText}`);const x=await F.json();let f=[];Array.isArray(x)?f=x:x.data&&Array.isArray(x.data)?f=x.data:x.data&&x.data.knowledgeList&&Array.isArray(x.data.knowledgeList)?f=x.data.knowledgeList:x.items&&Array.isArray(x.items)?f=x.items:x.knowledge&&Array.isArray(x.knowledge)&&(f=x.knowledge),T.innerHTML=`
2286
+ `;return}try{const L=localStorage.getItem("token"),P=await fetch(`http://localhost:3000/api/knowledge/group/${d._id}`,{headers:{Authorization:`Bearer ${L}`}});if(!P.ok)throw new Error(`HTTP ${P.status}: ${P.statusText}`);const v=await P.json();let f=[];Array.isArray(v)?f=v:v.data&&Array.isArray(v.data)?f=v.data:v.data&&v.data.knowledgeList&&Array.isArray(v.data.knowledgeList)?f=v.data.knowledgeList:v.items&&Array.isArray(v.items)?f=v.items:v.knowledge&&Array.isArray(v.knowledge)&&(f=v.knowledge),T.innerHTML=`
2096
2287
  <div class="view-header">
2097
- <h2>📚 知识库 - ${i.name}</h2>
2288
+ <h2>📚 知识库 - ${d.name}</h2>
2098
2289
  <button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
2099
2290
  </div>
2100
2291
  <div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -2136,31 +2327,31 @@
2136
2327
  </form>
2137
2328
  </div>
2138
2329
  </div>
2139
- `;const M=document.getElementById("knowledgeList");f.length===0?M.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(f.forEach(A=>{var z,W;const I=document.createElement("div");I.className="knowledge-card",I.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",I.innerHTML=`
2140
- ${A.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
2141
- <h3 style="margin: 0 0 10px 0; font-size: 18px; ${A.isShared?"padding-right: 80px;":""}">${A.title}</h3>
2142
- <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${A.content.substring(0,150)}${A.content.length>150?"...":""}</p>
2330
+ `;const B=document.getElementById("knowledgeList");f.length===0?B.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(f.forEach(D=>{var C,W;const $=document.createElement("div");$.className="knowledge-card",$.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",$.innerHTML=`
2331
+ ${D.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
2332
+ <h3 style="margin: 0 0 10px 0; font-size: 18px; ${D.isShared?"padding-right: 80px;":""}">${D.title}</h3>
2333
+ <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${D.content.substring(0,150)}${D.content.length>150?"...":""}</p>
2143
2334
  <div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
2144
- <span>👤 ${((z=A.author)==null?void 0:z.username)||((W=A.creator)==null?void 0:W.username)||"未知"}</span>
2145
- <span style="margin-left: 15px;">📅 ${new Date(A.createdAt).toLocaleDateString()}</span>
2335
+ <span>👤 ${((C=D.author)==null?void 0:C.username)||((W=D.creator)==null?void 0:W.username)||"未知"}</span>
2336
+ <span style="margin-left: 15px;">📅 ${new Date(D.createdAt).toLocaleDateString()}</span>
2146
2337
  </div>
2147
- ${A.tags&&A.tags.length>0?`
2338
+ ${D.tags&&D.tags.length>0?`
2148
2339
  <div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
2149
- ${A.tags.map(O=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${O}</span>`).join("")}
2340
+ ${D.tags.map(O=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${O}</span>`).join("")}
2150
2341
  </div>
2151
2342
  `:""}
2152
2343
  <div style="display: flex; gap: 10px;">
2153
- <button class="btn-secondary btn-sm" data-id="${A._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
2154
- <button class="btn-danger btn-sm" data-id="${A._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
2344
+ <button class="btn-secondary btn-sm" data-id="${D._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
2345
+ <button class="btn-danger btn-sm" data-id="${D._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
2155
2346
  </div>
2156
- `,I.onmouseenter=()=>{I.style.transform="translateY(-4px)",I.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},I.onmouseleave=()=>{I.style.transform="translateY(0)",I.style.boxShadow="none"},M.appendChild(I)}),document.querySelectorAll('[data-action="edit"]').forEach(A=>{A.addEventListener("click",async()=>{var z;const I=f.find(W=>W._id===A.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=I.title,document.querySelector('[name="content"]').value=I.content,document.querySelector('[name="tags"]').value=((z=I.tags)==null?void 0:z.join(", "))||"",document.getElementById("isSharedCheckbox").checked=I.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=I._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(A=>{A.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${A.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${E}`}}),alert("删除成功!"),await re(T)}catch(I){alert("删除失败: "+I.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async A=>{A.preventDefault();const I=new FormData(A.target),z={title:I.get("title"),content:I.get("content"),tags:I.get("tags").split(",").map(W=>W.trim()).filter(W=>W),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const W=A.target.dataset.editId,O=W?`http://localhost:3000/api/knowledge/${W}`:"http://localhost:3000/api/knowledge";if(!(await fetch(O,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${E}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await re(T)}catch(W){alert("操作失败: "+W.message)}})}catch(E){console.error("加载知识库失败:",E),T.innerHTML=`
2347
+ `,$.onmouseenter=()=>{$.style.transform="translateY(-4px)",$.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},$.onmouseleave=()=>{$.style.transform="translateY(0)",$.style.boxShadow="none"},B.appendChild($)}),document.querySelectorAll('[data-action="edit"]').forEach(D=>{D.addEventListener("click",async()=>{var C;const $=f.find(W=>W._id===D.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=$.title,document.querySelector('[name="content"]').value=$.content,document.querySelector('[name="tags"]').value=((C=$.tags)==null?void 0:C.join(", "))||"",document.getElementById("isSharedCheckbox").checked=$.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=$._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(D=>{D.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${D.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${L}`}}),alert("删除成功!"),await re(T)}catch($){alert("删除失败: "+$.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async D=>{D.preventDefault();const $=new FormData(D.target),C={title:$.get("title"),content:$.get("content"),tags:$.get("tags").split(",").map(W=>W.trim()).filter(W=>W),groupId:d._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const W=D.target.dataset.editId,O=W?`http://localhost:3000/api/knowledge/${W}`:"http://localhost:3000/api/knowledge";if(!(await fetch(O,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${L}`},body:JSON.stringify(C)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await re(T)}catch(W){alert("操作失败: "+W.message)}})}catch(L){console.error("加载知识库失败:",L),T.innerHTML=`
2157
2348
  <div class="view-header">
2158
- <h2>📚 知识库 - ${i.name}</h2>
2349
+ <h2>📚 知识库 - ${d.name}</h2>
2159
2350
  </div>
2160
2351
  <div class="empty-state" style="text-align: center; padding: 40px 20px;">
2161
2352
  <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
2162
2353
  <h3 style="margin-bottom: 8px; color: var(--danger);">加载失败</h3>
2163
- <p style="color: var(--text-secondary); margin-bottom: 16px;">${E.message}</p>
2354
+ <p style="color: var(--text-secondary); margin-bottom: 16px;">${L.message}</p>
2164
2355
  <button class="btn-primary" onclick="location.reload()">重新加载</button>
2165
2356
  </div>
2166
- `}}$("groups")}class mn{constructor(){this.authService=new Be,this.wsService=new Ct,this.currentUser=null,this.init()}async init(){const e=localStorage.getItem("token");if(e)try{this.currentUser=await this.authService.getCurrentUser(),this.wsService.connect(e),this.renderDashboard()}catch(n){console.error("认证失败:",n),this.renderLogin()}else this.renderLogin()}renderLogin(){zt(async(e,n)=>{this.currentUser=e,localStorage.setItem("token",n),this.wsService.connect(n),this.renderDashboard()})}renderDashboard(){this.currentUser.role==="admin"?pn(this.currentUser,this.wsService):gn(this.currentUser,this.wsService)}}new mn;
2357
+ `}}E("groups")}class hn{constructor(){this.authService=new Ce,this.wsService=new Dt,this.currentUser=null,this.init()}async init(){const e=localStorage.getItem("token");if(e)try{this.currentUser=await this.authService.getCurrentUser(),this.wsService.connect(e),this.renderDashboard()}catch(o){console.error("认证失败:",o),this.renderLogin()}else this.renderLogin()}renderLogin(){jt(async(e,o)=>{this.currentUser=e,localStorage.setItem("token",o),this.wsService.connect(o),this.renderDashboard()})}renderDashboard(){this.currentUser.role==="admin"?mn(this.currentUser,this.wsService):yn(this.currentUser,this.wsService)}}new hn;