collabdocchat 2.5.7 → 2.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{index-KJuHi72a.js → index-DkEWw6lP.js} +352 -258
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/scripts/cleanup-scripts.js +3 -0
- package/scripts/fix-startup-issues.js +3 -0
- package/scripts/start-simple.js +3 -0
- package/server/public/index.html +3 -0
- package/src/pages/admin-dashboard.js +184 -42
|
@@ -1,4 +1,4 @@
|
|
|
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
|
|
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 De="http://localhost:3000/api";class ze{async login(e,o){const n=await fetch(`${De}/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(`${De}/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(`${De}/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 jt{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 _t(a){const e=document.getElementById("app"),o=new ze;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 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&<(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=`
|
|
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 S=await o.login(s,d);a(S.user,S.token)}catch(S){document.getElementById("loginError").textContent=S.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 S=await o.register(s,d);a(S.user,S.token)}catch(S){document.getElementById("registerError").textContent=S.message}})}const je="http://localhost:3000/api";class gt{constructor(){this.token=localStorage.getItem("token")}async request(e,o={}){const n=await fetch(`${je}${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(`${je}/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`${je}/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 Le(a){if(typeof a!="string"||!a)throw new Error("expected a non-empty string, got: "+a)}function _e(a){if(typeof a!="number")throw new Error("expected a number, got: "+a)}const At=1,Pt=1,he="emoji",xe="keyvalue",Ye="favorites",Ft="tokens",mt="tokens",Ht="unicode",yt="count",Nt="group",Ot="order",bt="group-order",Ue="eTag",Be="url",at="skinTone",fe="readonly",Ke="readwrite",ht="skinUnicodes",qt="skinUnicodes",Ut="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Rt="en";function Gt(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 nt(a){return Gt(a,e=>e.unicode)}function Wt(a){function e(o,n,c){const s=n?a.createObjectStore(o,{keyPath:n}):a.createObjectStore(o);if(c)for(const[d,[S,N]]of Object.entries(c))s.createIndex(d,S,{multiEntry:N});return s}e(xe),e(he,Ht,{[mt]:[Ft,!0],[bt]:[[Nt,Ot]],[ht]:[qt,!0]}),e(Ye,void 0,{[yt]:[""]})}const Re={},Te={},Me={};function vt(a,e,o){o.onerror=()=>e(o.error),o.onblocked=()=>e(new Error("IDB blocked")),o.onsuccess=()=>a(o.result)}async function Vt(a){const e=await new Promise((o,n)=>{const c=indexedDB.open(a,At);Re[a]=c,c.onupgradeneeded=s=>{s.oldVersion<Pt&&Wt(c.result)},vt(o,n,c)});return e.onclose=()=>Je(a),e}function Yt(a){return Te[a]||(Te[a]=Vt(a)),Te[a]}function me(a,e,o,n){return new Promise((c,s)=>{const d=a.transaction(e,o,{durability:"relaxed"}),S=typeof e=="string"?d.objectStore(e):e.map(L=>d.objectStore(L));let N;n(S,d,L=>{N=L}),d.oncomplete=()=>c(N),d.onerror=()=>s(d.error)})}function Je(a){const e=Re[a],o=e&&e.result;if(o){o.close();const n=Me[a];if(n)for(const c of n)c()}delete Re[a],delete Te[a],delete Me[a]}function Kt(a){return new Promise((e,o)=>{Je(a);const n=indexedDB.deleteDatabase(a);vt(e,o,n)})}function Jt(a,e){let o=Me[a];o||(o=Me[a]=[]),o.push(e)}const Xt=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 ve(a){return a.split(/[\s_]+/).map(e=>!e.match(/\w/)||Xt.has(e)?e.toLowerCase():e.replace(/[)(:,]/g,"").replace(/’/g,"'").toLowerCase()).filter(Boolean)}const Zt=2;function xt(a){return a.filter(Boolean).map(e=>e.toLowerCase()).filter(e=>e.length>=Zt)}function Qt(a){return a.map(({annotation:o,emoticon:n,group:c,order:s,shortcodes:d,skins:S,tags:N,emoji:L,version:j})=>{const H=[...new Set(xt([...(d||[]).map(ve).flat(),...(N||[]).map(ve).flat(),...ve(o),n]))].sort(),_={annotation:o,group:c,order:s,tags:N,tokens:H,unicode:L,version:j};if(n&&(_.emoticon=n),d&&(_.shortcodes=d),S){_.skinTones=[],_.skinUnicodes=[],_.skinVersions=[];for(const{tone:U,emoji:se,version:ae}of S)_.skinTones.push(U),_.skinUnicodes.push(se),_.skinVersions.push(ae)}return _})}function ft(a,e,o,n){a[e](o).onsuccess=c=>n&&n(c.target.result)}function be(a,e,o){ft(a,"get",e,o)}function wt(a,e,o){ft(a,"getAll",e,o)}function Xe(a){a.commit&&a.commit()}function ea(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 kt(a,e){const o=ea(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 ta(a){return!await Ze(a,xe,Be)}async function aa(a,e,o){const[n,c]=await Promise.all([Ue,Be].map(s=>Ze(a,xe,s)));return n===o&&c===e}async function na(a,e){return me(a,he,fe,(n,c,s)=>{let d;const S=()=>{n.getAll(d&&IDBKeyRange.lowerBound(d,!0),50).onsuccess=N=>{const L=N.target.result;for(const j of L)if(d=j.unicode,e(j))return s(j);if(L.length<50)return s();S()}};S()})}async function Et(a,e,o,n){try{const c=Qt(e);await me(a,[he,xe],Ke,([s,d],S)=>{let N,L,j=0;function H(){++j===2&&_()}function _(){if(!(N===n&&L===o)){s.clear();for(const U of c)s.put(U);d.put(n,Ue),d.put(o,Be),Xe(S)}}be(d,Ue,U=>{N=U,H()}),be(d,Be,U=>{L=U,H()})})}finally{}}async function oa(a,e){return me(a,he,fe,(o,n,c)=>{const s=IDBKeyRange.bound([e,0],[e+1,0],!1,!0);wt(o.index(bt),s,c)})}async function Lt(a,e){const o=xt(ve(e));return o.length?me(a,he,fe,(n,c,s)=>{const d=[],S=()=>{d.length===o.length&&N()},N=()=>{const L=kt(d,j=>j.unicode);s(L.sort((j,H)=>j.order<H.order?-1:1))};for(let L=0;L<o.length;L++){const j=o[L],H=L===o.length-1?IDBKeyRange.bound(j,j+"",!1,!0):IDBKeyRange.only(j);wt(n.index(mt),H,_=>{d.push(_),S()})}}):[]}async function sa(a,e){const o=await Lt(a,e);return o.length?o.filter(n=>(n.shortcodes||[]).map(s=>s.toLowerCase()).includes(e.toLowerCase()))[0]||null:await na(a,c=>(c.shortcodes||[]).includes(e.toLowerCase()))||null}async function ia(a,e){return me(a,he,fe,(o,n,c)=>be(o,e,s=>{if(s)return c(s);be(o.index(ht),e,d=>c(d||null))}))}function Ze(a,e,o){return me(a,e,fe,(n,c,s)=>be(n,o,s))}function ra(a,e,o,n){return me(a,e,Ke,(c,s)=>{c.put(n,o),Xe(s)})}function da(a,e){return me(a,Ye,Ke,(o,n)=>be(o,e,c=>{o.put((c||0)+1,e),Xe(n)}))}function la(a,e,o){return o===0?[]:me(a,[Ye,he],fe,([n,c],s,d)=>{const S=[];n.index(yt).openCursor(void 0,"prev").onsuccess=N=>{const L=N.target.result;if(!L)return d(S);function j(U){if(S.push(U),S.length===o)return d(S);L.continue()}const H=L.primaryKey,_=e.byName(H);if(_)return j(_);be(c,H,U=>{if(U)return j(U);L.continue()})}})}const $e="";function ca(a,e){const o=new Map;for(const c of a){const s=e(c);for(const d of s){let S=o;for(let L=0;L<d.length;L++){const j=d.charAt(L);let H=S.get(j);H||(H=new Map,S.set(j,H)),S=H}let N=S.get($e);N||(N=[],S.set($e,N)),N.push(c)}}return(c,s)=>{let d=o;for(let L=0;L<c.length;L++){const j=c.charAt(L),H=d.get(j);if(H)d=H;else return[]}if(s)return d.get($e)||[];const S=[],N=[d];for(;N.length;){const j=[...N.shift().entries()].sort((H,_)=>H[0]<_[0]?-1:1);for(const[H,_]of j)H===$e?S.push(..._):N.push(_)}return S}}const pa=["name","url"];function ua(a){const e=a&&Array.isArray(a),o=e&&a.length&&(!a[0]||pa.some(n=>!(n in a[0])));if(!e||o)throw new Error("Custom emojis are in the wrong format")}function ot(a){ua(a);const e=(_,U)=>_.name.toLowerCase()<U.name.toLowerCase()?-1:1,o=a.sort(e),c=ca(a,_=>{const U=new Set;if(_.shortcodes)for(const se of _.shortcodes)for(const ae of ve(se))U.add(ae);return U}),s=_=>c(_,!0),d=_=>c(_,!1),S=_=>{const U=ve(_),se=U.map((ae,ie)=>(ie<U.length-1?s:d)(ae));return kt(se,ae=>ae.name).sort(e)},N=new Map,L=new Map;for(const _ of a){L.set(_.name.toLowerCase(),_);for(const U of _.shortcodes||[])N.set(U.toLowerCase(),_)}return{all:o,search:S,byShortcode:_=>N.get(_.toLowerCase()),byName:_=>L.get(_.toLowerCase())}}const ga=typeof wrappedJSObject<"u";function ke(a){if(!a)return a;if(ga&&(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 $t(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 ya(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 It(a,e){if(Math.floor(a.status/100)!==2)throw new Error("Failed to fetch: "+e+": "+a.status)}async function ba(a){const e=await fetch(a,{method:"HEAD"});It(e,a);const o=e.headers.get("etag");return $t(o),o}async function Ge(a){const e=await fetch(a);It(e,a);const o=e.headers.get("etag");$t(o);const n=await e.json();return ya(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 va(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 Tt(a){const e=JSON.stringify(a);let o=va(e);const n=await crypto.subtle.digest("SHA-1",o),c=ha(n);return btoa(c)}async function xa(a,e){let o,n=await ba(e);if(!n){const c=await Ge(e);n=c[0],o=c[1],n||(n=await Tt(o))}await aa(a,e,n)||(o||(o=(await Ge(e))[1]),await Et(a,o,e,n))}async function fa(a,e){let[o,n]=await Ge(e);o||(o=await Tt(n)),await Et(a,n,e,o)}async function wa(a,e){try{await xa(a,e)}catch(o){if(o.name!=="InvalidStateError")throw o}}class ka{constructor({dataSource:e=Ut,locale:o=Rt,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=ot(n),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){const e=this._db=await Yt(this._dbName);Jt(this._dbName,this._clear);const o=this.dataSource;await ta(e)?await fa(e,o):this._lazyUpdate=wa(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 _e(e),await this.ready(),nt(await oa(this._db,e)).map(ke)}async getEmojiBySearchQuery(e){Le(e),await this.ready();const o=this._custom.search(e),n=nt(await Lt(this._db,e)).map(ke);return[...o,...n]}async getEmojiByShortcode(e){Le(e),await this.ready();const o=this._custom.byShortcode(e);return o||ke(await sa(this._db,e))}async getEmojiByUnicodeOrName(e){Le(e),await this.ready();const o=this._custom.byName(e);return o||ke(await ia(this._db,e))}async getPreferredSkinTone(){return await this.ready(),await Ze(this._db,xe,at)||0}async setPreferredSkinTone(e){return _e(e),await this.ready(),ra(this._db,xe,at,e)}async incrementFavoriteEmojiCount(e){return Le(e),await this.ready(),da(this._db,e)}async getTopFavoriteEmoji(e){return _e(e),await this.ready(),(await la(this._db,this._custom,e)).map(ke)}set customEmoji(e){this._custom=ot(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 Je(this._dbName)}async delete(){await this._shutdown(),await Kt(this._dbName)}}const We=[[-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})),Ae=We.slice(1),Ea=2,st=6,St=typeof requestIdleCallback=="function"?requestIdleCallback:setTimeout;function it(a){return a.unicode.includes("")}const La={"":17,"":16,"🫨":15.1,"🫠":14,"🥲":13.1,"🥻":12.1,"🥰":11,"🤩":5,"👱♀️":4,"🤣":3,"👁️🗨️":2,"😀":1,"😐️":.7,"😃":.6},$a=1e3,Ia="🖐️",Ta=8,Sa=["😊","😒","❤️","👍️","😍","😂","😭","☺️","😔","😩","😏","💕","🙌","😘"],Bt='"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif',Ba=(a,e)=>a<e?-1:a>e?1:0,rt=(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 ${Bt}`,n.fillStyle=e,n.scale(.01,.01),n.fillText(a,0,0),n.getImageData(0,0,1,1).data},Ma=(a,e)=>{const o=[...a].join(","),n=[...e].join(",");return o===n&&!o.startsWith("0,0,0,")};function Ca(a){const e=rt(a,"#000"),o=rt(a,"#fff");return e&&o&&Ma(e,o)}function za(){const a=Object.entries(La);try{for(const[e,o]of a)if(Ca(e))return o}catch{}finally{}return a[0][1]}let Pe;const Fe=()=>(Pe||(Pe=new Promise(a=>St(()=>a(za())))),Pe),Ve=new Map,Da="️",ja="\uD83C",_a="",Aa=127995,Pa=57339;function Fa(a,e){if(e===0)return a;const o=a.indexOf(_a);return o!==-1?a.substring(0,o)+String.fromCodePoint(Aa+e-1)+a.substring(o):(a.endsWith(Da)&&(a=a.substring(0,a.length-1)),a+ja+String.fromCodePoint(Pa+e-1))}function ge(a){a.preventDefault(),a.stopPropagation()}function He(a,e,o){return e+=a?-1:1,e<0?e=o.length-1:e>=o.length&&(e=0),e}function Mt(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 Ha(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:S,category:N,annotation:L})=>({unicode:n,name:S,shortcodes:s,url:d,category:N,annotation:L,id:n||S,skins:c&&o(c)}))}const Se=requestAnimationFrame;let Na=typeof ResizeObserver=="function";function Oa(a,e,o){let n;Na?(n=new ResizeObserver(o),n.observe(a)):Se(o),e.addEventListener("abort",()=>{n&&n.disconnect()})}function dt(a){{const e=document.createRange();return e.selectNode(a.firstChild),e.getBoundingClientRect().width}}let Ne;function qa(a,e,o){let n=!0;for(const c of a){const s=o(c);if(!s)continue;const d=dt(s);typeof Ne>"u"&&(Ne=dt(e));const S=d/1.8<Ne;Ve.set(c.unicode,S),S||(n=!1)}return n}function Ua(a){return Mt(a,e=>e)}function Ra(a){a&&(a.scrollTop=0)}function Ee(a,e,o){let n=a.get(e);return n||(n=o(),a.set(e,n)),n}function lt(a){return""+a}function Ga(a){const e=document.createElement("template");return e.innerHTML=a,e}const Wa=new WeakMap,Va=new WeakMap,Ya=Symbol("un-keyed"),Ka="replaceChildren"in Element.prototype;function Ja(a,e){Ka?a.replaceChildren(...e):(a.innerHTML="",a.append(...e))}function Xa(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 Za(a,e){const{targetNode:o}=e;let{targetParentNode:n}=e,c=!1;n?c=Xa(n,a):(c=!0,e.targetNode=void 0,e.targetParentNode=n=o.parentNode),c&&Ja(n,a)}function Qa(a,e){for(const o of e){const{targetNode:n,currentExpression:c,binding:{expressionIndex:s,attributeName:d,attributeValuePre:S,attributeValuePost:N}}=o,L=a[s];if(c!==L)if(o.currentExpression=L,d)if(L===null)n.removeAttribute(d);else{const j=S+lt(L)+N;n.setAttribute(d,j)}else{let j;Array.isArray(L)?Za(L,o):L instanceof Element?(j=L,n.replaceWith(j)):n.nodeValue=lt(L),j&&(o.targetNode=j)}}}function en(a){let e="",o=!1,n=!1,c=-1;const s=new Map,d=[];let S=0;for(let L=0,j=a.length;L<j;L++){const H=a[L];if(e+=H.slice(S),L===j-1)break;for(let q=0;q<H.length;q++)switch(H.charAt(q)){case"<":{H.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=Ee(s,_,()=>[]);let se,ae,ie;if(n){const q=/(\S+)="?([^"=]*)$/.exec(H);se=q[1],ae=q[2];const V=/^([^">]*)("?)/.exec(a[L+1]);ie=V[1],e=e.slice(0,-1*q[0].length),S=V[0].length}else S=0;const ce={attributeName:se,attributeValuePre:ae,attributeValuePost:ie,expressionIndex:L};U.push(ce),!o&&!n&&(e+=" ")}return{template:Ga(e),elementsToBindings:s}}function ct(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 tn(a,e){const o=[];let n;if(e.size===1&&(n=e.get(0)))ct(n,a,o);else{const c=document.createTreeWalker(a,NodeFilter.SHOW_ELEMENT);let s=a,d=-1;do{const S=e.get(++d);S&&ct(S,s,o)}while(s=c.nextNode())}return o}function an(a){const{template:e,elementsToBindings:o}=Ee(Wa,a,()=>en(a)),n=e.cloneNode(!0).content.firstElementChild,c=tn(n,o);return function(d){return Qa(d,c),n}}function nn(a){const e=Ee(Va,a,()=>new Map);let o=Ya;function n(s,...d){const S=Ee(e,s,()=>new Map);return Ee(S,o,()=>an(s))(d)}function c(s,d,S){return s.map((N,L)=>{const j=o;o=S(N);try{return d(N,L)}finally{o=j}})}return{map:c,html:n}}function on(a,e,o,n,c,s,d,S,N){const{labelWithSkin:L,titleForEmoji:j,unicodeWithSkin:H}=o,{html:_,map:U}=nn(e);function se(q,V,ne){return U(q,(oe,de)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?de===e.activeSearchItem:null}" aria-label="${L(oe,e.currentSkinTone)}" title="${j(oe)}" class="${"emoji"+(V&&de===e.activeSearchItem?" active":"")+(oe.unicode?"":" custom-emoji")}" id="${`${ne}-${oe.id}`}" style="${oe.unicode?null:`--custom-emoji-background: url(${JSON.stringify(oe.url)})`}">${oe.unicode?H(oe,e.currentSkinTone):""}</button>`,oe=>`${ne}-${oe.id}`)}const ie=_`<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}">${se(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">${se(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 ne of a.querySelectorAll(`[${q}]`))V(ne,ne.getAttribute(q))};if(N){a.appendChild(ie);for(const q of["click","focusout","input","keydown","keyup"])ce(`data-on-${q}`,(V,ne)=>{V.addEventListener(q,n[ne])});ce("data-ref",(q,V)=>{s[V]=q}),d.addEventListener("abort",()=>{a.removeChild(ie)})}ce("data-action",(q,V)=>{let ne=S.get(V);ne||S.set(V,ne=new WeakSet),ne.has(q)||(ne.add(q),c[V](q))})}const Ce=typeof queueMicrotask=="function"?queueMicrotask:a=>Promise.resolve().then(a);function sn(a){let e=!1,o;const n=new Map,c=new Set;let s;const d=()=>{if(e)return;const L=[...c];c.clear();try{for(const j of L)j()}finally{s=!1,c.size&&(s=!0,Ce(d))}},S=new Proxy({},{get(L,j){if(o){let H=n.get(j);H||(H=new Set,n.set(j,H)),H.add(o)}return L[j]},set(L,j,H){if(L[j]!==H){L[j]=H;const _=n.get(j);if(_){for(const U of _)c.add(U);s||(s=!0,Ce(d))}}return!0}}),N=L=>{const j=()=>{const H=o;o=j;try{return L()}finally{o=H}};return j()};return a.addEventListener("abort",()=>{e=!0}),{state:S,createEffect:N}}function Oe(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 pt=new WeakMap;function rn(a,e,o){{const n=a.closest(".tabpanel");let c=pt.get(n);c||(c=new IntersectionObserver(o,{root:n,rootMargin:"50% 0px 50% 0px",threshold:0}),pt.set(n,c),e.addEventListener("abort",()=>{c.disconnect()})),c.observe(a)}}const qe=[],{assign:Ie}=Object;function dn(a,e){const o={},n=new AbortController,c=n.signal,{state:s,createEffect:d}=sn(c),S=new Map;Ie(s,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),Ie(s,e),Ie(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:Ta,isRtl:!1,currentGroupIndex:0,groups:Ae,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()},L=t=>a.getElementById(`emo-${t.id}`),j=(t,r)=>{o.rootElement.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0}))},H=(t,r)=>t.id===r.id,_=(t,r)=>{const{category:l,emojis:b}=t,{category:E,emojis:w}=r;return l!==E?!1:Oe(b,w,H)},U=t=>{Oe(s.currentEmojis,t,H)||(s.currentEmojis=t)},se=t=>{s.searchMode!==t&&(s.searchMode=t)},ae=t=>{Oe(s.currentEmojisWithCategories,t,_)||(s.currentEmojisWithCategories=t)},ie=(t,r)=>r&&t.skins&&t.skins[r]||t.unicode,V={labelWithSkin:(t,r)=>Ua([t.name||ie(t,r),t.annotation,...t.shortcodes||qe].filter(Boolean)).join(", "),titleForEmoji:t=>t.annotation||(t.shortcodes||qe).join(", "),unicodeWithSkin:ie},ne={onClickSkinToneButton:y,onEmojiClick:m,onNavClick:re,onNavKeydown:X,onSearchKeydown:F,onSkinToneOptionsClick:g,onSkinToneOptionsFocusOut:p,onSkinToneOptionsKeydown:h,onSkinToneOptionsKeyup:M,onSearchInput:i},oe={calculateEmojiGridStyle:$,updateOnIntersection:A};let de=!0;d(()=>{on(a,s,V,ne,oe,o,c,S,de),de=!1}),s.emojiVersion||Fe().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},$a);try{await s.database.ready(),s.databaseLoaded=!0}catch(b){console.error(b),s.message=s.i18n.networkErrorMessage}finally{clearTimeout(l),r&&(r=!1,s.message="")}}s.database&&t()}),d(()=>{s.pickerStyle=`
|
|
52
52
|
--num-groups: ${s.groups.length};
|
|
53
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=`
|
|
54
|
+
--num-skintones: ${st};`}),d(()=>{s.customEmoji&&s.database&&B()}),d(()=>{s.customEmoji&&s.customEmoji.length?s.groups!==We&&(s.groups=We):s.groups!==Ae&&(s.currentGroupIndex&&s.currentGroupIndex--,s.groups=Ae)}),d(()=>{async function t(){s.databaseLoaded&&(s.currentSkinTone=await s.database.getPreferredSkinTone())}t()}),d(()=>{s.skinTones=Array(st).fill().map((t,r)=>Fa(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(Sa.map(b=>r.getEmojiByUnicodeOrName(b)))).filter(Boolean);s.defaultFavoriteEmojis=l}s.databaseLoaded&&t()});function B(){const{customEmoji:t,database:r}=s,l=t||qe;r.customEmoji!==l&&(r.customEmoji=l)}d(()=>{async function t(){B();const{database:r,defaultFavoriteEmojis:l,numColumns:b}=s,E=await r.getTopFavoriteEmoji(b),w=await T(Mt([...E,...l],P=>P.unicode||P.name).slice(0,b));s.currentFavorites=w}s.databaseLoaded&&s.defaultFavoriteEmojis&&t()});function $(t){Oa(t,c,()=>{{const r=getComputedStyle(o.rootElement),l=parseInt(r.getPropertyValue("--num-columns"),10),b=r.getPropertyValue("direction")==="rtl";s.numColumns=l,s.isRtl=b}})}function A(t){rn(t,c,r=>{for(const{target:l,isIntersecting:b}of r)l.classList.toggle("onscreen",b)})}d(()=>{async function t(){const{searchText:r,currentGroup:l,databaseLoaded:b,customEmoji:E}=s;if(!b)s.currentEmojis=[],s.searchMode=!1;else if(r.length>=Ea){const w=await W(r);s.searchText===r&&(U(w),se(!0))}else{const{id:w}=l;if(w!==-1||E&&E.length){const P=await z(w);s.currentGroup.id===w&&(U(P),se(!1))}}}t()});const v=()=>{Se(()=>Ra(o.tabpanelElement))};d(()=>{const{currentEmojis:t,emojiVersion:r}=s,l=t.filter(b=>b.unicode).filter(b=>it(b)&&!Ve.has(b.unicode));if(!r&&l.length)U(t),Se(()=>k(l));else{const b=r?t:t.filter(C);U(b),v()}});function k(t){qa(t,o.baselineEmoji,L)?v():s.currentEmojis=[...s.currentEmojis]}function C(t){return!t.unicode||!it(t)||Ve.get(t.unicode)}async function D(t){const r=s.emojiVersion||await Fe();return t.filter(({version:l})=>!l||l<=r)}async function T(t){return Ha(t,s.emojiVersion||await Fe())}async function z(t){const r=t===-1?s.customEmoji:await s.database.getEmojiByGroup(t);return T(await D(r))}async function W(t){return T(await D(await s.database.getEmojiBySearchQuery(t)))}d(()=>{}),d(()=>{function t(){const{searchMode:l,currentEmojis:b}=s;if(l)return[{category:"",emojis:b}];const E=new Map;for(const w of b){const P=w.category||"";let G=E.get(P);G||(G=[],E.set(P,G)),G.push(w)}return[...E.entries()].map(([w,P])=>({category:w,emojis:P})).sort((w,P)=>s.customCategorySorting(w.category,P.category))}const r=t();ae(r)}),d(()=>{s.activeSearchItemId=s.activeSearchItem!==-1&&s.currentEmojis[s.activeSearchItem].id}),d(()=>{const{rawSearchText:t}=s;St(()=>{s.searchText=(t||"").trim(),s.activeSearchItem=-1})});function F(t){if(!s.searchMode||!s.currentEmojis.length)return;const r=l=>{ge(t),s.activeSearchItem=He(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 ge(t),pe(s.currentEmojis[s.activeSearchItem].id)}}function re(t){const{target:r}=t,l=r.closest(".nav-button");if(!l)return;const b=parseInt(l.dataset.groupId,10);o.searchElement.value="",s.rawSearchText="",s.searchText="",s.activeSearchItem=-1,s.currentGroupIndex=s.groups.findIndex(E=>E.id===b)}function X(t){const{target:r,key:l}=t,b=E=>{E&&(ge(t),E.focus())};switch(l){case"ArrowLeft":return b(r.previousElementSibling);case"ArrowRight":return b(r.nextElementSibling);case"Home":return b(r.parentElement.firstElementChild);case"End":return b(r.parentElement.lastElementChild)}}async function le(t){const r=await s.database.getEmojiByUnicodeOrName(t),l=[...s.currentEmojis,...s.currentFavorites].find(E=>E.id===t),b=l.unicode&&ie(l,s.currentSkinTone);return await s.database.incrementFavoriteEmojiCount(t),{emoji:r,skinTone:s.currentSkinTone,...b&&{unicode:b},...l.name&&{name:l.name}}}async function pe(t){const r=le(t);j("emoji-click-sync",r),j("emoji-click",await r)}function m(t){const{target:r}=t;if(!r.classList.contains("emoji"))return;ge(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 g(t){const{target:{id:r}}=t,l=r&&r.match(/^skintone-(\d)/);if(!l)return;ge(t);const b=parseInt(l[1],10);u(b)}function y(t){s.skinTonePickerExpanded=!s.skinTonePickerExpanded,s.activeSkinTone=s.currentSkinTone,s.skinTonePickerExpanded&&(ge(t),Se(()=>N("skintone-list")))}d(()=>{s.skinTonePickerExpanded?o.skinToneDropdown.addEventListener("transitionend",()=>{s.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):s.skinTonePickerExpandedAfterAnimation=!1});function h(t){if(!s.skinTonePickerExpanded)return;const r=async l=>{ge(t),s.activeSkinTone=l};switch(t.key){case"ArrowUp":return r(He(!0,s.activeSkinTone,s.skinTones));case"ArrowDown":return r(He(!1,s.activeSkinTone,s.skinTones));case"Home":return r(0);case"End":return r(s.skinTones.length-1);case"Enter":return ge(t),u(s.activeSkinTone);case"Escape":return ge(t),s.skinTonePickerExpanded=!1,N("skintone-button")}}function M(t){if(s.skinTonePickerExpanded)switch(t.key){case" ":return ge(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){Ie(s,t)},$destroy(){n.abort()}}}const ln="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",cn="en";var pn={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"}},un=':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 Ct=["customEmoji","customCategorySorting","database","dataSource","i18n","locale","skinToneEmoji","emojiVersion"],gn=`:host{--emoji-font-family:${Bt}}`;class Qe extends HTMLElement{constructor(e){super(),this.attachShadow({mode:"open"});const o=document.createElement("style");o.textContent=un+gn,this.shadowRoot.appendChild(o),this._ctx={locale:cn,dataSource:ln,skinToneEmoji:Ia,customCategorySorting:Ba,customEmoji:null,i18n:pn,emojiVersion:null,...e};for(const n of Ct)n!=="database"&&Object.prototype.hasOwnProperty.call(this,n)&&(this._ctx[n]=this[n],delete this[n]);this._dbFlush()}connectedCallback(){ut(this),this._cmp||(this._cmp=dn(this.shadowRoot,this._ctx))}disconnectedCallback(){ut(this),Ce(()=>{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 ka({locale:e,dataSource:o}))}_dbFlush(){Ce(()=>this._dbCreate())}}const zt={};for(const a of Ct)zt[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(Qe.prototype,zt);function ut(a){a instanceof Qe||Object.setPrototypeOf(a,customElements.get(a.tagName.toLowerCase()).prototype)}customElements.get("emoji-picker")||customElements.define("emoji-picker",Qe);function mn(a,e){const o=document.getElementById("app"),n=new gt,c=new ze,s=a.id||a._id;let d=null,S=[];const N=localStorage.getItem("currentTheme")||"dark";yn(N),o.innerHTML=`
|
|
55
55
|
<div class="dashboard">
|
|
56
56
|
<aside class="sidebar">
|
|
57
57
|
<div class="sidebar-header">
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
<div id="contentArea"></div>
|
|
127
127
|
</main>
|
|
128
128
|
</div>
|
|
129
|
-
`,document.querySelectorAll(".nav-item").forEach(
|
|
129
|
+
`,document.querySelectorAll(".nav-item").forEach(m=>{m.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(g=>g.classList.remove("active")),m.classList.add("active");const u=m.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 L(m){S=(await n.getGroups()).groups,m.innerHTML=`
|
|
130
130
|
<div class="view-header">
|
|
131
131
|
<h2>群组管理</h2>
|
|
132
132
|
<button class="btn-primary" id="createGroupBtn">创建群组</button>
|
|
@@ -168,45 +168,45 @@
|
|
|
168
168
|
<button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
|
|
169
169
|
</div>
|
|
170
170
|
</div>
|
|
171
|
-
`;const
|
|
172
|
-
<h3>${
|
|
173
|
-
<p>${
|
|
171
|
+
`;const g=document.getElementById("groupsList");S.forEach(y=>{const h=document.createElement("div");h.className="group-card",h.innerHTML=`
|
|
172
|
+
<h3>${y.name}</h3>
|
|
173
|
+
<p>${y.description||"暂无描述"}</p>
|
|
174
174
|
<div class="group-stats">
|
|
175
|
-
<span>👥 ${
|
|
176
|
-
<span>📄 ${
|
|
175
|
+
<span>👥 ${y.members.length} 成员</span>
|
|
176
|
+
<span>📄 ${y.documents.length} 文档</span>
|
|
177
177
|
</div>
|
|
178
178
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
179
|
-
<button class="btn-select" data-id="${
|
|
180
|
-
<button class="btn-secondary" data-id="${
|
|
179
|
+
<button class="btn-select" data-id="${y._id}">选择</button>
|
|
180
|
+
<button class="btn-secondary" data-id="${y._id}" data-action="manage">管理成员</button>
|
|
181
181
|
</div>
|
|
182
|
-
`,
|
|
182
|
+
`,g.appendChild(h)}),document.querySelectorAll(".btn-select").forEach(y=>{y.addEventListener("click",()=>{d=S.find(h=>h._id===y.dataset.id),e.joinGroup(d._id),alert(`已加入群组: ${d.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(y=>{y.addEventListener("click",async()=>{const h=y.dataset.id;await H(h)})}),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 y=>{y.preventDefault();const h=new FormData(y.target),M=Array.from(document.querySelectorAll("#usersList input:checked")).map(p=>p.value);try{const p=await n.createGroup(h.get("name"),h.get("description"),M);alert("群组创建成功!"),await L(m),document.getElementById("createGroupModal").classList.add("hidden")}catch(p){console.error("创建群组错误:",p),alert("创建失败: "+p.message)}})}async function j(){try{const m=await n.getAllUsers(),u=document.getElementById("usersList");u.innerHTML=m.users.map(g=>`
|
|
183
183
|
<label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
|
|
184
|
-
<input type="checkbox" value="${
|
|
185
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${
|
|
186
|
-
<span>${
|
|
184
|
+
<input type="checkbox" value="${g._id}">
|
|
185
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${g.username[0].toUpperCase()}</div>
|
|
186
|
+
<span>${g.username} (${g.role==="admin"?"管理员":"用户"})</span>
|
|
187
187
|
</label>
|
|
188
|
-
`).join("")}catch(
|
|
189
|
-
<h4>当前成员 (${
|
|
188
|
+
`).join("")}catch(m){console.error("加载用户失败:",m),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function H(m){try{const u=await n.getGroup(m),g=await n.getAllUsers(),y=u.group,h=document.getElementById("currentMembers");h.innerHTML=`
|
|
189
|
+
<h4>当前成员 (${y.members.length})</h4>
|
|
190
190
|
<div style="max-height: 200px; overflow-y: auto;">
|
|
191
|
-
${
|
|
191
|
+
${y.members.map(t=>`
|
|
192
192
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
193
193
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
194
194
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${t.username[0].toUpperCase()}</div>
|
|
195
|
-
<span>${t.username} ${t._id.toString()===
|
|
195
|
+
<span>${t.username} ${t._id.toString()===y.admin._id.toString()?"(管理员)":""}</span>
|
|
196
196
|
</div>
|
|
197
|
-
${t._id.toString()!==
|
|
197
|
+
${t._id.toString()!==y.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${m}', '${t._id}')">移除</button>`:""}
|
|
198
198
|
</div>
|
|
199
199
|
`).join("")}
|
|
200
200
|
</div>
|
|
201
|
-
`;const M=
|
|
201
|
+
`;const M=y.members.map(t=>t._id.toString()),p=g.users.filter(t=>!M.includes(t._id)),i=document.getElementById("availableUsers");p.length===0?i.innerHTML="<p>所有用户都已在群组中</p>":i.innerHTML=p.map(t=>`
|
|
202
202
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
203
203
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
204
204
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${t.username[0].toUpperCase()}</div>
|
|
205
205
|
<span>${t.username}</span>
|
|
206
206
|
</div>
|
|
207
|
-
<button class="btn-primary btn-sm" onclick="addMember('${
|
|
207
|
+
<button class="btn-primary btn-sm" onclick="addMember('${m}', '${t._id}')">添加</button>
|
|
208
208
|
</div>
|
|
209
|
-
`).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(u){console.error("加载成员失败:",u),alert("加载失败: "+u.message)}}window.addMember=async(
|
|
209
|
+
`).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(u){console.error("加载成员失败:",u),alert("加载失败: "+u.message)}}window.addMember=async(m,u)=>{try{await n.addMember(m,u),alert("成员添加成功!"),await H(m)}catch(g){alert("添加失败: "+g.message)}},window.removeMember=async(m,u)=>{if(confirm("确定要移除该成员吗?"))try{await n.removeMember(m,u),alert("成员移除成功!"),await H(m)}catch(g){alert("移除失败: "+g.message)}};async function _(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const u=await n.getTasks(d._id);m.innerHTML=`
|
|
210
210
|
<div class="view-header">
|
|
211
211
|
<h2>任务管理 - ${d.name}</h2>
|
|
212
212
|
<button class="btn-primary" id="createTaskBtn">创建任务</button>
|
|
@@ -248,21 +248,21 @@
|
|
|
248
248
|
</div>
|
|
249
249
|
</div>
|
|
250
250
|
</div>
|
|
251
|
-
`;const
|
|
251
|
+
`;const g=document.getElementById("tasksList");u.tasks.length===0?g.innerHTML='<div class="empty-state">暂无任务</div>':(u.tasks.forEach(y=>{const h=document.createElement("div");h.className=`task-card status-${y.status}`,h.innerHTML=`
|
|
252
252
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
253
253
|
<div style="flex: 1;">
|
|
254
|
-
<h3>${
|
|
255
|
-
<p>${
|
|
254
|
+
<h3>${y.title}</h3>
|
|
255
|
+
<p>${y.description||"无描述"}</p>
|
|
256
256
|
<div class="task-meta">
|
|
257
|
-
<span class="status-badge">${v(
|
|
258
|
-
<span>截止: ${
|
|
257
|
+
<span class="status-badge">${v(y.status)}</span>
|
|
258
|
+
<span>截止: ${y.deadline?new Date(y.deadline).toLocaleDateString():"无"}</span>
|
|
259
259
|
</div>
|
|
260
260
|
</div>
|
|
261
261
|
<div style="display: flex; gap: 10px;">
|
|
262
|
-
<button class="btn-primary btn-sm" data-id="${
|
|
263
|
-
<button class="btn-danger btn-sm" data-id="${
|
|
262
|
+
<button class="btn-primary btn-sm" data-id="${y._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
|
|
263
|
+
<button class="btn-danger btn-sm" data-id="${y._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
|
|
264
264
|
</div>
|
|
265
|
-
`,
|
|
265
|
+
`,g.appendChild(h)}),document.querySelectorAll('[data-action="delete-task"]').forEach(y=>{y.addEventListener("click",async h=>{h.stopPropagation();const M=y.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await n.deleteTask(M),alert("任务删除成功!"),await _(m)}catch(p){console.error("删除任务错误:",p),alert("删除失败: "+(p.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(y=>{y.addEventListener("click",async h=>{var i;h.stopPropagation();const M=y.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=`
|
|
266
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;">
|
|
267
267
|
|
|
268
268
|
<!-- 任务标题 -->
|
|
@@ -319,14 +319,14 @@
|
|
|
319
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>`:""}
|
|
320
320
|
</div>
|
|
321
321
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
322
|
-
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(l=>{var
|
|
323
|
-
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${
|
|
324
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${
|
|
322
|
+
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(l=>{var w,P;const b=p.completedBy&&p.completedBy.some(G=>G.user&&G.user._id===l._id),E=b?p.completedBy.find(G=>G.user&&G.user._id===l._id):null;return`
|
|
323
|
+
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${b?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${b?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${b?"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, ${b?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${b?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((P=(w=l.username)==null?void 0:w[0])==null?void 0:P.toUpperCase())||"?"}</div>
|
|
325
325
|
<div style="display: flex; flex-direction: column; gap: 1px;">
|
|
326
326
|
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${l.username||"未知用户"}</span>
|
|
327
|
-
${
|
|
327
|
+
${b?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${E&&E.completedAt?new Date(E.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>'}
|
|
328
328
|
</div>
|
|
329
|
-
${
|
|
329
|
+
${b?'<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>':""}
|
|
330
330
|
</div>
|
|
331
331
|
`}).join(""):'<p style="margin: 0; color: var(--text-secondary); font-style: italic; font-size: 13px;">未分配给任何人</p>'}
|
|
332
332
|
</div>
|
|
@@ -417,23 +417,23 @@
|
|
|
417
417
|
|
|
418
418
|
<!-- 投票选项 -->
|
|
419
419
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
420
|
-
${((i=p.poll.options)==null?void 0:i.map(l=>{const
|
|
420
|
+
${((i=p.poll.options)==null?void 0:i.map(l=>{const b=p.poll.totalVotes>0?Math.round((l.votes||0)/p.poll.totalVotes*100):0,E=l.voters||[];return`
|
|
421
421
|
<div style="background: var(--bg-tertiary); border-radius: 8px; padding: 10px; border: 1px solid var(--border);">
|
|
422
422
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
|
423
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} 票 (${
|
|
424
|
+
<span style="font-weight: 700; font-size: 13px; color: var(--primary);">${l.votes||0} 票 (${b}%)</span>
|
|
425
425
|
</div>
|
|
426
426
|
<div style="height: 6px; background: var(--bg-secondary); border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
|
|
427
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
427
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${b}%; transition: width 0.3s;"></div>
|
|
428
428
|
</div>
|
|
429
|
-
${
|
|
429
|
+
${E.length>0?`
|
|
430
430
|
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
431
|
-
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${
|
|
431
|
+
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${E.length}人):</div>
|
|
432
432
|
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
|
433
|
-
${
|
|
433
|
+
${E.map(w=>{const P=typeof w=="string"?w:w.username;return`
|
|
434
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;">
|
|
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;">${
|
|
436
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
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;">${P?P[0].toUpperCase():"?"}</span>
|
|
436
|
+
<span style="font-weight: 600; color: var(--text-primary);">${P||"未知用户"}</span>
|
|
437
437
|
</span>
|
|
438
438
|
`}).join("")}
|
|
439
439
|
</div>
|
|
@@ -453,10 +453,10 @@
|
|
|
453
453
|
<span>未投票成员 (${p.poll.unvotedMembers.length}人):</span>
|
|
454
454
|
</div>
|
|
455
455
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
456
|
-
${p.poll.unvotedMembers.map(l=>{const
|
|
456
|
+
${p.poll.unvotedMembers.map(l=>{const b=typeof l=="string"?l:l.username;return`
|
|
457
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;">
|
|
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;">${
|
|
459
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
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;">${b?b[0].toUpperCase():"?"}</span>
|
|
459
|
+
<span style="font-weight: 600; color: var(--text-primary);">${b||"未知用户"}</span>
|
|
460
460
|
<span style="font-size: 10px; color: var(--danger); background: rgba(239, 68, 68, 0.1); padding: 2px 6px; border-radius: 8px;">未投票</span>
|
|
461
461
|
</span>
|
|
462
462
|
`}).join("")}
|
|
@@ -472,7 +472,7 @@
|
|
|
472
472
|
`:""}
|
|
473
473
|
|
|
474
474
|
</div>
|
|
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
|
|
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 y=>{y.preventDefault();const h=new FormData(y.target);try{const p=(await n.getGroup(d._id)).group.members.map(i=>i._id);await n.createTask({title:h.get("title"),description:h.get("description"),groupId:d._id,assignedTo:p,deadline:h.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await _(m),document.getElementById("createTaskModal").classList.add("hidden")}catch(M){console.error("创建任务错误:",M),alert("创建失败: "+M.message)}})}async function U(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const u=await n.getDocuments(d._id),y=(await n.getGroup(d._id)).group;m.innerHTML=`
|
|
476
476
|
<div class="view-header">
|
|
477
477
|
<h2>📄 共享文档 - ${d.name}</h2>
|
|
478
478
|
<button class="btn-primary" id="createDocBtn">➕ 创建文档</button>
|
|
@@ -498,13 +498,13 @@
|
|
|
498
498
|
<div class="form-group">
|
|
499
499
|
<label>👥 成员编辑权限</label>
|
|
500
500
|
<div style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
501
|
-
${
|
|
501
|
+
${y.members.map(p=>`
|
|
502
502
|
<div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
|
|
503
503
|
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${p.username[0].toUpperCase()}</div>
|
|
504
504
|
<span style="flex: 1;">${p.username}</span>
|
|
505
505
|
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
|
506
|
-
<input type="checkbox" name="editableMembers" value="${p._id}" ${p._id===
|
|
507
|
-
<span style="font-size: 13px;">${p._id===
|
|
506
|
+
<input type="checkbox" name="editableMembers" value="${p._id}" ${p._id===y.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
|
|
507
|
+
<span style="font-size: 13px;">${p._id===y.admin.toString()?"管理员":"可编辑"}</span>
|
|
508
508
|
</label>
|
|
509
509
|
</div>
|
|
510
510
|
`).join("")}
|
|
@@ -530,7 +530,7 @@
|
|
|
530
530
|
</div>
|
|
531
531
|
</div>
|
|
532
532
|
</div>
|
|
533
|
-
`;const
|
|
533
|
+
`;const h=document.getElementById("docsList");u.documents.length===0?h.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=`
|
|
534
534
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
535
535
|
<div style="flex: 1;">
|
|
536
536
|
<h3>📄 ${p.title}</h3>
|
|
@@ -546,20 +546,20 @@
|
|
|
546
546
|
<button class="btn-danger btn-sm" data-id="${p._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
|
|
547
547
|
</div>
|
|
548
548
|
</div>
|
|
549
|
-
`,
|
|
549
|
+
`,h.appendChild(i)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(p=>{p.addEventListener("click",()=>{se(m,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,y)})}),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(m)}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=`
|
|
550
550
|
<div style="margin-bottom: 16px;">
|
|
551
551
|
<h4 style="margin: 0 0 8px 0;">📄 ${p.title}</h4>
|
|
552
552
|
<p style="font-size: 13px; color: var(--text-secondary); margin: 0;">管理哪些成员可以编辑此文档</p>
|
|
553
553
|
</div>
|
|
554
554
|
<form id="updatePermissionForm">
|
|
555
555
|
<div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
556
|
-
${i.members.map(l=>{var
|
|
556
|
+
${i.members.map(l=>{var w;const b=((w=p.editableMembers)==null?void 0:w.includes(l._id))||l._id===i.admin.toString(),E=l._id===i.admin.toString();return`
|
|
557
557
|
<div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
|
|
558
558
|
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${l.username[0].toUpperCase()}</div>
|
|
559
559
|
<span style="flex: 1;">${l.username}</span>
|
|
560
560
|
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
|
561
|
-
<input type="checkbox" name="editableMembers" value="${l._id}" ${
|
|
562
|
-
<span style="font-size: 13px;">${
|
|
561
|
+
<input type="checkbox" name="editableMembers" value="${l._id}" ${b?"checked":""} ${E?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
|
|
562
|
+
<span style="font-size: 13px;">${E?"管理员":"可编辑"}</span>
|
|
563
563
|
</label>
|
|
564
564
|
</div>
|
|
565
565
|
`}).join("")}
|
|
@@ -570,11 +570,11 @@
|
|
|
570
570
|
<button type="button" class="btn-secondary" id="cancelPermissionBtn" style="flex: 1;">取消</button>
|
|
571
571
|
</div>
|
|
572
572
|
</form>
|
|
573
|
-
`,t.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{t.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async l=>{l.preventDefault();const
|
|
573
|
+
`,t.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{t.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async l=>{l.preventDefault();const E=new FormData(l.target).getAll("editableMembers");try{await n.updateDocumentPermissions(p._id,E),alert("权限更新成功!"),t.classList.add("hidden"),await U(m)}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 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(m),document.getElementById("createDocModal").classList.add("hidden")}catch(r){console.error("创建文档错误:",r),alert("创建失败: "+r.message)}})}async function se(m,u){const y=(await n.getDocument(u)).document;m.innerHTML=`
|
|
574
574
|
<div class="view-header">
|
|
575
575
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
576
|
-
<h2>${
|
|
577
|
-
<span class="doc-status">${
|
|
576
|
+
<h2>${y.title}</h2>
|
|
577
|
+
<span class="doc-status">${y.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
|
|
578
578
|
</div>
|
|
579
579
|
<div class="editor-container">
|
|
580
580
|
<div class="editor-toolbar">
|
|
@@ -585,10 +585,10 @@
|
|
|
585
585
|
</div>
|
|
586
586
|
<div id="editor"></div>
|
|
587
587
|
<div class="editor-footer">
|
|
588
|
-
<span>最后编辑: ${new Date(
|
|
588
|
+
<span>最后编辑: ${new Date(y.updatedAt).toLocaleString()}</span>
|
|
589
589
|
</div>
|
|
590
590
|
</div>
|
|
591
|
-
`;const
|
|
591
|
+
`;const h=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});h.root.innerHTML=y.content||"";let M,p;h.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=h.root.innerHTML;try{await n.updateDocument(u,i)}catch(t){console.error("自动保存失败:",t)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const i=h.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&&(h.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(m)})}async function ae(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=await n.getGroupFiles(d._id);m.innerHTML=`
|
|
592
592
|
<div class="view-header">
|
|
593
593
|
<h2>文件管理 - ${d.name}</h2>
|
|
594
594
|
<button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
|
|
@@ -619,27 +619,129 @@
|
|
|
619
619
|
</form>
|
|
620
620
|
</div>
|
|
621
621
|
</div>
|
|
622
|
-
`;const
|
|
622
|
+
`;const g=document.getElementById("filesList");!u.files||u.files.length===0?g.innerHTML='<div class="empty-state">暂无文件</div>':(u.files.forEach(y=>{const h=document.createElement("div");h.className="file-card";const M=ie(y.mimetype),p=ce(y.size);h.innerHTML=`
|
|
623
623
|
<div class="file-icon">${M}</div>
|
|
624
624
|
<div class="file-info">
|
|
625
|
-
<h4>${
|
|
625
|
+
<h4>${y.originalName}</h4>
|
|
626
626
|
<div class="file-meta">
|
|
627
|
-
<span>上传者: ${
|
|
627
|
+
<span>上传者: ${y.uploader.username}</span>
|
|
628
628
|
<span>大小: ${p}</span>
|
|
629
|
-
<span>时间: ${new Date(
|
|
629
|
+
<span>时间: ${new Date(y.createdAt).toLocaleString()}</span>
|
|
630
630
|
</div>
|
|
631
|
-
${
|
|
631
|
+
${y.description?`<p class="file-description">${y.description}</p>`:""}
|
|
632
632
|
</div>
|
|
633
633
|
<div class="file-actions">
|
|
634
|
-
<a href="${n.getFileDownloadUrl(
|
|
635
|
-
<button class="btn-danger" data-id="${
|
|
634
|
+
<a href="${n.getFileDownloadUrl(y._id)}" class="btn-primary" download>下载</a>
|
|
635
|
+
<button class="btn-danger" data-id="${y._id}" data-action="delete-file">删除</button>
|
|
636
636
|
</div>
|
|
637
|
-
`,
|
|
637
|
+
`,g.appendChild(h)}),document.querySelectorAll('[data-action="delete-file"]').forEach(y=>{y.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await n.deleteFile(y.dataset.id),alert("文件删除成功!"),await ae(m)}catch(h){alert("删除失败: "+h.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 y=>{y.preventDefault();const h=document.getElementById("fileInput"),M=document.getElementById("fileDescription").value;if(!h.files[0]){alert("请选择文件");return}try{await n.uploadFile(d._id,h.files[0],M),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await ae(m)}catch(p){alert("上传失败: "+p.message)}})}catch(u){console.error("获取文件列表失败:",u),m.innerHTML=`
|
|
638
638
|
<div class="view-header">
|
|
639
639
|
<h2>文件管理</h2>
|
|
640
640
|
</div>
|
|
641
641
|
<div class="empty-state">加载文件失败: ${u.message}</div>
|
|
642
|
-
`}}function
|
|
642
|
+
`}}function ie(m){return m.startsWith("image/")?"🖼️":m==="application/pdf"?"📕":m.includes("word")||m.includes("document")?"📘":m.includes("excel")||m.includes("spreadsheet")?"📗":m.includes("zip")||m.includes("compressed")?"📦":"📄"}function ce(m){if(m===0)return"0 Bytes";const u=1024,g=["Bytes","KB","MB","GB"],y=Math.floor(Math.log(m)/Math.log(u));return Math.round(m/Math.pow(u,y)*100)/100+" "+g[y]}async function q(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let g=(await n.getGroup(d._id)).group;function y(f){if(f.startsWith("[白板作品]")){const I=f.replace("[白板作品]","").trim(),x=I.includes("/api/files/")&&I.includes("/download");let R=I;if(x&&!I.includes("token=")){const O=localStorage.getItem("token");R=I.includes("?")?`${I}&token=${O}`:`${I}?token=${O}`}return`
|
|
643
|
+
<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);">
|
|
644
|
+
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
645
|
+
<span style="font-size: 18px;">🎨</span>
|
|
646
|
+
<span>白板作品</span>
|
|
647
|
+
</div>
|
|
648
|
+
<div style="position: relative; display: inline-block;">
|
|
649
|
+
<img src="${R}" alt="白板作品"
|
|
650
|
+
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);"
|
|
651
|
+
onclick="window.open('${R}', '_blank')"
|
|
652
|
+
onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${R}');"
|
|
653
|
+
onload="console.log('白板图片加载成功:', '${R}');">
|
|
654
|
+
<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;">
|
|
655
|
+
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
656
|
+
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
657
|
+
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
658
|
+
<button onclick="navigator.clipboard.writeText('${I}'); 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>
|
|
659
|
+
</div>
|
|
660
|
+
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
`}if(f.startsWith("[投票]")){const I=f.replace("[投票]","").trim();return`
|
|
664
|
+
<div class="poll-card" data-poll-id="${I}" 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('${I}')">
|
|
665
|
+
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
666
|
+
<span style="font-size: 32px;">📊</span>
|
|
667
|
+
<div style="flex: 1;">
|
|
668
|
+
<div style="font-weight: 600; font-size: 16px; color: var(--text-primary); margin-bottom: 4px;">投票</div>
|
|
669
|
+
<div style="font-size: 13px; color: var(--text-secondary);">点击查看详情并参与投票</div>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
<div style="padding: 8px 12px; background: rgba(99, 102, 241, 0.1); border-radius: 6px; font-size: 12px; color: var(--primary); text-align: center;">
|
|
673
|
+
📋 查看投票详情
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
`}return f}window.viewPollDetail=async f=>{try{const I=localStorage.getItem("token"),x=await fetch(`http://localhost:3000/api/polls/${f}`,{headers:{Authorization:`Bearer ${I}`}});if(!x.ok)throw new Error("获取投票详情失败");const O=(await x.json()).poll,K=O.options.reduce((Q,ue)=>Q+ue.votes.length,0),Z=O.status==="ended"||O.endTime&&new Date(O.endTime)<new Date,J=`
|
|
677
|
+
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
678
|
+
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
679
|
+
<div class="modal-header">
|
|
680
|
+
<h3>📊 投票详情</h3>
|
|
681
|
+
<button class="modal-close" id="closePollDetailModal">×</button>
|
|
682
|
+
</div>
|
|
683
|
+
<div class="modal-body" style="padding: 24px;">
|
|
684
|
+
<h2 style="margin: 0 0 12px 0;">${O.title}</h2>
|
|
685
|
+
${O.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${O.description}</p>`:""}
|
|
686
|
+
|
|
687
|
+
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
|
|
688
|
+
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
689
|
+
${O.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
690
|
+
</span>
|
|
691
|
+
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
692
|
+
${O.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
693
|
+
</span>
|
|
694
|
+
<span style="font-size: 13px; padding: 6px 12px; background: ${Z?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
695
|
+
${Z?"已结束":"进行中"}
|
|
696
|
+
</span>
|
|
697
|
+
</div>
|
|
698
|
+
|
|
699
|
+
<div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; margin-bottom: 20px;">
|
|
700
|
+
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
701
|
+
<div>
|
|
702
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
703
|
+
<div style="font-weight: 600;">👤 ${O.creatorName}</div>
|
|
704
|
+
</div>
|
|
705
|
+
<div>
|
|
706
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
707
|
+
<div style="font-weight: 600; color: var(--primary);">👥 ${K} 人</div>
|
|
708
|
+
</div>
|
|
709
|
+
<div>
|
|
710
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
711
|
+
<div style="font-weight: 600;">⏰ ${new Date(O.createdAt).toLocaleString("zh-CN")}</div>
|
|
712
|
+
</div>
|
|
713
|
+
${O.endTime?`
|
|
714
|
+
<div>
|
|
715
|
+
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
716
|
+
<div style="font-weight: 600;">⏰ ${new Date(O.endTime).toLocaleString("zh-CN")}</div>
|
|
717
|
+
</div>
|
|
718
|
+
`:""}
|
|
719
|
+
</div>
|
|
720
|
+
</div>
|
|
721
|
+
|
|
722
|
+
<h3 style="margin-bottom: 16px;">投票选项</h3>
|
|
723
|
+
${O.options.map((Q,ue)=>{const Y=K>0?(Q.votes.length/K*100).toFixed(1):0;return`
|
|
724
|
+
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
725
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
|
726
|
+
<span style="font-weight: 500; font-size: 16px;">${Q.text}</span>
|
|
727
|
+
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${Q.votes.length} 票 (${Y}%)</span>
|
|
728
|
+
</div>
|
|
729
|
+
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden; margin-bottom: 12px;">
|
|
730
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${Y}%; transition: width 0.3s;"></div>
|
|
731
|
+
</div>
|
|
732
|
+
${!O.anonymous&&Q.votes.length>0?`
|
|
733
|
+
<div style="font-size: 13px; color: var(--text-secondary);">
|
|
734
|
+
<strong>投票者:</strong> ${Q.votes.map(ee=>ee.username).join(", ")}
|
|
735
|
+
</div>
|
|
736
|
+
`:""}
|
|
737
|
+
</div>
|
|
738
|
+
`}).join("")}
|
|
739
|
+
|
|
740
|
+
<button class="btn-secondary" id="closePollDetailBtn" style="width: 100%; margin-top: 20px;">关闭</button>
|
|
741
|
+
</div>
|
|
742
|
+
</div>
|
|
743
|
+
</div>
|
|
744
|
+
`,te=document.getElementById("pollDetailModal");te&&te.remove(),document.body.insertAdjacentHTML("beforeend",J),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()})}catch(I){console.error("加载投票详情失败:",I),alert("加载投票详情失败: "+I.message)}},m.innerHTML=`
|
|
643
745
|
<div class="view-header">
|
|
644
746
|
<h2>群聊 - ${d.name}</h2>
|
|
645
747
|
<div style="display: flex; gap: 10px;">
|
|
@@ -835,44 +937,36 @@
|
|
|
835
937
|
</div>
|
|
836
938
|
</div>
|
|
837
939
|
</div>
|
|
838
|
-
`;const
|
|
839
|
-
<div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
|
|
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)'">
|
|
841
|
-
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
|
|
842
|
-
`}z.innerHTML=`
|
|
940
|
+
`;const h=document.getElementById("messages"),M=document.getElementById("messageInput"),p=document.getElementById("sendBtn"),i=document.getElementById("emojiBtn"),t=document.getElementById("emojiPicker");let r=new Set((g.mutedUsers||[]).map(String)),l=!!g.mutedAll;i.addEventListener("click",()=>{t.classList.toggle("hidden")}),t.addEventListener("emoji-click",f=>{M.value+=f.detail.unicode,M.focus(),t.classList.add("hidden")}),document.addEventListener("click",f=>{!i.contains(f.target)&&!t.contains(f.target)&&t.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const f=await n.getGroupMessages(d._id);f.messages&&(f.messages.forEach(I=>{const x=document.createElement("div");x.className=`message ${I.sender===s?"own":""}`;const R=y(I.content),O=I.content.startsWith("[白板作品]")||I.content.startsWith("[投票]"),K=I.content.startsWith("[白板作品]");x.innerHTML=`
|
|
843
941
|
<div class="message-header">
|
|
844
|
-
<span class="message-user">${
|
|
845
|
-
<span class="message-time">${new Date(
|
|
942
|
+
<span class="message-user">${I.username}</span>
|
|
943
|
+
<span class="message-time">${new Date(I.timestamp).toLocaleTimeString()}</span>
|
|
846
944
|
</div>
|
|
847
|
-
<div class="message-content" style="${
|
|
848
|
-
`,
|
|
945
|
+
<div class="message-content" style="${K?"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);":O?"background: transparent; padding: 0;":""}">${R}</div>
|
|
946
|
+
`,h.appendChild(x)}),h.scrollTop=h.scrollHeight)}catch(f){console.error("加载历史消息失败:",f)}const b=()=>{const f=document.getElementById("muteAllBtn");f.textContent=l?"取消全体禁言":"全体禁言",f.style.background=l?"var(--danger)":""};b(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
|
|
849
947
|
|
|
850
948
|
确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
|
|
851
949
|
|
|
852
|
-
群组名称:`+d.name)!==d.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const
|
|
950
|
+
群组名称:`+d.name)!==d.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const x=document.getElementById("clearChatBtn"),R=x.innerHTML;x.innerHTML="⏳ 清除中...",x.disabled=!0;const O=localStorage.getItem("token"),K=await fetch(`http://localhost:3000/api/messages/group/${d._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${O}`,"Content-Type":"application/json"}});if(!K.ok){const J=await K.json();throw new Error(J.message||"清除失败")}const Z=await K.json();h.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Z.deletedCount||0} 条聊天记录!`),x.innerHTML=R,x.disabled=!1}catch(x){console.error("清除聊天记录失败:",x),alert("❌ 清除失败: "+x.message);const R=document.getElementById("clearChatBtn");R.innerHTML="🗑️ 清除记录",R.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const f=!l;l=!!(await n.setMuteAll(d._id,f)).mutedAll,b();const x=document.createElement("div");x.className="notification",x.textContent=l?"已开启全体禁言(成员无法发言)":"已取消全体禁言",h.appendChild(x),h.scrollTop=h.scrollHeight}catch(f){alert("设置失败: "+f.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{g=(await n.getGroup(d._id)).group,r=new Set((g.mutedUsers||[]).map(String));const I=document.getElementById("membersList");I.innerHTML=g.members.filter(x=>x._id.toString()!==s).map(x=>{const R=r.has(x._id.toString());return`
|
|
853
951
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
|
|
854
952
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
855
|
-
<div class="avatar" style="width: 35px; height: 35px;">${
|
|
856
|
-
<span>${
|
|
953
|
+
<div class="avatar" style="width: 35px; height: 35px;">${x.username[0].toUpperCase()}</div>
|
|
954
|
+
<span>${x.username}</span>
|
|
857
955
|
</div>
|
|
858
|
-
<button class="btn-secondary btn-sm" onclick="toggleMute('${
|
|
859
|
-
${
|
|
956
|
+
<button class="btn-secondary btn-sm" onclick="toggleMute('${x._id}')" id="mute-${x._id}">
|
|
957
|
+
${R?"取消禁言":"禁言"}
|
|
860
958
|
</button>
|
|
861
959
|
</div>
|
|
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
|
|
863
|
-
<input type="text" class="poll-option" placeholder="选项 ${
|
|
960
|
+
`}).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 f=document.getElementById("pollOptions"),I=f.querySelectorAll(".poll-option-input").length+1,x=document.createElement("div");x.className="poll-option-input",x.style.cssText="display: flex; gap: 10px; margin-bottom: 10px;",x.innerHTML=`
|
|
961
|
+
<input type="text" class="poll-option" placeholder="选项 ${I}" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
|
|
864
962
|
<button type="button" class="btn-danger remove-option" style="padding: 10px 15px;">删除</button>
|
|
865
|
-
`,
|
|
866
|
-
<div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
|
|
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)'">
|
|
868
|
-
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
|
|
869
|
-
`}A.innerHTML=`
|
|
963
|
+
`,f.appendChild(x),x.querySelector(".remove-option").addEventListener("click",()=>{f.querySelectorAll(".poll-option-input").length>2?x.remove():alert("至少需要2个选项!")})}),document.getElementById("createPollForm").addEventListener("submit",async f=>{f.preventDefault();const I=document.getElementById("pollTitle").value.trim(),x=document.getElementById("pollDescription").value.trim(),R=document.getElementById("allowMultiple").checked,O=document.getElementById("anonymous").checked,K=document.getElementById("pollEndTime").value,Z=document.querySelectorAll(".poll-option"),J=Array.from(Z).map(te=>te.value.trim()).filter(te=>te);if(J.length<2){alert("至少需要2个选项!");return}try{const te=localStorage.getItem("token"),Q=await fetch("http://localhost:3000/api/polls",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${te}`},body:JSON.stringify({title:I,description:x,options:J,groupId:d._id,allowMultiple:R,anonymous:O,endTime:K||null})});if(!Q.ok){const ee=await Q.json();throw new Error(ee.error||"创建投票失败")}const Y=(await Q.json()).poll;e.sendChatMessage(d._id,a.username,`[投票]${Y._id}`),alert("投票创建成功!"),document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}catch(te){console.error("创建投票失败:",te),alert("创建投票失败: "+te.message)}}),window.toggleMute=async f=>{try{const I=!r.has(f),x=await n.setUserMute(d._id,f,I);r=new Set((x.mutedUsers||[]).map(String));const R=document.getElementById(`mute-${f}`);R.textContent=r.has(f)?"取消禁言":"禁言",R.style.background=r.has(f)?"var(--danger)":""}catch(I){alert("操作失败: "+I.message)}},e.on("chat_message",f=>{if(f.groupId===d._id){const I=document.createElement("div");I.className=`message ${f.userId===s?"own":""}`;const x=y(f.content),R=f.content.startsWith("[白板作品]")||f.content.startsWith("[投票]"),O=f.content.startsWith("[白板作品]");I.innerHTML=`
|
|
870
964
|
<div class="message-header">
|
|
871
|
-
<span class="message-user">${
|
|
872
|
-
<span class="message-time">${new Date(
|
|
965
|
+
<span class="message-user">${f.username}</span>
|
|
966
|
+
<span class="message-time">${new Date(f.timestamp).toLocaleTimeString()}</span>
|
|
873
967
|
</div>
|
|
874
|
-
<div class="message-content" style="${
|
|
875
|
-
`,
|
|
968
|
+
<div class="message-content" style="${O?"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);":R?"background: transparent; padding: 0;":""}">${x}</div>
|
|
969
|
+
`,h.appendChild(I),h.scrollTop=h.scrollHeight}}),e.on("chat_blocked",f=>{if(f.groupId===d._id){const I=document.createElement("div");I.className="notification",I.textContent=f.message||"消息发送失败",h.appendChild(I),h.scrollTop=h.scrollHeight}});const E=()=>{const f=M.value.trim();f&&(e.sendChatMessage(d._id,a.username,f),M.value="")};p.addEventListener("click",E),M.addEventListener("keypress",f=>{f.key==="Enter"&&E()}),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(f=>{f.addEventListener("click",()=>{document.getElementById("aiInputText").value=f.dataset.question,document.getElementById("aiSendBtnModal").click()}),f.addEventListener("mouseenter",I=>{I.target.style.background="var(--primary)",I.target.style.color="white",I.target.style.transform="translateY(-2px)"}),f.addEventListener("mouseleave",I=>{I.target.style.background="var(--bg)",I.target.style.color="inherit",I.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",f=>{f.key==="Enter"&&!f.shiftKey&&(f.preventDefault(),document.getElementById("aiSendBtnModal").click())}),w.addEventListener("focus",()=>{w.style.borderColor="var(--primary)"}),w.addEventListener("blur",()=>{w.style.borderColor="var(--border)"});const P=document.getElementById("aiSendBtnModal");P.addEventListener("mouseenter",()=>{P.style.transform="scale(1.05)"}),P.addEventListener("mouseleave",()=>{P.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const f=document.getElementById("aiInputText"),I=f.value.trim();if(!I)return;const x=document.getElementById("aiChatMessages"),R=document.createElement("div");R.className="ai-message user",R.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;",R.textContent=I,x.appendChild(R),f.value="";const O=document.createElement("div");O.className="ai-message ai loading",O.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",O.textContent="思考中...",x.appendChild(O),x.scrollTop=x.scrollHeight;try{const K=localStorage.getItem("token"),J=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${K}`},body:JSON.stringify({question:I,groupId:d==null?void 0:d._id})})).json();O.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=J.answer||"抱歉,我无法回答这个问题。",x.appendChild(te),x.scrollTop=x.scrollHeight}catch(K){O.remove();const Z=document.createElement("div");Z.className="ai-message ai error",Z.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Z.textContent="抱歉,发生了错误: "+K.message,x.appendChild(Z),x.scrollTop=x.scrollHeight}}),window.showImageModal=f=>{const I=document.getElementById("imagePreviewModal"),x=document.getElementById("previewImage"),R=document.getElementById("downloadImageBtn");x.src=f,I.classList.remove("hidden"),R.onclick=async()=>{try{const K=await(await fetch(f)).blob(),Z=window.URL.createObjectURL(K),J=document.createElement("a");J.href=Z,J.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(J),J.click(),document.body.removeChild(J),window.URL.revokeObjectURL(Z)}catch(O){console.error("下载失败:",O),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",f=>{f.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),G()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function G(){const f=document.getElementById("whiteboardCanvas");if(!f)return;const I=f.getContext("2d");let x=!1,R="pen",O="#000000",K=3,Z=0,J=0;document.querySelectorAll(".tool-btn").forEach(Y=>{Y.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(ee=>{ee.style.background="transparent",ee.style.borderColor="var(--border)",ee.style.color="inherit",ee.classList.remove("active")}),Y.style.background="var(--primary)",Y.style.borderColor="var(--primary)",Y.style.color="white",Y.classList.add("active"),R=Y.dataset.tool}});const te=document.getElementById("colorPickerCanvas");te&&(te.onchange=Y=>{O=Y.target.value});const Q=document.getElementById("brushSizeCanvas"),ue=document.getElementById("brushSizeLabel");Q&&ue&&(Q.oninput=Y=>{K=Y.target.value,ue.textContent=`大小: ${K}`}),f.onmousedown=Y=>{x=!0;const ee=f.getBoundingClientRect();Z=Y.clientX-ee.left,J=Y.clientY-ee.top},f.onmousemove=Y=>{if(!x)return;const ee=f.getBoundingClientRect(),ye=Y.clientX-ee.left,we=Y.clientY-ee.top;I.beginPath(),I.moveTo(Z,J),I.lineTo(ye,we),I.strokeStyle=R==="eraser"?"#ffffff":O,I.lineWidth=K,I.lineCap="round",I.stroke(),Z=ye,J=we},f.onmouseup=()=>{x=!1},f.onmouseleave=()=>{x=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&I.clearRect(0,0,f.width,f.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const Y=f.toDataURL("image/png"),ee=document.createElement("a");ee.download=`whiteboard-${Date.now()}.png`,ee.href=Y,ee.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const Y=f.toDataURL("image/png"),ee=await fetch(Y).then(tt=>tt.blob()),ye=new FormData;ye.append("file",ee,`whiteboard-${Date.now()}.png`),ye.append("groupId",d._id),ye.append("description","协作白板作品");const we=localStorage.getItem("token"),et=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${we}`},body:ye});if(et.ok){const Dt=`http://localhost:3000/api/files/${(await et.json()).file._id}/download?token=${we}`;e.sendChatMessage(d._id,a.username,`[白板作品]${Dt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(Y){console.error("发送白板作品错误:",Y),alert("发送失败: "+Y.message)}}}}async function V(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}m.innerHTML=`
|
|
876
970
|
<div class="view-header">
|
|
877
971
|
<h2>随机点名 - ${d.name}</h2>
|
|
878
972
|
</div>
|
|
@@ -884,17 +978,17 @@
|
|
|
884
978
|
</div>
|
|
885
979
|
<div id="callResult" class="call-result"></div>
|
|
886
980
|
</div>
|
|
887
|
-
`,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const u=parseInt(document.getElementById("callCount").value);try{const
|
|
981
|
+
`,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const u=parseInt(document.getElementById("callCount").value);try{const g=await n.randomCall(d._id,u),y=document.getElementById("callResult");if(y.innerHTML=`
|
|
888
982
|
<h3>点名结果:</h3>
|
|
889
983
|
<div class="called-members">
|
|
890
|
-
${
|
|
984
|
+
${g.calledMembers.map(h=>`
|
|
891
985
|
<div class="member-card">
|
|
892
|
-
<div class="avatar">${
|
|
893
|
-
<div class="member-name">${
|
|
986
|
+
<div class="avatar">${h.username[0].toUpperCase()}</div>
|
|
987
|
+
<div class="member-name">${h.username}</div>
|
|
894
988
|
</div>
|
|
895
989
|
`).join("")}
|
|
896
990
|
</div>
|
|
897
|
-
`,Array.isArray(
|
|
991
|
+
`,Array.isArray(g.calledMembers)&&g.calledMembers.length>0&&e&&d&&a){const h=g.calledMembers.map(p=>p.username).join("、"),M=`🎲 本次随机点名(${g.calledMembers.length} 人):${h}`;try{e.sendChatMessage(d._id,a.username,M)}catch(p){console.error("发送点名结果到群聊失败:",p)}}}catch(g){alert("点名失败: "+g.message)}})}async function ne(m){m.innerHTML=`
|
|
898
992
|
<div class="view-header">
|
|
899
993
|
<h2>操作记录</h2>
|
|
900
994
|
<div style="display: flex; gap: 10px;">
|
|
@@ -958,7 +1052,7 @@
|
|
|
958
1052
|
</div>
|
|
959
1053
|
</div>
|
|
960
1054
|
</div>
|
|
961
|
-
`;let u=1,
|
|
1055
|
+
`;let u=1,g={};try{const t=await n.getGroups(),r=document.getElementById("auditGroupFilter");t.groups.forEach(l=>{const b=document.createElement("option");b.value=l._id,b.textContent=l.name,r.appendChild(b)})}catch(t){console.error("加载群组列表失败:",t)}async function y(t=1,r={}){try{const l=document.getElementById("auditLogs");l.innerHTML='<div class="loading">加载中...</div>';const b={page:t,limit:20},E=await n.getAuditLogs(r,b);if(E.logs.length===0){l.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}l.innerHTML=`
|
|
962
1056
|
<div class="audit-table">
|
|
963
1057
|
<div class="audit-header">
|
|
964
1058
|
<div>时间</div>
|
|
@@ -967,26 +1061,26 @@
|
|
|
967
1061
|
<div>资源</div>
|
|
968
1062
|
<div>详情</div>
|
|
969
1063
|
</div>
|
|
970
|
-
${
|
|
971
|
-
<div class="audit-row" onclick="showAuditDetail('${
|
|
972
|
-
<div class="audit-time">${new Date(
|
|
1064
|
+
${E.logs.map(G=>{var f,I,x,R,O;return`
|
|
1065
|
+
<div class="audit-row" onclick="showAuditDetail('${G._id}')">
|
|
1066
|
+
<div class="audit-time">${new Date(G.createdAt).toLocaleString()}</div>
|
|
973
1067
|
<div class="audit-user">
|
|
974
|
-
<div class="avatar">${((
|
|
975
|
-
<span>${((R=
|
|
1068
|
+
<div class="avatar">${((x=(I=(f=G.user)==null?void 0:f.username)==null?void 0:I[0])==null?void 0:x.toUpperCase())||"?"}</div>
|
|
1069
|
+
<span>${((R=G.user)==null?void 0:R.username)||"未知用户"}</span>
|
|
976
1070
|
</div>
|
|
977
1071
|
<div class="audit-action">
|
|
978
|
-
<span class="action-badge action-${
|
|
1072
|
+
<span class="action-badge action-${G.action}">${A(G.action)}</span>
|
|
979
1073
|
</div>
|
|
980
|
-
<div class="audit-resource">${
|
|
981
|
-
<div class="audit-description">${((G
|
|
1074
|
+
<div class="audit-resource">${G.resourceTitle||G.resourceId}</div>
|
|
1075
|
+
<div class="audit-description">${((O=G.details)==null?void 0:O.description)||"-"}</div>
|
|
982
1076
|
</div>
|
|
983
1077
|
`}).join("")}
|
|
984
1078
|
</div>
|
|
985
|
-
`;const
|
|
1079
|
+
`;const w=document.getElementById("auditPagination"),P=document.getElementById("pageInfo");P.textContent=`第 ${E.pagination.page} 页,共 ${E.pagination.pages} 页`,document.getElementById("prevPage").disabled=E.pagination.page<=1,document.getElementById("nextPage").disabled=E.pagination.page>=E.pagination.pages,w.style.display=E.pagination.pages>1?"flex":"none"}catch(l){console.error("加载审计日志失败:",l),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+l.message+"</div>"}}async function h(){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]}),b=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=b.summary.totalLogs,document.getElementById("activeUsers").textContent=b.summary.topUsers.length}catch(t){console.error("加载统计信息失败:",t)}}function M(t){var w,P,G,f,I,x;const r=((w=t.user)==null?void 0:w.username)||"未知用户",l=A(t.action),b=t.resourceTitle||t.resourceId;let E='<strong style="color: #6366f1;">'+r+"</strong> ";switch(t.action){case"document_create":E+='创建了文档 <strong>"'+b+'"</strong>';break;case"document_update":E+='更新了文档 <strong>"'+b+'"</strong>',(P=t.details)!=null&&P.field&&(E+=" 的 <strong>"+p(t.details.field)+"</strong>");break;case"document_delete":E+='删除了文档 <strong>"'+b+'"</strong>';break;case"content_edit":if(E+='编辑了文档 <strong>"'+b+'"</strong> 的内容',t.changes){const R=((G=t.changes.insertions)==null?void 0:G.reduce((K,Z)=>K+Z.length,0))||0,O=((f=t.changes.deletions)==null?void 0:f.reduce((K,Z)=>K+Z.length,0))||0;(R>0||O>0)&&(E+=' (<span style="color: #10b981;">+'+R+'</span> / <span style="color: #ef4444;">-'+O+"</span> 字符)")}break;case"title_edit":E+='修改了文档 <strong>"'+b+'"</strong> 的标题',(I=t.details)!=null&&I.oldValue&&((x=t.details)!=null&&x.newValue)&&(E+=' 从 <strong>"'+t.details.oldValue+'"</strong> 改为 <strong>"'+t.details.newValue+'"</strong>');break;case"document_permission_change":E+='修改了文档 <strong>"'+b+'"</strong> 的权限设置';break;default:E+="执行了 <strong>"+l+"</strong> 操作"}return E}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,"<").replace(/>/g,">")}window.showAuditDetail=async t=>{var r,l,b,E,w,P;try{const G=localStorage.getItem("token"),x=(await(await fetch(`http://localhost:3000/api/audit/${t}`,{headers:{Authorization:`Bearer ${G}`}})).json()).log,R=document.getElementById("auditDetailModal"),O=document.getElementById("auditDetailContent"),K=te=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[te]||"#6366f1";O.innerHTML=`
|
|
986
1080
|
<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%);">
|
|
987
1081
|
|
|
988
1082
|
<!-- 操作信息 -->
|
|
989
|
-
<div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${
|
|
1083
|
+
<div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${K(x.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
|
990
1084
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
|
|
991
1085
|
<span style="font-size: 24px;">📋</span>
|
|
992
1086
|
<h4 style="margin: 0; color: var(--text-primary); font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">操作信息</h4>
|
|
@@ -994,14 +1088,14 @@
|
|
|
994
1088
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
995
1089
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作类型:</span>
|
|
996
1090
|
<span style="display: inline-flex; align-items: center; gap: 8px;">
|
|
997
|
-
<span style="display: inline-block; padding: 4px 10px; background: ${
|
|
1091
|
+
<span style="display: inline-block; padding: 4px 10px; background: ${K(x.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${A(x.action)}</span>
|
|
998
1092
|
</span>
|
|
999
1093
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作时间:</span>
|
|
1000
|
-
<span style="font-weight: 600; color: var(--text-primary);">${new Date(
|
|
1094
|
+
<span style="font-weight: 600; color: var(--text-primary);">${new Date(x.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
|
|
1001
1095
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作用户:</span>
|
|
1002
1096
|
<span style="display: flex; align-items: center; gap: 10px;">
|
|
1003
|
-
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((
|
|
1004
|
-
<span style="font-weight: 600; color: var(--text-primary);">${((
|
|
1097
|
+
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((b=(l=(r=x.user)==null?void 0:r.username)==null?void 0:l[0])==null?void 0:b.toUpperCase())||"?"}</div>
|
|
1098
|
+
<span style="font-weight: 600; color: var(--text-primary);">${((E=x.user)==null?void 0:E.username)||"未知用户"}</span>
|
|
1005
1099
|
</span>
|
|
1006
1100
|
</div>
|
|
1007
1101
|
</div>
|
|
@@ -1014,15 +1108,15 @@
|
|
|
1014
1108
|
</div>
|
|
1015
1109
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
1016
1110
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源类型:</span>
|
|
1017
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
1111
|
+
<span style="font-weight: 600; color: var(--text-primary);">${x.resourceType||"未知"}</span>
|
|
1018
1112
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源ID:</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);">${
|
|
1113
|
+
<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);">${x.resourceId}</span>
|
|
1020
1114
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源标题:</span>
|
|
1021
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
1115
|
+
<span style="font-weight: 600; color: var(--text-primary);">${x.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
|
|
1022
1116
|
</div>
|
|
1023
1117
|
</div>
|
|
1024
1118
|
|
|
1025
|
-
${
|
|
1119
|
+
${x.details?`
|
|
1026
1120
|
<!-- 详细信息 -->
|
|
1027
1121
|
<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);">
|
|
1028
1122
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
|
|
@@ -1062,27 +1156,27 @@
|
|
|
1062
1156
|
</div>
|
|
1063
1157
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
1064
1158
|
<span style="color: var(--text-tertiary); font-weight: 500;">IP地址:</span>
|
|
1065
|
-
<span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${
|
|
1159
|
+
<span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${x.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
|
|
1066
1160
|
<span style="color: var(--text-tertiary); font-weight: 500;">用户代理:</span>
|
|
1067
|
-
<span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${
|
|
1161
|
+
<span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${x.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
|
|
1068
1162
|
</div>
|
|
1069
1163
|
</div>
|
|
1070
1164
|
</div>
|
|
1071
|
-
`,document.getElementById("auditDescriptionText").innerHTML=M(
|
|
1165
|
+
`,document.getElementById("auditDescriptionText").innerHTML=M(x);const Z=document.getElementById("auditDetailsContent");let J="";if(x.details&&(x.details.field&&(J+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',J+='<span style="font-weight: 600; color: var(--text-primary);">'+p(x.details.field)+"</span>"),x.details.oldValue!==void 0&&x.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(x.details.oldValue)+"</div>"),x.details.newValue!==void 0&&x.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(x.details.newValue)+"</div>")),J?(Z.innerHTML=J,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",x.changes&&(((w=x.changes.insertions)==null?void 0:w.length)>0||((P=x.changes.deletions)==null?void 0:P.length)>0)){const te=document.getElementById("auditChangesContent");let Q="";if(x.changes.insertions&&x.changes.insertions.length>0){const ue=x.changes.insertions.reduce((Y,ee)=>Y+ee.length,0);Q+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',Q+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',Q+='<div style="color: var(--text-secondary);">共 '+ue+" 个字符</div>",Q+="</div>"}if(x.changes.deletions&&x.changes.deletions.length>0){const ue=x.changes.deletions.reduce((Y,ee)=>Y+ee.length,0);Q+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',Q+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',Q+='<div style="color: var(--text-secondary);">共 '+ue+" 个字符</div>",Q+="</div>"}te.innerHTML=Q,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";R.classList.remove("hidden")}catch(G){alert("加载详情失败: "+G.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{g={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(g).forEach(t=>{g[t]||delete g[t]}),u=1,y(u,g)}),document.getElementById("prevPage").addEventListener("click",()=>{u>1&&(u--,y(u,g))}),document.getElementById("nextPage").addEventListener("click",()=>{u++,y(u,g)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const r=Object.keys(g||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(r))try{const l=await n.clearAuditLogs(g||{});alert(`已清除 ${l.deletedCount||0} 条操作记录`),u=1,await h(),await y(u,g)}catch(l){alert("清除失败: "+l.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),h(),y()}async function oe(m){if(!d){m.innerHTML=`
|
|
1072
1166
|
<div class="empty-state" style="text-align: center; padding: 60px 20px;">
|
|
1073
1167
|
<div style="font-size: 64px; margin-bottom: 20px;">🗳️</div>
|
|
1074
1168
|
<h3 style="font-size: 24px; margin-bottom: 12px;">投票管理</h3>
|
|
1075
1169
|
<p style="color: var(--text-secondary); margin-bottom: 24px;">请先选择一个群组</p>
|
|
1076
1170
|
<button class="btn-primary" onclick="document.querySelector('[data-view=\\"groups\\"]').click()">前往群组管理</button>
|
|
1077
1171
|
</div>
|
|
1078
|
-
`;return}try{const u=localStorage.getItem("token"),
|
|
1172
|
+
`;return}try{const u=localStorage.getItem("token"),g=await fetch(`http://localhost:3000/api/polls/group/${d._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!g.ok)throw new Error("获取投票列表失败");const h=(await g.json()).polls||[];m.innerHTML=`
|
|
1079
1173
|
<div class="view-header">
|
|
1080
1174
|
<h2>🗳️ 投票管理 - ${d.name}</h2>
|
|
1081
1175
|
</div>
|
|
1082
1176
|
<div class="polls-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; padding: 20px;">
|
|
1083
|
-
${
|
|
1177
|
+
${h.length===0?'<div class="empty-state" style="grid-column: 1/-1;">暂无投票</div>':""}
|
|
1084
1178
|
</div>
|
|
1085
|
-
`;const M=
|
|
1179
|
+
`;const M=m.querySelector(".polls-grid");h.forEach(p=>{const i=p.options.reduce((l,b)=>l+b.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
1180
|
<div style="display: flex; align-items: start; justify-content: space-between; margin-bottom: 15px;">
|
|
1087
1181
|
<div style="flex: 1;">
|
|
1088
1182
|
<h3 style="margin: 0 0 8px 0; font-size: 18px;">${p.title}</h3>
|
|
@@ -1104,14 +1198,14 @@
|
|
|
1104
1198
|
</div>
|
|
1105
1199
|
|
|
1106
1200
|
<div style="margin-bottom: 15px;">
|
|
1107
|
-
${p.options.map((l,
|
|
1201
|
+
${p.options.map((l,b)=>{const E=i>0?(l.votes.length/i*100).toFixed(1):0;return`
|
|
1108
1202
|
<div style="margin-bottom: 10px;">
|
|
1109
1203
|
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
|
1110
1204
|
<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} 票 (${
|
|
1205
|
+
<span style="font-size: 14px; font-weight: 600; color: var(--primary);">${l.votes.length} 票 (${E}%)</span>
|
|
1112
1206
|
</div>
|
|
1113
1207
|
<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: ${
|
|
1208
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${E}%; transition: width 0.3s;"></div>
|
|
1115
1209
|
</div>
|
|
1116
1210
|
</div>
|
|
1117
1211
|
`}).join("")}
|
|
@@ -1127,12 +1221,12 @@
|
|
|
1127
1221
|
${t?"":`<button class="btn-secondary btn-sm end-poll" data-poll-id="${p._id}">结束投票</button>`}
|
|
1128
1222
|
<button class="btn-danger btn-sm delete-poll" data-poll-id="${p._id}">删除</button>
|
|
1129
1223
|
</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
|
|
1224
|
+
`,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 de(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 oe(m)}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 oe(m)}catch(i){alert("操作失败: "+i.message)}})})}catch(u){console.error("加载投票列表失败:",u),m.innerHTML=`
|
|
1131
1225
|
<div class="view-header">
|
|
1132
1226
|
<h2>🗳️ 投票管理</h2>
|
|
1133
1227
|
</div>
|
|
1134
1228
|
<div class="empty-state">加载失败: ${u.message}</div>
|
|
1135
|
-
`}}async function
|
|
1229
|
+
`}}async function de(m){try{const u=localStorage.getItem("token"),g=await fetch(`http://localhost:3000/api/polls/${m}`,{headers:{Authorization:`Bearer ${u}`}});if(!g.ok)throw new Error("获取投票详情失败");const h=(await g.json()).poll,M=h.options.reduce((r,l)=>r+l.votes.length,0),p=h.status==="ended"||h.endTime&&new Date(h.endTime)<new Date,i=`
|
|
1136
1230
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1137
1231
|
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
1138
1232
|
<div class="modal-header">
|
|
@@ -1140,15 +1234,15 @@
|
|
|
1140
1234
|
<button class="modal-close" id="closePollDetailModal">×</button>
|
|
1141
1235
|
</div>
|
|
1142
1236
|
<div class="modal-body" style="padding: 24px;">
|
|
1143
|
-
<h2 style="margin: 0 0 12px 0;">${
|
|
1144
|
-
${
|
|
1237
|
+
<h2 style="margin: 0 0 12px 0;">${h.title}</h2>
|
|
1238
|
+
${h.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${h.description}</p>`:""}
|
|
1145
1239
|
|
|
1146
1240
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
|
|
1147
1241
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
1148
|
-
${
|
|
1242
|
+
${h.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
1149
1243
|
</span>
|
|
1150
1244
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
1151
|
-
${
|
|
1245
|
+
${h.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
1152
1246
|
</span>
|
|
1153
1247
|
<span style="font-size: 13px; padding: 6px 12px; background: ${p?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
1154
1248
|
${p?"已结束":"进行中"}
|
|
@@ -1159,7 +1253,7 @@
|
|
|
1159
1253
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
1160
1254
|
<div>
|
|
1161
1255
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
1162
|
-
<div style="font-weight: 600;">👤 ${
|
|
1256
|
+
<div style="font-weight: 600;">👤 ${h.creatorName}</div>
|
|
1163
1257
|
</div>
|
|
1164
1258
|
<div>
|
|
1165
1259
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
@@ -1167,30 +1261,30 @@
|
|
|
1167
1261
|
</div>
|
|
1168
1262
|
<div>
|
|
1169
1263
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
1170
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
1264
|
+
<div style="font-weight: 600;">⏰ ${new Date(h.createdAt).toLocaleString("zh-CN")}</div>
|
|
1171
1265
|
</div>
|
|
1172
|
-
${
|
|
1266
|
+
${h.endTime?`
|
|
1173
1267
|
<div>
|
|
1174
1268
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
1175
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
1269
|
+
<div style="font-weight: 600;">⏰ ${new Date(h.endTime).toLocaleString("zh-CN")}</div>
|
|
1176
1270
|
</div>
|
|
1177
1271
|
`:""}
|
|
1178
1272
|
</div>
|
|
1179
1273
|
</div>
|
|
1180
1274
|
|
|
1181
1275
|
<h3 style="margin-bottom: 16px;">投票选项</h3>
|
|
1182
|
-
${
|
|
1276
|
+
${h.options.map((r,l)=>{const b=M>0?(r.votes.length/M*100).toFixed(1):0;return`
|
|
1183
1277
|
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
1184
1278
|
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
|
1185
1279
|
<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} 票 (${
|
|
1280
|
+
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${r.votes.length} 票 (${b}%)</span>
|
|
1187
1281
|
</div>
|
|
1188
1282
|
<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: ${
|
|
1283
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${b}%; transition: width 0.3s;"></div>
|
|
1190
1284
|
</div>
|
|
1191
|
-
${!
|
|
1285
|
+
${!h.anonymous&&r.votes.length>0?`
|
|
1192
1286
|
<div style="font-size: 13px; color: var(--text-secondary);">
|
|
1193
|
-
<strong>投票者:</strong> ${r.votes.map(
|
|
1287
|
+
<strong>投票者:</strong> ${r.votes.map(E=>E.username).join(", ")}
|
|
1194
1288
|
</div>
|
|
1195
1289
|
`:""}
|
|
1196
1290
|
</div>
|
|
@@ -1200,7 +1294,7 @@
|
|
|
1200
1294
|
</div>
|
|
1201
1295
|
</div>
|
|
1202
1296
|
</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
|
|
1297
|
+
`,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 B(m){m.innerHTML=`
|
|
1204
1298
|
<div class="view-header">
|
|
1205
1299
|
<h2>🔍 搜索</h2>
|
|
1206
1300
|
</div>
|
|
@@ -1222,18 +1316,18 @@
|
|
|
1222
1316
|
</div>
|
|
1223
1317
|
<div class="search-results" id="searchResults"></div>
|
|
1224
1318
|
</div>
|
|
1225
|
-
`;const u=document.getElementById("searchInput"),
|
|
1319
|
+
`;const u=document.getElementById("searchInput"),g=document.getElementById("searchBtn"),y=document.getElementById("searchResults"),h=async()=>{const M=u.value.trim();if(!M){y.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const p={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};y.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?y.innerHTML='<div class="empty-state">未找到相关结果</div>':y.innerHTML=i.map(t=>`
|
|
1226
1320
|
<div class="search-result-item">
|
|
1227
1321
|
<div class="result-header">
|
|
1228
1322
|
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[t.type]} ${t.type==="message"?"消息":t.type==="document"?"文档":"任务"}</span>
|
|
1229
1323
|
<span class="result-time">${new Date(t.time).toLocaleString()}</span>
|
|
1230
1324
|
</div>
|
|
1231
|
-
<h4>${
|
|
1232
|
-
<p>${
|
|
1325
|
+
<h4>${$(t.title,M)}</h4>
|
|
1326
|
+
<p>${$(t.content,M)}</p>
|
|
1233
1327
|
${t.group?`<span class="result-group">群组: ${t.group}</span>`:""}
|
|
1234
1328
|
${t.status?`<span class="result-status">状态: ${v(t.status)}</span>`:""}
|
|
1235
1329
|
</div>
|
|
1236
|
-
`).join("")}catch(i){
|
|
1330
|
+
`).join("")}catch(i){y.innerHTML=`<div class="empty-state">搜索失败: ${i.message}</div>`}};g.addEventListener("click",h),u.addEventListener("keypress",M=>{M.key==="Enter"&&h()})}function $(m,u){if(!u)return m;const g=new RegExp(`(${u})`,"gi");return m.replace(g,"<mark>$1</mark>")}function A(m){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[m]||m}function v(m){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[m]||m}async function k(m){var u;if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const g=localStorage.getItem("token"),y=await fetch(`http://localhost:3000/api/knowledge/group/${d._id}`,{headers:{Authorization:`Bearer ${g}`}});if(!y.ok)throw new Error(`HTTP ${y.status}: ${y.statusText}`);const h=await y.json();console.log("知识库数据:",h);const M=((u=h.data)==null?void 0:u.knowledgeList)||[];console.log("知识库条目数量:",M.length),m.innerHTML=`
|
|
1237
1331
|
<div class="view-header">
|
|
1238
1332
|
<h2>📚 知识库管理 - ${d.name}</h2>
|
|
1239
1333
|
<button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
|
|
@@ -1289,7 +1383,7 @@
|
|
|
1289
1383
|
<button class="btn-secondary btn-sm" data-id="${i._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
1290
1384
|
<button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1291
1385
|
</div>
|
|
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 ${
|
|
1386
|
+
`,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 ${g}`}});if(!t.ok)throw new Error("下载失败");const r=await t.blob(),l=window.URL.createObjectURL(r),b=document.createElement("a");b.href=l,b.download=i.dataset.filename,document.body.appendChild(b),b.click(),window.URL.revokeObjectURL(l),document.body.removeChild(b)}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 ${g}`}}),alert("删除成功!"),await k(m)}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,b=l?`http://localhost:3000/api/knowledge/${l}`:"http://localhost:3000/api/knowledge",w=await fetch(b,{method:l?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`},body:JSON.stringify(r)});if(!w.ok){const G=await w.json();throw new Error(G.message||"操作失败")}const P=await w.json();console.log("知识库操作结果:",P),alert(l?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await k(m)}catch(l){console.error("知识库操作错误:",l),alert("操作失败: "+l.message)}})}catch(g){m.innerHTML=`<div class="empty-state">加载失败: ${g.message}</div>`}}function C(m){if(!m)return"未设置";if(typeof m=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[m]||m;const u=[];if(m.event){const g={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};u.push(g[m.event]||m.event)}if(m.conditions&&Object.keys(m.conditions).length>0){const g=[];for(const[y,h]of Object.entries(m.conditions)){const p={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[y]||y;g.push(`${p}=${h}`)}g.length>0&&u.push(`(条件: ${g.join(", ")})`)}return m.schedule&&u.push(`⏱️ 定时: ${m.schedule}`),u.length>0?u.join(" "):"自定义触发条件"}async function D(m){var u;if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const g=localStorage.getItem("token"),M=((u=(await(await fetch(`http://localhost:3000/api/workflows/group/${d._id}`,{headers:{Authorization:`Bearer ${g}`}})).json()).data)==null?void 0:u.workflows)||[];m.innerHTML=`
|
|
1293
1387
|
<div class="view-header">
|
|
1294
1388
|
<h2>⚙️ 工作流管理 - ${d.name}</h2>
|
|
1295
1389
|
<button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</button>
|
|
@@ -1361,7 +1455,7 @@
|
|
|
1361
1455
|
</div>
|
|
1362
1456
|
<p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${i.description||"无描述"}</p>
|
|
1363
1457
|
<div class="workflow-meta" style="font-size: 12px; color: var(--text-tertiary); margin: 10px 0;">
|
|
1364
|
-
<span>🔔 触发条件: ${
|
|
1458
|
+
<span>🔔 触发条件: ${C(i.trigger)}</span>
|
|
1365
1459
|
</div>
|
|
1366
1460
|
<div style="display: flex; gap: 10px; margin-top: 15px;">
|
|
1367
1461
|
<button class="btn-secondary btn-sm" data-id="${i._id}" data-action="toggle" style="flex: 1;">
|
|
@@ -1369,7 +1463,7 @@
|
|
|
1369
1463
|
</button>
|
|
1370
1464
|
<button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1371
1465
|
</div>
|
|
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 ${
|
|
1466
|
+
`,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 ${g}`}}),await D(m)}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 ${g}`}}),alert("删除成功!"),await D(m)}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 ${g}`},body:JSON.stringify(r)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await D(m)}catch(l){alert("创建失败: "+l.message)}})}catch(g){m.innerHTML=`<div class="empty-state">加载失败: ${g.message}</div>`}}async function T(m){var u;try{const g=localStorage.getItem("token"),M=((u=(await(await fetch("http://localhost:3000/api/backup/list",{headers:{Authorization:`Bearer ${g}`}})).json()).data)==null?void 0:u.backups)||[];m.innerHTML=`
|
|
1373
1467
|
<div class="view-header">
|
|
1374
1468
|
<h2>💾 备份管理</h2>
|
|
1375
1469
|
<button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
|
|
@@ -1390,7 +1484,7 @@
|
|
|
1390
1484
|
<button class="btn-primary btn-sm" data-backup-name="${i.name}" data-filename="${i.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
|
|
1391
1485
|
<button class="btn-danger btn-sm" data-backup-name="${i.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1392
1486
|
</div>
|
|
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
|
|
1487
|
+
`,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 b=await fetch(`http://localhost:3000/api/backup/download/${t}`,{method:"GET",headers:{Authorization:`Bearer ${l}`}});if(!b.ok)throw new Error(`下载失败: ${b.status} ${b.statusText}`);const E=await b.blob();console.log("文件下载成功,大小:",E.size,"bytes");const w=window.URL.createObjectURL(E),P=document.createElement("a");P.href=w,P.download=r,P.style.display="none",document.body.appendChild(P),P.click(),setTimeout(()=>{document.body.removeChild(P),window.URL.revokeObjectURL(w)},100);const G=i.textContent;i.textContent="✅ 下载成功",i.disabled=!0,setTimeout(()=>{i.textContent=G,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 ${g}`}});if(!r.ok)throw new Error(`删除失败: ${r.status}`);alert("删除成功!"),await T(m)}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 ${g}`}}),alert("备份创建成功!"),await T(m)}catch(t){alert("创建失败: "+t.message)}finally{i.disabled=!1,i.textContent="➕ 创建备份"}}})}catch(g){m.innerHTML=`<div class="empty-state">加载失败: ${g.message}</div>`}}async function z(m){m.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function W(m){m.innerHTML=`
|
|
1394
1488
|
<div class="view-header">
|
|
1395
1489
|
<h2>📤 数据导出</h2>
|
|
1396
1490
|
</div>
|
|
@@ -1435,7 +1529,7 @@
|
|
|
1435
1529
|
<div id="historyList">加载中...</div>
|
|
1436
1530
|
</div>
|
|
1437
1531
|
</div>
|
|
1438
|
-
`;try{const u=localStorage.getItem("token"),
|
|
1532
|
+
`;try{const u=localStorage.getItem("token"),y=await(await fetch("http://localhost:3000/api/export/history",{headers:{Authorization:`Bearer ${u}`}})).json(),h=document.getElementById("historyList");y.exports&&y.exports.length>0?h.innerHTML=y.exports.map(M=>`
|
|
1439
1533
|
<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;">
|
|
1440
1534
|
<div>
|
|
1441
1535
|
<div style="font-weight: 600; margin-bottom: 5px;">📦 ${M.format.toUpperCase()} 导出</div>
|
|
@@ -1443,7 +1537,7 @@
|
|
|
1443
1537
|
</div>
|
|
1444
1538
|
<a href="http://localhost:3000/api/export/download/${M.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
|
|
1445
1539
|
</div>
|
|
1446
|
-
`).join(""):
|
|
1540
|
+
`).join(""):h.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},g=document.getElementById("exportBtn");g.disabled=!0,g.textContent="⏳ 导出中...";try{const y=localStorage.getItem("token"),h=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify(u)});if(h.ok){const M=await h.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(m)}else throw new Error("导出失败")}catch(y){alert("导出失败: "+y.message)}finally{g.disabled=!1,g.textContent="🚀 开始导出"}})}async function F(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}m.innerHTML=`
|
|
1447
1541
|
<div class="view-header">
|
|
1448
1542
|
<h2>🎨 协作白板 - ${d.name}</h2>
|
|
1449
1543
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1465,7 +1559,7 @@
|
|
|
1465
1559
|
<span class="user-badge">👤 ${a.username}</span>
|
|
1466
1560
|
</div>
|
|
1467
1561
|
</div>
|
|
1468
|
-
`;const u=document.getElementById("whiteboard"),
|
|
1562
|
+
`;const u=document.getElementById("whiteboard"),g=u.getContext("2d");let y=!1,h="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"),h=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=>{y=!0;const l=u.getBoundingClientRect();i=r.clientX-l.left,t=r.clientY-l.top}),u.addEventListener("mousemove",r=>{if(!y)return;const l=u.getBoundingClientRect(),b=r.clientX-l.left,E=r.clientY-l.top;g.beginPath(),g.moveTo(i,t),g.lineTo(b,E),g.strokeStyle=h==="eraser"?"#ffffff":M,g.lineWidth=p,g.lineCap="round",g.stroke(),i=b,t=E,e.sendWhiteboardData(d._id,{tool:h,color:M,size:p,from:{x:i,y:t},to:{x:b,y:E}})}),u.addEventListener("mouseup",()=>{y=!1}),u.addEventListener("mouseleave",()=>{y=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&g.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&&(g.beginPath(),g.moveTo(r.from.x,r.from.y),g.lineTo(r.to.x,r.to.y),g.strokeStyle=r.tool==="eraser"?"#ffffff":r.color,g.lineWidth=r.size,g.lineCap="round",g.stroke())})}async function re(m){m.innerHTML=`
|
|
1469
1563
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1470
1564
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1471
1565
|
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚙️ 设置中心</span>
|
|
@@ -1561,7 +1655,7 @@
|
|
|
1561
1655
|
<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>
|
|
1562
1656
|
</div>
|
|
1563
1657
|
</div>
|
|
1564
|
-
`,document.querySelectorAll(".settings-card").forEach(
|
|
1658
|
+
`,document.querySelectorAll(".settings-card").forEach(g=>{g.addEventListener("mouseenter",()=>{g.style.transform="translateY(-4px)",g.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),g.addEventListener("mouseleave",()=>{g.style.transform="translateY(0)",g.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(g=>{g.addEventListener("focus",()=>{g.style.borderColor="var(--primary)"}),g.addEventListener("blur",()=>{g.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 g={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(g)),X(g.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",g=>{X(g.target.value)});const u=localStorage.getItem("userSettings");if(u){const g=JSON.parse(u);g.email&&(document.getElementById("userEmail").value=g.email),document.getElementById("emailNotifications").checked=g.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=g.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=g.soundNotifications!==!1,g.theme&&(document.getElementById("themeSelect").value=g.theme,X(g.theme)),g.language&&(document.getElementById("languageSelect").value=g.language)}}function X(m){const u=document.documentElement,g={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"}},y=g[m]||g.dark;u.style.setProperty("--primary",y.primary),u.style.setProperty("--primary-dark",y.primaryDark),u.style.setProperty("--secondary",y.secondary),u.style.setProperty("--bg-dark",y.bgDark),u.style.setProperty("--bg-card",y.bgCard),u.style.setProperty("--bg-hover",y.bgHover),u.style.setProperty("--text-primary",y.textPrimary),u.style.setProperty("--text-secondary",y.textSecondary),u.style.setProperty("--border",y.border),localStorage.setItem("currentTheme",m)}async function le(m){m.innerHTML=`
|
|
1565
1659
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1566
1660
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1567
1661
|
<span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">❓ 帮助中心</span>
|
|
@@ -1741,7 +1835,7 @@
|
|
|
1741
1835
|
</div>
|
|
1742
1836
|
</div>
|
|
1743
1837
|
</div>
|
|
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
|
|
1838
|
+
`,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 g=u.style.background.includes("gradient");u.style.boxShadow=g?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function pe(m){const u=document.getElementById("contentArea");switch(m){case"groups":await L(u);break;case"tasks":await _(u);break;case"documents":await U(u);break;case"chat":await q(u);break;case"files":await ae(u);break;case"search":await B(u);break;case"call":await V(u);break;case"audit":await ne(u);break;case"polls":await oe(u);break;case"knowledge":await k(u);break;case"workflow":await D(u);break;case"backup":await T(u);break;case"export":await W(u);break;case"ai":await z(u);break;case"export":await W(u);break;case"whiteboard":await F(u);break;case"settings":await re(u);break;case"help":await le(u);break}}pe("groups")}function yn(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 bn(a,e){const o=document.getElementById("app"),n=new gt,c=new ze,s=a.id||a._id;let d=null,S=[];function N(B){if(B.startsWith("[白板作品]")){const $=B.replace("[白板作品]","").trim(),A=$.includes("/api/files/")&&$.includes("/download");let v=$;if(A&&!$.includes("token=")){const k=localStorage.getItem("token");v=$.includes("?")?`${$}&token=${k}`:`${$}?token=${k}`}return`
|
|
1745
1839
|
<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);">
|
|
1746
1840
|
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
1747
1841
|
<span style="font-size: 18px;">🎨</span>
|
|
@@ -1757,13 +1851,13 @@
|
|
|
1757
1851
|
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
1758
1852
|
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
1759
1853
|
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
1760
|
-
<button onclick="navigator.clipboard.writeText('${
|
|
1854
|
+
<button onclick="navigator.clipboard.writeText('${$}'); 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>
|
|
1761
1855
|
</div>
|
|
1762
1856
|
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
1763
1857
|
</div>
|
|
1764
1858
|
</div>
|
|
1765
|
-
`}if(
|
|
1766
|
-
<div class="poll-card" data-poll-id="${
|
|
1859
|
+
`}if(B.startsWith("[投票]")){const $=B.replace("[投票]","").trim();return`
|
|
1860
|
+
<div class="poll-card" data-poll-id="${$}" 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('${$}')">
|
|
1767
1861
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
1768
1862
|
<span style="font-size: 32px;">📊</span>
|
|
1769
1863
|
<div style="flex: 1;">
|
|
@@ -1775,25 +1869,25 @@
|
|
|
1775
1869
|
📋 查看投票详情
|
|
1776
1870
|
</div>
|
|
1777
1871
|
</div>
|
|
1778
|
-
`}return
|
|
1779
|
-
<div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${
|
|
1872
|
+
`}return B}window.viewPollDetail=async B=>{try{const A=(await n.getPoll(B)).poll,v=A.options.reduce((F,re)=>F+re.votes.length,0),k=A.options.some(F=>F.votes.includes(s)),C=A.status==="ended"||A.endTime&&new Date(A.endTime)<new Date;let D="";A.options.forEach((F,re)=>{const X=v>0?(F.votes.length/v*100).toFixed(1):0,le=F.votes.includes(s);D+=`
|
|
1873
|
+
<div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${le?"var(--primary)":"var(--border)"};">
|
|
1780
1874
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1781
1875
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1782
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1783
|
-
${
|
|
1876
|
+
<span style="font-weight: 500; color: var(--text-primary);">${F.text}</span>
|
|
1877
|
+
${le?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
|
|
1784
1878
|
</div>
|
|
1785
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1879
|
+
<span style="font-weight: 600; color: var(--primary);">${F.votes.length} 票 (${X}%)</span>
|
|
1786
1880
|
</div>
|
|
1787
1881
|
<div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
|
|
1788
1882
|
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${X}%; transition: width 0.3s;"></div>
|
|
1789
1883
|
</div>
|
|
1790
|
-
${!
|
|
1884
|
+
${!A.anonymous&&F.votes.length>0?`
|
|
1791
1885
|
<div style="margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
|
|
1792
|
-
投票者: ${
|
|
1886
|
+
投票者: ${F.voterNames?F.voterNames.join(", "):""}
|
|
1793
1887
|
</div>
|
|
1794
1888
|
`:""}
|
|
1795
1889
|
</div>
|
|
1796
|
-
`});const
|
|
1890
|
+
`});const T=`
|
|
1797
1891
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1798
1892
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
|
1799
1893
|
<div class="modal-header">
|
|
@@ -1802,24 +1896,24 @@
|
|
|
1802
1896
|
</div>
|
|
1803
1897
|
<div class="modal-body" style="padding: 24px;">
|
|
1804
1898
|
<div style="margin-bottom: 24px;">
|
|
1805
|
-
<h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${
|
|
1806
|
-
${
|
|
1899
|
+
<h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${A.title}</h2>
|
|
1900
|
+
${A.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${A.description}</p>`:""}
|
|
1807
1901
|
|
|
1808
1902
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px;">
|
|
1809
1903
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
|
|
1810
|
-
${
|
|
1904
|
+
${A.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
1811
1905
|
</span>
|
|
1812
1906
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
|
|
1813
|
-
${
|
|
1907
|
+
${A.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
1814
1908
|
</span>
|
|
1815
|
-
${
|
|
1909
|
+
${C?'<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>'}
|
|
1816
1910
|
</div>
|
|
1817
1911
|
|
|
1818
1912
|
<div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; border-left: 4px solid var(--primary); margin-bottom: 24px;">
|
|
1819
1913
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
1820
1914
|
<div>
|
|
1821
1915
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
1822
|
-
<div style="font-weight: 600; color: var(--text-primary);">👤 ${
|
|
1916
|
+
<div style="font-weight: 600; color: var(--text-primary);">👤 ${A.creatorName}</div>
|
|
1823
1917
|
</div>
|
|
1824
1918
|
<div>
|
|
1825
1919
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
@@ -1827,12 +1921,12 @@
|
|
|
1827
1921
|
</div>
|
|
1828
1922
|
<div>
|
|
1829
1923
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
1830
|
-
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(
|
|
1924
|
+
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(A.createdAt).toLocaleString("zh-CN")}</div>
|
|
1831
1925
|
</div>
|
|
1832
|
-
${
|
|
1926
|
+
${A.endTime?`
|
|
1833
1927
|
<div>
|
|
1834
1928
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
1835
|
-
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(
|
|
1929
|
+
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(A.endTime).toLocaleString("zh-CN")}</div>
|
|
1836
1930
|
</div>
|
|
1837
1931
|
`:""}
|
|
1838
1932
|
</div>
|
|
@@ -1841,16 +1935,16 @@
|
|
|
1841
1935
|
|
|
1842
1936
|
<div style="margin-bottom: 24px;">
|
|
1843
1937
|
<h3 style="margin-bottom: 16px; color: var(--text-primary);">投票选项</h3>
|
|
1844
|
-
${!
|
|
1938
|
+
${!C&&!k?`
|
|
1845
1939
|
<form id="voteForm">
|
|
1846
|
-
${
|
|
1940
|
+
${A.options.map((F,re)=>{const X=v>0?(F.votes.length/v*100).toFixed(1):0;return`
|
|
1847
1941
|
<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)'">
|
|
1848
1942
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1849
1943
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1850
|
-
<input type="${
|
|
1851
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1944
|
+
<input type="${A.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${re}" style="width: 18px; height: 18px; cursor: pointer;">
|
|
1945
|
+
<span style="font-weight: 500; color: var(--text-primary);">${F.text}</span>
|
|
1852
1946
|
</div>
|
|
1853
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1947
|
+
<span style="font-weight: 600; color: var(--primary);">${F.votes.length} 票 (${X}%)</span>
|
|
1854
1948
|
</div>
|
|
1855
1949
|
<div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
|
|
1856
1950
|
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${X}%; transition: width 0.3s;"></div>
|
|
@@ -1861,8 +1955,8 @@
|
|
|
1861
1955
|
<div style="text-align: center; color: var(--warning); margin-top: 12px; font-size: 13px;">⚠️ 提交后不可修改</div>
|
|
1862
1956
|
</form>
|
|
1863
1957
|
`:D}
|
|
1864
|
-
${
|
|
1865
|
-
${
|
|
1958
|
+
${k?'<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>':""}
|
|
1959
|
+
${C?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
|
|
1866
1960
|
</div>
|
|
1867
1961
|
|
|
1868
1962
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1871,7 +1965,7 @@
|
|
|
1871
1965
|
</div>
|
|
1872
1966
|
</div>
|
|
1873
1967
|
</div>
|
|
1874
|
-
`,
|
|
1968
|
+
`,z=document.getElementById("pollDetailModal");z&&z.remove(),document.body.insertAdjacentHTML("beforeend",T),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const W=document.getElementById("voteForm");W&&!C&&!k&&W.addEventListener("submit",async F=>{F.preventDefault();const re=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(X=>parseInt(X.value));if(re.length===0){alert("请选择至少一个选项!");return}try{await n.vote(B,re),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const X=document.querySelector(".nav-item.active");if(X&&X.dataset.view==="tasks"){const le=document.getElementById("contentArea");await _(le)}}catch(X){console.error("投票失败:",X),alert("投票失败:"+X.message)}})}catch($){console.error("加载投票详情失败:",$),alert("加载投票详情失败:"+$.message)}},o.innerHTML=`
|
|
1875
1969
|
<div class="dashboard">
|
|
1876
1970
|
<aside class="sidebar">
|
|
1877
1971
|
<div class="sidebar-header">
|
|
@@ -1921,12 +2015,12 @@
|
|
|
1921
2015
|
<div id="contentArea"></div>
|
|
1922
2016
|
</main>
|
|
1923
2017
|
</div>
|
|
1924
|
-
`,document.querySelectorAll(".nav-item").forEach(
|
|
2018
|
+
`,document.querySelectorAll(".nav-item").forEach(B=>{B.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(A=>A.classList.remove("active")),B.classList.add("active");const $=B.dataset.view;L($)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()});async function L(B){const $=document.getElementById("contentArea");switch(B){case"groups":await j($);break;case"allgroups":await H($);break;case"tasks":await _($);break;case"documents":await U($);break;case"files":await ae($);break;case"chat":await q($);break;case"search":await V($);break;case"knowledge":await de($);break}}async function j(B){S=(await n.getGroups()).groups,B.innerHTML=`
|
|
1925
2019
|
<div class="view-header">
|
|
1926
2020
|
<h2>我的群组</h2>
|
|
1927
2021
|
</div>
|
|
1928
2022
|
<div class="groups-grid" id="groupsList"></div>
|
|
1929
|
-
`;const
|
|
2023
|
+
`;const A=document.getElementById("groupsList");if(S.length===0){A.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}S.forEach(v=>{const k=document.createElement("div");k.className="group-card",k.innerHTML=`
|
|
1930
2024
|
<h3>${v.name}</h3>
|
|
1931
2025
|
<p>${v.description||"暂无描述"}</p>
|
|
1932
2026
|
<div class="group-stats">
|
|
@@ -1938,63 +2032,63 @@
|
|
|
1938
2032
|
<button class="btn-select" data-id="${v._id}">进入群组</button>
|
|
1939
2033
|
<button class="btn-secondary" data-id="${v._id}" data-action="leave">退出群组</button>
|
|
1940
2034
|
</div>
|
|
1941
|
-
`,
|
|
2035
|
+
`,A.appendChild(k)}),document.querySelectorAll(".btn-select").forEach(v=>{v.addEventListener("click",()=>{d=S.find(k=>k._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(B)}catch(k){alert("退出失败: "+k.message)}})})}async function H(B){const $=await n.getAllGroups(),v=(await n.getGroups()).groups.map(C=>C._id);B.innerHTML=`
|
|
1942
2036
|
<div class="view-header">
|
|
1943
2037
|
<h2>所有群组</h2>
|
|
1944
2038
|
</div>
|
|
1945
2039
|
<div class="groups-grid" id="allGroupsList"></div>
|
|
1946
|
-
`;const
|
|
1947
|
-
<h3>${
|
|
1948
|
-
<p>${
|
|
2040
|
+
`;const k=document.getElementById("allGroupsList");$.groups.forEach(C=>{const D=v.includes(C._id),T=document.createElement("div");T.className="group-card",T.innerHTML=`
|
|
2041
|
+
<h3>${C.name}</h3>
|
|
2042
|
+
<p>${C.description||"暂无描述"}</p>
|
|
1949
2043
|
<div class="group-stats">
|
|
1950
|
-
<span>👥 ${
|
|
1951
|
-
<span>📄 ${
|
|
2044
|
+
<span>👥 ${C.members.length} 成员</span>
|
|
2045
|
+
<span>📄 ${C.documents.length} 文档</span>
|
|
1952
2046
|
</div>
|
|
1953
|
-
${D?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${
|
|
1954
|
-
`,
|
|
2047
|
+
${D?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${C._id}" data-action="join">加入群组</button>`}
|
|
2048
|
+
`,k.appendChild(T)}),document.querySelectorAll('[data-action="join"]').forEach(C=>{C.addEventListener("click",async()=>{try{await n.joinGroup(C.dataset.id),alert("加入成功!"),await H(B)}catch(D){alert("加入失败: "+D.message)}})})}async function _(B){try{const $=await n.getMyTasks();let A=[];try{const C=(await n.getGroups()).groups;for(const D of C)try{const T=await n.getGroupPolls(D._id);T.polls&&Array.isArray(T.polls)&&(A=A.concat(T.polls.map(z=>({...z,groupName:D.name}))))}catch(T){console.error(`获取群组 ${D.name} 的投票失败:`,T)}}catch(k){console.error("获取投票失败:",k)}B.innerHTML=`
|
|
1955
2049
|
<div class="view-header">
|
|
1956
2050
|
<h2>我的任务</h2>
|
|
1957
2051
|
</div>
|
|
1958
2052
|
<div class="tasks-list" id="tasksList"></div>
|
|
1959
|
-
`;const v=document.getElementById("tasksList");if(
|
|
1960
|
-
<h3>${
|
|
1961
|
-
<p>${
|
|
2053
|
+
`;const v=document.getElementById("tasksList");if($.tasks.length===0&&A.length===0){v.innerHTML='<div class="empty-state">暂无任务</div>';return}$.tasks.forEach(k=>{const C=document.createElement("div");C.className=`task-card status-${k.status}`,C.innerHTML=`
|
|
2054
|
+
<h3>${k.title}</h3>
|
|
2055
|
+
<p>${k.description}</p>
|
|
1962
2056
|
<div class="task-meta">
|
|
1963
|
-
<span class="status-badge">${
|
|
1964
|
-
<span>群组: ${
|
|
1965
|
-
${
|
|
2057
|
+
<span class="status-badge">${oe(k.status)}</span>
|
|
2058
|
+
<span>群组: ${k.group.name}</span>
|
|
2059
|
+
${k.deadline?`<span>截止: ${new Date(k.deadline).toLocaleDateString()}</span>`:""}
|
|
1966
2060
|
</div>
|
|
1967
|
-
${
|
|
2061
|
+
${k.relatedDocument?`<a href="#" class="doc-link" data-id="${k.relatedDocument._id}">📄 查看相关文档</a>`:""}
|
|
1968
2062
|
<div class="task-actions">
|
|
1969
|
-
${
|
|
1970
|
-
${
|
|
2063
|
+
${k.status==="pending"?`<button class="btn-primary btn-sm" data-id="${k._id}" data-action="start">开始任务</button>`:""}
|
|
2064
|
+
${k.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${k._id}" data-action="complete">完成任务</button>`:""}
|
|
1971
2065
|
</div>
|
|
1972
|
-
`,v.appendChild(
|
|
2066
|
+
`,v.appendChild(C)}),A.forEach(k=>{const C=k.options.reduce((W,F)=>W+F.votes.length,0),D=k.options.some(W=>W.votes.includes(s)),T=k.status==="ended"||k.endTime&&new Date(k.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=`
|
|
1973
2067
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
1974
2068
|
<span style="font-size: 32px;">📊</span>
|
|
1975
|
-
<h3 style="margin: 0;">${
|
|
2069
|
+
<h3 style="margin: 0;">${k.title}</h3>
|
|
1976
2070
|
</div>
|
|
1977
|
-
<p>${
|
|
2071
|
+
<p>${k.description||"暂无描述"}</p>
|
|
1978
2072
|
<div class="task-meta">
|
|
1979
|
-
<span class="status-badge" style="background: ${
|
|
1980
|
-
<span>群组: ${
|
|
1981
|
-
<span>👥 ${
|
|
2073
|
+
<span class="status-badge" style="background: ${T?"var(--danger)":"var(--success)"};">${T?"已结束":"进行中"}</span>
|
|
2074
|
+
<span>群组: ${k.groupName}</span>
|
|
2075
|
+
<span>👥 ${C} 人投票</span>
|
|
1982
2076
|
${D?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
|
|
1983
2077
|
</div>
|
|
1984
2078
|
<div class="task-actions">
|
|
1985
|
-
<button class="btn-primary btn-sm" data-poll-id="${
|
|
2079
|
+
<button class="btn-primary btn-sm" data-poll-id="${k._id}" data-action="view-poll">查看详情</button>
|
|
1986
2080
|
</div>
|
|
1987
|
-
`,v.appendChild(
|
|
2081
|
+
`,v.appendChild(z)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(k=>{k.addEventListener("click",async()=>{const C=k.dataset.id,T=k.dataset.action==="start"?"in_progress":"completed";try{await n.updateTaskStatus(C,T),await _(B)}catch(z){alert("操作失败: "+z.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(k=>{k.addEventListener("click",()=>{const C=k.dataset.pollId;window.viewPollDetail(C)})})}catch($){console.error("获取任务失败:",$),B.innerHTML=`
|
|
1988
2082
|
<div class="view-header">
|
|
1989
2083
|
<h2>我的任务</h2>
|
|
1990
2084
|
</div>
|
|
1991
|
-
<div class="empty-state">加载任务失败: ${
|
|
1992
|
-
`}}async function U(
|
|
2085
|
+
<div class="empty-state">加载任务失败: ${$.message}</div>
|
|
2086
|
+
`}}async function U(B){if(!d){B.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const $=await n.getDocuments(d._id);B.innerHTML=`
|
|
1993
2087
|
<div class="view-header">
|
|
1994
2088
|
<h2>共享文档 - ${d.name}</h2>
|
|
1995
2089
|
</div>
|
|
1996
2090
|
<div class="documents-list" id="docsList"></div>
|
|
1997
|
-
`;const
|
|
2091
|
+
`;const A=document.getElementById("docsList");if($.documents.length===0){A.innerHTML='<div class="empty-state">暂无文档</div>';return}$.documents.forEach(v=>{const k=document.createElement("div");k.className="document-card",k.innerHTML=`
|
|
1998
2092
|
<h3>📄 ${v.title}</h3>
|
|
1999
2093
|
<div class="doc-meta">
|
|
2000
2094
|
<span>创建者: ${v.creator.username}</span>
|
|
@@ -2004,7 +2098,7 @@
|
|
|
2004
2098
|
<button class="btn-edit" data-id="${v._id}">
|
|
2005
2099
|
${v.permission==="readonly"?"查看":"编辑"}
|
|
2006
2100
|
</button>
|
|
2007
|
-
`,
|
|
2101
|
+
`,A.appendChild(k)}),document.querySelectorAll(".btn-edit").forEach(v=>{v.addEventListener("click",()=>{se(B,v.dataset.id)})})}async function se(B,$){const v=(await n.getDocument($)).document;B.innerHTML=`
|
|
2008
2102
|
<div class="view-header">
|
|
2009
2103
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
2010
2104
|
<h2>${v.title}</h2>
|
|
@@ -2022,7 +2116,7 @@
|
|
|
2022
2116
|
<span>最后编辑: ${new Date(v.updatedAt).toLocaleString()}</span>
|
|
2023
2117
|
</div>
|
|
2024
2118
|
</div>
|
|
2025
|
-
`;const
|
|
2119
|
+
`;const k=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(k.root.innerHTML=v.content||"",v.permission==="editable"){let C,D;k.on("text-change",()=>{clearTimeout(C),clearTimeout(D),e.sendTyping($,a.username,!0),C=setTimeout(()=>{e.sendTyping($,a.username,!1)},1e3),D=setTimeout(async()=>{const T=k.root.innerHTML;try{await n.updateDocument($,T)}catch(z){console.error("自动保存失败:",z)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const T=k.root.innerHTML;await n.updateDocument($,T),alert("保存成功!")}catch(T){alert("保存失败: "+T.message)}})}e.on("document_update",C=>{if(C.documentId===$&&C.userId!==a.id){const D=k.getSelection();k.root.innerHTML=C.content,D&&k.setSelection(D)}}),e.on("typing",C=>{if(C.documentId===$&&C.userId!==a.id){const D=document.getElementById("onlineUsers");if(C.isTyping)D.innerHTML+=`<span class="user-badge typing" data-user="${C.userId}">✏️ ${C.username}</span>`;else{const T=D.querySelector(`[data-user="${C.userId}"]`);T&&T.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{U(B)})}async function ae(B){if(!d){B.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const $=await n.getGroupFiles(d._id);B.innerHTML=`
|
|
2026
2120
|
<div class="view-header">
|
|
2027
2121
|
<h2>文件共享 - ${d.name}</h2>
|
|
2028
2122
|
<button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
|
|
@@ -2053,8 +2147,8 @@
|
|
|
2053
2147
|
</form>
|
|
2054
2148
|
</div>
|
|
2055
2149
|
</div>
|
|
2056
|
-
`;const
|
|
2057
|
-
<div class="file-icon">${
|
|
2150
|
+
`;const A=document.getElementById("filesList");!$.files||$.files.length===0?A.innerHTML='<div class="empty-state">暂无文件</div>':($.files.forEach(v=>{const k=document.createElement("div");k.className="file-card";const C=ie(v.mimetype),D=ce(v.size);k.innerHTML=`
|
|
2151
|
+
<div class="file-icon">${C}</div>
|
|
2058
2152
|
<div class="file-info">
|
|
2059
2153
|
<h4>${v.originalName}</h4>
|
|
2060
2154
|
<div class="file-meta">
|
|
@@ -2068,12 +2162,12 @@
|
|
|
2068
2162
|
<a href="${n.getFileDownloadUrl(v._id)}" class="btn-primary" download>下载</a>
|
|
2069
2163
|
${String(v.uploader._id)===String(s)?`<button class="btn-danger" data-id="${v._id}" data-action="delete-file">删除</button>`:""}
|
|
2070
2164
|
</div>
|
|
2071
|
-
`,
|
|
2165
|
+
`,A.appendChild(k)}),document.querySelectorAll('[data-action="delete-file"]').forEach(v=>{v.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await n.deleteFile(v.dataset.id),alert("文件删除成功!"),await ae(B)}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 v=>{v.preventDefault();const k=document.getElementById("fileInput"),C=document.getElementById("fileDescription").value;if(!k.files[0]){alert("请选择文件");return}try{await n.uploadFile(d._id,k.files[0],C),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await ae(B)}catch(D){alert("上传失败: "+D.message)}})}catch($){console.error("获取文件列表失败:",$),B.innerHTML=`
|
|
2072
2166
|
<div class="view-header">
|
|
2073
2167
|
<h2>文件共享</h2>
|
|
2074
2168
|
</div>
|
|
2075
|
-
<div class="empty-state">加载文件失败: ${
|
|
2076
|
-
`}}function
|
|
2169
|
+
<div class="empty-state">加载文件失败: ${$.message}</div>
|
|
2170
|
+
`}}function ie(B){return B.startsWith("image/")?"🖼️":B==="application/pdf"?"📕":B.includes("word")||B.includes("document")?"📘":B.includes("excel")||B.includes("spreadsheet")?"📗":B.includes("zip")||B.includes("compressed")?"📦":"📄"}function ce(B){if(B===0)return"0 Bytes";const $=1024,A=["Bytes","KB","MB","GB"],v=Math.floor(Math.log(B)/Math.log($));return Math.round(B/Math.pow($,v)*100)/100+" "+A[v]}async function q(B){if(!d){B.innerHTML=`
|
|
2077
2171
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2078
2172
|
<div style="font-size: 64px; margin-bottom: 20px;">💬</div>
|
|
2079
2173
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">群聊</h3>
|
|
@@ -2082,15 +2176,15 @@
|
|
|
2082
2176
|
前往我的群组
|
|
2083
2177
|
</button>
|
|
2084
2178
|
</div>
|
|
2085
|
-
`;return}try{let
|
|
2179
|
+
`;return}try{let le=function(i,t,r="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(i,{body:t,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},g=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",b="#667eea",E=3;document.querySelectorAll(".tool-btn").forEach(w=>{w.addEventListener("click",()=>{l=w.dataset.tool,document.querySelectorAll(".tool-btn").forEach(P=>{P.style.background="var(--bg-secondary)",P.style.color="var(--text-primary)",P.style.border="1px solid var(--border)"}),w.style.background="var(--primary)",w.style.color="white",w.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",w=>{b=w.target.value}),document.getElementById("brushSize").addEventListener("input",w=>{E=w.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 w=i.toDataURL("image/png"),P=document.createElement("canvas");P.width=i.width,P.height=i.height;const G=P.toDataURL("image/png");if(w===G){alert("画布是空的,请先绘制内容!");return}if(w.length*.75/1024/1024<1)e.sendChatMessage(d._id,a.username,`[白板作品]${w}`),alert("白板作品已发送到群聊!");else{const I=await fetch(w).then(K=>K.blob()),x=new FormData;x.append("file",I,`whiteboard-${Date.now()}.png`),x.append("groupId",d._id),x.append("description","协作白板作品");const R=localStorage.getItem("token"),O=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${R}`},body:x});if(O.ok){const J=`http://localhost:3000/api/files/${(await O.json()).file._id}/download?token=${R}`;e.sendChatMessage(d._id,a.username,`[白板作品]${J}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(w){console.error("发送白板失败:",w),alert("发送失败,请重试!")}}),i.addEventListener("mousedown",w=>{r=!0;const P=i.getBoundingClientRect(),G=w.clientX-P.left,f=w.clientY-P.top;t.beginPath(),t.moveTo(G,f)}),i.addEventListener("mousemove",w=>{if(!r)return;const P=i.getBoundingClientRect(),G=w.clientX-P.left,f=w.clientY-P.top;t.lineWidth=E,t.lineCap="round",l==="pen"?(t.strokeStyle=b,t.globalCompositeOperation="source-over"):l==="eraser"&&(t.globalCompositeOperation="destination-out"),t.lineTo(G,f),t.stroke(),e.sendWhiteboardDraw(d._id,{tool:l,color:b,size:E,x:G,y:f})}),i.addEventListener("mouseup",()=>{r=!1}),i.addEventListener("mouseleave",()=>{r=!1}),e.on("whiteboard_draw",w=>{w.groupId===d._id&&(t.lineWidth=w.size,t.lineCap="round",w.tool==="pen"?(t.strokeStyle=w.color,t.globalCompositeOperation="source-over"):w.tool==="eraser"&&(t.globalCompositeOperation="destination-out"),t.lineTo(w.x,w.y),t.stroke())}),e.on("whiteboard_clear",w=>{w.groupId===d._id&&t.clearRect(0,0,i.width,i.height)})};var $=le,A=g;const k=(await n.getGroup(d._id)).group,C=!!k.mutedAll,D=(k.mutedUsers||[]).map(String).includes(String(s)),T=!C&&!D;B.innerHTML=`
|
|
2086
2180
|
<div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
|
|
2087
2181
|
<h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
|
|
2088
2182
|
<span style="font-size: 32px;">💬</span>
|
|
2089
2183
|
<span>群聊 - ${d.name}</span>
|
|
2090
2184
|
</h2>
|
|
2091
|
-
${
|
|
2185
|
+
${T?"":`
|
|
2092
2186
|
<div style="margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.2); border-radius: 8px; font-size: 14px;">
|
|
2093
|
-
⚠️ ${
|
|
2187
|
+
⚠️ ${C?"全体禁言中,无法发言":"你已被禁言"}
|
|
2094
2188
|
</div>
|
|
2095
2189
|
`}
|
|
2096
2190
|
</div>
|
|
@@ -2113,9 +2207,9 @@
|
|
|
2113
2207
|
<div class="chat-container" style="display: flex; flex-direction: column; height: calc(100vh - 350px); background: var(--bg-secondary); border-radius: 12px; overflow: hidden;">
|
|
2114
2208
|
<div class="messages" id="messages" style="flex: 1; overflow-y: auto; padding: 20px;"></div>
|
|
2115
2209
|
<div class="chat-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
|
|
2116
|
-
<button class="btn-emoji" id="emojiBtn" ${
|
|
2117
|
-
<input type="text" id="messageInput" placeholder="${
|
|
2118
|
-
<button class="btn-primary" id="sendBtn" ${
|
|
2210
|
+
<button class="btn-emoji" id="emojiBtn" ${T?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${T?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
|
|
2211
|
+
<input type="text" id="messageInput" placeholder="${T?"输入消息...":C?"全体禁言中,无法发言":"你已被禁言"}" ${T?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
|
|
2212
|
+
<button class="btn-primary" id="sendBtn" ${T?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${T?"pointer":"not-allowed"};">发送</button>
|
|
2119
2213
|
</div>
|
|
2120
2214
|
<emoji-picker id="emojiPicker" class="hidden" style="position: absolute; bottom: 80px; left: 20px; z-index: 1000;"></emoji-picker>
|
|
2121
2215
|
</div>
|
|
@@ -2152,12 +2246,12 @@
|
|
|
2152
2246
|
</div>
|
|
2153
2247
|
</div>
|
|
2154
2248
|
</div>
|
|
2155
|
-
`;const
|
|
2249
|
+
`;const z=document.getElementById("messages"),W=document.getElementById("messageInput"),F=document.getElementById("sendBtn");try{const i=await n.getGroupMessages(d._id);i.messages&&Array.isArray(i.messages)&&(i.messages.length===0?z.innerHTML=`
|
|
2156
2250
|
<div style="text-align: center; padding: 40px; color: var(--text-tertiary);">
|
|
2157
2251
|
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
2158
2252
|
<p>还没有消息,开始聊天吧!</p>
|
|
2159
2253
|
</div>
|
|
2160
|
-
`:(i.messages.forEach(t=>{const r=document.createElement("div"),l=String(t.sender)===String(s)||t.username===a.username,
|
|
2254
|
+
`:(i.messages.forEach(t=>{const r=document.createElement("div"),l=String(t.sender)===String(s)||t.username===a.username,b=N(t.content),E=t.content.startsWith("[白板作品]")||t.content.startsWith("[投票]"),w=t.content.startsWith("[白板作品]");r.className=`message ${l?"own":""}`,r.style.cssText=`
|
|
2161
2255
|
margin-bottom: 16px;
|
|
2162
2256
|
display: flex;
|
|
2163
2257
|
flex-direction: column;
|
|
@@ -2167,25 +2261,25 @@
|
|
|
2167
2261
|
<span class="message-user">${t.username}</span>
|
|
2168
2262
|
<span class="message-time">${new Date(t.timestamp).toLocaleTimeString("zh-CN")}</span>
|
|
2169
2263
|
</div>
|
|
2170
|
-
<div class="message-content" style="background: ${
|
|
2171
|
-
`,
|
|
2264
|
+
<div class="message-content" style="background: ${w||E?"transparent":l?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${l&&!E?"white":"var(--text-primary)"}; padding: ${E?"0":"12px 16px"}; border-radius: 16px; max-width: ${E?"90%":"70%"}; word-wrap: break-word; box-shadow: ${l&&!E?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${b}</div>
|
|
2265
|
+
`,z.appendChild(r)}),z.scrollTop=z.scrollHeight))}catch(i){console.error("加载历史消息失败:",i),z.innerHTML=`
|
|
2172
2266
|
<div style="text-align: center; padding: 40px; color: var(--danger);">
|
|
2173
2267
|
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
2174
2268
|
<p>加载历史消息失败</p>
|
|
2175
2269
|
<p style="font-size: 14px; color: var(--text-tertiary);">${i.message}</p>
|
|
2176
2270
|
</div>
|
|
2177
|
-
`}const
|
|
2271
|
+
`}const re=document.getElementById("emojiBtn"),X=document.getElementById("emojiPicker");T&&(re.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=>{!re.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=`
|
|
2178
2272
|
margin-bottom: 16px;
|
|
2179
2273
|
display: flex;
|
|
2180
2274
|
flex-direction: column;
|
|
2181
2275
|
align-items: ${r?"flex-end":"flex-start"};
|
|
2182
|
-
`;const l=N(i.content),
|
|
2276
|
+
`;const l=N(i.content),b=i.content.startsWith("[白板作品]")||i.content.startsWith("[投票]"),E=i.content.startsWith("[白板作品]");t.innerHTML=`
|
|
2183
2277
|
<div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
|
|
2184
2278
|
<span class="message-user">${i.username}</span>
|
|
2185
2279
|
<span class="message-time">${new Date(i.timestamp).toLocaleTimeString("zh-CN")}</span>
|
|
2186
2280
|
</div>
|
|
2187
|
-
<div class="message-content" style="background: ${
|
|
2188
|
-
`,
|
|
2281
|
+
<div class="message-content" style="background: ${E||b?"transparent":r?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${r&&!b?"white":"var(--text-primary)"}; padding: ${b?"0":"12px 16px"}; border-radius: 16px; max-width: ${b?"90%":"70%"}; word-wrap: break-word; box-shadow: ${r&&!b?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${l}</div>
|
|
2282
|
+
`,z.appendChild(t),z.scrollTop=z.scrollHeight,r||le(`${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=`
|
|
2189
2283
|
text-align: center;
|
|
2190
2284
|
padding: 12px;
|
|
2191
2285
|
margin: 16px auto;
|
|
@@ -2193,7 +2287,7 @@
|
|
|
2193
2287
|
color: white;
|
|
2194
2288
|
border-radius: 8px;
|
|
2195
2289
|
max-width: 80%;
|
|
2196
|
-
`,t.textContent=i.message||"消息发送失败",
|
|
2290
|
+
`,t.textContent=i.message||"消息发送失败",z.appendChild(t),z.scrollTop=z.scrollHeight}}),e.on("call_response",i=>{if(i.groupId===d._id){const t=document.createElement("div");t.style.cssText=`
|
|
2197
2291
|
text-align: center;
|
|
2198
2292
|
padding: 12px;
|
|
2199
2293
|
margin: 16px auto;
|
|
@@ -2201,7 +2295,7 @@
|
|
|
2201
2295
|
color: white;
|
|
2202
2296
|
border-radius: 8px;
|
|
2203
2297
|
max-width: 80%;
|
|
2204
|
-
`,t.textContent=`${i.username} 已响应点名`,
|
|
2298
|
+
`,t.textContent=`${i.username} 已响应点名`,z.appendChild(t),z.scrollTop=z.scrollHeight}});const pe=()=>{if(!T){alert(C?"全体禁言中,无法发言":"你已被禁言");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)}};T&&(F.addEventListener("click",pe),W.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),pe())}));const m=document.querySelectorAll(".chat-tab"),u=document.querySelectorAll(".tab-content");m.forEach(i=>{i.addEventListener("click",()=>{const t=i.dataset.tab;m.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"&&g()})});const y=document.getElementById("aiChat"),h=document.getElementById("aiInput"),M=document.getElementById("aiSendBtn"),p=async()=>{const i=h.value.trim();if(!i)return;const t=document.createElement("div");t.style.cssText=`
|
|
2205
2299
|
background: var(--primary);
|
|
2206
2300
|
color: white;
|
|
2207
2301
|
padding: 12px 16px;
|
|
@@ -2210,7 +2304,7 @@
|
|
|
2210
2304
|
max-width: 80%;
|
|
2211
2305
|
margin-left: auto;
|
|
2212
2306
|
word-wrap: break-word;
|
|
2213
|
-
`,t.textContent=i,
|
|
2307
|
+
`,t.textContent=i,y.appendChild(t),h.value="";const r=document.createElement("div");r.style.cssText=`
|
|
2214
2308
|
background: var(--bg-tertiary);
|
|
2215
2309
|
color: var(--text-secondary);
|
|
2216
2310
|
padding: 12px 16px;
|
|
@@ -2218,7 +2312,7 @@
|
|
|
2218
2312
|
margin-bottom: 16px;
|
|
2219
2313
|
max-width: 80%;
|
|
2220
2314
|
font-style: italic;
|
|
2221
|
-
`,r.textContent="🤔 思考中...",
|
|
2315
|
+
`,r.textContent="🤔 思考中...",y.appendChild(r),y.scrollTop=y.scrollHeight;try{const l=localStorage.getItem("token"),E=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 w=document.createElement("div");w.style.cssText=`
|
|
2222
2316
|
background: var(--bg-tertiary);
|
|
2223
2317
|
color: var(--text-primary);
|
|
2224
2318
|
padding: 12px 16px;
|
|
@@ -2227,21 +2321,21 @@
|
|
|
2227
2321
|
max-width: 80%;
|
|
2228
2322
|
line-height: 1.6;
|
|
2229
2323
|
word-wrap: break-word;
|
|
2230
|
-
`,
|
|
2324
|
+
`,w.textContent=E.answer||"抱歉,我无法回答这个问题。",y.appendChild(w),y.scrollTop=y.scrollHeight}catch(l){r.remove();const b=document.createElement("div");b.style.cssText=`
|
|
2231
2325
|
background: var(--danger);
|
|
2232
2326
|
color: white;
|
|
2233
2327
|
padding: 12px 16px;
|
|
2234
2328
|
border-radius: 12px;
|
|
2235
2329
|
margin-bottom: 16px;
|
|
2236
2330
|
max-width: 80%;
|
|
2237
|
-
`,
|
|
2331
|
+
`,b.textContent="抱歉,发生了错误: "+l.message,y.appendChild(b),y.scrollTop=y.scrollHeight}};M.addEventListener("click",p),h.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),p())})}catch(v){console.error("加载群聊失败:",v),B.innerHTML=`
|
|
2238
2332
|
<div class="empty-state" style="text-align: center; padding: 60px 20px;">
|
|
2239
2333
|
<div style="font-size: 64px; margin-bottom: 20px;">⚠️</div>
|
|
2240
2334
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--danger);">加载失败</h3>
|
|
2241
2335
|
<p style="color: var(--text-secondary); margin-bottom: 24px;">${v.message}</p>
|
|
2242
2336
|
<button class="btn-primary" onclick="location.reload()">重新加载</button>
|
|
2243
2337
|
</div>
|
|
2244
|
-
`}}async function V(
|
|
2338
|
+
`}}async function V(B){B.innerHTML=`
|
|
2245
2339
|
<div class="view-header">
|
|
2246
2340
|
<h2>🔍 搜索</h2>
|
|
2247
2341
|
</div>
|
|
@@ -2263,18 +2357,18 @@
|
|
|
2263
2357
|
</div>
|
|
2264
2358
|
<div class="search-results" id="searchResults"></div>
|
|
2265
2359
|
</div>
|
|
2266
|
-
`;const
|
|
2360
|
+
`;const $=document.getElementById("searchInput"),A=document.getElementById("searchBtn"),v=document.getElementById("searchResults"),k=async()=>{const C=$.value.trim();if(!C){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 T=[];if(D.messages&&d)try{const z=await n.getGroupMessages(d._id);z.messages&&z.messages.filter(F=>F.content.toLowerCase().includes(C.toLowerCase())).forEach(F=>{T.push({type:"message",title:`消息 - ${F.username}`,content:F.content,time:F.timestamp,group:d.name})})}catch(z){console.error("搜索消息失败:",z)}if(D.documents)try{if(d){const z=await n.getDocuments(d._id);z.documents&&z.documents.filter(F=>F.title.toLowerCase().includes(C.toLowerCase())||F.content.toLowerCase().includes(C.toLowerCase())).forEach(F=>{T.push({type:"document",title:F.title,content:F.content.substring(0,200),time:F.updatedAt,id:F._id,group:d.name})})}}catch(z){console.error("搜索文档失败:",z)}if(D.tasks)try{const z=await n.getMyTasks();z.tasks&&z.tasks.filter(F=>F.title.toLowerCase().includes(C.toLowerCase())||F.description&&F.description.toLowerCase().includes(C.toLowerCase())).forEach(F=>{T.push({type:"task",title:F.title,content:F.description||"",time:F.updatedAt,id:F._id,status:F.status})})}catch(z){console.error("搜索任务失败:",z)}T.length===0?v.innerHTML='<div class="empty-state">未找到相关结果</div>':v.innerHTML=T.map(z=>`
|
|
2267
2361
|
<div class="search-result-item">
|
|
2268
2362
|
<div class="result-header">
|
|
2269
|
-
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[
|
|
2270
|
-
<span class="result-time">${new Date(
|
|
2363
|
+
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[z.type]} ${z.type==="message"?"消息":z.type==="document"?"文档":"任务"}</span>
|
|
2364
|
+
<span class="result-time">${new Date(z.time).toLocaleString()}</span>
|
|
2271
2365
|
</div>
|
|
2272
|
-
<h4>${
|
|
2273
|
-
<p>${
|
|
2274
|
-
${
|
|
2275
|
-
${
|
|
2366
|
+
<h4>${ne(z.title,C)}</h4>
|
|
2367
|
+
<p>${ne(z.content,C)}</p>
|
|
2368
|
+
${z.group?`<span class="result-group">群组: ${z.group}</span>`:""}
|
|
2369
|
+
${z.status?`<span class="result-status">状态: ${oe(z.status)}</span>`:""}
|
|
2276
2370
|
</div>
|
|
2277
|
-
`).join("")}catch(
|
|
2371
|
+
`).join("")}catch(T){v.innerHTML=`<div class="empty-state">搜索失败: ${T.message}</div>`}};A.addEventListener("click",k),$.addEventListener("keypress",C=>{C.key==="Enter"&&k()})}function ne(B,$){if(!$)return B;const A=new RegExp(`(${$})`,"gi");return B.replace(A,"<mark>$1</mark>")}function oe(B){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[B]||B}async function de(B){if(!d){B.innerHTML=`
|
|
2278
2372
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2279
2373
|
<div style="font-size: 64px; margin-bottom: 20px;">📚</div>
|
|
2280
2374
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">知识库</h3>
|
|
@@ -2283,7 +2377,7 @@
|
|
|
2283
2377
|
前往我的群组
|
|
2284
2378
|
</button>
|
|
2285
2379
|
</div>
|
|
2286
|
-
`;return}try{const
|
|
2380
|
+
`;return}try{const $=localStorage.getItem("token"),A=await fetch(`http://localhost:3000/api/knowledge/group/${d._id}`,{headers:{Authorization:`Bearer ${$}`}});if(!A.ok)throw new Error(`HTTP ${A.status}: ${A.statusText}`);const v=await A.json();let k=[];Array.isArray(v)?k=v:v.data&&Array.isArray(v.data)?k=v.data:v.data&&v.data.knowledgeList&&Array.isArray(v.data.knowledgeList)?k=v.data.knowledgeList:v.items&&Array.isArray(v.items)?k=v.items:v.knowledge&&Array.isArray(v.knowledge)&&(k=v.knowledge),B.innerHTML=`
|
|
2287
2381
|
<div class="view-header">
|
|
2288
2382
|
<h2>📚 知识库 - ${d.name}</h2>
|
|
2289
2383
|
<button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
|
|
@@ -2327,31 +2421,31 @@
|
|
|
2327
2421
|
</form>
|
|
2328
2422
|
</div>
|
|
2329
2423
|
</div>
|
|
2330
|
-
`;const
|
|
2424
|
+
`;const C=document.getElementById("knowledgeList");k.length===0?C.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(k.forEach(D=>{var z,W;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=`
|
|
2331
2425
|
${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
2426
|
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${D.isShared?"padding-right: 80px;":""}">${D.title}</h3>
|
|
2333
2427
|
<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>
|
|
2334
2428
|
<div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
|
|
2335
|
-
<span>👤 ${((
|
|
2429
|
+
<span>👤 ${((z=D.author)==null?void 0:z.username)||((W=D.creator)==null?void 0:W.username)||"未知"}</span>
|
|
2336
2430
|
<span style="margin-left: 15px;">📅 ${new Date(D.createdAt).toLocaleDateString()}</span>
|
|
2337
2431
|
</div>
|
|
2338
2432
|
${D.tags&&D.tags.length>0?`
|
|
2339
2433
|
<div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
|
|
2340
|
-
${D.tags.map(
|
|
2434
|
+
${D.tags.map(F=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${F}</span>`).join("")}
|
|
2341
2435
|
</div>
|
|
2342
2436
|
`:""}
|
|
2343
2437
|
<div style="display: flex; gap: 10px;">
|
|
2344
2438
|
<button class="btn-secondary btn-sm" data-id="${D._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
2345
2439
|
<button class="btn-danger btn-sm" data-id="${D._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
2346
2440
|
</div>
|
|
2347
|
-
|
|
2441
|
+
`,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"},C.appendChild(T)}),document.querySelectorAll('[data-action="edit"]').forEach(D=>{D.addEventListener("click",async()=>{var z;const T=k.find(W=>W._id===D.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=((z=T.tags)==null?void 0:z.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="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 ${$}`}}),alert("删除成功!"),await de(B)}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("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async D=>{D.preventDefault();const T=new FormData(D.target),z={title:T.get("title"),content:T.get("content"),tags:T.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,F=W?`http://localhost:3000/api/knowledge/${W}`:"http://localhost:3000/api/knowledge";if(!(await fetch(F,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${$}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await de(B)}catch(W){alert("操作失败: "+W.message)}})}catch($){console.error("加载知识库失败:",$),B.innerHTML=`
|
|
2348
2442
|
<div class="view-header">
|
|
2349
2443
|
<h2>📚 知识库 - ${d.name}</h2>
|
|
2350
2444
|
</div>
|
|
2351
2445
|
<div class="empty-state" style="text-align: center; padding: 40px 20px;">
|
|
2352
2446
|
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
2353
2447
|
<h3 style="margin-bottom: 8px; color: var(--danger);">加载失败</h3>
|
|
2354
|
-
<p style="color: var(--text-secondary); margin-bottom: 16px;">${
|
|
2448
|
+
<p style="color: var(--text-secondary); margin-bottom: 16px;">${$.message}</p>
|
|
2355
2449
|
<button class="btn-primary" onclick="location.reload()">重新加载</button>
|
|
2356
2450
|
</div>
|
|
2357
|
-
`}}
|
|
2451
|
+
`}}L("groups")}class hn{constructor(){this.authService=new ze,this.wsService=new jt,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(){_t(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):bn(this.currentUser,this.wsService)}}new hn;
|