collabdocchat 2.5.11 → 2.5.14
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-Vc_T-zsG.js → index-DM1cxPAx.js} +407 -402
- package/dist/index.html +1 -1
- package/package.json +5 -1
- package/scripts/cleanup-scripts.js +3 -0
- package/scripts/fix-startup-issues.js +3 -0
- package/scripts/start-simple.js +3 -0
- package/server/public/index.html +3 -0
- package/src/main.js +3 -0
- package/src/polyfills.js +11 -0
- package/vite.config.js +27 -0
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
(function(){const
|
|
1
|
+
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))s(m);new MutationObserver(m=>{for(const l of m)if(l.type==="childList")for(const c of l.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&s(c)}).observe(document,{childList:!0,subtree:!0});function i(m){const l={};return m.integrity&&(l.integrity=m.integrity),m.referrerPolicy&&(l.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?l.credentials="include":m.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function s(m){if(m.ep)return;m.ep=!0;const l=i(m);fetch(m.href,l)}})();function on(r){return r&&r.__esModule&&Object.prototype.hasOwnProperty.call(r,"default")?r.default:r}var At={},Ke={};Ke.byteLength=ln;Ke.toByteArray=pn;Ke.fromByteArray=mn;var Le=[],ke=[],sn=typeof Uint8Array<"u"?Uint8Array:Array,Xe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var Ae=0,dn=Xe.length;Ae<dn;++Ae)Le[Ae]=Xe[Ae],ke[Xe.charCodeAt(Ae)]=Ae;ke[45]=62;ke[95]=63;function zt(r){var t=r.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var i=r.indexOf("=");i===-1&&(i=t);var s=i===t?0:4-i%4;return[i,s]}function ln(r){var t=zt(r),i=t[0],s=t[1];return(i+s)*3/4-s}function cn(r,t,i){return(t+i)*3/4-i}function pn(r){var t,i=zt(r),s=i[0],m=i[1],l=new sn(cn(r,s,m)),c=0,u=m>0?s-4:s,F;for(F=0;F<u;F+=4)t=ke[r.charCodeAt(F)]<<18|ke[r.charCodeAt(F+1)]<<12|ke[r.charCodeAt(F+2)]<<6|ke[r.charCodeAt(F+3)],l[c++]=t>>16&255,l[c++]=t>>8&255,l[c++]=t&255;return m===2&&(t=ke[r.charCodeAt(F)]<<2|ke[r.charCodeAt(F+1)]>>4,l[c++]=t&255),m===1&&(t=ke[r.charCodeAt(F)]<<10|ke[r.charCodeAt(F+1)]<<4|ke[r.charCodeAt(F+2)]>>2,l[c++]=t>>8&255,l[c++]=t&255),l}function un(r){return Le[r>>18&63]+Le[r>>12&63]+Le[r>>6&63]+Le[r&63]}function gn(r,t,i){for(var s,m=[],l=t;l<i;l+=3)s=(r[l]<<16&16711680)+(r[l+1]<<8&65280)+(r[l+2]&255),m.push(un(s));return m.join("")}function mn(r){for(var t,i=r.length,s=i%3,m=[],l=16383,c=0,u=i-s;c<u;c+=l)m.push(gn(r,c,c+l>u?u:c+l));return s===1?(t=r[i-1],m.push(Le[t>>2]+Le[t<<4&63]+"==")):s===2&&(t=(r[i-2]<<8)+r[i-1],m.push(Le[t>>10]+Le[t>>4&63]+Le[t<<2&63]+"=")),m.join("")}var yt={};/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */yt.read=function(r,t,i,s,m){var l,c,u=m*8-s-1,F=(1<<u)-1,D=F>>1,z=-7,R=i?m-1:0,U=i?-1:1,W=r[t+R];for(R+=U,l=W&(1<<-z)-1,W>>=-z,z+=u;z>0;l=l*256+r[t+R],R+=U,z-=8);for(c=l&(1<<-z)-1,l>>=-z,z+=s;z>0;c=c*256+r[t+R],R+=U,z-=8);if(l===0)l=1-D;else{if(l===F)return c?NaN:(W?-1:1)*(1/0);c=c+Math.pow(2,s),l=l-D}return(W?-1:1)*c*Math.pow(2,l-s)};yt.write=function(r,t,i,s,m,l){var c,u,F,D=l*8-m-1,z=(1<<D)-1,R=z>>1,U=m===23?Math.pow(2,-24)-Math.pow(2,-77):0,W=s?0:l-1,ue=s?1:-1,oe=t<0||t===0&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(u=isNaN(t)?1:0,c=z):(c=Math.floor(Math.log(t)/Math.LN2),t*(F=Math.pow(2,-c))<1&&(c--,F*=2),c+R>=1?t+=U/F:t+=U*Math.pow(2,1-R),t*F>=2&&(c++,F/=2),c+R>=z?(u=0,c=z):c+R>=1?(u=(t*F-1)*Math.pow(2,m),c=c+R):(u=t*Math.pow(2,R-1)*Math.pow(2,m),c=0));m>=8;r[i+W]=u&255,W+=ue,u/=256,m-=8);for(c=c<<m|u,D+=m;D>0;r[i+W]=c&255,W+=ue,c/=256,D-=8);r[i+W-ue]|=oe*128};/*!
|
|
2
|
+
* The buffer module from node.js, for the browser.
|
|
3
|
+
*
|
|
4
|
+
* @author Feross Aboukhadijeh <https://feross.org>
|
|
5
|
+
* @license MIT
|
|
6
|
+
*/(function(r){const t=Ke,i=yt,s=typeof Symbol=="function"&&typeof Symbol.for=="function"?Symbol.for("nodejs.util.inspect.custom"):null;r.Buffer=u,r.SlowBuffer=K,r.INSPECT_MAX_BYTES=50;const m=2147483647;r.kMaxLength=m,u.TYPED_ARRAY_SUPPORT=l(),!u.TYPED_ARRAY_SUPPORT&&typeof console<"u"&&typeof console.error=="function"&&console.error("This browser lacks typed array (Uint8Array) support which is required by `buffer` v5.x. Use `buffer` v4.x if you require old browser support.");function l(){try{const a=new Uint8Array(1),e={foo:function(){return 42}};return Object.setPrototypeOf(e,Uint8Array.prototype),Object.setPrototypeOf(a,e),a.foo()===42}catch{return!1}}Object.defineProperty(u.prototype,"parent",{enumerable:!0,get:function(){if(u.isBuffer(this))return this.buffer}}),Object.defineProperty(u.prototype,"offset",{enumerable:!0,get:function(){if(u.isBuffer(this))return this.byteOffset}});function c(a){if(a>m)throw new RangeError('The value "'+a+'" is invalid for option "size"');const e=new Uint8Array(a);return Object.setPrototypeOf(e,u.prototype),e}function u(a,e,n){if(typeof a=="number"){if(typeof e=="string")throw new TypeError('The "string" argument must be of type string. Received type number');return R(a)}return F(a,e,n)}u.poolSize=8192;function F(a,e,n){if(typeof a=="string")return U(a,e);if(ArrayBuffer.isView(a))return ue(a);if(a==null)throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof a);if(q(a,ArrayBuffer)||a&&q(a.buffer,ArrayBuffer)||typeof SharedArrayBuffer<"u"&&(q(a,SharedArrayBuffer)||a&&q(a.buffer,SharedArrayBuffer)))return oe(a,e,n);if(typeof a=="number")throw new TypeError('The "value" argument must not be of type number. Received type number');const d=a.valueOf&&a.valueOf();if(d!=null&&d!==a)return u.from(d,e,n);const y=fe(a);if(y)return y;if(typeof Symbol<"u"&&Symbol.toPrimitive!=null&&typeof a[Symbol.toPrimitive]=="function")return u.from(a[Symbol.toPrimitive]("string"),e,n);throw new TypeError("The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type "+typeof a)}u.from=function(a,e,n){return F(a,e,n)},Object.setPrototypeOf(u.prototype,Uint8Array.prototype),Object.setPrototypeOf(u,Uint8Array);function D(a){if(typeof a!="number")throw new TypeError('"size" argument must be of type number');if(a<0)throw new RangeError('The value "'+a+'" is invalid for option "size"')}function z(a,e,n){return D(a),a<=0?c(a):e!==void 0?typeof n=="string"?c(a).fill(e,n):c(a).fill(e):c(a)}u.alloc=function(a,e,n){return z(a,e,n)};function R(a){return D(a),c(a<0?0:be(a)|0)}u.allocUnsafe=function(a){return R(a)},u.allocUnsafeSlow=function(a){return R(a)};function U(a,e){if((typeof e!="string"||e==="")&&(e="utf8"),!u.isEncoding(e))throw new TypeError("Unknown encoding: "+e);const n=te(a,e)|0;let d=c(n);const y=d.write(a,e);return y!==n&&(d=d.slice(0,y)),d}function W(a){const e=a.length<0?0:be(a.length)|0,n=c(e);for(let d=0;d<e;d+=1)n[d]=a[d]&255;return n}function ue(a){if(q(a,Uint8Array)){const e=new Uint8Array(a);return oe(e.buffer,e.byteOffset,e.byteLength)}return W(a)}function oe(a,e,n){if(e<0||a.byteLength<e)throw new RangeError('"offset" is outside of buffer bounds');if(a.byteLength<e+(n||0))throw new RangeError('"length" is outside of buffer bounds');let d;return e===void 0&&n===void 0?d=new Uint8Array(a):n===void 0?d=new Uint8Array(a,e):d=new Uint8Array(a,e,n),Object.setPrototypeOf(d,u.prototype),d}function fe(a){if(u.isBuffer(a)){const e=be(a.length)|0,n=c(e);return n.length===0||a.copy(n,0,0,e),n}if(a.length!==void 0)return typeof a.length!="number"||re(a.length)?c(0):W(a);if(a.type==="Buffer"&&Array.isArray(a.data))return W(a.data)}function be(a){if(a>=m)throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+m.toString(16)+" bytes");return a|0}function K(a){return+a!=a&&(a=0),u.alloc(+a)}u.isBuffer=function(e){return e!=null&&e._isBuffer===!0&&e!==u.prototype},u.compare=function(e,n){if(q(e,Uint8Array)&&(e=u.from(e,e.offset,e.byteLength)),q(n,Uint8Array)&&(n=u.from(n,n.offset,n.byteLength)),!u.isBuffer(e)||!u.isBuffer(n))throw new TypeError('The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array');if(e===n)return 0;let d=e.length,y=n.length;for(let x=0,I=Math.min(d,y);x<I;++x)if(e[x]!==n[x]){d=e[x],y=n[x];break}return d<y?-1:y<d?1:0},u.isEncoding=function(e){switch(String(e).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},u.concat=function(e,n){if(!Array.isArray(e))throw new TypeError('"list" argument must be an Array of Buffers');if(e.length===0)return u.alloc(0);let d;if(n===void 0)for(n=0,d=0;d<e.length;++d)n+=e[d].length;const y=u.allocUnsafe(n);let x=0;for(d=0;d<e.length;++d){let I=e[d];if(q(I,Uint8Array))x+I.length>y.length?(u.isBuffer(I)||(I=u.from(I)),I.copy(y,x)):Uint8Array.prototype.set.call(y,I,x);else if(u.isBuffer(I))I.copy(y,x);else throw new TypeError('"list" argument must be an Array of Buffers');x+=I.length}return y};function te(a,e){if(u.isBuffer(a))return a.length;if(ArrayBuffer.isView(a)||q(a,ArrayBuffer))return a.byteLength;if(typeof a!="string")throw new TypeError('The "string" argument must be one of type string, Buffer, or ArrayBuffer. Received type '+typeof a);const n=a.length,d=arguments.length>2&&arguments[2]===!0;if(!d&&n===0)return 0;let y=!1;for(;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":return ee(a).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return n*2;case"hex":return n>>>1;case"base64":return T(a).length;default:if(y)return d?-1:ee(a).length;e=(""+e).toLowerCase(),y=!0}}u.byteLength=te;function me(a,e,n){let d=!1;if((e===void 0||e<0)&&(e=0),e>this.length||((n===void 0||n>this.length)&&(n=this.length),n<=0)||(n>>>=0,e>>>=0,n<=e))return"";for(a||(a="utf8");;)switch(a){case"hex":return ie(this,e,n);case"utf8":case"utf-8":return _(this,e,n);case"ascii":return Y(this,e,n);case"latin1":case"binary":return xe(this,e,n);case"base64":return H(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return we(this,e,n);default:if(d)throw new TypeError("Unknown encoding: "+a);a=(a+"").toLowerCase(),d=!0}}u.prototype._isBuffer=!0;function se(a,e,n){const d=a[e];a[e]=a[n],a[n]=d}u.prototype.swap16=function(){const e=this.length;if(e%2!==0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(let n=0;n<e;n+=2)se(this,n,n+1);return this},u.prototype.swap32=function(){const e=this.length;if(e%4!==0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(let n=0;n<e;n+=4)se(this,n,n+3),se(this,n+1,n+2);return this},u.prototype.swap64=function(){const e=this.length;if(e%8!==0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(let n=0;n<e;n+=8)se(this,n,n+7),se(this,n+1,n+6),se(this,n+2,n+5),se(this,n+3,n+4);return this},u.prototype.toString=function(){const e=this.length;return e===0?"":arguments.length===0?_(this,0,e):me.apply(this,arguments)},u.prototype.toLocaleString=u.prototype.toString,u.prototype.equals=function(e){if(!u.isBuffer(e))throw new TypeError("Argument must be a Buffer");return this===e?!0:u.compare(this,e)===0},u.prototype.inspect=function(){let e="";const n=r.INSPECT_MAX_BYTES;return e=this.toString("hex",0,n).replace(/(.{2})/g,"$1 ").trim(),this.length>n&&(e+=" ... "),"<Buffer "+e+">"},s&&(u.prototype[s]=u.prototype.inspect),u.prototype.compare=function(e,n,d,y,x){if(q(e,Uint8Array)&&(e=u.from(e,e.offset,e.byteLength)),!u.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(n===void 0&&(n=0),d===void 0&&(d=e?e.length:0),y===void 0&&(y=0),x===void 0&&(x=this.length),n<0||d>e.length||y<0||x>this.length)throw new RangeError("out of range index");if(y>=x&&n>=d)return 0;if(y>=x)return-1;if(n>=d)return 1;if(n>>>=0,d>>>=0,y>>>=0,x>>>=0,this===e)return 0;let I=x-y,Q=d-n;const ge=Math.min(I,Q),ce=this.slice(y,x),ye=e.slice(n,d);for(let le=0;le<ge;++le)if(ce[le]!==ye[le]){I=ce[le],Q=ye[le];break}return I<Q?-1:Q<I?1:0};function ve(a,e,n,d,y){if(a.length===0)return-1;if(typeof n=="string"?(d=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,re(n)&&(n=y?0:a.length-1),n<0&&(n=a.length+n),n>=a.length){if(y)return-1;n=a.length-1}else if(n<0)if(y)n=0;else return-1;if(typeof e=="string"&&(e=u.from(e,d)),u.isBuffer(e))return e.length===0?-1:P(a,e,n,d,y);if(typeof e=="number")return e=e&255,typeof Uint8Array.prototype.indexOf=="function"?y?Uint8Array.prototype.indexOf.call(a,e,n):Uint8Array.prototype.lastIndexOf.call(a,e,n):P(a,[e],n,d,y);throw new TypeError("val must be string, number or Buffer")}function P(a,e,n,d,y){let x=1,I=a.length,Q=e.length;if(d!==void 0&&(d=String(d).toLowerCase(),d==="ucs2"||d==="ucs-2"||d==="utf16le"||d==="utf-16le")){if(a.length<2||e.length<2)return-1;x=2,I/=2,Q/=2,n/=2}function ge(ye,le){return x===1?ye[le]:ye.readUInt16BE(le*x)}let ce;if(y){let ye=-1;for(ce=n;ce<I;ce++)if(ge(a,ce)===ge(e,ye===-1?0:ce-ye)){if(ye===-1&&(ye=ce),ce-ye+1===Q)return ye*x}else ye!==-1&&(ce-=ce-ye),ye=-1}else for(n+Q>I&&(n=I-Q),ce=n;ce>=0;ce--){let ye=!0;for(let le=0;le<Q;le++)if(ge(a,ce+le)!==ge(e,le)){ye=!1;break}if(ye)return ce}return-1}u.prototype.includes=function(e,n,d){return this.indexOf(e,n,d)!==-1},u.prototype.indexOf=function(e,n,d){return ve(this,e,n,d,!0)},u.prototype.lastIndexOf=function(e,n,d){return ve(this,e,n,d,!1)};function A(a,e,n,d){n=Number(n)||0;const y=a.length-n;d?(d=Number(d),d>y&&(d=y)):d=y;const x=e.length;d>x/2&&(d=x/2);let I;for(I=0;I<d;++I){const Q=parseInt(e.substr(I*2,2),16);if(re(Q))return I;a[n+I]=Q}return I}function V(a,e,n,d){return X(ee(e,a.length-n),a,n,d)}function $(a,e,n,d){return X(S(e),a,n,d)}function M(a,e,n,d){return X(T(e),a,n,d)}function N(a,e,n,d){return X(j(e,a.length-n),a,n,d)}u.prototype.write=function(e,n,d,y){if(n===void 0)y="utf8",d=this.length,n=0;else if(d===void 0&&typeof n=="string")y=n,d=this.length,n=0;else if(isFinite(n))n=n>>>0,isFinite(d)?(d=d>>>0,y===void 0&&(y="utf8")):(y=d,d=void 0);else throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");const x=this.length-n;if((d===void 0||d>x)&&(d=x),e.length>0&&(d<0||n<0)||n>this.length)throw new RangeError("Attempt to write outside buffer bounds");y||(y="utf8");let I=!1;for(;;)switch(y){case"hex":return A(this,e,n,d);case"utf8":case"utf-8":return V(this,e,n,d);case"ascii":case"latin1":case"binary":return $(this,e,n,d);case"base64":return M(this,e,n,d);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return N(this,e,n,d);default:if(I)throw new TypeError("Unknown encoding: "+y);y=(""+y).toLowerCase(),I=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};function H(a,e,n){return e===0&&n===a.length?t.fromByteArray(a):t.fromByteArray(a.slice(e,n))}function _(a,e,n){n=Math.min(a.length,n);const d=[];let y=e;for(;y<n;){const x=a[y];let I=null,Q=x>239?4:x>223?3:x>191?2:1;if(y+Q<=n){let ge,ce,ye,le;switch(Q){case 1:x<128&&(I=x);break;case 2:ge=a[y+1],(ge&192)===128&&(le=(x&31)<<6|ge&63,le>127&&(I=le));break;case 3:ge=a[y+1],ce=a[y+2],(ge&192)===128&&(ce&192)===128&&(le=(x&15)<<12|(ge&63)<<6|ce&63,le>2047&&(le<55296||le>57343)&&(I=le));break;case 4:ge=a[y+1],ce=a[y+2],ye=a[y+3],(ge&192)===128&&(ce&192)===128&&(ye&192)===128&&(le=(x&15)<<18|(ge&63)<<12|(ce&63)<<6|ye&63,le>65535&&le<1114112&&(I=le))}}I===null?(I=65533,Q=1):I>65535&&(I-=65536,d.push(I>>>10&1023|55296),I=56320|I&1023),d.push(I),y+=Q}return Z(d)}const O=4096;function Z(a){const e=a.length;if(e<=O)return String.fromCharCode.apply(String,a);let n="",d=0;for(;d<e;)n+=String.fromCharCode.apply(String,a.slice(d,d+=O));return n}function Y(a,e,n){let d="";n=Math.min(a.length,n);for(let y=e;y<n;++y)d+=String.fromCharCode(a[y]&127);return d}function xe(a,e,n){let d="";n=Math.min(a.length,n);for(let y=e;y<n;++y)d+=String.fromCharCode(a[y]);return d}function ie(a,e,n){const d=a.length;(!e||e<0)&&(e=0),(!n||n<0||n>d)&&(n=d);let y="";for(let x=e;x<n;++x)y+=de[a[x]];return y}function we(a,e,n){const d=a.slice(e,n);let y="";for(let x=0;x<d.length-1;x+=2)y+=String.fromCharCode(d[x]+d[x+1]*256);return y}u.prototype.slice=function(e,n){const d=this.length;e=~~e,n=n===void 0?d:~~n,e<0?(e+=d,e<0&&(e=0)):e>d&&(e=d),n<0?(n+=d,n<0&&(n=0)):n>d&&(n=d),n<e&&(n=e);const y=this.subarray(e,n);return Object.setPrototypeOf(y,u.prototype),y};function ae(a,e,n){if(a%1!==0||a<0)throw new RangeError("offset is not uint");if(a+e>n)throw new RangeError("Trying to access beyond buffer length")}u.prototype.readUintLE=u.prototype.readUIntLE=function(e,n,d){e=e>>>0,n=n>>>0,d||ae(e,n,this.length);let y=this[e],x=1,I=0;for(;++I<n&&(x*=256);)y+=this[e+I]*x;return y},u.prototype.readUintBE=u.prototype.readUIntBE=function(e,n,d){e=e>>>0,n=n>>>0,d||ae(e,n,this.length);let y=this[e+--n],x=1;for(;n>0&&(x*=256);)y+=this[e+--n]*x;return y},u.prototype.readUint8=u.prototype.readUInt8=function(e,n){return e=e>>>0,n||ae(e,1,this.length),this[e]},u.prototype.readUint16LE=u.prototype.readUInt16LE=function(e,n){return e=e>>>0,n||ae(e,2,this.length),this[e]|this[e+1]<<8},u.prototype.readUint16BE=u.prototype.readUInt16BE=function(e,n){return e=e>>>0,n||ae(e,2,this.length),this[e]<<8|this[e+1]},u.prototype.readUint32LE=u.prototype.readUInt32LE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+this[e+3]*16777216},u.prototype.readUint32BE=u.prototype.readUInt32BE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),this[e]*16777216+(this[e+1]<<16|this[e+2]<<8|this[e+3])},u.prototype.readBigUInt64LE=ne(function(e){e=e>>>0,E(e,"offset");const n=this[e],d=this[e+7];(n===void 0||d===void 0)&&C(e,this.length-8);const y=n+this[++e]*2**8+this[++e]*2**16+this[++e]*2**24,x=this[++e]+this[++e]*2**8+this[++e]*2**16+d*2**24;return BigInt(y)+(BigInt(x)<<BigInt(32))}),u.prototype.readBigUInt64BE=ne(function(e){e=e>>>0,E(e,"offset");const n=this[e],d=this[e+7];(n===void 0||d===void 0)&&C(e,this.length-8);const y=n*2**24+this[++e]*2**16+this[++e]*2**8+this[++e],x=this[++e]*2**24+this[++e]*2**16+this[++e]*2**8+d;return(BigInt(y)<<BigInt(32))+BigInt(x)}),u.prototype.readIntLE=function(e,n,d){e=e>>>0,n=n>>>0,d||ae(e,n,this.length);let y=this[e],x=1,I=0;for(;++I<n&&(x*=256);)y+=this[e+I]*x;return x*=128,y>=x&&(y-=Math.pow(2,8*n)),y},u.prototype.readIntBE=function(e,n,d){e=e>>>0,n=n>>>0,d||ae(e,n,this.length);let y=n,x=1,I=this[e+--y];for(;y>0&&(x*=256);)I+=this[e+--y]*x;return x*=128,I>=x&&(I-=Math.pow(2,8*n)),I},u.prototype.readInt8=function(e,n){return e=e>>>0,n||ae(e,1,this.length),this[e]&128?(255-this[e]+1)*-1:this[e]},u.prototype.readInt16LE=function(e,n){e=e>>>0,n||ae(e,2,this.length);const d=this[e]|this[e+1]<<8;return d&32768?d|4294901760:d},u.prototype.readInt16BE=function(e,n){e=e>>>0,n||ae(e,2,this.length);const d=this[e+1]|this[e]<<8;return d&32768?d|4294901760:d},u.prototype.readInt32LE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},u.prototype.readInt32BE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},u.prototype.readBigInt64LE=ne(function(e){e=e>>>0,E(e,"offset");const n=this[e],d=this[e+7];(n===void 0||d===void 0)&&C(e,this.length-8);const y=this[e+4]+this[e+5]*2**8+this[e+6]*2**16+(d<<24);return(BigInt(y)<<BigInt(32))+BigInt(n+this[++e]*2**8+this[++e]*2**16+this[++e]*2**24)}),u.prototype.readBigInt64BE=ne(function(e){e=e>>>0,E(e,"offset");const n=this[e],d=this[e+7];(n===void 0||d===void 0)&&C(e,this.length-8);const y=(n<<24)+this[++e]*2**16+this[++e]*2**8+this[++e];return(BigInt(y)<<BigInt(32))+BigInt(this[++e]*2**24+this[++e]*2**16+this[++e]*2**8+d)}),u.prototype.readFloatLE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),i.read(this,e,!0,23,4)},u.prototype.readFloatBE=function(e,n){return e=e>>>0,n||ae(e,4,this.length),i.read(this,e,!1,23,4)},u.prototype.readDoubleLE=function(e,n){return e=e>>>0,n||ae(e,8,this.length),i.read(this,e,!0,52,8)},u.prototype.readDoubleBE=function(e,n){return e=e>>>0,n||ae(e,8,this.length),i.read(this,e,!1,52,8)};function v(a,e,n,d,y,x){if(!u.isBuffer(a))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>y||e<x)throw new RangeError('"value" argument is out of bounds');if(n+d>a.length)throw new RangeError("Index out of range")}u.prototype.writeUintLE=u.prototype.writeUIntLE=function(e,n,d,y){if(e=+e,n=n>>>0,d=d>>>0,!y){const Q=Math.pow(2,8*d)-1;v(this,e,n,d,Q,0)}let x=1,I=0;for(this[n]=e&255;++I<d&&(x*=256);)this[n+I]=e/x&255;return n+d},u.prototype.writeUintBE=u.prototype.writeUIntBE=function(e,n,d,y){if(e=+e,n=n>>>0,d=d>>>0,!y){const Q=Math.pow(2,8*d)-1;v(this,e,n,d,Q,0)}let x=d-1,I=1;for(this[n+x]=e&255;--x>=0&&(I*=256);)this[n+x]=e/I&255;return n+d},u.prototype.writeUint8=u.prototype.writeUInt8=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,1,255,0),this[n]=e&255,n+1},u.prototype.writeUint16LE=u.prototype.writeUInt16LE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,2,65535,0),this[n]=e&255,this[n+1]=e>>>8,n+2},u.prototype.writeUint16BE=u.prototype.writeUInt16BE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,2,65535,0),this[n]=e>>>8,this[n+1]=e&255,n+2},u.prototype.writeUint32LE=u.prototype.writeUInt32LE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,4,4294967295,0),this[n+3]=e>>>24,this[n+2]=e>>>16,this[n+1]=e>>>8,this[n]=e&255,n+4},u.prototype.writeUint32BE=u.prototype.writeUInt32BE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,4,4294967295,0),this[n]=e>>>24,this[n+1]=e>>>16,this[n+2]=e>>>8,this[n+3]=e&255,n+4};function b(a,e,n,d,y){f(e,d,y,a,n,7);let x=Number(e&BigInt(4294967295));a[n++]=x,x=x>>8,a[n++]=x,x=x>>8,a[n++]=x,x=x>>8,a[n++]=x;let I=Number(e>>BigInt(32)&BigInt(4294967295));return a[n++]=I,I=I>>8,a[n++]=I,I=I>>8,a[n++]=I,I=I>>8,a[n++]=I,n}function w(a,e,n,d,y){f(e,d,y,a,n,7);let x=Number(e&BigInt(4294967295));a[n+7]=x,x=x>>8,a[n+6]=x,x=x>>8,a[n+5]=x,x=x>>8,a[n+4]=x;let I=Number(e>>BigInt(32)&BigInt(4294967295));return a[n+3]=I,I=I>>8,a[n+2]=I,I=I>>8,a[n+1]=I,I=I>>8,a[n]=I,n+8}u.prototype.writeBigUInt64LE=ne(function(e,n=0){return b(this,e,n,BigInt(0),BigInt("0xffffffffffffffff"))}),u.prototype.writeBigUInt64BE=ne(function(e,n=0){return w(this,e,n,BigInt(0),BigInt("0xffffffffffffffff"))}),u.prototype.writeIntLE=function(e,n,d,y){if(e=+e,n=n>>>0,!y){const ge=Math.pow(2,8*d-1);v(this,e,n,d,ge-1,-ge)}let x=0,I=1,Q=0;for(this[n]=e&255;++x<d&&(I*=256);)e<0&&Q===0&&this[n+x-1]!==0&&(Q=1),this[n+x]=(e/I>>0)-Q&255;return n+d},u.prototype.writeIntBE=function(e,n,d,y){if(e=+e,n=n>>>0,!y){const ge=Math.pow(2,8*d-1);v(this,e,n,d,ge-1,-ge)}let x=d-1,I=1,Q=0;for(this[n+x]=e&255;--x>=0&&(I*=256);)e<0&&Q===0&&this[n+x+1]!==0&&(Q=1),this[n+x]=(e/I>>0)-Q&255;return n+d},u.prototype.writeInt8=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,1,127,-128),e<0&&(e=255+e+1),this[n]=e&255,n+1},u.prototype.writeInt16LE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,2,32767,-32768),this[n]=e&255,this[n+1]=e>>>8,n+2},u.prototype.writeInt16BE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,2,32767,-32768),this[n]=e>>>8,this[n+1]=e&255,n+2},u.prototype.writeInt32LE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,4,2147483647,-2147483648),this[n]=e&255,this[n+1]=e>>>8,this[n+2]=e>>>16,this[n+3]=e>>>24,n+4},u.prototype.writeInt32BE=function(e,n,d){return e=+e,n=n>>>0,d||v(this,e,n,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),this[n]=e>>>24,this[n+1]=e>>>16,this[n+2]=e>>>8,this[n+3]=e&255,n+4},u.prototype.writeBigInt64LE=ne(function(e,n=0){return b(this,e,n,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))}),u.prototype.writeBigInt64BE=ne(function(e,n=0){return w(this,e,n,-BigInt("0x8000000000000000"),BigInt("0x7fffffffffffffff"))});function B(a,e,n,d,y,x){if(n+d>a.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function L(a,e,n,d,y){return e=+e,n=n>>>0,y||B(a,e,n,4),i.write(a,e,n,d,23,4),n+4}u.prototype.writeFloatLE=function(e,n,d){return L(this,e,n,!0,d)},u.prototype.writeFloatBE=function(e,n,d){return L(this,e,n,!1,d)};function k(a,e,n,d,y){return e=+e,n=n>>>0,y||B(a,e,n,8),i.write(a,e,n,d,52,8),n+8}u.prototype.writeDoubleLE=function(e,n,d){return k(this,e,n,!0,d)},u.prototype.writeDoubleBE=function(e,n,d){return k(this,e,n,!1,d)},u.prototype.copy=function(e,n,d,y){if(!u.isBuffer(e))throw new TypeError("argument should be a Buffer");if(d||(d=0),!y&&y!==0&&(y=this.length),n>=e.length&&(n=e.length),n||(n=0),y>0&&y<d&&(y=d),y===d||e.length===0||this.length===0)return 0;if(n<0)throw new RangeError("targetStart out of bounds");if(d<0||d>=this.length)throw new RangeError("Index out of range");if(y<0)throw new RangeError("sourceEnd out of bounds");y>this.length&&(y=this.length),e.length-n<y-d&&(y=e.length-n+d);const x=y-d;return this===e&&typeof Uint8Array.prototype.copyWithin=="function"?this.copyWithin(n,d,y):Uint8Array.prototype.set.call(e,this.subarray(d,y),n),x},u.prototype.fill=function(e,n,d,y){if(typeof e=="string"){if(typeof n=="string"?(y=n,n=0,d=this.length):typeof d=="string"&&(y=d,d=this.length),y!==void 0&&typeof y!="string")throw new TypeError("encoding must be a string");if(typeof y=="string"&&!u.isEncoding(y))throw new TypeError("Unknown encoding: "+y);if(e.length===1){const I=e.charCodeAt(0);(y==="utf8"&&I<128||y==="latin1")&&(e=I)}}else typeof e=="number"?e=e&255:typeof e=="boolean"&&(e=Number(e));if(n<0||this.length<n||this.length<d)throw new RangeError("Out of range index");if(d<=n)return this;n=n>>>0,d=d===void 0?this.length:d>>>0,e||(e=0);let x;if(typeof e=="number")for(x=n;x<d;++x)this[x]=e;else{const I=u.isBuffer(e)?e:u.from(e,y),Q=I.length;if(Q===0)throw new TypeError('The value "'+e+'" is invalid for argument "value"');for(x=0;x<d-n;++x)this[x+n]=I[x%Q]}return this};const p={};function h(a,e,n){p[a]=class extends n{constructor(){super(),Object.defineProperty(this,"message",{value:e.apply(this,arguments),writable:!0,configurable:!0}),this.name=`${this.name} [${a}]`,this.stack,delete this.name}get code(){return a}set code(y){Object.defineProperty(this,"code",{configurable:!0,enumerable:!0,value:y,writable:!0})}toString(){return`${this.name} [${a}]: ${this.message}`}}}h("ERR_BUFFER_OUT_OF_BOUNDS",function(a){return a?`${a} is outside of buffer bounds`:"Attempt to access memory outside buffer bounds"},RangeError),h("ERR_INVALID_ARG_TYPE",function(a,e){return`The "${a}" argument must be of type number. Received type ${typeof e}`},TypeError),h("ERR_OUT_OF_RANGE",function(a,e,n){let d=`The value of "${a}" is out of range.`,y=n;return Number.isInteger(n)&&Math.abs(n)>2**32?y=o(String(n)):typeof n=="bigint"&&(y=String(n),(n>BigInt(2)**BigInt(32)||n<-(BigInt(2)**BigInt(32)))&&(y=o(y)),y+="n"),d+=` It must be ${e}. Received ${y}`,d},RangeError);function o(a){let e="",n=a.length;const d=a[0]==="-"?1:0;for(;n>=d+4;n-=3)e=`_${a.slice(n-3,n)}${e}`;return`${a.slice(0,n)}${e}`}function g(a,e,n){E(e,"offset"),(a[e]===void 0||a[e+n]===void 0)&&C(e,a.length-(n+1))}function f(a,e,n,d,y,x){if(a>n||a<e){const I=typeof e=="bigint"?"n":"";let Q;throw e===0||e===BigInt(0)?Q=`>= 0${I} and < 2${I} ** ${(x+1)*8}${I}`:Q=`>= -(2${I} ** ${(x+1)*8-1}${I}) and < 2 ** ${(x+1)*8-1}${I}`,new p.ERR_OUT_OF_RANGE("value",Q,a)}g(d,y,x)}function E(a,e){if(typeof a!="number")throw new p.ERR_INVALID_ARG_TYPE(e,"number",a)}function C(a,e,n){throw Math.floor(a)!==a?(E(a,n),new p.ERR_OUT_OF_RANGE("offset","an integer",a)):e<0?new p.ERR_BUFFER_OUT_OF_BOUNDS:new p.ERR_OUT_OF_RANGE("offset",`>= 0 and <= ${e}`,a)}const G=/[^+/0-9A-Za-z-_]/g;function J(a){if(a=a.split("=")[0],a=a.trim().replace(G,""),a.length<2)return"";for(;a.length%4!==0;)a=a+"=";return a}function ee(a,e){e=e||1/0;let n;const d=a.length;let y=null;const x=[];for(let I=0;I<d;++I){if(n=a.charCodeAt(I),n>55295&&n<57344){if(!y){if(n>56319){(e-=3)>-1&&x.push(239,191,189);continue}else if(I+1===d){(e-=3)>-1&&x.push(239,191,189);continue}y=n;continue}if(n<56320){(e-=3)>-1&&x.push(239,191,189),y=n;continue}n=(y-55296<<10|n-56320)+65536}else y&&(e-=3)>-1&&x.push(239,191,189);if(y=null,n<128){if((e-=1)<0)break;x.push(n)}else if(n<2048){if((e-=2)<0)break;x.push(n>>6|192,n&63|128)}else if(n<65536){if((e-=3)<0)break;x.push(n>>12|224,n>>6&63|128,n&63|128)}else if(n<1114112){if((e-=4)<0)break;x.push(n>>18|240,n>>12&63|128,n>>6&63|128,n&63|128)}else throw new Error("Invalid code point")}return x}function S(a){const e=[];for(let n=0;n<a.length;++n)e.push(a.charCodeAt(n)&255);return e}function j(a,e){let n,d,y;const x=[];for(let I=0;I<a.length&&!((e-=2)<0);++I)n=a.charCodeAt(I),d=n>>8,y=n%256,x.push(y),x.push(d);return x}function T(a){return t.toByteArray(J(a))}function X(a,e,n,d){let y;for(y=0;y<d&&!(y+n>=e.length||y>=a.length);++y)e[y+n]=a[y];return y}function q(a,e){return a instanceof e||a!=null&&a.constructor!=null&&a.constructor.name!=null&&a.constructor.name===e.name}function re(a){return a!==a}const de=function(){const a="0123456789abcdef",e=new Array(256);for(let n=0;n<16;++n){const d=n*16;for(let y=0;y<16;++y)e[d+y]=a[n]+a[y]}return e}();function ne(a){return typeof BigInt>"u"?pe:a}function pe(){throw new Error("BigInt not supported")}})(At);var _t={exports:{}},he=_t.exports={},Be,Ie;function dt(){throw new Error("setTimeout has not been defined")}function lt(){throw new Error("clearTimeout has not been defined")}(function(){try{typeof setTimeout=="function"?Be=setTimeout:Be=dt}catch{Be=dt}try{typeof clearTimeout=="function"?Ie=clearTimeout:Ie=lt}catch{Ie=lt}})();function jt(r){if(Be===setTimeout)return setTimeout(r,0);if((Be===dt||!Be)&&setTimeout)return Be=setTimeout,setTimeout(r,0);try{return Be(r,0)}catch{try{return Be.call(null,r,0)}catch{return Be.call(this,r,0)}}}function yn(r){if(Ie===clearTimeout)return clearTimeout(r);if((Ie===lt||!Ie)&&clearTimeout)return Ie=clearTimeout,clearTimeout(r);try{return Ie(r)}catch{try{return Ie.call(null,r)}catch{return Ie.call(this,r)}}}var $e=[],ze=!1,Me,He=-1;function hn(){!ze||!Me||(ze=!1,Me.length?$e=Me.concat($e):He=-1,$e.length&&Ft())}function Ft(){if(!ze){var r=jt(hn);ze=!0;for(var t=$e.length;t;){for(Me=$e,$e=[];++He<t;)Me&&Me[He].run();He=-1,t=$e.length}Me=null,ze=!1,yn(r)}}he.nextTick=function(r){var t=new Array(arguments.length-1);if(arguments.length>1)for(var i=1;i<arguments.length;i++)t[i-1]=arguments[i];$e.push(new Pt(r,t)),$e.length===1&&!ze&&jt(Ft)};function Pt(r,t){this.fun=r,this.array=t}Pt.prototype.run=function(){this.fun.apply(null,this.array)};he.title="browser";he.browser=!0;he.env={};he.argv=[];he.version="";he.versions={};function Te(){}he.on=Te;he.addListener=Te;he.once=Te;he.off=Te;he.removeListener=Te;he.removeAllListeners=Te;he.emit=Te;he.prependListener=Te;he.prependOnceListener=Te;he.listeners=function(r){return[]};he.binding=function(r){throw new Error("process.binding is not supported")};he.cwd=function(){return"/"};he.chdir=function(r){throw new Error("process.chdir is not supported")};he.umask=function(){return 0};var fn=_t.exports;const xn=on(fn);window.Buffer=At.Buffer;window.process=xn;window.global=window;const Ze="http://localhost:3000/api";class Je{async login(t,i){const s=await fetch(`${Ze}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:t,password:i})});if(!s.ok){const m=await s.json();throw new Error(m.message)}return await s.json()}async register(t,i){const s=await fetch(`${Ze}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:t,password:i})});if(!s.ok){const m=await s.json();throw new Error(m.message)}return await s.json()}async getCurrentUser(){const t=localStorage.getItem("token"),i=await fetch(`${Ze}/auth/me`,{headers:{Authorization:`Bearer ${t}`}});if(!i.ok)throw new Error("获取用户信息失败");return(await i.json()).user}logout(){localStorage.removeItem("token"),window.location.reload()}}class bn{constructor(){this.ws=null,this.listeners=new Map}connect(t){this.ws=new WebSocket("ws://localhost:3000"),this.ws.onopen=()=>{console.log("✅ WebSocket 连接成功"),this.send({type:"auth",token:t})},this.ws.onmessage=i=>{const s=JSON.parse(i.data);this.notifyListeners(s.type,s)},this.ws.onerror=i=>{console.error("❌ WebSocket 错误:",i)},this.ws.onclose=()=>{console.log("🔌 WebSocket 连接关闭"),setTimeout(()=>this.connect(t),3e3)}}send(t){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(t))}on(t,i){this.listeners.has(t)||this.listeners.set(t,[]),this.listeners.get(t).push(i)}off(t,i){if(this.listeners.has(t)){const s=this.listeners.get(t),m=s.indexOf(i);m>-1&&s.splice(m,1)}}notifyListeners(t,i){this.listeners.has(t)&&this.listeners.get(t).forEach(s=>s(i))}joinGroup(t){this.send({type:"join_group",groupId:t})}sendChatMessage(t,i,s){this.send({type:"chat_message",groupId:t,username:i,content:s})}syncDocument(t,i,s){this.send({type:"document_sync",documentId:t,content:i,cursorPosition:s})}respondToCall(t,i){this.send({type:"call_response",groupId:t,username:i})}sendTyping(t,i,s){this.send({type:"typing",documentId:t,username:i,isTyping:s})}sendWhiteboardDraw(t,i){this.send({type:"whiteboard_draw",groupId:t,...i})}sendWhiteboardClear(t){this.send({type:"whiteboard_clear",groupId:t})}}function vn(r){const t=document.getElementById("app"),i=new Je;t.innerHTML=`
|
|
2
7
|
<div class="login-container">
|
|
3
8
|
<div class="login-card">
|
|
4
9
|
<div class="login-header">
|
|
@@ -48,10 +53,10 @@
|
|
|
48
53
|
</div>
|
|
49
54
|
</div>
|
|
50
55
|
</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",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
|
-
--num-groups: ${
|
|
53
|
-
--indicator-opacity: ${
|
|
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=`
|
|
56
|
+
`,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 s=>{s.preventDefault();const m=new FormData(s.target),l=m.get("username"),c=m.get("password");try{const u=await i.login(l,c);r(u.user,u.token)}catch(u){document.getElementById("loginError").textContent=u.message}}),document.getElementById("registerForm").addEventListener("submit",async s=>{s.preventDefault();const m=new FormData(s.target),l=m.get("username"),c=m.get("password");try{const u=await i.register(l,c);r(u.user,u.token)}catch(u){document.getElementById("registerError").textContent=u.message}})}const Qe="http://localhost:3000/api";class Ut{constructor(){this.token=localStorage.getItem("token")}async request(t,i={}){const s=await fetch(`${Qe}${t}`,{...i,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`,...i.headers}});if(!s.ok){const m=await s.json().catch(()=>({message:"请求失败"}));throw console.error("API 错误:",{endpoint:t,status:s.status,error:m}),new Error(m.message||`请求失败: ${s.status}`)}return await s.json()}async getGroups(){return await this.request("/groups")}async getAllGroups(){return await this.request("/groups/all")}async getGroup(t){return await this.request(`/groups/${t}`)}async createGroup(t,i,s){return await this.request("/groups",{method:"POST",body:JSON.stringify({name:t,description:i,members:s})})}async joinGroup(t){return await this.request(`/groups/${t}/join`,{method:"POST"})}async leaveGroup(t){return await this.request(`/groups/${t}/leave`,{method:"POST"})}async addMember(t,i){return await this.request(`/groups/${t}/members`,{method:"POST",body:JSON.stringify({userId:i})})}async removeMember(t,i){return await this.request(`/groups/${t}/members/${i}`,{method:"DELETE"})}async setMuteAll(t,i){return await this.request(`/groups/${t}/mute/all`,{method:"POST",body:JSON.stringify({enabled:i})})}async setUserMute(t,i,s){return await this.request(`/groups/${t}/mute/users/${i}`,{method:"POST",body:JSON.stringify({muted:s})})}async getAllUsers(){return await this.request("/auth/users")}async getGroupMessages(t){return await this.request(`/groups/${t}/messages`)}async randomCall(t,i=1){return await this.request(`/groups/${t}/call`,{method:"POST",body:JSON.stringify({count:i})})}async clearGroupMessages(t){return await this.request(`/groups/${t}/messages`,{method:"DELETE",body:JSON.stringify({deleteAll:!0})})}async getTasks(t){return await this.request(`/tasks/group/${t}`)}async getMyTasks(){return await this.request("/tasks/my")}async createTask(t){return await this.request("/tasks",{method:"POST",body:JSON.stringify(t)})}async updateTaskStatus(t,i){return await this.request(`/tasks/${t}/status`,{method:"PATCH",body:JSON.stringify({status:i})})}async deleteTask(t){return await this.request(`/tasks/${t}`,{method:"DELETE"})}async getDocuments(t){return await this.request(`/documents/group/${t}`)}async getDocument(t){return await this.request(`/documents/${t}`)}async createDocument(t,i,s,m=[]){return await this.request("/documents",{method:"POST",body:JSON.stringify({title:t,content:i,groupId:s,editableMembers:m})})}async updateDocument(t,i){return await this.request(`/documents/${t}`,{method:"PATCH",body:JSON.stringify({content:i})})}async updateDocumentPermissions(t,i){return await this.request(`/documents/${t}/permissions`,{method:"PATCH",body:JSON.stringify({editableMembers:i})})}async getDocumentVersions(t){return await this.request(`/documents/${t}/versions`)}async deleteDocument(t){return await this.request(`/documents/${t}`,{method:"DELETE"})}async getAuditLogs(t={},i={}){const s=new URLSearchParams;Object.keys(t).forEach(l=>{t[l]&&s.append(l,t[l])}),Object.keys(i).forEach(l=>{i[l]&&s.append(l,i[l])});const m=s.toString();return await this.request(`/audit${m?"?"+m:""}`)}async getUserActivityStats(t,i={}){const m=new URLSearchParams(i).toString();return await this.request(`/audit/user-stats/${t}${m?"?"+m:""}`)}async getDocumentEditHistory(t,i=20){return await this.request(`/audit/document-history/${t}?limit=${i}`)}async getGroupAuditLogs(t,i={},s={}){const m=new URLSearchParams;Object.keys(i).forEach(c=>{i[c]&&m.append(c,i[c])}),Object.keys(s).forEach(c=>{s[c]&&m.append(c,s[c])});const l=m.toString();return await this.request(`/audit/group/${t}${l?"?"+l:""}`)}async getAuditSummary(t={}){const s=new URLSearchParams(t).toString();return await this.request(`/audit/stats/summary${s?"?"+s:""}`)}async getAuditLogDetail(t){return await this.request(`/audit/${t}`)}async clearAuditLogs(t={}){return await this.request("/audit",{method:"DELETE",body:JSON.stringify(t)})}async uploadFile(t,i,s=""){const m=new FormData;m.append("file",i),m.append("groupId",t),s&&m.append("description",s);const l=await fetch(`${Qe}/files/upload`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:m});if(!l.ok){const c=await l.json().catch(()=>({message:"上传失败"}));throw new Error(c.message||"上传失败")}return await l.json()}async getGroupFiles(t){return await this.request(`/files/group/${t}`)}async deleteFile(t){return await this.request(`/files/${t}`,{method:"DELETE"})}getFileDownloadUrl(t){return`${Qe}/files/${t}/download?token=${this.token}`}async createPoll(t){return await this.request("/polls",{method:"POST",body:JSON.stringify(t)})}async getGroupPolls(t){return await this.request(`/polls/group/${t}`)}async getPoll(t){return await this.request(`/polls/${t}`)}async vote(t,i){return await this.request(`/polls/${t}/vote`,{method:"POST",body:JSON.stringify({optionIndexes:i})})}async endPoll(t){return await this.request(`/polls/${t}/end`,{method:"PUT"})}async deletePoll(t){return await this.request(`/polls/${t}`,{method:"DELETE"})}}function Ne(r){if(typeof r!="string"||!r)throw new Error("expected a non-empty string, got: "+r)}function et(r){if(typeof r!="number")throw new Error("expected a number, got: "+r)}const wn=1,kn=1,De="emoji",je="keyvalue",ht="favorites",En="tokens",Nt="tokens",Bn="unicode",Ot="count",In="group",Ln="order",Rt="group-order",ct="eTag",We="url",kt="skinTone",Fe="readonly",ft="readwrite",Ht="skinUnicodes",$n="skinUnicodes",Tn="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Sn="en";function Mn(r,t){const i=new Set,s=[];for(const m of r){const l=t(m);i.has(l)||(i.add(l),s.push(m))}return s}function Et(r){return Mn(r,t=>t.unicode)}function Cn(r){function t(i,s,m){const l=s?r.createObjectStore(i,{keyPath:s}):r.createObjectStore(i);if(m)for(const[c,[u,F]]of Object.entries(m))l.createIndex(c,u,{multiEntry:F});return l}t(je),t(De,Bn,{[Nt]:[En,!0],[Rt]:[[In,Ln]],[Ht]:[$n,!0]}),t(ht,void 0,{[Ot]:[""]})}const pt={},qe={},Ve={};function qt(r,t,i){i.onerror=()=>t(i.error),i.onblocked=()=>t(new Error("IDB blocked")),i.onsuccess=()=>r(i.result)}async function Dn(r){const t=await new Promise((i,s)=>{const m=indexedDB.open(r,wn);pt[r]=m,m.onupgradeneeded=l=>{l.oldVersion<kn&&Cn(m.result)},qt(i,s,m)});return t.onclose=()=>xt(r),t}function An(r){return qe[r]||(qe[r]=Dn(r)),qe[r]}function Se(r,t,i,s){return new Promise((m,l)=>{const c=r.transaction(t,i,{durability:"relaxed"}),u=typeof t=="string"?c.objectStore(t):t.map(D=>c.objectStore(D));let F;s(u,c,D=>{F=D}),c.oncomplete=()=>m(F),c.onerror=()=>l(c.error)})}function xt(r){const t=pt[r],i=t&&t.result;if(i){i.close();const s=Ve[r];if(s)for(const m of s)m()}delete pt[r],delete qe[r],delete Ve[r]}function zn(r){return new Promise((t,i)=>{xt(r);const s=indexedDB.deleteDatabase(r);qt(t,i,s)})}function _n(r,t){let i=Ve[r];i||(i=Ve[r]=[]),i.push(t)}const jn=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 _e(r){return r.split(/[\s_]+/).map(t=>!t.match(/\w/)||jn.has(t)?t.toLowerCase():t.replace(/[)(:,]/g,"").replace(/’/g,"'").toLowerCase()).filter(Boolean)}const Fn=2;function Gt(r){return r.filter(Boolean).map(t=>t.toLowerCase()).filter(t=>t.length>=Fn)}function Pn(r){return r.map(({annotation:i,emoticon:s,group:m,order:l,shortcodes:c,skins:u,tags:F,emoji:D,version:z})=>{const R=[...new Set(Gt([...(c||[]).map(_e).flat(),...(F||[]).map(_e).flat(),..._e(i),s]))].sort(),U={annotation:i,group:m,order:l,tags:F,tokens:R,unicode:D,version:z};if(s&&(U.emoticon=s),c&&(U.shortcodes=c),u){U.skinTones=[],U.skinUnicodes=[],U.skinVersions=[];for(const{tone:W,emoji:ue,version:oe}of u)U.skinTones.push(W),U.skinUnicodes.push(ue),U.skinVersions.push(oe)}return U})}function Wt(r,t,i,s){r[t](i).onsuccess=m=>s&&s(m.target.result)}function Ce(r,t,i){Wt(r,"get",t,i)}function Vt(r,t,i){Wt(r,"getAll",t,i)}function bt(r){r.commit&&r.commit()}function Un(r,t){let i=r[0];for(let s=1;s<r.length;s++){const m=r[s];t(i)>t(m)&&(i=m)}return i}function Yt(r,t){const i=Un(r,m=>m.length),s=[];for(const m of i)r.some(l=>l.findIndex(c=>t(c)===t(m))===-1)||s.push(m);return s}async function Nn(r){return!await vt(r,je,We)}async function On(r,t,i){const[s,m]=await Promise.all([ct,We].map(l=>vt(r,je,l)));return s===i&&m===t}async function Rn(r,t){return Se(r,De,Fe,(s,m,l)=>{let c;const u=()=>{s.getAll(c&&IDBKeyRange.lowerBound(c,!0),50).onsuccess=F=>{const D=F.target.result;for(const z of D)if(c=z.unicode,t(z))return l(z);if(D.length<50)return l();u()}};u()})}async function Kt(r,t,i,s){try{const m=Pn(t);await Se(r,[De,je],ft,([l,c],u)=>{let F,D,z=0;function R(){++z===2&&U()}function U(){if(!(F===s&&D===i)){l.clear();for(const W of m)l.put(W);c.put(s,ct),c.put(i,We),bt(u)}}Ce(c,ct,W=>{F=W,R()}),Ce(c,We,W=>{D=W,R()})})}finally{}}async function Hn(r,t){return Se(r,De,Fe,(i,s,m)=>{const l=IDBKeyRange.bound([t,0],[t+1,0],!1,!0);Vt(i.index(Rt),l,m)})}async function Jt(r,t){const i=Gt(_e(t));return i.length?Se(r,De,Fe,(s,m,l)=>{const c=[],u=()=>{c.length===i.length&&F()},F=()=>{const D=Yt(c,z=>z.unicode);l(D.sort((z,R)=>z.order<R.order?-1:1))};for(let D=0;D<i.length;D++){const z=i[D],R=D===i.length-1?IDBKeyRange.bound(z,z+"",!1,!0):IDBKeyRange.only(z);Vt(s.index(Nt),R,U=>{c.push(U),u()})}}):[]}async function qn(r,t){const i=await Jt(r,t);return i.length?i.filter(s=>(s.shortcodes||[]).map(l=>l.toLowerCase()).includes(t.toLowerCase()))[0]||null:await Rn(r,m=>(m.shortcodes||[]).includes(t.toLowerCase()))||null}async function Gn(r,t){return Se(r,De,Fe,(i,s,m)=>Ce(i,t,l=>{if(l)return m(l);Ce(i.index(Ht),t,c=>m(c||null))}))}function vt(r,t,i){return Se(r,t,Fe,(s,m,l)=>Ce(s,i,l))}function Wn(r,t,i,s){return Se(r,t,ft,(m,l)=>{m.put(s,i),bt(l)})}function Vn(r,t){return Se(r,ht,ft,(i,s)=>Ce(i,t,m=>{i.put((m||0)+1,t),bt(s)}))}function Yn(r,t,i){return i===0?[]:Se(r,[ht,De],Fe,([s,m],l,c)=>{const u=[];s.index(Ot).openCursor(void 0,"prev").onsuccess=F=>{const D=F.target.result;if(!D)return c(u);function z(W){if(u.push(W),u.length===i)return c(u);D.continue()}const R=D.primaryKey,U=t.byName(R);if(U)return z(U);Ce(m,R,W=>{if(W)return z(W);D.continue()})}})}const Oe="";function Kn(r,t){const i=new Map;for(const m of r){const l=t(m);for(const c of l){let u=i;for(let D=0;D<c.length;D++){const z=c.charAt(D);let R=u.get(z);R||(R=new Map,u.set(z,R)),u=R}let F=u.get(Oe);F||(F=[],u.set(Oe,F)),F.push(m)}}return(m,l)=>{let c=i;for(let D=0;D<m.length;D++){const z=m.charAt(D),R=c.get(z);if(R)c=R;else return[]}if(l)return c.get(Oe)||[];const u=[],F=[c];for(;F.length;){const z=[...F.shift().entries()].sort((R,U)=>R[0]<U[0]?-1:1);for(const[R,U]of z)R===Oe?u.push(...U):F.push(U)}return u}}const Jn=["name","url"];function Xn(r){const t=r&&Array.isArray(r),i=t&&r.length&&(!r[0]||Jn.some(s=>!(s in r[0])));if(!t||i)throw new Error("Custom emojis are in the wrong format")}function Bt(r){Xn(r);const t=(U,W)=>U.name.toLowerCase()<W.name.toLowerCase()?-1:1,i=r.sort(t),m=Kn(r,U=>{const W=new Set;if(U.shortcodes)for(const ue of U.shortcodes)for(const oe of _e(ue))W.add(oe);return W}),l=U=>m(U,!0),c=U=>m(U,!1),u=U=>{const W=_e(U),ue=W.map((oe,fe)=>(fe<W.length-1?l:c)(oe));return Yt(ue,oe=>oe.name).sort(t)},F=new Map,D=new Map;for(const U of r){D.set(U.name.toLowerCase(),U);for(const W of U.shortcodes||[])F.set(W.toLowerCase(),U)}return{all:i,search:u,byShortcode:U=>F.get(U.toLowerCase()),byName:U=>D.get(U.toLowerCase())}}const Zn=typeof wrappedJSObject<"u";function Pe(r){if(!r)return r;if(Zn&&(r=structuredClone(r)),delete r.tokens,r.skinTones){const t=r.skinTones.length;r.skins=Array(t);for(let i=0;i<t;i++)r.skins[i]={tone:r.skinTones[i],unicode:r.skinUnicodes[i],version:r.skinVersions[i]};delete r.skinTones,delete r.skinUnicodes,delete r.skinVersions}return r}function Xt(r){r||console.warn("emoji-picker-element is more efficient if the dataSource server exposes an ETag header.")}const Qn=["annotation","emoji","group","order","version"];function er(r){if(!r||!Array.isArray(r)||!r[0]||typeof r[0]!="object"||Qn.some(t=>!(t in r[0])))throw new Error("Emoji data is in the wrong format")}function Zt(r,t){if(Math.floor(r.status/100)!==2)throw new Error("Failed to fetch: "+t+": "+r.status)}async function tr(r){const t=await fetch(r,{method:"HEAD"});Zt(t,r);const i=t.headers.get("etag");return Xt(i),i}async function ut(r){const t=await fetch(r);Zt(t,r);const i=t.headers.get("etag");Xt(i);const s=await t.json();return er(s),[i,s]}function nr(r){for(var t="",i=new Uint8Array(r),s=i.byteLength,m=-1;++m<s;)t+=String.fromCharCode(i[m]);return t}function rr(r){for(var t=r.length,i=new ArrayBuffer(t),s=new Uint8Array(i),m=-1;++m<t;)s[m]=r.charCodeAt(m);return i}async function Qt(r){const t=JSON.stringify(r);let i=rr(t);const s=await crypto.subtle.digest("SHA-1",i),m=nr(s);return btoa(m)}async function ar(r,t){let i,s=await tr(t);if(!s){const m=await ut(t);s=m[0],i=m[1],s||(s=await Qt(i))}await On(r,t,s)||(i||(i=(await ut(t))[1]),await Kt(r,i,t,s))}async function ir(r,t){let[i,s]=await ut(t);i||(i=await Qt(s)),await Kt(r,s,t,i)}async function or(r,t){try{await ar(r,t)}catch(i){if(i.name!=="InvalidStateError")throw i}}class sr{constructor({dataSource:t=Tn,locale:i=Sn,customEmoji:s=[]}={}){this.dataSource=t,this.locale=i,this._dbName=`emoji-picker-element-${this.locale}`,this._db=void 0,this._lazyUpdate=void 0,this._custom=Bt(s),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){const t=this._db=await An(this._dbName);_n(this._dbName,this._clear);const i=this.dataSource;await Nn(t)?await ir(t,i):this._lazyUpdate=or(t,i)}async ready(){const t=async()=>(this._ready||(this._ready=this._init()),this._ready);await t(),this._db||await t()}async getEmojiByGroup(t){return et(t),await this.ready(),Et(await Hn(this._db,t)).map(Pe)}async getEmojiBySearchQuery(t){Ne(t),await this.ready();const i=this._custom.search(t),s=Et(await Jt(this._db,t)).map(Pe);return[...i,...s]}async getEmojiByShortcode(t){Ne(t),await this.ready();const i=this._custom.byShortcode(t);return i||Pe(await qn(this._db,t))}async getEmojiByUnicodeOrName(t){Ne(t),await this.ready();const i=this._custom.byName(t);return i||Pe(await Gn(this._db,t))}async getPreferredSkinTone(){return await this.ready(),await vt(this._db,je,kt)||0}async setPreferredSkinTone(t){return et(t),await this.ready(),Wn(this._db,je,kt,t)}async incrementFavoriteEmojiCount(t){return Ne(t),await this.ready(),Vn(this._db,t)}async getTopFavoriteEmoji(t){return et(t),await this.ready(),(await Yn(this._db,this._custom,t)).map(Pe)}set customEmoji(t){this._custom=Bt(t)}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 xt(this._dbName)}async delete(){await this._shutdown(),await zn(this._dbName)}}const gt=[[-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(([r,t,i])=>({id:r,emoji:t,name:i})),tt=gt.slice(1),dr=2,It=6,en=typeof requestIdleCallback=="function"?requestIdleCallback:setTimeout;function Lt(r){return r.unicode.includes("")}const lr={"":17,"":16,"🫨":15.1,"🫠":14,"🥲":13.1,"🥻":12.1,"🥰":11,"🤩":5,"👱♀️":4,"🤣":3,"👁️🗨️":2,"😀":1,"😐️":.7,"😃":.6},cr=1e3,pr="🖐️",ur=8,gr=["😊","😒","❤️","👍️","😍","😂","😭","☺️","😔","😩","😏","💕","🙌","😘"],tn='"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif',mr=(r,t)=>r<t?-1:r>t?1:0,$t=(r,t)=>{const i=document.createElement("canvas");i.width=i.height=1;const s=i.getContext("2d",{willReadFrequently:!0});return s.textBaseline="top",s.font=`100px ${tn}`,s.fillStyle=t,s.scale(.01,.01),s.fillText(r,0,0),s.getImageData(0,0,1,1).data},yr=(r,t)=>{const i=[...r].join(","),s=[...t].join(",");return i===s&&!i.startsWith("0,0,0,")};function hr(r){const t=$t(r,"#000"),i=$t(r,"#fff");return t&&i&&yr(t,i)}function fr(){const r=Object.entries(lr);try{for(const[t,i]of r)if(hr(t))return i}catch{}finally{}return r[0][1]}let nt;const rt=()=>(nt||(nt=new Promise(r=>en(()=>r(fr())))),nt),mt=new Map,xr="️",br="\uD83C",vr="",wr=127995,kr=57339;function Er(r,t){if(t===0)return r;const i=r.indexOf(vr);return i!==-1?r.substring(0,i)+String.fromCodePoint(wr+t-1)+r.substring(i):(r.endsWith(xr)&&(r=r.substring(0,r.length-1)),r+br+String.fromCodePoint(kr+t-1))}function Ee(r){r.preventDefault(),r.stopPropagation()}function at(r,t,i){return t+=r?-1:1,t<0?t=i.length-1:t>=i.length&&(t=0),t}function nn(r,t){const i=new Set,s=[];for(const m of r){const l=t(m);i.has(l)||(i.add(l),s.push(m))}return s}function Br(r,t){const i=s=>{const m={};for(const l of s)typeof l.tone=="number"&&l.version<=t&&(m[l.tone]=l.unicode);return m};return r.map(({unicode:s,skins:m,shortcodes:l,url:c,name:u,category:F,annotation:D})=>({unicode:s,name:u,shortcodes:l,url:c,category:F,annotation:D,id:s||u,skins:m&&i(m)}))}const Ge=requestAnimationFrame;let Ir=typeof ResizeObserver=="function";function Lr(r,t,i){let s;Ir?(s=new ResizeObserver(i),s.observe(r)):Ge(i),t.addEventListener("abort",()=>{s&&s.disconnect()})}function Tt(r){{const t=document.createRange();return t.selectNode(r.firstChild),t.getBoundingClientRect().width}}let it;function $r(r,t,i){let s=!0;for(const m of r){const l=i(m);if(!l)continue;const c=Tt(l);typeof it>"u"&&(it=Tt(t));const u=c/1.8<it;mt.set(m.unicode,u),u||(s=!1)}return s}function Tr(r){return nn(r,t=>t)}function Sr(r){r&&(r.scrollTop=0)}function Ue(r,t,i){let s=r.get(t);return s||(s=i(),r.set(t,s)),s}function St(r){return""+r}function Mr(r){const t=document.createElement("template");return t.innerHTML=r,t}const Cr=new WeakMap,Dr=new WeakMap,Ar=Symbol("un-keyed"),zr="replaceChildren"in Element.prototype;function _r(r,t){zr?r.replaceChildren(...t):(r.innerHTML="",r.append(...t))}function jr(r,t){let i=r.firstChild,s=0;for(;i;){if(t[s]!==i)return!0;i=i.nextSibling,s++}return s!==t.length}function Fr(r,t){const{targetNode:i}=t;let{targetParentNode:s}=t,m=!1;s?m=jr(s,r):(m=!0,t.targetNode=void 0,t.targetParentNode=s=i.parentNode),m&&_r(s,r)}function Pr(r,t){for(const i of t){const{targetNode:s,currentExpression:m,binding:{expressionIndex:l,attributeName:c,attributeValuePre:u,attributeValuePost:F}}=i,D=r[l];if(m!==D)if(i.currentExpression=D,c)if(D===null)s.removeAttribute(c);else{const z=u+St(D)+F;s.setAttribute(c,z)}else{let z;Array.isArray(D)?Fr(D,i):D instanceof Element?(z=D,s.replaceWith(z)):s.nodeValue=St(D),z&&(i.targetNode=z)}}}function Ur(r){let t="",i=!1,s=!1,m=-1;const l=new Map,c=[];let u=0;for(let D=0,z=r.length;D<z;D++){const R=r[D];if(t+=R.slice(u),D===z-1)break;for(let K=0;K<R.length;K++)switch(R.charAt(K)){case"<":{R.charAt(K+1)==="/"?c.pop():(i=!0,c.push(++m));break}case">":{i=!1,s=!1;break}case"=":{s=!0;break}}const U=c[c.length-1],W=Ue(l,U,()=>[]);let ue,oe,fe;if(s){const K=/(\S+)="?([^"=]*)$/.exec(R);ue=K[1],oe=K[2];const te=/^([^">]*)("?)/.exec(r[D+1]);fe=te[1],t=t.slice(0,-1*K[0].length),u=te[0].length}else u=0;const be={attributeName:ue,attributeValuePre:oe,attributeValuePost:fe,expressionIndex:D};W.push(be),!i&&!s&&(t+=" ")}return{template:Mr(t),elementsToBindings:l}}function Mt(r,t,i){for(let s=0;s<r.length;s++){const m=r[s],l=m.attributeName?t:t.firstChild,c={binding:m,targetNode:l,targetParentNode:void 0,currentExpression:void 0};i.push(c)}}function Nr(r,t){const i=[];let s;if(t.size===1&&(s=t.get(0)))Mt(s,r,i);else{const m=document.createTreeWalker(r,NodeFilter.SHOW_ELEMENT);let l=r,c=-1;do{const u=t.get(++c);u&&Mt(u,l,i)}while(l=m.nextNode())}return i}function Or(r){const{template:t,elementsToBindings:i}=Ue(Cr,r,()=>Ur(r)),s=t.cloneNode(!0).content.firstElementChild,m=Nr(s,i);return function(c){return Pr(c,m),s}}function Rr(r){const t=Ue(Dr,r,()=>new Map);let i=Ar;function s(l,...c){const u=Ue(t,l,()=>new Map);return Ue(u,i,()=>Or(l))(c)}function m(l,c,u){return l.map((F,D)=>{const z=i;i=u(F);try{return c(F,D)}finally{i=z}})}return{map:m,html:s}}function Hr(r,t,i,s,m,l,c,u,F){const{labelWithSkin:D,titleForEmoji:z,unicodeWithSkin:R}=i,{html:U,map:W}=Rr(t);function ue(K,te,me){return W(K,(se,ve)=>U`<button role="${te?"option":"menuitem"}" aria-selected="${te?ve===t.activeSearchItem:null}" aria-label="${D(se,t.currentSkinTone)}" title="${z(se)}" class="${"emoji"+(te&&ve===t.activeSearchItem?" active":"")+(se.unicode?"":" custom-emoji")}" id="${`${me}-${se.id}`}" style="${se.unicode?null:`--custom-emoji-background: url(${JSON.stringify(se.url)})`}">${se.unicode?R(se,t.currentSkinTone):""}</button>`,se=>`${me}-${se.id}`)}const fe=U`<section data-ref="rootElement" class="picker" aria-label="${t.i18n.regionLabel}" style="${t.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="${t.i18n.searchLabel}" autocapitalize="none" autocomplete="off" spellcheck="true" aria-expanded="${!!(t.searchMode&&t.currentEmojis.length)}" aria-controls="search-results" aria-describedby="search-description" aria-autocomplete="list" aria-activedescendant="${t.activeSearchItemId?`emo-${t.activeSearchItemId}`:null}" data-ref="searchElement" data-on-input="onSearchInput" data-on-keydown="onSearchKeydown"><label class="sr-only" for="search">${t.i18n.searchLabel}</label> <span id="search-description" class="sr-only">${t.i18n.searchDescription}</span></div><div class="skintone-button-wrapper ${t.skinTonePickerExpandedAfterAnimation?"expanded":""}"><button id="skintone-button" class="emoji ${t.skinTonePickerExpanded?"hide-focus":""}" aria-label="${t.skinToneButtonLabel}" title="${t.skinToneButtonLabel}" aria-describedby="skintone-description" aria-haspopup="listbox" aria-expanded="${t.skinTonePickerExpanded}" aria-controls="skintone-list" data-on-click="onClickSkinToneButton">${t.skinToneButtonText||""}</button></div><span id="skintone-description" class="sr-only">${t.i18n.skinToneDescription}</span><div data-ref="skinToneDropdown" id="skintone-list" class="skintone-list hide-focus ${t.skinTonePickerExpanded?"":"hidden no-animate"}" style="transform:translateY(${t.skinTonePickerExpanded?0:"calc(-1 * var(--num-skintones) * var(--total-emoji-size))"})" role="listbox" aria-label="${t.i18n.skinTonesLabel}" aria-activedescendant="skintone-${t.activeSkinTone}" aria-hidden="${!t.skinTonePickerExpanded}" tabIndex="-1" data-on-focusout="onSkinToneOptionsFocusOut" data-on-click="onSkinToneOptionsClick" data-on-keydown="onSkinToneOptionsKeydown" data-on-keyup="onSkinToneOptionsKeyup">${W(t.skinTones,(K,te)=>U`<div id="skintone-${te}" class="emoji ${te===t.activeSkinTone?"active":""}" aria-selected="${te===t.activeSkinTone}" role="option" title="${t.i18n.skinTones[te]}" aria-label="${t.i18n.skinTones[te]}">${K}</div>`,K=>K)}</div></div><div class="nav" role="tablist" style="grid-template-columns:repeat(${t.groups.length},1fr)" aria-label="${t.i18n.categoriesLabel}" data-on-keydown="onNavKeydown" data-on-click="onNavClick">${W(t.groups,K=>U`<button role="tab" class="nav-button" aria-controls="tab-${K.id}" aria-label="${t.i18n.categories[K.name]}" aria-selected="${!t.searchMode&&t.currentGroup.id===K.id}" title="${t.i18n.categories[K.name]}" data-group-id="${K.id}"><div class="nav-emoji emoji">${K.emoji}</div></button>`,K=>K.id)}</div><div class="indicator-wrapper"><div class="indicator" style="transform:translateX(${(t.isRtl?-1:1)*t.currentGroupIndex*100}%)"></div></div><div class="message ${t.message?"":"gone"}" role="alert" aria-live="polite">${t.message||""}</div><div data-ref="tabpanelElement" class="tabpanel ${!t.databaseLoaded||t.message?"gone":""}" role="${t.searchMode?"region":"tabpanel"}" aria-label="${t.searchMode?t.i18n.searchResultsLabel:t.i18n.categories[t.currentGroup.name]}" id="${t.searchMode?null:`tab-${t.currentGroup.id}`}" tabIndex="0" data-on-click="onEmojiClick"><div data-action="calculateEmojiGridStyle">${W(t.currentEmojisWithCategories,(K,te)=>U`<div><div id="menu-label-${te}" class="category ${t.currentEmojisWithCategories.length===1&&t.currentEmojisWithCategories[0].category===""?"gone":""}" aria-hidden="true">${t.searchMode?t.i18n.searchResultsLabel:K.category?K.category:t.currentEmojisWithCategories.length>1?t.i18n.categories.custom:t.i18n.categories[t.currentGroup.name]}</div><div class="emoji-menu ${te!==0&&!t.searchMode&&t.currentGroup.id===-1?"visibility-auto":""}" style="${`--num-rows: ${Math.ceil(K.emojis.length/t.numColumns)}`}" data-action="updateOnIntersection" role="${t.searchMode?"listbox":"menu"}" aria-labelledby="menu-label-${te}" id="${t.searchMode?"search-results":null}">${ue(K.emojis,t.searchMode,"emo")}</div></div>`,K=>K.category)}</div></div><div class="favorites onscreen emoji-menu ${t.message?"gone":""}" role="menu" aria-label="${t.i18n.favoritesLabel}" data-on-click="onEmojiClick">${ue(t.currentFavorites,!1,"fav")}</div><button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">😀</button></section>`,be=(K,te)=>{for(const me of r.querySelectorAll(`[${K}]`))te(me,me.getAttribute(K))};if(F){r.appendChild(fe);for(const K of["click","focusout","input","keydown","keyup"])be(`data-on-${K}`,(te,me)=>{te.addEventListener(K,s[me])});be("data-ref",(K,te)=>{l[te]=K}),c.addEventListener("abort",()=>{r.removeChild(fe)})}be("data-action",(K,te)=>{let me=u.get(te);me||u.set(te,me=new WeakSet),me.has(K)||(me.add(K),m[te](K))})}const Ye=typeof queueMicrotask=="function"?queueMicrotask:r=>Promise.resolve().then(r);function qr(r){let t=!1,i;const s=new Map,m=new Set;let l;const c=()=>{if(t)return;const D=[...m];m.clear();try{for(const z of D)z()}finally{l=!1,m.size&&(l=!0,Ye(c))}},u=new Proxy({},{get(D,z){if(i){let R=s.get(z);R||(R=new Set,s.set(z,R)),R.add(i)}return D[z]},set(D,z,R){if(D[z]!==R){D[z]=R;const U=s.get(z);if(U){for(const W of U)m.add(W);l||(l=!0,Ye(c))}}return!0}}),F=D=>{const z=()=>{const R=i;i=z;try{return D()}finally{i=R}};return z()};return r.addEventListener("abort",()=>{t=!0}),{state:u,createEffect:F}}function ot(r,t,i){if(r.length!==t.length)return!1;for(let s=0;s<r.length;s++)if(!i(r[s],t[s]))return!1;return!0}const Ct=new WeakMap;function Gr(r,t,i){{const s=r.closest(".tabpanel");let m=Ct.get(s);m||(m=new IntersectionObserver(i,{root:s,rootMargin:"50% 0px 50% 0px",threshold:0}),Ct.set(s,m),t.addEventListener("abort",()=>{m.disconnect()})),m.observe(r)}}const st=[],{assign:Re}=Object;function Wr(r,t){const i={},s=new AbortController,m=s.signal,{state:l,createEffect:c}=qr(m),u=new Map;Re(l,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),Re(l,t),Re(l,{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:ur,isRtl:!1,currentGroupIndex:0,groups:tt,databaseLoaded:!1,activeSearchItemId:void 0}),c(()=>{l.currentGroup!==l.groups[l.currentGroupIndex]&&(l.currentGroup=l.groups[l.currentGroupIndex])});const F=o=>{r.getElementById(o).focus()},D=o=>r.getElementById(`emo-${o.id}`),z=(o,g)=>{i.rootElement.dispatchEvent(new CustomEvent(o,{detail:g,bubbles:!0,composed:!0}))},R=(o,g)=>o.id===g.id,U=(o,g)=>{const{category:f,emojis:E}=o,{category:C,emojis:G}=g;return f!==C?!1:ot(E,G,R)},W=o=>{ot(l.currentEmojis,o,R)||(l.currentEmojis=o)},ue=o=>{l.searchMode!==o&&(l.searchMode=o)},oe=o=>{ot(l.currentEmojisWithCategories,o,U)||(l.currentEmojisWithCategories=o)},fe=(o,g)=>g&&o.skins&&o.skins[g]||o.unicode,te={labelWithSkin:(o,g)=>Tr([o.name||fe(o,g),o.annotation,...o.shortcodes||st].filter(Boolean)).join(", "),titleForEmoji:o=>o.annotation||(o.shortcodes||st).join(", "),unicodeWithSkin:fe},me={onClickSkinToneButton:B,onEmojiClick:v,onNavClick:xe,onNavKeydown:ie,onSearchKeydown:Y,onSkinToneOptionsClick:w,onSkinToneOptionsFocusOut:p,onSkinToneOptionsKeydown:L,onSkinToneOptionsKeyup:k,onSearchInput:h},se={calculateEmojiGridStyle:A,updateOnIntersection:V};let ve=!0;c(()=>{Hr(r,l,te,me,se,i,m,u,ve),ve=!1}),l.emojiVersion||rt().then(o=>{o||(l.message=l.i18n.emojiUnsupportedMessage)}),c(()=>{async function o(){let g=!1;const f=setTimeout(()=>{g=!0,l.message=l.i18n.loadingMessage},cr);try{await l.database.ready(),l.databaseLoaded=!0}catch(E){console.error(E),l.message=l.i18n.networkErrorMessage}finally{clearTimeout(f),g&&(g=!1,l.message="")}}l.database&&o()}),c(()=>{l.pickerStyle=`
|
|
57
|
+
--num-groups: ${l.groups.length};
|
|
58
|
+
--indicator-opacity: ${l.searchMode?0:1};
|
|
59
|
+
--num-skintones: ${It};`}),c(()=>{l.customEmoji&&l.database&&P()}),c(()=>{l.customEmoji&&l.customEmoji.length?l.groups!==gt&&(l.groups=gt):l.groups!==tt&&(l.currentGroupIndex&&l.currentGroupIndex--,l.groups=tt)}),c(()=>{async function o(){l.databaseLoaded&&(l.currentSkinTone=await l.database.getPreferredSkinTone())}o()}),c(()=>{l.skinTones=Array(It).fill().map((o,g)=>Er(l.skinToneEmoji,g))}),c(()=>{l.skinToneButtonText=l.skinTones[l.currentSkinTone]}),c(()=>{l.skinToneButtonLabel=l.i18n.skinToneLabel.replace("{skinTone}",l.i18n.skinTones[l.currentSkinTone])}),c(()=>{async function o(){const{database:g}=l,f=(await Promise.all(gr.map(E=>g.getEmojiByUnicodeOrName(E)))).filter(Boolean);l.defaultFavoriteEmojis=f}l.databaseLoaded&&o()});function P(){const{customEmoji:o,database:g}=l,f=o||st;g.customEmoji!==f&&(g.customEmoji=f)}c(()=>{async function o(){P();const{database:g,defaultFavoriteEmojis:f,numColumns:E}=l,C=await g.getTopFavoriteEmoji(E),G=await _(nn([...C,...f],J=>J.unicode||J.name).slice(0,E));l.currentFavorites=G}l.databaseLoaded&&l.defaultFavoriteEmojis&&o()});function A(o){Lr(o,m,()=>{{const g=getComputedStyle(i.rootElement),f=parseInt(g.getPropertyValue("--num-columns"),10),E=g.getPropertyValue("direction")==="rtl";l.numColumns=f,l.isRtl=E}})}function V(o){Gr(o,m,g=>{for(const{target:f,isIntersecting:E}of g)f.classList.toggle("onscreen",E)})}c(()=>{async function o(){const{searchText:g,currentGroup:f,databaseLoaded:E,customEmoji:C}=l;if(!E)l.currentEmojis=[],l.searchMode=!1;else if(g.length>=dr){const G=await Z(g);l.searchText===g&&(W(G),ue(!0))}else{const{id:G}=f;if(G!==-1||C&&C.length){const J=await O(G);l.currentGroup.id===G&&(W(J),ue(!1))}}}o()});const $=()=>{Ge(()=>Sr(i.tabpanelElement))};c(()=>{const{currentEmojis:o,emojiVersion:g}=l,f=o.filter(E=>E.unicode).filter(E=>Lt(E)&&!mt.has(E.unicode));if(!g&&f.length)W(o),Ge(()=>M(f));else{const E=g?o:o.filter(N);W(E),$()}});function M(o){$r(o,i.baselineEmoji,D)?$():l.currentEmojis=[...l.currentEmojis]}function N(o){return!o.unicode||!Lt(o)||mt.get(o.unicode)}async function H(o){const g=l.emojiVersion||await rt();return o.filter(({version:f})=>!f||f<=g)}async function _(o){return Br(o,l.emojiVersion||await rt())}async function O(o){const g=o===-1?l.customEmoji:await l.database.getEmojiByGroup(o);return _(await H(g))}async function Z(o){return _(await H(await l.database.getEmojiBySearchQuery(o)))}c(()=>{}),c(()=>{function o(){const{searchMode:f,currentEmojis:E}=l;if(f)return[{category:"",emojis:E}];const C=new Map;for(const G of E){const J=G.category||"";let ee=C.get(J);ee||(ee=[],C.set(J,ee)),ee.push(G)}return[...C.entries()].map(([G,J])=>({category:G,emojis:J})).sort((G,J)=>l.customCategorySorting(G.category,J.category))}const g=o();oe(g)}),c(()=>{l.activeSearchItemId=l.activeSearchItem!==-1&&l.currentEmojis[l.activeSearchItem].id}),c(()=>{const{rawSearchText:o}=l;en(()=>{l.searchText=(o||"").trim(),l.activeSearchItem=-1})});function Y(o){if(!l.searchMode||!l.currentEmojis.length)return;const g=f=>{Ee(o),l.activeSearchItem=at(f,l.activeSearchItem,l.currentEmojis)};switch(o.key){case"ArrowDown":return g(!1);case"ArrowUp":return g(!0);case"Enter":if(l.activeSearchItem===-1)l.activeSearchItem=0;else return Ee(o),ae(l.currentEmojis[l.activeSearchItem].id)}}function xe(o){const{target:g}=o,f=g.closest(".nav-button");if(!f)return;const E=parseInt(f.dataset.groupId,10);i.searchElement.value="",l.rawSearchText="",l.searchText="",l.activeSearchItem=-1,l.currentGroupIndex=l.groups.findIndex(C=>C.id===E)}function ie(o){const{target:g,key:f}=o,E=C=>{C&&(Ee(o),C.focus())};switch(f){case"ArrowLeft":return E(g.previousElementSibling);case"ArrowRight":return E(g.nextElementSibling);case"Home":return E(g.parentElement.firstElementChild);case"End":return E(g.parentElement.lastElementChild)}}async function we(o){const g=await l.database.getEmojiByUnicodeOrName(o),f=[...l.currentEmojis,...l.currentFavorites].find(C=>C.id===o),E=f.unicode&&fe(f,l.currentSkinTone);return await l.database.incrementFavoriteEmojiCount(o),{emoji:g,skinTone:l.currentSkinTone,...E&&{unicode:E},...f.name&&{name:f.name}}}async function ae(o){const g=we(o);z("emoji-click-sync",g),z("emoji-click",await g)}function v(o){const{target:g}=o;if(!g.classList.contains("emoji"))return;Ee(o);const f=g.id.substring(4);ae(f)}function b(o){l.currentSkinTone=o,l.skinTonePickerExpanded=!1,F("skintone-button"),z("skin-tone-change",{skinTone:o}),l.database.setPreferredSkinTone(o)}function w(o){const{target:{id:g}}=o,f=g&&g.match(/^skintone-(\d)/);if(!f)return;Ee(o);const E=parseInt(f[1],10);b(E)}function B(o){l.skinTonePickerExpanded=!l.skinTonePickerExpanded,l.activeSkinTone=l.currentSkinTone,l.skinTonePickerExpanded&&(Ee(o),Ge(()=>F("skintone-list")))}c(()=>{l.skinTonePickerExpanded?i.skinToneDropdown.addEventListener("transitionend",()=>{l.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):l.skinTonePickerExpandedAfterAnimation=!1});function L(o){if(!l.skinTonePickerExpanded)return;const g=async f=>{Ee(o),l.activeSkinTone=f};switch(o.key){case"ArrowUp":return g(at(!0,l.activeSkinTone,l.skinTones));case"ArrowDown":return g(at(!1,l.activeSkinTone,l.skinTones));case"Home":return g(0);case"End":return g(l.skinTones.length-1);case"Enter":return Ee(o),b(l.activeSkinTone);case"Escape":return Ee(o),l.skinTonePickerExpanded=!1,F("skintone-button")}}function k(o){if(l.skinTonePickerExpanded)switch(o.key){case" ":return Ee(o),b(l.activeSkinTone)}}async function p(o){const{relatedTarget:g}=o;(!g||g.id!=="skintone-list")&&(l.skinTonePickerExpanded=!1)}function h(o){l.rawSearchText=o.target.value}return{$set(o){Re(l,o)},$destroy(){s.abort()}}}const Vr="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Yr="en";var Kr={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"}},Jr=':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 rn=["customEmoji","customCategorySorting","database","dataSource","i18n","locale","skinToneEmoji","emojiVersion"],Xr=`:host{--emoji-font-family:${tn}}`;class wt extends HTMLElement{constructor(t){super(),this.attachShadow({mode:"open"});const i=document.createElement("style");i.textContent=Jr+Xr,this.shadowRoot.appendChild(i),this._ctx={locale:Yr,dataSource:Vr,skinToneEmoji:pr,customCategorySorting:mr,customEmoji:null,i18n:Kr,emojiVersion:null,...t};for(const s of rn)s!=="database"&&Object.prototype.hasOwnProperty.call(this,s)&&(this._ctx[s]=this[s],delete this[s]);this._dbFlush()}connectedCallback(){Dt(this),this._cmp||(this._cmp=Wr(this.shadowRoot,this._ctx))}disconnectedCallback(){Dt(this),Ye(()=>{if(!this.isConnected&&this._cmp){this._cmp.$destroy(),this._cmp=void 0;const{database:t}=this._ctx;t.close().catch(i=>console.error(i))}})}static get observedAttributes(){return["locale","data-source","skin-tone-emoji","emoji-version"]}attributeChangedCallback(t,i,s){this._set(t.replace(/-([a-z])/g,(m,l)=>l.toUpperCase()),t==="emoji-version"?parseFloat(s):s)}_set(t,i){this._ctx[t]=i,this._cmp&&this._cmp.$set({[t]:i}),["locale","dataSource"].includes(t)&&this._dbFlush()}_dbCreate(){const{locale:t,dataSource:i,database:s}=this._ctx;(!s||s.locale!==t||s.dataSource!==i)&&this._set("database",new sr({locale:t,dataSource:i}))}_dbFlush(){Ye(()=>this._dbCreate())}}const an={};for(const r of rn)an[r]={get(){return r==="database"&&this._dbCreate(),this._ctx[r]},set(t){if(r==="database")throw new Error("database is read-only");this._set(r,t)}};Object.defineProperties(wt.prototype,an);function Dt(r){r instanceof wt||Object.setPrototypeOf(r,customElements.get(r.tagName.toLowerCase()).prototype)}customElements.get("emoji-picker")||customElements.define("emoji-picker",wt);function Zr(r,t){const i=document.getElementById("app"),s=new Ut,m=new Je,l=r.id||r._id;let c=null,u=[];const F=localStorage.getItem("currentTheme")||"dark";Qr(F),i.innerHTML=`
|
|
55
60
|
<div class="dashboard">
|
|
56
61
|
<aside class="sidebar">
|
|
57
62
|
<div class="sidebar-header">
|
|
@@ -60,9 +65,9 @@
|
|
|
60
65
|
</div>
|
|
61
66
|
|
|
62
67
|
<div class="user-info">
|
|
63
|
-
<div class="avatar">${
|
|
68
|
+
<div class="avatar">${r.username[0].toUpperCase()}</div>
|
|
64
69
|
<div>
|
|
65
|
-
<div class="username">${
|
|
70
|
+
<div class="username">${r.username}</div>
|
|
66
71
|
<div class="user-role">管理员</div>
|
|
67
72
|
</div>
|
|
68
73
|
</div>
|
|
@@ -126,7 +131,7 @@
|
|
|
126
131
|
<div id="contentArea"></div>
|
|
127
132
|
</main>
|
|
128
133
|
</div>
|
|
129
|
-
`,document.querySelectorAll(".nav-item").forEach(
|
|
134
|
+
`,document.querySelectorAll(".nav-item").forEach(v=>{v.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(w=>w.classList.remove("active")),v.classList.add("active");const b=v.dataset.view;ae(b)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{m.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{ae("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{ae("help")});async function D(v){u=(await s.getGroups()).groups,v.innerHTML=`
|
|
130
135
|
<div class="view-header">
|
|
131
136
|
<h2>群组管理</h2>
|
|
132
137
|
<button class="btn-primary" id="createGroupBtn">创建群组</button>
|
|
@@ -168,47 +173,47 @@
|
|
|
168
173
|
<button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
|
|
169
174
|
</div>
|
|
170
175
|
</div>
|
|
171
|
-
`;const
|
|
172
|
-
<h3>${
|
|
173
|
-
<p>${
|
|
176
|
+
`;const w=document.getElementById("groupsList");u.forEach(B=>{const L=document.createElement("div");L.className="group-card",L.innerHTML=`
|
|
177
|
+
<h3>${B.name}</h3>
|
|
178
|
+
<p>${B.description||"暂无描述"}</p>
|
|
174
179
|
<div class="group-stats">
|
|
175
|
-
<span>👥 ${
|
|
176
|
-
<span>📄 ${
|
|
180
|
+
<span>👥 ${B.members.length} 成员</span>
|
|
181
|
+
<span>📄 ${B.documents.length} 文档</span>
|
|
177
182
|
</div>
|
|
178
183
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
179
|
-
<button class="btn-select" data-id="${
|
|
180
|
-
<button class="btn-secondary" data-id="${
|
|
184
|
+
<button class="btn-select" data-id="${B._id}">选择</button>
|
|
185
|
+
<button class="btn-secondary" data-id="${B._id}" data-action="manage">管理成员</button>
|
|
181
186
|
</div>
|
|
182
|
-
`,
|
|
187
|
+
`,w.appendChild(L)}),document.querySelectorAll(".btn-select").forEach(B=>{B.addEventListener("click",()=>{c=u.find(L=>L._id===B.dataset.id),t.joinGroup(c._id),alert(`已加入群组: ${c.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(B=>{B.addEventListener("click",async()=>{const L=B.dataset.id;await R(L)})}),document.getElementById("createGroupBtn").addEventListener("click",async()=>{document.getElementById("createGroupModal").classList.remove("hidden"),await z()}),document.getElementById("closeModal").addEventListener("click",()=>{document.getElementById("createGroupModal").classList.add("hidden")}),document.getElementById("closeMembersModal").addEventListener("click",()=>{document.getElementById("manageMembersModal").classList.add("hidden")}),document.getElementById("createGroupForm").addEventListener("submit",async B=>{B.preventDefault();const L=new FormData(B.target),k=Array.from(document.querySelectorAll("#usersList input:checked")).map(p=>p.value);try{const p=await s.createGroup(L.get("name"),L.get("description"),k);alert("群组创建成功!"),await D(v),document.getElementById("createGroupModal").classList.add("hidden")}catch(p){console.error("创建群组错误:",p),alert("创建失败: "+p.message)}})}async function z(){try{const v=await s.getAllUsers(),b=document.getElementById("usersList");b.innerHTML=v.users.map(w=>`
|
|
183
188
|
<label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
|
|
184
|
-
<input type="checkbox" value="${
|
|
185
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${
|
|
186
|
-
<span>${
|
|
189
|
+
<input type="checkbox" value="${w._id}">
|
|
190
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${w.username[0].toUpperCase()}</div>
|
|
191
|
+
<span>${w.username} (${w.role==="admin"?"管理员":"用户"})</span>
|
|
187
192
|
</label>
|
|
188
|
-
`).join("")}catch(
|
|
189
|
-
<h4>当前成员 (${
|
|
193
|
+
`).join("")}catch(v){console.error("加载用户失败:",v),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function R(v){try{const b=await s.getGroup(v),w=await s.getAllUsers(),B=b.group,L=document.getElementById("currentMembers");L.innerHTML=`
|
|
194
|
+
<h4>当前成员 (${B.members.length})</h4>
|
|
190
195
|
<div style="max-height: 200px; overflow-y: auto;">
|
|
191
|
-
${
|
|
196
|
+
${B.members.map(o=>`
|
|
192
197
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
193
198
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
194
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${
|
|
195
|
-
<span>${
|
|
199
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${o.username[0].toUpperCase()}</div>
|
|
200
|
+
<span>${o.username} ${o._id.toString()===B.admin._id.toString()?"(管理员)":""}</span>
|
|
196
201
|
</div>
|
|
197
|
-
${
|
|
202
|
+
${o._id.toString()!==B.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${v}', '${o._id}')">移除</button>`:""}
|
|
198
203
|
</div>
|
|
199
204
|
`).join("")}
|
|
200
205
|
</div>
|
|
201
|
-
`;const
|
|
206
|
+
`;const k=B.members.map(o=>o._id.toString()),p=w.users.filter(o=>!k.includes(o._id)),h=document.getElementById("availableUsers");p.length===0?h.innerHTML="<p>所有用户都已在群组中</p>":h.innerHTML=p.map(o=>`
|
|
202
207
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
|
|
203
208
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
204
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${
|
|
205
|
-
<span>${
|
|
209
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${o.username[0].toUpperCase()}</div>
|
|
210
|
+
<span>${o.username}</span>
|
|
206
211
|
</div>
|
|
207
|
-
<button class="btn-primary btn-sm" onclick="addMember('${
|
|
212
|
+
<button class="btn-primary btn-sm" onclick="addMember('${v}', '${o._id}')">添加</button>
|
|
208
213
|
</div>
|
|
209
|
-
`).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(
|
|
214
|
+
`).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(b){console.error("加载成员失败:",b),alert("加载失败: "+b.message)}}window.addMember=async(v,b)=>{try{await s.addMember(v,b),alert("成员添加成功!"),await R(v)}catch(w){alert("添加失败: "+w.message)}},window.removeMember=async(v,b)=>{if(confirm("确定要移除该成员吗?"))try{await s.removeMember(v,b),alert("成员移除成功!"),await R(v)}catch(w){alert("移除失败: "+w.message)}};async function U(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const b=await s.getTasks(c._id);v.innerHTML=`
|
|
210
215
|
<div class="view-header">
|
|
211
|
-
<h2>任务管理 - ${
|
|
216
|
+
<h2>任务管理 - ${c.name}</h2>
|
|
212
217
|
<button class="btn-primary" id="createTaskBtn">创建任务</button>
|
|
213
218
|
</div>
|
|
214
219
|
<div class="tasks-list" id="tasksList"></div>
|
|
@@ -248,25 +253,25 @@
|
|
|
248
253
|
</div>
|
|
249
254
|
</div>
|
|
250
255
|
</div>
|
|
251
|
-
`;const
|
|
256
|
+
`;const w=document.getElementById("tasksList");b.tasks.length===0?w.innerHTML='<div class="empty-state">暂无任务</div>':(b.tasks.forEach(B=>{const L=document.createElement("div");L.className=`task-card status-${B.status}`,L.innerHTML=`
|
|
252
257
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
253
258
|
<div style="flex: 1;">
|
|
254
|
-
<h3>${
|
|
255
|
-
<p>${
|
|
259
|
+
<h3>${B.title}</h3>
|
|
260
|
+
<p>${B.description||"无描述"}</p>
|
|
256
261
|
<div class="task-meta">
|
|
257
|
-
<span class="status-badge">${
|
|
258
|
-
<span>截止: ${
|
|
262
|
+
<span class="status-badge">${$(B.status)}</span>
|
|
263
|
+
<span>截止: ${B.deadline?new Date(B.deadline).toLocaleDateString():"无"}</span>
|
|
259
264
|
</div>
|
|
260
265
|
</div>
|
|
261
266
|
<div style="display: flex; gap: 10px;">
|
|
262
|
-
<button class="btn-primary btn-sm" data-id="${
|
|
263
|
-
<button class="btn-danger btn-sm" data-id="${
|
|
267
|
+
<button class="btn-primary btn-sm" data-id="${B._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
|
|
268
|
+
<button class="btn-danger btn-sm" data-id="${B._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
|
|
264
269
|
</div>
|
|
265
|
-
`,
|
|
270
|
+
`,w.appendChild(L)}),document.querySelectorAll('[data-action="delete-task"]').forEach(B=>{B.addEventListener("click",async L=>{L.stopPropagation();const k=B.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await s.deleteTask(k),alert("任务删除成功!"),await U(v)}catch(p){console.error("删除任务错误:",p),alert("删除失败: "+(p.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(B=>{B.addEventListener("click",async L=>{var h;L.stopPropagation();const k=B.dataset.id,p=b.tasks.find(o=>o._id===k);if(p){const o=document.getElementById("taskDetailContent"),g=f=>({pending:"#6366f1","in-progress":"#f59e0b",completed:"#10b981",cancelled:"#ef4444"})[f]||"#6366f1";o.innerHTML=`
|
|
266
271
|
<div class="task-detail" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-radius: 12px; padding: 16px;">
|
|
267
272
|
|
|
268
273
|
<!-- 任务标题 -->
|
|
269
|
-
<div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${
|
|
274
|
+
<div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${g(p.status)};">
|
|
270
275
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
|
|
271
276
|
<span style="font-size: 18px;">📌</span>
|
|
272
277
|
<h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务标题</h4>
|
|
@@ -292,9 +297,9 @@
|
|
|
292
297
|
<span style="font-size: 18px;">📊</span>
|
|
293
298
|
<h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务状态</h4>
|
|
294
299
|
</div>
|
|
295
|
-
<span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${
|
|
300
|
+
<span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${g(p.status)}; color: white; font-weight: 600; font-size: 13px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">
|
|
296
301
|
${p.status==="completed"?"✓":p.status==="in-progress"?"⟳":p.status==="cancelled"?"✕":"○"}
|
|
297
|
-
${
|
|
302
|
+
${$(p.status)}
|
|
298
303
|
</span>
|
|
299
304
|
</div>
|
|
300
305
|
|
|
@@ -319,14 +324,14 @@
|
|
|
319
324
|
${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
325
|
</div>
|
|
321
326
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
322
|
-
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(
|
|
323
|
-
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${
|
|
324
|
-
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${
|
|
327
|
+
${p.assignedTo&&p.assignedTo.length>0?p.assignedTo.map(f=>{var G,J;const E=p.completedBy&&p.completedBy.some(ee=>ee.user&&ee.user._id===f._id),C=E?p.completedBy.find(ee=>ee.user&&ee.user._id===f._id):null;return`
|
|
328
|
+
<div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${E?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${E?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${E?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
|
|
329
|
+
<div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${E?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${E?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((J=(G=f.username)==null?void 0:G[0])==null?void 0:J.toUpperCase())||"?"}</div>
|
|
325
330
|
<div style="display: flex; flex-direction: column; gap: 1px;">
|
|
326
|
-
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${
|
|
327
|
-
${
|
|
331
|
+
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${f.username||"未知用户"}</span>
|
|
332
|
+
${E?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${C&&C.completedAt?new Date(C.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
|
|
328
333
|
</div>
|
|
329
|
-
${
|
|
334
|
+
${E?'<span style="position: absolute; top: -4px; right: -4px; background: #10b981; color: white; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span>':""}
|
|
330
335
|
</div>
|
|
331
336
|
`}).join(""):'<p style="margin: 0; color: var(--text-secondary); font-style: italic; font-size: 13px;">未分配给任何人</p>'}
|
|
332
337
|
</div>
|
|
@@ -417,23 +422,23 @@
|
|
|
417
422
|
|
|
418
423
|
<!-- 投票选项 -->
|
|
419
424
|
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
420
|
-
${((
|
|
425
|
+
${((h=p.poll.options)==null?void 0:h.map(f=>{const E=p.poll.totalVotes>0?Math.round((f.votes||0)/p.poll.totalVotes*100):0,C=f.voters||[];return`
|
|
421
426
|
<div style="background: var(--bg-tertiary); border-radius: 8px; padding: 10px; border: 1px solid var(--border);">
|
|
422
427
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
|
|
423
|
-
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${
|
|
424
|
-
<span style="font-weight: 700; font-size: 13px; color: var(--primary);">${
|
|
428
|
+
<span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${f.text}</span>
|
|
429
|
+
<span style="font-weight: 700; font-size: 13px; color: var(--primary);">${f.votes||0} 票 (${E}%)</span>
|
|
425
430
|
</div>
|
|
426
431
|
<div style="height: 6px; background: var(--bg-secondary); border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
|
|
427
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
432
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${E}%; transition: width 0.3s;"></div>
|
|
428
433
|
</div>
|
|
429
|
-
${
|
|
434
|
+
${C.length>0?`
|
|
430
435
|
<div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
431
|
-
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${
|
|
436
|
+
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${C.length}人):</div>
|
|
432
437
|
<div style="display: flex; flex-wrap: wrap; gap: 4px;">
|
|
433
|
-
${
|
|
438
|
+
${C.map(G=>{const J=typeof G=="string"?G:G.username;return`
|
|
434
439
|
<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);">${
|
|
440
|
+
<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;">${J?J[0].toUpperCase():"?"}</span>
|
|
441
|
+
<span style="font-weight: 600; color: var(--text-primary);">${J||"未知用户"}</span>
|
|
437
442
|
</span>
|
|
438
443
|
`}).join("")}
|
|
439
444
|
</div>
|
|
@@ -453,10 +458,10 @@
|
|
|
453
458
|
<span>未投票成员 (${p.poll.unvotedMembers.length}人):</span>
|
|
454
459
|
</div>
|
|
455
460
|
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
|
456
|
-
${p.poll.unvotedMembers.map(
|
|
461
|
+
${p.poll.unvotedMembers.map(f=>{const E=typeof f=="string"?f:f.username;return`
|
|
457
462
|
<span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg-tertiary); border: 2px solid rgba(239, 68, 68, 0.3); border-radius: 12px; font-size: 12px;">
|
|
458
|
-
<span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${
|
|
459
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
463
|
+
<span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${E?E[0].toUpperCase():"?"}</span>
|
|
464
|
+
<span style="font-weight: 600; color: var(--text-primary);">${E||"未知用户"}</span>
|
|
460
465
|
<span style="font-size: 10px; color: var(--danger); background: rgba(239, 68, 68, 0.1); padding: 2px 6px; border-radius: 8px;">未投票</span>
|
|
461
466
|
</span>
|
|
462
467
|
`}).join("")}
|
|
@@ -472,9 +477,9 @@
|
|
|
472
477
|
`:""}
|
|
473
478
|
|
|
474
479
|
</div>
|
|
475
|
-
`,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async
|
|
480
|
+
`,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async B=>{B.preventDefault();const L=new FormData(B.target);try{const p=(await s.getGroup(c._id)).group.members.map(h=>h._id);await s.createTask({title:L.get("title"),description:L.get("description"),groupId:c._id,assignedTo:p,deadline:L.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await U(v),document.getElementById("createTaskModal").classList.add("hidden")}catch(k){console.error("创建任务错误:",k),alert("创建失败: "+k.message)}})}async function W(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const b=await s.getDocuments(c._id),B=(await s.getGroup(c._id)).group;v.innerHTML=`
|
|
476
481
|
<div class="view-header">
|
|
477
|
-
<h2>📄 共享文档 - ${
|
|
482
|
+
<h2>📄 共享文档 - ${c.name}</h2>
|
|
478
483
|
<button class="btn-primary" id="createDocBtn">➕ 创建文档</button>
|
|
479
484
|
</div>
|
|
480
485
|
<div class="documents-list" id="docsList"></div>
|
|
@@ -498,13 +503,13 @@
|
|
|
498
503
|
<div class="form-group">
|
|
499
504
|
<label>👥 成员编辑权限</label>
|
|
500
505
|
<div style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
501
|
-
${
|
|
506
|
+
${B.members.map(p=>`
|
|
502
507
|
<div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
|
|
503
508
|
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${p.username[0].toUpperCase()}</div>
|
|
504
509
|
<span style="flex: 1;">${p.username}</span>
|
|
505
510
|
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
|
506
|
-
<input type="checkbox" name="editableMembers" value="${p._id}" ${p._id===
|
|
507
|
-
<span style="font-size: 13px;">${p._id===
|
|
511
|
+
<input type="checkbox" name="editableMembers" value="${p._id}" ${p._id===B.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
|
|
512
|
+
<span style="font-size: 13px;">${p._id===B.admin.toString()?"管理员":"可编辑"}</span>
|
|
508
513
|
</label>
|
|
509
514
|
</div>
|
|
510
515
|
`).join("")}
|
|
@@ -530,13 +535,13 @@
|
|
|
530
535
|
</div>
|
|
531
536
|
</div>
|
|
532
537
|
</div>
|
|
533
|
-
`;const
|
|
538
|
+
`;const L=document.getElementById("docsList");b.documents.length===0?L.innerHTML='<div class="empty-state">暂无共享文档</div>':(b.documents.forEach(p=>{var g;const h=document.createElement("div");h.className="document-card";const o=((g=p.editableMembers)==null?void 0:g.length)||0;h.innerHTML=`
|
|
534
539
|
<div style="display: flex; justify-content: space-between; align-items: start;">
|
|
535
540
|
<div style="flex: 1;">
|
|
536
541
|
<h3>📄 ${p.title}</h3>
|
|
537
542
|
<div class="doc-meta" style="display: flex; gap: 15px; margin-top: 8px; font-size: 13px; color: var(--text-secondary);">
|
|
538
543
|
<span>👤 创建者: ${p.creator.username}</span>
|
|
539
|
-
<span>✏️ ${
|
|
544
|
+
<span>✏️ ${o} 人可编辑</span>
|
|
540
545
|
<span>📅 ${new Date(p.createdAt).toLocaleDateString()}</span>
|
|
541
546
|
</div>
|
|
542
547
|
</div>
|
|
@@ -546,20 +551,20 @@
|
|
|
546
551
|
<button class="btn-danger btn-sm" data-id="${p._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
|
|
547
552
|
</div>
|
|
548
553
|
</div>
|
|
549
|
-
`,
|
|
554
|
+
`,L.appendChild(h)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(p=>{p.addEventListener("click",()=>{ue(v,p.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(p=>{p.addEventListener("click",async()=>{const h=p.dataset.id,o=b.documents.find(g=>g._id===h);k(o,B)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(p=>{p.addEventListener("click",async h=>{h.stopPropagation();const o=p.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await s.deleteDocument(o),alert("文档删除成功!"),await W(v)}catch(g){console.error("删除文档错误:",g),alert("删除失败: "+(g.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 k(p,h){const o=document.getElementById("editPermissionModal"),g=document.getElementById("permissionContent");g.innerHTML=`
|
|
550
555
|
<div style="margin-bottom: 16px;">
|
|
551
556
|
<h4 style="margin: 0 0 8px 0;">📄 ${p.title}</h4>
|
|
552
557
|
<p style="font-size: 13px; color: var(--text-secondary); margin: 0;">管理哪些成员可以编辑此文档</p>
|
|
553
558
|
</div>
|
|
554
559
|
<form id="updatePermissionForm">
|
|
555
560
|
<div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
|
|
556
|
-
${
|
|
561
|
+
${h.members.map(f=>{var G;const E=((G=p.editableMembers)==null?void 0:G.includes(f._id))||f._id===h.admin.toString(),C=f._id===h.admin.toString();return`
|
|
557
562
|
<div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
|
|
558
|
-
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${
|
|
559
|
-
<span style="flex: 1;">${
|
|
563
|
+
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${f.username[0].toUpperCase()}</div>
|
|
564
|
+
<span style="flex: 1;">${f.username}</span>
|
|
560
565
|
<label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
|
|
561
|
-
<input type="checkbox" name="editableMembers" value="${
|
|
562
|
-
<span style="font-size: 13px;">${
|
|
566
|
+
<input type="checkbox" name="editableMembers" value="${f._id}" ${E?"checked":""} ${C?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
|
|
567
|
+
<span style="font-size: 13px;">${C?"管理员":"可编辑"}</span>
|
|
563
568
|
</label>
|
|
564
569
|
</div>
|
|
565
570
|
`}).join("")}
|
|
@@ -570,27 +575,27 @@
|
|
|
570
575
|
<button type="button" class="btn-secondary" id="cancelPermissionBtn" style="flex: 1;">取消</button>
|
|
571
576
|
</div>
|
|
572
577
|
</form>
|
|
573
|
-
`,
|
|
578
|
+
`,o.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{o.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async f=>{f.preventDefault();const C=new FormData(f.target).getAll("editableMembers");try{await s.updateDocumentPermissions(p._id,C),alert("权限更新成功!"),o.classList.add("hidden"),await W(v)}catch(G){console.error("更新权限错误:",G),alert("更新失败: "+G.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async p=>{p.preventDefault();const h=new FormData(p.target),o=h.getAll("editableMembers");try{await s.createDocument(h.get("title"),h.get("content"),c._id,o),alert("文档创建成功!"),await W(v),document.getElementById("createDocModal").classList.add("hidden")}catch(g){console.error("创建文档错误:",g),alert("创建失败: "+g.message)}})}async function ue(v,b){const B=(await s.getDocument(b)).document;v.innerHTML=`
|
|
574
579
|
<div class="view-header">
|
|
575
580
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
576
|
-
<h2>${
|
|
577
|
-
<span class="doc-status">${
|
|
581
|
+
<h2>${B.title}</h2>
|
|
582
|
+
<span class="doc-status">${B.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
|
|
578
583
|
</div>
|
|
579
584
|
<div class="editor-container">
|
|
580
585
|
<div class="editor-toolbar">
|
|
581
586
|
<div class="online-users" id="onlineUsers">
|
|
582
|
-
<span class="user-badge">👤 ${
|
|
587
|
+
<span class="user-badge">👤 ${r.username}</span>
|
|
583
588
|
</div>
|
|
584
589
|
<button class="btn-primary" id="saveBtn">保存</button>
|
|
585
590
|
</div>
|
|
586
591
|
<div id="editor"></div>
|
|
587
592
|
<div class="editor-footer">
|
|
588
|
-
<span>最后编辑: ${new Date(
|
|
593
|
+
<span>最后编辑: ${new Date(B.updatedAt).toLocaleString()}</span>
|
|
589
594
|
</div>
|
|
590
595
|
</div>
|
|
591
|
-
`;const
|
|
596
|
+
`;const L=new Quill("#editor",{theme:"snow",modules:{toolbar:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:!1});L.root.innerHTML=B.content||"";let k,p;L.on("text-change",()=>{clearTimeout(k),clearTimeout(p),t.sendTyping(b,r.username,!0),k=setTimeout(()=>{t.sendTyping(b,r.username,!1)},1e3),p=setTimeout(async()=>{const h=L.root.innerHTML;try{await s.updateDocument(b,h)}catch(o){console.error("自动保存失败:",o)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const h=L.root.innerHTML;await s.updateDocument(b,h),alert("保存成功!")}catch(h){alert("保存失败: "+h.message)}}),t.on("document_update",h=>{h.documentId===b&&h.userId!==r.id&&(L.root.innerHTML=h.content)}),t.on("typing",h=>{if(h.documentId===b&&h.userId!==r.id){const o=document.getElementById("onlineUsers");if(h.isTyping)o.innerHTML+=`<span class="user-badge typing" data-user="${h.userId}">✏️ ${h.username}</span>`;else{const g=o.querySelector(`[data-user="${h.userId}"]`);g&&g.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{W(v)})}async function oe(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const b=await s.getGroupFiles(c._id);v.innerHTML=`
|
|
592
597
|
<div class="view-header">
|
|
593
|
-
<h2>文件管理 - ${
|
|
598
|
+
<h2>文件管理 - ${c.name}</h2>
|
|
594
599
|
<button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
|
|
595
600
|
</div>
|
|
596
601
|
<div class="files-list" id="filesList"></div>
|
|
@@ -619,49 +624,49 @@
|
|
|
619
624
|
</form>
|
|
620
625
|
</div>
|
|
621
626
|
</div>
|
|
622
|
-
`;const
|
|
623
|
-
<div class="file-icon">${
|
|
627
|
+
`;const w=document.getElementById("filesList");!b.files||b.files.length===0?w.innerHTML='<div class="empty-state">暂无文件</div>':(b.files.forEach(B=>{const L=document.createElement("div");L.className="file-card";const k=fe(B.mimetype),p=be(B.size);L.innerHTML=`
|
|
628
|
+
<div class="file-icon">${k}</div>
|
|
624
629
|
<div class="file-info">
|
|
625
|
-
<h4>${
|
|
630
|
+
<h4>${B.originalName}</h4>
|
|
626
631
|
<div class="file-meta">
|
|
627
|
-
<span>上传者: ${
|
|
632
|
+
<span>上传者: ${B.uploader.username}</span>
|
|
628
633
|
<span>大小: ${p}</span>
|
|
629
|
-
<span>时间: ${new Date(
|
|
634
|
+
<span>时间: ${new Date(B.createdAt).toLocaleString()}</span>
|
|
630
635
|
</div>
|
|
631
|
-
${
|
|
636
|
+
${B.description?`<p class="file-description">${B.description}</p>`:""}
|
|
632
637
|
</div>
|
|
633
638
|
<div class="file-actions">
|
|
634
|
-
<a href="${
|
|
635
|
-
<button class="btn-danger" data-id="${
|
|
639
|
+
<a href="${s.getFileDownloadUrl(B._id)}" class="btn-primary" download>下载</a>
|
|
640
|
+
<button class="btn-danger" data-id="${B._id}" data-action="delete-file">删除</button>
|
|
636
641
|
</div>
|
|
637
|
-
`,
|
|
642
|
+
`,w.appendChild(L)}),document.querySelectorAll('[data-action="delete-file"]').forEach(B=>{B.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await s.deleteFile(B.dataset.id),alert("文件删除成功!"),await oe(v)}catch(L){alert("删除失败: "+L.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async B=>{B.preventDefault();const L=document.getElementById("fileInput"),k=document.getElementById("fileDescription").value;if(!L.files[0]){alert("请选择文件");return}try{await s.uploadFile(c._id,L.files[0],k),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await oe(v)}catch(p){alert("上传失败: "+p.message)}})}catch(b){console.error("获取文件列表失败:",b),v.innerHTML=`
|
|
638
643
|
<div class="view-header">
|
|
639
644
|
<h2>文件管理</h2>
|
|
640
645
|
</div>
|
|
641
|
-
<div class="empty-state">加载文件失败: ${
|
|
642
|
-
`}}function
|
|
646
|
+
<div class="empty-state">加载文件失败: ${b.message}</div>
|
|
647
|
+
`}}function fe(v){return v.startsWith("image/")?"🖼️":v==="application/pdf"?"📕":v.includes("word")||v.includes("document")?"📘":v.includes("excel")||v.includes("spreadsheet")?"📗":v.includes("zip")||v.includes("compressed")?"📦":"📄"}function be(v){if(v===0)return"0 Bytes";const b=1024,w=["Bytes","KB","MB","GB"],B=Math.floor(Math.log(v)/Math.log(b));return Math.round(v/Math.pow(b,B)*100)/100+" "+w[B]}async function K(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let w=(await s.getGroup(c._id)).group;function B(S){if(S.startsWith("[白板作品]")){const j=S.replace("[白板作品]","").trim(),T=j.includes("/api/files/")&&j.includes("/download");let X=j;if(T&&!j.includes("token=")){const q=localStorage.getItem("token");X=j.includes("?")?`${j}&token=${q}`:`${j}?token=${q}`}return`
|
|
643
648
|
<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
649
|
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
645
650
|
<span style="font-size: 18px;">🎨</span>
|
|
646
651
|
<span>白板作品</span>
|
|
647
652
|
</div>
|
|
648
653
|
<div style="position: relative; display: inline-block;">
|
|
649
|
-
<img src="${
|
|
654
|
+
<img src="${X}" alt="白板作品"
|
|
650
655
|
style="max-width: 400px; max-height: 400px; border-radius: 8px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); background: rgba(255,255,255,0.1);"
|
|
651
|
-
onclick="window.open('${
|
|
652
|
-
onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${
|
|
653
|
-
onload="console.log('白板图片加载成功:', '${
|
|
656
|
+
onclick="window.open('${X}', '_blank')"
|
|
657
|
+
onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${X}');"
|
|
658
|
+
onload="console.log('白板图片加载成功:', '${X}');">
|
|
654
659
|
<div style="display: none; padding: 20px; background: rgba(255,255,255,0.1); border: 2px dashed rgba(255,255,255,0.3); border-radius: 8px; text-align: center; color: white;">
|
|
655
660
|
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
656
661
|
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
657
662
|
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
658
|
-
<button onclick="navigator.clipboard.writeText('${
|
|
663
|
+
<button onclick="navigator.clipboard.writeText('${j}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
|
|
659
664
|
</div>
|
|
660
665
|
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
661
666
|
</div>
|
|
662
667
|
</div>
|
|
663
|
-
`}if(
|
|
664
|
-
<div class="poll-card" data-poll-id="${
|
|
668
|
+
`}if(S.startsWith("[投票]")){const j=S.replace("[投票]","").trim();return`
|
|
669
|
+
<div class="poll-card" data-poll-id="${j}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${j}')">
|
|
665
670
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
666
671
|
<span style="font-size: 32px;">📊</span>
|
|
667
672
|
<div style="flex: 1;">
|
|
@@ -673,7 +678,7 @@
|
|
|
673
678
|
📋 查看投票详情
|
|
674
679
|
</div>
|
|
675
680
|
</div>
|
|
676
|
-
`}return
|
|
681
|
+
`}return S}window.viewPollDetail=async S=>{try{const j=localStorage.getItem("token"),T=await fetch(`http://localhost:3000/api/polls/${S}`,{headers:{Authorization:`Bearer ${j}`}});if(!T.ok)throw new Error("获取投票详情失败");const q=(await T.json()).poll;console.log("投票详情数据:",q),console.log("创建者信息:",q.creatorName,q.creator),q.options.forEach((a,e)=>{console.log(`选项 ${e} (${a.text}) 的投票记录:`,a.votes)});const re=q.options.reduce((a,e)=>a+e.votes.length,0),de=q.status==="ended"||q.endTime&&new Date(q.endTime)<new Date,ne=`
|
|
677
682
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
678
683
|
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
679
684
|
<div class="modal-header">
|
|
@@ -681,18 +686,18 @@
|
|
|
681
686
|
<button class="modal-close" id="closePollDetailModal">×</button>
|
|
682
687
|
</div>
|
|
683
688
|
<div class="modal-body" style="padding: 24px;">
|
|
684
|
-
<h2 style="margin: 0 0 12px 0;">${
|
|
685
|
-
${
|
|
689
|
+
<h2 style="margin: 0 0 12px 0;">${q.title}</h2>
|
|
690
|
+
${q.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${q.description}</p>`:""}
|
|
686
691
|
|
|
687
692
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
|
|
688
693
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
689
|
-
${
|
|
694
|
+
${q.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
690
695
|
</span>
|
|
691
696
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
692
|
-
${
|
|
697
|
+
${q.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
693
698
|
</span>
|
|
694
|
-
<span style="font-size: 13px; padding: 6px 12px; background: ${
|
|
695
|
-
${
|
|
699
|
+
<span style="font-size: 13px; padding: 6px 12px; background: ${de?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
700
|
+
${de?"已结束":"进行中"}
|
|
696
701
|
</span>
|
|
697
702
|
</div>
|
|
698
703
|
|
|
@@ -700,40 +705,40 @@
|
|
|
700
705
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
701
706
|
<div>
|
|
702
707
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
703
|
-
<div style="font-weight: 600;">👤 ${
|
|
708
|
+
<div style="font-weight: 600;">👤 ${q.creatorName||q.creator&&q.creator.username||"未知用户"}</div>
|
|
704
709
|
</div>
|
|
705
710
|
<div>
|
|
706
711
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
707
|
-
<div style="font-weight: 600; color: var(--primary);">👥 ${
|
|
712
|
+
<div style="font-weight: 600; color: var(--primary);">👥 ${re} 人</div>
|
|
708
713
|
</div>
|
|
709
714
|
<div>
|
|
710
715
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
711
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
716
|
+
<div style="font-weight: 600;">⏰ ${new Date(q.createdAt).toLocaleString("zh-CN")}</div>
|
|
712
717
|
</div>
|
|
713
|
-
${
|
|
718
|
+
${q.endTime?`
|
|
714
719
|
<div>
|
|
715
720
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
716
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
721
|
+
<div style="font-weight: 600;">⏰ ${new Date(q.endTime).toLocaleString("zh-CN")}</div>
|
|
717
722
|
</div>
|
|
718
723
|
`:""}
|
|
719
724
|
</div>
|
|
720
725
|
</div>
|
|
721
726
|
|
|
722
727
|
<h3 style="margin-bottom: 16px;">投票选项</h3>
|
|
723
|
-
${
|
|
728
|
+
${q.options.map((a,e)=>{const n=re>0?(a.votes.length/re*100).toFixed(1):0;let d="";if(!q.anonymous&&a.votes.length>0){const y=a.votes.map(x=>{if(x&&typeof x=="object"){if(x.username&&typeof x.username=="string"&&x.username.trim())return x.username.trim();if(x.user&&typeof x.user=="object"&&x.user.username&&typeof x.user.username=="string")return x.user.username.trim()}return null}).filter(x=>x!==null&&x!=="未知用户"&&x.trim()!=="");y.length>0?d=`
|
|
724
729
|
<div style="font-size: 13px; color: var(--text-secondary); margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
|
|
725
|
-
<strong style="color: var(--text-primary);">👥 投票者:</strong> ${
|
|
730
|
+
<strong style="color: var(--text-primary);">👥 投票者:</strong> ${y.join(", ")}
|
|
726
731
|
</div>
|
|
727
|
-
`:console.warn("投票者信息缺失:",
|
|
732
|
+
`:console.warn("投票者信息缺失:",a.votes)}return`
|
|
728
733
|
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
729
734
|
<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;">${
|
|
735
|
+
<span style="font-weight: 500; font-size: 16px;">${a.text}</span>
|
|
736
|
+
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${a.votes.length} 票 (${n}%)</span>
|
|
732
737
|
</div>
|
|
733
738
|
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden;">
|
|
734
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
739
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${n}%; transition: width 0.3s;"></div>
|
|
735
740
|
</div>
|
|
736
|
-
${
|
|
741
|
+
${d}
|
|
737
742
|
</div>
|
|
738
743
|
`}).join("")}
|
|
739
744
|
|
|
@@ -741,9 +746,9 @@
|
|
|
741
746
|
</div>
|
|
742
747
|
</div>
|
|
743
748
|
</div>
|
|
744
|
-
`,
|
|
749
|
+
`,pe=document.getElementById("pollDetailModal");pe&&pe.remove(),document.body.insertAdjacentHTML("beforeend",ne),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()})}catch(j){console.error("加载投票详情失败:",j),alert("加载投票详情失败: "+j.message)}},v.innerHTML=`
|
|
745
750
|
<div class="view-header">
|
|
746
|
-
<h2>群聊 - ${
|
|
751
|
+
<h2>群聊 - ${c.name}</h2>
|
|
747
752
|
<div style="display: flex; gap: 10px;">
|
|
748
753
|
<button class="btn-primary" id="createPollBtn" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none;">📊 创建投票</button>
|
|
749
754
|
<button class="btn-secondary" id="muteAllBtn">全体禁言</button>
|
|
@@ -937,38 +942,38 @@
|
|
|
937
942
|
</div>
|
|
938
943
|
</div>
|
|
939
944
|
</div>
|
|
940
|
-
`;const
|
|
945
|
+
`;const L=document.getElementById("messages"),k=document.getElementById("messageInput"),p=document.getElementById("sendBtn"),h=document.getElementById("emojiBtn"),o=document.getElementById("emojiPicker");let g=new Set((w.mutedUsers||[]).map(String)),f=!!w.mutedAll;h.addEventListener("click",()=>{o.classList.toggle("hidden")}),o.addEventListener("emoji-click",S=>{k.value+=S.detail.unicode,k.focus(),o.classList.add("hidden")}),document.addEventListener("click",S=>{!h.contains(S.target)&&!o.contains(S.target)&&o.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const S=await s.getGroupMessages(c._id);S.messages&&(S.messages.forEach(j=>{const T=document.createElement("div");T.className=`message ${j.sender===l?"own":""}`;const X=B(j.content),q=j.content.startsWith("[白板作品]")||j.content.startsWith("[投票]"),re=j.content.startsWith("[白板作品]");T.innerHTML=`
|
|
941
946
|
<div class="message-header">
|
|
942
|
-
<span class="message-user">${
|
|
943
|
-
<span class="message-time">${new Date(
|
|
947
|
+
<span class="message-user">${j.username}</span>
|
|
948
|
+
<span class="message-time">${new Date(j.timestamp).toLocaleTimeString()}</span>
|
|
944
949
|
</div>
|
|
945
|
-
<div class="message-content" style="${
|
|
946
|
-
`,
|
|
950
|
+
<div class="message-content" style="${re?"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);":q?"background: transparent; padding: 0;":""}">${X}</div>
|
|
951
|
+
`,L.appendChild(T)}),L.scrollTop=L.scrollHeight)}catch(S){console.error("加载历史消息失败:",S)}const E=()=>{const S=document.getElementById("muteAllBtn");S.textContent=f?"取消全体禁言":"全体禁言",S.style.background=f?"var(--danger)":""};E(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
|
|
947
952
|
|
|
948
953
|
确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
|
|
949
954
|
|
|
950
|
-
群组名称:`+
|
|
955
|
+
群组名称:`+c.name)!==c.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const T=document.getElementById("clearChatBtn"),X=T.innerHTML;T.innerHTML="⏳ 清除中...",T.disabled=!0;const q=localStorage.getItem("token"),re=await fetch(`http://localhost:3000/api/messages/group/${c._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${q}`,"Content-Type":"application/json"}});if(!re.ok){const ne=await re.json();throw new Error(ne.message||"清除失败")}const de=await re.json();L.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${de.deletedCount||0} 条聊天记录!`),T.innerHTML=X,T.disabled=!1}catch(T){console.error("清除聊天记录失败:",T),alert("❌ 清除失败: "+T.message);const X=document.getElementById("clearChatBtn");X.innerHTML="🗑️ 清除记录",X.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const S=!f;f=!!(await s.setMuteAll(c._id,S)).mutedAll,E();const T=document.createElement("div");T.className="notification",T.textContent=f?"已开启全体禁言(成员无法发言)":"已取消全体禁言",L.appendChild(T),L.scrollTop=L.scrollHeight}catch(S){alert("设置失败: "+S.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{w=(await s.getGroup(c._id)).group,g=new Set((w.mutedUsers||[]).map(String));const j=document.getElementById("membersList");j.innerHTML=w.members.filter(T=>T._id.toString()!==l).map(T=>{const X=g.has(T._id.toString());return`
|
|
951
956
|
<div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
|
|
952
957
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
953
|
-
<div class="avatar" style="width: 35px; height: 35px;">${
|
|
954
|
-
<span>${
|
|
958
|
+
<div class="avatar" style="width: 35px; height: 35px;">${T.username[0].toUpperCase()}</div>
|
|
959
|
+
<span>${T.username}</span>
|
|
955
960
|
</div>
|
|
956
|
-
<button class="btn-secondary btn-sm" onclick="toggleMute('${
|
|
957
|
-
${
|
|
961
|
+
<button class="btn-secondary btn-sm" onclick="toggleMute('${T._id}')" id="mute-${T._id}">
|
|
962
|
+
${X?"取消禁言":"禁言"}
|
|
958
963
|
</button>
|
|
959
964
|
</div>
|
|
960
|
-
`}).join(""),document.getElementById("manageMuteModal").classList.remove("hidden")}),document.getElementById("closeMuteModal").addEventListener("click",()=>{document.getElementById("manageMuteModal").classList.add("hidden")}),document.getElementById("createPollBtn").addEventListener("click",()=>{document.getElementById("createPollModal").classList.remove("hidden")}),document.getElementById("closePollModal").addEventListener("click",()=>{document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}),document.getElementById("cancelPollModal").addEventListener("click",()=>{document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}),document.getElementById("addPollOption").addEventListener("click",()=>{const
|
|
961
|
-
<input type="text" class="poll-option" placeholder="选项 ${
|
|
965
|
+
`}).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 S=document.getElementById("pollOptions"),j=S.querySelectorAll(".poll-option-input").length+1,T=document.createElement("div");T.className="poll-option-input",T.style.cssText="display: flex; gap: 10px; margin-bottom: 10px;",T.innerHTML=`
|
|
966
|
+
<input type="text" class="poll-option" placeholder="选项 ${j}" required style="flex: 1; padding: 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary); color: white;">
|
|
962
967
|
<button type="button" class="btn-danger remove-option" style="padding: 10px 15px;">删除</button>
|
|
963
|
-
`,
|
|
968
|
+
`,S.appendChild(T),T.querySelector(".remove-option").addEventListener("click",()=>{S.querySelectorAll(".poll-option-input").length>2?T.remove():alert("至少需要2个选项!")})}),document.getElementById("createPollForm").addEventListener("submit",async S=>{S.preventDefault();const j=document.getElementById("pollTitle").value.trim(),T=document.getElementById("pollDescription").value.trim(),X=document.getElementById("allowMultiple").checked,q=document.getElementById("anonymous").checked,re=document.getElementById("pollEndTime").value,de=document.querySelectorAll(".poll-option"),ne=Array.from(de).map(pe=>pe.value.trim()).filter(pe=>pe);if(ne.length<2){alert("至少需要2个选项!");return}try{const pe=localStorage.getItem("token"),a=await fetch("http://localhost:3000/api/polls",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${pe}`},body:JSON.stringify({title:j,description:T,options:ne,groupId:c._id,allowMultiple:X,anonymous:q,endTime:re||null})});if(!a.ok){const d=await a.json();throw new Error(d.error||"创建投票失败")}const n=(await a.json()).poll;t.sendChatMessage(c._id,r.username,`[投票]${n._id}`),alert("投票创建成功!"),document.getElementById("createPollModal").classList.add("hidden"),document.getElementById("createPollForm").reset()}catch(pe){console.error("创建投票失败:",pe),alert("创建投票失败: "+pe.message)}}),window.toggleMute=async S=>{try{const j=!g.has(S),T=await s.setUserMute(c._id,S,j);g=new Set((T.mutedUsers||[]).map(String));const X=document.getElementById(`mute-${S}`);X.textContent=g.has(S)?"取消禁言":"禁言",X.style.background=g.has(S)?"var(--danger)":""}catch(j){alert("操作失败: "+j.message)}},t.on("chat_message",S=>{if(S.groupId===c._id){const j=document.createElement("div");j.className=`message ${S.userId===l?"own":""}`;const T=B(S.content),X=S.content.startsWith("[白板作品]")||S.content.startsWith("[投票]"),q=S.content.startsWith("[白板作品]");j.innerHTML=`
|
|
964
969
|
<div class="message-header">
|
|
965
|
-
<span class="message-user">${
|
|
966
|
-
<span class="message-time">${new Date(
|
|
970
|
+
<span class="message-user">${S.username}</span>
|
|
971
|
+
<span class="message-time">${new Date(S.timestamp).toLocaleTimeString()}</span>
|
|
967
972
|
</div>
|
|
968
|
-
<div class="message-content" style="${
|
|
969
|
-
`,
|
|
973
|
+
<div class="message-content" style="${q?"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);":X?"background: transparent; padding: 0;":""}">${T}</div>
|
|
974
|
+
`,L.appendChild(j),L.scrollTop=L.scrollHeight}}),t.on("chat_blocked",S=>{if(S.groupId===c._id){const j=document.createElement("div");j.className="notification",j.textContent=S.message||"消息发送失败",L.appendChild(j),L.scrollTop=L.scrollHeight}});const C=()=>{const S=k.value.trim();S&&(t.sendChatMessage(c._id,r.username,S),k.value="")};p.addEventListener("click",C),k.addEventListener("keypress",S=>{S.key==="Enter"&&C()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(S=>{S.addEventListener("click",()=>{document.getElementById("aiInputText").value=S.dataset.question,document.getElementById("aiSendBtnModal").click()}),S.addEventListener("mouseenter",j=>{j.target.style.background="var(--primary)",j.target.style.color="white",j.target.style.transform="translateY(-2px)"}),S.addEventListener("mouseleave",j=>{j.target.style.background="var(--bg)",j.target.style.color="inherit",j.target.style.transform="translateY(0)"})});const G=document.getElementById("aiInputText");G.addEventListener("input",()=>{G.style.height="auto",G.style.height=G.scrollHeight+"px"}),G.addEventListener("keydown",S=>{S.key==="Enter"&&!S.shiftKey&&(S.preventDefault(),document.getElementById("aiSendBtnModal").click())}),G.addEventListener("focus",()=>{G.style.borderColor="var(--primary)"}),G.addEventListener("blur",()=>{G.style.borderColor="var(--border)"});const J=document.getElementById("aiSendBtnModal");J.addEventListener("mouseenter",()=>{J.style.transform="scale(1.05)"}),J.addEventListener("mouseleave",()=>{J.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const S=document.getElementById("aiInputText"),j=S.value.trim();if(!j)return;const T=document.getElementById("aiChatMessages"),X=document.createElement("div");X.className="ai-message user",X.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;",X.textContent=j,T.appendChild(X),S.value="";const q=document.createElement("div");q.className="ai-message ai loading",q.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",q.textContent="思考中...",T.appendChild(q),T.scrollTop=T.scrollHeight;try{const re=localStorage.getItem("token"),ne=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${re}`},body:JSON.stringify({question:j,groupId:c==null?void 0:c._id})})).json();q.remove();const pe=document.createElement("div");pe.className="ai-message ai",pe.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;",pe.textContent=ne.answer||"抱歉,我无法回答这个问题。",T.appendChild(pe),T.scrollTop=T.scrollHeight}catch(re){q.remove();const de=document.createElement("div");de.className="ai-message ai error",de.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",de.textContent="抱歉,发生了错误: "+re.message,T.appendChild(de),T.scrollTop=T.scrollHeight}}),window.showImageModal=S=>{const j=document.getElementById("imagePreviewModal"),T=document.getElementById("previewImage"),X=document.getElementById("downloadImageBtn");T.src=S,j.classList.remove("hidden"),X.onclick=async()=>{try{const re=await(await fetch(S)).blob(),de=window.URL.createObjectURL(re),ne=document.createElement("a");ne.href=de,ne.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(ne),ne.click(),document.body.removeChild(ne),window.URL.revokeObjectURL(de)}catch(q){console.error("下载失败:",q),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",S=>{S.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),ee()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function ee(){const S=document.getElementById("whiteboardCanvas");if(!S)return;const j=S.getContext("2d");let T=!1,X="pen",q="#000000",re=3,de=0,ne=0;document.querySelectorAll(".tool-btn").forEach(n=>{n.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(d=>{d.style.background="transparent",d.style.borderColor="var(--border)",d.style.color="inherit",d.classList.remove("active")}),n.style.background="var(--primary)",n.style.borderColor="var(--primary)",n.style.color="white",n.classList.add("active"),X=n.dataset.tool}});const pe=document.getElementById("colorPickerCanvas");pe&&(pe.onchange=n=>{q=n.target.value});const a=document.getElementById("brushSizeCanvas"),e=document.getElementById("brushSizeLabel");a&&e&&(a.oninput=n=>{re=n.target.value,e.textContent=`大小: ${re}`}),S.onmousedown=n=>{T=!0;const d=S.getBoundingClientRect();de=n.clientX-d.left,ne=n.clientY-d.top},S.onmousemove=n=>{if(!T)return;const d=S.getBoundingClientRect(),y=n.clientX-d.left,x=n.clientY-d.top;j.beginPath(),j.moveTo(de,ne),j.lineTo(y,x),j.strokeStyle=X==="eraser"?"#ffffff":q,j.lineWidth=re,j.lineCap="round",j.stroke(),de=y,ne=x},S.onmouseup=()=>{T=!1},S.onmouseleave=()=>{T=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&j.clearRect(0,0,S.width,S.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const n=S.toDataURL("image/png"),d=document.createElement("a");d.download=`whiteboard-${Date.now()}.png`,d.href=n,d.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const n=S.toDataURL("image/png"),d=await fetch(n).then(Q=>Q.blob()),y=new FormData;y.append("file",d,`whiteboard-${Date.now()}.png`),y.append("groupId",c._id),y.append("description","协作白板作品");const x=localStorage.getItem("token"),I=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${x}`},body:y});if(I.ok){const ce=`http://localhost:3000/api/files/${(await I.json()).file._id}/download?token=${x}`;t.sendChatMessage(c._id,r.username,`[白板作品]${ce}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(n){console.error("发送白板作品错误:",n),alert("发送失败: "+n.message)}}}}async function te(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}v.innerHTML=`
|
|
970
975
|
<div class="view-header">
|
|
971
|
-
<h2>随机点名 - ${
|
|
976
|
+
<h2>随机点名 - ${c.name}</h2>
|
|
972
977
|
</div>
|
|
973
978
|
<div class="call-panel">
|
|
974
979
|
<div class="call-controls">
|
|
@@ -978,17 +983,17 @@
|
|
|
978
983
|
</div>
|
|
979
984
|
<div id="callResult" class="call-result"></div>
|
|
980
985
|
</div>
|
|
981
|
-
`,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const
|
|
986
|
+
`,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const b=parseInt(document.getElementById("callCount").value);try{const w=await s.randomCall(c._id,b),B=document.getElementById("callResult");if(B.innerHTML=`
|
|
982
987
|
<h3>点名结果:</h3>
|
|
983
988
|
<div class="called-members">
|
|
984
|
-
${
|
|
989
|
+
${w.calledMembers.map(L=>`
|
|
985
990
|
<div class="member-card">
|
|
986
|
-
<div class="avatar">${
|
|
987
|
-
<div class="member-name">${
|
|
991
|
+
<div class="avatar">${L.username[0].toUpperCase()}</div>
|
|
992
|
+
<div class="member-name">${L.username}</div>
|
|
988
993
|
</div>
|
|
989
994
|
`).join("")}
|
|
990
995
|
</div>
|
|
991
|
-
`,Array.isArray(
|
|
996
|
+
`,Array.isArray(w.calledMembers)&&w.calledMembers.length>0&&t&&c&&r){const L=w.calledMembers.map(p=>p.username).join("、"),k=`🎲 本次随机点名(${w.calledMembers.length} 人):${L}`;try{t.sendChatMessage(c._id,r.username,k)}catch(p){console.error("发送点名结果到群聊失败:",p)}}}catch(w){alert("点名失败: "+w.message)}})}async function me(v){v.innerHTML=`
|
|
992
997
|
<div class="view-header">
|
|
993
998
|
<h2>操作记录</h2>
|
|
994
999
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1052,7 +1057,7 @@
|
|
|
1052
1057
|
</div>
|
|
1053
1058
|
</div>
|
|
1054
1059
|
</div>
|
|
1055
|
-
`;let
|
|
1060
|
+
`;let b=1,w={};try{const o=await s.getGroups(),g=document.getElementById("auditGroupFilter");o.groups.forEach(f=>{const E=document.createElement("option");E.value=f._id,E.textContent=f.name,g.appendChild(E)})}catch(o){console.error("加载群组列表失败:",o)}async function B(o=1,g={}){try{const f=document.getElementById("auditLogs");f.innerHTML='<div class="loading">加载中...</div>';const E={page:o,limit:20},C=await s.getAuditLogs(g,E);if(C.logs.length===0){f.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}f.innerHTML=`
|
|
1056
1061
|
<div class="audit-table">
|
|
1057
1062
|
<div class="audit-header">
|
|
1058
1063
|
<div>时间</div>
|
|
@@ -1061,26 +1066,26 @@
|
|
|
1061
1066
|
<div>资源</div>
|
|
1062
1067
|
<div>详情</div>
|
|
1063
1068
|
</div>
|
|
1064
|
-
${
|
|
1065
|
-
<div class="audit-row" onclick="showAuditDetail('${
|
|
1066
|
-
<div class="audit-time">${new Date(
|
|
1069
|
+
${C.logs.map(ee=>{var S,j,T,X,q;return`
|
|
1070
|
+
<div class="audit-row" onclick="showAuditDetail('${ee._id}')">
|
|
1071
|
+
<div class="audit-time">${new Date(ee.createdAt).toLocaleString()}</div>
|
|
1067
1072
|
<div class="audit-user">
|
|
1068
|
-
<div class="avatar">${((
|
|
1069
|
-
<span>${((
|
|
1073
|
+
<div class="avatar">${((T=(j=(S=ee.user)==null?void 0:S.username)==null?void 0:j[0])==null?void 0:T.toUpperCase())||"?"}</div>
|
|
1074
|
+
<span>${((X=ee.user)==null?void 0:X.username)||"未知用户"}</span>
|
|
1070
1075
|
</div>
|
|
1071
1076
|
<div class="audit-action">
|
|
1072
|
-
<span class="action-badge action-${
|
|
1077
|
+
<span class="action-badge action-${ee.action}">${V(ee.action)}</span>
|
|
1073
1078
|
</div>
|
|
1074
|
-
<div class="audit-resource">${
|
|
1075
|
-
<div class="audit-description">${((
|
|
1079
|
+
<div class="audit-resource">${ee.resourceTitle||ee.resourceId}</div>
|
|
1080
|
+
<div class="audit-description">${((q=ee.details)==null?void 0:q.description)||"-"}</div>
|
|
1076
1081
|
</div>
|
|
1077
1082
|
`}).join("")}
|
|
1078
1083
|
</div>
|
|
1079
|
-
`;const
|
|
1084
|
+
`;const G=document.getElementById("auditPagination"),J=document.getElementById("pageInfo");J.textContent=`第 ${C.pagination.page} 页,共 ${C.pagination.pages} 页`,document.getElementById("prevPage").disabled=C.pagination.page<=1,document.getElementById("nextPage").disabled=C.pagination.page>=C.pagination.pages,G.style.display=C.pagination.pages>1?"flex":"none"}catch(f){console.error("加载审计日志失败:",f),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+f.message+"</div>"}}async function L(){try{const o=new Date,g=new Date(o.getTime()-7*24*60*60*1e3),f=await s.getAuditSummary({startDate:o.toISOString().split("T")[0],endDate:o.toISOString().split("T")[0]}),E=await s.getAuditSummary({startDate:g.toISOString().split("T")[0],endDate:o.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=f.summary.totalLogs,document.getElementById("weekCount").textContent=E.summary.totalLogs,document.getElementById("activeUsers").textContent=E.summary.topUsers.length}catch(o){console.error("加载统计信息失败:",o)}}function k(o){var G,J,ee,S,j,T;const g=((G=o.user)==null?void 0:G.username)||"未知用户",f=V(o.action),E=o.resourceTitle||o.resourceId;let C='<strong style="color: #6366f1;">'+g+"</strong> ";switch(o.action){case"document_create":C+='创建了文档 <strong>"'+E+'"</strong>';break;case"document_update":C+='更新了文档 <strong>"'+E+'"</strong>',(J=o.details)!=null&&J.field&&(C+=" 的 <strong>"+p(o.details.field)+"</strong>");break;case"document_delete":C+='删除了文档 <strong>"'+E+'"</strong>';break;case"content_edit":if(C+='编辑了文档 <strong>"'+E+'"</strong> 的内容',o.changes){const X=((ee=o.changes.insertions)==null?void 0:ee.reduce((re,de)=>re+de.length,0))||0,q=((S=o.changes.deletions)==null?void 0:S.reduce((re,de)=>re+de.length,0))||0;(X>0||q>0)&&(C+=' (<span style="color: #10b981;">+'+X+'</span> / <span style="color: #ef4444;">-'+q+"</span> 字符)")}break;case"title_edit":C+='修改了文档 <strong>"'+E+'"</strong> 的标题',(j=o.details)!=null&&j.oldValue&&((T=o.details)!=null&&T.newValue)&&(C+=' 从 <strong>"'+o.details.oldValue+'"</strong> 改为 <strong>"'+o.details.newValue+'"</strong>');break;case"document_permission_change":C+='修改了文档 <strong>"'+E+'"</strong> 的权限设置';break;default:C+="执行了 <strong>"+f+"</strong> 操作"}return C}function p(o){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[o]||o}function h(o){if(o==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof o=="object")return JSON.stringify(o,null,2);const g=String(o);return g.length>500?g.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':g.replace(/</g,"<").replace(/>/g,">")}window.showAuditDetail=async o=>{var g,f,E,C,G,J;try{const ee=localStorage.getItem("token"),T=(await(await fetch(`http://localhost:3000/api/audit/${o}`,{headers:{Authorization:`Bearer ${ee}`}})).json()).log,X=document.getElementById("auditDetailModal"),q=document.getElementById("auditDetailContent"),re=pe=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[pe]||"#6366f1";q.innerHTML=`
|
|
1080
1085
|
<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
1086
|
|
|
1082
1087
|
<!-- 操作信息 -->
|
|
1083
|
-
<div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${
|
|
1088
|
+
<div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${re(T.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
|
1084
1089
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
|
|
1085
1090
|
<span style="font-size: 24px;">📋</span>
|
|
1086
1091
|
<h4 style="margin: 0; color: var(--text-primary); font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">操作信息</h4>
|
|
@@ -1088,14 +1093,14 @@
|
|
|
1088
1093
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
1089
1094
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作类型:</span>
|
|
1090
1095
|
<span style="display: inline-flex; align-items: center; gap: 8px;">
|
|
1091
|
-
<span style="display: inline-block; padding: 4px 10px; background: ${
|
|
1096
|
+
<span style="display: inline-block; padding: 4px 10px; background: ${re(T.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${V(T.action)}</span>
|
|
1092
1097
|
</span>
|
|
1093
1098
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作时间:</span>
|
|
1094
|
-
<span style="font-weight: 600; color: var(--text-primary);">${new Date(
|
|
1099
|
+
<span style="font-weight: 600; color: var(--text-primary);">${new Date(T.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
|
|
1095
1100
|
<span style="color: var(--text-tertiary); font-weight: 500;">操作用户:</span>
|
|
1096
1101
|
<span style="display: flex; align-items: center; gap: 10px;">
|
|
1097
|
-
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((
|
|
1098
|
-
<span style="font-weight: 600; color: var(--text-primary);">${((
|
|
1102
|
+
<div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((E=(f=(g=T.user)==null?void 0:g.username)==null?void 0:f[0])==null?void 0:E.toUpperCase())||"?"}</div>
|
|
1103
|
+
<span style="font-weight: 600; color: var(--text-primary);">${((C=T.user)==null?void 0:C.username)||"未知用户"}</span>
|
|
1099
1104
|
</span>
|
|
1100
1105
|
</div>
|
|
1101
1106
|
</div>
|
|
@@ -1108,15 +1113,15 @@
|
|
|
1108
1113
|
</div>
|
|
1109
1114
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
1110
1115
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源类型:</span>
|
|
1111
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
1116
|
+
<span style="font-weight: 600; color: var(--text-primary);">${T.resourceType||"未知"}</span>
|
|
1112
1117
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源ID:</span>
|
|
1113
|
-
<span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${
|
|
1118
|
+
<span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${T.resourceId}</span>
|
|
1114
1119
|
<span style="color: var(--text-tertiary); font-weight: 500;">资源标题:</span>
|
|
1115
|
-
<span style="font-weight: 600; color: var(--text-primary);">${
|
|
1120
|
+
<span style="font-weight: 600; color: var(--text-primary);">${T.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
|
|
1116
1121
|
</div>
|
|
1117
1122
|
</div>
|
|
1118
1123
|
|
|
1119
|
-
${
|
|
1124
|
+
${T.details?`
|
|
1120
1125
|
<!-- 详细信息 -->
|
|
1121
1126
|
<div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
|
|
1122
1127
|
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
|
|
@@ -1156,33 +1161,33 @@
|
|
|
1156
1161
|
</div>
|
|
1157
1162
|
<div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
|
|
1158
1163
|
<span style="color: var(--text-tertiary); font-weight: 500;">IP地址:</span>
|
|
1159
|
-
<span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${
|
|
1164
|
+
<span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${T.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
|
|
1160
1165
|
<span style="color: var(--text-tertiary); font-weight: 500;">用户代理:</span>
|
|
1161
|
-
<span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${
|
|
1166
|
+
<span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${T.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
|
|
1162
1167
|
</div>
|
|
1163
1168
|
</div>
|
|
1164
1169
|
</div>
|
|
1165
|
-
`,document.getElementById("auditDescriptionText").innerHTML=
|
|
1170
|
+
`,document.getElementById("auditDescriptionText").innerHTML=k(T);const de=document.getElementById("auditDetailsContent");let ne="";if(T.details&&(T.details.field&&(ne+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',ne+='<span style="font-weight: 600; color: var(--text-primary);">'+p(T.details.field)+"</span>"),T.details.oldValue!==void 0&&T.details.oldValue!==null&&(ne+='<span style="color: var(--text-tertiary); font-weight: 500;">原始值:</span>',ne+=`<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;">`+h(T.details.oldValue)+"</div>"),T.details.newValue!==void 0&&T.details.newValue!==null&&(ne+='<span style="color: var(--text-tertiary); font-weight: 500;">新值:</span>',ne+=`<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;">`+h(T.details.newValue)+"</div>")),ne?(de.innerHTML=ne,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",T.changes&&(((G=T.changes.insertions)==null?void 0:G.length)>0||((J=T.changes.deletions)==null?void 0:J.length)>0)){const pe=document.getElementById("auditChangesContent");let a="";if(T.changes.insertions&&T.changes.insertions.length>0){const e=T.changes.insertions.reduce((n,d)=>n+d.length,0);a+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',a+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',a+='<div style="color: var(--text-secondary);">共 '+e+" 个字符</div>",a+="</div>"}if(T.changes.deletions&&T.changes.deletions.length>0){const e=T.changes.deletions.reduce((n,d)=>n+d.length,0);a+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',a+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',a+='<div style="color: var(--text-secondary);">共 '+e+" 个字符</div>",a+="</div>"}pe.innerHTML=a,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";X.classList.remove("hidden")}catch(ee){alert("加载详情失败: "+ee.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{w={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(w).forEach(o=>{w[o]||delete w[o]}),b=1,B(b,w)}),document.getElementById("prevPage").addEventListener("click",()=>{b>1&&(b--,B(b,w))}),document.getElementById("nextPage").addEventListener("click",()=>{b++,B(b,w)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const g=Object.keys(w||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(g))try{const f=await s.clearAuditLogs(w||{});alert(`已清除 ${f.deletedCount||0} 条操作记录`),b=1,await L(),await B(b,w)}catch(f){alert("清除失败: "+f.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),L(),B()}async function se(v){if(!c){v.innerHTML=`
|
|
1166
1171
|
<div class="empty-state" style="text-align: center; padding: 60px 20px;">
|
|
1167
1172
|
<div style="font-size: 64px; margin-bottom: 20px;">🗳️</div>
|
|
1168
1173
|
<h3 style="font-size: 24px; margin-bottom: 12px;">投票管理</h3>
|
|
1169
1174
|
<p style="color: var(--text-secondary); margin-bottom: 24px;">请先选择一个群组</p>
|
|
1170
1175
|
<button class="btn-primary" onclick="document.querySelector('[data-view=\\"groups\\"]').click()">前往群组管理</button>
|
|
1171
1176
|
</div>
|
|
1172
|
-
`;return}try{const
|
|
1177
|
+
`;return}try{const b=localStorage.getItem("token"),w=await fetch(`http://localhost:3000/api/polls/group/${c._id}`,{headers:{Authorization:`Bearer ${b}`}});if(!w.ok)throw new Error("获取投票列表失败");const L=(await w.json()).polls||[];v.innerHTML=`
|
|
1173
1178
|
<div class="view-header">
|
|
1174
|
-
<h2>🗳️ 投票管理 - ${
|
|
1179
|
+
<h2>🗳️ 投票管理 - ${c.name}</h2>
|
|
1175
1180
|
</div>
|
|
1176
1181
|
<div class="polls-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; padding: 20px;">
|
|
1177
|
-
${
|
|
1182
|
+
${L.length===0?'<div class="empty-state" style="grid-column: 1/-1;">暂无投票</div>':""}
|
|
1178
1183
|
</div>
|
|
1179
|
-
`;const
|
|
1184
|
+
`;const k=v.querySelector(".polls-grid");L.forEach(p=>{const h=p.options.reduce((f,E)=>f+E.votes.length,0),o=p.status==="ended"||p.endTime&&new Date(p.endTime)<new Date,g=document.createElement("div");g.className="poll-card",g.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;",g.innerHTML=`
|
|
1180
1185
|
<div style="display: flex; align-items: start; justify-content: space-between; margin-bottom: 15px;">
|
|
1181
1186
|
<div style="flex: 1;">
|
|
1182
1187
|
<h3 style="margin: 0 0 8px 0; font-size: 18px;">${p.title}</h3>
|
|
1183
1188
|
${p.description?`<p style="color: var(--text-secondary); margin: 0 0 12px 0; font-size: 14px;">${p.description}</p>`:""}
|
|
1184
1189
|
</div>
|
|
1185
|
-
<span class="status-badge" style="background: ${
|
|
1190
|
+
<span class="status-badge" style="background: ${o?"var(--danger)":"var(--success)"}; color: white; padding: 4px 12px; border-radius: 12px; font-size: 12px; white-space: nowrap;">${o?"已结束":"进行中"}</span>
|
|
1186
1191
|
</div>
|
|
1187
1192
|
|
|
1188
1193
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 15px;">
|
|
@@ -1193,19 +1198,19 @@
|
|
|
1193
1198
|
${p.anonymous?"🔒 匿名":"👤 实名"}
|
|
1194
1199
|
</span>
|
|
1195
1200
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
|
|
1196
|
-
👥 ${
|
|
1201
|
+
👥 ${h} 人投票
|
|
1197
1202
|
</span>
|
|
1198
1203
|
</div>
|
|
1199
1204
|
|
|
1200
1205
|
<div style="margin-bottom: 15px;">
|
|
1201
|
-
${p.options.map((
|
|
1206
|
+
${p.options.map((f,E)=>{const C=h>0?(f.votes.length/h*100).toFixed(1):0;return`
|
|
1202
1207
|
<div style="margin-bottom: 10px;">
|
|
1203
1208
|
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
|
|
1204
|
-
<span style="font-size: 14px; color: var(--text-primary);">${
|
|
1205
|
-
<span style="font-size: 14px; font-weight: 600; color: var(--primary);">${
|
|
1209
|
+
<span style="font-size: 14px; color: var(--text-primary);">${f.text}</span>
|
|
1210
|
+
<span style="font-size: 14px; font-weight: 600; color: var(--primary);">${f.votes.length} 票 (${C}%)</span>
|
|
1206
1211
|
</div>
|
|
1207
1212
|
<div style="height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden;">
|
|
1208
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
1213
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${C}%; transition: width 0.3s;"></div>
|
|
1209
1214
|
</div>
|
|
1210
1215
|
</div>
|
|
1211
1216
|
`}).join("")}
|
|
@@ -1218,15 +1223,15 @@
|
|
|
1218
1223
|
|
|
1219
1224
|
<div style="display: flex; gap: 10px;">
|
|
1220
1225
|
<button class="btn-primary btn-sm view-poll-detail" data-poll-id="${p._id}" style="flex: 1;">查看详情</button>
|
|
1221
|
-
${
|
|
1226
|
+
${o?"":`<button class="btn-secondary btn-sm end-poll" data-poll-id="${p._id}">结束投票</button>`}
|
|
1222
1227
|
<button class="btn-danger btn-sm delete-poll" data-poll-id="${p._id}">删除</button>
|
|
1223
1228
|
</div>
|
|
1224
|
-
`,
|
|
1229
|
+
`,g.onmouseenter=()=>{g.style.transform="translateY(-4px)",g.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},g.onmouseleave=()=>{g.style.transform="translateY(0)",g.style.boxShadow="none"},k.appendChild(g)}),document.querySelectorAll(".view-poll-detail").forEach(p=>{p.addEventListener("click",async()=>{const h=p.dataset.pollId;await ve(h)})}),document.querySelectorAll(".end-poll").forEach(p=>{p.addEventListener("click",async()=>{if(confirm("确定要结束这个投票吗?"))try{const h=localStorage.getItem("token");if(!(await fetch(`http://localhost:3000/api/polls/${p.dataset.pollId}/end`,{method:"PUT",headers:{Authorization:`Bearer ${h}`}})).ok)throw new Error("结束投票失败");alert("投票已结束!"),await se(v)}catch(h){alert("操作失败: "+h.message)}})}),document.querySelectorAll(".delete-poll").forEach(p=>{p.addEventListener("click",async()=>{if(confirm("确定要删除这个投票吗?"))try{const h=localStorage.getItem("token");if(!(await fetch(`http://localhost:3000/api/polls/${p.dataset.pollId}`,{method:"DELETE",headers:{Authorization:`Bearer ${h}`}})).ok)throw new Error("删除投票失败");alert("投票已删除!"),await se(v)}catch(h){alert("操作失败: "+h.message)}})})}catch(b){console.error("加载投票列表失败:",b),v.innerHTML=`
|
|
1225
1230
|
<div class="view-header">
|
|
1226
1231
|
<h2>🗳️ 投票管理</h2>
|
|
1227
1232
|
</div>
|
|
1228
|
-
<div class="empty-state">加载失败: ${
|
|
1229
|
-
`}}async function
|
|
1233
|
+
<div class="empty-state">加载失败: ${b.message}</div>
|
|
1234
|
+
`}}async function ve(v){try{const b=localStorage.getItem("token"),w=await fetch(`http://localhost:3000/api/polls/${v}`,{headers:{Authorization:`Bearer ${b}`}});if(!w.ok)throw new Error("获取投票详情失败");const L=(await w.json()).poll,k=L.options.reduce((g,f)=>g+f.votes.length,0),p=L.status==="ended"||L.endTime&&new Date(L.endTime)<new Date,h=`
|
|
1230
1235
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1231
1236
|
<div class="modal-content" style="max-width: 800px; max-height: 90vh; overflow-y: auto;">
|
|
1232
1237
|
<div class="modal-header">
|
|
@@ -1234,15 +1239,15 @@
|
|
|
1234
1239
|
<button class="modal-close" id="closePollDetailModal">×</button>
|
|
1235
1240
|
</div>
|
|
1236
1241
|
<div class="modal-body" style="padding: 24px;">
|
|
1237
|
-
<h2 style="margin: 0 0 12px 0;">${
|
|
1238
|
-
${
|
|
1242
|
+
<h2 style="margin: 0 0 12px 0;">${L.title}</h2>
|
|
1243
|
+
${L.description?`<p style="color: var(--text-secondary); margin: 0 0 20px 0;">${L.description}</p>`:""}
|
|
1239
1244
|
|
|
1240
1245
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 20px;">
|
|
1241
1246
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
1242
|
-
${
|
|
1247
|
+
${L.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
1243
1248
|
</span>
|
|
1244
1249
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px;">
|
|
1245
|
-
${
|
|
1250
|
+
${L.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
1246
1251
|
</span>
|
|
1247
1252
|
<span style="font-size: 13px; padding: 6px 12px; background: ${p?"var(--danger)":"var(--success)"}; border-radius: 14px; color: white;">
|
|
1248
1253
|
${p?"已结束":"进行中"}
|
|
@@ -1253,38 +1258,38 @@
|
|
|
1253
1258
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
1254
1259
|
<div>
|
|
1255
1260
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
1256
|
-
<div style="font-weight: 600;">👤 ${
|
|
1261
|
+
<div style="font-weight: 600;">👤 ${L.creatorName}</div>
|
|
1257
1262
|
</div>
|
|
1258
1263
|
<div>
|
|
1259
1264
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
1260
|
-
<div style="font-weight: 600; color: var(--primary);">👥 ${
|
|
1265
|
+
<div style="font-weight: 600; color: var(--primary);">👥 ${k} 人</div>
|
|
1261
1266
|
</div>
|
|
1262
1267
|
<div>
|
|
1263
1268
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
1264
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
1269
|
+
<div style="font-weight: 600;">⏰ ${new Date(L.createdAt).toLocaleString("zh-CN")}</div>
|
|
1265
1270
|
</div>
|
|
1266
|
-
${
|
|
1271
|
+
${L.endTime?`
|
|
1267
1272
|
<div>
|
|
1268
1273
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
1269
|
-
<div style="font-weight: 600;">⏰ ${new Date(
|
|
1274
|
+
<div style="font-weight: 600;">⏰ ${new Date(L.endTime).toLocaleString("zh-CN")}</div>
|
|
1270
1275
|
</div>
|
|
1271
1276
|
`:""}
|
|
1272
1277
|
</div>
|
|
1273
1278
|
</div>
|
|
1274
1279
|
|
|
1275
1280
|
<h3 style="margin-bottom: 16px;">投票选项</h3>
|
|
1276
|
-
${
|
|
1281
|
+
${L.options.map((g,f)=>{const E=k>0?(g.votes.length/k*100).toFixed(1):0;return`
|
|
1277
1282
|
<div style="margin-bottom: 16px; padding: 16px; background: var(--bg-tertiary); border-radius: 8px;">
|
|
1278
1283
|
<div style="display: flex; justify-content: space-between; margin-bottom: 8px;">
|
|
1279
|
-
<span style="font-weight: 500; font-size: 16px;">${
|
|
1280
|
-
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${
|
|
1284
|
+
<span style="font-weight: 500; font-size: 16px;">${g.text}</span>
|
|
1285
|
+
<span style="font-weight: 600; color: var(--primary); font-size: 16px;">${g.votes.length} 票 (${E}%)</span>
|
|
1281
1286
|
</div>
|
|
1282
1287
|
<div style="height: 10px; background: var(--bg-secondary); border-radius: 5px; overflow: hidden; margin-bottom: 12px;">
|
|
1283
|
-
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${
|
|
1288
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${E}%; transition: width 0.3s;"></div>
|
|
1284
1289
|
</div>
|
|
1285
|
-
${!
|
|
1290
|
+
${!L.anonymous&&g.votes.length>0?`
|
|
1286
1291
|
<div style="font-size: 13px; color: var(--text-secondary);">
|
|
1287
|
-
<strong>投票者:</strong> ${
|
|
1292
|
+
<strong>投票者:</strong> ${g.votes.map(C=>C.username).join(", ")}
|
|
1288
1293
|
</div>
|
|
1289
1294
|
`:""}
|
|
1290
1295
|
</div>
|
|
@@ -1294,7 +1299,7 @@
|
|
|
1294
1299
|
</div>
|
|
1295
1300
|
</div>
|
|
1296
1301
|
</div>
|
|
1297
|
-
`,
|
|
1302
|
+
`,o=document.getElementById("pollDetailModal");o&&o.remove(),document.body.insertAdjacentHTML("beforeend",h),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()})}catch(b){console.error("加载投票详情失败:",b),alert("加载投票详情失败: "+b.message)}}async function P(v){v.innerHTML=`
|
|
1298
1303
|
<div class="view-header">
|
|
1299
1304
|
<h2>🔍 搜索</h2>
|
|
1300
1305
|
</div>
|
|
@@ -1316,20 +1321,20 @@
|
|
|
1316
1321
|
</div>
|
|
1317
1322
|
<div class="search-results" id="searchResults"></div>
|
|
1318
1323
|
</div>
|
|
1319
|
-
`;const
|
|
1324
|
+
`;const b=document.getElementById("searchInput"),w=document.getElementById("searchBtn"),B=document.getElementById("searchResults"),L=async()=>{const k=b.value.trim();if(!k){B.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const p={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};B.innerHTML='<div class="loading">搜索中...</div>';try{const h=[];if(p.messages&&c)try{const o=await s.getGroupMessages(c._id);o.messages&&o.messages.filter(f=>f.content.toLowerCase().includes(k.toLowerCase())).forEach(f=>{h.push({type:"message",title:`消息 - ${f.username}`,content:f.content,time:f.timestamp,group:c.name})})}catch(o){console.error("搜索消息失败:",o)}if(p.documents)try{if(c){const o=await s.getDocuments(c._id);o.documents&&o.documents.filter(f=>f.title.toLowerCase().includes(k.toLowerCase())||f.content.toLowerCase().includes(k.toLowerCase())).forEach(f=>{h.push({type:"document",title:f.title,content:f.content.substring(0,200),time:f.updatedAt,id:f._id,group:c.name})})}}catch(o){console.error("搜索文档失败:",o)}if(p.tasks&&c)try{const o=await s.getTasks(c._id);o.tasks&&o.tasks.filter(f=>f.title.toLowerCase().includes(k.toLowerCase())||f.description&&f.description.toLowerCase().includes(k.toLowerCase())).forEach(f=>{h.push({type:"task",title:f.title,content:f.description||"",time:f.updatedAt,id:f._id,status:f.status,group:c.name})})}catch(o){console.error("搜索任务失败:",o)}h.length===0?B.innerHTML='<div class="empty-state">未找到相关结果</div>':B.innerHTML=h.map(o=>`
|
|
1320
1325
|
<div class="search-result-item">
|
|
1321
1326
|
<div class="result-header">
|
|
1322
|
-
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[
|
|
1323
|
-
<span class="result-time">${new Date(
|
|
1327
|
+
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[o.type]} ${o.type==="message"?"消息":o.type==="document"?"文档":"任务"}</span>
|
|
1328
|
+
<span class="result-time">${new Date(o.time).toLocaleString()}</span>
|
|
1324
1329
|
</div>
|
|
1325
|
-
<h4>${
|
|
1326
|
-
<p>${
|
|
1327
|
-
${
|
|
1328
|
-
${
|
|
1330
|
+
<h4>${A(o.title,k)}</h4>
|
|
1331
|
+
<p>${A(o.content,k)}</p>
|
|
1332
|
+
${o.group?`<span class="result-group">群组: ${o.group}</span>`:""}
|
|
1333
|
+
${o.status?`<span class="result-status">状态: ${$(o.status)}</span>`:""}
|
|
1329
1334
|
</div>
|
|
1330
|
-
`).join("")}catch(
|
|
1335
|
+
`).join("")}catch(h){B.innerHTML=`<div class="empty-state">搜索失败: ${h.message}</div>`}};w.addEventListener("click",L),b.addEventListener("keypress",k=>{k.key==="Enter"&&L()})}function A(v,b){if(!b)return v;const w=new RegExp(`(${b})`,"gi");return v.replace(w,"<mark>$1</mark>")}function V(v){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[v]||v}function $(v){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[v]||v}async function M(v){var b;if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const w=localStorage.getItem("token"),B=await fetch(`http://localhost:3000/api/knowledge/group/${c._id}`,{headers:{Authorization:`Bearer ${w}`}});if(!B.ok)throw new Error(`HTTP ${B.status}: ${B.statusText}`);const L=await B.json();console.log("知识库数据:",L);const k=((b=L.data)==null?void 0:b.knowledgeList)||[];console.log("知识库条目数量:",k.length),v.innerHTML=`
|
|
1331
1336
|
<div class="view-header">
|
|
1332
|
-
<h2>📚 知识库管理 - ${
|
|
1337
|
+
<h2>📚 知识库管理 - ${c.name}</h2>
|
|
1333
1338
|
<button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
|
|
1334
1339
|
</div>
|
|
1335
1340
|
<div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
|
|
@@ -1366,26 +1371,26 @@
|
|
|
1366
1371
|
</form>
|
|
1367
1372
|
</div>
|
|
1368
1373
|
</div>
|
|
1369
|
-
`;const p=document.getElementById("knowledgeList");
|
|
1370
|
-
${
|
|
1371
|
-
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${
|
|
1372
|
-
<p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${
|
|
1374
|
+
`;const p=document.getElementById("knowledgeList");k.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(k.forEach(h=>{var g;const o=document.createElement("div");o.className="knowledge-card",o.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",o.innerHTML=`
|
|
1375
|
+
${h.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
|
|
1376
|
+
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${h.isShared?"padding-right: 80px;":""}">${h.title}</h3>
|
|
1377
|
+
<p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${h.content.substring(0,150)}${h.content.length>150?"...":""}</p>
|
|
1373
1378
|
<div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
|
|
1374
|
-
<span>👤 ${((
|
|
1375
|
-
<span style="margin-left: 15px;">📅 ${new Date(
|
|
1379
|
+
<span>👤 ${((g=h.author)==null?void 0:g.username)||"未知"}</span>
|
|
1380
|
+
<span style="margin-left: 15px;">📅 ${new Date(h.createdAt).toLocaleDateString()}</span>
|
|
1376
1381
|
</div>
|
|
1377
|
-
${
|
|
1382
|
+
${h.tags&&h.tags.length>0?`
|
|
1378
1383
|
<div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
|
|
1379
|
-
${
|
|
1384
|
+
${h.tags.map(f=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${f}</span>`).join("")}
|
|
1380
1385
|
</div>
|
|
1381
1386
|
`:""}
|
|
1382
1387
|
<div style="display: flex; gap: 10px;">
|
|
1383
|
-
<button class="btn-secondary btn-sm" data-id="${
|
|
1384
|
-
<button class="btn-danger btn-sm" data-id="${
|
|
1388
|
+
<button class="btn-secondary btn-sm" data-id="${h._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
1389
|
+
<button class="btn-danger btn-sm" data-id="${h._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1385
1390
|
</div>
|
|
1386
|
-
`,
|
|
1391
|
+
`,o.onmouseenter=()=>{o.style.transform="translateY(-4px)",o.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},o.onmouseleave=()=>{o.style.transform="translateY(0)",o.style.boxShadow="none"},p.appendChild(o)}),document.querySelectorAll('[data-action="edit"]').forEach(h=>{h.addEventListener("click",async()=>{var g;const o=k.find(f=>f._id===h.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=o.title,document.querySelector('[name="content"]').value=o.content,document.querySelector('[name="tags"]').value=((g=o.tags)==null?void 0:g.join(", "))||"",document.getElementById("isSharedCheckbox").checked=o.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=o._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(h=>{h.addEventListener("click",async()=>{try{const o=await fetch(`http://localhost:3000/api/backup/download/${h.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${w}`}});if(!o.ok)throw new Error("下载失败");const g=await o.blob(),f=window.URL.createObjectURL(g),E=document.createElement("a");E.href=f,E.download=h.dataset.filename,document.body.appendChild(E),E.click(),window.URL.revokeObjectURL(f),document.body.removeChild(E)}catch(o){alert("下载失败: "+o.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(h=>{h.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${h.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${w}`}}),alert("删除成功!"),await M(v)}catch(o){alert("删除失败: "+o.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 h=>{h.preventDefault();const o=new FormData(h.target),g={title:o.get("title"),content:o.get("content"),tags:o.get("tags").split(",").map(f=>f.trim()).filter(f=>f),groupId:c._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const f=h.target.dataset.editId,E=f?`http://localhost:3000/api/knowledge/${f}`:"http://localhost:3000/api/knowledge",G=await fetch(E,{method:f?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`},body:JSON.stringify(g)});if(!G.ok){const ee=await G.json();throw new Error(ee.message||"操作失败")}const J=await G.json();console.log("知识库操作结果:",J),alert(f?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await M(v)}catch(f){console.error("知识库操作错误:",f),alert("操作失败: "+f.message)}})}catch(w){v.innerHTML=`<div class="empty-state">加载失败: ${w.message}</div>`}}function N(v){if(!v)return"未设置";if(typeof v=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[v]||v;const b=[];if(v.event){const w={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};b.push(w[v.event]||v.event)}if(v.conditions&&Object.keys(v.conditions).length>0){const w=[];for(const[B,L]of Object.entries(v.conditions)){const p={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[B]||B;w.push(`${p}=${L}`)}w.length>0&&b.push(`(条件: ${w.join(", ")})`)}return v.schedule&&b.push(`⏱️ 定时: ${v.schedule}`),b.length>0?b.join(" "):"自定义触发条件"}async function H(v){var b;if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const w=localStorage.getItem("token"),k=((b=(await(await fetch(`http://localhost:3000/api/workflows/group/${c._id}`,{headers:{Authorization:`Bearer ${w}`}})).json()).data)==null?void 0:b.workflows)||[];v.innerHTML=`
|
|
1387
1392
|
<div class="view-header">
|
|
1388
|
-
<h2>⚙️ 工作流管理 - ${
|
|
1393
|
+
<h2>⚙️ 工作流管理 - ${c.name}</h2>
|
|
1389
1394
|
<button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</button>
|
|
1390
1395
|
</div>
|
|
1391
1396
|
<div class="workflow-grid" id="workflowList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
|
|
@@ -1448,43 +1453,43 @@
|
|
|
1448
1453
|
</form>
|
|
1449
1454
|
</div>
|
|
1450
1455
|
</div>
|
|
1451
|
-
`;const p=document.getElementById("workflowList");
|
|
1456
|
+
`;const p=document.getElementById("workflowList");k.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无工作流</div>':(k.forEach(h=>{const o=document.createElement("div");o.className="workflow-card",o.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const g=h.status==="active";o.innerHTML=`
|
|
1452
1457
|
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
|
|
1453
|
-
<h3 style="margin: 0; font-size: 18px;">${
|
|
1454
|
-
<span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${
|
|
1458
|
+
<h3 style="margin: 0; font-size: 18px;">${h.name}</h3>
|
|
1459
|
+
<span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${g?"var(--success)":"var(--warning)"}; color: white;">${g?"✅ 活跃":"⏸️ 暂停"}</span>
|
|
1455
1460
|
</div>
|
|
1456
|
-
<p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${
|
|
1461
|
+
<p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${h.description||"无描述"}</p>
|
|
1457
1462
|
<div class="workflow-meta" style="font-size: 12px; color: var(--text-tertiary); margin: 10px 0;">
|
|
1458
|
-
<span>🔔 触发条件: ${
|
|
1463
|
+
<span>🔔 触发条件: ${N(h.trigger)}</span>
|
|
1459
1464
|
</div>
|
|
1460
1465
|
<div style="display: flex; gap: 10px; margin-top: 15px;">
|
|
1461
|
-
<button class="btn-secondary btn-sm" data-id="${
|
|
1462
|
-
${
|
|
1466
|
+
<button class="btn-secondary btn-sm" data-id="${h._id}" data-action="toggle" style="flex: 1;">
|
|
1467
|
+
${g?"⏸️ 暂停":"▶️ 启用"}
|
|
1463
1468
|
</button>
|
|
1464
|
-
<button class="btn-danger btn-sm" data-id="${
|
|
1469
|
+
<button class="btn-danger btn-sm" data-id="${h._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1465
1470
|
</div>
|
|
1466
|
-
`,
|
|
1471
|
+
`,o.onmouseenter=()=>{o.style.transform="translateY(-4px)",o.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},o.onmouseleave=()=>{o.style.transform="translateY(0)",o.style.boxShadow="none"},p.appendChild(o)}),document.querySelectorAll('[data-action="toggle"]').forEach(h=>{h.addEventListener("click",async()=>{try{await fetch(`http://localhost:3000/api/workflows/${h.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${w}`}}),await H(v)}catch(o){alert("操作失败: "+o.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(h=>{h.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:3000/api/workflows/${h.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${w}`}}),alert("删除成功!"),await H(v)}catch(o){alert("删除失败: "+o.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async h=>{h.preventDefault();const o=new FormData(h.target),g={name:o.get("name"),description:o.get("description"),trigger:o.get("trigger"),groupId:c._id,actions:[]};try{await fetch("http://localhost:3000/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${w}`},body:JSON.stringify(g)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await H(v)}catch(f){alert("创建失败: "+f.message)}})}catch(w){v.innerHTML=`<div class="empty-state">加载失败: ${w.message}</div>`}}async function _(v){var b;try{const w=localStorage.getItem("token"),k=((b=(await(await fetch("http://localhost:3000/api/backup/list",{headers:{Authorization:`Bearer ${w}`}})).json()).data)==null?void 0:b.backups)||[];v.innerHTML=`
|
|
1467
1472
|
<div class="view-header">
|
|
1468
1473
|
<h2>💾 备份管理</h2>
|
|
1469
1474
|
<button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
|
|
1470
1475
|
</div>
|
|
1471
1476
|
<div class="backup-grid" id="backupList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
|
|
1472
|
-
`;const p=document.getElementById("backupList");
|
|
1477
|
+
`;const p=document.getElementById("backupList");k.length===0?p.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无备份</div>':(k.forEach(h=>{const o=document.createElement("div");o.className="backup-card",o.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const g=(h.size/1024/1024).toFixed(2);o.innerHTML=`
|
|
1473
1478
|
<div style="display: flex; align-items: center; gap: 15px; margin-bottom: 15px;">
|
|
1474
1479
|
<div style="font-size: 48px;">📦</div>
|
|
1475
1480
|
<div style="flex: 1;">
|
|
1476
|
-
<h3 style="margin: 0 0 5px 0; font-size: 16px;">${
|
|
1477
|
-
<p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${
|
|
1481
|
+
<h3 style="margin: 0 0 5px 0; font-size: 16px;">${h.filename}</h3>
|
|
1482
|
+
<p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${g} MB</p>
|
|
1478
1483
|
</div>
|
|
1479
1484
|
</div>
|
|
1480
1485
|
<div class="backup-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 15px;">
|
|
1481
|
-
<span>📅 ${new Date(
|
|
1486
|
+
<span>📅 ${new Date(h.createdAt).toLocaleString()}</span>
|
|
1482
1487
|
</div>
|
|
1483
1488
|
<div style="display: flex; gap: 10px;">
|
|
1484
|
-
<button class="btn-primary btn-sm" data-backup-name="${
|
|
1485
|
-
<button class="btn-danger btn-sm" data-backup-name="${
|
|
1489
|
+
<button class="btn-primary btn-sm" data-backup-name="${h.name}" data-filename="${h.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
|
|
1490
|
+
<button class="btn-danger btn-sm" data-backup-name="${h.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
1486
1491
|
</div>
|
|
1487
|
-
`,
|
|
1492
|
+
`,o.onmouseenter=()=>{o.style.transform="translateY(-4px)",o.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},o.onmouseleave=()=>{o.style.transform="translateY(0)",o.style.boxShadow="none"},p.appendChild(o)}),document.querySelectorAll('[data-action="download"]').forEach(h=>{h.addEventListener("click",async()=>{try{const o=h.dataset.backupName,g=h.dataset.filename,f=localStorage.getItem("token");console.log("开始下载备份:",{backupName:o,filename:g});const E=await fetch(`http://localhost:3000/api/backup/download/${o}`,{method:"GET",headers:{Authorization:`Bearer ${f}`}});if(!E.ok)throw new Error(`下载失败: ${E.status} ${E.statusText}`);const C=await E.blob();console.log("文件下载成功,大小:",C.size,"bytes");const G=window.URL.createObjectURL(C),J=document.createElement("a");J.href=G,J.download=g,J.style.display="none",document.body.appendChild(J),J.click(),setTimeout(()=>{document.body.removeChild(J),window.URL.revokeObjectURL(G)},100);const ee=h.textContent;h.textContent="✅ 下载成功",h.disabled=!0,setTimeout(()=>{h.textContent=ee,h.disabled=!1},2e3)}catch(o){console.error("下载失败:",o),alert("下载失败: "+o.message),h.textContent="⬇️ 下载",h.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(h=>{h.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const o=h.dataset.backupName;console.log("删除备份:",o);const g=await fetch(`http://localhost:3000/api/backup/${o}`,{method:"DELETE",headers:{Authorization:`Bearer ${w}`}});if(!g.ok)throw new Error(`删除失败: ${g.status}`);alert("删除成功!"),await _(v)}catch(o){console.error("删除失败:",o),alert("删除失败: "+o.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const h=document.getElementById("createBackupBtn");h.disabled=!0,h.textContent="⏳ 创建中...";try{await fetch("http://localhost:3000/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${w}`}}),alert("备份创建成功!"),await _(v)}catch(o){alert("创建失败: "+o.message)}finally{h.disabled=!1,h.textContent="➕ 创建备份"}}})}catch(w){v.innerHTML=`<div class="empty-state">加载失败: ${w.message}</div>`}}async function O(v){v.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function Z(v){v.innerHTML=`
|
|
1488
1493
|
<div class="view-header">
|
|
1489
1494
|
<h2>📤 数据导出</h2>
|
|
1490
1495
|
</div>
|
|
@@ -1529,17 +1534,17 @@
|
|
|
1529
1534
|
<div id="historyList">加载中...</div>
|
|
1530
1535
|
</div>
|
|
1531
1536
|
</div>
|
|
1532
|
-
`;try{const
|
|
1537
|
+
`;try{const b=localStorage.getItem("token"),B=await(await fetch("http://localhost:3000/api/export/history",{headers:{Authorization:`Bearer ${b}`}})).json(),L=document.getElementById("historyList");B.exports&&B.exports.length>0?L.innerHTML=B.exports.map(k=>`
|
|
1533
1538
|
<div class="export-item" style="display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--bg); border-radius: 8px; margin-bottom: 10px;">
|
|
1534
1539
|
<div>
|
|
1535
|
-
<div style="font-weight: 600; margin-bottom: 5px;">📦 ${
|
|
1536
|
-
<div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(
|
|
1540
|
+
<div style="font-weight: 600; margin-bottom: 5px;">📦 ${k.format.toUpperCase()} 导出</div>
|
|
1541
|
+
<div style="font-size: 12px; color: var(--text-secondary);">📅 ${new Date(k.createdAt).toLocaleString()}</div>
|
|
1537
1542
|
</div>
|
|
1538
|
-
<a href="http://localhost:3000/api/export/download/${
|
|
1543
|
+
<a href="http://localhost:3000/api/export/download/${k.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
|
|
1539
1544
|
</div>
|
|
1540
|
-
`).join(""):
|
|
1545
|
+
`).join(""):L.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const b={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},w=document.getElementById("exportBtn");w.disabled=!0,w.textContent="⏳ 导出中...";try{const B=localStorage.getItem("token"),L=await fetch("http://localhost:3000/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${B}`},body:JSON.stringify(b)});if(L.ok){const k=await L.blob(),p=window.URL.createObjectURL(k),h=document.createElement("a");h.href=p,h.download=`export-${Date.now()}.${b.format}`,h.click(),alert("导出成功!"),await Z(v)}else throw new Error("导出失败")}catch(B){alert("导出失败: "+B.message)}finally{w.disabled=!1,w.textContent="🚀 开始导出"}})}async function Y(v){if(!c){v.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}v.innerHTML=`
|
|
1541
1546
|
<div class="view-header">
|
|
1542
|
-
<h2>🎨 协作白板 - ${
|
|
1547
|
+
<h2>🎨 协作白板 - ${c.name}</h2>
|
|
1543
1548
|
<div style="display: flex; gap: 10px;">
|
|
1544
1549
|
<button class="btn-secondary" id="clearCanvas">清空画布</button>
|
|
1545
1550
|
<button class="btn-primary" id="saveCanvas">保存白板</button>
|
|
@@ -1556,10 +1561,10 @@
|
|
|
1556
1561
|
</div>
|
|
1557
1562
|
<canvas id="whiteboard" width="1200" height="600" style="border: 1px solid var(--border); background: white; cursor: crosshair;"></canvas>
|
|
1558
1563
|
<div class="whiteboard-users" id="whiteboardUsers">
|
|
1559
|
-
<span class="user-badge">👤 ${
|
|
1564
|
+
<span class="user-badge">👤 ${r.username}</span>
|
|
1560
1565
|
</div>
|
|
1561
1566
|
</div>
|
|
1562
|
-
`;const
|
|
1567
|
+
`;const b=document.getElementById("whiteboard"),w=b.getContext("2d");let B=!1,L="pen",k="#000000",p=3;document.querySelectorAll(".tool-btn").forEach(g=>{g.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(f=>f.classList.remove("active")),g.classList.add("active"),L=g.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",g=>{k=g.target.value}),document.getElementById("brushSize").addEventListener("input",g=>{p=g.target.value});let h=0,o=0;b.addEventListener("mousedown",g=>{B=!0;const f=b.getBoundingClientRect();h=g.clientX-f.left,o=g.clientY-f.top}),b.addEventListener("mousemove",g=>{if(!B)return;const f=b.getBoundingClientRect(),E=g.clientX-f.left,C=g.clientY-f.top;w.beginPath(),w.moveTo(h,o),w.lineTo(E,C),w.strokeStyle=L==="eraser"?"#ffffff":k,w.lineWidth=p,w.lineCap="round",w.stroke(),h=E,o=C,t.sendWhiteboardData(c._id,{tool:L,color:k,size:p,from:{x:h,y:o},to:{x:E,y:C}})}),b.addEventListener("mouseup",()=>{B=!1}),b.addEventListener("mouseleave",()=>{B=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&w.clearRect(0,0,b.width,b.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const g=b.toDataURL("image/png"),f=document.createElement("a");f.download=`whiteboard-${Date.now()}.png`,f.href=g,f.click(),alert("白板已保存!")}),t.on("whiteboard_draw",g=>{g.groupId===c._id&&g.userId!==l&&(w.beginPath(),w.moveTo(g.from.x,g.from.y),w.lineTo(g.to.x,g.to.y),w.strokeStyle=g.tool==="eraser"?"#ffffff":g.color,w.lineWidth=g.size,w.lineCap="round",w.stroke())})}async function xe(v){v.innerHTML=`
|
|
1563
1568
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1564
1569
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1565
1570
|
<span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚙️ 设置中心</span>
|
|
@@ -1575,11 +1580,11 @@
|
|
|
1575
1580
|
</div>
|
|
1576
1581
|
<div class="setting-item" style="margin-bottom: 20px;">
|
|
1577
1582
|
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">👤 用户名</label>
|
|
1578
|
-
<input type="text" value="${
|
|
1583
|
+
<input type="text" value="${r.username}" disabled style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--bg-tertiary); cursor: not-allowed;">
|
|
1579
1584
|
</div>
|
|
1580
1585
|
<div class="setting-item" style="margin-bottom: 20px;">
|
|
1581
1586
|
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">📧 邮箱</label>
|
|
1582
|
-
<input type="email" id="userEmail" value="${
|
|
1587
|
+
<input type="email" id="userEmail" value="${r.email||""}" placeholder="请输入邮箱地址" style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; transition: border-color 0.2s;">
|
|
1583
1588
|
</div>
|
|
1584
1589
|
<div class="setting-item">
|
|
1585
1590
|
<label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">🔐 密码</label>
|
|
@@ -1655,7 +1660,7 @@
|
|
|
1655
1660
|
<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
1661
|
</div>
|
|
1657
1662
|
</div>
|
|
1658
|
-
`,document.querySelectorAll(".settings-card").forEach(
|
|
1663
|
+
`,document.querySelectorAll(".settings-card").forEach(w=>{w.addEventListener("mouseenter",()=>{w.style.transform="translateY(-4px)",w.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),w.addEventListener("mouseleave",()=>{w.style.transform="translateY(0)",w.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(w=>{w.addEventListener("focus",()=>{w.style.borderColor="var(--primary)"}),w.addEventListener("blur",()=>{w.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 w={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(w)),ie(w.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",w=>{ie(w.target.value)});const b=localStorage.getItem("userSettings");if(b){const w=JSON.parse(b);w.email&&(document.getElementById("userEmail").value=w.email),document.getElementById("emailNotifications").checked=w.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=w.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=w.soundNotifications!==!1,w.theme&&(document.getElementById("themeSelect").value=w.theme,ie(w.theme)),w.language&&(document.getElementById("languageSelect").value=w.language)}}function ie(v){const b=document.documentElement,w={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},B=w[v]||w.dark;b.style.setProperty("--primary",B.primary),b.style.setProperty("--primary-dark",B.primaryDark),b.style.setProperty("--secondary",B.secondary),b.style.setProperty("--bg-dark",B.bgDark),b.style.setProperty("--bg-card",B.bgCard),b.style.setProperty("--bg-hover",B.bgHover),b.style.setProperty("--text-primary",B.textPrimary),b.style.setProperty("--text-secondary",B.textSecondary),b.style.setProperty("--border",B.border),localStorage.setItem("currentTheme",v)}async function we(v){v.innerHTML=`
|
|
1659
1664
|
<div class="view-header" style="margin-bottom: 30px;">
|
|
1660
1665
|
<h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
|
|
1661
1666
|
<span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">❓ 帮助中心</span>
|
|
@@ -1835,29 +1840,29 @@
|
|
|
1835
1840
|
</div>
|
|
1836
1841
|
</div>
|
|
1837
1842
|
</div>
|
|
1838
|
-
`,document.querySelectorAll(".help-card").forEach(
|
|
1843
|
+
`,document.querySelectorAll(".help-card").forEach(b=>{b.addEventListener("mouseenter",()=>{b.style.transform="translateY(-4px)",b.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),b.addEventListener("mouseleave",()=>{b.style.transform="translateY(0)";const w=b.style.background.includes("gradient");b.style.boxShadow=w?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function ae(v){const b=document.getElementById("contentArea");switch(v){case"groups":await D(b);break;case"tasks":await U(b);break;case"documents":await W(b);break;case"chat":await K(b);break;case"files":await oe(b);break;case"search":await P(b);break;case"call":await te(b);break;case"audit":await me(b);break;case"polls":await se(b);break;case"knowledge":await M(b);break;case"workflow":await H(b);break;case"backup":await _(b);break;case"export":await Z(b);break;case"ai":await O(b);break;case"export":await Z(b);break;case"whiteboard":await Y(b);break;case"settings":await xe(b);break;case"help":await we(b);break}}ae("groups")}function Qr(r){const t=document.documentElement,i={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"}},s=i[r]||i.dark;t.style.setProperty("--primary",s.primary),t.style.setProperty("--primary-dark",s.primaryDark),t.style.setProperty("--secondary",s.secondary),t.style.setProperty("--bg-dark",s.bgDark),t.style.setProperty("--bg-card",s.bgCard),t.style.setProperty("--bg-hover",s.bgHover),t.style.setProperty("--text-primary",s.textPrimary),t.style.setProperty("--text-secondary",s.textSecondary),t.style.setProperty("--border",s.border)}function ea(r,t){const i=document.getElementById("app"),s=new Ut,m=new Je,l=r.id||r._id;let c=null,u=[];function F(P){if(P.startsWith("[白板作品]")){const A=P.replace("[白板作品]","").trim(),V=A.includes("/api/files/")&&A.includes("/download");let $=A;if(V&&!A.includes("token=")){const M=localStorage.getItem("token");$=A.includes("?")?`${A}&token=${M}`:`${A}?token=${M}`}return`
|
|
1839
1844
|
<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
1845
|
<div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
|
|
1841
1846
|
<span style="font-size: 18px;">🎨</span>
|
|
1842
1847
|
<span>白板作品</span>
|
|
1843
1848
|
</div>
|
|
1844
1849
|
<div style="position: relative; display: inline-block;">
|
|
1845
|
-
<img src="${
|
|
1850
|
+
<img src="${$}" alt="白板作品"
|
|
1846
1851
|
style="max-width: 400px; max-height: 400px; border-radius: 8px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); background: rgba(255,255,255,0.1);"
|
|
1847
|
-
onclick="window.open('${
|
|
1848
|
-
onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${
|
|
1849
|
-
onload="console.log('白板图片加载成功:', '${
|
|
1852
|
+
onclick="window.open('${$}', '_blank')"
|
|
1853
|
+
onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${$}');"
|
|
1854
|
+
onload="console.log('白板图片加载成功:', '${$}');">
|
|
1850
1855
|
<div style="display: none; padding: 20px; background: rgba(255,255,255,0.1); border: 2px dashed rgba(255,255,255,0.3); border-radius: 8px; text-align: center; color: white;">
|
|
1851
1856
|
<div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
|
|
1852
1857
|
<div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
|
|
1853
1858
|
<div style="font-size: 12px;">图片可能已被删除或URL无效</div>
|
|
1854
|
-
<button onclick="navigator.clipboard.writeText('${
|
|
1859
|
+
<button onclick="navigator.clipboard.writeText('${A}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
|
|
1855
1860
|
</div>
|
|
1856
1861
|
<div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
|
|
1857
1862
|
</div>
|
|
1858
1863
|
</div>
|
|
1859
|
-
`}if(
|
|
1860
|
-
<div class="poll-card" data-poll-id="${
|
|
1864
|
+
`}if(P.startsWith("[投票]")){const A=P.replace("[投票]","").trim();return`
|
|
1865
|
+
<div class="poll-card" data-poll-id="${A}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${A}')">
|
|
1861
1866
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
1862
1867
|
<span style="font-size: 32px;">📊</span>
|
|
1863
1868
|
<div style="flex: 1;">
|
|
@@ -1869,25 +1874,25 @@
|
|
|
1869
1874
|
📋 查看投票详情
|
|
1870
1875
|
</div>
|
|
1871
1876
|
</div>
|
|
1872
|
-
`}return
|
|
1873
|
-
<div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${
|
|
1877
|
+
`}return P}window.viewPollDetail=async P=>{try{const V=(await s.getPoll(P)).poll,$=V.options.reduce((Y,xe)=>Y+xe.votes.length,0),M=V.options.some(Y=>Y.votes.includes(l)),N=V.status==="ended"||V.endTime&&new Date(V.endTime)<new Date;let H="";V.options.forEach((Y,xe)=>{const ie=$>0?(Y.votes.length/$*100).toFixed(1):0,we=Y.votes.includes(l);H+=`
|
|
1878
|
+
<div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${we?"var(--primary)":"var(--border)"};">
|
|
1874
1879
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1875
1880
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1876
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1877
|
-
${
|
|
1881
|
+
<span style="font-weight: 500; color: var(--text-primary);">${Y.text}</span>
|
|
1882
|
+
${we?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
|
|
1878
1883
|
</div>
|
|
1879
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1884
|
+
<span style="font-weight: 600; color: var(--primary);">${Y.votes.length} 票 (${ie}%)</span>
|
|
1880
1885
|
</div>
|
|
1881
1886
|
<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: ${
|
|
1887
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${ie}%; transition: width 0.3s;"></div>
|
|
1883
1888
|
</div>
|
|
1884
|
-
${!
|
|
1889
|
+
${!V.anonymous&&Y.votes.length>0?`
|
|
1885
1890
|
<div style="margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
|
|
1886
|
-
投票者: ${
|
|
1891
|
+
投票者: ${Y.voterNames?Y.voterNames.join(", "):""}
|
|
1887
1892
|
</div>
|
|
1888
1893
|
`:""}
|
|
1889
1894
|
</div>
|
|
1890
|
-
`});const
|
|
1895
|
+
`});const _=`
|
|
1891
1896
|
<div id="pollDetailModal" class="modal" style="display: flex;">
|
|
1892
1897
|
<div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
|
|
1893
1898
|
<div class="modal-header">
|
|
@@ -1896,37 +1901,37 @@
|
|
|
1896
1901
|
</div>
|
|
1897
1902
|
<div class="modal-body" style="padding: 24px;">
|
|
1898
1903
|
<div style="margin-bottom: 24px;">
|
|
1899
|
-
<h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${
|
|
1900
|
-
${
|
|
1904
|
+
<h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${V.title}</h2>
|
|
1905
|
+
${V.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${V.description}</p>`:""}
|
|
1901
1906
|
|
|
1902
1907
|
<div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px;">
|
|
1903
1908
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
|
|
1904
|
-
${
|
|
1909
|
+
${V.allowMultiple?"✓ 多选投票":"○ 单选投票"}
|
|
1905
1910
|
</span>
|
|
1906
1911
|
<span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
|
|
1907
|
-
${
|
|
1912
|
+
${V.anonymous?"🔒 匿名投票":"👤 实名投票"}
|
|
1908
1913
|
</span>
|
|
1909
|
-
${
|
|
1914
|
+
${N?'<span style="font-size: 13px; padding: 6px 12px; background: var(--danger); border-radius: 14px; color: white;">已结束</span>':'<span style="font-size: 13px; padding: 6px 12px; background: var(--success); border-radius: 14px; color: white;">进行中</span>'}
|
|
1910
1915
|
</div>
|
|
1911
1916
|
|
|
1912
1917
|
<div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; border-left: 4px solid var(--primary); margin-bottom: 24px;">
|
|
1913
1918
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
|
|
1914
1919
|
<div>
|
|
1915
1920
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
|
|
1916
|
-
<div style="font-weight: 600; color: var(--text-primary);">👤 ${
|
|
1921
|
+
<div style="font-weight: 600; color: var(--text-primary);">👤 ${V.creatorName}</div>
|
|
1917
1922
|
</div>
|
|
1918
1923
|
<div>
|
|
1919
1924
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
|
|
1920
|
-
<div style="font-weight: 600; color: var(--primary);">👥 ${
|
|
1925
|
+
<div style="font-weight: 600; color: var(--primary);">👥 ${$} 人</div>
|
|
1921
1926
|
</div>
|
|
1922
1927
|
<div>
|
|
1923
1928
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
|
|
1924
|
-
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(
|
|
1929
|
+
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(V.createdAt).toLocaleString("zh-CN")}</div>
|
|
1925
1930
|
</div>
|
|
1926
|
-
${
|
|
1931
|
+
${V.endTime?`
|
|
1927
1932
|
<div>
|
|
1928
1933
|
<div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
|
|
1929
|
-
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(
|
|
1934
|
+
<div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(V.endTime).toLocaleString("zh-CN")}</div>
|
|
1930
1935
|
</div>
|
|
1931
1936
|
`:""}
|
|
1932
1937
|
</div>
|
|
@@ -1935,28 +1940,28 @@
|
|
|
1935
1940
|
|
|
1936
1941
|
<div style="margin-bottom: 24px;">
|
|
1937
1942
|
<h3 style="margin-bottom: 16px; color: var(--text-primary);">投票选项</h3>
|
|
1938
|
-
${!
|
|
1943
|
+
${!N&&!M?`
|
|
1939
1944
|
<form id="voteForm">
|
|
1940
|
-
${
|
|
1945
|
+
${V.options.map((Y,xe)=>{const ie=$>0?(Y.votes.length/$*100).toFixed(1):0;return`
|
|
1941
1946
|
<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
1947
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
1943
1948
|
<div style="display: flex; align-items: center; gap: 8px;">
|
|
1944
|
-
<input type="${
|
|
1945
|
-
<span style="font-weight: 500; color: var(--text-primary);">${
|
|
1949
|
+
<input type="${V.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${xe}" style="width: 18px; height: 18px; cursor: pointer;">
|
|
1950
|
+
<span style="font-weight: 500; color: var(--text-primary);">${Y.text}</span>
|
|
1946
1951
|
</div>
|
|
1947
|
-
<span style="font-weight: 600; color: var(--primary);">${
|
|
1952
|
+
<span style="font-weight: 600; color: var(--primary);">${Y.votes.length} 票 (${ie}%)</span>
|
|
1948
1953
|
</div>
|
|
1949
1954
|
<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: ${
|
|
1955
|
+
<div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${ie}%; transition: width 0.3s;"></div>
|
|
1951
1956
|
</div>
|
|
1952
1957
|
</div>
|
|
1953
1958
|
`}).join("")}
|
|
1954
1959
|
<button type="submit" class="btn-primary" style="width: 100%; padding: 12px; margin-top: 16px;">提交投票</button>
|
|
1955
1960
|
<div style="text-align: center; color: var(--warning); margin-top: 12px; font-size: 13px;">⚠️ 提交后不可修改</div>
|
|
1956
1961
|
</form>
|
|
1957
|
-
`:
|
|
1958
|
-
${
|
|
1959
|
-
${
|
|
1962
|
+
`:H}
|
|
1963
|
+
${M?'<div style="text-align: center; color: var(--success); margin-top: 16px; padding: 12px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; font-weight: 600;">✓ 您已参与投票,投票后不可修改</div>':""}
|
|
1964
|
+
${N?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
|
|
1960
1965
|
</div>
|
|
1961
1966
|
|
|
1962
1967
|
<div style="display: flex; gap: 10px;">
|
|
@@ -1965,7 +1970,7 @@
|
|
|
1965
1970
|
</div>
|
|
1966
1971
|
</div>
|
|
1967
1972
|
</div>
|
|
1968
|
-
`,
|
|
1973
|
+
`,O=document.getElementById("pollDetailModal");O&&O.remove(),document.body.insertAdjacentHTML("beforeend",_),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const Z=document.getElementById("voteForm");Z&&!N&&!M&&Z.addEventListener("submit",async Y=>{Y.preventDefault();const xe=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(ie=>parseInt(ie.value));if(xe.length===0){alert("请选择至少一个选项!");return}try{await s.vote(P,xe),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const ie=document.querySelector(".nav-item.active");if(ie&&ie.dataset.view==="tasks"){const we=document.getElementById("contentArea");await U(we)}}catch(ie){console.error("投票失败:",ie),alert("投票失败:"+ie.message)}})}catch(A){console.error("加载投票详情失败:",A),alert("加载投票详情失败:"+A.message)}},i.innerHTML=`
|
|
1969
1974
|
<div class="dashboard">
|
|
1970
1975
|
<aside class="sidebar">
|
|
1971
1976
|
<div class="sidebar-header">
|
|
@@ -1974,9 +1979,9 @@
|
|
|
1974
1979
|
</div>
|
|
1975
1980
|
|
|
1976
1981
|
<div class="user-info">
|
|
1977
|
-
<div class="avatar">${
|
|
1982
|
+
<div class="avatar">${r.username[0].toUpperCase()}</div>
|
|
1978
1983
|
<div>
|
|
1979
|
-
<div class="username">${
|
|
1984
|
+
<div class="username">${r.username}</div>
|
|
1980
1985
|
<div class="user-role">普通用户</div>
|
|
1981
1986
|
</div>
|
|
1982
1987
|
</div>
|
|
@@ -2015,110 +2020,110 @@
|
|
|
2015
2020
|
<div id="contentArea"></div>
|
|
2016
2021
|
</main>
|
|
2017
2022
|
</div>
|
|
2018
|
-
`,document.querySelectorAll(".nav-item").forEach(
|
|
2023
|
+
`,document.querySelectorAll(".nav-item").forEach(P=>{P.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(V=>V.classList.remove("active")),P.classList.add("active");const A=P.dataset.view;D(A)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{m.logout()});async function D(P){const A=document.getElementById("contentArea");switch(P){case"groups":await z(A);break;case"allgroups":await R(A);break;case"tasks":await U(A);break;case"documents":await W(A);break;case"files":await oe(A);break;case"chat":await K(A);break;case"search":await te(A);break;case"knowledge":await ve(A);break}}async function z(P){u=(await s.getGroups()).groups,P.innerHTML=`
|
|
2019
2024
|
<div class="view-header">
|
|
2020
2025
|
<h2>我的群组</h2>
|
|
2021
2026
|
</div>
|
|
2022
2027
|
<div class="groups-grid" id="groupsList"></div>
|
|
2023
|
-
`;const
|
|
2024
|
-
<h3>${
|
|
2025
|
-
<p>${
|
|
2028
|
+
`;const V=document.getElementById("groupsList");if(u.length===0){V.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}u.forEach($=>{const M=document.createElement("div");M.className="group-card",M.innerHTML=`
|
|
2029
|
+
<h3>${$.name}</h3>
|
|
2030
|
+
<p>${$.description||"暂无描述"}</p>
|
|
2026
2031
|
<div class="group-stats">
|
|
2027
|
-
<span>👥 ${
|
|
2028
|
-
<span>📄 ${
|
|
2029
|
-
<span>📋 ${
|
|
2032
|
+
<span>👥 ${$.members.length} 成员</span>
|
|
2033
|
+
<span>📄 ${$.documents.length} 文档</span>
|
|
2034
|
+
<span>📋 ${$.tasks.length} 任务</span>
|
|
2030
2035
|
</div>
|
|
2031
2036
|
<div style="display: flex; gap: 10px; margin-top: 10px;">
|
|
2032
|
-
<button class="btn-select" data-id="${
|
|
2033
|
-
<button class="btn-secondary" data-id="${
|
|
2037
|
+
<button class="btn-select" data-id="${$._id}">进入群组</button>
|
|
2038
|
+
<button class="btn-secondary" data-id="${$._id}" data-action="leave">退出群组</button>
|
|
2034
2039
|
</div>
|
|
2035
|
-
`,
|
|
2040
|
+
`,V.appendChild(M)}),document.querySelectorAll(".btn-select").forEach($=>{$.addEventListener("click",()=>{c=u.find(M=>M._id===$.dataset.id),t.joinGroup(c._id),alert(`已进入群组: ${c.name}`)})}),document.querySelectorAll('[data-action="leave"]').forEach($=>{$.addEventListener("click",async()=>{if(confirm("确定要退出该群组吗?"))try{await s.leaveGroup($.dataset.id),alert("已退出群组"),await z(P)}catch(M){alert("退出失败: "+M.message)}})})}async function R(P){const A=await s.getAllGroups(),$=(await s.getGroups()).groups.map(N=>N._id);P.innerHTML=`
|
|
2036
2041
|
<div class="view-header">
|
|
2037
2042
|
<h2>所有群组</h2>
|
|
2038
2043
|
</div>
|
|
2039
2044
|
<div class="groups-grid" id="allGroupsList"></div>
|
|
2040
|
-
`;const
|
|
2041
|
-
<h3>${
|
|
2042
|
-
<p>${
|
|
2045
|
+
`;const M=document.getElementById("allGroupsList");A.groups.forEach(N=>{const H=$.includes(N._id),_=document.createElement("div");_.className="group-card",_.innerHTML=`
|
|
2046
|
+
<h3>${N.name}</h3>
|
|
2047
|
+
<p>${N.description||"暂无描述"}</p>
|
|
2043
2048
|
<div class="group-stats">
|
|
2044
|
-
<span>👥 ${
|
|
2045
|
-
<span>📄 ${
|
|
2049
|
+
<span>👥 ${N.members.length} 成员</span>
|
|
2050
|
+
<span>📄 ${N.documents.length} 文档</span>
|
|
2046
2051
|
</div>
|
|
2047
|
-
${
|
|
2048
|
-
`,
|
|
2052
|
+
${H?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${N._id}" data-action="join">加入群组</button>`}
|
|
2053
|
+
`,M.appendChild(_)}),document.querySelectorAll('[data-action="join"]').forEach(N=>{N.addEventListener("click",async()=>{try{await s.joinGroup(N.dataset.id),alert("加入成功!"),await R(P)}catch(H){alert("加入失败: "+H.message)}})})}async function U(P){try{const A=await s.getMyTasks();let V=[];try{const N=(await s.getGroups()).groups;for(const H of N)try{const _=await s.getGroupPolls(H._id);_.polls&&Array.isArray(_.polls)&&(V=V.concat(_.polls.map(O=>({...O,groupName:H.name}))))}catch(_){console.error(`获取群组 ${H.name} 的投票失败:`,_)}}catch(M){console.error("获取投票失败:",M)}P.innerHTML=`
|
|
2049
2054
|
<div class="view-header">
|
|
2050
2055
|
<h2>我的任务</h2>
|
|
2051
2056
|
</div>
|
|
2052
2057
|
<div class="tasks-list" id="tasksList"></div>
|
|
2053
|
-
`;const
|
|
2054
|
-
<h3>${
|
|
2055
|
-
<p>${
|
|
2058
|
+
`;const $=document.getElementById("tasksList");if(A.tasks.length===0&&V.length===0){$.innerHTML='<div class="empty-state">暂无任务</div>';return}A.tasks.forEach(M=>{const N=document.createElement("div");N.className=`task-card status-${M.status}`,N.innerHTML=`
|
|
2059
|
+
<h3>${M.title}</h3>
|
|
2060
|
+
<p>${M.description}</p>
|
|
2056
2061
|
<div class="task-meta">
|
|
2057
|
-
<span class="status-badge">${se(
|
|
2058
|
-
<span>群组: ${
|
|
2059
|
-
${
|
|
2062
|
+
<span class="status-badge">${se(M.status)}</span>
|
|
2063
|
+
<span>群组: ${M.group.name}</span>
|
|
2064
|
+
${M.deadline?`<span>截止: ${new Date(M.deadline).toLocaleDateString()}</span>`:""}
|
|
2060
2065
|
</div>
|
|
2061
|
-
${
|
|
2066
|
+
${M.relatedDocument?`<a href="#" class="doc-link" data-id="${M.relatedDocument._id}">📄 查看相关文档</a>`:""}
|
|
2062
2067
|
<div class="task-actions">
|
|
2063
|
-
${
|
|
2064
|
-
${
|
|
2068
|
+
${M.status==="pending"?`<button class="btn-primary btn-sm" data-id="${M._id}" data-action="start">开始任务</button>`:""}
|
|
2069
|
+
${M.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${M._id}" data-action="complete">完成任务</button>`:""}
|
|
2065
2070
|
</div>
|
|
2066
|
-
|
|
2071
|
+
`,$.appendChild(N)}),V.forEach(M=>{const N=M.options.reduce((Z,Y)=>Z+Y.votes.length,0),H=M.options.some(Z=>Z.votes.includes(l)),_=M.status==="ended"||M.endTime&&new Date(M.endTime)<new Date,O=document.createElement("div");O.className="task-card poll-task",O.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);",O.innerHTML=`
|
|
2067
2072
|
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
|
|
2068
2073
|
<span style="font-size: 32px;">📊</span>
|
|
2069
|
-
<h3 style="margin: 0;">${
|
|
2074
|
+
<h3 style="margin: 0;">${M.title}</h3>
|
|
2070
2075
|
</div>
|
|
2071
|
-
<p>${
|
|
2076
|
+
<p>${M.description||"暂无描述"}</p>
|
|
2072
2077
|
<div class="task-meta">
|
|
2073
|
-
<span class="status-badge" style="background: ${
|
|
2074
|
-
<span>群组: ${
|
|
2075
|
-
<span>👥 ${
|
|
2076
|
-
${
|
|
2078
|
+
<span class="status-badge" style="background: ${_?"var(--danger)":"var(--success)"};">${_?"已结束":"进行中"}</span>
|
|
2079
|
+
<span>群组: ${M.groupName}</span>
|
|
2080
|
+
<span>👥 ${N} 人投票</span>
|
|
2081
|
+
${H?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
|
|
2077
2082
|
</div>
|
|
2078
2083
|
<div class="task-actions">
|
|
2079
|
-
<button class="btn-primary btn-sm" data-poll-id="${
|
|
2084
|
+
<button class="btn-primary btn-sm" data-poll-id="${M._id}" data-action="view-poll">查看详情</button>
|
|
2080
2085
|
</div>
|
|
2081
|
-
|
|
2086
|
+
`,$.appendChild(O)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(M=>{M.addEventListener("click",async()=>{const N=M.dataset.id,_=M.dataset.action==="start"?"in_progress":"completed";try{await s.updateTaskStatus(N,_),await U(P)}catch(O){alert("操作失败: "+O.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(M=>{M.addEventListener("click",()=>{const N=M.dataset.pollId;window.viewPollDetail(N)})})}catch(A){console.error("获取任务失败:",A),P.innerHTML=`
|
|
2082
2087
|
<div class="view-header">
|
|
2083
2088
|
<h2>我的任务</h2>
|
|
2084
2089
|
</div>
|
|
2085
|
-
<div class="empty-state">加载任务失败: ${
|
|
2086
|
-
`}}async function
|
|
2090
|
+
<div class="empty-state">加载任务失败: ${A.message}</div>
|
|
2091
|
+
`}}async function W(P){if(!c){P.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const A=await s.getDocuments(c._id);P.innerHTML=`
|
|
2087
2092
|
<div class="view-header">
|
|
2088
|
-
<h2>共享文档 - ${
|
|
2093
|
+
<h2>共享文档 - ${c.name}</h2>
|
|
2089
2094
|
</div>
|
|
2090
2095
|
<div class="documents-list" id="docsList"></div>
|
|
2091
|
-
`;const
|
|
2092
|
-
<h3>📄 ${
|
|
2096
|
+
`;const V=document.getElementById("docsList");if(A.documents.length===0){V.innerHTML='<div class="empty-state">暂无文档</div>';return}A.documents.forEach($=>{const M=document.createElement("div");M.className="document-card",M.innerHTML=`
|
|
2097
|
+
<h3>📄 ${$.title}</h3>
|
|
2093
2098
|
<div class="doc-meta">
|
|
2094
|
-
<span>创建者: ${
|
|
2095
|
-
<span>${
|
|
2096
|
-
<span>更新: ${new Date(
|
|
2099
|
+
<span>创建者: ${$.creator.username}</span>
|
|
2100
|
+
<span>${$.permission==="readonly"?"🔒 只读":"✏️ 可编辑"}</span>
|
|
2101
|
+
<span>更新: ${new Date($.updatedAt).toLocaleString()}</span>
|
|
2097
2102
|
</div>
|
|
2098
|
-
<button class="btn-edit" data-id="${
|
|
2099
|
-
${
|
|
2103
|
+
<button class="btn-edit" data-id="${$._id}">
|
|
2104
|
+
${$.permission==="readonly"?"查看":"编辑"}
|
|
2100
2105
|
</button>
|
|
2101
|
-
`,
|
|
2106
|
+
`,V.appendChild(M)}),document.querySelectorAll(".btn-edit").forEach($=>{$.addEventListener("click",()=>{ue(P,$.dataset.id)})})}async function ue(P,A){const $=(await s.getDocument(A)).document;P.innerHTML=`
|
|
2102
2107
|
<div class="view-header">
|
|
2103
2108
|
<button class="btn-back" id="backBtn">← 返回</button>
|
|
2104
|
-
<h2>${
|
|
2105
|
-
<span class="doc-status">${
|
|
2109
|
+
<h2>${$.title}</h2>
|
|
2110
|
+
<span class="doc-status">${$.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
|
|
2106
2111
|
</div>
|
|
2107
2112
|
<div class="editor-container">
|
|
2108
2113
|
<div class="editor-toolbar">
|
|
2109
2114
|
<div class="online-users" id="onlineUsers">
|
|
2110
|
-
<span class="user-badge">👤 ${
|
|
2115
|
+
<span class="user-badge">👤 ${r.username}</span>
|
|
2111
2116
|
</div>
|
|
2112
|
-
${
|
|
2117
|
+
${$.permission==="editable"?'<button class="btn-primary" id="saveBtn">保存</button>':""}
|
|
2113
2118
|
</div>
|
|
2114
|
-
<div id="editor" ${
|
|
2119
|
+
<div id="editor" ${$.permission==="readonly"?'class="readonly"':""}></div>
|
|
2115
2120
|
<div class="editor-footer">
|
|
2116
|
-
<span>最后编辑: ${new Date(
|
|
2121
|
+
<span>最后编辑: ${new Date($.updatedAt).toLocaleString()}</span>
|
|
2117
2122
|
</div>
|
|
2118
2123
|
</div>
|
|
2119
|
-
`;const
|
|
2124
|
+
`;const M=new Quill("#editor",{theme:"snow",modules:{toolbar:$.permission==="readonly"?!1:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:$.permission==="readonly"});if(M.root.innerHTML=$.content||"",$.permission==="editable"){let N,H;M.on("text-change",()=>{clearTimeout(N),clearTimeout(H),t.sendTyping(A,r.username,!0),N=setTimeout(()=>{t.sendTyping(A,r.username,!1)},1e3),H=setTimeout(async()=>{const _=M.root.innerHTML;try{await s.updateDocument(A,_)}catch(O){console.error("自动保存失败:",O)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const _=M.root.innerHTML;await s.updateDocument(A,_),alert("保存成功!")}catch(_){alert("保存失败: "+_.message)}})}t.on("document_update",N=>{if(N.documentId===A&&N.userId!==r.id){const H=M.getSelection();M.root.innerHTML=N.content,H&&M.setSelection(H)}}),t.on("typing",N=>{if(N.documentId===A&&N.userId!==r.id){const H=document.getElementById("onlineUsers");if(N.isTyping)H.innerHTML+=`<span class="user-badge typing" data-user="${N.userId}">✏️ ${N.username}</span>`;else{const _=H.querySelector(`[data-user="${N.userId}"]`);_&&_.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{W(P)})}async function oe(P){if(!c){P.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const A=await s.getGroupFiles(c._id);P.innerHTML=`
|
|
2120
2125
|
<div class="view-header">
|
|
2121
|
-
<h2>文件共享 - ${
|
|
2126
|
+
<h2>文件共享 - ${c.name}</h2>
|
|
2122
2127
|
<button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
|
|
2123
2128
|
</div>
|
|
2124
2129
|
<div class="files-list" id="filesList"></div>
|
|
@@ -2147,27 +2152,27 @@
|
|
|
2147
2152
|
</form>
|
|
2148
2153
|
</div>
|
|
2149
2154
|
</div>
|
|
2150
|
-
`;const
|
|
2151
|
-
<div class="file-icon">${
|
|
2155
|
+
`;const V=document.getElementById("filesList");!A.files||A.files.length===0?V.innerHTML='<div class="empty-state">暂无文件</div>':(A.files.forEach($=>{const M=document.createElement("div");M.className="file-card";const N=fe($.mimetype),H=be($.size),_=$.uploader._id||$.uploader.id||$.uploader,O=String(_)===String(l);M.innerHTML=`
|
|
2156
|
+
<div class="file-icon">${N}</div>
|
|
2152
2157
|
<div class="file-info">
|
|
2153
|
-
<h4>${
|
|
2158
|
+
<h4>${$.originalName}</h4>
|
|
2154
2159
|
<div class="file-meta">
|
|
2155
|
-
<span>上传者: ${
|
|
2156
|
-
<span>大小: ${
|
|
2157
|
-
<span>时间: ${new Date(
|
|
2160
|
+
<span>上传者: ${$.uploader.username||"未知"}</span>
|
|
2161
|
+
<span>大小: ${H}</span>
|
|
2162
|
+
<span>时间: ${new Date($.createdAt).toLocaleString()}</span>
|
|
2158
2163
|
</div>
|
|
2159
|
-
${
|
|
2164
|
+
${$.description?`<p class="file-description">${$.description}</p>`:""}
|
|
2160
2165
|
</div>
|
|
2161
2166
|
<div class="file-actions">
|
|
2162
|
-
<a href="${
|
|
2163
|
-
${
|
|
2167
|
+
<a href="${s.getFileDownloadUrl($._id)}" class="btn-primary" download>下载</a>
|
|
2168
|
+
${O?`<button class="btn-danger" data-id="${$._id}" data-action="delete-file">删除</button>`:""}
|
|
2164
2169
|
</div>
|
|
2165
|
-
`,
|
|
2170
|
+
`,V.appendChild(M)}),document.querySelectorAll('[data-action="delete-file"]').forEach($=>{$.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await s.deleteFile($.dataset.id),alert("文件删除成功!"),await oe(P)}catch(M){alert("删除失败: "+M.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async $=>{$.preventDefault();const M=document.getElementById("fileInput"),N=document.getElementById("fileDescription").value;if(!M.files[0]){alert("请选择文件");return}try{await s.uploadFile(c._id,M.files[0],N),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await oe(P)}catch(H){alert("上传失败: "+H.message)}})}catch(A){console.error("获取文件列表失败:",A),P.innerHTML=`
|
|
2166
2171
|
<div class="view-header">
|
|
2167
2172
|
<h2>文件共享</h2>
|
|
2168
2173
|
</div>
|
|
2169
|
-
<div class="empty-state">加载文件失败: ${
|
|
2170
|
-
`}}function
|
|
2174
|
+
<div class="empty-state">加载文件失败: ${A.message}</div>
|
|
2175
|
+
`}}function fe(P){return P.startsWith("image/")?"🖼️":P==="application/pdf"?"📕":P.includes("word")||P.includes("document")?"📘":P.includes("excel")||P.includes("spreadsheet")?"📗":P.includes("zip")||P.includes("compressed")?"📦":"📄"}function be(P){if(P===0)return"0 Bytes";const A=1024,V=["Bytes","KB","MB","GB"],$=Math.floor(Math.log(P)/Math.log(A));return Math.round(P/Math.pow(A,$)*100)/100+" "+V[$]}async function K(P){if(!c){P.innerHTML=`
|
|
2171
2176
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2172
2177
|
<div style="font-size: 64px; margin-bottom: 20px;">💬</div>
|
|
2173
2178
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">群聊</h3>
|
|
@@ -2176,15 +2181,15 @@
|
|
|
2176
2181
|
前往我的群组
|
|
2177
2182
|
</button>
|
|
2178
2183
|
</div>
|
|
2179
|
-
`;return}try{let
|
|
2184
|
+
`;return}try{let A=function(k,p,h="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(k,{body:p,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},V=function(){const k=document.getElementById("whiteboard");if(!k||k.dataset.initialized)return;k.dataset.initialized="true";const p=k.getContext("2d");k.width=k.offsetWidth,k.height=k.offsetHeight;let h=!1,o="pen",g="#667eea",f=3;document.querySelectorAll(".tool-btn").forEach(E=>{E.addEventListener("click",()=>{o=E.dataset.tool,document.querySelectorAll(".tool-btn").forEach(C=>{C.style.background="var(--bg-secondary)",C.style.color="var(--text-primary)",C.style.border="1px solid var(--border)"}),E.style.background="var(--primary)",E.style.color="white",E.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",E=>{g=E.target.value}),document.getElementById("brushSize").addEventListener("input",E=>{f=E.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(p.clearRect(0,0,k.width,k.height),t.sendWhiteboardClear(c._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const E=k.toDataURL("image/png"),C=document.createElement("canvas");C.width=k.width,C.height=k.height;const G=C.toDataURL("image/png");if(E===G){alert("画布是空的,请先绘制内容!");return}if(E.length*.75/1024/1024<1)t.sendChatMessage(c._id,r.username,`[白板作品]${E}`),alert("白板作品已发送到群聊!");else{const ee=await fetch(E).then(X=>X.blob()),S=new FormData;S.append("file",ee,`whiteboard-${Date.now()}.png`),S.append("groupId",c._id),S.append("description","协作白板作品");const j=localStorage.getItem("token"),T=await fetch("http://localhost:3000/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${j}`},body:S});if(T.ok){const re=`http://localhost:3000/api/files/${(await T.json()).file._id}/download?token=${j}`;t.sendChatMessage(c._id,r.username,`[白板作品]${re}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(E){console.error("发送白板失败:",E),alert("发送失败,请重试!")}}),k.addEventListener("mousedown",E=>{h=!0;const C=k.getBoundingClientRect(),G=E.clientX-C.left,J=E.clientY-C.top;p.beginPath(),p.moveTo(G,J)}),k.addEventListener("mousemove",E=>{if(!h)return;const C=k.getBoundingClientRect(),G=E.clientX-C.left,J=E.clientY-C.top;p.lineWidth=f,p.lineCap="round",o==="pen"?(p.strokeStyle=g,p.globalCompositeOperation="source-over"):o==="eraser"&&(p.globalCompositeOperation="destination-out"),p.lineTo(G,J),p.stroke(),t.sendWhiteboardDraw(c._id,{tool:o,color:g,size:f,x:G,y:J})}),k.addEventListener("mouseup",()=>{h=!1}),k.addEventListener("mouseleave",()=>{h=!1}),t.on("whiteboard_draw",E=>{E.groupId===c._id&&(p.lineWidth=E.size,p.lineCap="round",E.tool==="pen"?(p.strokeStyle=E.color,p.globalCompositeOperation="source-over"):E.tool==="eraser"&&(p.globalCompositeOperation="destination-out"),p.lineTo(E.x,E.y),p.stroke())}),t.on("whiteboard_clear",E=>{E.groupId===c._id&&p.clearRect(0,0,k.width,k.height)})};const M=(await s.getGroup(c._id)).group,N=!!M.mutedAll,H=(M.mutedUsers||[]).map(String).includes(String(l)),_=!N&&!H;P.innerHTML=`
|
|
2180
2185
|
<div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
|
|
2181
2186
|
<h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
|
|
2182
2187
|
<span style="font-size: 32px;">💬</span>
|
|
2183
|
-
<span>群聊 - ${
|
|
2188
|
+
<span>群聊 - ${c.name}</span>
|
|
2184
2189
|
</h2>
|
|
2185
|
-
${
|
|
2190
|
+
${_?"":`
|
|
2186
2191
|
<div style="margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.2); border-radius: 8px; font-size: 14px;">
|
|
2187
|
-
⚠️ ${
|
|
2192
|
+
⚠️ ${N?"全体禁言中,无法发言":"你已被禁言"}
|
|
2188
2193
|
</div>
|
|
2189
2194
|
`}
|
|
2190
2195
|
</div>
|
|
@@ -2207,9 +2212,9 @@
|
|
|
2207
2212
|
<div class="chat-container" style="display: flex; flex-direction: column; height: calc(100vh - 350px); background: var(--bg-secondary); border-radius: 12px; overflow: hidden;">
|
|
2208
2213
|
<div class="messages" id="messages" style="flex: 1; overflow-y: auto; padding: 20px;"></div>
|
|
2209
2214
|
<div class="chat-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
|
|
2210
|
-
<button class="btn-emoji" id="emojiBtn" ${
|
|
2211
|
-
<input type="text" id="messageInput" placeholder="${
|
|
2212
|
-
<button class="btn-primary" id="sendBtn" ${
|
|
2215
|
+
<button class="btn-emoji" id="emojiBtn" ${_?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${_?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
|
|
2216
|
+
<input type="text" id="messageInput" placeholder="${_?"输入消息...":N?"全体禁言中,无法发言":"你已被禁言"}" ${_?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
|
|
2217
|
+
<button class="btn-primary" id="sendBtn" ${_?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${_?"pointer":"not-allowed"};">发送</button>
|
|
2213
2218
|
</div>
|
|
2214
2219
|
<emoji-picker id="emojiPicker" class="hidden" style="position: absolute; bottom: 80px; left: 20px; z-index: 1000;"></emoji-picker>
|
|
2215
2220
|
</div>
|
|
@@ -2246,40 +2251,40 @@
|
|
|
2246
2251
|
</div>
|
|
2247
2252
|
</div>
|
|
2248
2253
|
</div>
|
|
2249
|
-
`;const
|
|
2254
|
+
`;const O=document.getElementById("messages"),Z=document.getElementById("messageInput"),Y=document.getElementById("sendBtn");try{const k=await s.getGroupMessages(c._id);k.messages&&Array.isArray(k.messages)&&(k.messages.length===0?O.innerHTML=`
|
|
2250
2255
|
<div style="text-align: center; padding: 40px; color: var(--text-tertiary);">
|
|
2251
2256
|
<div style="font-size: 48px; margin-bottom: 16px;">💬</div>
|
|
2252
2257
|
<p>还没有消息,开始聊天吧!</p>
|
|
2253
2258
|
</div>
|
|
2254
|
-
`:(
|
|
2259
|
+
`:(k.messages.forEach(p=>{const h=document.createElement("div"),o=String(p.sender)===String(l)||p.username===r.username,g=F(p.content),f=p.content.startsWith("[白板作品]")||p.content.startsWith("[投票]"),E=p.content.startsWith("[白板作品]");h.className=`message ${o?"own":""}`,h.style.cssText=`
|
|
2255
2260
|
margin-bottom: 16px;
|
|
2256
2261
|
display: flex;
|
|
2257
2262
|
flex-direction: column;
|
|
2258
|
-
align-items: ${
|
|
2259
|
-
`,
|
|
2263
|
+
align-items: ${o?"flex-end":"flex-start"};
|
|
2264
|
+
`,h.innerHTML=`
|
|
2260
2265
|
<div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
|
|
2261
|
-
<span class="message-user">${
|
|
2262
|
-
<span class="message-time">${new Date(
|
|
2266
|
+
<span class="message-user">${p.username}</span>
|
|
2267
|
+
<span class="message-time">${new Date(p.timestamp).toLocaleTimeString("zh-CN")}</span>
|
|
2263
2268
|
</div>
|
|
2264
|
-
<div class="message-content" style="background: ${
|
|
2265
|
-
`,
|
|
2269
|
+
<div class="message-content" style="background: ${E||f?"transparent":o?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${o&&!f?"white":"var(--text-primary)"}; padding: ${f?"0":"12px 16px"}; border-radius: 16px; max-width: ${f?"90%":"70%"}; word-wrap: break-word; box-shadow: ${o&&!f?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${g}</div>
|
|
2270
|
+
`,O.appendChild(h)}),O.scrollTop=O.scrollHeight))}catch(k){console.error("加载历史消息失败:",k),O.innerHTML=`
|
|
2266
2271
|
<div style="text-align: center; padding: 40px; color: var(--danger);">
|
|
2267
2272
|
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
2268
2273
|
<p>加载历史消息失败</p>
|
|
2269
|
-
<p style="font-size: 14px; color: var(--text-tertiary);">${
|
|
2274
|
+
<p style="font-size: 14px; color: var(--text-tertiary);">${k.message}</p>
|
|
2270
2275
|
</div>
|
|
2271
|
-
`}const
|
|
2276
|
+
`}const xe=document.getElementById("emojiBtn"),ie=document.getElementById("emojiPicker");_&&(xe.addEventListener("click",()=>{ie.classList.toggle("hidden")}),ie.addEventListener("emoji-click",k=>{Z.value+=k.detail.unicode,Z.focus(),ie.classList.add("hidden")}),document.addEventListener("click",k=>{!xe.contains(k.target)&&!ie.contains(k.target)&&ie.classList.add("hidden")})),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission(),t.on("chat_message",k=>{if(k.groupId===c._id){const p=document.createElement("div"),h=String(k.userId)===String(l)||k.username===r.username;p.className=`message ${h?"own":""}`,p.style.cssText=`
|
|
2272
2277
|
margin-bottom: 16px;
|
|
2273
2278
|
display: flex;
|
|
2274
2279
|
flex-direction: column;
|
|
2275
|
-
align-items: ${
|
|
2276
|
-
`;const
|
|
2280
|
+
align-items: ${h?"flex-end":"flex-start"};
|
|
2281
|
+
`;const o=F(k.content),g=k.content.startsWith("[白板作品]")||k.content.startsWith("[投票]"),f=k.content.startsWith("[白板作品]");p.innerHTML=`
|
|
2277
2282
|
<div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
|
|
2278
|
-
<span class="message-user">${
|
|
2279
|
-
<span class="message-time">${new Date(
|
|
2283
|
+
<span class="message-user">${k.username}</span>
|
|
2284
|
+
<span class="message-time">${new Date(k.timestamp).toLocaleTimeString("zh-CN")}</span>
|
|
2280
2285
|
</div>
|
|
2281
|
-
<div class="message-content" style="background: ${
|
|
2282
|
-
`,
|
|
2286
|
+
<div class="message-content" style="background: ${f||g?"transparent":h?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${h&&!g?"white":"var(--text-primary)"}; padding: ${g?"0":"12px 16px"}; border-radius: 16px; max-width: ${g?"90%":"70%"}; word-wrap: break-word; box-shadow: ${h&&!g?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${o}</div>
|
|
2287
|
+
`,O.appendChild(p),O.scrollTop=O.scrollHeight,h||A(`${k.username} 在 ${c.name}`,k.content.startsWith("[")?"发送了特殊消息":k.content)}}),t.on("chat_blocked",k=>{if(k.groupId===c._id){const p=document.createElement("div");p.style.cssText=`
|
|
2283
2288
|
text-align: center;
|
|
2284
2289
|
padding: 12px;
|
|
2285
2290
|
margin: 16px auto;
|
|
@@ -2287,7 +2292,7 @@
|
|
|
2287
2292
|
color: white;
|
|
2288
2293
|
border-radius: 8px;
|
|
2289
2294
|
max-width: 80%;
|
|
2290
|
-
`,
|
|
2295
|
+
`,p.textContent=k.message||"消息发送失败",O.appendChild(p),O.scrollTop=O.scrollHeight}}),t.on("call_response",k=>{if(k.groupId===c._id){const p=document.createElement("div");p.style.cssText=`
|
|
2291
2296
|
text-align: center;
|
|
2292
2297
|
padding: 12px;
|
|
2293
2298
|
margin: 16px auto;
|
|
@@ -2295,7 +2300,7 @@
|
|
|
2295
2300
|
color: white;
|
|
2296
2301
|
border-radius: 8px;
|
|
2297
2302
|
max-width: 80%;
|
|
2298
|
-
`,
|
|
2303
|
+
`,p.textContent=`${k.username} 已响应点名`,O.appendChild(p),O.scrollTop=O.scrollHeight}});const we=()=>{if(!_){alert(N?"全体禁言中,无法发言":"你已被禁言");return}const k=Z.value.trim();if(k)try{t.sendChatMessage(c._id,r.username,k),Z.value=""}catch(p){console.error("发送消息失败:",p),alert("发送失败: "+p.message)}};_&&(Y.addEventListener("click",we),Z.addEventListener("keypress",k=>{k.key==="Enter"&&!k.shiftKey&&(k.preventDefault(),we())}));const ae=document.querySelectorAll(".chat-tab"),v=document.querySelectorAll(".tab-content");ae.forEach(k=>{k.addEventListener("click",()=>{const p=k.dataset.tab;ae.forEach(h=>{h.dataset.tab===p?(h.style.background="var(--primary)",h.style.color="white"):(h.style.background="transparent",h.style.color="var(--text-primary)")}),v.forEach(h=>{h.dataset.content===p?h.style.display="block":h.style.display="none"}),p==="whiteboard"&&V()})});const b=document.getElementById("aiChat"),w=document.getElementById("aiInput"),B=document.getElementById("aiSendBtn"),L=async()=>{const k=w.value.trim();if(!k)return;const p=document.createElement("div");p.style.cssText=`
|
|
2299
2304
|
background: var(--primary);
|
|
2300
2305
|
color: white;
|
|
2301
2306
|
padding: 12px 16px;
|
|
@@ -2304,7 +2309,7 @@
|
|
|
2304
2309
|
max-width: 80%;
|
|
2305
2310
|
margin-left: auto;
|
|
2306
2311
|
word-wrap: break-word;
|
|
2307
|
-
`,
|
|
2312
|
+
`,p.textContent=k,b.appendChild(p),w.value="";const h=document.createElement("div");h.style.cssText=`
|
|
2308
2313
|
background: var(--bg-tertiary);
|
|
2309
2314
|
color: var(--text-secondary);
|
|
2310
2315
|
padding: 12px 16px;
|
|
@@ -2312,7 +2317,7 @@
|
|
|
2312
2317
|
margin-bottom: 16px;
|
|
2313
2318
|
max-width: 80%;
|
|
2314
2319
|
font-style: italic;
|
|
2315
|
-
`,
|
|
2320
|
+
`,h.textContent="🤔 思考中...",b.appendChild(h),b.scrollTop=b.scrollHeight;try{const o=localStorage.getItem("token"),f=await(await fetch("http://localhost:3000/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({question:k,groupId:c==null?void 0:c._id})})).json();h.remove();const E=document.createElement("div");E.style.cssText=`
|
|
2316
2321
|
background: var(--bg-tertiary);
|
|
2317
2322
|
color: var(--text-primary);
|
|
2318
2323
|
padding: 12px 16px;
|
|
@@ -2321,21 +2326,21 @@
|
|
|
2321
2326
|
max-width: 80%;
|
|
2322
2327
|
line-height: 1.6;
|
|
2323
2328
|
word-wrap: break-word;
|
|
2324
|
-
`,
|
|
2329
|
+
`,E.textContent=f.answer||"抱歉,我无法回答这个问题。",b.appendChild(E),b.scrollTop=b.scrollHeight}catch(o){h.remove();const g=document.createElement("div");g.style.cssText=`
|
|
2325
2330
|
background: var(--danger);
|
|
2326
2331
|
color: white;
|
|
2327
2332
|
padding: 12px 16px;
|
|
2328
2333
|
border-radius: 12px;
|
|
2329
2334
|
margin-bottom: 16px;
|
|
2330
2335
|
max-width: 80%;
|
|
2331
|
-
`,
|
|
2336
|
+
`,g.textContent="抱歉,发生了错误: "+o.message,b.appendChild(g),b.scrollTop=b.scrollHeight}};B.addEventListener("click",L),w.addEventListener("keypress",k=>{k.key==="Enter"&&!k.shiftKey&&(k.preventDefault(),L())})}catch(A){console.error("加载群聊失败:",A),P.innerHTML=`
|
|
2332
2337
|
<div class="empty-state" style="text-align: center; padding: 60px 20px;">
|
|
2333
2338
|
<div style="font-size: 64px; margin-bottom: 20px;">⚠️</div>
|
|
2334
2339
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--danger);">加载失败</h3>
|
|
2335
|
-
<p style="color: var(--text-secondary); margin-bottom: 24px;">${
|
|
2340
|
+
<p style="color: var(--text-secondary); margin-bottom: 24px;">${A.message}</p>
|
|
2336
2341
|
<button class="btn-primary" onclick="location.reload()">重新加载</button>
|
|
2337
2342
|
</div>
|
|
2338
|
-
`}}async function
|
|
2343
|
+
`}}async function te(P){P.innerHTML=`
|
|
2339
2344
|
<div class="view-header">
|
|
2340
2345
|
<h2>🔍 搜索</h2>
|
|
2341
2346
|
</div>
|
|
@@ -2357,18 +2362,18 @@
|
|
|
2357
2362
|
</div>
|
|
2358
2363
|
<div class="search-results" id="searchResults"></div>
|
|
2359
2364
|
</div>
|
|
2360
|
-
`;const
|
|
2365
|
+
`;const A=document.getElementById("searchInput"),V=document.getElementById("searchBtn"),$=document.getElementById("searchResults"),M=async()=>{const N=A.value.trim();if(!N){$.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const H={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};$.innerHTML='<div class="loading">搜索中...</div>';try{const _=[];if(H.messages&&c)try{const O=await s.getGroupMessages(c._id);O.messages&&O.messages.filter(Y=>Y.content.toLowerCase().includes(N.toLowerCase())).forEach(Y=>{_.push({type:"message",title:`消息 - ${Y.username}`,content:Y.content,time:Y.timestamp,group:c.name})})}catch(O){console.error("搜索消息失败:",O)}if(H.documents)try{if(c){const O=await s.getDocuments(c._id);O.documents&&O.documents.filter(Y=>Y.title.toLowerCase().includes(N.toLowerCase())||Y.content.toLowerCase().includes(N.toLowerCase())).forEach(Y=>{_.push({type:"document",title:Y.title,content:Y.content.substring(0,200),time:Y.updatedAt,id:Y._id,group:c.name})})}}catch(O){console.error("搜索文档失败:",O)}if(H.tasks)try{const O=await s.getMyTasks();O.tasks&&O.tasks.filter(Y=>Y.title.toLowerCase().includes(N.toLowerCase())||Y.description&&Y.description.toLowerCase().includes(N.toLowerCase())).forEach(Y=>{_.push({type:"task",title:Y.title,content:Y.description||"",time:Y.updatedAt,id:Y._id,status:Y.status})})}catch(O){console.error("搜索任务失败:",O)}_.length===0?$.innerHTML='<div class="empty-state">未找到相关结果</div>':$.innerHTML=_.map(O=>`
|
|
2361
2366
|
<div class="search-result-item">
|
|
2362
2367
|
<div class="result-header">
|
|
2363
|
-
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[
|
|
2364
|
-
<span class="result-time">${new Date(
|
|
2368
|
+
<span class="result-type">${{message:"💬",document:"📄",task:"📋"}[O.type]} ${O.type==="message"?"消息":O.type==="document"?"文档":"任务"}</span>
|
|
2369
|
+
<span class="result-time">${new Date(O.time).toLocaleString()}</span>
|
|
2365
2370
|
</div>
|
|
2366
|
-
<h4>${
|
|
2367
|
-
<p>${
|
|
2368
|
-
${
|
|
2369
|
-
${
|
|
2371
|
+
<h4>${me(O.title,N)}</h4>
|
|
2372
|
+
<p>${me(O.content,N)}</p>
|
|
2373
|
+
${O.group?`<span class="result-group">群组: ${O.group}</span>`:""}
|
|
2374
|
+
${O.status?`<span class="result-status">状态: ${se(O.status)}</span>`:""}
|
|
2370
2375
|
</div>
|
|
2371
|
-
`).join("")}catch(
|
|
2376
|
+
`).join("")}catch(_){$.innerHTML=`<div class="empty-state">搜索失败: ${_.message}</div>`}};V.addEventListener("click",M),A.addEventListener("keypress",N=>{N.key==="Enter"&&M()})}function me(P,A){if(!A)return P;const V=new RegExp(`(${A})`,"gi");return P.replace(V,"<mark>$1</mark>")}function se(P){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[P]||P}async function ve(P){if(!c){P.innerHTML=`
|
|
2372
2377
|
<div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
|
|
2373
2378
|
<div style="font-size: 64px; margin-bottom: 20px;">📚</div>
|
|
2374
2379
|
<h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">知识库</h3>
|
|
@@ -2377,9 +2382,9 @@
|
|
|
2377
2382
|
前往我的群组
|
|
2378
2383
|
</button>
|
|
2379
2384
|
</div>
|
|
2380
|
-
`;return}try{const
|
|
2385
|
+
`;return}try{const A=localStorage.getItem("token"),V=await fetch(`http://localhost:3000/api/knowledge/group/${c._id}`,{headers:{Authorization:`Bearer ${A}`}});if(!V.ok)throw new Error(`HTTP ${V.status}: ${V.statusText}`);const $=await V.json();let M=[];Array.isArray($)?M=$:$.data&&Array.isArray($.data)?M=$.data:$.data&&$.data.knowledgeList&&Array.isArray($.data.knowledgeList)?M=$.data.knowledgeList:$.items&&Array.isArray($.items)?M=$.items:$.knowledge&&Array.isArray($.knowledge)&&(M=$.knowledge),P.innerHTML=`
|
|
2381
2386
|
<div class="view-header">
|
|
2382
|
-
<h2>📚 知识库 - ${
|
|
2387
|
+
<h2>📚 知识库 - ${c.name}</h2>
|
|
2383
2388
|
<button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
|
|
2384
2389
|
</div>
|
|
2385
2390
|
<div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
|
|
@@ -2421,31 +2426,31 @@
|
|
|
2421
2426
|
</form>
|
|
2422
2427
|
</div>
|
|
2423
2428
|
</div>
|
|
2424
|
-
`;const
|
|
2425
|
-
${
|
|
2426
|
-
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${
|
|
2427
|
-
<p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${
|
|
2429
|
+
`;const N=document.getElementById("knowledgeList");M.length===0?N.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(M.forEach(H=>{var O,Z;const _=document.createElement("div");_.className="knowledge-card",_.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",_.innerHTML=`
|
|
2430
|
+
${H.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
|
|
2431
|
+
<h3 style="margin: 0 0 10px 0; font-size: 18px; ${H.isShared?"padding-right: 80px;":""}">${H.title}</h3>
|
|
2432
|
+
<p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${H.content.substring(0,150)}${H.content.length>150?"...":""}</p>
|
|
2428
2433
|
<div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
|
|
2429
|
-
<span>👤 ${((
|
|
2430
|
-
<span style="margin-left: 15px;">📅 ${new Date(
|
|
2434
|
+
<span>👤 ${((O=H.author)==null?void 0:O.username)||((Z=H.creator)==null?void 0:Z.username)||"未知"}</span>
|
|
2435
|
+
<span style="margin-left: 15px;">📅 ${new Date(H.createdAt).toLocaleDateString()}</span>
|
|
2431
2436
|
</div>
|
|
2432
|
-
${
|
|
2437
|
+
${H.tags&&H.tags.length>0?`
|
|
2433
2438
|
<div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
|
|
2434
|
-
${
|
|
2439
|
+
${H.tags.map(Y=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${Y}</span>`).join("")}
|
|
2435
2440
|
</div>
|
|
2436
2441
|
`:""}
|
|
2437
2442
|
<div style="display: flex; gap: 10px;">
|
|
2438
|
-
<button class="btn-secondary btn-sm" data-id="${
|
|
2439
|
-
<button class="btn-danger btn-sm" data-id="${
|
|
2443
|
+
<button class="btn-secondary btn-sm" data-id="${H._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
|
|
2444
|
+
<button class="btn-danger btn-sm" data-id="${H._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
|
|
2440
2445
|
</div>
|
|
2441
|
-
`,
|
|
2446
|
+
`,_.onmouseenter=()=>{_.style.transform="translateY(-4px)",_.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},_.onmouseleave=()=>{_.style.transform="translateY(0)",_.style.boxShadow="none"},N.appendChild(_)}),document.querySelectorAll('[data-action="edit"]').forEach(H=>{H.addEventListener("click",async()=>{var O;const _=M.find(Z=>Z._id===H.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=_.title,document.querySelector('[name="content"]').value=_.content,document.querySelector('[name="tags"]').value=((O=_.tags)==null?void 0:O.join(", "))||"",document.getElementById("isSharedCheckbox").checked=_.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=_._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(H=>{H.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:3000/api/knowledge/${H.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${A}`}}),alert("删除成功!"),await ve(P)}catch(_){alert("删除失败: "+_.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async H=>{H.preventDefault();const _=new FormData(H.target),O={title:_.get("title"),content:_.get("content"),tags:_.get("tags").split(",").map(Z=>Z.trim()).filter(Z=>Z),groupId:c._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const Z=H.target.dataset.editId,Y=Z?`http://localhost:3000/api/knowledge/${Z}`:"http://localhost:3000/api/knowledge";if(!(await fetch(Y,{method:Z?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${A}`},body:JSON.stringify(O)})).ok)throw new Error("操作失败");alert(Z?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await ve(P)}catch(Z){alert("操作失败: "+Z.message)}})}catch(A){console.error("加载知识库失败:",A),P.innerHTML=`
|
|
2442
2447
|
<div class="view-header">
|
|
2443
|
-
<h2>📚 知识库 - ${
|
|
2448
|
+
<h2>📚 知识库 - ${c.name}</h2>
|
|
2444
2449
|
</div>
|
|
2445
2450
|
<div class="empty-state" style="text-align: center; padding: 40px 20px;">
|
|
2446
2451
|
<div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
|
|
2447
2452
|
<h3 style="margin-bottom: 8px; color: var(--danger);">加载失败</h3>
|
|
2448
|
-
<p style="color: var(--text-secondary); margin-bottom: 16px;">${
|
|
2453
|
+
<p style="color: var(--text-secondary); margin-bottom: 16px;">${A.message}</p>
|
|
2449
2454
|
<button class="btn-primary" onclick="location.reload()">重新加载</button>
|
|
2450
2455
|
</div>
|
|
2451
|
-
`}}
|
|
2456
|
+
`}}D("groups")}class ta{constructor(){this.authService=new Je,this.wsService=new bn,this.currentUser=null,this.init()}async init(){const t=localStorage.getItem("token");if(t)try{this.currentUser=await this.authService.getCurrentUser(),this.wsService.connect(t),this.renderDashboard()}catch(i){console.error("认证失败:",i),this.renderLogin()}else this.renderLogin()}renderLogin(){vn(async(t,i)=>{this.currentUser=t,localStorage.setItem("token",i),this.wsService.connect(i),this.renderDashboard()})}renderDashboard(){this.currentUser.role==="admin"?Zr(this.currentUser,this.wsService):ea(this.currentUser,this.wsService)}}new ta;
|