collabdocchat 2.5.9 → 2.5.11
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-Dkrhl719.js → index-Vc_T-zsG.js} +78 -78
- 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/server/routes/polls.js +62 -5
- package/src/pages/admin-dashboard.js +25 -8
|
@@ -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,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=`
|
|
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",Nt="unicode",yt="count",Ot="group",Ht="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,Nt,{[mt]:[Ft,!0],[bt]:[[Ot,Ht]],[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:ie,version:ae}of S)_.skinTones.push(U),_.skinUnicodes.push(ie),_.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 ie of _.shortcodes)for(const ae of xe(ie))U.add(ae);return U}),s=_=>c(_,!0),d=_=>c(_,!1),S=_=>{const U=xe(_),ie=U.map((ae,re)=>(re<U.length-1?s:d)(ae));return kt(ie,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 Ne(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 Na(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 Oa=typeof ResizeObserver=="function";function Ha(a,e,o){let n;Oa?(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 Oe;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 Oe>"u"&&(Oe=dt(e));const S=d/1.8<Oe;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 ie,ae,re;if(n){const q=/(\S+)="?([^"=]*)$/.exec(O);ie=q[1],ae=q[2];const V=/^([^">]*)("?)/.exec(a[$+1]);re=V[1],e=e.slice(0,-1*q[0].length),S=V[0].length}else S=0;const ue={attributeName:ie,attributeValuePre:ae,attributeValuePost:re,expressionIndex:$};U.push(ue),!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 ie(q,V,oe){return U(q,(se,ce)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?ce===e.activeSearchItem:null}" aria-label="${$(se,e.currentSkinTone)}" title="${j(se)}" class="${"emoji"+(V&&ce===e.activeSearchItem?" active":"")+(se.unicode?"":" custom-emoji")}" id="${`${oe}-${se.id}`}" style="${se.unicode?null:`--custom-emoji-background: url(${JSON.stringify(se.url)})`}">${se.unicode?O(se,e.currentSkinTone):""}</button>`,se=>`${oe}-${se.id}`)}const re=_`<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}">${ie(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">${ie(e.currentFavorites,!1,"fav")}</div><button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">😀</button></section>`,ue=(q,V)=>{for(const oe of a.querySelectorAll(`[${q}]`))V(oe,oe.getAttribute(q))};if(H){a.appendChild(re);for(const q of["click","focusout","input","keydown","keyup"])ue(`data-on-${q}`,(V,oe)=>{V.addEventListener(q,n[oe])});ue("data-ref",(q,V)=>{s[V]=q}),d.addEventListener("abort",()=>{a.removeChild(re)})}ue("data-action",(q,V)=>{let oe=S.get(V);oe||S.set(V,oe=new WeakSet),oe.has(q)||(oe.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 He(a,e,o){if(a.length!==e.length)return!1;for(let n=0;n<a.length;n++)if(!o(a[n],e[n]))return!1;return!0}const 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:He(b,w,O)},U=t=>{He(s.currentEmojis,t,O)||(s.currentEmojis=t)},ie=t=>{s.searchMode!==t&&(s.searchMode=t)},ae=t=>{He(s.currentEmojisWithCategories,t,_)||(s.currentEmojisWithCategories=t)},re=(t,r)=>r&&t.skins&&t.skins[r]||t.unicode,V={labelWithSkin:(t,r)=>Ua([t.name||re(t,r),t.annotation,...t.shortcodes||qe].filter(Boolean)).join(", "),titleForEmoji:t=>t.annotation||(t.shortcodes||qe).join(", "),unicodeWithSkin:re},oe={onClickSkinToneButton:y,onEmojiClick:m,onNavClick:de,onNavKeydown:Z,onSearchKeydown:N,onSkinToneOptionsClick:g,onSkinToneOptionsFocusOut:p,onSkinToneOptionsKeydown:h,onSkinToneOptionsKeyup:M,onSearchInput:i},se={calculateEmojiGridStyle:I,updateOnIntersection:A};let ce=!0;d(()=>{on(a,s,V,oe,se,o,c,S,ce),ce=!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 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=`
|
|
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],F=>F.unicode||F.name).slice(0,b));s.currentFavorites=w}s.databaseLoaded&&s.defaultFavoriteEmojis&&t()});function I(t){Ha(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),ie(!0))}else{const{id:w}=l;if(w!==-1||E&&E.length){const F=await z(w);s.currentGroup.id===w&&(U(F),ie(!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 Na(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 F=w.category||"";let G=E.get(F);G||(G=[],E.set(F,G)),G.push(w)}return[...E.entries()].map(([w,F])=>({category:w,emojis:F})).sort((w,F)=>s.customCategorySorting(w.category,F.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 N(t){if(!s.searchMode||!s.currentEmojis.length)return;const r=l=>{ye(t),s.activeSearchItem=Ne(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),ge(s.currentEmojis[s.activeSearchItem].id)}}function de(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 Z(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 pe(t){const r=await s.database.getEmojiByUnicodeOrName(t),l=[...s.currentEmojis,...s.currentFavorites].find(E=>E.id===t),b=l.unicode&&re(l,s.currentSkinTone);return await s.database.incrementFavoriteEmojiCount(t),{emoji:r,skinTone:s.currentSkinTone,...b&&{unicode:b},...l.name&&{name:l.name}}}async function ge(t){const r=pe(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);ge(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(Ne(!0,s.activeSkinTone,s.skinTones));case"ArrowDown":return r(Ne(!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;ge(u)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{c.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{ge("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{ge("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>
|
|
@@ -319,9 +319,9 @@
|
|
|
319
319
|
${p.assignedTo&&p.assignedTo.length>0?`<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">${p.assignedTo.length} 人</span>`:""}
|
|
320
320
|
</div>
|
|
321
321
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
322
|
-
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(l=>{var w,
|
|
322
|
+
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(l=>{var w,F;const b=p.completedBy&&p.completedBy.some(G=>G.user&&G.user._id===l._id),E=b?p.completedBy.find(G=>G.user&&G.user._id===l._id):null;return`
|
|
323
323
|
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${b?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${b?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${b?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
|
|
324
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${b?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${b?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((
|
|
324
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${b?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${b?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((F=(w=l.username)==null?void 0:w[0])==null?void 0:F.toUpperCase())||"?"}</div>
|
|
325
325
|
<div style="display: flex; flex-direction: column; gap: 1px;">
|
|
326
326
|
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${l.username||"未知用户"}</span>
|
|
327
327
|
${b?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${E&&E.completedAt?new Date(E.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
|
|
@@ -430,10 +430,10 @@
|
|
|
430
430
|
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
431
431
|
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${E.length}人):</div>
|
|
432
432
|
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
|
433
|
-
${E.map(w=>{const
|
|
433
|
+
${E.map(w=>{const F=typeof w=="string"?w:w.username;return`
|
|
434
434
|
<span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 10px; font-size: 11px;">
|
|
435
|
-
<span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${
|
|
436
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
435
|
+
<span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${F?F[0].toUpperCase():"?"}</span>
|
|
436
|
+
<span style="font-weight: 600; color: var(--text-primary);">${F||"未知用户"}</span>
|
|
437
437
|
</span>
|
|
438
438
|
`}).join("")}
|
|
439
439
|
</div>
|
|
@@ -546,7 +546,7 @@
|
|
|
546
546
|
<button class="btn-danger btn-sm" data-id="${p._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
|
|
547
547
|
</div>
|
|
548
548
|
</div>
|
|
549
|
-
`,h.appendChild(i)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(p=>{p.addEventListener("click",()=>{
|
|
549
|
+
`,h.appendChild(i)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(p=>{p.addEventListener("click",()=>{ie(m,p.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(p=>{p.addEventListener("click",async()=>{const i=p.dataset.id,t=u.documents.find(r=>r._id===i);M(t,y)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(p=>{p.addEventListener("click",async i=>{i.stopPropagation();const t=p.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await n.deleteDocument(t),alert("文档删除成功!"),await U(m)}catch(r){console.error("删除文档错误:",r),alert("删除失败: "+(r.message||"未知错误"))}})})),document.getElementById("createDocBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.remove("hidden")}),document.getElementById("closeDocModal").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")}),document.getElementById("closeDocModalBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")});function M(p,i){const t=document.getElementById("editPermissionModal"),r=document.getElementById("permissionContent");r.innerHTML=`
|
|
550
550
|
<div style="margin-bottom: 16px;">
|
|
551
551
|
<h4 style="margin: 0 0 8px 0;">📄 ${p.title}</h4>
|
|
552
552
|
<p style="font-size: 13px; color: var(--text-secondary); margin: 0;">管理哪些成员可以编辑此文档</p>
|
|
@@ -570,7 +570,7 @@
|
|
|
570
570
|
<button type="button" class="btn-secondary" id="cancelPermissionBtn" style="flex: 1;">取消</button>
|
|
571
571
|
</div>
|
|
572
572
|
</form>
|
|
573
|
-
`,t.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{t.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async l=>{l.preventDefault();const E=new FormData(l.target).getAll("editableMembers");try{await n.updateDocumentPermissions(p._id,E),alert("权限更新成功!"),t.classList.add("hidden"),await U(m)}catch(w){console.error("更新权限错误:",w),alert("更新失败: "+w.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async p=>{p.preventDefault();const i=new FormData(p.target),t=i.getAll("editableMembers");try{await n.createDocument(i.get("title"),i.get("content"),d._id,t),alert("文档创建成功!"),await U(m),document.getElementById("createDocModal").classList.add("hidden")}catch(r){console.error("创建文档错误:",r),alert("创建失败: "+r.message)}})}async function
|
|
573
|
+
`,t.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{t.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async l=>{l.preventDefault();const E=new FormData(l.target).getAll("editableMembers");try{await n.updateDocumentPermissions(p._id,E),alert("权限更新成功!"),t.classList.add("hidden"),await U(m)}catch(w){console.error("更新权限错误:",w),alert("更新失败: "+w.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async p=>{p.preventDefault();const i=new FormData(p.target),t=i.getAll("editableMembers");try{await n.createDocument(i.get("title"),i.get("content"),d._id,t),alert("文档创建成功!"),await U(m),document.getElementById("createDocModal").classList.add("hidden")}catch(r){console.error("创建文档错误:",r),alert("创建失败: "+r.message)}})}async function ie(m,u){const y=(await n.getDocument(u)).document;m.innerHTML=`
|
|
574
574
|
<div class="view-header">
|
|
575
575
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
576
576
|
<h2>${y.title}</h2>
|
|
@@ -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=
|
|
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=re(y.mimetype),p=ue(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
|
|
642
|
+
`}}function re(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 ue(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 P=localStorage.getItem("token");R=T.includes("?")?`${T}&token=${P}`:`${T}?token=${P}`}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>
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
📋 查看投票详情
|
|
674
674
|
</div>
|
|
675
675
|
</div>
|
|
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
|
|
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 P=(await x.json()).poll;console.log("投票详情数据:",P),console.log("创建者信息:",P.creatorName,P.creator),P.options.forEach((X,le)=>{console.log(`选项 ${le} (${X.text}) 的投票记录:`,X.votes)});const K=P.options.reduce((X,le)=>X+le.votes.length,0),Q=P.status==="ended"||P.endTime&&new Date(P.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,18 +681,18 @@
|
|
|
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;">${P.title}</h2>
|
|
685
|
+
${P.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${P.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
|
+
${P.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
690
690
|
</span>
|
|
691
691
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
692
|
-
${
|
|
692
|
+
${P.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
693
693
|
</span>
|
|
694
|
-
<span style="font-size: 13px; padding: 6px 12px; background: ${
|
|
695
|
-
${
|
|
694
|
+
<span style="font-size: 13px; padding: 6px 12px; background: ${Q?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
695
|
+
${Q?"已结束":"进行中"}
|
|
696
696
|
</span>
|
|
697
697
|
</div>
|
|
698
698
|
|
|
@@ -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;">👤 ${P.creatorName||P.creator&&P.creator.username||"未知用户"}</div>
|
|
704
704
|
</div>
|
|
705
705
|
<div>
|
|
706
706
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
@@ -708,27 +708,27 @@
|
|
|
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(P.createdAt).toLocaleString("zh-CN")}</div>
|
|
712
712
|
</div>
|
|
713
|
-
${
|
|
713
|
+
${P.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(P.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
|
+
${P.options.map((X,le)=>{const Y=K>0?(X.votes.length/K*100).toFixed(1):0;let ee="";if(!P.anonymous&&X.votes.length>0){const me=X.votes.map(ne=>{if(ne&&typeof ne=="object"){if(ne.username&&typeof ne.username=="string"&&ne.username.trim())return ne.username.trim();if(ne.user&&typeof ne.user=="object"&&ne.user.username&&typeof ne.user.username=="string")return ne.user.username.trim()}return null}).filter(ne=>ne!==null&&ne!=="未知用户"&&ne.trim()!=="");me.length>0?ee=`
|
|
724
724
|
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
725
725
|
<strong style="color: var(--text-primary);">👥 投票者:</strong> ${me.join(", ")}
|
|
726
726
|
</div>
|
|
727
|
-
|
|
727
|
+
`:console.warn("投票者信息缺失:",X.votes)}return`
|
|
728
728
|
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
729
729
|
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
|
730
|
-
<span style="font-weight: 500; font-size: 16px;">${
|
|
731
|
-
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${
|
|
730
|
+
<span style="font-weight: 500; font-size: 16px;">${X.text}</span>
|
|
731
|
+
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${X.votes.length} 票 (${Y}%)</span>
|
|
732
732
|
</div>
|
|
733
733
|
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden;">
|
|
734
734
|
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${Y}%; transition: width 0.3s;"></div>
|
|
@@ -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(T=>{const x=document.createElement("div");x.className=`message ${T.sender===s?"own":""}`;const R=y(T.content),
|
|
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),P=T.content.startsWith("[白板作品]")||T.content.startsWith("[投票]"),K=T.content.startsWith("[白板作品]");x.innerHTML=`
|
|
941
941
|
<div class="message-header">
|
|
942
942
|
<span class="message-user">${T.username}</span>
|
|
943
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);":P?"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 P=localStorage.getItem("token"),K=await fetch(`http://localhost:3000/api/messages/group/${d._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${P}`,"Content-Type":"application/json"}});if(!K.ok){const J=await K.json();throw new Error(J.message||"清除失败")}const Q=await K.json();h.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Q.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>
|
|
@@ -960,13 +960,13 @@
|
|
|
960
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
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 T=document.getElementById("pollTitle").value.trim(),x=document.getElementById("pollDescription").value.trim(),R=document.getElementById("allowMultiple").checked,
|
|
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,P=document.getElementById("anonymous").checked,K=document.getElementById("pollEndTime").value,Q=document.querySelectorAll(".poll-option"),J=Array.from(Q).map(te=>te.value.trim()).filter(te=>te);if(J.length<2){alert("至少需要2个选项!");return}try{const te=localStorage.getItem("token"),X=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:P,endTime:K||null})});if(!X.ok){const ee=await X.json();throw new Error(ee.error||"创建投票失败")}const Y=(await X.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("[投票]"),P=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(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
|
|
968
|
+
<div class="message-content" style="${P?"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 F=document.getElementById("aiSendBtnModal");F.addEventListener("mouseenter",()=>{F.style.transform="scale(1.05)"}),F.addEventListener("mouseleave",()=>{F.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 P=document.createElement("div");P.className="ai-message ai loading",P.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",P.textContent="思考中...",x.appendChild(P),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();P.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){P.remove();const Q=document.createElement("div");Q.className="ai-message ai error",Q.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Q.textContent="抱歉,发生了错误: "+K.message,x.appendChild(Q),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(),Q=window.URL.createObjectURL(K),J=document.createElement("a");J.href=Q,J.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(J),J.click(),document.body.removeChild(J),window.URL.revokeObjectURL(Q)}catch(P){console.error("下载失败:",P),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",P="#000000",K=3,Q=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=>{P=Y.target.value});const X=document.getElementById("brushSizeCanvas"),le=document.getElementById("brushSizeLabel");X&&le&&(X.oninput=Y=>{K=Y.target.value,le.textContent=`大小: ${K}`}),f.onmousedown=Y=>{x=!0;const ee=f.getBoundingClientRect();Q=Y.clientX-ee.left,J=Y.clientY-ee.top},f.onmousemove=Y=>{if(!x)return;const ee=f.getBoundingClientRect(),me=Y.clientX-ee.left,ne=Y.clientY-ee.top;T.beginPath(),T.moveTo(Q,J),T.lineTo(me,ne),T.strokeStyle=R==="eraser"?"#ffffff":P,T.lineWidth=K,T.lineCap="round",T.stroke(),Q=me,J=ne},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 ne=localStorage.getItem("token"),et=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ne}`},body:me});if(et.ok){const Dt=`http://localhost:3000/api/files/${(await et.json()).file._id}/download?token=${ne}`;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>
|
|
@@ -988,7 +988,7 @@
|
|
|
988
988
|
</div>
|
|
989
989
|
`).join("")}
|
|
990
990
|
</div>
|
|
991
|
-
`,Array.isArray(g.calledMembers)&&g.calledMembers.length>0&&e&&d&&a){const h=g.calledMembers.map(p=>p.username).join("、"),M=`🎲 本次随机点名(${g.calledMembers.length} 人):${h}`;try{e.sendChatMessage(d._id,a.username,M)}catch(p){console.error("发送点名结果到群聊失败:",p)}}}catch(g){alert("点名失败: "+g.message)}})}async function
|
|
991
|
+
`,Array.isArray(g.calledMembers)&&g.calledMembers.length>0&&e&&d&&a){const h=g.calledMembers.map(p=>p.username).join("、"),M=`🎲 本次随机点名(${g.calledMembers.length} 人):${h}`;try{e.sendChatMessage(d._id,a.username,M)}catch(p){console.error("发送点名结果到群聊失败:",p)}}}catch(g){alert("点名失败: "+g.message)}})}async function oe(m){m.innerHTML=`
|
|
992
992
|
<div class="view-header">
|
|
993
993
|
<h2>操作记录</h2>
|
|
994
994
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1061,7 +1061,7 @@
|
|
|
1061
1061
|
<div>资源</div>
|
|
1062
1062
|
<div>详情</div>
|
|
1063
1063
|
</div>
|
|
1064
|
-
${E.logs.map(G=>{var f,T,x,R,
|
|
1064
|
+
${E.logs.map(G=>{var f,T,x,R,P;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">
|
|
@@ -1072,11 +1072,11 @@
|
|
|
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">${((P=G.details)==null?void 0:P.description)||"-"}</div>
|
|
1076
1076
|
</div>
|
|
1077
1077
|
`}).join("")}
|
|
1078
1078
|
</div>
|
|
1079
|
-
`;const w=document.getElementById("auditPagination"),
|
|
1079
|
+
`;const w=document.getElementById("auditPagination"),F=document.getElementById("pageInfo");F.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,F,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>',(F=t.details)!=null&&F.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,Q)=>K+Q.length,0))||0,P=((f=t.changes.deletions)==null?void 0:f.reduce((K,Q)=>K+Q.length,0))||0;(R>0||P>0)&&(E+=' (<span style="color: #10b981;">+'+R+'</span> / <span style="color: #ef4444;">-'+P+"</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,F;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"),P=document.getElementById("auditDetailContent"),K=te=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[te]||"#6366f1";P.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
|
|
1165
|
+
`,document.getElementById("auditDescriptionText").innerHTML=M(x);const Q=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?(Q.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||((F=x.changes.deletions)==null?void 0:F.length)>0)){const te=document.getElementById("auditChangesContent");let X="";if(x.changes.insertions&&x.changes.insertions.length>0){const le=x.changes.insertions.reduce((Y,ee)=>Y+ee.length,0);X+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',X+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',X+='<div style="color: var(--text-secondary);">共 '+le+" 个字符</div>",X+="</div>"}if(x.changes.deletions&&x.changes.deletions.length>0){const le=x.changes.deletions.reduce((Y,ee)=>Y+ee.length,0);X+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',X+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',X+='<div style="color: var(--text-secondary);">共 '+le+" 个字符</div>",X+="</div>"}te.innerHTML=X,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 se(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 ce(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 se(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 se(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 ce(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">
|
|
@@ -1383,7 +1383,7 @@
|
|
|
1383
1383
|
<button class="btn-secondary btn-sm" data-id="${i._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
1384
1384
|
<button class="btn-danger btn-sm" data-id="${i._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1385
1385
|
</div>
|
|
1386
|
-
`,t.onmouseenter=()=>{t.style.transform="translateY(-4px)",t.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},t.onmouseleave=()=>{t.style.transform="translateY(0)",t.style.boxShadow="none"},p.appendChild(t)}),document.querySelectorAll('[data-action="edit"]').forEach(i=>{i.addEventListener("click",async()=>{var r;const t=M.find(l=>l._id===i.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=t.title,document.querySelector('[name="content"]').value=t.content,document.querySelector('[name="tags"]').value=((r=t.tags)==null?void 0:r.join(", "))||"",document.getElementById("isSharedCheckbox").checked=t.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=t._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(i=>{i.addEventListener("click",async()=>{try{const t=await fetch(`http://localhost:3000/api/backup/download/${i.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${g}`}});if(!t.ok)throw new Error("下载失败");const r=await t.blob(),l=window.URL.createObjectURL(r),b=document.createElement("a");b.href=l,b.download=i.dataset.filename,document.body.appendChild(b),b.click(),window.URL.revokeObjectURL(l),document.body.removeChild(b)}catch(t){alert("下载失败: "+t.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(i=>{i.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${i.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${g}`}}),alert("删除成功!"),await k(m)}catch(t){alert("删除失败: "+t.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async i=>{i.preventDefault();const t=new FormData(i.target),r={title:t.get("title"),content:t.get("content"),tags:t.get("tags").split(",").map(l=>l.trim()).filter(l=>l),groupId:d._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const l=i.target.dataset.editId,b=l?`http://localhost:3000/api/knowledge/${l}`:"http://localhost:3000/api/knowledge",w=await fetch(b,{method:l?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`},body:JSON.stringify(r)});if(!w.ok){const G=await w.json();throw new Error(G.message||"操作失败")}const
|
|
1386
|
+
`,t.onmouseenter=()=>{t.style.transform="translateY(-4px)",t.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},t.onmouseleave=()=>{t.style.transform="translateY(0)",t.style.boxShadow="none"},p.appendChild(t)}),document.querySelectorAll('[data-action="edit"]').forEach(i=>{i.addEventListener("click",async()=>{var r;const t=M.find(l=>l._id===i.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=t.title,document.querySelector('[name="content"]').value=t.content,document.querySelector('[name="tags"]').value=((r=t.tags)==null?void 0:r.join(", "))||"",document.getElementById("isSharedCheckbox").checked=t.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=t._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(i=>{i.addEventListener("click",async()=>{try{const t=await fetch(`http://localhost:3000/api/backup/download/${i.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${g}`}});if(!t.ok)throw new Error("下载失败");const r=await t.blob(),l=window.URL.createObjectURL(r),b=document.createElement("a");b.href=l,b.download=i.dataset.filename,document.body.appendChild(b),b.click(),window.URL.revokeObjectURL(l),document.body.removeChild(b)}catch(t){alert("下载失败: "+t.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(i=>{i.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${i.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${g}`}}),alert("删除成功!"),await k(m)}catch(t){alert("删除失败: "+t.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async i=>{i.preventDefault();const t=new FormData(i.target),r={title:t.get("title"),content:t.get("content"),tags:t.get("tags").split(",").map(l=>l.trim()).filter(l=>l),groupId:d._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const l=i.target.dataset.editId,b=l?`http://localhost:3000/api/knowledge/${l}`:"http://localhost:3000/api/knowledge",w=await fetch(b,{method:l?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`},body:JSON.stringify(r)});if(!w.ok){const G=await w.json();throw new Error(G.message||"操作失败")}const F=await w.json();console.log("知识库操作结果:",F),alert(l?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await k(m)}catch(l){console.error("知识库操作错误:",l),alert("操作失败: "+l.message)}})}catch(g){m.innerHTML=`<div class="empty-state">加载失败: ${g.message}</div>`}}function C(m){if(!m)return"未设置";if(typeof m=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[m]||m;const u=[];if(m.event){const g={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};u.push(g[m.event]||m.event)}if(m.conditions&&Object.keys(m.conditions).length>0){const g=[];for(const[y,h]of Object.entries(m.conditions)){const p={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[y]||y;g.push(`${p}=${h}`)}g.length>0&&u.push(`(条件: ${g.join(", ")})`)}return m.schedule&&u.push(`⏱️ 定时: ${m.schedule}`),u.length>0?u.join(" "):"自定义触发条件"}async function D(m){var u;if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const g=localStorage.getItem("token"),M=((u=(await(await fetch(`http://localhost:3000/api/workflows/group/${d._id}`,{headers:{Authorization:`Bearer ${g}`}})).json()).data)==null?void 0:u.workflows)||[];m.innerHTML=`
|
|
1387
1387
|
<div class="view-header">
|
|
1388
1388
|
<h2>⚙️ 工作流管理 - ${d.name}</h2>
|
|
1389
1389
|
<button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</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),
|
|
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),F=document.createElement("a");F.href=w,F.download=r,F.style.display="none",document.body.appendChild(F),F.click(),setTimeout(()=>{document.body.removeChild(F),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>
|
|
@@ -1537,7 +1537,7 @@
|
|
|
1537
1537
|
</div>
|
|
1538
1538
|
<a href="http://localhost:3000/api/export/download/${M.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
|
|
1539
1539
|
</div>
|
|
1540
|
-
`).join(""):h.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const u={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},g=document.getElementById("exportBtn");g.disabled=!0,g.textContent="⏳ 导出中...";try{const y=localStorage.getItem("token"),h=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify(u)});if(h.ok){const M=await h.blob(),p=window.URL.createObjectURL(M),i=document.createElement("a");i.href=p,i.download=`export-${Date.now()}.${u.format}`,i.click(),alert("导出成功!"),await W(m)}else throw new Error("导出失败")}catch(y){alert("导出失败: "+y.message)}finally{g.disabled=!1,g.textContent="🚀 开始导出"}})}async function
|
|
1540
|
+
`).join(""):h.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const u={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},g=document.getElementById("exportBtn");g.disabled=!0,g.textContent="⏳ 导出中...";try{const y=localStorage.getItem("token"),h=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${y}`},body:JSON.stringify(u)});if(h.ok){const M=await h.blob(),p=window.URL.createObjectURL(M),i=document.createElement("a");i.href=p,i.download=`export-${Date.now()}.${u.format}`,i.click(),alert("导出成功!"),await W(m)}else throw new Error("导出失败")}catch(y){alert("导出失败: "+y.message)}finally{g.disabled=!1,g.textContent="🚀 开始导出"}})}async function N(m){if(!d){m.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}m.innerHTML=`
|
|
1541
1541
|
<div class="view-header">
|
|
1542
1542
|
<h2>🎨 协作白板 - ${d.name}</h2>
|
|
1543
1543
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1559,7 +1559,7 @@
|
|
|
1559
1559
|
<span class="user-badge">👤 ${a.username}</span>
|
|
1560
1560
|
</div>
|
|
1561
1561
|
</div>
|
|
1562
|
-
`;const u=document.getElementById("whiteboard"),g=u.getContext("2d");let y=!1,h="pen",M="#000000",p=3;document.querySelectorAll(".tool-btn").forEach(r=>{r.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(l=>l.classList.remove("active")),r.classList.add("active"),h=r.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",r=>{M=r.target.value}),document.getElementById("brushSize").addEventListener("input",r=>{p=r.target.value});let i=0,t=0;u.addEventListener("mousedown",r=>{y=!0;const l=u.getBoundingClientRect();i=r.clientX-l.left,t=r.clientY-l.top}),u.addEventListener("mousemove",r=>{if(!y)return;const l=u.getBoundingClientRect(),b=r.clientX-l.left,E=r.clientY-l.top;g.beginPath(),g.moveTo(i,t),g.lineTo(b,E),g.strokeStyle=h==="eraser"?"#ffffff":M,g.lineWidth=p,g.lineCap="round",g.stroke(),i=b,t=E,e.sendWhiteboardData(d._id,{tool:h,color:M,size:p,from:{x:i,y:t},to:{x:b,y:E}})}),u.addEventListener("mouseup",()=>{y=!1}),u.addEventListener("mouseleave",()=>{y=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&g.clearRect(0,0,u.width,u.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const r=u.toDataURL("image/png"),l=document.createElement("a");l.download=`whiteboard-${Date.now()}.png`,l.href=r,l.click(),alert("白板已保存!")}),e.on("whiteboard_draw",r=>{r.groupId===d._id&&r.userId!==s&&(g.beginPath(),g.moveTo(r.from.x,r.from.y),g.lineTo(r.to.x,r.to.y),g.strokeStyle=r.tool==="eraser"?"#ffffff":r.color,g.lineWidth=r.size,g.lineCap="round",g.stroke())})}async function
|
|
1562
|
+
`;const u=document.getElementById("whiteboard"),g=u.getContext("2d");let y=!1,h="pen",M="#000000",p=3;document.querySelectorAll(".tool-btn").forEach(r=>{r.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(l=>l.classList.remove("active")),r.classList.add("active"),h=r.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",r=>{M=r.target.value}),document.getElementById("brushSize").addEventListener("input",r=>{p=r.target.value});let i=0,t=0;u.addEventListener("mousedown",r=>{y=!0;const l=u.getBoundingClientRect();i=r.clientX-l.left,t=r.clientY-l.top}),u.addEventListener("mousemove",r=>{if(!y)return;const l=u.getBoundingClientRect(),b=r.clientX-l.left,E=r.clientY-l.top;g.beginPath(),g.moveTo(i,t),g.lineTo(b,E),g.strokeStyle=h==="eraser"?"#ffffff":M,g.lineWidth=p,g.lineCap="round",g.stroke(),i=b,t=E,e.sendWhiteboardData(d._id,{tool:h,color:M,size:p,from:{x:i,y:t},to:{x:b,y:E}})}),u.addEventListener("mouseup",()=>{y=!1}),u.addEventListener("mouseleave",()=>{y=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&g.clearRect(0,0,u.width,u.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const r=u.toDataURL("image/png"),l=document.createElement("a");l.download=`whiteboard-${Date.now()}.png`,l.href=r,l.click(),alert("白板已保存!")}),e.on("whiteboard_draw",r=>{r.groupId===d._id&&r.userId!==s&&(g.beginPath(),g.moveTo(r.from.x,r.from.y),g.lineTo(r.to.x,r.to.y),g.strokeStyle=r.tool==="eraser"?"#ffffff":r.color,g.lineWidth=r.size,g.lineCap="round",g.stroke())})}async function de(m){m.innerHTML=`
|
|
1563
1563
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1564
1564
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1565
1565
|
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚙️ 设置中心</span>
|
|
@@ -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)),
|
|
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)),Z(g.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",g=>{Z(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,Z(g.theme)),g.language&&(document.getElementById("languageSelect").value=g.language)}}function Z(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 pe(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 ge(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 oe(u);break;case"polls":await se(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 N(u);break;case"settings":await de(u);break;case"help":await pe(u);break}}ge("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>
|
|
@@ -1869,21 +1869,21 @@
|
|
|
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((
|
|
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((N,de)=>N+de.votes.length,0),k=A.options.some(N=>N.votes.includes(s)),C=A.status==="ended"||A.endTime&&new Date(A.endTime)<new Date;let D="";A.options.forEach((N,de)=>{const Z=v>0?(N.votes.length/v*100).toFixed(1):0,pe=N.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 ${pe?"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
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1877
|
-
${
|
|
1876
|
+
<span style="font-weight: 500; color: var(--text-primary);">${N.text}</span>
|
|
1877
|
+
${pe?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
|
|
1878
1878
|
</div>
|
|
1879
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1879
|
+
<span style="font-weight: 600; color: var(--primary);">${N.votes.length} 票 (${Z}%)</span>
|
|
1880
1880
|
</div>
|
|
1881
1881
|
<div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
|
|
1882
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
1882
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${Z}%; transition: width 0.3s;"></div>
|
|
1883
1883
|
</div>
|
|
1884
|
-
${!A.anonymous&&
|
|
1884
|
+
${!A.anonymous&&N.votes.length>0?`
|
|
1885
1885
|
<div style="margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
|
|
1886
|
-
投票者: ${
|
|
1886
|
+
投票者: ${N.voterNames?N.voterNames.join(", "):""}
|
|
1887
1887
|
</div>
|
|
1888
1888
|
`:""}
|
|
1889
1889
|
</div>
|
|
@@ -1937,17 +1937,17 @@
|
|
|
1937
1937
|
<h3 style="margin-bottom: 16px; color: var(--text-primary);">投票选项</h3>
|
|
1938
1938
|
${!C&&!k?`
|
|
1939
1939
|
<form id="voteForm">
|
|
1940
|
-
${A.options.map((
|
|
1940
|
+
${A.options.map((N,de)=>{const Z=v>0?(N.votes.length/v*100).toFixed(1):0;return`
|
|
1941
1941
|
<div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid var(--border); cursor: pointer;" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'">
|
|
1942
1942
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1943
1943
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1944
|
-
<input type="${A.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${
|
|
1945
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1944
|
+
<input type="${A.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${de}" style="width: 18px; height: 18px; cursor: pointer;">
|
|
1945
|
+
<span style="font-weight: 500; color: var(--text-primary);">${N.text}</span>
|
|
1946
1946
|
</div>
|
|
1947
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1947
|
+
<span style="font-weight: 600; color: var(--primary);">${N.votes.length} 票 (${Z}%)</span>
|
|
1948
1948
|
</div>
|
|
1949
1949
|
<div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
|
|
1950
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
1950
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${Z}%; transition: width 0.3s;"></div>
|
|
1951
1951
|
</div>
|
|
1952
1952
|
</div>
|
|
1953
1953
|
`}).join("")}
|
|
@@ -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",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
|
|
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 N=>{N.preventDefault();const de=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(Z=>parseInt(Z.value));if(de.length===0){alert("请选择至少一个选项!");return}try{await n.vote(B,de),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const Z=document.querySelector(".nav-item.active");if(Z&&Z.dataset.view==="tasks"){const pe=document.getElementById("contentArea");await _(pe)}}catch(Z){console.error("投票失败:",Z),alert("投票失败:"+Z.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 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
|
|
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 ce(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>
|
|
@@ -2054,7 +2054,7 @@
|
|
|
2054
2054
|
<h3>${k.title}</h3>
|
|
2055
2055
|
<p>${k.description}</p>
|
|
2056
2056
|
<div class="task-meta">
|
|
2057
|
-
<span class="status-badge">${
|
|
2057
|
+
<span class="status-badge">${se(k.status)}</span>
|
|
2058
2058
|
<span>群组: ${k.group.name}</span>
|
|
2059
2059
|
${k.deadline?`<span>截止: ${new Date(k.deadline).toLocaleDateString()}</span>`:""}
|
|
2060
2060
|
</div>
|
|
@@ -2063,7 +2063,7 @@
|
|
|
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,
|
|
2066
|
+
`,v.appendChild(C)}),A.forEach(k=>{const C=k.options.reduce((W,N)=>W+N.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>
|
|
@@ -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",()=>{
|
|
2101
|
+
`,A.appendChild(k)}),document.querySelectorAll(".btn-edit").forEach(v=>{v.addEventListener("click",()=>{ie(B,v.dataset.id)})})}async function ie(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>
|
|
@@ -2147,7 +2147,7 @@
|
|
|
2147
2147
|
</form>
|
|
2148
2148
|
</div>
|
|
2149
2149
|
</div>
|
|
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=
|
|
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=re(v.mimetype),D=ue(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>
|
|
@@ -2167,7 +2167,7 @@
|
|
|
2167
2167
|
<h2>文件共享</h2>
|
|
2168
2168
|
</div>
|
|
2169
2169
|
<div class="empty-state">加载文件失败: ${I.message}</div>
|
|
2170
|
-
`}}function
|
|
2170
|
+
`}}function re(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 ue(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,7 +2176,7 @@
|
|
|
2176
2176
|
前往我的群组
|
|
2177
2177
|
</button>
|
|
2178
2178
|
</div>
|
|
2179
|
-
`;return}try{let
|
|
2179
|
+
`;return}try{let pe=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(F=>{F.style.background="var(--bg-secondary)",F.style.color="var(--text-primary)",F.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"),F=document.createElement("canvas");F.width=i.width,F.height=i.height;const G=F.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"),P=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${R}`},body:x});if(P.ok){const J=`http://localhost:3000/api/files/${(await P.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 F=i.getBoundingClientRect(),G=w.clientX-F.left,f=w.clientY-F.top;t.beginPath(),t.moveTo(G,f)}),i.addEventListener("mousemove",w=>{if(!r)return;const F=i.getBoundingClientRect(),G=w.clientX-F.left,f=w.clientY-F.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=pe,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>
|
|
@@ -2246,7 +2246,7 @@
|
|
|
2246
2246
|
</div>
|
|
2247
2247
|
</div>
|
|
2248
2248
|
</div>
|
|
2249
|
-
`;const z=document.getElementById("messages"),W=document.getElementById("messageInput"),
|
|
2249
|
+
`;const z=document.getElementById("messages"),W=document.getElementById("messageInput"),N=document.getElementById("sendBtn");try{const i=await n.getGroupMessages(d._id);i.messages&&Array.isArray(i.messages)&&(i.messages.length===0?z.innerHTML=`
|
|
2250
2250
|
<div style="text-align: center; padding: 40px; color: var(--text-tertiary);">
|
|
2251
2251
|
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
2252
2252
|
<p>还没有消息,开始聊天吧!</p>
|
|
@@ -2268,7 +2268,7 @@
|
|
|
2268
2268
|
<p>加载历史消息失败</p>
|
|
2269
2269
|
<p style="font-size: 14px; color: var(--text-tertiary);">${i.message}</p>
|
|
2270
2270
|
</div>
|
|
2271
|
-
`}const
|
|
2271
|
+
`}const de=document.getElementById("emojiBtn"),Z=document.getElementById("emojiPicker");L&&(de.addEventListener("click",()=>{Z.classList.toggle("hidden")}),Z.addEventListener("emoji-click",i=>{W.value+=i.detail.unicode,W.focus(),Z.classList.add("hidden")}),document.addEventListener("click",i=>{!de.contains(i.target)&&!Z.contains(i.target)&&Z.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;
|
|
@@ -2279,7 +2279,7 @@
|
|
|
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||pe(`${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 ge=()=>{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&&(N.addEventListener("click",ge),W.addEventListener("keypress",i=>{i.key==="Enter"&&!i.shiftKey&&(i.preventDefault(),ge())}));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,18 +2357,18 @@
|
|
|
2357
2357
|
</div>
|
|
2358
2358
|
<div class="search-results" id="searchResults"></div>
|
|
2359
2359
|
</div>
|
|
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(
|
|
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(N=>N.content.toLowerCase().includes(C.toLowerCase())).forEach(N=>{L.push({type:"message",title:`消息 - ${N.username}`,content:N.content,time:N.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(N=>N.title.toLowerCase().includes(C.toLowerCase())||N.content.toLowerCase().includes(C.toLowerCase())).forEach(N=>{L.push({type:"document",title:N.title,content:N.content.substring(0,200),time:N.updatedAt,id:N._id,group:d.name})})}}catch(z){console.error("搜索文档失败:",z)}if(D.tasks)try{const z=await n.getMyTasks();z.tasks&&z.tasks.filter(N=>N.title.toLowerCase().includes(C.toLowerCase())||N.description&&N.description.toLowerCase().includes(C.toLowerCase())).forEach(N=>{L.push({type:"task",title:N.title,content:N.description||"",time:N.updatedAt,id:N._id,status:N.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>
|
|
2364
2364
|
<span class="result-time">${new Date(z.time).toLocaleString()}</span>
|
|
2365
2365
|
</div>
|
|
2366
|
-
<h4>${
|
|
2367
|
-
<p>${
|
|
2366
|
+
<h4>${oe(z.title,C)}</h4>
|
|
2367
|
+
<p>${oe(z.content,C)}</p>
|
|
2368
2368
|
${z.group?`<span class="result-group">群组: ${z.group}</span>`:""}
|
|
2369
|
-
${z.status?`<span class="result-status">状态: ${
|
|
2369
|
+
${z.status?`<span class="result-status">状态: ${se(z.status)}</span>`:""}
|
|
2370
2370
|
</div>
|
|
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
|
|
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 oe(B,I){if(!I)return B;const A=new RegExp(`(${I})`,"gi");return B.replace(A,"<mark>$1</mark>")}function se(B){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[B]||B}async function ce(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>
|
|
@@ -2431,14 +2431,14 @@
|
|
|
2431
2431
|
</div>
|
|
2432
2432
|
${D.tags&&D.tags.length>0?`
|
|
2433
2433
|
<div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
|
|
2434
|
-
${D.tags.map(
|
|
2434
|
+
${D.tags.map(N=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${N}</span>`).join("")}
|
|
2435
2435
|
</div>
|
|
2436
2436
|
`:""}
|
|
2437
2437
|
<div style="display: flex; gap: 10px;">
|
|
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
|
-
`,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
|
|
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 ce(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,N=W?`http://localhost:3000/api/knowledge/${W}`:"http://localhost:3000/api/knowledge";if(!(await fetch(N,{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 ce(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>
|