collabdocchat 2.5.8 → 2.5.9
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-DkEWw6lP.js → index-Dkrhl719.js} +96 -96
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/scripts/cleanup-scripts.js +2 -0
- package/scripts/fix-startup-issues.js +2 -0
- package/scripts/start-simple.js +2 -0
- package/server/public/index.html +2 -0
- package/src/pages/admin-dashboard.js +25 -6
- package/src/pages/user-dashboard.js +6 -2
|
@@ -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 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=`
|
|
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,ve="emoji",fe="keyvalue",Ye="favorites",Ft="tokens",mt="tokens",Ot="unicode",yt="count",Ht="group",Nt="order",bt="group-order",Ue="eTag",Be="url",at="skinTone",we="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,H]]of Object.entries(c))s.createIndex(d,S,{multiEntry:H});return s}e(fe),e(ve,Ot,{[mt]:[Ft,!0],[bt]:[[Ht,Nt]],[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 be(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($=>d.objectStore($));let H;n(S,d,$=>{H=$}),d.oncomplete=()=>c(H),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 xe(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:H,emoji:$,version:j})=>{const O=[...new Set(xt([...(d||[]).map(xe).flat(),...(H||[]).map(xe).flat(),...xe(o),n]))].sort(),_={annotation:o,group:c,order:s,tags:H,tokens:O,unicode:$,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 he(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,fe,Be)}async function aa(a,e,o){const[n,c]=await Promise.all([Ue,Be].map(s=>Ze(a,fe,s)));return n===o&&c===e}async function na(a,e){return be(a,ve,we,(n,c,s)=>{let d;const S=()=>{n.getAll(d&&IDBKeyRange.lowerBound(d,!0),50).onsuccess=H=>{const $=H.target.result;for(const j of $)if(d=j.unicode,e(j))return s(j);if($.length<50)return s();S()}};S()})}async function Et(a,e,o,n){try{const c=Qt(e);await be(a,[ve,fe],Ke,([s,d],S)=>{let H,$,j=0;function O(){++j===2&&_()}function _(){if(!(H===n&&$===o)){s.clear();for(const U of c)s.put(U);d.put(n,Ue),d.put(o,Be),Xe(S)}}he(d,Ue,U=>{H=U,O()}),he(d,Be,U=>{$=U,O()})})}finally{}}async function oa(a,e){return be(a,ve,we,(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(xe(e));return o.length?be(a,ve,we,(n,c,s)=>{const d=[],S=()=>{d.length===o.length&&H()},H=()=>{const $=kt(d,j=>j.unicode);s($.sort((j,O)=>j.order<O.order?-1:1))};for(let $=0;$<o.length;$++){const j=o[$],O=$===o.length-1?IDBKeyRange.bound(j,j+"",!1,!0):IDBKeyRange.only(j);wt(n.index(mt),O,_=>{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 be(a,ve,we,(o,n,c)=>he(o,e,s=>{if(s)return c(s);he(o.index(ht),e,d=>c(d||null))}))}function Ze(a,e,o){return be(a,e,we,(n,c,s)=>he(n,o,s))}function ra(a,e,o,n){return be(a,e,Ke,(c,s)=>{c.put(n,o),Xe(s)})}function da(a,e){return be(a,Ye,Ke,(o,n)=>he(o,e,c=>{o.put((c||0)+1,e),Xe(n)}))}function la(a,e,o){return o===0?[]:be(a,[Ye,ve],we,([n,c],s,d)=>{const S=[];n.index(yt).openCursor(void 0,"prev").onsuccess=H=>{const $=H.target.result;if(!$)return d(S);function j(U){if(S.push(U),S.length===o)return d(S);$.continue()}const O=$.primaryKey,_=e.byName(O);if(_)return j(_);he(c,O,U=>{if(U)return j(U);$.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 $=0;$<d.length;$++){const j=d.charAt($);let O=S.get(j);O||(O=new Map,S.set(j,O)),S=O}let H=S.get($e);H||(H=[],S.set($e,H)),H.push(c)}}return(c,s)=>{let d=o;for(let $=0;$<c.length;$++){const j=c.charAt($),O=d.get(j);if(O)d=O;else return[]}if(s)return d.get($e)||[];const S=[],H=[d];for(;H.length;){const j=[...H.shift().entries()].sort((O,_)=>O[0]<_[0]?-1:1);for(const[O,_]of j)O===$e?S.push(..._):H.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 xe(se))U.add(ae);return U}),s=_=>c(_,!0),d=_=>c(_,!1),S=_=>{const U=xe(_),se=U.map((ae,ie)=>(ie<U.length-1?s:d)(ae));return kt(se,ae=>ae.name).sort(e)},H=new Map,$=new Map;for(const _ of a){$.set(_.name.toLowerCase(),_);for(const U of _.shortcodes||[])H.set(U.toLowerCase(),_)}return{all:o,search:S,byShortcode:_=>H.get(_.toLowerCase()),byName:_=>$.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,fe,at)||0}async setPreferredSkinTone(e){return _e(e),await this.ready(),ra(this._db,fe,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 ye(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 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 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:S,category:H,annotation:$})=>({unicode:n,name:S,shortcodes:s,url:d,category:H,annotation:$,id:n||S,skins:c&&o(c)}))}const Se=requestAnimationFrame;let Ha=typeof ResizeObserver=="function";function Na(a,e,o){let n;Ha?(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 He;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 He>"u"&&(He=dt(e));const S=d/1.8<He;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:H}}=o,$=a[s];if(c!==$)if(o.currentExpression=$,d)if($===null)n.removeAttribute(d);else{const j=S+lt($)+H;n.setAttribute(d,j)}else{let j;Array.isArray($)?Za($,o):$ instanceof Element?(j=$,n.replaceWith(j)):n.nodeValue=lt($),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 $=0,j=a.length;$<j;$++){const O=a[$];if(e+=O.slice(S),$===j-1)break;for(let q=0;q<O.length;q++)switch(O.charAt(q)){case"<":{O.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(O);se=q[1],ae=q[2];const V=/^([^">]*)("?)/.exec(a[$+1]);ie=V[1],e=e.slice(0,-1*q[0].length),S=V[0].length}else S=0;const pe={attributeName:se,attributeValuePre:ae,attributeValuePost:ie,expressionIndex:$};U.push(pe),!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((H,$)=>{const j=o;o=S(H);try{return d(H,$)}finally{o=j}})}return{map:c,html:n}}function on(a,e,o,n,c,s,d,S,H){const{labelWithSkin:$,titleForEmoji:j,unicodeWithSkin:O}=o,{html:_,map:U}=nn(e);function se(q,V,ne){return U(q,(oe,le)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?le===e.activeSearchItem:null}" aria-label="${$(oe,e.currentSkinTone)}" title="${j(oe)}" class="${"emoji"+(V&&le===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?O(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>`,pe=(q,V)=>{for(const ne of a.querySelectorAll(`[${q}]`))V(ne,ne.getAttribute(q))};if(H){a.appendChild(ie);for(const q of["click","focusout","input","keydown","keyup"])pe(`data-on-${q}`,(V,ne)=>{V.addEventListener(q,n[ne])});pe("data-ref",(q,V)=>{s[V]=q}),d.addEventListener("abort",()=>{a.removeChild(ie)})}pe("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 $=[...c];c.clear();try{for(const j of $)j()}finally{s=!1,c.size&&(s=!0,Ce(d))}},S=new Proxy({},{get($,j){if(o){let O=n.get(j);O||(O=new Set,n.set(j,O)),O.add(o)}return $[j]},set($,j,O){if($[j]!==O){$[j]=O;const _=n.get(j);if(_){for(const U of _)c.add(U);s||(s=!0,Ce(d))}}return!0}}),H=$=>{const j=()=>{const O=o;o=j;try{return $()}finally{o=O}};return j()};return a.addEventListener("abort",()=>{e=!0}),{state:S,createEffect:H}}function Ne(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 H=t=>{a.getElementById(t).focus()},$=t=>a.getElementById(`emo-${t.id}`),j=(t,r)=>{o.rootElement.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0}))},O=(t,r)=>t.id===r.id,_=(t,r)=>{const{category:l,emojis:b}=t,{category:E,emojis:w}=r;return l!==E?!1:Ne(b,w,O)},U=t=>{Ne(s.currentEmojis,t,O)||(s.currentEmojis=t)},se=t=>{s.searchMode!==t&&(s.searchMode=t)},ae=t=>{Ne(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:I,updateOnIntersection:A};let le=!0;d(()=>{on(a,s,V,ne,oe,o,c,S,le),le=!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: ${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=`
|
|
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 L(Mt([...E,...l],P=>P.unicode||P.name).slice(0,b));s.currentFavorites=w}s.databaseLoaded&&s.defaultFavoriteEmojis&&t()});function I(t){Na(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,$)?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 L(t){return Oa(t,s.emojiVersion||await Fe())}async function z(t){const r=t===-1?s.customEmoji:await s.database.getEmojiByGroup(t);return L(await D(r))}async function W(t){return L(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=>{ye(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 ye(t),ue(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&&(ye(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 ce(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 ue(t){const r=ce(t);j("emoji-click-sync",r),j("emoji-click",await r)}function m(t){const{target:r}=t;if(!r.classList.contains("emoji"))return;ye(t);const l=r.id.substring(4);ue(l)}function u(t){s.currentSkinTone=t,s.skinTonePickerExpanded=!1,H("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;ye(t);const b=parseInt(l[1],10);u(b)}function y(t){s.skinTonePickerExpanded=!s.skinTonePickerExpanded,s.activeSkinTone=s.currentSkinTone,s.skinTonePickerExpanded&&(ye(t),Se(()=>H("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=>{ye(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 ye(t),u(s.activeSkinTone);case"Escape":return ye(t),s.skinTonePickerExpanded=!1,H("skintone-button")}}function M(t){if(s.skinTonePickerExpanded)switch(t.key){case" ":return ye(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 H=localStorage.getItem("currentTheme")||"dark";yn(H),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(m=>{m.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(g=>g.classList.remove("active")),m.classList.add("active");const u=m.dataset.view;
|
|
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;ue(u)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{ue("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{ue("help")});async function $(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>
|
|
@@ -179,13 +179,13 @@
|
|
|
179
179
|
<button class="btn-select" data-id="${y._id}">选择</button>
|
|
180
180
|
<button class="btn-secondary" data-id="${y._id}" data-action="manage">管理成员</button>
|
|
181
181
|
</div>
|
|
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
|
|
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 O(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 $(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
184
|
<input type="checkbox" value="${g._id}">
|
|
185
185
|
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${g.username[0].toUpperCase()}</div>
|
|
186
186
|
<span>${g.username} (${g.role==="admin"?"管理员":"用户"})</span>
|
|
187
187
|
</label>
|
|
188
|
-
`).join("")}catch(m){console.error("加载用户失败:",m),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function
|
|
188
|
+
`).join("")}catch(m){console.error("加载用户失败:",m),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function O(m){try{const u=await n.getGroup(m),g=await n.getAllUsers(),y=u.group,h=document.getElementById("currentMembers");h.innerHTML=`
|
|
189
189
|
<h4>当前成员 (${y.members.length})</h4>
|
|
190
190
|
<div style="max-height: 200px; overflow-y: auto;">
|
|
191
191
|
${y.members.map(t=>`
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
</div>
|
|
207
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(m,u)=>{try{await n.addMember(m,u),alert("成员添加成功!"),await
|
|
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 O(m)}catch(g){alert("添加失败: "+g.message)}},window.removeMember=async(m,u)=>{if(confirm("确定要移除该成员吗?"))try{await n.removeMember(m,u),alert("成员移除成功!"),await O(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>
|
|
@@ -619,7 +619,7 @@
|
|
|
619
619
|
</form>
|
|
620
620
|
</div>
|
|
621
621
|
</div>
|
|
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=
|
|
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=pe(y.size);h.innerHTML=`
|
|
623
623
|
<div class="file-icon">${M}</div>
|
|
624
624
|
<div class="file-info">
|
|
625
625
|
<h4>${y.originalName}</h4>
|
|
@@ -639,7 +639,7 @@
|
|
|
639
639
|
<h2>文件管理</h2>
|
|
640
640
|
</div>
|
|
641
641
|
<div class="empty-state">加载文件失败: ${u.message}</div>
|
|
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
|
|
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 pe(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 T=f.replace("[白板作品]","").trim(),x=T.includes("/api/files/")&&T.includes("/download");let R=T;if(x&&!T.includes("token=")){const N=localStorage.getItem("token");R=T.includes("?")?`${T}&token=${N}`:`${T}?token=${N}`}return`
|
|
643
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
644
|
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
645
645
|
<span style="font-size: 18px;">🎨</span>
|
|
@@ -655,13 +655,13 @@
|
|
|
655
655
|
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
656
656
|
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
657
657
|
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
658
|
-
<button onclick="navigator.clipboard.writeText('${
|
|
658
|
+
<button onclick="navigator.clipboard.writeText('${T}'); 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
659
|
</div>
|
|
660
660
|
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
661
661
|
</div>
|
|
662
662
|
</div>
|
|
663
|
-
`}if(f.startsWith("[投票]")){const
|
|
664
|
-
<div class="poll-card" data-poll-id="${
|
|
663
|
+
`}if(f.startsWith("[投票]")){const T=f.replace("[投票]","").trim();return`
|
|
664
|
+
<div class="poll-card" data-poll-id="${T}" 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('${T}')">
|
|
665
665
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
666
666
|
<span style="font-size: 32px;">📊</span>
|
|
667
667
|
<div style="flex: 1;">
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
📋 查看投票详情
|
|
674
674
|
</div>
|
|
675
675
|
</div>
|
|
676
|
-
`}return f}window.viewPollDetail=async f=>{try{const
|
|
676
|
+
`}return f}window.viewPollDetail=async f=>{try{const T=localStorage.getItem("token"),x=await fetch(`http://localhost:3000/api/polls/${f}`,{headers:{Authorization:`Bearer ${T}`}});if(!x.ok)throw new Error("获取投票详情失败");const N=(await x.json()).poll,K=N.options.reduce((Q,ge)=>Q+ge.votes.length,0),Z=N.status==="ended"||N.endTime&&new Date(N.endTime)<new Date,J=`
|
|
677
677
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
678
678
|
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
679
679
|
<div class="modal-header">
|
|
@@ -681,15 +681,15 @@
|
|
|
681
681
|
<button class="modal-close" id="closePollDetailModal">×</button>
|
|
682
682
|
</div>
|
|
683
683
|
<div class="modal-body" style="padding: 24px;">
|
|
684
|
-
<h2 style="margin: 0 0 12px 0;">${
|
|
685
|
-
${
|
|
684
|
+
<h2 style="margin: 0 0 12px 0;">${N.title}</h2>
|
|
685
|
+
${N.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${N.description}</p>`:""}
|
|
686
686
|
|
|
687
687
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
|
|
688
688
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
689
|
-
${
|
|
689
|
+
${N.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
690
690
|
</span>
|
|
691
691
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
692
|
-
${
|
|
692
|
+
${N.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
693
693
|
</span>
|
|
694
694
|
<span style="font-size: 13px; padding: 6px 12px; background: ${Z?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
695
695
|
${Z?"已结束":"进行中"}
|
|
@@ -700,7 +700,7 @@
|
|
|
700
700
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
701
701
|
<div>
|
|
702
702
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
703
|
-
<div style="font-weight: 600;">👤 ${
|
|
703
|
+
<div style="font-weight: 600;">👤 ${N.creatorName}</div>
|
|
704
704
|
</div>
|
|
705
705
|
<div>
|
|
706
706
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
@@ -708,32 +708,32 @@
|
|
|
708
708
|
</div>
|
|
709
709
|
<div>
|
|
710
710
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
711
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
711
|
+
<div style="font-weight: 600;">⏰ ${new Date(N.createdAt).toLocaleString("zh-CN")}</div>
|
|
712
712
|
</div>
|
|
713
|
-
${
|
|
713
|
+
${N.endTime?`
|
|
714
714
|
<div>
|
|
715
715
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
716
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
716
|
+
<div style="font-weight: 600;">⏰ ${new Date(N.endTime).toLocaleString("zh-CN")}</div>
|
|
717
717
|
</div>
|
|
718
718
|
`:""}
|
|
719
719
|
</div>
|
|
720
720
|
</div>
|
|
721
721
|
|
|
722
722
|
<h3 style="margin-bottom: 16px;">投票选项</h3>
|
|
723
|
-
${
|
|
723
|
+
${N.options.map((Q,ge)=>{const Y=K>0?(Q.votes.length/K*100).toFixed(1):0;let ee="";if(!N.anonymous&&Q.votes.length>0){const me=Q.votes.map(de=>typeof de=="object"&&de.username?de.username:typeof de=="object"&&de.user&&de.user.username?de.user.username:"未知用户").filter(de=>de!=="未知用户");me.length>0&&(ee=`
|
|
724
|
+
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
725
|
+
<strong style="color: var(--text-primary);">👥 投票者:</strong> ${me.join(", ")}
|
|
726
|
+
</div>
|
|
727
|
+
`)}return`
|
|
724
728
|
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
725
729
|
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
|
726
730
|
<span style="font-weight: 500; font-size: 16px;">${Q.text}</span>
|
|
727
731
|
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${Q.votes.length} 票 (${Y}%)</span>
|
|
728
732
|
</div>
|
|
729
|
-
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden;
|
|
733
|
+
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden;">
|
|
730
734
|
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${Y}%; transition: width 0.3s;"></div>
|
|
731
735
|
</div>
|
|
732
|
-
${
|
|
733
|
-
<div style="font-size: 13px; color: var(--text-secondary);">
|
|
734
|
-
<strong>投票者:</strong> ${Q.votes.map(ee=>ee.username).join(", ")}
|
|
735
|
-
</div>
|
|
736
|
-
`:""}
|
|
736
|
+
${ee}
|
|
737
737
|
</div>
|
|
738
738
|
`}).join("")}
|
|
739
739
|
|
|
@@ -741,7 +741,7 @@
|
|
|
741
741
|
</div>
|
|
742
742
|
</div>
|
|
743
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(
|
|
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(T){console.error("加载投票详情失败:",T),alert("加载投票详情失败: "+T.message)}},m.innerHTML=`
|
|
745
745
|
<div class="view-header">
|
|
746
746
|
<h2>群聊 - ${d.name}</h2>
|
|
747
747
|
<div style="display: flex; gap: 10px;">
|
|
@@ -937,17 +937,17 @@
|
|
|
937
937
|
</div>
|
|
938
938
|
</div>
|
|
939
939
|
</div>
|
|
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(
|
|
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(T=>{const x=document.createElement("div");x.className=`message ${T.sender===s?"own":""}`;const R=y(T.content),N=T.content.startsWith("[白板作品]")||T.content.startsWith("[投票]"),K=T.content.startsWith("[白板作品]");x.innerHTML=`
|
|
941
941
|
<div class="message-header">
|
|
942
|
-
<span class="message-user">${
|
|
943
|
-
<span class="message-time">${new Date(
|
|
942
|
+
<span class="message-user">${T.username}</span>
|
|
943
|
+
<span class="message-time">${new Date(T.timestamp).toLocaleTimeString()}</span>
|
|
944
944
|
</div>
|
|
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);":
|
|
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);":N?"background: transparent; padding: 0;":""}">${R}</div>
|
|
946
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(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
|
|
947
947
|
|
|
948
948
|
确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
|
|
949
949
|
|
|
950
|
-
群组名称:`+d.name)!==d.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const x=document.getElementById("clearChatBtn"),R=x.innerHTML;x.innerHTML="⏳ 清除中...",x.disabled=!0;const
|
|
950
|
+
群组名称:`+d.name)!==d.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const x=document.getElementById("clearChatBtn"),R=x.innerHTML;x.innerHTML="⏳ 清除中...",x.disabled=!0;const N=localStorage.getItem("token"),K=await fetch(`http://localhost:3000/api/messages/group/${d._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${N}`,"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 T=document.getElementById("membersList");T.innerHTML=g.members.filter(x=>x._id.toString()!==s).map(x=>{const R=r.has(x._id.toString());return`
|
|
951
951
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
|
|
952
952
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
953
953
|
<div class="avatar" style="width: 35px; height: 35px;">${x.username[0].toUpperCase()}</div>
|
|
@@ -957,16 +957,16 @@
|
|
|
957
957
|
${R?"取消禁言":"禁言"}
|
|
958
958
|
</button>
|
|
959
959
|
</div>
|
|
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"),
|
|
961
|
-
<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"),T=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="选项 ${T}" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
|
|
962
962
|
<button type="button" class="btn-danger remove-option" style="padding: 10px 15px;">删除</button>
|
|
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
|
|
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 T=document.getElementById("pollTitle").value.trim(),x=document.getElementById("pollDescription").value.trim(),R=document.getElementById("allowMultiple").checked,N=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:T,description:x,options:J,groupId:d._id,allowMultiple:R,anonymous:N,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 T=!r.has(f),x=await n.setUserMute(d._id,f,T);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(T){alert("操作失败: "+T.message)}},e.on("chat_message",f=>{if(f.groupId===d._id){const T=document.createElement("div");T.className=`message ${f.userId===s?"own":""}`;const x=y(f.content),R=f.content.startsWith("[白板作品]")||f.content.startsWith("[投票]"),N=f.content.startsWith("[白板作品]");T.innerHTML=`
|
|
964
964
|
<div class="message-header">
|
|
965
965
|
<span class="message-user">${f.username}</span>
|
|
966
966
|
<span class="message-time">${new Date(f.timestamp).toLocaleTimeString()}</span>
|
|
967
967
|
</div>
|
|
968
|
-
<div class="message-content" style="${
|
|
969
|
-
`,h.appendChild(
|
|
968
|
+
<div class="message-content" style="${N?"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(T),h.scrollTop=h.scrollHeight}}),e.on("chat_blocked",f=>{if(f.groupId===d._id){const T=document.createElement("div");T.className="notification",T.textContent=f.message||"消息发送失败",h.appendChild(T),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",T=>{T.target.style.background="var(--primary)",T.target.style.color="white",T.target.style.transform="translateY(-2px)"}),f.addEventListener("mouseleave",T=>{T.target.style.background="var(--bg)",T.target.style.color="inherit",T.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"),T=f.value.trim();if(!T)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=T,x.appendChild(R),f.value="";const N=document.createElement("div");N.className="ai-message ai loading",N.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",N.textContent="思考中...",x.appendChild(N),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:T,groupId:d==null?void 0:d._id})})).json();N.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){N.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 T=document.getElementById("imagePreviewModal"),x=document.getElementById("previewImage"),R=document.getElementById("downloadImageBtn");x.src=f,T.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(N){console.error("下载失败:",N),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 T=f.getContext("2d");let x=!1,R="pen",N="#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=>{N=Y.target.value});const Q=document.getElementById("brushSizeCanvas"),ge=document.getElementById("brushSizeLabel");Q&&ge&&(Q.oninput=Y=>{K=Y.target.value,ge.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(),me=Y.clientX-ee.left,de=Y.clientY-ee.top;T.beginPath(),T.moveTo(Z,J),T.lineTo(me,de),T.strokeStyle=R==="eraser"?"#ffffff":N,T.lineWidth=K,T.lineCap="round",T.stroke(),Z=me,J=de},f.onmouseup=()=>{x=!1},f.onmouseleave=()=>{x=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&T.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()),me=new FormData;me.append("file",ee,`whiteboard-${Date.now()}.png`),me.append("groupId",d._id),me.append("description","协作白板作品");const de=localStorage.getItem("token"),et=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${de}`},body:me});if(et.ok){const Dt=`http://localhost:3000/api/files/${(await et.json()).file._id}/download?token=${de}`;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=`
|
|
970
970
|
<div class="view-header">
|
|
971
971
|
<h2>随机点名 - ${d.name}</h2>
|
|
972
972
|
</div>
|
|
@@ -1061,22 +1061,22 @@
|
|
|
1061
1061
|
<div>资源</div>
|
|
1062
1062
|
<div>详情</div>
|
|
1063
1063
|
</div>
|
|
1064
|
-
${E.logs.map(G=>{var f,
|
|
1064
|
+
${E.logs.map(G=>{var f,T,x,R,N;return`
|
|
1065
1065
|
<div class="audit-row" onclick="showAuditDetail('${G._id}')">
|
|
1066
1066
|
<div class="audit-time">${new Date(G.createdAt).toLocaleString()}</div>
|
|
1067
1067
|
<div class="audit-user">
|
|
1068
|
-
<div class="avatar">${((x=(
|
|
1068
|
+
<div class="avatar">${((x=(T=(f=G.user)==null?void 0:f.username)==null?void 0:T[0])==null?void 0:x.toUpperCase())||"?"}</div>
|
|
1069
1069
|
<span>${((R=G.user)==null?void 0:R.username)||"未知用户"}</span>
|
|
1070
1070
|
</div>
|
|
1071
1071
|
<div class="audit-action">
|
|
1072
1072
|
<span class="action-badge action-${G.action}">${A(G.action)}</span>
|
|
1073
1073
|
</div>
|
|
1074
1074
|
<div class="audit-resource">${G.resourceTitle||G.resourceId}</div>
|
|
1075
|
-
<div class="audit-description">${((
|
|
1075
|
+
<div class="audit-description">${((N=G.details)==null?void 0:N.description)||"-"}</div>
|
|
1076
1076
|
</div>
|
|
1077
1077
|
`}).join("")}
|
|
1078
1078
|
</div>
|
|
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,
|
|
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,T,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,N=((f=t.changes.deletions)==null?void 0:f.reduce((K,Z)=>K+Z.length,0))||0;(R>0||N>0)&&(E+=' (<span style="color: #10b981;">+'+R+'</span> / <span style="color: #ef4444;">-'+N+"</span> 字符)")}break;case"title_edit":E+='修改了文档 <strong>"'+b+'"</strong> 的标题',(T=t.details)!=null&&T.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"),N=document.getElementById("auditDetailContent"),K=te=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[te]||"#6366f1";N.innerHTML=`
|
|
1080
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%);">
|
|
1081
1081
|
|
|
1082
1082
|
<!-- 操作信息 -->
|
|
@@ -1162,7 +1162,7 @@
|
|
|
1162
1162
|
</div>
|
|
1163
1163
|
</div>
|
|
1164
1164
|
</div>
|
|
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
|
|
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 ge=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);">共 '+ge+" 个字符</div>",Q+="</div>"}if(x.changes.deletions&&x.changes.deletions.length>0){const ge=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);">共 '+ge+" 个字符</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=`
|
|
1166
1166
|
<div class="empty-state" style="text-align: center; padding: 60px 20px;">
|
|
1167
1167
|
<div style="font-size: 64px; margin-bottom: 20px;">🗳️</div>
|
|
1168
1168
|
<h3 style="font-size: 24px; margin-bottom: 12px;">投票管理</h3>
|
|
@@ -1221,12 +1221,12 @@
|
|
|
1221
1221
|
${t?"":`<button class="btn-secondary btn-sm end-poll" data-poll-id="${p._id}">结束投票</button>`}
|
|
1222
1222
|
<button class="btn-danger btn-sm delete-poll" data-poll-id="${p._id}">删除</button>
|
|
1223
1223
|
</div>
|
|
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
|
|
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 le(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=`
|
|
1225
1225
|
<div class="view-header">
|
|
1226
1226
|
<h2>🗳️ 投票管理</h2>
|
|
1227
1227
|
</div>
|
|
1228
1228
|
<div class="empty-state">加载失败: ${u.message}</div>
|
|
1229
|
-
`}}async function
|
|
1229
|
+
`}}async function le(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=`
|
|
1230
1230
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1231
1231
|
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
1232
1232
|
<div class="modal-header">
|
|
@@ -1322,12 +1322,12 @@
|
|
|
1322
1322
|
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[t.type]} ${t.type==="message"?"消息":t.type==="document"?"文档":"任务"}</span>
|
|
1323
1323
|
<span class="result-time">${new Date(t.time).toLocaleString()}</span>
|
|
1324
1324
|
</div>
|
|
1325
|
-
<h4>${
|
|
1326
|
-
<p>${
|
|
1325
|
+
<h4>${I(t.title,M)}</h4>
|
|
1326
|
+
<p>${I(t.content,M)}</p>
|
|
1327
1327
|
${t.group?`<span class="result-group">群组: ${t.group}</span>`:""}
|
|
1328
1328
|
${t.status?`<span class="result-status">状态: ${v(t.status)}</span>`:""}
|
|
1329
1329
|
</div>
|
|
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
|
|
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 I(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=`
|
|
1331
1331
|
<div class="view-header">
|
|
1332
1332
|
<h2>📚 知识库管理 - ${d.name}</h2>
|
|
1333
1333
|
<button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
|
|
@@ -1463,7 +1463,7 @@
|
|
|
1463
1463
|
</button>
|
|
1464
1464
|
<button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1465
1465
|
</div>
|
|
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
|
|
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 L(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=`
|
|
1467
1467
|
<div class="view-header">
|
|
1468
1468
|
<h2>💾 备份管理</h2>
|
|
1469
1469
|
<button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
|
|
@@ -1484,7 +1484,7 @@
|
|
|
1484
1484
|
<button class="btn-primary btn-sm" data-backup-name="${i.name}" data-filename="${i.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
|
|
1485
1485
|
<button class="btn-danger btn-sm" data-backup-name="${i.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1486
1486
|
</div>
|
|
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
|
|
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 L(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 L(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=`
|
|
1488
1488
|
<div class="view-header">
|
|
1489
1489
|
<h2>📤 数据导出</h2>
|
|
1490
1490
|
</div>
|
|
@@ -1655,7 +1655,7 @@
|
|
|
1655
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>
|
|
1656
1656
|
</div>
|
|
1657
1657
|
</div>
|
|
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
|
|
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 ce(m){m.innerHTML=`
|
|
1659
1659
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1660
1660
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1661
1661
|
<span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">❓ 帮助中心</span>
|
|
@@ -1835,7 +1835,7 @@
|
|
|
1835
1835
|
</div>
|
|
1836
1836
|
</div>
|
|
1837
1837
|
</div>
|
|
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
|
|
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 ue(m){const u=document.getElementById("contentArea");switch(m){case"groups":await $(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 L(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 ce(u);break}}ue("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 H(B){if(B.startsWith("[白板作品]")){const I=B.replace("[白板作品]","").trim(),A=I.includes("/api/files/")&&I.includes("/download");let v=I;if(A&&!I.includes("token=")){const k=localStorage.getItem("token");v=I.includes("?")?`${I}&token=${k}`:`${I}?token=${k}`}return`
|
|
1839
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);">
|
|
1840
1840
|
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
1841
1841
|
<span style="font-size: 18px;">🎨</span>
|
|
@@ -1851,13 +1851,13 @@
|
|
|
1851
1851
|
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
1852
1852
|
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
1853
1853
|
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
1854
|
-
<button onclick="navigator.clipboard.writeText('${
|
|
1854
|
+
<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>
|
|
1855
1855
|
</div>
|
|
1856
1856
|
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
1857
1857
|
</div>
|
|
1858
1858
|
</div>
|
|
1859
|
-
`}if(B.startsWith("[投票]")){const
|
|
1860
|
-
<div class="poll-card" data-poll-id="${
|
|
1859
|
+
`}if(B.startsWith("[投票]")){const I=B.replace("[投票]","").trim();return`
|
|
1860
|
+
<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}')">
|
|
1861
1861
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
1862
1862
|
<span style="font-size: 32px;">📊</span>
|
|
1863
1863
|
<div style="flex: 1;">
|
|
@@ -1869,12 +1869,12 @@
|
|
|
1869
1869
|
📋 查看投票详情
|
|
1870
1870
|
</div>
|
|
1871
1871
|
</div>
|
|
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,
|
|
1873
|
-
<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,ce=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 ${ce?"var(--primary)":"var(--border)"};">
|
|
1874
1874
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1875
1875
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1876
1876
|
<span style="font-weight: 500; color: var(--text-primary);">${F.text}</span>
|
|
1877
|
-
${
|
|
1877
|
+
${ce?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
|
|
1878
1878
|
</div>
|
|
1879
1879
|
<span style="font-weight: 600; color: var(--primary);">${F.votes.length} 票 (${X}%)</span>
|
|
1880
1880
|
</div>
|
|
@@ -1887,7 +1887,7 @@
|
|
|
1887
1887
|
</div>
|
|
1888
1888
|
`:""}
|
|
1889
1889
|
</div>
|
|
1890
|
-
`});const
|
|
1890
|
+
`});const L=`
|
|
1891
1891
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1892
1892
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
|
1893
1893
|
<div class="modal-header">
|
|
@@ -1965,7 +1965,7 @@
|
|
|
1965
1965
|
</div>
|
|
1966
1966
|
</div>
|
|
1967
1967
|
</div>
|
|
1968
|
-
`,z=document.getElementById("pollDetailModal");z&&z.remove(),document.body.insertAdjacentHTML("beforeend",
|
|
1968
|
+
`,z=document.getElementById("pollDetailModal");z&&z.remove(),document.body.insertAdjacentHTML("beforeend",L),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 ce=document.getElementById("contentArea");await _(ce)}}catch(X){console.error("投票失败:",X),alert("投票失败:"+X.message)}})}catch(I){console.error("加载投票详情失败:",I),alert("加载投票详情失败:"+I.message)}},o.innerHTML=`
|
|
1969
1969
|
<div class="dashboard">
|
|
1970
1970
|
<aside class="sidebar">
|
|
1971
1971
|
<div class="sidebar-header">
|
|
@@ -2015,7 +2015,7 @@
|
|
|
2015
2015
|
<div id="contentArea"></div>
|
|
2016
2016
|
</main>
|
|
2017
2017
|
</div>
|
|
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
|
|
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 I=B.dataset.view;$(I)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()});async function $(B){const I=document.getElementById("contentArea");switch(B){case"groups":await j(I);break;case"allgroups":await O(I);break;case"tasks":await _(I);break;case"documents":await U(I);break;case"files":await ae(I);break;case"chat":await q(I);break;case"search":await V(I);break;case"knowledge":await le(I);break}}async function j(B){S=(await n.getGroups()).groups,B.innerHTML=`
|
|
2019
2019
|
<div class="view-header">
|
|
2020
2020
|
<h2>我的群组</h2>
|
|
2021
2021
|
</div>
|
|
@@ -2032,12 +2032,12 @@
|
|
|
2032
2032
|
<button class="btn-select" data-id="${v._id}">进入群组</button>
|
|
2033
2033
|
<button class="btn-secondary" data-id="${v._id}" data-action="leave">退出群组</button>
|
|
2034
2034
|
</div>
|
|
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
|
|
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 O(B){const I=await n.getAllGroups(),v=(await n.getGroups()).groups.map(C=>C._id);B.innerHTML=`
|
|
2036
2036
|
<div class="view-header">
|
|
2037
2037
|
<h2>所有群组</h2>
|
|
2038
2038
|
</div>
|
|
2039
2039
|
<div class="groups-grid" id="allGroupsList"></div>
|
|
2040
|
-
`;const k=document.getElementById("allGroupsList")
|
|
2040
|
+
`;const k=document.getElementById("allGroupsList");I.groups.forEach(C=>{const D=v.includes(C._id),L=document.createElement("div");L.className="group-card",L.innerHTML=`
|
|
2041
2041
|
<h3>${C.name}</h3>
|
|
2042
2042
|
<p>${C.description||"暂无描述"}</p>
|
|
2043
2043
|
<div class="group-stats">
|
|
@@ -2045,12 +2045,12 @@
|
|
|
2045
2045
|
<span>📄 ${C.documents.length} 文档</span>
|
|
2046
2046
|
</div>
|
|
2047
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(
|
|
2048
|
+
`,k.appendChild(L)}),document.querySelectorAll('[data-action="join"]').forEach(C=>{C.addEventListener("click",async()=>{try{await n.joinGroup(C.dataset.id),alert("加入成功!"),await O(B)}catch(D){alert("加入失败: "+D.message)}})})}async function _(B){try{const I=await n.getMyTasks();let A=[];try{const C=(await n.getGroups()).groups;for(const D of C)try{const L=await n.getGroupPolls(D._id);L.polls&&Array.isArray(L.polls)&&(A=A.concat(L.polls.map(z=>({...z,groupName:D.name}))))}catch(L){console.error(`获取群组 ${D.name} 的投票失败:`,L)}}catch(k){console.error("获取投票失败:",k)}B.innerHTML=`
|
|
2049
2049
|
<div class="view-header">
|
|
2050
2050
|
<h2>我的任务</h2>
|
|
2051
2051
|
</div>
|
|
2052
2052
|
<div class="tasks-list" id="tasksList"></div>
|
|
2053
|
-
`;const v=document.getElementById("tasksList");if(
|
|
2053
|
+
`;const v=document.getElementById("tasksList");if(I.tasks.length===0&&A.length===0){v.innerHTML='<div class="empty-state">暂无任务</div>';return}I.tasks.forEach(k=>{const C=document.createElement("div");C.className=`task-card status-${k.status}`,C.innerHTML=`
|
|
2054
2054
|
<h3>${k.title}</h3>
|
|
2055
2055
|
<p>${k.description}</p>
|
|
2056
2056
|
<div class="task-meta">
|
|
@@ -2063,14 +2063,14 @@
|
|
|
2063
2063
|
${k.status==="pending"?`<button class="btn-primary btn-sm" data-id="${k._id}" data-action="start">开始任务</button>`:""}
|
|
2064
2064
|
${k.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${k._id}" data-action="complete">完成任务</button>`:""}
|
|
2065
2065
|
</div>
|
|
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)),
|
|
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)),L=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=`
|
|
2067
2067
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
2068
2068
|
<span style="font-size: 32px;">📊</span>
|
|
2069
2069
|
<h3 style="margin: 0;">${k.title}</h3>
|
|
2070
2070
|
</div>
|
|
2071
2071
|
<p>${k.description||"暂无描述"}</p>
|
|
2072
2072
|
<div class="task-meta">
|
|
2073
|
-
<span class="status-badge" style="background: ${
|
|
2073
|
+
<span class="status-badge" style="background: ${L?"var(--danger)":"var(--success)"};">${L?"已结束":"进行中"}</span>
|
|
2074
2074
|
<span>群组: ${k.groupName}</span>
|
|
2075
2075
|
<span>👥 ${C} 人投票</span>
|
|
2076
2076
|
${D?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
|
|
@@ -2078,17 +2078,17 @@
|
|
|
2078
2078
|
<div class="task-actions">
|
|
2079
2079
|
<button class="btn-primary btn-sm" data-poll-id="${k._id}" data-action="view-poll">查看详情</button>
|
|
2080
2080
|
</div>
|
|
2081
|
-
`,v.appendChild(z)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(k=>{k.addEventListener("click",async()=>{const C=k.dataset.id,
|
|
2081
|
+
`,v.appendChild(z)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(k=>{k.addEventListener("click",async()=>{const C=k.dataset.id,L=k.dataset.action==="start"?"in_progress":"completed";try{await n.updateTaskStatus(C,L),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(I){console.error("获取任务失败:",I),B.innerHTML=`
|
|
2082
2082
|
<div class="view-header">
|
|
2083
2083
|
<h2>我的任务</h2>
|
|
2084
2084
|
</div>
|
|
2085
|
-
<div class="empty-state">加载任务失败: ${
|
|
2086
|
-
`}}async function U(B){if(!d){B.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const
|
|
2085
|
+
<div class="empty-state">加载任务失败: ${I.message}</div>
|
|
2086
|
+
`}}async function U(B){if(!d){B.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const I=await n.getDocuments(d._id);B.innerHTML=`
|
|
2087
2087
|
<div class="view-header">
|
|
2088
2088
|
<h2>共享文档 - ${d.name}</h2>
|
|
2089
2089
|
</div>
|
|
2090
2090
|
<div class="documents-list" id="docsList"></div>
|
|
2091
|
-
`;const A=document.getElementById("docsList");if(
|
|
2091
|
+
`;const A=document.getElementById("docsList");if(I.documents.length===0){A.innerHTML='<div class="empty-state">暂无文档</div>';return}I.documents.forEach(v=>{const k=document.createElement("div");k.className="document-card",k.innerHTML=`
|
|
2092
2092
|
<h3>📄 ${v.title}</h3>
|
|
2093
2093
|
<div class="doc-meta">
|
|
2094
2094
|
<span>创建者: ${v.creator.username}</span>
|
|
@@ -2098,7 +2098,7 @@
|
|
|
2098
2098
|
<button class="btn-edit" data-id="${v._id}">
|
|
2099
2099
|
${v.permission==="readonly"?"查看":"编辑"}
|
|
2100
2100
|
</button>
|
|
2101
|
-
`,A.appendChild(k)}),document.querySelectorAll(".btn-edit").forEach(v=>{v.addEventListener("click",()=>{se(B,v.dataset.id)})})}async function se(B
|
|
2101
|
+
`,A.appendChild(k)}),document.querySelectorAll(".btn-edit").forEach(v=>{v.addEventListener("click",()=>{se(B,v.dataset.id)})})}async function se(B,I){const v=(await n.getDocument(I)).document;B.innerHTML=`
|
|
2102
2102
|
<div class="view-header">
|
|
2103
2103
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
2104
2104
|
<h2>${v.title}</h2>
|
|
@@ -2116,7 +2116,7 @@
|
|
|
2116
2116
|
<span>最后编辑: ${new Date(v.updatedAt).toLocaleString()}</span>
|
|
2117
2117
|
</div>
|
|
2118
2118
|
</div>
|
|
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(
|
|
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(I,a.username,!0),C=setTimeout(()=>{e.sendTyping(I,a.username,!1)},1e3),D=setTimeout(async()=>{const L=k.root.innerHTML;try{await n.updateDocument(I,L)}catch(z){console.error("自动保存失败:",z)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const L=k.root.innerHTML;await n.updateDocument(I,L),alert("保存成功!")}catch(L){alert("保存失败: "+L.message)}})}e.on("document_update",C=>{if(C.documentId===I&&C.userId!==a.id){const D=k.getSelection();k.root.innerHTML=C.content,D&&k.setSelection(D)}}),e.on("typing",C=>{if(C.documentId===I&&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 L=D.querySelector(`[data-user="${C.userId}"]`);L&&L.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{U(B)})}async function ae(B){if(!d){B.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const I=await n.getGroupFiles(d._id);B.innerHTML=`
|
|
2120
2120
|
<div class="view-header">
|
|
2121
2121
|
<h2>文件共享 - ${d.name}</h2>
|
|
2122
2122
|
<button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
|
|
@@ -2147,12 +2147,12 @@
|
|
|
2147
2147
|
</form>
|
|
2148
2148
|
</div>
|
|
2149
2149
|
</div>
|
|
2150
|
-
`;const A=document.getElementById("filesList")
|
|
2150
|
+
`;const A=document.getElementById("filesList");!I.files||I.files.length===0?A.innerHTML='<div class="empty-state">暂无文件</div>':(I.files.forEach(v=>{const k=document.createElement("div");k.className="file-card";const C=ie(v.mimetype),D=pe(v.size),L=v.uploader._id||v.uploader.id||v.uploader,z=String(L)===String(s);k.innerHTML=`
|
|
2151
2151
|
<div class="file-icon">${C}</div>
|
|
2152
2152
|
<div class="file-info">
|
|
2153
2153
|
<h4>${v.originalName}</h4>
|
|
2154
2154
|
<div class="file-meta">
|
|
2155
|
-
<span>上传者: ${v.uploader.username}</span>
|
|
2155
|
+
<span>上传者: ${v.uploader.username||"未知"}</span>
|
|
2156
2156
|
<span>大小: ${D}</span>
|
|
2157
2157
|
<span>时间: ${new Date(v.createdAt).toLocaleString()}</span>
|
|
2158
2158
|
</div>
|
|
@@ -2160,14 +2160,14 @@
|
|
|
2160
2160
|
</div>
|
|
2161
2161
|
<div class="file-actions">
|
|
2162
2162
|
<a href="${n.getFileDownloadUrl(v._id)}" class="btn-primary" download>下载</a>
|
|
2163
|
-
${
|
|
2163
|
+
${z?`<button class="btn-danger" data-id="${v._id}" data-action="delete-file">删除</button>`:""}
|
|
2164
2164
|
</div>
|
|
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(
|
|
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(I){console.error("获取文件列表失败:",I),B.innerHTML=`
|
|
2166
2166
|
<div class="view-header">
|
|
2167
2167
|
<h2>文件共享</h2>
|
|
2168
2168
|
</div>
|
|
2169
|
-
<div class="empty-state">加载文件失败: ${
|
|
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
|
|
2169
|
+
<div class="empty-state">加载文件失败: ${I.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 pe(B){if(B===0)return"0 Bytes";const I=1024,A=["Bytes","KB","MB","GB"],v=Math.floor(Math.log(B)/Math.log(I));return Math.round(B/Math.pow(I,v)*100)/100+" "+A[v]}async function q(B){if(!d){B.innerHTML=`
|
|
2171
2171
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2172
2172
|
<div style="font-size: 64px; margin-bottom: 20px;">💬</div>
|
|
2173
2173
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">群聊</h3>
|
|
@@ -2176,13 +2176,13 @@
|
|
|
2176
2176
|
前往我的群组
|
|
2177
2177
|
</button>
|
|
2178
2178
|
</div>
|
|
2179
|
-
`;return}try{let
|
|
2179
|
+
`;return}try{let ce=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 T=await fetch(w).then(K=>K.blob()),x=new FormData;x.append("file",T,`whiteboard-${Date.now()}.png`),x.append("groupId",d._id),x.append("description","协作白板作品");const R=localStorage.getItem("token"),N=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${R}`},body:x});if(N.ok){const J=`http://localhost:3000/api/files/${(await N.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 I=ce,A=g;const k=(await n.getGroup(d._id)).group,C=!!k.mutedAll,D=(k.mutedUsers||[]).map(String).includes(String(s)),L=!C&&!D;B.innerHTML=`
|
|
2180
2180
|
<div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
|
|
2181
2181
|
<h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
|
|
2182
2182
|
<span style="font-size: 32px;">💬</span>
|
|
2183
2183
|
<span>群聊 - ${d.name}</span>
|
|
2184
2184
|
</h2>
|
|
2185
|
-
${
|
|
2185
|
+
${L?"":`
|
|
2186
2186
|
<div style="margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.2); border-radius: 8px; font-size: 14px;">
|
|
2187
2187
|
⚠️ ${C?"全体禁言中,无法发言":"你已被禁言"}
|
|
2188
2188
|
</div>
|
|
@@ -2207,9 +2207,9 @@
|
|
|
2207
2207
|
<div class="chat-container" style="display: flex; flex-direction: column; height: calc(100vh - 350px); background: var(--bg-secondary); border-radius: 12px; overflow: hidden;">
|
|
2208
2208
|
<div class="messages" id="messages" style="flex: 1; overflow-y: auto; padding: 20px;"></div>
|
|
2209
2209
|
<div class="chat-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
|
|
2210
|
-
<button class="btn-emoji" id="emojiBtn" ${
|
|
2211
|
-
<input type="text" id="messageInput" placeholder="${
|
|
2212
|
-
<button class="btn-primary" id="sendBtn" ${
|
|
2210
|
+
<button class="btn-emoji" id="emojiBtn" ${L?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${L?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
|
|
2211
|
+
<input type="text" id="messageInput" placeholder="${L?"输入消息...":C?"全体禁言中,无法发言":"你已被禁言"}" ${L?"":"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" ${L?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${L?"pointer":"not-allowed"};">发送</button>
|
|
2213
2213
|
</div>
|
|
2214
2214
|
<emoji-picker id="emojiPicker" class="hidden" style="position: absolute; bottom: 80px; left: 20px; z-index: 1000;"></emoji-picker>
|
|
2215
2215
|
</div>
|
|
@@ -2251,7 +2251,7 @@
|
|
|
2251
2251
|
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
2252
2252
|
<p>还没有消息,开始聊天吧!</p>
|
|
2253
2253
|
</div>
|
|
2254
|
-
`:(i.messages.forEach(t=>{const r=document.createElement("div"),l=String(t.sender)===String(s)||t.username===a.username,b=
|
|
2254
|
+
`:(i.messages.forEach(t=>{const r=document.createElement("div"),l=String(t.sender)===String(s)||t.username===a.username,b=H(t.content),E=t.content.startsWith("[白板作品]")||t.content.startsWith("[投票]"),w=t.content.startsWith("[白板作品]");r.className=`message ${l?"own":""}`,r.style.cssText=`
|
|
2255
2255
|
margin-bottom: 16px;
|
|
2256
2256
|
display: flex;
|
|
2257
2257
|
flex-direction: column;
|
|
@@ -2268,18 +2268,18 @@
|
|
|
2268
2268
|
<p>加载历史消息失败</p>
|
|
2269
2269
|
<p style="font-size: 14px; color: var(--text-tertiary);">${i.message}</p>
|
|
2270
2270
|
</div>
|
|
2271
|
-
`}const re=document.getElementById("emojiBtn"),X=document.getElementById("emojiPicker");
|
|
2271
|
+
`}const re=document.getElementById("emojiBtn"),X=document.getElementById("emojiPicker");L&&(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=`
|
|
2272
2272
|
margin-bottom: 16px;
|
|
2273
2273
|
display: flex;
|
|
2274
2274
|
flex-direction: column;
|
|
2275
2275
|
align-items: ${r?"flex-end":"flex-start"};
|
|
2276
|
-
`;const l=
|
|
2276
|
+
`;const l=H(i.content),b=i.content.startsWith("[白板作品]")||i.content.startsWith("[投票]"),E=i.content.startsWith("[白板作品]");t.innerHTML=`
|
|
2277
2277
|
<div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
|
|
2278
2278
|
<span class="message-user">${i.username}</span>
|
|
2279
2279
|
<span class="message-time">${new Date(i.timestamp).toLocaleTimeString("zh-CN")}</span>
|
|
2280
2280
|
</div>
|
|
2281
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||
|
|
2282
|
+
`,z.appendChild(t),z.scrollTop=z.scrollHeight,r||ce(`${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=`
|
|
2283
2283
|
text-align: center;
|
|
2284
2284
|
padding: 12px;
|
|
2285
2285
|
margin: 16px auto;
|
|
@@ -2295,7 +2295,7 @@
|
|
|
2295
2295
|
color: white;
|
|
2296
2296
|
border-radius: 8px;
|
|
2297
2297
|
max-width: 80%;
|
|
2298
|
-
`,t.textContent=`${i.username} 已响应点名`,z.appendChild(t),z.scrollTop=z.scrollHeight}});const
|
|
2298
|
+
`,t.textContent=`${i.username} 已响应点名`,z.appendChild(t),z.scrollTop=z.scrollHeight}});const ue=()=>{if(!L){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)}};L&&(F.addEventListener("click",ue),W.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),ue())}));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=`
|
|
2299
2299
|
background: var(--primary);
|
|
2300
2300
|
color: white;
|
|
2301
2301
|
padding: 12px 16px;
|
|
@@ -2357,7 +2357,7 @@
|
|
|
2357
2357
|
</div>
|
|
2358
2358
|
<div class="search-results" id="searchResults"></div>
|
|
2359
2359
|
</div>
|
|
2360
|
-
`;const
|
|
2360
|
+
`;const I=document.getElementById("searchInput"),A=document.getElementById("searchBtn"),v=document.getElementById("searchResults"),k=async()=>{const C=I.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 L=[];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=>{L.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=>{L.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=>{L.push({type:"task",title:F.title,content:F.description||"",time:F.updatedAt,id:F._id,status:F.status})})}catch(z){console.error("搜索任务失败:",z)}L.length===0?v.innerHTML='<div class="empty-state">未找到相关结果</div>':v.innerHTML=L.map(z=>`
|
|
2361
2361
|
<div class="search-result-item">
|
|
2362
2362
|
<div class="result-header">
|
|
2363
2363
|
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[z.type]} ${z.type==="message"?"消息":z.type==="document"?"文档":"任务"}</span>
|
|
@@ -2368,7 +2368,7 @@
|
|
|
2368
2368
|
${z.group?`<span class="result-group">群组: ${z.group}</span>`:""}
|
|
2369
2369
|
${z.status?`<span class="result-status">状态: ${oe(z.status)}</span>`:""}
|
|
2370
2370
|
</div>
|
|
2371
|
-
`).join("")}catch(
|
|
2371
|
+
`).join("")}catch(L){v.innerHTML=`<div class="empty-state">搜索失败: ${L.message}</div>`}};A.addEventListener("click",k),I.addEventListener("keypress",C=>{C.key==="Enter"&&k()})}function ne(B,I){if(!I)return B;const A=new RegExp(`(${I})`,"gi");return B.replace(A,"<mark>$1</mark>")}function oe(B){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[B]||B}async function le(B){if(!d){B.innerHTML=`
|
|
2372
2372
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2373
2373
|
<div style="font-size: 64px; margin-bottom: 20px;">📚</div>
|
|
2374
2374
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">知识库</h3>
|
|
@@ -2377,7 +2377,7 @@
|
|
|
2377
2377
|
前往我的群组
|
|
2378
2378
|
</button>
|
|
2379
2379
|
</div>
|
|
2380
|
-
`;return}try{const
|
|
2380
|
+
`;return}try{const I=localStorage.getItem("token"),A=await fetch(`http://localhost:3000/api/knowledge/group/${d._id}`,{headers:{Authorization:`Bearer ${I}`}});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=`
|
|
2381
2381
|
<div class="view-header">
|
|
2382
2382
|
<h2>📚 知识库 - ${d.name}</h2>
|
|
2383
2383
|
<button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
|
|
@@ -2421,7 +2421,7 @@
|
|
|
2421
2421
|
</form>
|
|
2422
2422
|
</div>
|
|
2423
2423
|
</div>
|
|
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
|
|
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 L=document.createElement("div");L.className="knowledge-card",L.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;",L.innerHTML=`
|
|
2425
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>':""}
|
|
2426
2426
|
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${D.isShared?"padding-right: 80px;":""}">${D.title}</h3>
|
|
2427
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>
|
|
@@ -2438,14 +2438,14 @@
|
|
|
2438
2438
|
<button class="btn-secondary btn-sm" data-id="${D._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
2439
2439
|
<button class="btn-danger btn-sm" data-id="${D._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
2440
2440
|
</div>
|
|
2441
|
-
`,
|
|
2441
|
+
`,L.onmouseenter=()=>{L.style.transform="translateY(-4px)",L.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},L.onmouseleave=()=>{L.style.transform="translateY(0)",L.style.boxShadow="none"},C.appendChild(L)}),document.querySelectorAll('[data-action="edit"]').forEach(D=>{D.addEventListener("click",async()=>{var z;const L=k.find(W=>W._id===D.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=L.title,document.querySelector('[name="content"]').value=L.content,document.querySelector('[name="tags"]').value=((z=L.tags)==null?void 0:z.join(", "))||"",document.getElementById("isSharedCheckbox").checked=L.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=L._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 ${I}`}}),alert("删除成功!"),await le(B)}catch(L){alert("删除失败: "+L.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 L=new FormData(D.target),z={title:L.get("title"),content:L.get("content"),tags:L.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 ${I}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await le(B)}catch(W){alert("操作失败: "+W.message)}})}catch(I){console.error("加载知识库失败:",I),B.innerHTML=`
|
|
2442
2442
|
<div class="view-header">
|
|
2443
2443
|
<h2>📚 知识库 - ${d.name}</h2>
|
|
2444
2444
|
</div>
|
|
2445
2445
|
<div class="empty-state" style="text-align: center; padding: 40px 20px;">
|
|
2446
2446
|
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
2447
2447
|
<h3 style="margin-bottom: 8px; color: var(--danger);">加载失败</h3>
|
|
2448
|
-
<p style="color: var(--text-secondary); margin-bottom: 16px;">${
|
|
2448
|
+
<p style="color: var(--text-secondary); margin-bottom: 16px;">${I.message}</p>
|
|
2449
2449
|
<button class="btn-primary" onclick="location.reload()">重新加载</button>
|
|
2450
2450
|
</div>
|
|
2451
|
-
`}}
|
|
2451
|
+
`}}$("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;
|