From c76f8b7ad5d04a44e51e36a7f5bc004c8b538b39 Mon Sep 17 00:00:00 2001 From: EnergoStalin Date: Thu, 1 Feb 2024 04:03:56 +0300 Subject: [PATCH] feat: copy slug button --- package.json | 11 +- pnpm-lock.yaml | 207 +++++++++++++++++- src/globals.d.ts | 3 + src/index.ts | 11 +- src/routes/calendar/index.ts | 8 + src/routes/calendar/popupWatcher.ts | 41 ++++ src/routes/calendar/slug/onpopup.ts | 31 +++ .../calendar/slug/widgets/Button.svelte | 58 +++++ src/utils/navigateHook.ts | 28 +++ src/utils/waitNotNull.ts | 23 ++ tsconfig.json | 19 +- tsup.config.ts | 18 +- 12 files changed, 433 insertions(+), 25 deletions(-) create mode 100644 src/routes/calendar/index.ts create mode 100644 src/routes/calendar/popupWatcher.ts create mode 100644 src/routes/calendar/slug/onpopup.ts create mode 100644 src/routes/calendar/slug/widgets/Button.svelte create mode 100644 src/utils/navigateHook.ts create mode 100644 src/utils/waitNotNull.ts diff --git a/package.json b/package.json index e10444a..969770f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "name", - "version": "v0.0.0", - "description": "description", - "homepage": "https://example.com", + "name": "s21eduscript", + "version": "v0.0.1", + "description": "s21 platform enchancements features", + "homepage": "https://edu.21-school.ru", "module": "esm", "type": "module", "engines": { @@ -28,10 +28,13 @@ "@types/node": "^20.11.13", "@types/tampermonkey": "^4.20.4", "esbuild-plugin-userscript": "^0.2.6", + "esbuild-svelte": "^0.8.0", "eslint": "^8.56.0", "eslint-kit": "^10.10.0", "http-server": "^14.1.1", "prettier": "^3.2.4", + "svelte": "^4.2.9", + "svelte-preprocess": "^5.1.3", "tsup": "^8.0.1", "typescript": "^5.3.3" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bbeced..f4dcd0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,18 +17,27 @@ devDependencies: esbuild-plugin-userscript: specifier: ^0.2.6 version: 0.2.6 + esbuild-svelte: + specifier: ^0.8.0 + version: 0.8.0(esbuild@0.19.12)(svelte@4.2.9) eslint: specifier: ^8.56.0 version: 8.56.0 eslint-kit: specifier: ^10.10.0 - version: 10.10.0(effector@23.1.0)(eslint@8.56.0)(prettier@3.2.4)(svelte@3.59.2)(typescript@5.3.3) + version: 10.10.0(effector@23.1.0)(eslint@8.56.0)(prettier@3.2.4)(svelte@4.2.9)(typescript@5.3.3) http-server: specifier: ^14.1.1 version: 14.1.1 prettier: specifier: ^3.2.4 version: 3.2.4 + svelte: + specifier: ^4.2.9 + version: 4.2.9 + svelte-preprocess: + specifier: ^5.1.3 + version: 5.1.3(@babel/core@7.23.9)(svelte@4.2.9)(typescript@5.3.3) tsup: specifier: ^8.0.1 version: 8.0.1(@swc/core@1.3.107)(typescript@5.3.3) @@ -956,6 +965,10 @@ packages: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true + /@types/pug@2.0.10: + resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + dev: true + /@types/semver@7.5.6: resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} dev: true @@ -1283,6 +1296,12 @@ packages: dequal: 2.0.3 dev: true + /axobject-query@4.0.0: + resolution: {integrity: sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==} + dependencies: + dequal: 2.0.3 + dev: true + /babel-plugin-jsx-dom-expressions@0.37.16(@babel/core@7.23.9): resolution: {integrity: sha512-ItMD16axbk+FqVb9vIbc7AOpNowy46VaSUHaMYPn+erPGpMCxsahQ1Iv+qhPMthjxtn5ROVMZ5AJtQvzjxjiNA==} peerDependencies: @@ -1356,6 +1375,10 @@ packages: update-browserslist-db: 1.0.13(browserslist@4.22.3) dev: true + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + dev: true + /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1437,6 +1460,16 @@ packages: escape-string-regexp: 1.0.5 dev: true + /code-red@1.0.4: + resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + '@types/estree': 1.0.5 + acorn: 8.11.3 + estree-walker: 3.0.3 + periscopic: 3.1.0 + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1496,6 +1529,14 @@ packages: which: 2.0.2 dev: true + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: true + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -1556,6 +1597,11 @@ packages: engines: {node: '>=6'} dev: true + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1700,6 +1746,10 @@ packages: is-symbol: 1.0.4 dev: true + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} + dev: true + /esbuild-plugin-userscript@0.2.6: resolution: {integrity: sha512-H9Ekfl2hkhbflAx50s3U4Sb/AG/DUkp2EnrAEqCvGHt555cg3YEw5WwDCInsohyQgWzkus+ZpFm/jncHAwOw1Q==} dependencies: @@ -1707,6 +1757,18 @@ packages: common-tags: 1.8.2 dev: true + /esbuild-svelte@0.8.0(esbuild@0.19.12)(svelte@4.2.9): + resolution: {integrity: sha512-uKcPf1kl2UGMjrfHChv4dLxGAvCNhf9s72mHo19ZhKP+LrVOuQkOM/g8GE7MiGpoqjpk8UHqL08uLRbSKXhmhw==} + engines: {node: '>=14'} + peerDependencies: + esbuild: '>=0.9.6' + svelte: '>=3.43.0 <5' + dependencies: + '@jridgewell/trace-mapping': 0.3.22 + esbuild: 0.19.12 + svelte: 4.2.9 + dev: true + /esbuild@0.19.12: resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} engines: {node: '>=12'} @@ -1804,7 +1866,7 @@ packages: - supports-color dev: true - /eslint-kit@10.10.0(effector@23.1.0)(eslint@8.56.0)(prettier@3.2.4)(svelte@3.59.2)(typescript@5.3.3): + /eslint-kit@10.10.0(effector@23.1.0)(eslint@8.56.0)(prettier@3.2.4)(svelte@4.2.9)(typescript@5.3.3): resolution: {integrity: sha512-vZ28PcE4nDt43RfNaF/dMQagU2wgknJ5H54PGHNuLOU0ZzrERYglHwF89X2o01SdetdcmEHMLHiGMk8i9OPxDA==} peerDependencies: eslint: ^8.41.0 @@ -1830,7 +1892,7 @@ packages: eslint-plugin-simple-import-sort: 10.0.0(eslint@8.56.0) eslint-plugin-solid: 0.13.1(eslint@8.56.0)(typescript@5.3.3) eslint-plugin-sonarjs: 0.23.0(eslint@8.56.0) - eslint-plugin-svelte3: 4.0.0(eslint@8.56.0)(svelte@3.59.2) + eslint-plugin-svelte3: 4.0.0(eslint@8.56.0)(svelte@4.2.9) eslint-plugin-unicorn: 50.0.1(eslint@8.56.0) eslint-plugin-vue: 9.21.0(eslint@8.56.0) prettier: 3.2.4 @@ -2037,14 +2099,14 @@ packages: eslint: 8.56.0 dev: true - /eslint-plugin-svelte3@4.0.0(eslint@8.56.0)(svelte@3.59.2): + /eslint-plugin-svelte3@4.0.0(eslint@8.56.0)(svelte@4.2.9): resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} peerDependencies: eslint: '>=8.0.0' svelte: ^3.2.0 dependencies: eslint: 8.56.0 - svelte: 3.59.2 + svelte: 4.2.9 dev: true /eslint-plugin-unicorn@50.0.1(eslint@8.56.0): @@ -2198,6 +2260,12 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2772,6 +2840,12 @@ packages: engines: {node: '>=8'} dev: true + /is-reference@3.0.2: + resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} + dependencies: + '@types/estree': 1.0.5 + dev: true + /is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -2979,6 +3053,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + dev: true + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3030,6 +3108,17 @@ packages: yallist: 4.0.0 dev: true + /magic-string@0.30.6: + resolution: {integrity: sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: true + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -3322,6 +3411,14 @@ packages: engines: {node: '>=8'} dev: true + /periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + dependencies: + '@types/estree': 1.0.5 + estree-walker: 3.0.3 + is-reference: 3.0.2 + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -3532,6 +3629,13 @@ packages: engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: true + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true @@ -3595,6 +3699,15 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true + /sander@0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} + dependencies: + es6-promise: 3.3.1 + graceful-fs: 4.2.11 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: true + /secure-compare@3.0.1: resolution: {integrity: sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==} dev: true @@ -3671,6 +3784,21 @@ packages: engines: {node: '>=8'} dev: true + /sorcery@0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} + hasBin: true + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + buffer-crc32: 0.2.13 + minimist: 1.2.8 + sander: 0.5.1 + dev: true + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -3832,9 +3960,72 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte@3.59.2: - resolution: {integrity: sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==} - engines: {node: '>= 8'} + /svelte-preprocess@5.1.3(@babel/core@7.23.9)(svelte@4.2.9)(typescript@5.3.3): + resolution: {integrity: sha512-xxAkmxGHT+J/GourS5mVJeOXZzne1FR5ljeOUAMXUkfEhkLEllRreXpbl3dIYJlcJRfL1LO1uIAPpBpBfiqGPw==} + engines: {node: '>= 16.0.0', pnpm: ^8.0.0} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + pug: ^3.0.0 + sass: ^1.26.8 + stylus: ^0.55.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@babel/core': 7.23.9 + '@types/pug': 2.0.10 + detect-indent: 6.1.0 + magic-string: 0.30.6 + sorcery: 0.11.0 + strip-indent: 3.0.0 + svelte: 4.2.9 + typescript: 5.3.3 + dev: true + + /svelte@4.2.9: + resolution: {integrity: sha512-hsoB/WZGEPFXeRRLPhPrbRz67PhP6sqYgvwcAs+gWdSQSvNDw+/lTeUJSWe5h2xC97Fz/8QxAOqItwBzNJPU8w==} + engines: {node: '>=16'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.22 + '@types/estree': 1.0.5 + acorn: 8.11.3 + aria-query: 5.3.0 + axobject-query: 4.0.0 + code-red: 1.0.4 + css-tree: 2.3.1 + estree-walker: 3.0.3 + is-reference: 3.0.2 + locate-character: 3.0.0 + magic-string: 0.30.6 + periscopic: 3.1.0 dev: true /synckit@0.8.8: diff --git a/src/globals.d.ts b/src/globals.d.ts index e69de29..f352af0 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -0,0 +1,3 @@ +declare interface History { + onpushstate: (arg: { state: unknown }) => void +} diff --git a/src/index.ts b/src/index.ts index f2551d0..ee1dc3e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,10 @@ -console.log("Hello world") +import { calendarRoute } from "./routes/calendar" +import { injectNavigationHook } from "./utils/navigateHook" + +const routes = { + "/calendar": calendarRoute, +} + +injectNavigationHook((url: string) => { + routes[new URL(url).pathname]?.() +}) diff --git a/src/routes/calendar/index.ts b/src/routes/calendar/index.ts new file mode 100644 index 0000000..35f612f --- /dev/null +++ b/src/routes/calendar/index.ts @@ -0,0 +1,8 @@ +import { addOnpopupListener, injectPopupWatcher } from "./popupWatcher" +import { injectSlugButton } from "./slug/onpopup" + +export async function calendarRoute() { + await injectPopupWatcher() + + addOnpopupListener(injectSlugButton) +} diff --git a/src/routes/calendar/popupWatcher.ts b/src/routes/calendar/popupWatcher.ts new file mode 100644 index 0000000..1090bef --- /dev/null +++ b/src/routes/calendar/popupWatcher.ts @@ -0,0 +1,41 @@ +import { waitNotNull } from "@/utils/waitNotNull" + +type PopupCallback = (popup: HTMLElement) => void +export const onpopup = new Set() + +const observer = new MutationObserver((mutationList) => { + if (mutationList[0]!.addedNodes.length === 0) return + + let nestedView: HTMLElement = mutationList[0]!.addedNodes[0]! as HTMLElement + while (nestedView.childNodes.length === 1) { + nestedView = nestedView.childNodes[0]! as HTMLElement + } + + for (const listener of onpopup) { + listener(nestedView) + } +}) + +export async function injectPopupWatcher() { + console.log("Popup watcher injected") + const view = await waitNotNull(() => + document + .evaluate( + "/html/body/div/div[5]/div", + document, + null, + XPathResult.ANY_TYPE, + null, + ) + .iterateNext(), + ) + + observer.observe(view, { + childList: true, + }) + console.log("Observing for popups on", view) +} + +export function addOnpopupListener(listener: PopupCallback) { + onpopup.add(listener) +} diff --git a/src/routes/calendar/slug/onpopup.ts b/src/routes/calendar/slug/onpopup.ts new file mode 100644 index 0000000..9284c5b --- /dev/null +++ b/src/routes/calendar/slug/onpopup.ts @@ -0,0 +1,31 @@ +import Button from "./widgets/Button.svelte" + +export function copySlugFromPopupToCpipboard(popup) { + return function () { + const project = popup.querySelector( + 'a[href^="/project/"][href$="/about"]', + ).textContent + + const names = Array.from( + popup.querySelectorAll('a[href^="/profile"]'), + ).map((e) => (e as HTMLLinkElement).href.split("/").pop()!.split("@")[0]) + + const time = popup.querySelector("p").textContent.split(", ")[1] + + navigator.clipboard.writeText(`${project} ${time} (${names.join(",")})`) + } +} + +// TODO: Restack button to bottom due to stupid react virtual DOM which reuses component obviously ignoring our button + +export function injectSlugButton(popup) { + // eslint-disable-next-line no-new + new Button({ + target: popup, + props: { + text: "Copy slug!", + clickedText: "Copied!", + onclick: copySlugFromPopupToCpipboard(popup), + }, + }) +} diff --git a/src/routes/calendar/slug/widgets/Button.svelte b/src/routes/calendar/slug/widgets/Button.svelte new file mode 100644 index 0000000..393c0c3 --- /dev/null +++ b/src/routes/calendar/slug/widgets/Button.svelte @@ -0,0 +1,58 @@ + + +
+ +
+ + diff --git a/src/utils/navigateHook.ts b/src/utils/navigateHook.ts new file mode 100644 index 0000000..942ea4d --- /dev/null +++ b/src/utils/navigateHook.ts @@ -0,0 +1,28 @@ +function pathnameHash() { + return unsafeWindow.location.pathname.replace("/", "") +} + +export function injectNavigationHook(callback) { + let hash = pathnameHash() + + const H = unsafeWindow.history + const oldPushState = H.pushState + H.pushState = function (...args) { + if (typeof H.onpushstate == "function") { + H.onpushstate(args[0]) + } + return oldPushState.apply(H, args) + } + + H.onpushstate = () => { + if (pathnameHash() === hash) return + + hash = pathnameHash() + callback(unsafeWindow.location.href) + } + + unsafeWindow.addEventListener("popstate", H.onpushstate) + + // Call first time manually since page load doesent count as navigation + callback(unsafeWindow.location.href) +} diff --git a/src/utils/waitNotNull.ts b/src/utils/waitNotNull.ts new file mode 100644 index 0000000..11f0a1a --- /dev/null +++ b/src/utils/waitNotNull.ts @@ -0,0 +1,23 @@ +// Magic! don't look!!! + +export async function waitNotNull( + func: () => Promise | T | null, + timeout = 10000, + interval = 1000, +): Promise { + return new Promise((res, rej) => { + let time = timeout + const i = setInterval(async () => { + const c = await func() + time -= interval + if (time <= 0) { + clearInterval(i) + rej!() + } + if (!c) return + + clearInterval(i) + res!(c) + }, interval) + }) +} diff --git a/tsconfig.json b/tsconfig.json index 20839bc..985bd2b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,12 @@ "compilerOptions": { /* Language and Environment */ "target": "ESNext", - "lib": ["ESNext", "DOM"], + "lib": [ + "ESNext", + "DOM" + ], "experimentalDecorators": true, "emitDecoratorMetadata": true, - /* Modules */ "module": "ESNext", "moduleResolution": "Bundler", @@ -13,18 +15,19 @@ "allowSyntheticDefaultImports": true, "baseUrl": ".", "paths": { - "@/*": ["src/*"], - "@root/*": ["./*"] + "@/*": [ + "src/*" + ], + "@root/*": [ + "./*" + ] }, - /* Emit */ "noEmit": true, - /* Interop Constraints */ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, - /* Type Checking */ "strict": true, "noImplicitThis": true, @@ -33,10 +36,8 @@ "noImplicitReturns": true, "noImplicitOverride": true, "noUncheckedIndexedAccess": true, - /* Completeness */ "skipLibCheck": true, - /* Allow implicit any by default */ "noImplicitAny": false }, diff --git a/tsup.config.ts b/tsup.config.ts index c85fe88..809abb0 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,5 +1,7 @@ // eslint-disable-next-line import/no-unresolved import { userscript } from "esbuild-plugin-userscript" +import sveltePlugin from "esbuild-svelte" +import preprocess from "svelte-preprocess" import { defineConfig } from "tsup" import pkg from "@root/package.json" assert { type: "json" } @@ -11,11 +13,11 @@ const metadata = { description: pkg.description, license: pkg.license, version: pkg.version, - namespace: pkg.homepage, - match: pkg.homepage, + namespace: "https://edu.21-school.ru", + match: "https://edu.21-school.ru/*", "run-at": "document-body", connect: [], - grant: [], + grant: ["GM.addStyle"], } // eslint-disable-next-line import/no-default-export @@ -30,7 +32,17 @@ export default defineConfig({ outExtension: () => { return { js: ".user.js", dts: ".user.dts" } }, + injectStyle(css) { + return ` + GM.addStyle(\`\n${css.slice(1, -1)}\`) + ` + }, esbuildPlugins: [ + sveltePlugin({ + preprocess: preprocess({ + typescript: true, + }), + }), userscript({ metadata, proxy: dev