Compare commits
1 commit
master
...
message-li
Author | SHA1 | Date | |
---|---|---|---|
fa6ced4565 |
44 changed files with 2172 additions and 1414 deletions
3
build.js
3
build.js
|
@ -31,7 +31,8 @@ const exec = require('util').promisify(require('child_process').exec);
|
|||
|
||||
const tasks = {
|
||||
js: [
|
||||
{ source: 'mami.js', target: '/assets', name: 'mami.{hash}.js', vars: { build: 'MAMI_JS', html: ':source' } },
|
||||
{ source: 'proto.js', target: '/assets', name: 'proto.{hash}.js', vars: { build: 'MAMI_PROTO_JS', html: ':source' } },
|
||||
{ source: 'mami.js', target: '/assets', name: 'mami.{hash}.js', vars: { build: 'MAMI_MAIN_JS', html: ':source' } },
|
||||
{ source: 'init.js', target: '/assets', name: 'init.{hash}.js', es: 'es5', vars: { html: ':source' } },
|
||||
],
|
||||
css: [
|
||||
|
|
352
package-lock.json
generated
352
package-lock.json
generated
|
@ -51,9 +51,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
|
@ -80,14 +80,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.2.tgz",
|
||||
"integrity": "sha512-dYyEkO6mRYtZFpnOsnYzv9rY69fHAHoawYOjGOEcxk9WYtaJhowMdP/w6NcOKnz2G7GlZaenjkzkMa6ZeQeMsg==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.6.5.tgz",
|
||||
"integrity": "sha512-tyVvUK/HDOUUsK6/GmWvnqUtD9oDpPUA4f7f7JCOV8hXxtfjMtAZeBKf93yrB1XZet69TDR7EN0hFC6i4MF0Ig==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.15"
|
||||
"@swc/types": "^0.1.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
|
@ -97,16 +97,16 @@
|
|||
"url": "https://opencollective.com/swc"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-darwin-arm64": "1.9.2",
|
||||
"@swc/core-darwin-x64": "1.9.2",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.9.2",
|
||||
"@swc/core-linux-arm64-gnu": "1.9.2",
|
||||
"@swc/core-linux-arm64-musl": "1.9.2",
|
||||
"@swc/core-linux-x64-gnu": "1.9.2",
|
||||
"@swc/core-linux-x64-musl": "1.9.2",
|
||||
"@swc/core-win32-arm64-msvc": "1.9.2",
|
||||
"@swc/core-win32-ia32-msvc": "1.9.2",
|
||||
"@swc/core-win32-x64-msvc": "1.9.2"
|
||||
"@swc/core-darwin-arm64": "1.6.5",
|
||||
"@swc/core-darwin-x64": "1.6.5",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.6.5",
|
||||
"@swc/core-linux-arm64-gnu": "1.6.5",
|
||||
"@swc/core-linux-arm64-musl": "1.6.5",
|
||||
"@swc/core-linux-x64-gnu": "1.6.5",
|
||||
"@swc/core-linux-x64-musl": "1.6.5",
|
||||
"@swc/core-win32-arm64-msvc": "1.6.5",
|
||||
"@swc/core-win32-ia32-msvc": "1.6.5",
|
||||
"@swc/core-win32-x64-msvc": "1.6.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/helpers": "*"
|
||||
|
@ -118,9 +118,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-arm64": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.2.tgz",
|
||||
"integrity": "sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.5.tgz",
|
||||
"integrity": "sha512-RGQhMdni2v1/ANQ/2K+F+QYdzaucekYBewZcX1ogqJ8G5sbPaBdYdDN1qQ4kHLCIkPtGP6qC7c71qPEqL2RidQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -134,9 +134,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-darwin-x64": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.2.tgz",
|
||||
"integrity": "sha512-9gD+bwBz8ZByjP6nZTXe/hzd0tySIAjpDHgkFiUrc+5zGF+rdTwhcNrzxNHJmy6mw+PW38jqII4uspFHUqqxuQ==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.6.5.tgz",
|
||||
"integrity": "sha512-/pSN0/Jtcbbb9+ovS9rKxR3qertpFAM3OEJr/+Dh/8yy7jK5G5EFPIrfsw/7Q5987ERPIJIH6BspK2CBB2tgcg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -150,9 +150,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm-gnueabihf": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.2.tgz",
|
||||
"integrity": "sha512-kYq8ief1Qrn+WmsTWAYo4r+Coul4dXN6cLFjiPZ29Cv5pyU+GFvSPAB4bEdMzwy99rCR0u2P10UExaeCjurjvg==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.5.tgz",
|
||||
"integrity": "sha512-B0g/dROCE747RRegs/jPHuKJgwXLracDhnqQa80kFdgWEMjlcb7OMCgs5OX86yJGRS4qcYbiMGD0Pp7Kbqn3yw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
|
@ -166,9 +166,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-gnu": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-n0W4XiXlmEIVqxt+rD3ZpkogsEWUk1jJ+i5bQNgB+1JuWh0fBE8c/blDgTQXa0GB5lTPVDZQussgdNOCnAZwiA==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.5.tgz",
|
||||
"integrity": "sha512-W8meapgXTq8AOtSvDG4yKR8ant2WWD++yOjgzAleB5VAC+oC+aa8YJROGxj8HepurU8kurqzcialwoMeq5SZZQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -182,9 +182,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-arm64-musl": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.2.tgz",
|
||||
"integrity": "sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.5.tgz",
|
||||
"integrity": "sha512-jyCKqoX50Fg8rJUQqh4u5PqnE7nqYKXHjVH2WcYr114/MU21zlsI+YL6aOQU1XP8bJQ2gPQ1rnlnGJdEHiKS/w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -198,9 +198,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-gnu": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.2.tgz",
|
||||
"integrity": "sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.5.tgz",
|
||||
"integrity": "sha512-G6HmUn/RRIlXC0YYFfBz2qh6OZkHS/KUPkhoG4X9ADcgWXXjOFh6JrefwsYj8VBAJEnr5iewzjNfj+nztwHaeA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -214,9 +214,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-linux-x64-musl": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.2.tgz",
|
||||
"integrity": "sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.5.tgz",
|
||||
"integrity": "sha512-AQpBjBnelQDSbeTJA50AXdS6+CP66LsXIMNTwhPSgUfE7Bx1ggZV11Fsi4Q5SGcs6a8Qw1cuYKN57ZfZC5QOuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -230,9 +230,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-arm64-msvc": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.5.tgz",
|
||||
"integrity": "sha512-MZTWM8kUwS30pVrtbzSGEXtek46aXNb/mT9D6rsS7NvOuv2w+qZhjR1rzf4LNbbn5f8VnR4Nac1WIOYZmfC5ng==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -246,9 +246,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-ia32-msvc": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-nLWBi4vZDdM/LkiQmPCakof8Dh1/t5EM7eudue04V1lIcqx9YHVRS3KMwEaCoHLGg0c312Wm4YgrWQd9vwZ5zQ==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.5.tgz",
|
||||
"integrity": "sha512-WZdu4gISAr3yOm1fVwKhhk6+MrP7kVX0KMP7+ZQFTN5zXQEiDSDunEJKVgjMVj3vlR+6mnAqa/L0V9Qa8+zKlQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
|
@ -262,9 +262,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@swc/core-win32-x64-msvc": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.2.tgz",
|
||||
"integrity": "sha512-ik/k+JjRJBFkXARukdU82tSVx0CbExFQoQ78qTO682esbYXzjdB5eLVkoUbwen299pnfr88Kn4kyIqFPTje8Xw==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.5.tgz",
|
||||
"integrity": "sha512-ezXgucnMTzlFIxQZw7ls/5r2hseFaRoDL04cuXUOs97E8r+nJSmFsRQm/ygH5jBeXNo59nyZCalrjJAjwfgACA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -284,9 +284,9 @@
|
|||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@swc/types": {
|
||||
"version": "0.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.15.tgz",
|
||||
"integrity": "sha512-XKaZ+dzDIQ9Ot9o89oJQ/aluI17+VvUnIpYJTcZtvv1iYX6MzHh3Ik2CSR7MdPKpPwfZXHBeCingb2b4PoDVdw==",
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.9.tgz",
|
||||
"integrity": "sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3"
|
||||
|
@ -302,9 +302,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
|
||||
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
|
||||
"version": "8.12.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
|
||||
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
|
@ -314,9 +314,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.20",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
|
||||
"integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
|
||||
"version": "10.4.19",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
|
||||
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -333,11 +333,11 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"caniuse-lite": "^1.0.30001646",
|
||||
"browserslist": "^4.23.0",
|
||||
"caniuse-lite": "^1.0.30001599",
|
||||
"fraction.js": "^4.3.7",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -357,9 +357,9 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.24.2",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
|
||||
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
|
||||
"version": "4.23.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz",
|
||||
"integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -376,10 +376,10 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001669",
|
||||
"electron-to-chromium": "^1.5.41",
|
||||
"node-releases": "^2.0.18",
|
||||
"update-browserslist-db": "^1.1.1"
|
||||
"caniuse-lite": "^1.0.30001629",
|
||||
"electron-to-chromium": "^1.4.796",
|
||||
"node-releases": "^2.0.14",
|
||||
"update-browserslist-db": "^1.0.16"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
|
@ -417,9 +417,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001680",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
|
||||
"integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
|
||||
"version": "1.0.30001636",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz",
|
||||
"integrity": "sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -529,12 +529,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cssnano": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.6.tgz",
|
||||
"integrity": "sha512-54woqx8SCbp8HwvNZYn68ZFAepuouZW4lTwiMVnBErM3VkO7/Sd4oTOt3Zz3bPx3kxQ36aISppyXj2Md4lg8bw==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.0.3.tgz",
|
||||
"integrity": "sha512-lsekJctOTqdCn4cNrtrSwsuMR/fHC+oiVMHkp/OugBWtwjH8XJag1/OtGaYJGtz0un1fQcRy4ryfYTQsfh+KSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssnano-preset-default": "^7.0.6",
|
||||
"cssnano-preset-default": "^7.0.3",
|
||||
"lilconfig": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -549,41 +549,41 @@
|
|||
}
|
||||
},
|
||||
"node_modules/cssnano-preset-default": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.6.tgz",
|
||||
"integrity": "sha512-ZzrgYupYxEvdGGuqL+JKOY70s7+saoNlHSCK/OGn1vB2pQK8KSET8jvenzItcY+kA7NoWvfbb/YhlzuzNKjOhQ==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.3.tgz",
|
||||
"integrity": "sha512-dQ3Ba1p/oewICp/szF1XjFFgql8OlOBrI2YNBUUwhHQnJNoMOcQTa+Bi7jSJN8r/eM1egW0Ud1se/S7qlduWKA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"css-declaration-sorter": "^7.2.0",
|
||||
"cssnano-utils": "^5.0.0",
|
||||
"postcss-calc": "^10.0.2",
|
||||
"postcss-colormin": "^7.0.2",
|
||||
"postcss-convert-values": "^7.0.4",
|
||||
"postcss-discard-comments": "^7.0.3",
|
||||
"postcss-discard-duplicates": "^7.0.1",
|
||||
"postcss-calc": "^10.0.0",
|
||||
"postcss-colormin": "^7.0.1",
|
||||
"postcss-convert-values": "^7.0.1",
|
||||
"postcss-discard-comments": "^7.0.1",
|
||||
"postcss-discard-duplicates": "^7.0.0",
|
||||
"postcss-discard-empty": "^7.0.0",
|
||||
"postcss-discard-overridden": "^7.0.0",
|
||||
"postcss-merge-longhand": "^7.0.4",
|
||||
"postcss-merge-rules": "^7.0.4",
|
||||
"postcss-merge-longhand": "^7.0.2",
|
||||
"postcss-merge-rules": "^7.0.2",
|
||||
"postcss-minify-font-values": "^7.0.0",
|
||||
"postcss-minify-gradients": "^7.0.0",
|
||||
"postcss-minify-params": "^7.0.2",
|
||||
"postcss-minify-selectors": "^7.0.4",
|
||||
"postcss-minify-params": "^7.0.1",
|
||||
"postcss-minify-selectors": "^7.0.2",
|
||||
"postcss-normalize-charset": "^7.0.0",
|
||||
"postcss-normalize-display-values": "^7.0.0",
|
||||
"postcss-normalize-positions": "^7.0.0",
|
||||
"postcss-normalize-repeat-style": "^7.0.0",
|
||||
"postcss-normalize-string": "^7.0.0",
|
||||
"postcss-normalize-timing-functions": "^7.0.0",
|
||||
"postcss-normalize-unicode": "^7.0.2",
|
||||
"postcss-normalize-unicode": "^7.0.1",
|
||||
"postcss-normalize-url": "^7.0.0",
|
||||
"postcss-normalize-whitespace": "^7.0.0",
|
||||
"postcss-ordered-values": "^7.0.1",
|
||||
"postcss-reduce-initial": "^7.0.2",
|
||||
"postcss-reduce-initial": "^7.0.1",
|
||||
"postcss-reduce-transforms": "^7.0.0",
|
||||
"postcss-svgo": "^7.0.1",
|
||||
"postcss-unique-selectors": "^7.0.3"
|
||||
"postcss-unique-selectors": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -703,9 +703,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.57",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.57.tgz",
|
||||
"integrity": "sha512-xS65H/tqgOwUBa5UmOuNSLuslDo7zho0y/lgQw35pnrqiZh7UOWHCeL/Bt6noJATbA6tpQJGCifsFsIRZj1Fqg==",
|
||||
"version": "1.4.811",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.811.tgz",
|
||||
"integrity": "sha512-CDyzcJ5XW78SHzsIOdn27z8J4ist8eaFLhdto2hSMSJQgsiwvbv2fbizcKUICryw1Wii1TI/FEkvzvJsR3awrA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
|
@ -721,9 +721,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
|
@ -831,9 +831,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.18",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/normalize-range": {
|
||||
|
@ -878,15 +878,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.49",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
||||
"version": "8.4.38",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -904,20 +904,20 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.1.1",
|
||||
"source-map-js": "^1.2.1"
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-calc": {
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.2.tgz",
|
||||
"integrity": "sha512-DT/Wwm6fCKgpYVI7ZEWuPJ4az8hiEHtCUeYjZXqU7Ou4QqYh1Df2yCQ7Ca6N7xqKPFkxN3fhf+u9KSoOCJNAjg==",
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.0.0.tgz",
|
||||
"integrity": "sha512-OmjhudoNTP0QleZCwl1i6NeBwN+5MZbY5ersLZz69mjJiDVv/p57RjRuKDkHeDWr4T+S97wQfsqRTNoDHB2e3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.1.2",
|
||||
"postcss-selector-parser": "^6.0.16",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -928,12 +928,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-colormin": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.2.tgz",
|
||||
"integrity": "sha512-YntRXNngcvEvDbEjTdRWGU606eZvB5prmHG4BF0yLmVpamXbpsRJzevyy6MZVyuecgzI2AWAlvFi8DAeCqwpvA==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.1.tgz",
|
||||
"integrity": "sha512-uszdT0dULt3FQs47G5UHCduYK+FnkLYlpu1HpWu061eGsKZ7setoG7kA+WC9NQLsOJf69D5TxGHgnAdRgylnFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"caniuse-api": "^3.0.0",
|
||||
"colord": "^2.9.3",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
|
@ -946,12 +946,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-convert-values": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.4.tgz",
|
||||
"integrity": "sha512-e2LSXPqEHVW6aoGbjV9RsSSNDO3A0rZLCBxN24zvxF25WknMPpX8Dm9UxxThyEbaytzggRuZxaGXqaOhxQ514Q==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.1.tgz",
|
||||
"integrity": "sha512-9x2ofb+hYPwHWMlWAzyWys2yMDZYGfkX9LodbaVTmLdlupmtH2AGvj8Up95wzzNPRDEzPIxQIkUaPJew3bT6xA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -962,12 +962,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-comments": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.3.tgz",
|
||||
"integrity": "sha512-q6fjd4WU4afNhWOA2WltHgCbkRhZPgQe7cXF74fuVB/ge4QbM9HEaOIzGSiMvM+g/cOsNAUGdf2JDzqA2F8iLA==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.1.tgz",
|
||||
"integrity": "sha512-GVrQxUOhmle1W6jX2SvNLt4kmN+JYhV7mzI6BMnkAWR9DtVvg8e67rrV0NfdWhn7x1zxvzdWkMBPdBDCls+uwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.1.2"
|
||||
"postcss-selector-parser": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -977,9 +977,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-discard-duplicates": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.1.tgz",
|
||||
"integrity": "sha512-oZA+v8Jkpu1ct/xbbrntHRsfLGuzoP+cpt0nJe5ED2FQF8n8bJtn7Bo28jSmBYwqgqnqkuSXJfSUEE7if4nClQ==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.0.tgz",
|
||||
"integrity": "sha512-bAnSuBop5LpAIUmmOSsuvtKAAKREB6BBIYStWUTGq8oG5q9fClDMMuY8i4UPI/cEcDx2TN+7PMnXYIId20UVDw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1013,13 +1013,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-merge-longhand": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.4.tgz",
|
||||
"integrity": "sha512-zer1KoZA54Q8RVHKOY5vMke0cCdNxMP3KBfDerjH/BYHh4nCIh+1Yy0t1pAEQF18ac/4z3OFclO+ZVH8azjR4A==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.2.tgz",
|
||||
"integrity": "sha512-06vrW6ZWi9qeP7KMS9fsa9QW56+tIMW55KYqF7X3Ccn+NI2pIgPV6gFfvXTMQ05H90Y5DvnCDPZ2IuHa30PMUg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"stylehacks": "^7.0.4"
|
||||
"stylehacks": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1029,15 +1029,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-merge-rules": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.4.tgz",
|
||||
"integrity": "sha512-ZsaamiMVu7uBYsIdGtKJ64PkcQt6Pcpep/uO90EpLS3dxJi6OXamIobTYcImyXGoW0Wpugh7DSD3XzxZS9JCPg==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.2.tgz",
|
||||
"integrity": "sha512-VAR47UNvRsdrTHLe7TV1CeEtF9SJYR5ukIB9U4GZyZOptgtsS20xSxy+k5wMrI3udST6O1XuIn7cjQkg7sDAAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"caniuse-api": "^3.0.0",
|
||||
"cssnano-utils": "^5.0.0",
|
||||
"postcss-selector-parser": "^6.1.2"
|
||||
"postcss-selector-parser": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1079,12 +1079,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-params": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.2.tgz",
|
||||
"integrity": "sha512-nyqVLu4MFl9df32zTsdcLqCFfE/z2+f8GE1KHPxWOAmegSo6lpV2GNy5XQvrzwbLmiU7d+fYay4cwto1oNdAaQ==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.1.tgz",
|
||||
"integrity": "sha512-e+Xt8xErSRPgSRFxHeBCSxMiO8B8xng7lh8E0A5ep1VfwYhY8FXhu4Q3APMjgx9YDDbSp53IBGENrzygbUvgUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"cssnano-utils": "^5.0.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
|
@ -1096,13 +1096,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-minify-selectors": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.4.tgz",
|
||||
"integrity": "sha512-JG55VADcNb4xFCf75hXkzc1rNeURhlo7ugf6JjiiKRfMsKlDzN9CXHZDyiG6x/zGchpjQS+UAgb1d4nqXqOpmA==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.2.tgz",
|
||||
"integrity": "sha512-dCzm04wqW1uqLmDZ41XYNBJfjgps3ZugDpogAmJXoCb5oCiTzIX4oPXXKxDpTvWOnKxQKR4EbV4ZawJBLcdXXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"postcss-selector-parser": "^6.1.2"
|
||||
"postcss-selector-parser": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1199,12 +1199,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-normalize-unicode": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.2.tgz",
|
||||
"integrity": "sha512-ztisabK5C/+ZWBdYC+Y9JCkp3M9qBv/XFvDtSw0d/XwfT3UaKeW/YTm/MD/QrPNxuecia46vkfEhewjwcYFjkg==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.1.tgz",
|
||||
"integrity": "sha512-PTPGdY9xAkTw+8ZZ71DUePb7M/Vtgkbbq+EoI33EuyQEzbKemEQMhe5QSr0VP5UfZlreANDPxSfcdSprENcbsg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1261,12 +1261,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-reduce-initial": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.2.tgz",
|
||||
"integrity": "sha512-pOnu9zqQww7dEKf62Nuju6JgsW2V0KRNBHxeKohU+JkHd/GAH5uvoObqFLqkeB2n20mr6yrlWDvo5UBU5GnkfA==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.1.tgz",
|
||||
"integrity": "sha512-0JDUSV4bGB5FGM5g8MkS+rvqKukJZ7OTHw/lcKn7xPNqeaqJyQbUO8/dJpvyTpaVwPsd3Uc33+CfNzdVowp2WA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"browserslist": "^4.23.1",
|
||||
"caniuse-api": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1292,9 +1292,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz",
|
||||
"integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
|
@ -1321,12 +1321,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-unique-selectors": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.3.tgz",
|
||||
"integrity": "sha512-J+58u5Ic5T1QjP/LDV9g3Cx4CNOgB5vz+kM6+OxHHhFACdcDeKhBXjQmB7fnIZM12YSTvsL0Opwco83DmacW2g==",
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.1.tgz",
|
||||
"integrity": "sha512-MH7QE/eKUftTB5ta40xcHLl7hkZjgDFydpfTK+QWXeHxghVt3VoPqYL5/G+zYZPPIs+8GuqFXSTgxBSoB1RZtQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.1.2"
|
||||
"postcss-selector-parser": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1360,9 +1360,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
|
@ -1379,13 +1379,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/stylehacks": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.4.tgz",
|
||||
"integrity": "sha512-i4zfNrGMt9SB4xRK9L83rlsFCgdGANfeDAYacO1pkqcE7cRHPdWHwnKZVz7WY17Veq/FvyYsRAU++Ga+qDFIww==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.2.tgz",
|
||||
"integrity": "sha512-HdkWZS9b4gbgYTdMg4gJLmm7biAUug1qTqXjS+u8X+/pUd+9Px1E+520GnOW3rST9MNsVOVpsJG+mPHNosxjOQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"browserslist": "^4.23.3",
|
||||
"postcss-selector-parser": "^6.1.2"
|
||||
"browserslist": "^4.23.1",
|
||||
"postcss-selector-parser": "^6.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || ^20.9.0 || >=22.0"
|
||||
|
@ -1429,9 +1429,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.36.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz",
|
||||
"integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==",
|
||||
"version": "5.31.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz",
|
||||
"integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
|
@ -1453,15 +1453,15 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
|
||||
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
|
||||
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
|
||||
"version": "1.0.16",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
|
||||
"integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
|
@ -1478,8 +1478,8 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"escalade": "^3.2.0",
|
||||
"picocolors": "^1.1.0"
|
||||
"escalade": "^3.1.2",
|
||||
"picocolors": "^1.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"update-browserslist-db": "cli.js"
|
||||
|
|
108
src/assproc.js
Normal file
108
src/assproc.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const readline = require('readline');
|
||||
const utils = require('./utils.js');
|
||||
|
||||
exports.process = async function(root, options) {
|
||||
const macroPrefix = options.prefix || '#';
|
||||
const entryPoint = options.entry || '';
|
||||
|
||||
root = fs.realpathSync(root);
|
||||
|
||||
const included = [];
|
||||
|
||||
const processFile = async function(fileName) {
|
||||
const fullPath = path.join(root, fileName);
|
||||
if(included.includes(fullPath))
|
||||
return '';
|
||||
included.push(fullPath);
|
||||
|
||||
if(!fullPath.startsWith(root))
|
||||
return '/* *** INVALID PATH: ' + fullPath + ' */';
|
||||
if(!fs.existsSync(fullPath))
|
||||
return '/* *** FILE NOT FOUND: ' + fullPath + ' */';
|
||||
|
||||
const lines = readline.createInterface({
|
||||
input: fs.createReadStream(fullPath),
|
||||
crlfDelay: Infinity,
|
||||
});
|
||||
|
||||
let output = '';
|
||||
let lastWasEmpty = false;
|
||||
|
||||
if(options.showPath)
|
||||
output += "/* *** PATH: " + fullPath + " */\n";
|
||||
|
||||
for await(const line of lines) {
|
||||
const lineTrimmed = utils.trim(line);
|
||||
if(lineTrimmed === '')
|
||||
continue;
|
||||
|
||||
if(line.startsWith(macroPrefix)) {
|
||||
const args = lineTrimmed.split(' ');
|
||||
const macro = utils.trim(utils.trimStart(args.shift(), macroPrefix));
|
||||
|
||||
switch(macro) {
|
||||
case 'comment':
|
||||
break;
|
||||
|
||||
case 'include': {
|
||||
const includePath = utils.trimEnd(args.join(' '), ';');
|
||||
output += utils.trim(await processFile(includePath));
|
||||
output += "\n";
|
||||
break;
|
||||
}
|
||||
|
||||
case 'buildvars':
|
||||
if(typeof options.buildVars === 'object') {
|
||||
const bvTarget = options.buildVarsTarget || 'window';
|
||||
const bvProps = [];
|
||||
|
||||
for(const bvName in options.buildVars)
|
||||
bvProps.push(`${bvName}: { value: ${JSON.stringify(options.buildVars[bvName])} }`);
|
||||
|
||||
if(Object.keys(bvProps).length > 0)
|
||||
output += `Object.defineProperties(${bvTarget}, { ${bvProps.join(', ')} });\n`;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
output += line;
|
||||
output += "\n";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
output += line;
|
||||
output += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
||||
return await processFile(entryPoint);
|
||||
};
|
||||
|
||||
exports.housekeep = function(assetsPath) {
|
||||
const files = fs.readdirSync(assetsPath).map(fileName => {
|
||||
const stats = fs.statSync(path.join(assetsPath, fileName));
|
||||
return {
|
||||
name: fileName,
|
||||
lastMod: stats.mtimeMs,
|
||||
};
|
||||
}).sort((a, b) => b.lastMod - a.lastMod).map(info => info.name);
|
||||
|
||||
const regex = /^(.+)[\-\.]([a-f0-9]+)\.(.+)$/i;
|
||||
const counts = {};
|
||||
|
||||
for(const fileName of files) {
|
||||
const match = fileName.match(regex);
|
||||
if(match) {
|
||||
const name = match[1] + '-' + match[3];
|
||||
counts[name] = (counts[name] || 0) + 1;
|
||||
|
||||
if(counts[name] > 5)
|
||||
fs.unlinkSync(path.join(assetsPath, fileName));
|
||||
} else console.log(`Encountered file name in assets folder with unexpected format: ${fileName}`);
|
||||
}
|
||||
};
|
|
@ -42,7 +42,7 @@
|
|||
|
||||
if(isCompatible) {
|
||||
(function(script) {
|
||||
script.src = MAMI_JS;
|
||||
script.src = MAMI_MAIN_JS;
|
||||
script.type = 'text/javascript';
|
||||
script.charset = 'utf-8';
|
||||
document.body.appendChild(script);
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
height: 40px;
|
||||
max-height: 140px;
|
||||
resize: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.input__text,
|
||||
|
@ -32,9 +33,6 @@
|
|||
cursor: pointer
|
||||
}
|
||||
|
||||
.input__menus {
|
||||
}
|
||||
|
||||
.input__menu {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
|
|
@ -40,6 +40,10 @@ a:hover {
|
|||
visibility: none !important;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: none !important;
|
||||
}
|
||||
|
||||
.sjis {
|
||||
font-family: IPAMonaPGothic, 'IPA モナー Pゴシック', Monapo, Mona, 'MS PGothic', 'MS Pゴシック', monospace;
|
||||
font-size: 16px;
|
||||
|
|
11
src/mami.js/avatar.js
Normal file
11
src/mami.js/avatar.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
const MamiFormatUserAvatarUrl = (userId, res = 0, changeTime = null) => {
|
||||
const template = futami.get('avatar') ?? '';
|
||||
if(template.length < 1)
|
||||
return '';
|
||||
|
||||
changeTime ??= Date.now();
|
||||
|
||||
return template.replace('{user:id}', userId)
|
||||
.replace('{resolution}', res)
|
||||
.replace('{user:avatar_change}', changeTime);
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
#include chatform/input.jsx
|
||||
#include chatform/markup.jsx
|
||||
#include chat/input.jsx
|
||||
#include chat/markup.jsx
|
||||
|
||||
const MamiChatForm = function(eventTarget) {
|
||||
const input = new MamiChatFormInput(eventTarget);
|
||||
const MamiChatForm = function(findUserInfosByName, findEmoteByName, eventTarget) {
|
||||
const input = new MamiChatFormInput(findUserInfosByName, findEmoteByName, eventTarget);
|
||||
const markup = new MamiChatFormMarkup;
|
||||
|
||||
const html = <form class="input" onsubmit={ev => {
|
37
src/mami.js/chat/garbparse.jsx
Normal file
37
src/mami.js/chat/garbparse.jsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
#include parsing.js
|
||||
#include url.js
|
||||
#include weeb.js
|
||||
#include ui/emotes.js
|
||||
|
||||
// replace this with better parsing, read the fucking book already
|
||||
const MamiMolestMessageContents = (info, textElem, userNameElem) => {
|
||||
const hasTextElem = textElem instanceof Element;
|
||||
|
||||
if(hasTextElem) {
|
||||
Umi.UI.Emoticons.Parse(textElem, info.sender);
|
||||
Umi.Parsing.Parse(textElem, info);
|
||||
|
||||
const textSplit = textElem.innerText.split(' ');
|
||||
for(const textPart of textSplit) {
|
||||
const uri = Umi.URI.Parse(textPart);
|
||||
|
||||
if(uri !== null && uri.Slashes !== null) {
|
||||
const anchorElem = <a class="markup__link" href={textPart} target="_blank" rel="nofollow noreferrer noopener">{textPart}</a>;
|
||||
textElem.innerHTML = textElem.innerHTML.replace(textPart.replace(/&/g, '&'), anchorElem.outerHTML);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(mami.settings.get('weeaboo')) {
|
||||
if(userNameElem instanceof Element)
|
||||
userNameElem.appendChild($t(Weeaboo.getNameSuffix(info.sender)));
|
||||
|
||||
if(hasTextElem) {
|
||||
textElem.appendChild($t(Weeaboo.getTextSuffix(info.sender)));
|
||||
|
||||
const kaomoji = Weeaboo.getRandomKaomoji(true, info);
|
||||
if(kaomoji)
|
||||
textElem.append(` ${kaomoji}`);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,7 +1,4 @@
|
|||
#include emotes.js
|
||||
#include users.js
|
||||
|
||||
const MamiChatFormInput = function(eventTarget) {
|
||||
const MamiChatFormInput = function(findUserInfosByName, findEmoteByName, eventTarget) {
|
||||
const textElem = <textarea class="input__text" name="text" autofocus="autofocus"/>;
|
||||
let submitButton;
|
||||
|
||||
|
@ -74,25 +71,24 @@ const MamiChatFormInput = function(eventTarget) {
|
|||
snippet = text.charAt(position) + snippet;
|
||||
}
|
||||
|
||||
let insertText;
|
||||
const insertText = portion => {
|
||||
const nextPos = start - snippet.length + portion.length;
|
||||
textElem.value = text.slice(0, start - snippet.length) + portion + text.slice(start);
|
||||
textElem.selectionEnd = textElem.selectionStart = nextPos;
|
||||
};
|
||||
|
||||
if(snippet.indexOf(':') === 0) {
|
||||
let emoteRank = 0;
|
||||
if(Umi.User.hasCurrentUser())
|
||||
emoteRank = Umi.User.getCurrentUser().perms.rank;
|
||||
const emotes = MamiEmotes.findByName(emoteRank, snippet.substring(1), true);
|
||||
if(emotes.length > 0)
|
||||
insertText = `:${emotes[0]}:`;
|
||||
findEmoteByName(snippet.substring(1))
|
||||
.then(emotes => {
|
||||
if(emotes.length > 0)
|
||||
insertText(`:${emotes[0]}:`);
|
||||
});
|
||||
} else {
|
||||
const users = Umi.Users.Find(snippet);
|
||||
if(users.length === 1)
|
||||
insertText = users[0].name;
|
||||
}
|
||||
|
||||
if(insertText !== undefined) {
|
||||
const nextPos = start - snippet.length + insertText.length;
|
||||
textElem.value = text.slice(0, start - snippet.length) + insertText + text.slice(start);
|
||||
textElem.selectionEnd = textElem.selectionStart = nextPos;
|
||||
findUserInfosByName(snippet)
|
||||
.then(userInfos => {
|
||||
if(userInfos.length === 1)
|
||||
insertText(userInfos[0].name);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
117
src/mami.js/chat/messages.jsx
Normal file
117
src/mami.js/chat/messages.jsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
#include uniqstr.js
|
||||
#include chat/msg-act.jsx
|
||||
#include chat/msg-text.jsx
|
||||
|
||||
const MamiChatMessages = function(globalEvents) {
|
||||
const html = <div class="chat"/>;
|
||||
const broadcasts = [];
|
||||
const channels = new Map;
|
||||
|
||||
let msgs = [];
|
||||
let autoScroll = false;
|
||||
let autoEmbed = false;
|
||||
|
||||
// so like how the fuck am i going to do this wtf
|
||||
// some msgs may not have an id, just gen a rando for those then?
|
||||
|
||||
const doAutoScroll = () => {
|
||||
if(autoScroll)
|
||||
html.lastElementChild?.scrollIntoView({ block: 'start', inline: 'end' });
|
||||
};
|
||||
|
||||
return {
|
||||
get element() { return html; },
|
||||
|
||||
get autoScroll() { return autoScroll; },
|
||||
set autoScroll(value) {
|
||||
autoScroll = !!value;
|
||||
},
|
||||
|
||||
get autoEmbed() { return autoEmbed; },
|
||||
set autoEmbed(value) {
|
||||
autoEmbed = !!value;
|
||||
},
|
||||
|
||||
addMessage: info => {
|
||||
console.info('addMessage()', info);
|
||||
|
||||
const msgId = info.id ?? `internal-${MamiUniqueStr(8)}`;
|
||||
|
||||
let msg;
|
||||
if('element' in info) {
|
||||
msg = info;
|
||||
} else {
|
||||
if(info.type === 'msg:text' || info.type.startsWith('legacy:'))
|
||||
msg = new MamiChatMessageText(info);
|
||||
else
|
||||
msg = new MamiChatMessageAction(info);
|
||||
}
|
||||
|
||||
if('first' in msg) {
|
||||
const prev = msgs[msgs.length - 1];
|
||||
msg.first = prev === undefined || !('first' in prev) || !('info' in prev) || info.sender.id !== prev.info.sender.id;
|
||||
}
|
||||
|
||||
msgs.push(msg);
|
||||
|
||||
html.appendChild(msg.element);
|
||||
|
||||
// only run when not hidden/current channel
|
||||
if(autoEmbed) {
|
||||
const callEmbedOn = msg.element.querySelectorAll('a[onclick^="Umi.Parser.SockChatBBcode.Embed"]');
|
||||
for(const embedElem of callEmbedOn)
|
||||
if(embedElem.dataset.embed !== '1')
|
||||
embedElem.click();
|
||||
}
|
||||
|
||||
// ^^ if(!eBase.classList.contains('hidden'))
|
||||
doAutoScroll();
|
||||
|
||||
if(globalEvents)
|
||||
globalEvents.dispatch('umi:ui:message_add', { element: msg.element });
|
||||
},
|
||||
removeMessage: msgId => {
|
||||
console.info('removeMessage()', msgId);
|
||||
|
||||
for(const i in msgs) {
|
||||
const msg = msgs[i];
|
||||
if(msg.matches(msgId)) {
|
||||
// TODO: i want login, logout, etc. actions to be collapsed as a single messages
|
||||
// some kinda flow to allow for that should exists (probably just check if the msg obj has certain functions and then handle deletion differently)
|
||||
if(html.contains(msg.element))
|
||||
html.removeChild(msg.element);
|
||||
msgs.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
doAutoScroll();
|
||||
},
|
||||
clearMessages: keepAmount => {
|
||||
console.info('clearMessages()', keepAmount);
|
||||
|
||||
msgs = [];
|
||||
html.replaceChildren();
|
||||
},
|
||||
|
||||
scrollIfNeeded: offsetOrForce => {
|
||||
console.info('scrollIfNeeded()', offsetOrForce);
|
||||
|
||||
if(typeof offsetOrForce === 'boolean' && offsetOrForce !== true)
|
||||
return;
|
||||
|
||||
if(typeof offsetOrForce === 'number' && html.scrollTop < (html.scrollHeight - html.offsetHeight - offsetOrForce))
|
||||
return;
|
||||
|
||||
doAutoScroll();
|
||||
},
|
||||
|
||||
// probably not the way i want this to work going forward
|
||||
switchChannel: channelName => {
|
||||
console.info('switchChannel()', channelName);
|
||||
//
|
||||
|
||||
doAutoScroll();
|
||||
},
|
||||
};
|
||||
};
|
82
src/mami.js/chat/msg-act.jsx
Normal file
82
src/mami.js/chat/msg-act.jsx
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include avatar.js
|
||||
#include chat/garbparse.jsx
|
||||
|
||||
const MamiChatMessageAction = function(info) {
|
||||
const avatarClasses = ['message__avatar'];
|
||||
|
||||
// TODO: make this not hardcoded probably (should be in its own object for the thing mentioned in messages.jsx)
|
||||
let text;
|
||||
let senderId = info.sender?.id ?? '-1';
|
||||
let senderName = info.sender?.name ?? '';
|
||||
let senderColour = info.sender?.colour ?? 'inherit';
|
||||
if(info.type === 'msg:action') {
|
||||
text = info.text;
|
||||
} else if(info.type === 'user:add') {
|
||||
text = 'has joined';
|
||||
} else if(info.type === 'user:remove') {
|
||||
if(info.detail.reason === 'kick') {
|
||||
avatarClasses.push('avatar-filter-invert');
|
||||
text = 'got bludgeoned to death';
|
||||
} else if(info.detail.reason === 'flood') {
|
||||
avatarClasses.push('avatar-filter-invert');
|
||||
text = 'got kicked for flood protection';
|
||||
} else if(info.detail.reason === 'timeout') {
|
||||
avatarClasses.push('avatar-filter-greyscale');
|
||||
text = 'exploded';
|
||||
} else {
|
||||
avatarClasses.push('avatar-filter-greyscale');
|
||||
text = 'has disconnected';
|
||||
}
|
||||
} else if(info.type === 'chan:join') {
|
||||
text = 'has joined the channel';
|
||||
} else if(info.type === 'chan:leave') {
|
||||
avatarClasses.push('avatar-filter-greyscale');
|
||||
text = 'has left the channel';
|
||||
} else if(info.type === 'user:nick') {
|
||||
if(senderId === '-1') {
|
||||
senderId = '0';
|
||||
senderName = info.detail.previousName;
|
||||
senderColour = 'inherit';
|
||||
}
|
||||
|
||||
text = `changed their name to ${info.detail.name}`;
|
||||
} else {
|
||||
text = `unknown action type: ${info.type}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`;
|
||||
}
|
||||
|
||||
if(text.indexOf("'") !== 0 || (text.match(/\'/g).length % 2) === 0)
|
||||
text = "\xA0" + text;
|
||||
|
||||
let textElem, userNameElem;
|
||||
const html = <div class={`message message-tiny message--user-${senderId} message--first`}>
|
||||
<div class={avatarClasses} style={`background-image: url('${MamiFormatUserAvatarUrl(senderId, 80)}');`} />
|
||||
<div class="message__container">
|
||||
<div class="message__meta">
|
||||
{userNameElem = <div class="message__user" style={`color: ${senderColour}`}>
|
||||
{senderName}
|
||||
</div>}
|
||||
{textElem = <div class="message-tiny-text">
|
||||
{text}
|
||||
</div>}
|
||||
<div class="message__time">
|
||||
{info.time.getHours().toString().padStart(2, '0')}:{info.time.getMinutes().toString().padStart(2, '0')}:{info.time.getSeconds().toString().padStart(2, '0')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
MamiMolestMessageContents(info, info.type === 'msg:action' ? textElem : undefined, userNameElem);
|
||||
|
||||
return {
|
||||
get element() { return html; },
|
||||
|
||||
matches: other => {
|
||||
if(typeof other === 'object' && other !== null && 'id' in other)
|
||||
other = other.id;
|
||||
if(typeof other !== 'string')
|
||||
return false;
|
||||
|
||||
return other === info.id;
|
||||
},
|
||||
};
|
||||
};
|
89
src/mami.js/chat/msg-text.jsx
Normal file
89
src/mami.js/chat/msg-text.jsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
#include avatar.js
|
||||
#include chat/garbparse.jsx
|
||||
|
||||
const MamiChatMessageText = function(info) {
|
||||
let text;
|
||||
if(info.type === 'msg:text') {
|
||||
text = info.text;
|
||||
} else if(info.type.startsWith('legacy:')) {
|
||||
const bi = info.botInfo;
|
||||
if(bi.type === 'banlist') {
|
||||
const bans = bi.args[0].split(', ');
|
||||
for(const i in bans)
|
||||
bans[i] = bans[i].slice(92, -4);
|
||||
|
||||
text = `Banned: ${bans.join(', ')}`;
|
||||
} else if(bi.type === 'who') {
|
||||
const users = bi.args[0].split(', ');
|
||||
for(const i in users) {
|
||||
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
||||
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
||||
if(isSelf) users[i] += ' (You)';
|
||||
}
|
||||
|
||||
text = `Online: ${users.join(', ')}`;
|
||||
} else if(bi.type === 'whochan') {
|
||||
const users = bi.args[1].split(', ');
|
||||
for(const i in users) {
|
||||
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
||||
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
||||
if(isSelf) users[i] += ' (You)';
|
||||
}
|
||||
|
||||
text = `Online in ${bi.args[0]}: ${users.join(', ')}`;
|
||||
} else if(bi.type === 'say') {
|
||||
text = bi.args[0];
|
||||
} else if(bi.type === 'ipaddr') {
|
||||
text = `IP address of ${bi.args[0]}: ${bi.args[1]}.`;
|
||||
} else {
|
||||
text = `unknown legacy text type: ${bi.type}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`;
|
||||
}
|
||||
} else {
|
||||
text = `unknown text type: ${info.type}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!`;
|
||||
}
|
||||
|
||||
let textElem, userNameElem;
|
||||
const html = <div class={`message message--user-${info.sender.id}`}>
|
||||
<div class="message__avatar" style={`background-image: url('${MamiFormatUserAvatarUrl(info.sender.id, 80)}');`} />
|
||||
<div class="message__container">
|
||||
<div class="message__meta">
|
||||
{userNameElem = <div class="message__user" style={`color: ${info.sender.colour}`}>
|
||||
{info.sender.name}
|
||||
</div>}
|
||||
<div class="message__time">
|
||||
{info.time.getHours().toString().padStart(2, '0')}:{info.time.getMinutes().toString().padStart(2, '0')}:{info.time.getSeconds().toString().padStart(2, '0')}
|
||||
</div>
|
||||
</div>
|
||||
{textElem = <div class="message__text">
|
||||
{text}
|
||||
</div>}
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
if(info.type === 'msg:text') {
|
||||
if(info.sender.id === '136')
|
||||
html.style.transform = 'scaleY(' + (0.76 + (0.01 * Math.max(0, Math.ceil(Date.now() / (7 * 24 * 60 * 60000)) - 2813))).toString() + ')';
|
||||
|
||||
MamiMolestMessageContents(info, textElem, userNameElem);
|
||||
}
|
||||
|
||||
return {
|
||||
get element() { return html; },
|
||||
|
||||
get info() { return info; },
|
||||
|
||||
get first() { return html.classList.contains('message--first'); },
|
||||
set first(value) {
|
||||
html.classList.toggle('message--first', value);
|
||||
},
|
||||
|
||||
matches: other => {
|
||||
if(typeof other === 'object' && other !== null && 'id' in other)
|
||||
other = other.id;
|
||||
if(typeof other !== 'string')
|
||||
return false;
|
||||
|
||||
return other === info.id;
|
||||
},
|
||||
};
|
||||
};
|
|
@ -168,6 +168,7 @@ const MamiColourPicker = function(options) {
|
|||
setPosition: setPosition,
|
||||
close: runReject,
|
||||
dialog: pos => {
|
||||
html.classList.add('invisible');
|
||||
html.classList.remove('hidden');
|
||||
|
||||
if(pos instanceof MouseEvent)
|
||||
|
@ -191,6 +192,8 @@ const MamiColourPicker = function(options) {
|
|||
if(pos !== undefined)
|
||||
setPosition(pos);
|
||||
|
||||
html.classList.remove('invisible');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
promiseResolve = resolve;
|
||||
promiseReject = reject;
|
||||
|
|
|
@ -15,14 +15,8 @@ const FutamiCommon = function(vars) {
|
|||
if(noCache)
|
||||
options.headers = { 'Cache-Control': 'no-cache' };
|
||||
|
||||
return (await $x.get(get(name), options)).body();
|
||||
},
|
||||
getApiJson: async (path, noCache) => {
|
||||
const options = { type: 'json' };
|
||||
if(noCache)
|
||||
options.headers = { 'Cache-Control': 'no-cache' };
|
||||
|
||||
return (await $x.get(get('api') + path, options)).body();
|
||||
const resp = await $x.get(get(name), options);
|
||||
return resp.body();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ const MamiContext = function(globalEventTarget, eventTarget) {
|
|||
let textTriggers;
|
||||
let eeprom;
|
||||
let conMan;
|
||||
let protoWorker;
|
||||
|
||||
return {
|
||||
get globalEvents() { return globalEventTarget; },
|
||||
|
@ -87,5 +88,12 @@ const MamiContext = function(globalEventTarget, eventTarget) {
|
|||
throw 'conMan must be a non-null object';
|
||||
conMan = value;
|
||||
},
|
||||
|
||||
get protoWorker() { return protoWorker; },
|
||||
set protoWorker(value) {
|
||||
if(typeof value !== 'object' || value === null)
|
||||
throw 'protoWorker must be a non-null object';
|
||||
protoWorker = value;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,22 +9,38 @@ const MamiEmotes = (function() {
|
|||
emotes.push(emote);
|
||||
};
|
||||
|
||||
const addLegacy = function(emoteOld) {
|
||||
const emote = {
|
||||
url: emoteOld.Image,
|
||||
minRank: emoteOld.Hierarchy,
|
||||
strings: [],
|
||||
};
|
||||
for(let i = 0; i < emoteOld.Text.length; ++i)
|
||||
emote.strings.push(emoteOld.Text[i].slice(1, -1));
|
||||
add(emote);
|
||||
};
|
||||
|
||||
return {
|
||||
clear: clear,
|
||||
add: add,
|
||||
addLegacy: addLegacy,
|
||||
load: function(batch) {
|
||||
for(const emote of batch)
|
||||
add(emote);
|
||||
},
|
||||
loadLegacy: function(batch) {
|
||||
for(const emote of batch)
|
||||
addLegacy(emote);
|
||||
},
|
||||
forEach: function(minRank, callback) {
|
||||
for(const emote of emotes)
|
||||
if(!emote.min_rank || emote.min_rank <= minRank)
|
||||
if(emote.minRank <= minRank)
|
||||
callback(emote);
|
||||
},
|
||||
all: minRank => {
|
||||
const items = [];
|
||||
for(const emote of emotes)
|
||||
if(!emote.min_rank || emote.min_rank <= minRank)
|
||||
if(emote.minRank <= minRank)
|
||||
items.push(emote);
|
||||
|
||||
return items;
|
||||
|
@ -32,7 +48,7 @@ const MamiEmotes = (function() {
|
|||
findByName: function(minRank, name, returnString) {
|
||||
const found = [];
|
||||
for(const emote of emotes)
|
||||
if(!emote.min_rank || emote.min_rank <= minRank) {
|
||||
if(emote.minRank <= minRank) {
|
||||
for(const string of emote.strings)
|
||||
if(string.indexOf(name) === 0) {
|
||||
found.push(returnString ? string : emote);
|
||||
|
|
|
@ -111,38 +111,55 @@ const MamiEmotePicker = function(args) {
|
|||
setPosition: setPosition,
|
||||
close: close,
|
||||
dialog: pos => {
|
||||
emotes = args.getEmotes();
|
||||
buildList();
|
||||
const dialogBody = () => {
|
||||
buildList();
|
||||
|
||||
html.classList.remove('hidden');
|
||||
html.classList.add('invisible');
|
||||
html.classList.remove('hidden');
|
||||
|
||||
if(pos instanceof MouseEvent)
|
||||
pos = pos.target;
|
||||
if(pos instanceof Element)
|
||||
pos = pos.getBoundingClientRect();
|
||||
if(pos instanceof DOMRect) {
|
||||
const bbb = pos;
|
||||
pos = {};
|
||||
if(pos instanceof MouseEvent)
|
||||
pos = pos.target;
|
||||
if(pos instanceof Element)
|
||||
pos = pos.getBoundingClientRect();
|
||||
if(pos instanceof DOMRect) {
|
||||
const bbb = pos;
|
||||
pos = {};
|
||||
|
||||
const mbb = html.getBoundingClientRect();
|
||||
const pbb = html.parentNode.getBoundingClientRect();
|
||||
const mbb = html.getBoundingClientRect();
|
||||
const pbb = html.parentNode.getBoundingClientRect();
|
||||
|
||||
pos.right = pbb.width - bbb.left;
|
||||
pos.bottom = pbb.height - bbb.top;
|
||||
pos.right = pbb.width - bbb.left;
|
||||
pos.bottom = pbb.height - bbb.top;
|
||||
|
||||
if(pos.right + mbb.width > pbb.width)
|
||||
pos.right = 0;
|
||||
else if(pos.right > mbb.width)
|
||||
pos.right -= mbb.width;
|
||||
else
|
||||
pos.right -= bbb.width;
|
||||
if(pos.right + mbb.width > pbb.width)
|
||||
pos.right = 0;
|
||||
else if(pos.right > mbb.width)
|
||||
pos.right -= mbb.width;
|
||||
else
|
||||
pos.right -= bbb.width;
|
||||
}
|
||||
|
||||
if(pos !== undefined)
|
||||
setPosition(pos);
|
||||
|
||||
html.classList.remove('invisible');
|
||||
searchElem.focus();
|
||||
};
|
||||
|
||||
const result = args.getEmotes();
|
||||
if(result instanceof Promise)
|
||||
result.then(list => {
|
||||
emotes = list;
|
||||
}).catch(() => {
|
||||
// uhhh just use the last version of the list LOL
|
||||
}).finally(() => {
|
||||
dialogBody();
|
||||
});
|
||||
else {
|
||||
emotes = result;
|
||||
dialogBody();
|
||||
}
|
||||
|
||||
if(pos !== undefined)
|
||||
setPosition(pos);
|
||||
|
||||
searchElem.focus();
|
||||
|
||||
return new Promise(resolve => { promiseResolve = resolve; });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -15,11 +15,12 @@ window.Umi = { UI: {} };
|
|||
#include themes.js
|
||||
#include txtrigs.js
|
||||
#include uniqstr.js
|
||||
#include users.js
|
||||
#include utility.js
|
||||
#include weeb.js
|
||||
#include worker.js
|
||||
#include audio/autoplay.js
|
||||
#include chatform/form.jsx
|
||||
#include chat/form.jsx
|
||||
#include chat/messages.jsx
|
||||
#include colpick/picker.jsx
|
||||
#include controls/msgbox.jsx
|
||||
#include controls/ping.jsx
|
||||
|
@ -226,7 +227,8 @@ const MamiInit = async args => {
|
|||
// revisit when emote reparsing is implemented
|
||||
loadingOverlay.message = 'Loading emoticons...';
|
||||
try {
|
||||
MamiEmotes.load(await futami.getApiJson('/v1/emotes'));
|
||||
const emotes = await futami.getJson('emotes');
|
||||
MamiEmotes.loadLegacy(emotes);
|
||||
} catch(ex) {
|
||||
console.error('Failed to load emoticons.', ex);
|
||||
}
|
||||
|
@ -286,7 +288,15 @@ const MamiInit = async args => {
|
|||
});
|
||||
|
||||
|
||||
const chatForm = new MamiChatForm(ctx.events.scopeTo('form'));
|
||||
const messages = new MamiChatMessages(ctx.globalEvents);
|
||||
settings.watch('autoScroll', ev => { messages.autoScroll = ev.detail.value; });
|
||||
settings.watch('autoEmbedV1', ev => { messages.autoEmbed = ev.detail.value; });
|
||||
|
||||
const chatForm = new MamiChatForm(
|
||||
async snippet => await sockChatClient.getUserInfosByName(snippet),
|
||||
async snippet => MamiEmotes.findByName((await sockChatClient.getCurrentUserInfo()).perms.rank, snippet, true),
|
||||
ctx.events.scopeTo('form')
|
||||
);
|
||||
|
||||
window.addEventListener('keydown', ev => {
|
||||
if((ev.ctrlKey && ev.key !== 'v') || ev.altKey)
|
||||
|
@ -298,6 +308,7 @@ const MamiInit = async args => {
|
|||
|
||||
settings.watch('showMarkupSelector', ev => {
|
||||
chatForm.markup.visible = ev.detail.value !== 'never';
|
||||
messages.scrollIfNeeded(chatForm.markup.height);
|
||||
Umi.UI.Messages.ScrollIfNeeded(chatForm.markup.height);
|
||||
});
|
||||
|
||||
|
@ -329,10 +340,11 @@ const MamiInit = async args => {
|
|||
});
|
||||
|
||||
|
||||
const layout = new Umi.UI.ChatLayout(chatForm, sidebar);
|
||||
const layout = new Umi.UI.ChatLayout(messages, chatForm, sidebar);
|
||||
await ctx.views.unshift(layout);
|
||||
|
||||
ctx.events.watch('form:resize', ev => {
|
||||
messages.scrollIfNeeded(ev.detail.diffHeight);
|
||||
Umi.UI.Messages.ScrollIfNeeded(ev.detail.diffHeight);
|
||||
});
|
||||
|
||||
|
@ -408,39 +420,50 @@ const MamiInit = async args => {
|
|||
|
||||
loadingOverlay.message = 'Building menus...';
|
||||
|
||||
MamiCompat('Umi.Parser.SockChatBBcode.EmbedStub', { value: () => {} }); // intentionally a no-op
|
||||
MamiCompat('Umi.Parser.SockChatBBcode.EmbedStub', { value: () => {} });
|
||||
MamiCompat('Umi.UI.View.SetText', { value: text => { chatForm.input.setText(text); } });
|
||||
|
||||
let sockChatClient;
|
||||
|
||||
const sbUsers = new MamiSidebarPanelUsers;
|
||||
sidebar.createPanel(sbUsers);
|
||||
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'profile',
|
||||
text: 'View profile',
|
||||
onclick: entry => window.open(futami.get('profile').replace('{user:id}', entry.id), '_blank'),
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'action',
|
||||
text: 'Describe action',
|
||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id,
|
||||
condition: async entry => await sockChatClient.getCurrentUserId() === entry.id,
|
||||
onclick: entry => { chatForm.input.setText('/me '); },
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'nick',
|
||||
text: 'Set nickname',
|
||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canSetNick,
|
||||
condition: async entry => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
return userInfo.id === entry.id && userInfo.perms.nick;
|
||||
},
|
||||
onclick: entry => { chatForm.input.setText('/nick '); },
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'bans',
|
||||
text: 'View bans',
|
||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canKick,
|
||||
condition: async entry => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
return userInfo.id === entry.id && userInfo.perms.kick;
|
||||
},
|
||||
onclick: entry => { Umi.Server.sendMessage('/bans'); },
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'kfe',
|
||||
text: 'Kick Fucking Everyone',
|
||||
condition: entry => Umi.User.getCurrentUser()?.id === entry.id && Umi.User.getCurrentUser().perms.canKick,
|
||||
condition: async entry => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
return userInfo.id === entry.id && userInfo.perms.kick;
|
||||
},
|
||||
onclick: async entry => {
|
||||
try {
|
||||
await ctx.msgbox.show({
|
||||
|
@ -449,30 +472,36 @@ const MamiInit = async args => {
|
|||
no: { text: 'nah' },
|
||||
});
|
||||
|
||||
const names = sbUsers.getAllUserNames();
|
||||
for(const name of names)
|
||||
if(Umi.User.getCurrentUser()?.name !== name)
|
||||
// this shouldn't call it like this but will have to leave it for now
|
||||
Umi.Server.sendMessage(`/kick ${name}`);
|
||||
const myInfo = await sockChatClient.getCurrentUserInfo();
|
||||
const userInfos = await sockChatClient.getUserInfos();
|
||||
for(const userInfo of userInfos)
|
||||
if(myInfo.id !== userInfo.id)
|
||||
sockChatClient.sendMessage(`/kick ${userInfo.name}`);
|
||||
} catch(ex) {}
|
||||
},
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'dm',
|
||||
text: 'Send direct message',
|
||||
condition: entry => Umi.User.getCurrentUser()?.id !== entry.id,
|
||||
condition: async entry => await sockChatClient.getCurrentUserId() !== entry.id,
|
||||
onclick: entry => { chatForm.input.setText(`/msg ${entry.name} `); },
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'kick',
|
||||
text: 'Kick from chat',
|
||||
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
||||
condition: async entry => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
return userInfo.id !== entry.id && userInfo.perms.kick;
|
||||
},
|
||||
onclick: entry => { chatForm.input.setText(`/kick ${entry.name} `); },
|
||||
});
|
||||
sbUsers.addOption({
|
||||
await sbUsers.addOption({
|
||||
name: 'ipaddr',
|
||||
text: 'View IP address',
|
||||
condition: entry => Umi.User.hasCurrentUser() && Umi.User.getCurrentUser().id !== entry.id && Umi.User.getCurrentUser().perms.canKick,
|
||||
condition: async entry => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
return userInfo.id !== entry.id && userInfo.perms.kick;
|
||||
},
|
||||
onclick: entry => { Umi.Server.sendMessage(`/ip ${entry.name}`); },
|
||||
});
|
||||
|
||||
|
@ -594,9 +623,9 @@ const MamiInit = async args => {
|
|||
button.disabled = true;
|
||||
button.textContent = 'Reloading emoticons...';
|
||||
try {
|
||||
const emotes = await futami.getApiJson('/v1/emotes', true);
|
||||
const emotes = await futami.getJson('emotes', true);
|
||||
MamiEmotes.clear();
|
||||
MamiEmotes.load(emotes);
|
||||
MamiEmotes.loadLegacy(emotes);
|
||||
} finally {
|
||||
button.textContent = textOrig;
|
||||
button.disabled = false;
|
||||
|
@ -641,11 +670,11 @@ const MamiInit = async args => {
|
|||
category.button('Import settings', () => {
|
||||
(new MamiSettingsBackup(settings)).importUpload(args.parent);
|
||||
}, ['Your current settings will be replaced with the ones in the export.', 'Are you sure you want to continue?']);
|
||||
category.button('Export settings', () => {
|
||||
const user = Umi.User.getCurrentUser();
|
||||
category.button('Export settings', async () => {
|
||||
const userInfo = await sockChatClient.getCurrentUserInfo();
|
||||
let fileName;
|
||||
if(user !== null)
|
||||
fileName = `${user.name}'s settings.mami`;
|
||||
if(userInfo !== undefined)
|
||||
fileName = `${userInfo.name}'s settings.mami`;
|
||||
|
||||
(new MamiSettingsBackup(settings)).exportDownload(args.parent, fileName);
|
||||
});
|
||||
|
@ -701,7 +730,7 @@ const MamiInit = async args => {
|
|||
sidebar.createAction(new MamiSidebarActionScroll(settings));
|
||||
sidebar.createAction(new MamiSidebarActionSound(settings));
|
||||
sidebar.createAction(new MamiSidebarActionCollapseAll);
|
||||
sidebar.createAction(new MamiSidebarActionClearBacklog(settings, soundCtx.library, ctx.msgbox));
|
||||
sidebar.createAction(new MamiSidebarActionClearBacklog(settings, soundCtx.library, ctx.msgbox, messages));
|
||||
|
||||
const pingIndicator = new MamiPingIndicator;
|
||||
const sbActPing = new MamiSidebarActionPing(pingIndicator, ctx.msgbox);
|
||||
|
@ -719,7 +748,7 @@ const MamiInit = async args => {
|
|||
onPick: emote => {
|
||||
chatForm.input.insertAtCursor(`:${emote.strings[0]}:`);
|
||||
},
|
||||
getEmotes: () => MamiEmotes.all(Umi.User.getCurrentUser().perms.rank),
|
||||
getEmotes: async () => MamiEmotes.all((await sockChatClient.getCurrentUserInfo()).perms.rank),
|
||||
setKeepOpenOnPick: value => { settings.set('keepEmotePickerOpen', value); },
|
||||
});
|
||||
layout.getElement().appendChild(emotePicker.element);
|
||||
|
@ -740,14 +769,14 @@ const MamiInit = async args => {
|
|||
ctx.eeprom = new MamiEEPROM(futami.get('eeprom2'), MamiMisuzuAuth.getLine);
|
||||
ctx.eeprom.init()
|
||||
.catch(ex => { console.error('Failed to initialise EEPROM.', ex); })
|
||||
.then(() => {
|
||||
sbUploads.addOption({
|
||||
.then(async () => {
|
||||
await sbUploads.addOption({
|
||||
name: 'view',
|
||||
text: 'View upload',
|
||||
condition: entry => entry.uploadInfo !== undefined,
|
||||
onclick: entry => window.open(entry.uploadInfo.url),
|
||||
});
|
||||
sbUploads.addOption({
|
||||
await sbUploads.addOption({
|
||||
name: 'insert',
|
||||
text: 'Insert into message',
|
||||
condition: entry => entry.uploadInfo !== undefined,
|
||||
|
@ -767,7 +796,7 @@ const MamiInit = async args => {
|
|||
chatForm.input.insertAtCursor(text);
|
||||
},
|
||||
});
|
||||
sbUploads.addOption({
|
||||
await sbUploads.addOption({
|
||||
name: 'delete',
|
||||
text: 'Delete upload',
|
||||
condition: entry => entry.uploadInfo !== undefined,
|
||||
|
@ -789,20 +818,20 @@ const MamiInit = async args => {
|
|||
task.onProgress(prog => {
|
||||
entry.progress = prog.progress;
|
||||
});
|
||||
entry.addOption({
|
||||
await entry.addOption({
|
||||
name: 'cancel',
|
||||
text: 'Cancel upload',
|
||||
onclick: () => { task.abort(); },
|
||||
});
|
||||
await entry.setOptionsVisible(true);
|
||||
|
||||
try {
|
||||
const fileInfo = await task.start();
|
||||
|
||||
entry.optionsVisible = false;
|
||||
await entry.setOptionsVisible(false);
|
||||
entry.uploadInfo = fileInfo;
|
||||
entry.removeOption('cancel');
|
||||
await entry.removeOption('cancel');
|
||||
entry.nukeProgress();
|
||||
sbUploads.reloadOptionsFor(entry);
|
||||
|
||||
if(settings.get('eepromAutoInsert'))
|
||||
entry.clickOption('insert');
|
||||
|
@ -837,7 +866,6 @@ const MamiInit = async args => {
|
|||
});
|
||||
|
||||
ctx.events.watch('form:upload', ev => {
|
||||
console.info(ev);
|
||||
for(const file of ev.detail.files)
|
||||
doUpload(file);
|
||||
});
|
||||
|
@ -903,7 +931,10 @@ const MamiInit = async args => {
|
|||
}
|
||||
};
|
||||
|
||||
const sockChat = new MamiSockChat(ctx.events.scopeTo('sockchat'));
|
||||
const protoWorker = new MamiWorker(MAMI_PROTO_JS, ctx.events.scopeTo('worker'));
|
||||
ctx.protoWorker = protoWorker;
|
||||
|
||||
const sockChat = new MamiSockChat(protoWorker);
|
||||
const conMan = new MamiConnectionManager(sockChat, settings, futami.get('servers'), ctx.events.scopeTo('conn'));
|
||||
ctx.conMan = conMan;
|
||||
|
||||
|
@ -917,6 +948,7 @@ const MamiInit = async args => {
|
|||
// hack for DM channels
|
||||
if(info.isUserChannel) {
|
||||
sbChannels.setActiveEntry(info.name);
|
||||
messages.switchChannel(info.name);
|
||||
Umi.UI.Messages.SwitchChannel(info);
|
||||
}
|
||||
};
|
||||
|
@ -954,7 +986,7 @@ const MamiInit = async args => {
|
|||
|
||||
const sockChatHandlers = new MamiSockChatHandlers(
|
||||
ctx, sockChat, setLoadingOverlay, sockChatReconnect, pingIndicator,
|
||||
sbActPing, sbChannels, sbUsers
|
||||
sbActPing, sbChannels, sbUsers, messages
|
||||
);
|
||||
settings.watch('dumpEvents', ev => sockChatHandlers.setDumpEvents(ev.detail.value));
|
||||
settings.watch('dumpPackets', ev => sockChat.setDumpPackets(ev.detail.value));
|
||||
|
@ -977,9 +1009,41 @@ const MamiInit = async args => {
|
|||
conMan.watch('attempt', conManAttempt);
|
||||
conMan.watch('fail', conManFail);
|
||||
|
||||
await sockChat.create();
|
||||
conMan.client = sockChat;
|
||||
await conMan.start();
|
||||
let workerStarting = false;
|
||||
const initWorker = async () => {
|
||||
if(workerStarting)
|
||||
return;
|
||||
workerStarting = true;
|
||||
|
||||
if(FUTAMI_DEBUG)
|
||||
console.info('[proto] initialising worker...');
|
||||
|
||||
try {
|
||||
await protoWorker.connect();
|
||||
await sockChat.create();
|
||||
conMan.client = sockChat;
|
||||
sockChatClient = sockChat.client;
|
||||
await conMan.start();
|
||||
} finally {
|
||||
workerStarting = false;
|
||||
}
|
||||
};
|
||||
|
||||
protoWorker.watch(':timeout', ev => {
|
||||
console.warn('worker timeout', ev.detail);
|
||||
initWorker();
|
||||
});
|
||||
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if(document.visibilityState === 'visible') {
|
||||
protoWorker.ping().catch(ex => {
|
||||
console.warn('worker died', ex);
|
||||
initWorker();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await initWorker();
|
||||
|
||||
return ctx;
|
||||
};
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
#include users.js
|
||||
|
||||
const MamiMessageAuthorInfo = function(self = false, user = null, id = null, name = null, colour = null, rank = null, avatar = null) {
|
||||
if(typeof self !== 'boolean')
|
||||
throw 'self must be a boolean';
|
||||
|
||||
if(user === null) {
|
||||
id ??= '';
|
||||
name ??= '';
|
||||
colour ??= 'inherit';
|
||||
rank ??= 0;
|
||||
avatar ??= new MamiUserAvatarInfo(id);
|
||||
} else {
|
||||
if(typeof user !== 'object')
|
||||
throw 'user must be an object or null';
|
||||
|
||||
id ??= user.id;
|
||||
name ??= user.name;
|
||||
colour ??= user.colour;
|
||||
rank ??= user.perms.rank;
|
||||
avatar ??= user.avatar;
|
||||
}
|
||||
|
||||
if(typeof id !== 'string')
|
||||
throw 'id must be a string';
|
||||
if(typeof name !== 'string')
|
||||
throw 'name must be a string';
|
||||
if(typeof colour !== 'string')
|
||||
throw 'colour must be a string';
|
||||
if(typeof rank !== 'number')
|
||||
throw 'rank must be a number';
|
||||
if(typeof avatar !== 'object')
|
||||
throw 'avatar must be an object';
|
||||
|
||||
return {
|
||||
get self() { return self; },
|
||||
|
||||
get user() { return user; },
|
||||
get hasUser() { return user !== null; },
|
||||
|
||||
get id() { return id; },
|
||||
get name() { return name; },
|
||||
get colour() { return colour; },
|
||||
get rank() { return rank; },
|
||||
get avatar() { return avatar; },
|
||||
};
|
||||
};
|
||||
|
||||
const MamiMessageInfo = function(type, created = null, detail = null, id = '', author = null, channel = '', silent = false) {
|
||||
if(typeof type !== 'string')
|
||||
throw 'type must be a string';
|
||||
if(created === null)
|
||||
created = new Date;
|
||||
else if(!(created instanceof Date))
|
||||
throw 'created must be an instance of window.Date or null';
|
||||
if(typeof id !== 'string')
|
||||
throw 'id must be a string';
|
||||
if(typeof author !== 'object')
|
||||
throw 'author must be an object';
|
||||
if(typeof channel !== 'string')
|
||||
throw 'channel must be a string';
|
||||
if(typeof silent !== 'boolean')
|
||||
throw 'silent must be a boolean';
|
||||
|
||||
return {
|
||||
get type() { return type; },
|
||||
get created() { return created; },
|
||||
get detail() { return detail; },
|
||||
get id() { return id; },
|
||||
get author() { return author; },
|
||||
get channel() { return channel; },
|
||||
get silent() { return silent; },
|
||||
};
|
||||
};
|
|
@ -1,303 +0,0 @@
|
|||
#include proto/sockchat/utils.js
|
||||
|
||||
const SockChatS2CPong = ctx => {
|
||||
const lastPong = Date.now();
|
||||
const lastPing = ctx.lastPing;
|
||||
|
||||
ctx.lastPing = undefined;
|
||||
if(lastPing === undefined)
|
||||
throw 'unexpected pong received??';
|
||||
|
||||
ctx.pingPromise?.resolve({
|
||||
ping: lastPing,
|
||||
pong: lastPong,
|
||||
diff: lastPong - lastPing,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CBanKick = (ctx, type, expiresTime) => {
|
||||
ctx.wasKicked = true;
|
||||
|
||||
const bakaInfo = {
|
||||
session: { success: false },
|
||||
baka: {
|
||||
type: type === '0' ? 'kick' : 'ban',
|
||||
},
|
||||
};
|
||||
|
||||
if(bakaInfo.baka.type === 'ban') {
|
||||
bakaInfo.baka.perma = expiresTime === '-1';
|
||||
bakaInfo.baka.until = expiresTime === '-1' ? undefined : new Date(parseInt(expiresTime) * 1000);
|
||||
}
|
||||
|
||||
ctx.dispatch('session:term', bakaInfo);
|
||||
};
|
||||
|
||||
const SockChatS2CContextClear = (ctx, mode) => {
|
||||
if(mode === '0' || mode === '3' || mode === '4')
|
||||
ctx.dispatch('msg:clear');
|
||||
|
||||
if(mode === '1' || mode === '3' || mode === '4')
|
||||
ctx.dispatch('user:clear');
|
||||
|
||||
if(mode === '2' || mode === '4')
|
||||
ctx.dispatch('chan:clear');
|
||||
};
|
||||
|
||||
const SockChatS2CChannelPopulate = (ctx, count, ...args) => {
|
||||
count = parseInt(count);
|
||||
ctx.dispatch('chan:clear');
|
||||
|
||||
for(let i = 0; i < count; ++i) {
|
||||
const offset = 3 * i;
|
||||
|
||||
ctx.dispatch('chan:add', {
|
||||
channel: {
|
||||
name: args[offset],
|
||||
hasPassword: args[offset + 1] !== '0',
|
||||
isTemporary: args[offset + 2] !== '0',
|
||||
isCurrent: args[offset] === ctx.channelName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ctx.dispatch('chan:focus', {
|
||||
channel: { name: ctx.channelName },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelAdd = (ctx, name, hasPass, isTemp) => {
|
||||
ctx.dispatch('chan:add', {
|
||||
channel: {
|
||||
name: name,
|
||||
hasPassword: hasPass !== '0',
|
||||
isTemporary: isTemp !== '0',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelUpdate = (ctx, prevName, name, hasPass, isTemp) => {
|
||||
ctx.dispatch('chan:update', {
|
||||
channel: {
|
||||
previousName: prevName,
|
||||
name: name,
|
||||
hasPassword: hasPass !== '0',
|
||||
isTemporary: isTemp !== '0',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelRemove = (ctx, name) => {
|
||||
ctx.dispatch('chan:remove', {
|
||||
channel: { name: name },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserPopulate = (ctx, count, ...args) => {
|
||||
count = parseInt(count);
|
||||
ctx.dispatch('user:clear');
|
||||
|
||||
for(let i = 0; i < count; ++i) {
|
||||
const offset = 5 * i;
|
||||
const statusInfo = SockChatParseStatusInfo(args[offset + 1]);
|
||||
|
||||
ctx.dispatch('user:add', {
|
||||
user: {
|
||||
id: args[offset],
|
||||
self: args[offset] === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(args[offset + 2]),
|
||||
perms: SockChatParseUserPerms(args[offset + 3]),
|
||||
hidden: args[offset + 4] !== '0',
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const SockChatS2CUserAdd = (ctx, timeStamp, userId, userName, userColour, userPerms, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
|
||||
ctx.dispatch('user:add', {
|
||||
msg: {
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
user: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserUpdate = (ctx, userId, userName, userColour, userPerms) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
|
||||
ctx.dispatch('user:update', {
|
||||
user: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserRemove = (ctx, userId, userName, reason, timeStamp, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
|
||||
ctx.dispatch('user:remove', {
|
||||
leave: { type: reason },
|
||||
msg: {
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
user: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelJoin = (ctx, userId, userName, userColour, userPerms, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
|
||||
ctx.dispatch('chan:join', {
|
||||
user: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
msg: {
|
||||
id: msgId,
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelLeave = (ctx, userId, msgId) => {
|
||||
ctx.dispatch('chan:leave', {
|
||||
user: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
},
|
||||
msg: {
|
||||
id: msgId,
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelFocus = (ctx, name) => {
|
||||
ctx.channelName = name;
|
||||
|
||||
ctx.dispatch('chan:focus', {
|
||||
channel: { name: ctx.channelName },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CMessagePopulate = (ctx, timeStamp, userId, userName, userColour, userPerms, msgText, msgId, msgNotify, msgFlags) => {
|
||||
const mFlags = SockChatParseMsgFlags(msgFlags);
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const info = {
|
||||
msg: {
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
sender: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
isBot: userId === '-1',
|
||||
silent: msgNotify === '0',
|
||||
flags: mFlags,
|
||||
flagsRaw: msgFlags,
|
||||
text: SockChatUnfuckText(msgText, mFlags.isAction),
|
||||
},
|
||||
};
|
||||
|
||||
if(!isNaN(msgId))
|
||||
info.msg.id = msgId;
|
||||
|
||||
if(info.msg.isBot) {
|
||||
const botParts = msgText.split("\f");
|
||||
info.msg.botInfo = {
|
||||
isError: botParts[0] === '1',
|
||||
type: botParts[1],
|
||||
args: botParts.slice(2),
|
||||
};
|
||||
|
||||
if(info.msg.botInfo.type === 'say')
|
||||
info.msg.botInfo.args[0] = SockChatUnfuckText(info.msg.botInfo.args[0]);
|
||||
}
|
||||
|
||||
ctx.dispatch('msg:add', info);
|
||||
};
|
||||
|
||||
const SockChatS2CMessageAdd = (ctx, timeStamp, userId, msgText, msgId, msgFlags) => {
|
||||
const mFlags = SockChatParseMsgFlags(msgFlags);
|
||||
let mText = SockChatUnfuckText(msgText, mFlags.isAction);
|
||||
let mChannelName = ctx.channelName;
|
||||
|
||||
if(msgFlags[4] !== '0') {
|
||||
if(userId === ctx.userId) {
|
||||
const mTextParts = mText.split(' ');
|
||||
mChannelName = `@${mTextParts.shift()}`;
|
||||
mText = mTextParts.join(' ');
|
||||
} else {
|
||||
mChannelName = `@~${userId}`;
|
||||
}
|
||||
}
|
||||
|
||||
const msgInfo = {
|
||||
msg: {
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: mChannelName,
|
||||
sender: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
},
|
||||
flags: mFlags,
|
||||
flagsRaw: msgFlags,
|
||||
isBot: userId === '-1',
|
||||
text: mText,
|
||||
},
|
||||
};
|
||||
|
||||
if(msgInfo.msg.isBot) {
|
||||
const botParts = msgText.split("\f");
|
||||
msgInfo.msg.botInfo = {
|
||||
isError: botParts[0] === '1',
|
||||
type: botParts[1],
|
||||
args: botParts.slice(2),
|
||||
};
|
||||
}
|
||||
|
||||
ctx.dispatch('msg:add', msgInfo);
|
||||
};
|
||||
|
||||
const SockChatS2CMessageRemove = (ctx, msgId) => {
|
||||
ctx.dispatch('msg:remove', {
|
||||
msg: {
|
||||
id: msgId,
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -25,7 +25,7 @@ const MamiSettings = function(storageOrPrefix, eventTarget) {
|
|||
});
|
||||
const dispatchUpdate = (name, value, silent, local) => eventTarget.dispatch(createUpdateEvent(name, value, false, silent, local));
|
||||
|
||||
const broadcast = new BroadcastChannel(`${MAMI_JS}:settings:${storage.name}`);
|
||||
const broadcast = new BroadcastChannel(`${MAMI_MAIN_JS}:settings:${storage.name}`);
|
||||
const broadcastUpdate = (name, value, silent) => {
|
||||
setTimeout(() => broadcast.postMessage({ act: 'update', name: name, value: value, silent: !!silent }), 0);
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#include awaitable.js
|
||||
#include ui/messages.jsx
|
||||
|
||||
const MamiSidebarActionClearBacklog = function(settings, sndLib, msgBox) {
|
||||
const MamiSidebarActionClearBacklog = function(settings, sndLib, msgBox, messages) {
|
||||
return {
|
||||
get name() { return 'act:clear-backlog'; },
|
||||
get text() { return 'Clear backlog'; },
|
||||
|
@ -20,6 +20,7 @@ const MamiSidebarActionClearBacklog = function(settings, sndLib, msgBox) {
|
|||
|
||||
sndLib.play('misc:explode');
|
||||
|
||||
messages.clearMessages(settings.get('explosionRadius'));
|
||||
Umi.UI.Messages.Clear(settings.get('explosionRadius'));
|
||||
|
||||
await MamiSleep(1700);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include animate.js
|
||||
#include utility.js
|
||||
|
||||
const MamiSidebarPanelUploadsEntry = function(fileInfo) {
|
||||
|
@ -5,8 +6,8 @@ const MamiSidebarPanelUploadsEntry = function(fileInfo) {
|
|||
let uploadInfo;
|
||||
let detailsElem, thumbElem, nameElem, progElem, optsElem;
|
||||
|
||||
const html = <div class="sidebar__user" style="background: linear-gradient(270deg, transparent 0, #111 40%) #222; margin-bottom: 1px;">
|
||||
{detailsElem = <div class="sidebar__user-details" title={fileInfo.name} style="transition: height .2s" onclick={() => { optsElem.classList.toggle('hidden'); }}>
|
||||
const html = <div class="sidebar__user" style="background: linear-gradient(270deg, transparent 0, var(--theme-colour-sidebar-background) 40%) var(--theme-colour-sidebar-background-highlight)">
|
||||
{detailsElem = <div class="sidebar__user-details" title={fileInfo.name} style="transition: height .2s" onclick={() => { toggleOptions(); }}>
|
||||
{thumbElem = <div class="sidebar__user-avatar hidden" style="transition: width .2s, height .2s" onmouseover={() => {
|
||||
thumbElem.style.width = '100px';
|
||||
detailsElem.style.height = thumbElem.style.height = '100px';
|
||||
|
@ -17,9 +18,98 @@ const MamiSidebarPanelUploadsEntry = function(fileInfo) {
|
|||
{nameElem = <div class="sidebar__user-name" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">{fileInfo.name}</div>}
|
||||
</div>}
|
||||
{progElem = <progress class="eeprom-item-progress" max="100" value="0"/>}
|
||||
{optsElem = <div class="sidebar__user-options"/>}
|
||||
{optsElem = <div class="sidebar__user-options hidden"/>}
|
||||
</div>;
|
||||
|
||||
let optionsVisible = false, optionsAnim, optionsTimeout;
|
||||
const toggleOptions = async state => {
|
||||
if(state === undefined)
|
||||
state = !optionsVisible;
|
||||
else if(state === optionsVisible)
|
||||
return;
|
||||
|
||||
if(optionsTimeout !== undefined) {
|
||||
clearTimeout(optionsTimeout);
|
||||
optionsTimeout = undefined;
|
||||
}
|
||||
|
||||
if(optionsAnim !== undefined) {
|
||||
optionsAnim.cancel();
|
||||
optionsAnim = undefined;
|
||||
}
|
||||
|
||||
let start, update, end, height;
|
||||
|
||||
if(state) {
|
||||
await reloadOptions();
|
||||
start = () => {
|
||||
optsElem.classList.remove('hidden');
|
||||
const curHeight = optsElem.style.height;
|
||||
optsElem.style.height = null;
|
||||
height = optsElem.clientHeight;
|
||||
optsElem.style.height = curHeight;
|
||||
};
|
||||
update = function(t) {
|
||||
optsElem.style.height = `${height * t}px`;
|
||||
};
|
||||
end = () => {
|
||||
optsElem.style.height = null;
|
||||
};
|
||||
} else {
|
||||
start = () => {
|
||||
height = optsElem.clientHeight;
|
||||
};
|
||||
update = t => {
|
||||
optsElem.style.height = `${height - (height * t)}px`;
|
||||
};
|
||||
end = () => {
|
||||
optsElem.style.height = '0';
|
||||
optsElem.classList.add('hidden');
|
||||
$rc(optsElem);
|
||||
};
|
||||
}
|
||||
|
||||
optionsAnim = MamiAnimate({
|
||||
async: true,
|
||||
delayed: true,
|
||||
duration: 500,
|
||||
easing: 'outExpo',
|
||||
start: start,
|
||||
update: update,
|
||||
end: end,
|
||||
});
|
||||
|
||||
optionsVisible = state;
|
||||
|
||||
return optionsAnim.start();
|
||||
};
|
||||
|
||||
const reloadOptions = async () => {
|
||||
for(const option of options.values()) {
|
||||
if(typeof option.info.condition === 'function' && !await option.info.condition(public)) {
|
||||
if(optsElem.contains(option.element))
|
||||
optsElem.removeChild(option.element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(option.element === undefined) {
|
||||
let text;
|
||||
if(typeof option.info.formatText === 'function')
|
||||
text = option.info.formatText(public);
|
||||
else
|
||||
text = option.info.text ?? option.info.name;
|
||||
|
||||
option.element = <div class="sidebar__user-option" onclick={ev => {
|
||||
if(typeof option.info.onclick === 'function')
|
||||
option.info.onclick(public, option.element, ev);
|
||||
}}>{text}</div>;
|
||||
}
|
||||
|
||||
if(!optsElem.contains(option.element))
|
||||
optsElem.appendChild(option.element);
|
||||
}
|
||||
};
|
||||
|
||||
const public = {
|
||||
get element() { return html; },
|
||||
|
||||
|
@ -56,10 +146,8 @@ const MamiSidebarPanelUploadsEntry = function(fileInfo) {
|
|||
progElem.value = Math.ceil(value * progElem.max);
|
||||
},
|
||||
|
||||
get optionsVisible() { return !optsElem.classList.contains('hidden'); },
|
||||
set optionsVisible(value) {
|
||||
optsElem.classList.toggle('hidden', !value);
|
||||
},
|
||||
get optionsVisible() { return optionsVisible; },
|
||||
setOptionsVisible: toggleOptions,
|
||||
|
||||
nukeProgress: () => {
|
||||
if(progElem === undefined)
|
||||
|
@ -71,42 +159,41 @@ const MamiSidebarPanelUploadsEntry = function(fileInfo) {
|
|||
|
||||
hasOption: name => options.has(name),
|
||||
getOptionNames: () => Array.from(options.keys()),
|
||||
addOption: option => {
|
||||
addOption: async option => {
|
||||
if(options.has(option.name))
|
||||
throw 'option has already been defined';
|
||||
|
||||
let text;
|
||||
if(typeof option.formatText === 'function')
|
||||
text = option.formatText(public);
|
||||
else
|
||||
text = option.text ?? option.name;
|
||||
|
||||
const elem = <div class="sidebar__user-option" onclick={ev => {
|
||||
if(typeof option.onclick === 'function')
|
||||
option.onclick(public, elem, ev);
|
||||
}}>{text}</div>;
|
||||
|
||||
options.set(option.name, {
|
||||
option: option,
|
||||
element: elem,
|
||||
info: option,
|
||||
element: undefined,
|
||||
});
|
||||
optsElem.appendChild(elem);
|
||||
|
||||
if(!optionsVisible)
|
||||
await reloadOptions();
|
||||
},
|
||||
removeOption: name => {
|
||||
removeOption: async name => {
|
||||
const info = options.get(name);
|
||||
if(info === undefined)
|
||||
return;
|
||||
|
||||
optsElem.removeChild(info.element);
|
||||
if(optsElem.contains(info.element))
|
||||
optsElem.removeChild(info.element);
|
||||
options.delete(name);
|
||||
|
||||
if(!optionsVisible)
|
||||
await reloadOptions();
|
||||
},
|
||||
clickOption: name => {
|
||||
const info = options.get(name) ?? options.get(`:${name}`);
|
||||
if(info === undefined)
|
||||
const option = options.get(name) ?? options.get(`:${name}`);
|
||||
if(option === undefined)
|
||||
return;
|
||||
|
||||
info.element.click();
|
||||
if(option.element !== undefined)
|
||||
option.element.click();
|
||||
else if(typeof option.info.onclick === 'function')
|
||||
option.info.onclick(public);
|
||||
},
|
||||
reloadOptions: reloadOptions,
|
||||
};
|
||||
|
||||
return public;
|
||||
|
@ -117,27 +204,24 @@ const MamiSidebarPanelUploads = function() {
|
|||
const options = new Map;
|
||||
const entries = [];
|
||||
|
||||
const reloadOptionsFor = entry => {
|
||||
const reloadOptionsFor = async entry => {
|
||||
const names = entry.getOptionNames();
|
||||
|
||||
for(const name of names)
|
||||
if(name.startsWith(':') && !options.has(name))
|
||||
entry.removeOption(name);
|
||||
await entry.removeOption(name);
|
||||
|
||||
for(const [name, option] of options) {
|
||||
if(typeof option.condition !== 'function' || option.condition(entry)) {
|
||||
if(!names.includes(name))
|
||||
entry.addOption(option);
|
||||
} else {
|
||||
if(names.includes(name))
|
||||
entry.removeOption(name);
|
||||
}
|
||||
}
|
||||
for(const [name, option] of options)
|
||||
if(!names.includes(name))
|
||||
await entry.addOption(option);
|
||||
|
||||
if(entry.optionsVisible)
|
||||
await entry.reloadOptions();
|
||||
};
|
||||
|
||||
const reloadOptions = () => {
|
||||
const reloadOptions = async () => {
|
||||
for(const entry of entries)
|
||||
reloadOptionsFor(entry);
|
||||
await reloadOptionsFor(entry);
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -163,20 +247,21 @@ const MamiSidebarPanelUploads = function() {
|
|||
if(!entries.includes(entry))
|
||||
return;
|
||||
|
||||
html.removeChild(entry.element);
|
||||
if(html.contains(entry.element))
|
||||
html.removeChild(entry.element);
|
||||
$ari(entries, entry);
|
||||
},
|
||||
|
||||
addOption: option => {
|
||||
addOption: async option => {
|
||||
if(!option.name.startsWith(':'))
|
||||
option.name = `:${option.name}`;
|
||||
if(options.has(option.name))
|
||||
throw 'option has already been defined';
|
||||
|
||||
options.set(option.name, option);
|
||||
reloadOptions();
|
||||
await reloadOptions();
|
||||
},
|
||||
removeOption: name => {
|
||||
removeOption: async name => {
|
||||
if(!name.startsWith(':'))
|
||||
name = `:${name}`;
|
||||
|
||||
|
@ -184,7 +269,7 @@ const MamiSidebarPanelUploads = function() {
|
|||
return;
|
||||
|
||||
options.delete(name);
|
||||
reloadOptions();
|
||||
await reloadOptions();
|
||||
},
|
||||
reloadOptions: reloadOptions,
|
||||
reloadOptionsFor: reloadOptionsFor,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#include animate.js
|
||||
#include users.js
|
||||
#include avatar.js
|
||||
#include utility.js
|
||||
|
||||
const MamiSidebarPanelUsersEntry = function(info) {
|
||||
|
@ -13,7 +13,7 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
const options = new Map;
|
||||
let avatarElem, nameElem, nameWrapperElem, statusElem, optsElem;
|
||||
const html = <div class="sidebar__user">
|
||||
<div class="sidebar__user-details" onclick={() => { setOptionsVisible(); }}>
|
||||
<div class="sidebar__user-details" onclick={() => { toggleOptions(); }}>
|
||||
{avatarElem = <div class="sidebar__user-avatar" />}
|
||||
{nameElem = <div class="sidebar__user-name"/>}
|
||||
</div>
|
||||
|
@ -63,7 +63,7 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
setStatusMessage(statusMessage);
|
||||
|
||||
let optionsVisible = false, optionsAnim, optionsTimeout;
|
||||
const setOptionsVisible = state => {
|
||||
const toggleOptions = async state => {
|
||||
if(state === undefined)
|
||||
state = !optionsVisible;
|
||||
else if(state === optionsVisible)
|
||||
|
@ -85,9 +85,11 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
if(mami.settings.get('autoCloseUserContext'))
|
||||
optionsTimeout = setTimeout(() => {
|
||||
if(mami.settings.get('autoCloseUserContext'))
|
||||
setOptionsVisible(false);
|
||||
toggleOptions(false);
|
||||
}, 300000);
|
||||
|
||||
await reloadOptions();
|
||||
|
||||
start = () => {
|
||||
optsElem.classList.remove('hidden');
|
||||
const curHeight = optsElem.style.height;
|
||||
|
@ -111,6 +113,7 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
end = () => {
|
||||
optsElem.style.height = '0';
|
||||
optsElem.classList.add('hidden');
|
||||
$rc(optsElem);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -129,10 +132,34 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
return optionsAnim.start();
|
||||
};
|
||||
|
||||
let avatar;
|
||||
const reloadOptions = async () => {
|
||||
for(const option of options.values()) {
|
||||
if(typeof option.info.condition === 'function' && !await option.info.condition(public)) {
|
||||
if(optsElem.contains(option.element))
|
||||
optsElem.removeChild(option.element);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(option.element === undefined) {
|
||||
let text;
|
||||
if(typeof option.info.formatText === 'function')
|
||||
text = option.info.formatText(public);
|
||||
else
|
||||
text = option.info.text ?? option.info.name;
|
||||
|
||||
option.element = <div class="sidebar__user-option" onclick={ev => {
|
||||
if(typeof option.info.onclick === 'function')
|
||||
option.info.onclick(public, option.element, ev);
|
||||
}}>{text}</div>;
|
||||
}
|
||||
|
||||
if(!optsElem.contains(option.element))
|
||||
optsElem.appendChild(option.element);
|
||||
}
|
||||
};
|
||||
|
||||
const updateAvatar = url => {
|
||||
avatar = new MamiUserAvatarInfo(id);
|
||||
avatarElem.style.backgroundImage = `url('${avatar.x60}')`;
|
||||
avatarElem.style.backgroundImage = `url('${MamiFormatUserAvatarUrl(id, 60)}')`;
|
||||
};
|
||||
updateAvatar();
|
||||
|
||||
|
@ -160,48 +187,47 @@ const MamiSidebarPanelUsersEntry = function(info) {
|
|||
set statusMessage(value) { setStatusMessage(value); },
|
||||
|
||||
get optionsVisible() { return optionsVisible; },
|
||||
setOptionsVisible: setOptionsVisible,
|
||||
setOptionsVisible: toggleOptions,
|
||||
|
||||
updateAvatar: updateAvatar,
|
||||
|
||||
hasOption: name => options.has(name),
|
||||
getOptionNames: () => Array.from(options.keys()),
|
||||
addOption: option => {
|
||||
addOption: async option => {
|
||||
if(options.has(option.name))
|
||||
throw 'option has already been defined';
|
||||
|
||||
let text;
|
||||
if(typeof option.formatText === 'function')
|
||||
text = option.formatText(public);
|
||||
else
|
||||
text = option.text ?? option.name;
|
||||
|
||||
const elem = <div class="sidebar__user-option" onclick={ev => {
|
||||
if(typeof option.onclick === 'function')
|
||||
option.onclick(public, elem, ev);
|
||||
}}>{text}</div>;
|
||||
|
||||
options.set(option.name, {
|
||||
option: option,
|
||||
element: elem,
|
||||
info: option,
|
||||
element: undefined,
|
||||
});
|
||||
optsElem.appendChild(elem);
|
||||
|
||||
if(!optionsVisible)
|
||||
await reloadOptions();
|
||||
},
|
||||
removeOption: name => {
|
||||
removeOption: async name => {
|
||||
const info = options.get(name);
|
||||
if(info === undefined)
|
||||
return;
|
||||
|
||||
optsElem.removeChild(info.element);
|
||||
if(optsElem.contains(info.element))
|
||||
optsElem.removeChild(info.element);
|
||||
options.delete(name);
|
||||
|
||||
if(!optionsVisible)
|
||||
await reloadOptions();
|
||||
},
|
||||
clickOption: name => {
|
||||
const info = options.get(name);
|
||||
if(info === undefined)
|
||||
const option = options.get(name) ?? options.get(`:${name}`);
|
||||
if(option === undefined)
|
||||
return;
|
||||
|
||||
info.element.click();
|
||||
if(option.element !== undefined)
|
||||
option.element.click();
|
||||
else if(typeof option.info.onclick === 'function')
|
||||
option.info.onclick(public);
|
||||
},
|
||||
reloadOptions: reloadOptions,
|
||||
};
|
||||
|
||||
return public;
|
||||
|
@ -212,27 +238,24 @@ const MamiSidebarPanelUsers = function() {
|
|||
const options = new Map;
|
||||
const entries = new Map;
|
||||
|
||||
const reloadOptionsFor = entry => {
|
||||
const reloadOptionsFor = async entry => {
|
||||
const names = entry.getOptionNames();
|
||||
|
||||
for(const name of names)
|
||||
if(name.startsWith(':') && !options.has(name))
|
||||
entry.removeOption(name);
|
||||
await entry.removeOption(name);
|
||||
|
||||
for(const [name, option] of options) {
|
||||
if(typeof option.condition !== 'function' || option.condition(entry)) {
|
||||
if(!names.includes(name))
|
||||
entry.addOption(option);
|
||||
} else {
|
||||
if(names.includes(name))
|
||||
entry.removeOption(name);
|
||||
}
|
||||
}
|
||||
for(const [name, option] of options)
|
||||
if(!names.includes(name))
|
||||
await entry.addOption(option);
|
||||
|
||||
if(entry.optionsVisible)
|
||||
await entry.reloadOptions();
|
||||
};
|
||||
|
||||
const reloadOptions = () => {
|
||||
const reloadOptions = async () => {
|
||||
for(const entry of entries)
|
||||
reloadOptionsFor(entry);
|
||||
await reloadOptionsFor(entry);
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -245,14 +268,6 @@ const MamiSidebarPanelUsers = function() {
|
|||
button.element.append(<i class="fas fa-users sidebar-gutter-font-icon"/>);
|
||||
},
|
||||
|
||||
// this method is cope (used for KFE exclusive please don't use it elsewhere cuz it will get nuked)
|
||||
getAllUserNames: () => {
|
||||
const names = [];
|
||||
for(const [id, entry] of entries)
|
||||
names.push(entry.name);
|
||||
return names;
|
||||
},
|
||||
|
||||
createEntry: info => {
|
||||
if(entries.has(info.id))
|
||||
throw 'this user has already been inserted';
|
||||
|
@ -301,16 +316,16 @@ const MamiSidebarPanelUsers = function() {
|
|||
}
|
||||
},
|
||||
|
||||
addOption: option => {
|
||||
addOption: async option => {
|
||||
if(!option.name.startsWith(':'))
|
||||
option.name = `:${option.name}`;
|
||||
if(options.has(option.name))
|
||||
throw 'option has already been defined';
|
||||
|
||||
options.set(option.name, option);
|
||||
reloadOptions();
|
||||
await reloadOptions();
|
||||
},
|
||||
removeOption: name => {
|
||||
removeOption: async name => {
|
||||
if(!name.startsWith(':'))
|
||||
name = `:${name}`;
|
||||
|
||||
|
@ -318,7 +333,7 @@ const MamiSidebarPanelUsers = function() {
|
|||
return;
|
||||
|
||||
options.delete(name);
|
||||
reloadOptions();
|
||||
await reloadOptions();
|
||||
},
|
||||
reloadOptions: reloadOptions,
|
||||
reloadOptionsFor: reloadOptionsFor,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include compat.js
|
||||
#include mszauth.js
|
||||
#include proto/sockchat/client.js
|
||||
|
||||
const MamiSockChat = function(eventTarget) {
|
||||
const MamiSockChat = function(protoWorker) {
|
||||
const events = protoWorker.eventTarget('sockchat');
|
||||
let restarting = false;
|
||||
let client;
|
||||
let dumpPackets = false;
|
||||
|
@ -10,8 +10,8 @@ const MamiSockChat = function(eventTarget) {
|
|||
return {
|
||||
get client() { return client; },
|
||||
|
||||
watch: eventTarget.watch,
|
||||
unwatch: eventTarget.unwatch,
|
||||
watch: events.watch,
|
||||
unwatch: events.unwatch,
|
||||
|
||||
create: async () => {
|
||||
if(client !== undefined && typeof client.close === 'function')
|
||||
|
@ -19,8 +19,8 @@ const MamiSockChat = function(eventTarget) {
|
|||
client.close();
|
||||
|
||||
restarting = false;
|
||||
client = new SockChatClient(eventTarget.dispatch, { ping: futami.get('ping') });
|
||||
client.setDumpPackets(dumpPackets);
|
||||
client = await protoWorker.root.create('sockchat', { ping: futami.get('ping') });
|
||||
await client.setDumpPackets(dumpPackets);
|
||||
|
||||
MamiCompat('Umi.Server', { get: () => client, configurable: true });
|
||||
MamiCompat('Umi.Server.SendMessage', { value: text => client.sendMessage(text), configurable: true });
|
||||
|
@ -38,11 +38,11 @@ const MamiSockChat = function(eventTarget) {
|
|||
const authInfo = MamiMisuzuAuth.getInfo();
|
||||
await client.sendAuth(authInfo.method, authInfo.token);
|
||||
},
|
||||
setDumpPackets: state => {
|
||||
setDumpPackets: async state => {
|
||||
dumpPackets = !!state;
|
||||
|
||||
if(client !== undefined && typeof client.setDumpPackets === 'function')
|
||||
client.setDumpPackets(dumpPackets);
|
||||
await client.setDumpPackets(dumpPackets);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
#include animate.js
|
||||
#include messages.js
|
||||
#include parsing.js
|
||||
#include users.js
|
||||
#include notices/baka.jsx
|
||||
#include sockchat/modal.js
|
||||
#include ui/emotes.js
|
||||
|
@ -9,7 +7,7 @@
|
|||
|
||||
const MamiSockChatHandlers = function(
|
||||
ctx, client, setLoadingOverlay, sockChatReconnect, pingIndicator,
|
||||
sbActPing, sbChannels, sbUsers
|
||||
sbActPing, sbChannels, sbUsers, messages
|
||||
) {
|
||||
if(typeof ctx !== 'object' || ctx === null)
|
||||
throw 'ctx must be an non-null object';
|
||||
|
@ -86,24 +84,7 @@ const MamiSockChatHandlers = function(
|
|||
if(dumpEvents) console.log('session:start', ev.detail);
|
||||
|
||||
sockChatRestarting = false;
|
||||
|
||||
const userInfo = new MamiUserInfo(
|
||||
ev.detail.user.id,
|
||||
ev.detail.user.name,
|
||||
ev.detail.user.colour,
|
||||
new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
|
||||
new MamiUserPermsInfo(
|
||||
ev.detail.user.perms.rank, ev.detail.user.perms.kick,
|
||||
ev.detail.user.perms.nick, ev.detail.user.perms.chan,
|
||||
),
|
||||
);
|
||||
Umi.User.setCurrentUser(userInfo);
|
||||
Umi.Users.Add(userInfo);
|
||||
|
||||
if(sbUsers.hasEntry(ev.detail.user.id))
|
||||
sbUsers.updateEntry(ev.detail.user.id, ev.detail.user);
|
||||
else
|
||||
sbUsers.createEntry(ev.detail.user);
|
||||
sbUsers.createEntry(ev.detail.user);
|
||||
|
||||
if(ctx.views.count > 1)
|
||||
ctx.views.pop();
|
||||
|
@ -136,76 +117,32 @@ const MamiSockChatHandlers = function(
|
|||
if(ev.detail.user.self)
|
||||
return;
|
||||
|
||||
const userInfo = new MamiUserInfo(
|
||||
ev.detail.user.id,
|
||||
ev.detail.user.name,
|
||||
ev.detail.user.colour,
|
||||
new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
|
||||
new MamiUserPermsInfo(
|
||||
ev.detail.user.perms.rank, ev.detail.user.perms.kick,
|
||||
ev.detail.user.perms.nick, ev.detail.user.perms.chan,
|
||||
),
|
||||
);
|
||||
Umi.Users.Add(userInfo);
|
||||
|
||||
sbUsers.createEntry(ev.detail.user);
|
||||
|
||||
if(ev.detail.msg !== undefined)
|
||||
Umi.UI.Messages.Add(new MamiMessageInfo(
|
||||
'user:join',
|
||||
ev.detail.msg.time,
|
||||
null,
|
||||
ev.detail.msg.id,
|
||||
new MamiMessageAuthorInfo(ev.detail.user.self, userInfo),
|
||||
ev.detail.msg.channel
|
||||
));
|
||||
if(ev.detail.msg !== undefined) {
|
||||
messages.addMessage(ev.detail.msg);
|
||||
Umi.UI.Messages.Add(ev.detail.msg);
|
||||
}
|
||||
};
|
||||
handlers['user:remove'] = ev => {
|
||||
if(dumpEvents) console.log('user:remove', ev.detail);
|
||||
|
||||
sbUsers.deleteEntry(ev.detail.user.id);
|
||||
|
||||
const userInfo = Umi.Users.Get(ev.detail.user.id);
|
||||
if(userInfo === null)
|
||||
return;
|
||||
|
||||
if(ev.detail.msg !== undefined)
|
||||
Umi.UI.Messages.Add(new MamiMessageInfo(
|
||||
'user:leave',
|
||||
ev.detail.msg.time,
|
||||
{ reason: ev.detail.leave.type },
|
||||
ev.detail.msg.id,
|
||||
new MamiMessageAuthorInfo(ev.detail.user.self, userInfo),
|
||||
ev.detail.msg.channel
|
||||
));
|
||||
|
||||
Umi.Users.Remove(userInfo);
|
||||
if(ev.detail.msg !== undefined) {
|
||||
messages.addMessage(ev.detail.msg);
|
||||
Umi.UI.Messages.Add(ev.detail.msg);
|
||||
}
|
||||
};
|
||||
handlers['user:update'] = ev => {
|
||||
if(dumpEvents) console.log('user:update', ev.detail);
|
||||
|
||||
sbUsers.updateEntry(ev.detail.user.id, ev.detail.user);
|
||||
|
||||
const userInfo = Umi.Users.Get(ev.detail.user.id);
|
||||
userInfo.name = ev.detail.user.name;
|
||||
userInfo.colour = ev.detail.user.colour;
|
||||
userInfo.avatar = new MamiUserAvatarInfo(ev.detail.user.id);
|
||||
userInfo.status = new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message);
|
||||
userInfo.perms = new MamiUserPermsInfo(
|
||||
ev.detail.user.perms.rank, ev.detail.user.perms.kick,
|
||||
ev.detail.user.perms.nick, ev.detail.user.perms.chan,
|
||||
);
|
||||
Umi.Users.Update(userInfo.id, userInfo);
|
||||
};
|
||||
handlers['user:clear'] = () => {
|
||||
if(dumpEvents) console.log('user:clear');
|
||||
|
||||
sbUsers.clearEntries();
|
||||
|
||||
const self = Umi.User.getCurrentUser();
|
||||
Umi.Users.Clear();
|
||||
if(self !== undefined)
|
||||
Umi.Users.Add(self);
|
||||
};
|
||||
|
||||
|
||||
|
@ -233,33 +170,18 @@ const MamiSockChatHandlers = function(
|
|||
if(dumpEvents) console.log('chan:focus', ev.detail);
|
||||
|
||||
sbChannels.setActiveEntry(ev.detail.channel.name);
|
||||
messages.switchChannel(ev.detail.channel.name);
|
||||
Umi.UI.Messages.SwitchChannel(ev.detail.channel);
|
||||
};
|
||||
handlers['chan:join'] = ev => {
|
||||
if(dumpEvents) console.log('chan:join', ev.detail);
|
||||
|
||||
const userInfo = new MamiUserInfo(
|
||||
ev.detail.user.id,
|
||||
ev.detail.user.name,
|
||||
ev.detail.user.colour,
|
||||
new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
|
||||
new MamiUserPermsInfo(
|
||||
ev.detail.user.perms.rank, ev.detail.user.perms.kick,
|
||||
ev.detail.user.perms.nick, ev.detail.user.perms.chan,
|
||||
)
|
||||
);
|
||||
Umi.Users.Add(userInfo);
|
||||
|
||||
sbUsers.createEntry(ev.detail.user);
|
||||
|
||||
if(ev.detail.msg !== undefined)
|
||||
Umi.UI.Messages.Add(new MamiMessageInfo(
|
||||
'channel:join',
|
||||
null, null,
|
||||
ev.detail.msg.id,
|
||||
new MamiMessageAuthorInfo(ev.detail.user.self, userInfo),
|
||||
ev.detail.msg.channel
|
||||
));
|
||||
if(ev.detail.msg !== undefined) {
|
||||
messages.addMessage(ev.detail.msg);
|
||||
Umi.UI.Messages.Add(ev.detail.msg);
|
||||
}
|
||||
};
|
||||
handlers['chan:leave'] = ev => {
|
||||
if(dumpEvents) console.log('chan:leave', ev.detail);
|
||||
|
@ -267,135 +189,29 @@ const MamiSockChatHandlers = function(
|
|||
if(ev.detail.user.self)
|
||||
return;
|
||||
|
||||
const userInfo = Umi.Users.Get(ev.detail.user.id);
|
||||
if(userInfo === null)
|
||||
return;
|
||||
|
||||
if(ev.detail.msg !== undefined)
|
||||
Umi.UI.Messages.Add(new MamiMessageInfo(
|
||||
'channel:leave',
|
||||
null, null,
|
||||
ev.detail.msg.id,
|
||||
new MamiMessageAuthorInfo(ev.detail.user.self, userInfo),
|
||||
ev.detail.msg.channel
|
||||
));
|
||||
|
||||
Umi.Users.Remove(userInfo);
|
||||
if(ev.detail.msg !== undefined) {
|
||||
messages.addMessage(ev.detail.msg);
|
||||
Umi.UI.Messages.Add(ev.detail.msg);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
handlers['msg:add'] = ev => {
|
||||
if(dumpEvents) console.log('msg:add', ev.detail);
|
||||
|
||||
const senderInfo = ev.detail.msg.sender;
|
||||
const rawUserInfo = Umi.Users.Get(senderInfo.id);
|
||||
const userInfo = senderInfo.name === undefined
|
||||
? rawUserInfo
|
||||
: new MamiUserInfo(
|
||||
senderInfo.id,
|
||||
senderInfo.name,
|
||||
senderInfo.colour,
|
||||
new MamiUserStatusInfo(senderInfo.status.isAway, senderInfo.status.message),
|
||||
new MamiUserPermsInfo(
|
||||
senderInfo.perms.rank, senderInfo.perms.kick,
|
||||
senderInfo.perms.nick, senderInfo.perms.chan,
|
||||
)
|
||||
);
|
||||
|
||||
// hack
|
||||
let channelName = ev.detail.msg.channel;
|
||||
if(channelName !== undefined && channelName.startsWith('@~')) {
|
||||
const chanUserInfo = Umi.Users.Get(channelName.substring(2));
|
||||
if(chanUserInfo !== null)
|
||||
channelName = `@${chanUserInfo.name}`;
|
||||
if(ev.detail.msg.type.startsWith('legacy:') && modals.handled(ev.detail.msg.botInfo.type)) {
|
||||
modals.show(ev.detail.msg.botInfo.type, ev.detail.msg.botInfo.args);
|
||||
return;
|
||||
}
|
||||
|
||||
// also hack
|
||||
if(ev.detail.msg.flags.isPM && !sbChannels.hasEntry(channelName))
|
||||
sbChannels.createEntry({
|
||||
name: channelName,
|
||||
hasPassword: false,
|
||||
isTemporary: true,
|
||||
isUserChannel: true,
|
||||
});
|
||||
|
||||
let type, detail, author;
|
||||
if(ev.detail.msg.isBot) {
|
||||
const botInfo = ev.detail.msg.botInfo;
|
||||
let authorMethod;
|
||||
|
||||
if(botInfo.type === 'join') {
|
||||
type = 'user:join';
|
||||
authorMethod = 'nameArg';
|
||||
} else if(['leave', 'kick', 'flood', 'timeout'].includes(botInfo.type)) {
|
||||
type = 'user:leave';
|
||||
authorMethod = 'nameArg';
|
||||
detail = { reason: botInfo.type };
|
||||
} else if(botInfo.type === 'jchan') {
|
||||
type = 'channel:join';
|
||||
authorMethod = 'nameArg';
|
||||
} else if(botInfo.type === 'lchan') {
|
||||
type = 'channel:leave';
|
||||
authorMethod = 'nameArg';
|
||||
}
|
||||
|
||||
if(authorMethod === 'nameArg') {
|
||||
author = botInfo.args[0];
|
||||
authorMethod = 'name';
|
||||
}
|
||||
|
||||
if(authorMethod === 'name') {
|
||||
const botUserInfo = Umi.Users.FindExact(author);
|
||||
author = new MamiMessageAuthorInfo(
|
||||
Umi.User.isCurrentUser(botUserInfo),
|
||||
botUserInfo,
|
||||
null,
|
||||
author
|
||||
);
|
||||
}
|
||||
|
||||
if(typeof type !== 'string') {
|
||||
if(modals.handled(botInfo.type)) {
|
||||
modals.show(botInfo.type, botInfo.args);
|
||||
return;
|
||||
}
|
||||
|
||||
type = `legacy:${botInfo.type}`;
|
||||
detail = {
|
||||
error: botInfo.isError,
|
||||
args: botInfo.args,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
author = new MamiMessageAuthorInfo(
|
||||
senderInfo.self,
|
||||
rawUserInfo,
|
||||
senderInfo.id ?? rawUserInfo?.id,
|
||||
senderInfo.name ?? rawUserInfo?.name,
|
||||
senderInfo.colour ?? rawUserInfo?.colour,
|
||||
senderInfo.perms?.rank ?? rawUserInfo?.perms?.rank ?? 0,
|
||||
senderInfo.avatar ?? rawUserInfo?.avatar ?? new MamiUserAvatarInfo(senderInfo.id ?? rawUserInfo?.id ?? '0'),
|
||||
);
|
||||
|
||||
type = `message:${ev.detail.msg.flags.isAction ? 'action' : 'text'}`;
|
||||
detail = { body: ev.detail.msg.text };
|
||||
}
|
||||
|
||||
sbChannels.setUnreadEntry(channelName);
|
||||
|
||||
Umi.UI.Messages.Add(new MamiMessageInfo(
|
||||
type,
|
||||
ev.detail.msg.time,
|
||||
detail,
|
||||
ev.detail.msg.id,
|
||||
author,
|
||||
channelName,
|
||||
ev.detail.msg.silent,
|
||||
));
|
||||
sbChannels.setUnreadEntry(ev.detail.msg.channel);
|
||||
messages.addMessage(ev.detail.msg);
|
||||
Umi.UI.Messages.Add(ev.detail.msg);
|
||||
};
|
||||
handlers['msg:remove'] = ev => {
|
||||
if(dumpEvents) console.log('msg:remove', ev.detail);
|
||||
|
||||
messages.removeMessage(ev.detail.msg.id);
|
||||
Umi.UI.Messages.Remove(ev.detail.msg.id);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include utility.js
|
||||
#include ui/chat-message-list.js
|
||||
|
||||
Umi.UI.ChatInterface = function(chatForm) {
|
||||
const messages = new Umi.UI.ChatMessageList;
|
||||
Umi.UI.ChatInterface = function(messages, chatForm) {
|
||||
const messagesOld = new Umi.UI.ChatMessageList;
|
||||
|
||||
const html = $e({
|
||||
attrs: {
|
||||
|
@ -10,13 +10,14 @@ Umi.UI.ChatInterface = function(chatForm) {
|
|||
},
|
||||
child: [
|
||||
messages,
|
||||
messagesOld,
|
||||
chatForm,
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
getMessageList: function() {
|
||||
return messages;
|
||||
return messagesOld;
|
||||
},
|
||||
getElement: function() {
|
||||
return html;
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
#include ui/chat-interface.js
|
||||
|
||||
// this needs revising at some point but will suffice for now
|
||||
Umi.UI.ChatLayout = function(chatForm, sideBar) {
|
||||
const main = new Umi.UI.ChatInterface(chatForm);
|
||||
Umi.UI.ChatLayout = function(messages, chatForm, sideBar) {
|
||||
const main = new Umi.UI.ChatInterface(messages, chatForm);
|
||||
|
||||
const html = $e({
|
||||
attrs: {
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
#include avatar.js
|
||||
#include common.js
|
||||
#include parsing.js
|
||||
#include title.js
|
||||
#include txtrigs.js
|
||||
#include url.js
|
||||
#include users.js
|
||||
#include utility.js
|
||||
#include weeb.js
|
||||
#include sound/umisound.js
|
||||
#include ui/emotes.js
|
||||
|
||||
Umi.UI.Messages = (function() {
|
||||
let focusChannelName = '';
|
||||
|
@ -29,113 +25,35 @@ Umi.UI.Messages = (function() {
|
|||
};
|
||||
|
||||
const botMsgs = {
|
||||
'say': { text: '%0' },
|
||||
'join': { text: '%0 has joined.', action: 'has joined', sound: 'join' },
|
||||
'leave': { text: '%0 has disconnected.', action: 'has disconnected', avatar: 'greyscale', sound: 'leave' },
|
||||
'jchan': { text: '%0 has joined the channel.', action: 'has joined the channel', sound: 'join' },
|
||||
'lchan': { text: '%0 has left the channel.', action: 'has left the channel', avatar: 'greyscale', sound: 'leave' },
|
||||
'kick': { text: '%0 got bludgeoned to death.', action: 'got bludgeoned to death', avatar: 'invert', sound: 'kick' },
|
||||
'flood': { text: '%0 got kicked for flood protection.', action: 'got kicked for flood protection', avatar: 'invert', sound: 'flood' },
|
||||
'timeout': { text: '%0 exploded.', action: 'exploded', avatar: 'greyscale', sound: 'timeout' },
|
||||
'nick': { text: '%0 changed their name to %1.', action: 'changed their name to %1' },
|
||||
'ipaddr': { text: 'IP address of %0 is %1.' },
|
||||
'banlist': {
|
||||
text: 'Banned: %0',
|
||||
filter: args => {
|
||||
const bans = args[0].split(', ');
|
||||
for(const i in bans)
|
||||
bans[i] = bans[i].slice(92, -4);
|
||||
|
||||
args[0] = bans.join(', ');
|
||||
return args;
|
||||
},
|
||||
},
|
||||
'who': {
|
||||
text: 'Online: %0',
|
||||
filter: args => {
|
||||
const users = args[0].split(', ');
|
||||
for(const i in users) {
|
||||
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
||||
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
||||
if(isSelf) users[i] += ' (You)';
|
||||
}
|
||||
|
||||
args[0] = users.join(', ');
|
||||
return args;
|
||||
},
|
||||
},
|
||||
'whochan': {
|
||||
text: 'Online in %0: %1',
|
||||
filter: args => {
|
||||
const users = args[1].split(', ');
|
||||
for(const i in users) {
|
||||
const isSelf = users[i].includes(' style="font-weight: bold;"');
|
||||
users[i] = users[i].slice(isSelf ? 102 : 75, -4);
|
||||
if(isSelf) users[i] += ' (You)';
|
||||
}
|
||||
|
||||
args[1] = users.join(', ');
|
||||
return args;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const formatTemplate = (template, args) => {
|
||||
if(typeof template !== 'string')
|
||||
template = '';
|
||||
|
||||
if(Array.isArray(args))
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
const arg = args[i] === undefined || args[i] === null ? '' : args[i].toString();
|
||||
template = template.replace(new RegExp(`%${i}`, 'g'), arg);
|
||||
}
|
||||
|
||||
return template;
|
||||
'join': { sound: 'join' },
|
||||
'leave': { sound: 'leave' },
|
||||
'jchan': { sound: 'join' },
|
||||
'lchan': { sound: 'leave' },
|
||||
'kick': { sound: 'kick' },
|
||||
'flood': { sound: 'flood' },
|
||||
'timeout': { sound: 'timeout' },
|
||||
};
|
||||
|
||||
return {
|
||||
Add: function(msg) {
|
||||
Add: async function(msg) {
|
||||
const elementId = `message-${msg.id}`;
|
||||
|
||||
if(msg.id !== '' && $i(elementId))
|
||||
return;
|
||||
|
||||
let isTiny = false;
|
||||
let skipTextParsing = false;
|
||||
let msgText = '';
|
||||
let msgTextLong = '';
|
||||
let msgAuthor = msg.author;
|
||||
|
||||
let soundIsLegacy = true;
|
||||
let soundName;
|
||||
let soundVolume;
|
||||
let soundRate;
|
||||
|
||||
const eText = <div/>;
|
||||
const eAvatar = <div class="message__avatar"/>;
|
||||
const eUser = <div class="message__user"/>;
|
||||
const eMeta = <div class="message__meta">{eUser}</div>;
|
||||
const eTime = <div class="message__time">
|
||||
{msg.created.getHours().toString().padStart(2, '0')}
|
||||
:{msg.created.getMinutes().toString().padStart(2, '0')}
|
||||
:{msg.created.getSeconds().toString().padStart(2, '0')}
|
||||
</div>;
|
||||
const eContainer = <div class="message__container">{eMeta}</div>;
|
||||
const eBase = <div class="message">
|
||||
{eAvatar}
|
||||
{eContainer}
|
||||
</div>;
|
||||
const eBase = <div class="message">{msg.id ?? 'no id'}</div>;
|
||||
|
||||
if(msg.type.startsWith('message:')) {
|
||||
msgText = msgTextLong = msg.detail.body;
|
||||
if(msg.type.startsWith('msg:')) {
|
||||
soundName = msg.author?.self === true ? 'outgoing' : 'incoming';
|
||||
|
||||
if(msg.type === 'message:action')
|
||||
isTiny = true;
|
||||
|
||||
if(mami.settings.get('playJokeSounds'))
|
||||
try {
|
||||
const trigger = mami.textTriggers.getTrigger(msgText);
|
||||
const trigger = mami.textTriggers.getTrigger(msg.text);
|
||||
if(trigger.isSoundType) {
|
||||
soundIsLegacy = false;
|
||||
soundName = trigger.getRandomSoundName();
|
||||
|
@ -146,128 +64,43 @@ Umi.UI.Messages = (function() {
|
|||
} else {
|
||||
let bIsError = false;
|
||||
let bType;
|
||||
let bArgs;
|
||||
|
||||
if(msg.type === 'user:join') {
|
||||
if(msg.type === 'user:add') {
|
||||
bType = 'join';
|
||||
bArgs = [msgAuthor.name];
|
||||
} else if(msg.type === 'user:leave') {
|
||||
} else if(msg.type === 'user:remove') {
|
||||
bType = msg.detail.reason;
|
||||
bArgs = [msgAuthor.name];
|
||||
} else if(msg.type === 'channel:join') {
|
||||
} else if(msg.type === 'chan:join') {
|
||||
bType = 'jchan';
|
||||
bArgs = [msgAuthor.name];
|
||||
} else if(msg.type === 'channel:leave') {
|
||||
} else if(msg.type === 'chan:leave') {
|
||||
bType = 'lchan';
|
||||
bArgs = [msgAuthor.name];
|
||||
} else if(msg.type.startsWith('legacy:')) {
|
||||
bType = msg.type.substring(7);
|
||||
bIsError = msg.detail.error;
|
||||
bArgs = msg.detail.args;
|
||||
bType = msg.botInfo.type;
|
||||
bIsError = msg.botInfo.isError;
|
||||
}
|
||||
|
||||
soundName = bIsError ? 'error' : 'server';
|
||||
|
||||
if(botMsgs.hasOwnProperty(bType)) {
|
||||
const bmInfo = botMsgs[bType];
|
||||
|
||||
if(typeof bmInfo.filter === 'function')
|
||||
bArgs = bmInfo.filter(bArgs);
|
||||
|
||||
if(typeof bmInfo.sound === 'string')
|
||||
soundName = bmInfo.sound;
|
||||
|
||||
let actionSuccess = false;
|
||||
if(typeof bmInfo.action === 'string')
|
||||
if(msgAuthor) {
|
||||
actionSuccess = true;
|
||||
isTiny = true;
|
||||
skipTextParsing = true;
|
||||
|
||||
msgText = formatTemplate(bmInfo.action, bArgs);
|
||||
if(typeof bmInfo.avatar === 'string')
|
||||
eAvatar.classList.add(`avatar-filter-${bmInfo.avatar}`);
|
||||
}
|
||||
|
||||
msgTextLong = formatTemplate(bmInfo.text, bArgs);
|
||||
|
||||
if(!actionSuccess)
|
||||
msgText = msgTextLong;
|
||||
} else
|
||||
msgText = msgTextLong = `!!! Received unsupported message type: ${msg.type} !!!`;
|
||||
}
|
||||
}
|
||||
|
||||
if(msgAuthor !== null) {
|
||||
eUser.style.color = msgAuthor.colour;
|
||||
eUser.textContent = msgAuthor.name;
|
||||
}
|
||||
|
||||
if(isTiny) {
|
||||
eText.classList.add('message-tiny-text');
|
||||
eBase.classList.add('message-tiny');
|
||||
|
||||
if(msgText.indexOf("'") !== 0 || (msgText.match(/\'/g).length % 2) === 0)
|
||||
msgText = "\xA0" + msgText;
|
||||
|
||||
eMeta.append(eText, eTime);
|
||||
} else {
|
||||
eText.classList.add('message__text');
|
||||
eMeta.append(eTime);
|
||||
eContainer.append(eText);
|
||||
|
||||
if(msgAuthor?.id === '136')
|
||||
eBase.style.transform = 'scaleY(' + (0.76 + (0.01 * Math.max(0, Math.ceil(Date.now() / (7 * 24 * 60 * 60000)) - 2813))).toString() + ')';
|
||||
}
|
||||
|
||||
eBase.classList.add(`message--user-${msgAuthor?.id ?? '-1'}`);
|
||||
|
||||
if(focusChannelName !== '' && msg.channel !== '' && msg.channel !== focusChannelName)
|
||||
if(focusChannelName !== '' && msg.channel && msg.channel !== focusChannelName)
|
||||
eBase.classList.add('hidden');
|
||||
|
||||
if(msg.id !== '') {
|
||||
if(msg.id) {
|
||||
eBase.id = elementId;
|
||||
eBase.dataset.id = msg.id;
|
||||
}
|
||||
if(msgAuthor)
|
||||
eBase.dataset.author = msgAuthor.id;
|
||||
if(msg.channel !== '')
|
||||
if(msg.sender)
|
||||
eBase.dataset.author = msg.sender.id;
|
||||
if(msg.channel)
|
||||
eBase.dataset.channel = msg.channel;
|
||||
if(isTiny)
|
||||
eBase.dataset.tiny = '1';
|
||||
eBase.dataset.created = msg.created.toISOString();
|
||||
eBase.dataset.created = msg.time.toISOString();
|
||||
|
||||
eBase.dataset.body = msgText;
|
||||
eText.innerText = msgText;
|
||||
|
||||
if(!skipTextParsing) {
|
||||
Umi.UI.Emoticons.Parse(eText, msgAuthor);
|
||||
Umi.Parsing.Parse(eText, msg);
|
||||
|
||||
const textSplit = eText.innerText.split(' ');
|
||||
for(const textPart of textSplit) {
|
||||
const uri = Umi.URI.Parse(textPart);
|
||||
|
||||
if(uri !== null && uri.Slashes !== null) {
|
||||
const anchorElem = <a class="markup__link" href={textPart} target="_blank" rel="nofollow noreferrer noopener">{textPart}</a>;
|
||||
eText.innerHTML = eText.innerHTML.replace(textPart.replace(/&/g, '&'), anchorElem.outerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
if(mami.settings.get('weeaboo')) {
|
||||
eUser.appendChild($t(Weeaboo.getNameSuffix(msgAuthor)));
|
||||
eText.appendChild($t(Weeaboo.getTextSuffix(msgAuthor)));
|
||||
|
||||
const kaomoji = Weeaboo.getRandomKaomoji(true, msg);
|
||||
if(kaomoji)
|
||||
eText.append(` ${kaomoji}`);
|
||||
}
|
||||
}
|
||||
|
||||
const avatarUrl = msgAuthor?.avatar?.[isTiny ? 'x40' : 'x80'] ?? '';
|
||||
if(avatarUrl.length > 0)
|
||||
eAvatar.style.backgroundImage = `url('${avatarUrl}')`;
|
||||
else
|
||||
eAvatar.classList.add('message__avatar--disabled');
|
||||
eBase.dataset.body = msg.text;
|
||||
|
||||
const msgsList = $i('umi-messages');
|
||||
|
||||
|
@ -282,38 +115,22 @@ Umi.UI.Messages = (function() {
|
|||
|
||||
eBase.classList.toggle('message--first', shouldDisplayAuthorInfo(eBase, insertAfter));
|
||||
|
||||
if(eBase.dataset.tiny !== insertAfter.dataset.tiny)
|
||||
eBase.classList.add(isTiny ? 'message-tiny-fix' : 'message-big-fix');
|
||||
|
||||
insertAfter.after(eBase);
|
||||
|
||||
if(eBase.nextElementSibling instanceof Element)
|
||||
eBase.nextElementSibling.classList.toggle('message--first', shouldDisplayAuthorInfo(eBase.nextElementSibling, eBase));
|
||||
} else {
|
||||
eBase.classList.add('message--first');
|
||||
if(isTiny) eBase.classList.add('message-tiny-fix');
|
||||
msgsList.append(eBase);
|
||||
}
|
||||
|
||||
if(!eBase.classList.contains('hidden')) {
|
||||
if(mami.settings.get('autoEmbedV1')) {
|
||||
const callEmbedOn = eBase.querySelectorAll('a[onclick^="Umi.Parser.SockChatBBcode.Embed"]');
|
||||
for(const embedElem of callEmbedOn)
|
||||
if(embedElem.dataset.embed !== '1')
|
||||
embedElem.click();
|
||||
}
|
||||
|
||||
if(mami.settings.get('autoScroll'))
|
||||
eBase.scrollIntoView({ inline: 'end' });
|
||||
}
|
||||
|
||||
let isMentioned = false;
|
||||
const mentionTriggers = mami.settings.get('notificationTriggers').toLowerCase().split(' ');
|
||||
const currentUser = Umi.User.getCurrentUser();
|
||||
const currentUser = await Umi.Server.getCurrentUserInfo();
|
||||
if(typeof currentUser === 'object' && typeof currentUser.name === 'string')
|
||||
mentionTriggers.push(currentUser.name.toLowerCase());
|
||||
|
||||
const mentionText = ` ${msgTextLong} `.toLowerCase();
|
||||
const mentionText = ` ${msg.text} `.toLowerCase();
|
||||
for(const trigger of mentionTriggers) {
|
||||
if(trigger.trim() === '')
|
||||
continue;
|
||||
|
@ -329,7 +146,7 @@ Umi.UI.Messages = (function() {
|
|||
|
||||
if(document.hidden) {
|
||||
if(mami.settings.get('flashTitle')) {
|
||||
let titleText = msgAuthor?.name ?? msgTextLong;
|
||||
let titleText = msg.sender?.name ?? msg.text;
|
||||
if(focusChannelName !== '' && focusChannelName !== msg.channel)
|
||||
titleText += ` @ ${msg.channel}`;
|
||||
|
||||
|
@ -343,14 +160,12 @@ Umi.UI.Messages = (function() {
|
|||
if(mami.settings.get('enableNotifications') && isMentioned) {
|
||||
const options = {};
|
||||
|
||||
options.icon = MamiFormatUserAvatarUrl(msg.sender.id, 100);
|
||||
options.body = 'Click here to see what they said.';
|
||||
if(mami.settings.get('notificationShowMessage'))
|
||||
options.body += "\n" + msgTextLong;
|
||||
options.body += `\n${msg.text}`;
|
||||
|
||||
if(avatarUrl.length > 0)
|
||||
options.icon = avatarUrl;
|
||||
|
||||
const notif = new Notification(`${msgAuthor.name} mentioned you!`, options);
|
||||
const notif = new Notification(`${msg.sender.name} mentioned you!`, options);
|
||||
notif.addEventListener('click', () => {
|
||||
window.focus();
|
||||
});
|
||||
|
@ -368,12 +183,6 @@ Umi.UI.Messages = (function() {
|
|||
|
||||
mami.sound.library.play(soundName, soundVolume, soundRate);
|
||||
}
|
||||
|
||||
mami.globalEvents.dispatch('umi:ui:message_add', { element: eBase });
|
||||
},
|
||||
IsScrolledToBottom: () => {
|
||||
const msgsList = $i('umi-messages');
|
||||
return msgsList.scrollTop === (msgsList.scrollHeight - msgsList.offsetHeight);
|
||||
},
|
||||
ScrollIfNeeded: (offsetOrForce = 0) => {
|
||||
const msgsList = $i('umi-messages');
|
||||
|
|
|
@ -1,183 +0,0 @@
|
|||
const MamiUserPermsInfo = function(rank = 0, canKick = false, canSetNick = false, canCreateChannels = false) {
|
||||
if(typeof rank !== 'number')
|
||||
throw 'rank must be a number';
|
||||
if(typeof canKick !== 'boolean')
|
||||
throw 'canKick must be a boolean';
|
||||
if(typeof canSetNick !== 'boolean')
|
||||
throw 'canSetNick must be a boolean';
|
||||
if(typeof canCreateChannels !== 'boolean')
|
||||
throw 'canCreateChannels must be a boolean';
|
||||
|
||||
return {
|
||||
get rank() { return rank; },
|
||||
get canKick() { return canKick; },
|
||||
get canSetNick() { return canSetNick; },
|
||||
get canCreateChannels() { return canCreateChannels; },
|
||||
};
|
||||
};
|
||||
|
||||
const MamiUserStatusInfo = function(isAway = false, message = '') {
|
||||
if(typeof isAway !== 'boolean')
|
||||
throw 'isAway must be a boolean';
|
||||
if(typeof message !== 'string')
|
||||
throw 'message must be a string';
|
||||
|
||||
return {
|
||||
get isAway() { return isAway; },
|
||||
get message() { return message; },
|
||||
};
|
||||
};
|
||||
|
||||
const MamiUserAvatarInfo = function(userId = null) {
|
||||
userId ??= '';
|
||||
if(typeof userId !== 'string')
|
||||
throw 'userId must be a string or null';
|
||||
|
||||
const template = futami.get('avatar') ?? '';
|
||||
const changeTime = Date.now();
|
||||
|
||||
const getAvatar = res => {
|
||||
return template.replace('{user:id}', userId)
|
||||
.replace('{resolution}', res)
|
||||
.replace('{user:avatar_change}', changeTime);
|
||||
};
|
||||
|
||||
return {
|
||||
get original() { return getAvatar('0'); },
|
||||
get x80() { return getAvatar('80'); },
|
||||
get x60() { return getAvatar('60'); },
|
||||
get x40() { return getAvatar('40'); },
|
||||
};
|
||||
};
|
||||
|
||||
const MamiUserInfo = function(id, name, colour = 'inherit', status = null, perms = null, avatar = null) {
|
||||
if(typeof id !== 'string')
|
||||
throw 'id must be a string';
|
||||
if(typeof name !== 'string')
|
||||
throw 'name must be a string';
|
||||
if(typeof colour !== 'string') // should be like, object or something maybe
|
||||
throw 'colour must be a string';
|
||||
if(status === null)
|
||||
status = new MamiUserStatusInfo;
|
||||
else if(typeof status !== 'object')
|
||||
throw 'status must be an object';
|
||||
if(perms === null)
|
||||
perms = new MamiUserPermsInfo;
|
||||
else if(typeof perms !== 'object')
|
||||
throw 'perms must be an object';
|
||||
if(avatar === null)
|
||||
avatar = new MamiUserAvatarInfo(id);
|
||||
else if(typeof avatar !== 'object')
|
||||
throw 'avatar must be an object';
|
||||
|
||||
return {
|
||||
get id() { return id; },
|
||||
|
||||
get name() { return name; },
|
||||
set name(value) {
|
||||
if(typeof value !== 'string')
|
||||
throw 'value must be a string';
|
||||
name = value;
|
||||
},
|
||||
|
||||
get colour() { return colour; },
|
||||
set colour(value) {
|
||||
if(typeof value !== 'string') // ^
|
||||
throw 'value must be a string';
|
||||
colour = value;
|
||||
},
|
||||
|
||||
get status() { return status; },
|
||||
set status(value) {
|
||||
if(typeof value !== 'object' || value === null)
|
||||
throw 'value must be an object';
|
||||
status = value;
|
||||
},
|
||||
|
||||
get perms() { return perms; },
|
||||
set perms(value) {
|
||||
if(typeof value !== 'object' || value === null)
|
||||
throw 'value must be an object';
|
||||
perms = value;
|
||||
},
|
||||
|
||||
get avatar() { return avatar; },
|
||||
set avatar(value) {
|
||||
if(typeof value !== 'object' || value === null)
|
||||
throw 'value must be an object';
|
||||
avatar = value;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Umi.User = (() => {
|
||||
let userInfo;
|
||||
|
||||
return {
|
||||
hasCurrentUser: () => userInfo !== undefined,
|
||||
getCurrentUser: () => userInfo,
|
||||
setCurrentUser: value => { userInfo = value; },
|
||||
isCurrentUser: otherInfo => otherInfo !== null && typeof otherInfo === 'object' && typeof otherInfo.id === 'string'
|
||||
&& userInfo !== null && typeof userInfo === 'object'
|
||||
&& typeof userInfo.id === 'string'
|
||||
&& (userInfo === otherInfo || userInfo.id === otherInfo.id),
|
||||
};
|
||||
})();
|
||||
|
||||
Umi.Users = (function() {
|
||||
const users = new Map;
|
||||
|
||||
return {
|
||||
Add: function(user) {
|
||||
const userId = user.id;
|
||||
if(!users.has(userId)) {
|
||||
users.set(userId, user);
|
||||
}
|
||||
},
|
||||
Remove: function(user) {
|
||||
const userId = user.id;
|
||||
if(users.has(userId)) {
|
||||
users.delete(userId);
|
||||
}
|
||||
},
|
||||
Clear: function() {
|
||||
users.clear();
|
||||
},
|
||||
All: function() {
|
||||
return Array.from(users.values());
|
||||
},
|
||||
Get: function(userId) {
|
||||
userId = userId.toString();
|
||||
if(users.has(userId))
|
||||
return users.get(userId);
|
||||
return null;
|
||||
},
|
||||
Find: function(userName) {
|
||||
const found = [];
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
users.forEach(function(user) {
|
||||
if(user.name.toLowerCase().includes(userName))
|
||||
found.push(user);
|
||||
});
|
||||
|
||||
return found;
|
||||
},
|
||||
FindExact: function(userName) {
|
||||
if(typeof userName !== 'string')
|
||||
return null;
|
||||
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
for(const user of users.values())
|
||||
if(user.name.toLowerCase() === userName)
|
||||
return user;
|
||||
|
||||
return null;
|
||||
},
|
||||
Update: function(userId, user) {
|
||||
userId = userId.toString();
|
||||
users.set(userId, user);
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -36,12 +36,12 @@ const Weeaboo = (function() {
|
|||
};
|
||||
|
||||
pub.getNameSuffix = function(user) {
|
||||
if(typeof user !== 'object' || user === null)
|
||||
if(typeof user !== 'object' || user === null || user === undefined)
|
||||
return '';
|
||||
|
||||
if(user.rank >= 10)
|
||||
if(user.perms.rank >= 10)
|
||||
return '-sama';
|
||||
if(user.rank >= 5)
|
||||
if(user.perms.rank >= 5)
|
||||
return '-sensei';
|
||||
if(user.colour.toLowerCase() === '#f02d7d')
|
||||
return '-san';
|
||||
|
@ -59,7 +59,7 @@ const Weeaboo = (function() {
|
|||
};
|
||||
|
||||
pub.getTextSuffix = function(user) {
|
||||
if(typeof user !== 'object' || user === null)
|
||||
if(typeof user !== 'object' || user === null || user === undefined)
|
||||
return '';
|
||||
|
||||
const userId = user.id;
|
||||
|
|
281
src/mami.js/worker.js
Normal file
281
src/mami.js/worker.js
Normal file
|
@ -0,0 +1,281 @@
|
|||
#include uniqstr.js
|
||||
|
||||
const MamiWorker = function(url, eventTarget) {
|
||||
const timeOutMs = 30000;
|
||||
|
||||
let worker, workerId;
|
||||
let connectTimeout;
|
||||
let pingId;
|
||||
let hasTimedout;
|
||||
|
||||
const root = {};
|
||||
const objects = new Map;
|
||||
const pending = new Map;
|
||||
const clearObjects = () => {
|
||||
for(const [name, object] of objects)
|
||||
for(const method in object)
|
||||
delete object[method];
|
||||
objects.clear();
|
||||
objects.set('', root);
|
||||
};
|
||||
|
||||
clearObjects();
|
||||
|
||||
const broadcastTimeoutZone = body => {
|
||||
const localWorkerId = workerId;
|
||||
|
||||
body(detail => {
|
||||
if(localWorkerId !== workerId || hasTimedout)
|
||||
return;
|
||||
hasTimedout = true;
|
||||
|
||||
eventTarget.dispatch(':timeout', detail);
|
||||
});
|
||||
};
|
||||
|
||||
const handlers = {};
|
||||
|
||||
const handleMessage = ev => {
|
||||
if(typeof ev.data === 'object' && ev.data !== null && typeof ev.data.type === 'string') {
|
||||
if(ev.data.type in handlers)
|
||||
handlers[ev.data.type](ev.data.detail);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const callObjectMethod = (objName, metName, ...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(typeof objName !== 'string')
|
||||
throw 'objName must be a string';
|
||||
if(typeof metName !== 'string')
|
||||
throw 'metName must be a string';
|
||||
|
||||
const id = MamiUniqueStr(8);
|
||||
const info = { id: id, resolve: resolve, reject: reject };
|
||||
pending.set(id, info);
|
||||
|
||||
worker.postMessage({ type: 'metcall', detail: { id: id, object: objName, method: metName, args: args } });
|
||||
|
||||
broadcastTimeoutZone(timeout => {
|
||||
info.timeOut = setTimeout(() => {
|
||||
const reject = info.reject;
|
||||
info.resolve = info.reject = undefined;
|
||||
|
||||
info.timeOut = undefined;
|
||||
pending.delete(id);
|
||||
|
||||
timeout({ at: 'call', obj: objName, met: metName });
|
||||
|
||||
if(typeof reject === 'function')
|
||||
reject('timeout');
|
||||
}, timeOutMs);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const defineObjectMethod = (object, objName, method) => object[method] = (...args) => callObjectMethod(objName, method, ...args);
|
||||
|
||||
handlers['objdef'] = info => {
|
||||
let object = objects.get(info.object);
|
||||
if(object === undefined)
|
||||
objects.set(info.object, object = {});
|
||||
|
||||
if(typeof info.eventPrefix === 'string') {
|
||||
const scopedTarget = eventTarget.scopeTo(info.eventPrefix);
|
||||
object.watch = scopedTarget.watch;
|
||||
object.unwatch = scopedTarget.unwatch;
|
||||
}
|
||||
|
||||
for(const method of info.methods)
|
||||
defineObjectMethod(object, info.object, method);
|
||||
};
|
||||
|
||||
handlers['objdel'] = info => {
|
||||
// this should never happen
|
||||
if(info.object === '') {
|
||||
console.error('Worker attempted to delete root object!!!!!');
|
||||
return;
|
||||
}
|
||||
|
||||
const object = objects.get(info.object);
|
||||
if(object === undefined)
|
||||
return;
|
||||
|
||||
objects.delete(info.object);
|
||||
|
||||
const methods = Object.keys(object);
|
||||
for(const method of methods)
|
||||
delete object[method];
|
||||
};
|
||||
|
||||
handlers['metdef'] = info => {
|
||||
const object = objects.get(info.object);
|
||||
if(object === undefined) {
|
||||
console.error('Worker attempted to define method on undefined object.');
|
||||
return;
|
||||
}
|
||||
|
||||
defineObjectMethod(object, info.object, info.method);
|
||||
};
|
||||
|
||||
handlers['metdel'] = info => {
|
||||
const object = objects.get(info.object);
|
||||
if(object === undefined) {
|
||||
console.error('Worker attempted to delete method on undefined object.');
|
||||
return;
|
||||
}
|
||||
|
||||
delete object[info.method];
|
||||
};
|
||||
|
||||
handlers['funcret'] = resp => {
|
||||
const info = pending.get(resp.id);
|
||||
if(info === undefined)
|
||||
return;
|
||||
|
||||
pending.delete(info.id);
|
||||
|
||||
if(info.timeOut !== undefined)
|
||||
clearTimeout(info.timeOut);
|
||||
|
||||
const handler = resp.success ? info.resolve : info.reject;
|
||||
info.resolve = info.reject = undefined;
|
||||
|
||||
if(handler !== undefined) {
|
||||
let result = resp.result;
|
||||
if(resp.object)
|
||||
result = objects.get(result);
|
||||
|
||||
handler(result);
|
||||
}
|
||||
};
|
||||
|
||||
handlers['evtdisp'] = resp => {
|
||||
eventTarget.dispatch(resp.name, resp.detail);
|
||||
};
|
||||
|
||||
return {
|
||||
get root() { return root; },
|
||||
|
||||
watch: eventTarget.watch,
|
||||
unwatch: eventTarget.unwatch,
|
||||
eventTarget: prefix => eventTarget.scopeTo(prefix),
|
||||
|
||||
ping: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(worker === undefined)
|
||||
throw 'no worker active';
|
||||
|
||||
let pingTimeout;
|
||||
let localPingId = pingId;
|
||||
|
||||
const pingHandleMessage = ev => {
|
||||
if(typeof ev.data === 'string' && ev.data.startsWith('pong:') && ev.data.substring(5) === localPingId)
|
||||
try {
|
||||
reject = undefined;
|
||||
pingId = undefined;
|
||||
|
||||
if(pingTimeout !== undefined)
|
||||
clearTimeout(pingTimeout);
|
||||
|
||||
worker?.removeEventListener('message', pingHandleMessage);
|
||||
|
||||
if(typeof resolve === 'function')
|
||||
resolve();
|
||||
} finally {
|
||||
resolve = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
worker.addEventListener('message', pingHandleMessage);
|
||||
|
||||
if(localPingId === undefined) {
|
||||
pingId = localPingId = MamiUniqueStr(8);
|
||||
|
||||
broadcastTimeoutZone(timeout => {
|
||||
pingTimeout = setTimeout(() => {
|
||||
try {
|
||||
resolve = undefined;
|
||||
|
||||
worker?.removeEventListener('message', pingHandleMessage);
|
||||
|
||||
timeout({ at: 'ping' });
|
||||
if(typeof reject === 'function')
|
||||
reject('ping timeout');
|
||||
} finally {
|
||||
reject = undefined;
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
worker.postMessage(`ping:${localPingId}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
sabotage: () => {
|
||||
worker?.terminate();
|
||||
},
|
||||
|
||||
connect: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connectFinally = () => {
|
||||
if(connectTimeout !== undefined) {
|
||||
clearTimeout(connectTimeout);
|
||||
connectTimeout = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const connectHandleMessage = ev => {
|
||||
worker?.removeEventListener('message', connectHandleMessage);
|
||||
|
||||
if(typeof ev.data !== 'object' || ev.data === null || ev.data.type !== 'objdef'
|
||||
|| typeof ev.data.detail !== 'object' || ev.data.detail.object !== '') {
|
||||
callReject('data');
|
||||
} else
|
||||
callResolve();
|
||||
};
|
||||
|
||||
const callResolve = () => {
|
||||
reject = undefined;
|
||||
connectFinally();
|
||||
try {
|
||||
if(typeof resolve === 'function')
|
||||
resolve(root);
|
||||
} finally {
|
||||
resolve = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const callReject = (...args) => {
|
||||
resolve = undefined;
|
||||
connectFinally();
|
||||
try {
|
||||
broadcastTimeoutZone(timeout => {
|
||||
timeout({ at: 'connect' });
|
||||
});
|
||||
|
||||
if(typeof reject === 'function')
|
||||
reject(...args);
|
||||
} finally {
|
||||
reject = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
if(worker !== undefined) {
|
||||
worker.terminate();
|
||||
workerId = worker = undefined;
|
||||
}
|
||||
|
||||
hasTimedout = false;
|
||||
workerId = MamiUniqueStr(5);
|
||||
worker = new Worker(url);
|
||||
worker.addEventListener('message', handleMessage);
|
||||
worker.addEventListener('message', connectHandleMessage);
|
||||
|
||||
connectTimeout = setTimeout(() => callReject('timeout'), timeOutMs);
|
||||
worker.postMessage({ type: 'init' });
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
19
src/proto.js/main.js
Normal file
19
src/proto.js/main.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include skel.js
|
||||
#include sockchat/proto.js
|
||||
|
||||
const skel = new WorkerSkeleton;
|
||||
|
||||
skel.defineMethod('create', (name, options) => {
|
||||
if(typeof name !== 'string')
|
||||
throw 'name must be a string';
|
||||
|
||||
let proto, prefix;
|
||||
|
||||
if(name === 'sockchat')
|
||||
proto = new SockChatProtocol(
|
||||
skel.createDispatcher(prefix = 'sockchat'),
|
||||
options
|
||||
);
|
||||
|
||||
return skel.defineObject(proto, prefix);
|
||||
}, true);
|
214
src/proto.js/skel.js
Normal file
214
src/proto.js/skel.js
Normal file
|
@ -0,0 +1,214 @@
|
|||
#include uniqstr.js
|
||||
|
||||
const WorkerSkeletonObject = function(name, defineObjectMethod, deleteObjectMethod, deleteObject) {
|
||||
if(typeof name !== 'string')
|
||||
throw 'name must be a string';
|
||||
if(typeof defineObjectMethod !== 'function')
|
||||
throw 'defineObjectMethod must be a function';
|
||||
if(typeof deleteObjectMethod !== 'function')
|
||||
throw 'deleteObjectMethod must be a function';
|
||||
if(typeof deleteObject !== 'function')
|
||||
throw 'deleteObject must be a function';
|
||||
|
||||
return {
|
||||
getObjectName: () => name,
|
||||
defineMethod: (...args) => defineObjectMethod(name, ...args),
|
||||
deleteMethod: (...args) => deleteObjectMethod(name, ...args),
|
||||
destroy: () => deleteObject(name),
|
||||
};
|
||||
};
|
||||
|
||||
const WorkerSkeleton = function(globalScope) {
|
||||
if(globalScope === undefined)
|
||||
globalScope = self;
|
||||
|
||||
const objects = new Map;
|
||||
const handlers = {};
|
||||
let initialised = false;
|
||||
|
||||
const sendPayload = (type, detail) => {
|
||||
globalScope.postMessage({ type: type, detail: detail });
|
||||
};
|
||||
|
||||
const sendObjectDefinePayload = (objName, objBody, eventPrefix) => {
|
||||
sendPayload('objdef', { object: objName, methods: Object.keys(objBody), eventPrefix: eventPrefix });
|
||||
};
|
||||
|
||||
const defineObject = (objName, objBody, eventPrefix) => {
|
||||
if(typeof objName !== 'string')
|
||||
throw 'objName must be a string';
|
||||
if(typeof eventPrefix !== 'string' && eventPrefix !== undefined)
|
||||
throw 'eventPrefix must be string or undefined';
|
||||
if(objects.has(objName))
|
||||
throw 'objName is already defined';
|
||||
|
||||
const object = {};
|
||||
for(const name in objBody) {
|
||||
const item = objBody[name];
|
||||
if(typeof item === 'function')
|
||||
object[name] = { body: item };
|
||||
}
|
||||
|
||||
objects.set(objName, object);
|
||||
if(initialised)
|
||||
sendObjectDefinePayload(objName, object, eventPrefix);
|
||||
};
|
||||
|
||||
const deleteObject = objName => {
|
||||
if(typeof objName !== 'string')
|
||||
throw 'objName must be a string';
|
||||
if(!objects.has(objName))
|
||||
throw 'objName is not defined';
|
||||
|
||||
objects.delete(objName);
|
||||
if(initialised)
|
||||
sendPayload('objdel', { object: objName });
|
||||
};
|
||||
|
||||
const defineObjectMethod = (objName, metName, metBody, returnsObject) => {
|
||||
if(typeof objName !== 'string')
|
||||
throw 'objName must be a string';
|
||||
if(typeof metName !== 'string')
|
||||
throw 'metName must be a string';
|
||||
if(typeof metBody !== 'function')
|
||||
throw 'metBody must be a function';
|
||||
|
||||
const objBody = objects.get(objName);
|
||||
if(objBody === undefined)
|
||||
throw 'objName has not been defined';
|
||||
|
||||
objBody[metName] = { body: metBody, returnsObject: returnsObject === true };
|
||||
if(initialised)
|
||||
sendPayload('metdef', { object: objName, method: metName });
|
||||
};
|
||||
|
||||
const deleteObjectMethod = (objName, metName) => {
|
||||
if(typeof objName !== 'string')
|
||||
throw 'objName must be a string';
|
||||
if(typeof metName !== 'string')
|
||||
throw 'metName must be a string';
|
||||
|
||||
const objBody = objects.get(objName);
|
||||
if(objBody === undefined)
|
||||
throw 'objName has not been defined';
|
||||
|
||||
delete objBody[objName];
|
||||
if(initialised)
|
||||
sendPayload('metdel', { object: objName, method: metName });
|
||||
};
|
||||
|
||||
const createDispatcher = prefix => {
|
||||
if(prefix === undefined)
|
||||
prefix = '';
|
||||
else if(typeof prefix !== 'string')
|
||||
throw 'prefix must be a string or undefined';
|
||||
|
||||
if(prefix !== '' && !prefix.endsWith(':'))
|
||||
prefix += ':';
|
||||
|
||||
return (name, detail) => sendPayload('evtdisp', { name: prefix + name, detail: detail });
|
||||
};
|
||||
|
||||
defineObject('', {});
|
||||
|
||||
const defineHandler = (name, handler) => {
|
||||
if(typeof name !== 'string')
|
||||
throw 'name must be a string';
|
||||
if(typeof handler !== 'function')
|
||||
throw 'handler must be a function';
|
||||
if(name in handlers)
|
||||
throw 'name is already defined';
|
||||
|
||||
handlers[name] = handler;
|
||||
};
|
||||
|
||||
defineHandler('init', () => {
|
||||
if(initialised)
|
||||
return;
|
||||
initialised = true;
|
||||
|
||||
sendObjectDefinePayload('', objects.get(''));
|
||||
});
|
||||
|
||||
defineHandler('metcall', req => {
|
||||
if(typeof req.id !== 'string')
|
||||
throw 'call id is not a string';
|
||||
|
||||
const respond = (id, success, result, mightBeObject) => {
|
||||
let isObject = false;
|
||||
if(mightBeObject) {
|
||||
const resultType = typeof result;
|
||||
if(resultType === 'string' && objects.has(result))
|
||||
isObject = true;
|
||||
else if(resultType === 'object' && result !== null && typeof result.getObjectName === 'function') {
|
||||
const objectName = result.getObjectName();
|
||||
if(objects.has(objectName)) {
|
||||
isObject = true;
|
||||
result = objectName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendPayload('funcret', {
|
||||
id: id,
|
||||
success: success,
|
||||
result: result,
|
||||
object: isObject,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
if(typeof req.object !== 'string')
|
||||
throw 'object name is not a string';
|
||||
if(typeof req.method !== 'string')
|
||||
throw 'method name is not a string';
|
||||
|
||||
const object = objects.get(req.object);
|
||||
if(object === undefined)
|
||||
throw 'object is not defined';
|
||||
if(!(req.method in object))
|
||||
throw 'method is not defined in object';
|
||||
|
||||
const args = Array.isArray(req.args) ? req.args : [];
|
||||
const info = object[req.method];
|
||||
let result = info.body(...args);
|
||||
|
||||
if(result instanceof Promise) {
|
||||
result.then(result => respond(req.id, true, result, info.returnsObject)).catch(ex => respond(req.id, false, ex));
|
||||
} else
|
||||
respond(req.id, true, result, info.returnsObject);
|
||||
} catch(ex) {
|
||||
respond(req.id, false, ex);
|
||||
}
|
||||
});
|
||||
|
||||
globalScope.addEventListener('message', ev => {
|
||||
if(typeof ev.data === 'string') {
|
||||
if(ev.data.startsWith('ping:')) {
|
||||
globalScope.postMessage(`pong:${ev.data.substring(5)}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(typeof ev.data === 'object' && ev.data !== null && typeof ev.data.type === 'string') {
|
||||
if(ev.data.type in handlers)
|
||||
handlers[ev.data.type](ev.data.detail);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
sendPayload: sendPayload,
|
||||
createDispatcher: createDispatcher,
|
||||
defineObject: (object, eventPrefix) => {
|
||||
if(typeof object !== 'object' || object === null)
|
||||
return undefined;
|
||||
|
||||
const name = MamiUniqueStr(8);
|
||||
defineObject(name, object, eventPrefix);
|
||||
return new WorkerSkeletonObject(name, defineObjectMethod, deleteObjectMethod, deleteObject);
|
||||
},
|
||||
defineMethod: (...args) => defineObjectMethod('', ...args),
|
||||
deleteMethod: (...args) => deleteObjectMethod('', ...args),
|
||||
};
|
||||
};
|
449
src/proto.js/sockchat/authed.js
Normal file
449
src/proto.js/sockchat/authed.js
Normal file
|
@ -0,0 +1,449 @@
|
|||
#include sockchat/utils.js
|
||||
|
||||
const SockChatS2CPong = ctx => {
|
||||
const lastPong = Date.now();
|
||||
const lastPing = ctx.lastPing;
|
||||
|
||||
ctx.lastPing = undefined;
|
||||
if(lastPing === undefined)
|
||||
throw 'unexpected pong received??';
|
||||
|
||||
ctx.pingPromise?.resolve({
|
||||
ping: lastPing,
|
||||
pong: lastPong,
|
||||
diff: lastPong - lastPing,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CBanKick = (ctx, type, expiresTime) => {
|
||||
ctx.wasKicked = true;
|
||||
|
||||
const bakaInfo = {
|
||||
session: { success: false },
|
||||
baka: {
|
||||
type: type === '0' ? 'kick' : 'ban',
|
||||
},
|
||||
};
|
||||
|
||||
if(bakaInfo.baka.type === 'ban') {
|
||||
bakaInfo.baka.perma = expiresTime === '-1';
|
||||
bakaInfo.baka.until = expiresTime === '-1' ? undefined : new Date(parseInt(expiresTime) * 1000);
|
||||
}
|
||||
|
||||
ctx.dispatch('session:term', bakaInfo);
|
||||
};
|
||||
|
||||
const SockChatS2CContextClear = (ctx, mode) => {
|
||||
if(mode === '0' || mode === '3' || mode === '4')
|
||||
ctx.dispatch('msg:clear');
|
||||
|
||||
if(mode === '1' || mode === '3' || mode === '4') {
|
||||
ctx.users.clear();
|
||||
ctx.dispatch('user:clear');
|
||||
}
|
||||
|
||||
if(mode === '2' || mode === '4') {
|
||||
ctx.channels.clear();
|
||||
ctx.dispatch('chan:clear');
|
||||
}
|
||||
};
|
||||
|
||||
const SockChatS2CChannelPopulate = (ctx, count, ...args) => {
|
||||
count = parseInt(count);
|
||||
ctx.dispatch('chan:clear');
|
||||
|
||||
for(let i = 0; i < count; ++i) {
|
||||
const offset = 3 * i;
|
||||
const chanInfo = {
|
||||
name: args[offset],
|
||||
hasPassword: args[offset + 1] !== '0',
|
||||
isTemporary: args[offset + 2] !== '0',
|
||||
};
|
||||
|
||||
ctx.channels.set(chanInfo.name, chanInfo);
|
||||
ctx.dispatch('chan:add', {
|
||||
channel: {
|
||||
name: chanInfo.name,
|
||||
hasPassword: chanInfo.hasPassword,
|
||||
isTemporary: chanInfo.name.isTemporary,
|
||||
isCurrent: chanInfo.name === ctx.channelName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
ctx.dispatch('chan:focus', {
|
||||
channel: { name: ctx.channelName },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelAdd = (ctx, name, hasPass, isTemp) => {
|
||||
const chanInfo = {
|
||||
name: name,
|
||||
hasPassword: hasPass !== '0',
|
||||
isTemporary: isTemp !== '0',
|
||||
};
|
||||
|
||||
ctx.channels.set(chanInfo.name, chanInfo);
|
||||
ctx.dispatch('chan:add', {
|
||||
channel: chanInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelUpdate = (ctx, prevName, name, hasPass, isTemp) => {
|
||||
const chanInfo = ctx.channels.get(prevName);
|
||||
|
||||
if(prevName !== name) {
|
||||
chanInfo.name = name;
|
||||
ctx.channels.delete(prevName);
|
||||
ctx.channels.set(name, chanInfo);
|
||||
}
|
||||
|
||||
chanInfo.hasPassword = hasPass !== '0';
|
||||
chanInfo.isTemporary = isTemp !== '0';
|
||||
|
||||
ctx.dispatch('chan:update', {
|
||||
channel: {
|
||||
previousName: prevName,
|
||||
name: chanInfo.name,
|
||||
hasPassword: chanInfo.hasPassword,
|
||||
isTemporary: chanInfo.isTemporary,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CChannelRemove = (ctx, name) => {
|
||||
ctx.channels.delete(name);
|
||||
ctx.dispatch('chan:remove', {
|
||||
channel: { name: name },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserPopulate = (ctx, count, ...args) => {
|
||||
count = parseInt(count);
|
||||
ctx.dispatch('user:clear');
|
||||
|
||||
for(let i = 0; i < count; ++i) {
|
||||
const offset = 5 * i;
|
||||
const statusInfo = SockChatParseStatusInfo(args[offset + 1]);
|
||||
const userInfo = {
|
||||
id: args[offset],
|
||||
self: args[offset] === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(args[offset + 2]),
|
||||
perms: SockChatParseUserPerms(args[offset + 3]),
|
||||
hidden: args[offset + 4] !== '0',
|
||||
};
|
||||
|
||||
ctx.users.set(userInfo.id, userInfo);
|
||||
ctx.dispatch('user:add', {
|
||||
user: userInfo,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const SockChatS2CUserAdd = (ctx, timeStamp, userId, userName, userColour, userPerms, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const userInfo = {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
};
|
||||
|
||||
ctx.users.set(userInfo.id, userInfo);
|
||||
ctx.dispatch('user:add', {
|
||||
msg: {
|
||||
type: 'user:add',
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
sender: userInfo,
|
||||
},
|
||||
user: userInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserUpdate = (ctx, userId, userName, userColour, userPerms) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const userInfo = ctx.users.get(userId);
|
||||
|
||||
userInfo.self = userId === ctx.userId;
|
||||
userInfo.name = statusInfo.name;
|
||||
userInfo.status = statusInfo.status;
|
||||
userInfo.colour = SockChatParseUserColour(userColour);
|
||||
userInfo.perms = SockChatParseUserPerms(userPerms);
|
||||
|
||||
ctx.users.set(userInfo.id, userInfo);
|
||||
ctx.dispatch('user:update', {
|
||||
user: userInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserRemove = (ctx, userId, userName, reason, timeStamp, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const userInfo = ctx.users.get(userId);
|
||||
userInfo.name = statusInfo.name;
|
||||
userInfo.status = statusInfo.status;
|
||||
ctx.users.delete(userId);
|
||||
|
||||
ctx.dispatch('user:remove', {
|
||||
leave: { type: reason },
|
||||
msg: {
|
||||
type: 'user:remove',
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
sender: userInfo,
|
||||
detail: { reason: reason },
|
||||
},
|
||||
user: userInfo,
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelJoin = (ctx, userId, userName, userColour, userPerms, msgId) => {
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const userInfo = {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
};
|
||||
|
||||
ctx.users.set(userInfo.id, userInfo);
|
||||
ctx.dispatch('chan:join', {
|
||||
user: userInfo,
|
||||
msg: {
|
||||
type: 'chan:join',
|
||||
id: msgId,
|
||||
time: new Date,
|
||||
channel: ctx.channelName,
|
||||
sender: userInfo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelLeave = (ctx, userId, msgId) => {
|
||||
const userInfo = ctx.users.get(userId);
|
||||
ctx.users.delete(userId); // should this happen? probably not!
|
||||
|
||||
ctx.dispatch('chan:leave', {
|
||||
user: userInfo,
|
||||
msg: {
|
||||
type: 'chan:leave',
|
||||
id: msgId,
|
||||
time: new Date,
|
||||
channel: ctx.channelName,
|
||||
sender: userInfo,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CUserChannelFocus = (ctx, name) => {
|
||||
ctx.channelName = name;
|
||||
|
||||
ctx.dispatch('chan:focus', {
|
||||
channel: { name: ctx.channelName },
|
||||
});
|
||||
};
|
||||
|
||||
const SockChatS2CMessagePopulate = (ctx, timeStamp, userId, userName, userColour, userPerms, msgText, msgId, msgNotify, msgFlags) => {
|
||||
const mFlags = SockChatParseMsgFlags(msgFlags);
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const info = {
|
||||
msg: {
|
||||
type: `msg:${mFlags.isAction ? 'action' : 'text'}`,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: ctx.channelName,
|
||||
sender: {
|
||||
id: userId,
|
||||
self: userId === ctx.userId,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
isBot: userId === '-1',
|
||||
silent: msgNotify === '0',
|
||||
flags: mFlags,
|
||||
text: SockChatUnfuckText(msgText, mFlags.isAction),
|
||||
},
|
||||
};
|
||||
|
||||
if(!isNaN(msgId))
|
||||
info.msg.id = msgId;
|
||||
|
||||
if(info.msg.isBot) {
|
||||
const botParts = msgText.split("\f");
|
||||
info.msg.botInfo = {
|
||||
isError: botParts[0] === '1',
|
||||
type: botParts[1],
|
||||
args: botParts.slice(2),
|
||||
};
|
||||
|
||||
let botUserNameIndex = 2;
|
||||
if(info.msg.botInfo.type === 'say') {
|
||||
info.msg.botInfo.args[0] = info.msg.text = SockChatUnfuckText(info.msg.botInfo.args[0], mFlags.isAction);
|
||||
} else if(info.msg.botInfo.type === 'join') {
|
||||
info.msg.type = 'user:add';
|
||||
} else if(['leave', 'kick', 'flood', 'timeout'].includes(info.msg.botInfo.type)) {
|
||||
info.msg.type = 'user:remove';
|
||||
info.msg.detail = { reason: info.msg.botInfo.type };
|
||||
} else if(info.msg.botInfo.type === 'nick') {
|
||||
info.msg.type = 'user:nick';
|
||||
info.msg.detail = {
|
||||
previousName: info.msg.botInfo.args[0],
|
||||
name: info.msg.botInfo.args[1],
|
||||
};
|
||||
} else if(info.msg.botInfo.type === 'jchan') {
|
||||
info.msg.type = 'chan:join';
|
||||
} else if(info.msg.botInfo.type === 'lchan') {
|
||||
info.msg.type = 'chan:leave';
|
||||
} else {
|
||||
info.msg.type = `legacy:${info.msg.botInfo.type}`;
|
||||
info.msg.detail = {
|
||||
error: info.msg.botInfo.isError,
|
||||
args: info.msg.botInfo.args,
|
||||
};
|
||||
}
|
||||
|
||||
if(['join', 'leave', 'kick', 'flood', 'timeout', 'jchan', 'lchan', 'nick'].includes(botParts[1])) {
|
||||
info.msg.sender = undefined;
|
||||
|
||||
const botUserTarget = botParts[botUserNameIndex].toLowerCase();
|
||||
for(const botUserInfo of ctx.users.values())
|
||||
if(botUserInfo.name.toLowerCase() === botUserTarget) {
|
||||
info.msg.sender = botUserInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
if(info.msg.sender === undefined)
|
||||
info.msg.sender = {
|
||||
id: '0',
|
||||
self: false,
|
||||
name: botParts[botUserNameIndex],
|
||||
colour: 'inherit',
|
||||
perms: { rank: 0 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ctx.dispatch('msg:add', info);
|
||||
};
|
||||
|
||||
const SockChatS2CMessageAdd = (ctx, timeStamp, userId, msgText, msgId, msgFlags) => {
|
||||
const mFlags = SockChatParseMsgFlags(msgFlags);
|
||||
let mText = SockChatUnfuckText(msgText, mFlags.isAction);
|
||||
let mChannelName = ctx.channelName;
|
||||
|
||||
const userInfo = userId === '-1' ? {
|
||||
id: '-1',
|
||||
self: false,
|
||||
name: 'ChatBot',
|
||||
colour: 'inherit',
|
||||
perms: { rank: 0 },
|
||||
} : ctx.users.get(userId);
|
||||
|
||||
if(msgFlags[4] !== '0') {
|
||||
if(userId === ctx.userId) {
|
||||
const mTextParts = mText.split(' ');
|
||||
mChannelName = `@${mTextParts.shift()}`;
|
||||
mText = mTextParts.join(' ');
|
||||
} else
|
||||
mChannelName = `@${userInfo.name}`;
|
||||
|
||||
if(!ctx.channels.has(mChannelName)) {
|
||||
const chanInfo = {
|
||||
name: mChannelName,
|
||||
hasPassword: false,
|
||||
isTemporary: true,
|
||||
isUserChannel: true,
|
||||
};
|
||||
|
||||
ctx.channels.set(chanInfo.name, chanInfo);
|
||||
ctx.dispatch('chan:add', { channel: chanInfo });
|
||||
}
|
||||
}
|
||||
|
||||
const msgInfo = {
|
||||
msg: {
|
||||
type: `msg:${mFlags.isAction ? 'action' : 'text'}`,
|
||||
id: msgId,
|
||||
time: new Date(parseInt(timeStamp) * 1000),
|
||||
channel: mChannelName,
|
||||
sender: userInfo,
|
||||
flags: mFlags,
|
||||
isBot: userId === '-1',
|
||||
text: mText,
|
||||
},
|
||||
};
|
||||
|
||||
if(msgInfo.msg.isBot) {
|
||||
const botParts = msgText.split("\f");
|
||||
msgInfo.msg.botInfo = {
|
||||
isError: botParts[0] === '1',
|
||||
type: botParts[1],
|
||||
args: botParts.slice(2),
|
||||
};
|
||||
|
||||
let botUserNameIndex = 2;
|
||||
if(msgInfo.msg.botInfo.type === 'join') {
|
||||
msgInfo.msg.type = 'user:add';
|
||||
} else if(['leave', 'kick', 'flood', 'timeout'].includes(msgInfo.msg.botInfo.type)) {
|
||||
msgInfo.msg.type = 'user:remove';
|
||||
msgInfo.msg.detail = { reason: msgInfo.msg.botInfo.type };
|
||||
} else if(msgInfo.msg.botInfo.type === 'nick') {
|
||||
msgInfo.msg.type = 'user:nick';
|
||||
msgInfo.msg.detail = {
|
||||
previousName: msgInfo.msg.botInfo.args[0],
|
||||
name: msgInfo.msg.botInfo.args[1],
|
||||
};
|
||||
} else if(msgInfo.msg.botInfo.type === 'jchan') {
|
||||
msgInfo.msg.type = 'chan:join';
|
||||
} else if(msgInfo.msg.botInfo.type === 'lchan') {
|
||||
msgInfo.msg.type = 'chan:leave';
|
||||
} else {
|
||||
msgInfo.msg.type = `legacy:${msgInfo.msg.botInfo.type}`;
|
||||
msgInfo.msg.detail = {
|
||||
error: msgInfo.msg.botInfo.isError,
|
||||
args: msgInfo.msg.botInfo.args,
|
||||
};
|
||||
}
|
||||
|
||||
if(['join', 'leave', 'kick', 'flood', 'timeout', 'jchan', 'lchan', 'nick'].includes(botParts[1])) {
|
||||
msgInfo.msg.sender = undefined;
|
||||
|
||||
const botUserTarget = botParts[botUserNameIndex].toLowerCase();
|
||||
for(const botUserInfo of ctx.users.values())
|
||||
if(botUserInfo.name.toLowerCase() === botUserTarget) {
|
||||
msgInfo.msg.sender = botUserInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
if(msgInfo.msg.sender === undefined)
|
||||
msgInfo.msg.sender = {
|
||||
id: '0',
|
||||
self: false,
|
||||
name: botParts[botUserNameIndex],
|
||||
colour: 'inherit',
|
||||
perms: { rank: 0 },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ctx.dispatch('msg:add', msgInfo);
|
||||
};
|
||||
|
||||
const SockChatS2CMessageRemove = (ctx, msgId) => {
|
||||
ctx.dispatch('msg:remove', {
|
||||
msg: {
|
||||
type: 'msg:remove',
|
||||
id: msgId,
|
||||
channel: ctx.channelName,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
#include proto/sockchat/keepalive.js
|
||||
#include sockchat/keepalive.js
|
||||
|
||||
const SockChatContext = function(dispatch, sendPing, pingDelay) {
|
||||
if(typeof dispatch !== 'function')
|
||||
|
@ -12,6 +12,9 @@ const SockChatContext = function(dispatch, sendPing, pingDelay) {
|
|||
|
||||
get isAuthed() { return userId !== undefined; },
|
||||
|
||||
users: new Map,
|
||||
channels: new Map,
|
||||
|
||||
channelName: undefined,
|
||||
pseudoChannelName: undefined,
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#include timedp.js
|
||||
#include proto/sockchat/authed.js
|
||||
#include proto/sockchat/ctx.js
|
||||
#include proto/sockchat/unauthed.js
|
||||
#include sockchat/authed.js
|
||||
#include sockchat/ctx.js
|
||||
#include sockchat/unauthed.js
|
||||
|
||||
const SockChatClient = function(dispatch, options) {
|
||||
const SockChatProtocol = function(dispatch, options) {
|
||||
if(typeof dispatch !== 'function')
|
||||
throw 'dispatch must be a function';
|
||||
if(typeof options !== 'object' || options === null)
|
||||
|
@ -81,6 +81,8 @@ const SockChatClient = function(dispatch, options) {
|
|||
ctx.userId = undefined;
|
||||
ctx.channelName = undefined;
|
||||
ctx.pseudoChannelName = undefined;
|
||||
ctx.users.clear();
|
||||
ctx.channels.clear();
|
||||
|
||||
if(ev.code === 1012)
|
||||
ctx.isRestarting = true;
|
||||
|
@ -252,5 +254,42 @@ const SockChatClient = function(dispatch, options) {
|
|||
await sendMessage(`/join ${name}`);
|
||||
}
|
||||
},
|
||||
getCurrentUserId: () => ctx.userId,
|
||||
getCurrentUserInfo: () => ctx.users.get(ctx.userId),
|
||||
getUserInfos: () => Array.from(ctx.users.values()),
|
||||
getUserInfoById: userId => {
|
||||
if(typeof userId === 'string')
|
||||
userId = parseInt(userId);
|
||||
if(typeof userId !== 'number')
|
||||
throw 'userId must be a number';
|
||||
|
||||
return ctx.users.get(userId);
|
||||
},
|
||||
getUserInfoByName: (userName, exact = true) => {
|
||||
if(typeof userName !== 'string')
|
||||
throw 'userName must be a string';
|
||||
|
||||
const compare = exact ? (a, b) => a.toLowerCase() === b : (a, b) => a.toLowerCase().includes(b);
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
for(const info of ctx.users.values())
|
||||
if(compare(info.name, userName))
|
||||
return info;
|
||||
|
||||
return undefined;
|
||||
},
|
||||
getUserInfosByName: userName => {
|
||||
if(typeof userName !== 'string')
|
||||
throw 'userName must be a string';
|
||||
|
||||
const users = [];
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
for(const info of ctx.users.values())
|
||||
if(info.name.toLowerCase().includes(userName))
|
||||
users.push(info);
|
||||
|
||||
return users;
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,10 +1,19 @@
|
|||
#include proto/sockchat/utils.js
|
||||
#include sockchat/utils.js
|
||||
|
||||
const SockChatS2CAuthSuccess = (ctx, userId, userName, userColour, userPerms, chanName, maxLength) => {
|
||||
ctx.userId = userId;
|
||||
ctx.channelName = chanName;
|
||||
|
||||
const statusInfo = SockChatParseStatusInfo(userName);
|
||||
const userInfo = {
|
||||
id: ctx.userId,
|
||||
self: true,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
};
|
||||
ctx.users.set(userInfo.id, userInfo);
|
||||
|
||||
const info = {
|
||||
wasConnected: ctx.wasConnected,
|
||||
|
@ -12,14 +21,7 @@ const SockChatS2CAuthSuccess = (ctx, userId, userName, userColour, userPerms, ch
|
|||
ctx: {
|
||||
maxMsgLength: parseInt(maxLength),
|
||||
},
|
||||
user: {
|
||||
id: ctx.userId,
|
||||
self: true,
|
||||
name: statusInfo.name,
|
||||
status: statusInfo.status,
|
||||
colour: SockChatParseUserColour(userColour),
|
||||
perms: SockChatParseUserPerms(userPerms),
|
||||
},
|
||||
user: userInfo,
|
||||
channel: {
|
||||
name: ctx.channelName,
|
||||
},
|
|
@ -14,8 +14,9 @@ const SockChatParseUserColour = str => {
|
|||
|
||||
const SockChatParseUserPerms = str => {
|
||||
const parts = str.split(str.includes("\f") ? "\f" : ' ');
|
||||
const rank = parseInt(parts[0] ?? '0');
|
||||
return {
|
||||
rank: parseInt(parts[0] ?? '0'),
|
||||
rank: isNaN(rank) ? 0 : rank,
|
||||
kick: parts[1] !== undefined && parts[1] !== '0',
|
||||
nick: parts[3] !== undefined && parts[3] !== '0',
|
||||
chan: parts[4] !== undefined && parts[4] !== '0',
|
38
src/proto.js/uniqstr.js
Normal file
38
src/proto.js/uniqstr.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
const MamiRandomInt = (min, max) => {
|
||||
let ret = 0;
|
||||
const range = max - min;
|
||||
|
||||
const bitsNeeded = Math.ceil(Math.log2(range));
|
||||
if(bitsNeeded > 53)
|
||||
return -1;
|
||||
|
||||
const bytesNeeded = Math.ceil(bitsNeeded / 8),
|
||||
mask = Math.pow(2, bitsNeeded) - 1;
|
||||
|
||||
const bytes = new Uint8Array(bytesNeeded);
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
let p = (bytesNeeded - 1) * 8;
|
||||
for(let i = 0; i < bytesNeeded; ++i) {
|
||||
ret += bytes[i] * Math.pow(2, p);
|
||||
p -= 8;
|
||||
}
|
||||
|
||||
ret &= mask;
|
||||
|
||||
if(ret >= range)
|
||||
return MamiRandomInt(min, max);
|
||||
|
||||
return min + ret;
|
||||
};
|
||||
|
||||
const MamiUniqueStr = (() => {
|
||||
const chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789';
|
||||
|
||||
return length => {
|
||||
let str = '';
|
||||
for(let i = 0; i < length; ++i)
|
||||
str += chars[MamiRandomInt(0, chars.length)];
|
||||
return str;
|
||||
};
|
||||
})();
|
Loading…
Reference in a new issue