diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-06-11 19:31:03 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-06-11 19:31:03 +0900 |
| commit | 182a1bf653ecfbcf76e4530b3077c6252b0d4827 (patch) | |
| tree | 45d1472747d4cac017e96616f844292f5785ccdd /packages | |
| parent | 12.110.1 (diff) | |
| parent | 12.111.0 (diff) | |
| download | misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.gz misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.tar.bz2 misskey-182a1bf653ecfbcf76e4530b3077c6252b0d4827.zip | |
Merge pull request #8783 from misskey-dev/develop
Release: 12.111.0
Diffstat (limited to 'packages')
479 files changed, 10887 insertions, 12085 deletions
diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs index e2e31e9e33..5a06889dcd 100644 --- a/packages/backend/.eslintrc.cjs +++ b/packages/backend/.eslintrc.cjs @@ -6,4 +6,27 @@ module.exports = { extends: [ '../shared/.eslintrc.js', ], + rules: { + 'import/order': ['warn', { + 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + 'pathGroups': [ + { + 'pattern': '@/**', + 'group': 'external', + 'position': 'after' + } + ], + }], + 'no-restricted-globals': [ + 'error', + { + 'name': '__dirname', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + }, + { + 'name': '__filename', + 'message': 'Not in ESModule. Use `import.meta.url` instead.' + } + ] + }, }; diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json index 26628066eb..87c571cfd6 100644 --- a/packages/backend/.mocharc.json +++ b/packages/backend/.mocharc.json @@ -5,6 +5,6 @@ "loader=./test/loader.js" ], "slow": 1000, - "timeout": 35000, + "timeout": 10000, "exit": true } diff --git a/packages/backend/.vscode/settings.json b/packages/backend/.vscode/settings.json index df3bf05071..9fb3b29d4a 100644 --- a/packages/backend/.vscode/settings.json +++ b/packages/backend/.vscode/settings.json @@ -2,5 +2,9 @@ "typescript.tsdk": "node_modules\\typescript\\lib", "path-intellisense.mappings": { "@": "${workspaceRoot}/packages/backend/src/" + }, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": true } } diff --git a/packages/backend/migration/1651224615271-foreign-key.js b/packages/backend/migration/1651224615271-foreign-key.js new file mode 100644 index 0000000000..44ba7fb6c4 --- /dev/null +++ b/packages/backend/migration/1651224615271-foreign-key.js @@ -0,0 +1,89 @@ +export class foreignKeyReports1651224615271 { + name = 'foreignKeyReports1651224615271' + + async up(queryRunner) { + await Promise.all([ + queryRunner.query(`ALTER INDEX "public"."IDX_seoignmeoprigmkpodgrjmkpormg" RENAME TO "IDX_c8cc87bd0f2f4487d17c651fbf"`), + queryRunner.query(`DROP INDEX "public"."IDX_note_on_channelId_and_id_desc"`), + + // remove unnecessary default null, see also down + queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "followersUri" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "session" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "name" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "iconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareName" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareVersion" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "name" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerName" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerEmail" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "themeColor" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "description" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "channelId" DROP DEFAULT`), + queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" DROP DEFAULT`), + + queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`), + queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`), + queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`), + + queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => { + queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + }), + + queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`), + queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`), + queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`), + + queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" TO "FK_a9ca79ad939bf06066b81c9d3aa"`), + + queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" ADD VALUE 'pollEnded' AFTER 'pollVote'`), + ]); + } + + async down(queryRunner) { + await Promise.all([ + // There is no ALTER TYPE REMOVE VALUE query, so the reverse operation is a bit more complex + queryRunner.query(`UPDATE "user_profile" SET "mutingNotificationTypes" = array_remove("mutingNotificationTypes", 'pollEnded')`) + .then(() => + queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`) + ).then(() => + queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`) + ).then(() => + queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`) + ).then(() => + queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`) + ), + + queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" TO "FK_3126dd7c502c9e4d7597ef7ef10"`), + + queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`), + queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`), + queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`), + queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`), + queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`), + + queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" SET DEFAULT '{}'`), + queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`), + + queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`), + queryRunner.query(`DROP INDEX "public"."IDX_f22169eb10657bded6d875ac8f"`), + queryRunner.query(`DROP INDEX "public"."IDX_315c779174fe8247ab324f036e"`), + + /* DEFAULT's are not set again because if the column can be NULL, then DEFAULT NULL is not necessary. + see also https://github.com/typeorm/typeorm/issues/7579#issuecomment-835423615 */ + + queryRunner.query(`CREATE INDEX "IDX_note_on_channelId_and_id_desc" ON "note" ("id", "channelId") `), + queryRunner.query(`ALTER INDEX "public"."IDX_c8cc87bd0f2f4487d17c651fbf" RENAME TO "IDX_seoignmeoprigmkpodgrjmkpormg"`), + ]); + } +} diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js new file mode 100644 index 0000000000..8da1fd7fbb --- /dev/null +++ b/packages/backend/migration/1652859567549-uniform-themecolor.js @@ -0,0 +1,36 @@ +import tinycolor from 'tinycolor2'; + +export class uniformThemecolor1652859567549 { + name = 'uniformThemecolor1652859567549' + + async up(queryRunner) { + const formatColor = (color) => { + let tc = new tinycolor(color); + if (tc.isValid()) { + return tc.toHexString(); + } else { + return null; + } + }; + + await queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL') + .then(instances => Promise.all(instances.map(instance => { + // update theme color to uniform format, e.g. #00ff00 + // invalid theme colors get set to null + return queryRunner.query('UPDATE "instance" SET "themeColor" = $1 WHERE "id" = $2', [formatColor(instance.themeColor), instance.id]); + }))); + + // also fix own theme color + await queryRunner.query('SELECT "themeColor" FROM "meta" WHERE "themeColor" IS NOT NULL LIMIT 1') + .then(metas => { + if (metas.length > 0) { + return queryRunner.query('UPDATE "meta" SET "themeColor" = $1', [formatColor(metas[0].themeColor)]); + } + }); + } + + async down(queryRunner) { + // The original representation is not stored, so migrating back is not possible. + // The new format also works in older versions so this is not a problem. + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 314818f80b..2186dcc6a9 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -6,7 +6,7 @@ "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "watch": "node watch.mjs", "lint": "eslint --quiet \"src/**/*.ts\"", - "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "test": "npm run mocha" }, "resolutions": { @@ -14,71 +14,25 @@ "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.1", + "@bull-board/koa": "3.11.1", + "@discordapp/twemoji": "14.0.2", "@elastic/elasticsearch": "7.11.0", "@koa/cors": "3.1.0", "@koa/multer": "3.0.0", "@koa/router": "9.0.1", - "@sinonjs/fake-timers": "9.1.1", + "@peertube/http-signature": "1.6.0", + "@sinonjs/fake-timers": "9.1.2", "@syuilo/aiscript": "0.11.1", - "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.8", - "@types/cbor": "6.0.0", - "@types/escape-regexp": "0.0.1", - "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.5", - "@types/jsdom": "16.2.14", - "@types/jsonld": "1.5.6", - "@types/koa": "2.13.4", - "@types/koa-bodyparser": "4.3.7", - "@types/koa-cors": "0.0.2", - "@types/koa-favicon": "2.0.21", - "@types/koa-logger": "3.1.2", - "@types/koa-mount": "4.0.1", - "@types/koa-send": "4.1.3", - "@types/koa-views": "7.0.0", - "@types/koa__cors": "3.1.1", - "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.11", - "@types/mocha": "9.1.0", - "@types/node": "17.0.23", - "@types/node-fetch": "3.0.3", - "@types/nodemailer": "6.4.4", - "@types/oauth": "0.9.1", - "@types/parse5": "6.0.3", - "@types/portscanner": "2.1.1", - "@types/pug": "2.0.6", - "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.2", - "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.3", - "@types/redis": "4.0.11", - "@types/rename": "1.0.4", - "@types/sanitize-html": "2.6.2", - "@types/sharp": "0.30.1", - "@types/sinonjs__fake-timers": "8.1.2", - "@types/speakeasy": "2.0.7", - "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.3", - "@types/uuid": "8.3.4", - "@types/web-push": "3.3.2", - "@types/websocket": "1.0.5", - "@types/ws": "8.5.3", - "@typescript-eslint/eslint-plugin": "5.18.0", - "@typescript-eslint/parser": "5.18.0", - "@bull-board/koa": "3.10.3", "abort-controller": "3.0.0", "ajv": "8.11.0", - "archiver": "5.3.0", + "archiver": "5.3.1", "autobind-decorator": "2.4.0", "autwh": "0.1.0", - "aws-sdk": "2.1111.0", + "aws-sdk": "2.1152.0", "bcryptjs": "2.4.3", "blurhash": "1.1.5", - "broadcast-channel": "4.10.0", - "bull": "4.8.1", + "bull": "4.8.3", "cacheable-lookup": "6.0.4", - "cafy": "15.2.1", "cbor": "8.1.0", "chalk": "5.0.1", "chalk-template": "0.4.0", @@ -88,22 +42,19 @@ "date-fns": "2.28.0", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", - "eslint": "8.13.0", - "eslint-plugin-import": "2.26.0", "feed": "4.2.2", - "file-type": "17.1.1", + "file-type": "17.1.2", "fluent-ffmpeg": "2.1.2", - "got": "12.0.3", + "got": "12.1.0", "hpagent": "0.1.2", - "http-signature": "1.3.6", - "ip-cidr": "3.0.4", + "ip-cidr": "3.0.10", "is-svg": "4.3.2", "js-yaml": "4.1.0", "jsdom": "19.0.0", "json5": "2.2.1", "json5-loader": "4.0.1", - "jsonld": "5.2.0", - "jsrsasign": "8.0.20", + "jsonld": "6.0.0", + "jsrsasign": "10.5.24", "koa": "2.13.4", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", @@ -113,19 +64,18 @@ "koa-send": "5.0.1", "koa-slow": "2.1.0", "koa-views": "7.0.2", - "mfm-js": "0.21.0", + "mfm-js": "0.22.1", "mime-types": "2.1.35", "misskey-js": "0.0.14", - "mocha": "9.2.2", + "mocha": "10.0.0", "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "3.2.3", - "nodemailer": "6.7.3", + "node-fetch": "3.2.6", + "nodemailer": "6.7.5", "os-utils": "0.0.14", "parse5": "6.0.1", "pg": "8.7.3", - "portscanner": "2.2.0", "private-ip": "2.3.3", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -144,35 +94,83 @@ "rndstr": "1.0.0", "s-age": "1.1.2", "sanitize-html": "2.7.0", - "semver": "7.3.6", - "sharp": "0.30.3", + "semver": "7.3.7", + "sharp": "0.29.3", "speakeasy": "2.0.0", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", "style-loader": "3.3.1", - "summaly": "2.5.0", + "summaly": "2.5.1", "syslog-pro": "1.0.0", - "systeminformation": "5.11.9", + "systeminformation": "5.11.16", "tinycolor2": "1.4.2", "tmp": "0.2.1", - "ts-loader": "9.2.8", - "ts-node": "10.7.0", - "tsc-alias": "1.4.1", - "tsconfig-paths": "3.14.1", + "ts-loader": "9.3.0", + "ts-node": "10.8.1", + "tsc-alias": "1.6.9", + "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", - "typeorm": "0.3.5", - "typescript": "4.6.3", + "typeorm": "0.3.6", "ulid": "2.3.0", "unzipper": "0.10.11", "uuid": "8.3.2", - "web-push": "3.4.5", + "web-push": "3.5.0", "websocket": "1.0.34", - "ws": "8.5.0", - "xev": "2.0.1" + "ws": "8.8.0", + "xev": "3.0.2" }, "devDependencies": { - "@redocly/openapi-core": "1.0.0-beta.93", + "@redocly/openapi-core": "1.0.0-beta.97", + "@types/semver": "7.3.9", + "@types/bcryptjs": "2.4.2", + "@types/bull": "3.15.8", + "@types/cbor": "6.0.0", + "@types/escape-regexp": "0.0.1", "@types/fluent-ffmpeg": "2.1.20", + "@types/is-url": "1.2.30", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", + "@types/jsonld": "1.5.6", + "@types/jsrsasign": "10.5.1", + "@types/koa": "2.13.4", + "@types/koa-bodyparser": "4.3.7", + "@types/koa-cors": "0.0.2", + "@types/koa-favicon": "2.0.21", + "@types/koa-logger": "3.1.2", + "@types/koa-mount": "4.0.1", + "@types/koa-send": "4.1.3", + "@types/koa-views": "7.0.0", + "@types/koa__cors": "3.1.1", + "@types/koa__multer": "2.0.4", + "@types/koa__router": "8.0.11", + "@types/mocha": "9.1.1", + "@types/node": "17.0.41", + "@types/node-fetch": "3.0.3", + "@types/nodemailer": "6.4.4", + "@types/oauth": "0.9.1", + "@types/parse5": "6.0.3", + "@types/pug": "2.0.6", + "@types/punycode": "2.1.0", + "@types/qrcode": "1.4.2", + "@types/random-seed": "0.3.3", + "@types/ratelimiter": "3.4.3", + "@types/redis": "4.0.11", + "@types/rename": "1.0.4", + "@types/sanitize-html": "2.6.2", + "@types/sharp": "0.30.2", + "@types/sinonjs__fake-timers": "8.1.2", + "@types/speakeasy": "2.0.7", + "@types/tinycolor2": "1.4.3", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", + "@types/web-push": "3.3.2", + "@types/websocket": "1.0.5", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", + "typescript": "4.7.3", + "eslint": "8.17.0", + "eslint-plugin-import": "2.26.0", "cross-env": "7.0.3", "execa": "6.1.0" } diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts index 8d484312dc..d1f9cd9552 100644 --- a/packages/backend/src/@types/http-signature.d.ts +++ b/packages/backend/src/@types/http-signature.d.ts @@ -1,5 +1,5 @@ -declare module 'http-signature' { - import { IncomingMessage, ClientRequest } from 'http'; +declare module '@peertube/http-signature' { + import { IncomingMessage, ClientRequest } from 'node:http'; interface ISignature { keyId: string; diff --git a/packages/backend/src/@types/jsrsasign.d.ts b/packages/backend/src/@types/jsrsasign.d.ts deleted file mode 100644 index bb52f8f64e..0000000000 --- a/packages/backend/src/@types/jsrsasign.d.ts +++ /dev/null @@ -1,800 +0,0 @@ -// Attention: Partial Type Definition - -declare module 'jsrsasign' { - //// HELPER TYPES - - /** - * Attention: The value might be changed by the function. - */ - type Mutable<T> = T; - - /** - * Deprecated: The function might be deleted in future release. - */ - type Deprecated<T> = T; - - //// COMMON TYPES - - /** - * byte number - */ - type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255; - - /** - * hexadecimal string /[0-9A-F]/ - */ - type HexString = string; - - /** - * binary string /[01]/ - */ - type BinString = string; - - /** - * base64 string /[A-Za-z0-9+/]=+/ - */ - type Base64String = string; - - /** - * base64 URL encoded string /[A-Za-z0-9_-]/ - */ - type Base64URLString = string; - - /** - * time value (ex. "151231235959Z") - */ - type TimeValue = string; - - /** - * OID string (ex. '1.2.3.4.567') - */ - type OID = string; - - /** - * OID name - */ - type OIDName = string; - - /** - * PEM formatted string - */ - type PEM = string; - - //// ASN1 TYPES - - class ASN1Object { - public isModified: boolean; - - public hTLV: ASN1TLV; - - public hT: ASN1T; - - public hL: ASN1L; - - public hV: ASN1V; - - public getLengthHexFromValue(): HexString; - - public getEncodedHex(): ASN1TLV; - - public getValueHex(): ASN1V; - - public getFreshValueHex(): ASN1V; - } - - class DERAbstractStructured extends ASN1Object { - constructor(params?: Partial<Record<'array', ASN1Object[]>>); - - public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void; - - public appendASN1Object(asn1Object: ASN1Object): void; - } - - class DERSequence extends DERAbstractStructured { - constructor(params?: Partial<Record<'array', ASN1Object[]>>); - - public getFreshValueHex(): ASN1V; - } - - //// ASN1HEX TYPES - - /** - * ASN.1 DER encoded data (hexadecimal string) - */ - type ASN1S = HexString; - - /** - * index of something - */ - type Idx<T extends { [idx: string]: unknown } | { [idx: number]: unknown }> = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never; - - /** - * byte length of something - */ - type ByteLength<T extends { length: unknown }> = T['length']; - - /** - * ASN.1 L(length) (hexadecimal string) - */ - type ASN1L = HexString; - - /** - * ASN.1 T(tag) (hexadecimal string) - */ - type ASN1T = HexString; - - /** - * ASN.1 V(value) (hexadecimal string) - */ - type ASN1V = HexString; - - /** - * ASN.1 TLV (hexadecimal string) - */ - type ASN1TLV = HexString; - - /** - * ASN.1 object string - */ - type ASN1ObjectString = string; - - /** - * nth - */ - type Nth = number; - - /** - * ASN.1 DER encoded OID value (hexadecimal string) - */ - type ASN1OIDV = HexString; - - class ASN1HEX { - public static getLblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1L>; - - public static getL(s: ASN1S, idx: Idx<ASN1S>): ASN1L; - - public static getVblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1V>; - - public static getVidx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1V>; - - public static getV(s: ASN1S, idx: Idx<ASN1S>): ASN1V; - - public static getTLV(s: ASN1S, idx: Idx<ASN1S>): ASN1TLV; - - public static getNextSiblingIdx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1ObjectString>; - - public static getChildIdx(h: ASN1S, pos: Idx<ASN1S>): Idx<ASN1ObjectString>[]; - - public static getNthChildIdx(h: ASN1S, idx: Idx<ASN1S>, nth: Nth): Idx<ASN1ObjectString>; - - public static getIdxbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): Idx<Mutable<Nth[]>>; - - public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV; - - // eslint:disable-next-line:bool-param-default - public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V; - - public static hextooidstr(hex: ASN1OIDV): OID; - - public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record<string, unknown>, idx?: Idx<ASN1S>, indent?: string): string; - - public static isASN1HEX(hex: string): hex is HexString; - - public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName; - } - - //// BIG INTEGER TYPES (PARTIAL) - - class BigInteger { - constructor(a: null); - - constructor(a: number, b: SecureRandom); - - constructor(a: number, b: number, c: SecureRandom); - - constructor(a: unknown); - - constructor(a: string, b: number); - - public am(i: number, x: number, w: number, j: number, c: number, n: number): number; - - public DB: number; - - public DM: number; - - public DV: number; - - public FV: number; - - public F1: number; - - public F2: number; - - protected copyTo(r: Mutable<BigInteger>): void; - - protected fromInt(x: number): void; - - protected fromString(s: string, b: number): void; - - protected clamp(): void; - - public toString(b: number): string; - - public negate(): BigInteger; - - public abs(): BigInteger; - - public compareTo(a: BigInteger): number; - - public bitLength(): number; - - protected dlShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected drShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected lShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected rShiftTo(n: number, r: Mutable<BigInteger>): void; - - protected subTo(a: BigInteger, r: Mutable<BigInteger>): void; - - protected multiplyTo(a: BigInteger, r: Mutable<BigInteger>): void; - - protected squareTo(r: Mutable<BigInteger>): void; - - protected divRemTo(m: BigInteger, q: Mutable<BigInteger>, r: Mutable<BigInteger>): void; - - public mod(a: BigInteger): BigInteger; - - protected invDigit(): number; - - protected isEven(): boolean; - - protected exp(e: number, z: Classic | Montgomery): BigInteger; - - public modPowInt(e: number, m: BigInteger): BigInteger; - - public static ZERO: BigInteger; - - public static ONE: BigInteger; - } - - class Classic { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable<BigInteger>): void; - - public mulTo(x: BigInteger, r: Mutable<BigInteger>): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void; - } - - class Montgomery { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable<BigInteger>): void; - - public mulTo(x: BigInteger, r: Mutable<BigInteger>): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void; - } - - //// KEYUTIL TYPES - - type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type AlgList = { - 'AES-256-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 32; ivlen: 16; }; - 'AES-192-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 24; ivlen: 16; }; - 'AES-128-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 16; ivlen: 16; }; - 'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8; }; - 'DES-CBC': { 'proc': DecryptDES; 'eproc': EncryptDES; keylen: 8; ivlen: 8; }; - }; - - type AlgName = keyof AlgList; - - type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA'; - - type GetKeyRSAParam = RSAKey | { - n: BigInteger; - e: number; - } | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | { - n: BigInteger; - e: number; - d: BigInteger; - } | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd', Base64URLString>; - - type GetKeyECDSAParam = KJUR.crypto.ECDSA | { - curve: KJUR.crypto.CurveName; - xy: HexString; - } | { - curve: KJUR.crypto.CurveName; - d: HexString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - }; - - type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>; - - type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string; - - class KEYUTIL { - public version: '1.0.0'; - - public parsePKCS5PEM(sPKCS5PEM: PEM): Partial<Record<'type' | 's', string>> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>); - - public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>; - - public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String; - - public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString; - - public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM; - - public parseHexOfEncryptedPKCS8(sHEX: HexString): { - ciphertext: ASN1V; - encryptionSchemeAlg: 'TripleDES'; - encryptionSchemeIV: ASN1V; - pbkdf2Salt: ASN1V; - pbkdf2Iter: number; - }; - - public getPBKDF2KeyHexFromParam(info: ReturnType<this['parseHexOfEncryptedPKCS8']>, passcode: string): HexString; - - private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString; - - public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>; - - public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): { - algparam: ASN1V | null; - algoid: ASN1V; - keyidx: Idx<ASN1V>; - }; - - public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>; - - public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>; - - public parsePublicPKCS8Hex(pkcs8PubHex: HexString): { - algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null; - algoid: ASN1V; - key: ASN1V; - }; - - public static getKey(param: GetKeyRSAParam): RSAKey; - - public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA; - - public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA; - - public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>; - - public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>; - - public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do - - public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>; - - public static getJWKFromKey(keyObj: RSAKey): { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString>; - - public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - }; - } - - //// KJUR NAMESPACE (PARTIAL) - - namespace KJUR { - namespace crypto { - type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1'; - - class DSA { - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'DSA'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void; - - public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void; - - public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void; - - public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void; - - public signWithMessageHash(sHashHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - } - - class ECDSA { - constructor(params?: { - curve?: CurveName; - prv?: HexString; - pub?: HexString; - }); - - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'EC'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public getBigRandom(limit: BigInteger): BigInteger; - - public setNamedCurve(curveName: CurveName): void; - - public setPrivateKeyHex(prvKeyHex: HexString): void; - - public setPublicKeyHex(pubKeyHex: HexString): void; - - public getPublicKeyXYHex(): Record<'x' | 'y', HexString>; - - public getShortNISTPCurveName(): 'P-256' | 'P-384' | null; - - public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>; - - public signWithMessageHash(hashHex: HexString): HexString; - - public signHex(hashHex: HexString, privHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - - public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>; - - public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>; - - public static asn1SigToConcatSig(asn1Sig: HexString): HexString; - - public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV; - - public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV; - - public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV; - - public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null; - } - - class Signature { - constructor(params?: ({ - alg: string; - prov?: string; - } | {}) & ({ - psssaltlen: number; - } | {}) & ({ - prvkeypem: PEM; - prvkeypas?: never; - } | {})); - - private _setAlgNames(): void; - - private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString; - - public setAlgAndProvider(alg: string, prov: string): void; - - public init(key: GetKeyParam, pass?: string): void; - - public updateString(str: string): void; - - public updateHex(hex: HexString): void; - - public sign(): HexString; - - public signString(str: string): HexString; - - public signHex(hex: HexString): HexString; - - public verify(hSigVal: string): boolean | 0; - } - } - } - - //// RSAKEY TYPES - - class RSAKey { - public n: BigInteger | null; - - public e: number; - - public d: BigInteger | null; - - public p: BigInteger | null; - - public q: BigInteger | null; - - public dmp1: BigInteger | null; - - public dmq1: BigInteger | null; - - public coeff: BigInteger | null; - - public type: 'RSA'; - - public isPrivate?: boolean; - - public isPublic?: boolean; - - //// RSA PUBLIC - - protected doPublic(x: BigInteger): BigInteger; - - public setPublic(N: BigInteger, E: number): void; - - public setPublic(N: HexString, E: HexString): void; - - public encrypt(text: string): HexString | null; - - public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null; - - //// RSA PRIVATE - - protected doPrivate(x: BigInteger): BigInteger; - - public setPrivate(N: BigInteger, E: number, D: BigInteger): void; - - public setPrivate(N: HexString, E: HexString, D: HexString): void; - - public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void; - - public generate(B: number, E: HexString): void; - - public decrypt(ctext: HexString): string; - - public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null; - - //// RSA PEM - - public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[]; - - public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[]; - - public readPrivateKeyFromPEMString(keyPEM: PEM): void; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS5PubKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: Nth): void; - - //// RSA SIGN - - public sign(s: string, hashAlg: string): HexString; - - public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString; - - public signPSS(s: string, hashAlg: string, sLen: number): HexString; - - public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString; - - public verify(sMsg: string, hSig: HexString): boolean | 0; - - public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0; - - public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public static SALT_LEN_HLEN: -1; - - public static SALT_LEN_MAX: -2; - - public static SALT_LEN_RECOVER: -2; - } - - /// RNG TYPES - class SecureRandom { - public nextBytes(ba: Mutable<ByteNumber[]>): void; - } - - //// X509 TYPES - - type ExtInfo = { - critical: boolean; - oid: OID; - vidx: Idx<ASN1V>; - }; - - type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>; - - type ExtCertificatePolicy = { - id: OIDName; - } & Partial<{ - cps: string; - } | { - unotice: string; - }>; - - class X509 { - public hex: HexString | null; - - public version: number; - - public foffset: number; - - public aExtInfo: null; - - public getVersion(): number; - - public getSerialNumberHex(): ASN1V; - - public getSignatureAlgorithmField(): OIDName; - - public getIssuerHex(): ASN1TLV; - - public getIssuerString(): HexString; - - public getSubjectHex(): ASN1TLV; - - public getSubjectString(): HexString; - - public getNotBefore(): TimeValue; - - public getNotAfter(): TimeValue; - - public getPublicKeyHex(): ASN1TLV; - - public getPublicKeyIdx(): Idx<Mutable<Nth[]>>; - - public getPublicKeyContentIdx(): Idx<Mutable<Nth[]>>; - - public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public getSignatureAlgorithmName(): OIDName; - - public getSignatureValueHex(): ASN1V; - - public verifySignature(pubKey: GetKeyParam): boolean | 0; - - public parseExt(): void; - - public getExtInfo(oidOrName: OID | string): ExtInfo | undefined; - - public getExtBasicConstraints(): ExtInfo | {} | { - cA: true; - pathLen?: number; - }; - - public getExtKeyUsageBin(): BinString; - - public getExtKeyUsageString(): string; - - public getExtSubjectKeyIdentifier(): ASN1V | undefined; - - public getExtAuthorityKeyIdentifier(): { - kid: ASN1V; - } | undefined; - - public getExtExtKeyUsageName(): OIDName[] | undefined; - - public getExtSubjectAltName(): Deprecated<string[]>; - - public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined; - - public getExtCRLDistributionPointsURI(): string[] | undefined; - - public getExtAIAInfo(): ExtAIAInfo | undefined; - - public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined; - - public readCertPEM(sCertPEM: PEM): void; - - public readCertHex(sCertHex: HexString): void; - - public getInfo(): string; - - public static hex2dn(hex: HexString, idx?: Idx<HexString>): string; - - public static hex2rdn(hex: HexString, idx?: Idx<HexString>): string; - - public static hex2attrTypeValue(hex: HexString, idx?: Idx<HexString>): string; - - public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): { - algparam: ASN1V | null; - leyhex: ASN1V; - algoid: ASN1V; - }; - } -} diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts index 5bb20a729f..c3d0592256 100644 --- a/packages/backend/src/boot/index.ts +++ b/packages/backend/src/boot/index.ts @@ -1,6 +1,6 @@ import cluster from 'node:cluster'; import chalk from 'chalk'; -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Logger from '@/services/logger.js'; import { envOption } from '../env.js'; @@ -12,7 +12,7 @@ import { workerMain } from './worker.js'; const logger = new Logger('core', 'cyan'); const clusterLogger = logger.createSubLogger('cluster', 'orange', false); -const ev = new Xev.default(); +const ev = new Xev(); /** * Init process diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts index 09d20f9361..bf51960482 100644 --- a/packages/backend/src/boot/master.ts +++ b/packages/backend/src/boot/master.ts @@ -5,7 +5,6 @@ import * as os from 'node:os'; import cluster from 'node:cluster'; import chalk from 'chalk'; import chalkTemplate from 'chalk-template'; -import * as portscanner from 'portscanner'; import semver from 'semver'; import Logger from '@/services/logger.js'; @@ -48,11 +47,6 @@ function greet() { bootLogger.info(`Misskey v${meta.version}`, null, true); } -function isRoot() { - // maybe process.getuid will be undefined under not POSIX environment (e.g. Windows) - return process.getuid != null && process.getuid() === 0; -} - /** * Init master process */ @@ -67,7 +61,6 @@ export async function masterMain() { showNodejsVersion(); config = loadConfigBoot(); await connectDb(); - await validatePort(config); } catch (e) { bootLogger.error('Fatal error occurred during initialization', null, true); process.exit(1); @@ -97,8 +90,6 @@ function showEnvironment(): void { logger.warn('The environment is not in production mode.'); logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true); } - - logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`); } function showNodejsVersion(): void { @@ -152,29 +143,6 @@ async function connectDb(): Promise<void> { } } -async function validatePort(config: Config): Promise<void> { - const isWellKnownPort = (port: number) => port < 1024; - - async function isPortAvailable(port: number): Promise<boolean> { - return await portscanner.checkPortStatus(port, '127.0.0.1') === 'closed'; - } - - if (config.port == null || Number.isNaN(config.port)) { - bootLogger.error('The port is not configured. Please configure port.', null, true); - process.exit(1); - } - - if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) { - bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true); - process.exit(1); - } - - if (!await isPortAvailable(config.port)) { - bootLogger.error(`Port ${config.port} is already in use`, null, true); - process.exit(1); - } -} - async function spawnWorkers(limit: number = 1) { const workers = Math.min(limit, os.cpus().length); bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); @@ -186,6 +154,10 @@ function spawnWorker(): Promise<void> { return new Promise(res => { const worker = cluster.fork(); worker.on('message', message => { + if (message === 'listenFailed') { + bootLogger.error(`The server Listen failed due to the previous error.`); + process.exit(1); + } if (message !== 'ready') return; res(); }); diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts index 7f765463e4..9654a4f3b0 100644 --- a/packages/backend/src/config/load.ts +++ b/packages/backend/src/config/load.ts @@ -25,6 +25,7 @@ const path = process.env.NODE_ENV === 'test' export default function load() { const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8')); + const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8')); const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source; const mixin = {} as Mixin; @@ -45,6 +46,7 @@ export default function load() { mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`; mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`; mixin.userAgent = `Misskey/${meta.version} (${config.url})`; + mixin.clientEntry = clientManifest['src/init.ts']; if (!config.redis.prefix) config.redis.prefix = mixin.host; diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts index 58a27803cb..948545db7a 100644 --- a/packages/backend/src/config/types.ts +++ b/packages/backend/src/config/types.ts @@ -80,6 +80,7 @@ export type Mixin = { authUrl: string; driveUrl: string; userAgent: string; + clientEntry: string; }; export type Config = Source & Mixin; diff --git a/packages/backend/src/daemons/queue-stats.ts b/packages/backend/src/daemons/queue-stats.ts index bfef110545..1535abc6af 100644 --- a/packages/backend/src/daemons/queue-stats.ts +++ b/packages/backend/src/daemons/queue-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import { deliverQueue, inboxQueue } from '../queue/queues.js'; -const ev = new Xev.default(); +const ev = new Xev(); const interval = 10000; diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts index 327305ccc5..faf4e6e4a4 100644 --- a/packages/backend/src/daemons/server-stats.ts +++ b/packages/backend/src/daemons/server-stats.ts @@ -1,8 +1,8 @@ import si from 'systeminformation'; -import { default as Xev } from 'xev'; +import Xev from 'xev'; import * as osUtils from 'os-utils'; -const ev = new Xev.default(); +const ev = new Xev(); const interval = 2000; diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index eb5fc2e186..298f6713ea 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -5,9 +5,6 @@ pg.types.setTypeParser(20, Number); import { Logger, DataSource } from 'typeorm'; import * as highlight from 'cli-highlight'; import config from '@/config/index.js'; -import { envOption } from '../env.js'; - -import { dbLogger } from './logger.js'; import { User } from '@/models/entities/user.js'; import { DriveFile } from '@/models/entities/drive-file.js'; @@ -74,6 +71,9 @@ import { UserPending } from '@/models/entities/user-pending.js'; import { entities as charts } from '@/services/chart/entities.js'; import { Webhook } from '@/models/entities/webhook.js'; +import { envOption } from '../env.js'; +import { dbLogger } from './logger.js'; +import { redisClient } from './redis.js'; const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false); @@ -208,16 +208,25 @@ export const db = new DataSource({ migrations: ['../../migration/*.js'], }); -export async function initDb() { +export async function initDb(force = false) { + if (force) { + if (db.isInitialized) { + await db.destroy(); + } + await db.initialize(); + return; + } + if (db.isInitialized) { // nop } else { - await db.connect(); + await db.initialize(); } } export async function resetDb() { const reset = async () => { + await redisClient.FLUSHDB(); const tables = await db.query(`SELECT relname AS "table" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts index 623cb0e71c..15110b6b70 100644 --- a/packages/backend/src/mfm/from-html.ts +++ b/packages/backend/src/mfm/from-html.ts @@ -6,6 +6,9 @@ const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/; const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/; export function fromHtml(html: string, hashtagNames?: string[]): string { + // some AP servers like Pixelfed use br tags as well as newlines + html = html.replace(/<br\s?\/?>\r?\n/gi, '\n'); + const dom = parse5.parseFragment(html); let text = ''; diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts index 01bbe98a85..e5b911ed32 100644 --- a/packages/backend/src/misc/cache.ts +++ b/packages/backend/src/misc/cache.ts @@ -48,6 +48,7 @@ export class Cache<T> { // Cache MISS const value = await fetcher(); + this.set(key, value); return value; } diff --git a/packages/backend/src/misc/cafy-id.ts b/packages/backend/src/misc/cafy-id.ts deleted file mode 100644 index dd81c5c4cf..0000000000 --- a/packages/backend/src/misc/cafy-id.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Context } from 'cafy'; - -// eslint-disable-next-line @typescript-eslint/ban-types -export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> { - public readonly name = 'ID'; - - constructor(optional = false, nullable = false) { - super(optional, nullable); - - this.push((v: any) => { - if (typeof v !== 'string') { - return new Error('must-be-an-id'); - } - return true; - }); - } - - public getType() { - return super.getType('String'); - } - - public makeOptional(): ID<undefined> { - return new ID(true, false); - } - - public makeNullable(): ID<null> { - return new ID(false, true); - } - - public makeOptionalNullable(): ID<undefined | null> { - return new ID(true, true); - } -} diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts index 04604cf7d0..f07be634fb 100644 --- a/packages/backend/src/misc/create-temp.ts +++ b/packages/backend/src/misc/create-temp.ts @@ -1,10 +1,19 @@ import * as tmp from 'tmp'; -export function createTemp(): Promise<[string, any]> { - return new Promise<[string, any]>((res, rej) => { +export function createTemp(): Promise<[string, () => void]> { + return new Promise<[string, () => void]>((res, rej) => { tmp.file((e, path, fd, cleanup) => { if (e) return rej(e); res([path, cleanup]); }); }); } + +export function createTempDir(): Promise<[string, () => void]> { + return new Promise<[string, () => void]>((res, rej) => { + tmp.dir((e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + }); + }); +} diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts index 5417c10962..e855ac28ee 100644 --- a/packages/backend/src/misc/fetch-meta.ts +++ b/packages/backend/src/misc/fetch-meta.ts @@ -20,9 +20,16 @@ export async function fetchMeta(noCache = false): Promise<Meta> { cache = meta; return meta; } else { - const saved = await transactionalEntityManager.save(Meta, { - id: 'x', - }) as Meta; + // metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う + const saved = await transactionalEntityManager + .upsert( + Meta, + { + id: 'x', + }, + ['id'], + ) + .then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0])); cache = saved; return saved; diff --git a/packages/backend/src/misc/fetch.ts b/packages/backend/src/misc/fetch.ts index 4b1013c9f5..af6bf2fca7 100644 --- a/packages/backend/src/misc/fetch.ts +++ b/packages/backend/src/misc/fetch.ts @@ -1,10 +1,10 @@ -import * as http from 'http'; -import * as https from 'https'; +import * as http from 'node:http'; +import * as https from 'node:https'; +import { URL } from 'node:url'; import CacheableLookup from 'cacheable-lookup'; import fetch from 'node-fetch'; import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent'; import config from '@/config/index.js'; -import { URL } from 'node:url'; export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>) { const res = await getResponse({ @@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout = } export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) { - const timeout = args?.timeout || 10 * 1000; + const timeout = args.timeout || 10 * 1000; const controller = new AbortController(); setTimeout(() => { @@ -47,7 +47,7 @@ export async function getResponse(args: { url: string, method: string, body?: st headers: args.headers, body: args.body, timeout, - size: args?.size || 10 * 1024 * 1024, + size: args.size || 10 * 1024 * 1024, agent: getAgentByUrl, signal: controller.signal, }); diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts new file mode 100644 index 0000000000..379325bb13 --- /dev/null +++ b/packages/backend/src/misc/get-ip-hash.ts @@ -0,0 +1,9 @@ +import IPCIDR from 'ip-cidr'; + +export function getIpHash(ip: string) { + // because a single person may control many IPv6 addresses, + // only a /64 subnet prefix of any IP will be taken into account. + // (this means for IPv4 the entire address is used) + const prefix = IPCIDR.createAddress(ip).mask(64); + return 'ip-' + BigInt('0b' + prefix).toString(36); +} diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts index 86f1356c31..6a185d09f6 100644 --- a/packages/backend/src/misc/populate-emojis.ts +++ b/packages/backend/src/misc/populate-emojis.ts @@ -63,7 +63,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu const isLocal = emoji.host == null; const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため - const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`; + const url = isLocal ? emojiUrl : `${config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`; return { name: emojiName, diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts index 5b69812090..fdecc278d4 100644 --- a/packages/backend/src/misc/schema.ts +++ b/packages/backend/src/misc/schema.ts @@ -89,7 +89,7 @@ export interface Schema extends OfSchema { readonly optional?: boolean; readonly items?: Schema; readonly properties?: Obj; - readonly required?: ReadonlyArray<keyof NonNullable<this['properties']>>; + readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>; readonly description?: string; readonly example?: any; readonly format?: string; @@ -98,6 +98,9 @@ export interface Schema extends OfSchema { readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null; readonly maxLength?: number; readonly minLength?: number; + readonly maximum?: number; + readonly minimum?: number; + readonly pattern?: string; } type RequiredPropertyNames<s extends Obj> = { @@ -105,24 +108,26 @@ type RequiredPropertyNames<s extends Obj> = { // K is not optional s[K]['optional'] extends false ? K : // K has default value - s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never + s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : + never }[keyof s]; -export interface Obj { [key: string]: Schema; } +export type Obj = Record<string, Schema>; +// https://github.com/misskey-dev/misskey/issues/8535 +// To avoid excessive stack depth error, +// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it). export type ObjType<s extends Obj, RequiredProps extends keyof s> = - { -readonly [P in keyof s]?: SchemaType<s[P]> } & - { -readonly [P in RequiredProps]: SchemaType<s[P]> } & - { -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> }; + UnionToIntersection< + { -readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]> } & + { -readonly [R in RequiredProps]-?: SchemaType<s[R]> } & + { -readonly [P in keyof s]?: SchemaType<s[P]> } + >; type NullOrUndefined<p extends Schema, T> = - p['nullable'] extends true - ? p['optional'] extends true - ? (T | null | undefined) - : (T | null) - : p['optional'] extends true - ? (T | undefined) - : T; + | (p['nullable'] extends true ? null : never) + | (p['optional'] extends true ? undefined : never) + | T; // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection // Get intersection from union @@ -139,9 +144,9 @@ export type SchemaTypeDef<p extends Schema> = p['type'] extends 'number' ? number : p['type'] extends 'string' ? ( p['enum'] extends readonly string[] ? - p['enum'][number] : - p['format'] extends 'date-time' ? string : // Dateにする?? - string + p['enum'][number] : + p['format'] extends 'date-time' ? string : // Dateにする?? + string ) : p['type'] extends 'boolean' ? boolean : p['type'] extends 'object' ? ( diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts index 69cdc49cec..c6e2141a46 100644 --- a/packages/backend/src/models/entities/access-token.ts +++ b/packages/backend/src/models/entities/access-token.ts @@ -15,7 +15,6 @@ export class AccessToken { @Column('timestamp with time zone', { nullable: true, - default: null, }) public lastUsedAt: Date | null; @@ -29,7 +28,6 @@ export class AccessToken { @Column('varchar', { length: 128, nullable: true, - default: null, }) public session: string | null; @@ -52,7 +50,6 @@ export class AccessToken { @Column({ ...id(), nullable: true, - default: null, }) public appId: App['id'] | null; @@ -65,21 +62,18 @@ export class AccessToken { @Column('varchar', { length: 128, nullable: true, - default: null, }) public name: string | null; @Column('varchar', { length: 512, nullable: true, - default: null, }) public description: string | null; @Column('varchar', { length: 512, nullable: true, - default: null, }) public iconUrl: string | null; diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts index b825856201..295d1b486c 100644 --- a/packages/backend/src/models/entities/auth-session.ts +++ b/packages/backend/src/models/entities/auth-session.ts @@ -23,7 +23,7 @@ export class AuthSession { ...id(), nullable: true, }) - public userId: User['id']; + public userId: User['id'] | null; @ManyToOne(type => User, { onDelete: 'CASCADE', diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts index da6b3c7a7f..1386684c32 100644 --- a/packages/backend/src/models/entities/clip.ts +++ b/packages/backend/src/models/entities/clip.ts @@ -37,7 +37,7 @@ export class Clip { public isPublic: boolean; @Column('varchar', { - length: 2048, nullable: true, default: null, + length: 2048, nullable: true, comment: 'The description of the Clip.', }) public description: string | null; diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts index 3d375f0e35..a636d1d519 100644 --- a/packages/backend/src/models/entities/drive-file.ts +++ b/packages/backend/src/models/entities/drive-file.ts @@ -79,7 +79,6 @@ export class DriveFile { }) public properties: { width?: number; height?: number; orientation?: number; avgColor?: string }; - @Index() @Column('boolean') public storedInternal: boolean; diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts index b72ca72331..7332dd1857 100644 --- a/packages/backend/src/models/entities/emoji.ts +++ b/packages/backend/src/models/entities/emoji.ts @@ -36,6 +36,7 @@ export class Emoji { @Column('varchar', { length: 512, + default: '', }) public publicUrl: string; diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts index bb24d6b30f..7ea9234384 100644 --- a/packages/backend/src/models/entities/instance.ts +++ b/packages/backend/src/models/entities/instance.ts @@ -107,53 +107,53 @@ export class Instance { public isSuspended: boolean; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, comment: 'The software of the Instance.', }) public softwareName: string | null; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, }) public softwareVersion: string | null; @Column('boolean', { - nullable: true, default: null, + nullable: true, }) public openRegistrations: boolean | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public name: string | null; @Column('varchar', { - length: 4096, nullable: true, default: null, + length: 4096, nullable: true, }) public description: string | null; @Column('varchar', { - length: 128, nullable: true, default: null, + length: 128, nullable: true, }) public maintainerName: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public maintainerEmail: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public iconUrl: string | null; @Column('varchar', { - length: 256, nullable: true, default: null, + length: 256, nullable: true, }) public faviconUrl: string | null; @Column('varchar', { - length: 64, nullable: true, default: null, + length: 64, nullable: true, }) public themeColor: string | null; diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts index 4d58b5f04f..80b5228bcd 100644 --- a/packages/backend/src/models/entities/meta.ts +++ b/packages/backend/src/models/entities/meta.ts @@ -78,7 +78,7 @@ export class Meta { public blockedHosts: string[]; @Column('varchar', { - length: 512, array: true, default: '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}', + length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}', }) public pinnedPages: string[]; @@ -346,14 +346,12 @@ export class Meta { @Column('varchar', { length: 8192, - default: null, nullable: true, }) public defaultLightTheme: string | null; @Column('varchar', { length: 8192, - default: null, nullable: true, }) public defaultDarkTheme: string | null; diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts index b3a7e7a671..8f9e69063a 100644 --- a/packages/backend/src/models/entities/muting.ts +++ b/packages/backend/src/models/entities/muting.ts @@ -17,7 +17,6 @@ export class Muting { @Index() @Column('timestamp with time zone', { nullable: true, - default: null, }) public expiresAt: Date | null; diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts index da49d53b69..0ffeb85f69 100644 --- a/packages/backend/src/models/entities/note.ts +++ b/packages/backend/src/models/entities/note.ts @@ -53,8 +53,8 @@ export class Note { }) public threadId: string | null; - @Column('varchar', { - length: 8192, nullable: true, + @Column('text', { + nullable: true, }) public text: string | null; @@ -179,7 +179,7 @@ export class Note { @Index() @Column({ ...id(), - nullable: true, default: null, + nullable: true, comment: 'The ID of source channel.', }) public channelId: Channel['id'] | null; diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts index f95cb144c5..1778742ea2 100644 --- a/packages/backend/src/models/entities/user-profile.ts +++ b/packages/backend/src/models/entities/user-profile.ts @@ -192,6 +192,7 @@ export class UserProfile { @Column('jsonb', { default: [], + comment: 'List of instances muted by the user.', }) public mutedInstances: string[]; diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts index 29d9a0c2ca..df92fb8259 100644 --- a/packages/backend/src/models/entities/user.ts +++ b/packages/backend/src/models/entities/user.ts @@ -207,7 +207,7 @@ export class User { @Column('boolean', { default: false, - comment: 'Whether to show users replying to other users in the timeline', + comment: 'Whether to show users replying to other users in the timeline.', }) public showTimelineReplies: boolean; diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts index 69dc1721c2..0d589d4f11 100644 --- a/packages/backend/src/models/repositories/drive-file.ts +++ b/packages/backend/src/models/repositories/drive-file.ts @@ -1,6 +1,5 @@ import { db } from '@/db/postgre.js'; import { DriveFile } from '@/models/entities/drive-file.js'; -import { Users, DriveFolders } from '../index.js'; import { User } from '@/models/entities/user.js'; import { toPuny } from '@/misc/convert-host.js'; import { awaitAll, Promiseable } from '@/prelude/await-all.js'; @@ -9,6 +8,7 @@ import config from '@/config/index.js'; import { query, appendQuery } from '@/prelude/url.js'; import { Meta } from '@/models/entities/meta.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Users, DriveFolders } from '../index.js'; type PackOptions = { detail?: boolean, @@ -29,6 +29,8 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ getPublicProperties(file: DriveFile): DriveFile['properties'] { if (file.properties.orientation != null) { + // TODO + //const properties = structuredClone(file.properties); const properties = JSON.parse(JSON.stringify(file.properties)); if (file.properties.orientation >= 5) { [properties.width, properties.height] = [properties.height, properties.width]; @@ -111,7 +113,40 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ async pack( src: DriveFile['id'] | DriveFile, - options?: PackOptions + options?: PackOptions, + ): Promise<Packed<'DriveFile'>> { + const opts = Object.assign({ + detail: false, + self: false, + }, options); + + const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); + + return await awaitAll<Packed<'DriveFile'>>({ + id: file.id, + createdAt: file.createdAt.toISOString(), + name: file.name, + type: file.type, + md5: file.md5, + size: file.size, + isSensitive: file.isSensitive, + blurhash: file.blurhash, + properties: opts.self ? file.properties : this.getPublicProperties(file), + url: opts.self ? file.url : this.getPublicUrl(file, false), + thumbnailUrl: this.getPublicUrl(file, true), + comment: file.comment, + folderId: file.folderId, + folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { + detail: true, + }) : null, + userId: opts.withUser ? file.userId : null, + user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null, + }); + }, + + async packNullable( + src: DriveFile['id'] | DriveFile, + options?: PackOptions, ): Promise<Packed<'DriveFile'> | null> { const opts = Object.assign({ detail: false, @@ -145,9 +180,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({ async packMany( files: (DriveFile['id'] | DriveFile)[], - options?: PackOptions - ) { - const items = await Promise.all(files.map(f => this.pack(f, options))); - return items.filter(x => x != null); + options?: PackOptions, + ): Promise<Packed<'DriveFile'>[]> { + const items = await Promise.all(files.map(f => this.packNullable(f, options))); + return items.filter((x): x is Packed<'DriveFile'> => x != null); }, }); diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index cf5fcb1787..3fefab0319 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -136,6 +136,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { export const NoteRepository = db.getRepository(Note).extend({ async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> { + // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 if (note.visibility === 'specified') { if (meId == null) { @@ -144,13 +145,7 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } else { // 指定されているかどうか - const specified = note.visibleUserIds.some((id: any) => meId === id); - - if (specified) { - return true; - } else { - return false; - } + return note.visibleUserIds.some((id: any) => meId === id); } } @@ -168,16 +163,25 @@ export const NoteRepository = db.getRepository(Note).extend({ return true; } else { // フォロワーかどうか - const following = await Followings.findOneBy({ - followeeId: note.userId, - followerId: meId, - }); + const [following, user] = await Promise.all([ + Followings.count({ + where: { + followeeId: note.userId, + followerId: meId, + }, + take: 1, + }), + Users.findOneByOrFail({ id: meId }), + ]); - if (following == null) { - return false; - } else { - return true; - } + /* If we know the following, everyhting is fine. + + But if we do not know the following, it might be that both the + author of the note and the author of the like are remote users, + in which case we can never know the following. Instead we have + to assume that the users are following each other. + */ + return following > 0 || (note.userHost != null && user.host != null); } } diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts index 1bffb23fa2..092b26b396 100644 --- a/packages/backend/src/models/repositories/page.ts +++ b/packages/backend/src/models/repositories/page.ts @@ -1,10 +1,10 @@ import { db } from '@/db/postgre.js'; import { Page } from '@/models/entities/page.js'; import { Packed } from '@/misc/schema.js'; -import { Users, DriveFiles, PageLikes } from '../index.js'; import { awaitAll } from '@/prelude/await-all.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { User } from '@/models/entities/user.js'; +import { Users, DriveFiles, PageLikes } from '../index.js'; export const PageRepository = db.getRepository(Page).extend({ async pack( @@ -14,7 +14,7 @@ export const PageRepository = db.getRepository(Page).extend({ const meId = me ? me.id : null; const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src }); - const attachedFiles: Promise<DriveFile | undefined>[] = []; + const attachedFiles: Promise<DriveFile | null>[] = []; const collectFile = (xs: any[]) => { for (const x of xs) { if (x.type === 'image') { @@ -73,7 +73,7 @@ export const PageRepository = db.getRepository(Page).extend({ script: page.script, eyeCatchingImageId: page.eyeCatchingImageId, eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null, - attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)), + attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)), likedCount: page.likedCount, isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined, }); diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts index 541fbaf003..8a4e48efdd 100644 --- a/packages/backend/src/models/repositories/user.ts +++ b/packages/backend/src/models/repositories/user.ts @@ -61,47 +61,58 @@ export const UserRepository = db.getRepository(User).extend({ //#endregion async getRelation(me: User['id'], target: User['id']) { - const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ - Followings.findOneBy({ - followerId: me, - followeeId: target, - }), - Followings.findOneBy({ - followerId: target, - followeeId: me, - }), - FollowRequests.findOneBy({ - followerId: me, - followeeId: target, - }), - FollowRequests.findOneBy({ - followerId: target, - followeeId: me, - }), - Blockings.findOneBy({ - blockerId: me, - blockeeId: target, - }), - Blockings.findOneBy({ - blockerId: target, - blockeeId: me, - }), - Mutings.findOneBy({ - muterId: me, - muteeId: target, - }), - ]); - - return { + return awaitAll({ id: target, - isFollowing: following1 != null, - hasPendingFollowRequestFromYou: followReq1 != null, - hasPendingFollowRequestToYou: followReq2 != null, - isFollowed: following2 != null, - isBlocking: toBlocking != null, - isBlocked: fromBlocked != null, - isMuted: mute != null, - }; + isFollowing: Followings.count({ + where: { + followerId: me, + followeeId: target, + }, + take: 1, + }).then(n => n > 0), + isFollowed: Followings.count({ + where: { + followerId: target, + followeeId: me, + }, + take: 1, + }).then(n => n > 0), + hasPendingFollowRequestFromYou: FollowRequests.count({ + where: { + followerId: me, + followeeId: target, + }, + take: 1, + }).then(n => n > 0), + hasPendingFollowRequestToYou: FollowRequests.count({ + where: { + followerId: target, + followeeId: me, + }, + take: 1, + }).then(n => n > 0), + isBlocking: Blockings.count({ + where: { + blockerId: me, + blockeeId: target, + }, + take: 1, + }).then(n => n > 0), + isBlocked: Blockings.count({ + where: { + blockerId: target, + blockeeId: me, + }, + take: 1, + }).then(n => n > 0), + isMuted: Mutings.count({ + where: { + muterId: me, + muteeId: target, + }, + take: 1, + }).then(n => n > 0), + }); }, async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> { diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 2d40290e4c..c5fd7de1cb 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -1,4 +1,4 @@ -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import { v4 as uuid } from 'uuid'; import config from '@/config/index.js'; @@ -305,11 +305,13 @@ export default function() { systemQueue.add('resyncCharts', { }, { repeat: { cron: '0 0 * * *' }, + removeOnComplete: true, }); systemQueue.add('cleanCharts', { }, { repeat: { cron: '0 0 * * *' }, + removeOnComplete: true, }); systemQueue.add('checkExpiredMutings', { diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts index 166c9e4cd3..f5e0424a79 100644 --- a/packages/backend/src/queue/processors/db/export-blocking.ts +++ b/packages/backend/src/queue/processors/db/export-blocking.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Blockings } from '@/models/index.js'; import { MoreThan } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -22,73 +22,72 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let exportedCount = 0; - let cursor: any = null; + let exportedCount = 0; + let cursor: any = null; - while (true) { - const blockings = await Blockings.find({ - where: { - blockerId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }); + while (true) { + const blockings = await Blockings.find({ + where: { + blockerId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); - if (blockings.length === 0) { - job.progress(100); - break; - } + if (blockings.length === 0) { + job.progress(100); + break; + } - cursor = blockings[blockings.length - 1].id; + cursor = blockings[blockings.length - 1].id; - for (const block of blockings) { - const u = await Users.findOneBy({ id: block.blockeeId }); - if (u == null) { - exportedCount++; continue; - } + for (const block of blockings) { + const u = await Users.findOneBy({ id: block.blockeeId }); + if (u == null) { + exportedCount++; continue; + } - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); + exportedCount++; + } + + const total = await Blockings.countBy({ + blockerId: user.id, }); - exportedCount++; - } - const total = await Blockings.countBy({ - blockerId: user.id, - }); + job.progress(exportedCount / total); + } - job.progress(exportedCount / total); - } + stream.end(); + logger.succ(`Exported to: ${path}`); - stream.end(); - logger.succ(`Exported to: ${path}`); + const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); - const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts index c2467fb5f0..8ce1d05272 100644 --- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts @@ -1,5 +1,4 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { ulid } from 'ulid'; @@ -10,6 +9,7 @@ import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { Users, Emojis } from '@/models/index.js'; import { } from '@/queue/types.js'; +import { createTemp, createTempDir } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import config from '@/config/index.js'; import { IsNull } from 'typeorm'; @@ -25,13 +25,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi return; } - // Create temp dir - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTempDir(); logger.info(`Temp dir is ${path}`); @@ -98,12 +92,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi metaStream.end(); // Create archive - const [archivePath, archiveCleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [archivePath, archiveCleanup] = await createTemp(); const archiveStream = fs.createWriteStream(archivePath); const archive = archiver('zip', { zlib: { level: 0 }, diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts index 965500ac27..4ac165567b 100644 --- a/packages/backend/src/queue/processors/db/export-following.ts +++ b/packages/backend/src/queue/processors/db/export-following.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Followings, Mutings } from '@/models/index.js'; import { In, MoreThan, Not } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -23,73 +23,72 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () => } // Create temp file - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let cursor: Following['id'] | null = null; + let cursor: Following['id'] | null = null; - const mutings = job.data.excludeMuting ? await Mutings.findBy({ - muterId: user.id, - }) : []; + const mutings = job.data.excludeMuting ? await Mutings.findBy({ + muterId: user.id, + }) : []; - while (true) { - const followings = await Followings.find({ - where: { - followerId: user.id, - ...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}), - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }) as Following[]; + while (true) { + const followings = await Followings.find({ + where: { + followerId: user.id, + ...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}), + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }) as Following[]; - if (followings.length === 0) { - break; - } + if (followings.length === 0) { + break; + } - cursor = followings[followings.length - 1].id; + cursor = followings[followings.length - 1].id; - for (const following of followings) { - const u = await Users.findOneBy({ id: following.followeeId }); - if (u == null) { - continue; - } + for (const following of followings) { + const u = await Users.findOneBy({ id: following.followeeId }); + if (u == null) { + continue; + } - if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { - continue; - } + if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) { + continue; + } - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); - }); + } } - } - stream.end(); - logger.succ(`Exported to: ${path}`); + stream.end(); + logger.succ(`Exported to: ${path}`); - const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); + + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts index 0ef81971f1..6a36cfa072 100644 --- a/packages/backend/src/queue/processors/db/export-mute.ts +++ b/packages/backend/src/queue/processors/db/export-mute.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, Mutings } from '@/models/index.js'; import { IsNull, MoreThan } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -22,74 +22,73 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - let exportedCount = 0; - let cursor: any = null; + let exportedCount = 0; + let cursor: any = null; - while (true) { - const mutes = await Mutings.find({ - where: { - muterId: user.id, - expiresAt: IsNull(), - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }); + while (true) { + const mutes = await Mutings.find({ + where: { + muterId: user.id, + expiresAt: IsNull(), + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }); - if (mutes.length === 0) { - job.progress(100); - break; - } + if (mutes.length === 0) { + job.progress(100); + break; + } - cursor = mutes[mutes.length - 1].id; + cursor = mutes[mutes.length - 1].id; - for (const mute of mutes) { - const u = await Users.findOneBy({ id: mute.muteeId }); - if (u == null) { - exportedCount++; continue; - } + for (const mute of mutes) { + const u = await Users.findOneBy({ id: mute.muteeId }); + if (u == null) { + exportedCount++; continue; + } - const content = getFullApAccount(u.username, u.host); - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + const content = getFullApAccount(u.username, u.host); + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); + exportedCount++; + } + + const total = await Mutings.countBy({ + muterId: user.id, }); - exportedCount++; - } - const total = await Mutings.countBy({ - muterId: user.id, - }); + job.progress(exportedCount / total); + } - job.progress(exportedCount / total); - } + stream.end(); + logger.succ(`Exported to: ${path}`); - stream.end(); - logger.succ(`Exported to: ${path}`); + const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); - const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts index 7e12a6fac2..051fcdf385 100644 --- a/packages/backend/src/queue/processors/db/export-notes.ts +++ b/packages/backend/src/queue/processors/db/export-notes.ts @@ -1,5 +1,4 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; @@ -10,6 +9,7 @@ import { MoreThan } from 'typeorm'; import { Note } from '@/models/entities/note.js'; import { Poll } from '@/models/entities/poll.js'; import { DbUserJobData } from '@/queue/types.js'; +import { createTemp } from '@/misc/create-temp.js'; const logger = queueLogger.createSubLogger('export-notes'); @@ -23,82 +23,81 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom } // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - const write = (text: string): Promise<void> => { - return new Promise<void>((res, rej) => { - stream.write(text, err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + const write = (text: string): Promise<void> => { + return new Promise<void>((res, rej) => { + stream.write(text, err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); - }); - }; + }; - await write('['); + await write('['); - let exportedNotesCount = 0; - let cursor: Note['id'] | null = null; + let exportedNotesCount = 0; + let cursor: Note['id'] | null = null; - while (true) { - const notes = await Notes.find({ - where: { - userId: user.id, - ...(cursor ? { id: MoreThan(cursor) } : {}), - }, - take: 100, - order: { - id: 1, - }, - }) as Note[]; + while (true) { + const notes = await Notes.find({ + where: { + userId: user.id, + ...(cursor ? { id: MoreThan(cursor) } : {}), + }, + take: 100, + order: { + id: 1, + }, + }) as Note[]; - if (notes.length === 0) { - job.progress(100); - break; - } + if (notes.length === 0) { + job.progress(100); + break; + } - cursor = notes[notes.length - 1].id; + cursor = notes[notes.length - 1].id; - for (const note of notes) { - let poll: Poll | undefined; - if (note.hasPoll) { - poll = await Polls.findOneByOrFail({ noteId: note.id }); + for (const note of notes) { + let poll: Poll | undefined; + if (note.hasPoll) { + poll = await Polls.findOneByOrFail({ noteId: note.id }); + } + const content = JSON.stringify(serialize(note, poll)); + const isFirst = exportedNotesCount === 0; + await write(isFirst ? content : ',\n' + content); + exportedNotesCount++; } - const content = JSON.stringify(serialize(note, poll)); - const isFirst = exportedNotesCount === 0; - await write(isFirst ? content : ',\n' + content); - exportedNotesCount++; - } - const total = await Notes.countBy({ - userId: user.id, - }); + const total = await Notes.countBy({ + userId: user.id, + }); - job.progress(exportedNotesCount / total); - } + job.progress(exportedNotesCount / total); + } + + await write(']'); - await write(']'); + stream.end(); + logger.succ(`Exported to: ${path}`); - stream.end(); - logger.succ(`Exported to: ${path}`); + const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); - const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts index 45852a6038..71dd72df27 100644 --- a/packages/backend/src/queue/processors/db/export-user-lists.ts +++ b/packages/backend/src/queue/processors/db/export-user-lists.ts @@ -1,11 +1,11 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import { queueLogger } from '../../logger.js'; import { addFile } from '@/services/drive/add-file.js'; import { format as dateFormat } from 'date-fns'; import { getFullApAccount } from '@/misc/convert-host.js'; +import { createTemp } from '@/misc/create-temp.js'; import { Users, UserLists, UserListJoinings } from '@/models/index.js'; import { In } from 'typeorm'; import { DbUserJobData } from '@/queue/types.js'; @@ -26,46 +26,45 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): }); // Create temp file - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); logger.info(`Temp file is ${path}`); - const stream = fs.createWriteStream(path, { flags: 'a' }); + try { + const stream = fs.createWriteStream(path, { flags: 'a' }); - for (const list of lists) { - const joinings = await UserListJoinings.findBy({ userListId: list.id }); - const users = await Users.findBy({ - id: In(joinings.map(j => j.userId)), - }); + for (const list of lists) { + const joinings = await UserListJoinings.findBy({ userListId: list.id }); + const users = await Users.findBy({ + id: In(joinings.map(j => j.userId)), + }); - for (const u of users) { - const acct = getFullApAccount(u.username, u.host); - const content = `${list.name},${acct}`; - await new Promise<void>((res, rej) => { - stream.write(content + '\n', err => { - if (err) { - logger.error(err); - rej(err); - } else { - res(); - } + for (const u of users) { + const acct = getFullApAccount(u.username, u.host); + const content = `${list.name},${acct}`; + await new Promise<void>((res, rej) => { + stream.write(content + '\n', err => { + if (err) { + logger.error(err); + rej(err); + } else { + res(); + } + }); }); - }); + } } - } - stream.end(); - logger.succ(`Exported to: ${path}`); + stream.end(); + logger.succ(`Exported to: ${path}`); + + const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; + const driveFile = await addFile({ user, path, name: fileName, force: true }); - const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv'; - const driveFile = await addFile({ user, path, name: fileName, force: true }); + logger.succ(`Exported to: ${driveFile.id}`); + } finally { + cleanup(); + } - logger.succ(`Exported to: ${driveFile.id}`); - cleanup(); done(); } diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts index 28e0b867a4..64dfe85374 100644 --- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts +++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts @@ -1,9 +1,9 @@ import Bull from 'bull'; -import * as tmp from 'tmp'; import * as fs from 'node:fs'; import unzipper from 'unzipper'; import { queueLogger } from '../../logger.js'; +import { createTempDir } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { DriveFiles, Emojis } from '@/models/index.js'; import { DbUserImportJobData } from '@/queue/types.js'; @@ -25,13 +25,7 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don return; } - // Create temp dir - const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTempDir(); logger.info(`Temp dir is ${path}`); diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 4fbfdb234f..198dde6050 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -1,6 +1,6 @@ import { URL } from 'node:url'; import Bull from 'bull'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import perform from '@/remote/activitypub/perform.js'; import Logger from '@/services/logger.js'; import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 6c0b9d9bf6..5ea4725561 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -3,7 +3,7 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user.js'; import { Webhook } from '@/models/entities/webhook'; import { IActivity } from '@/remote/activitypub/type.js'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; export type DeliverJobData = { /** Actor */ diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index ef07966e42..1a02f675ca 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -5,14 +5,52 @@ import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/ import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; -import { IObject, getApId } from './type.js'; -import { resolvePerson } from './models/person.js'; import { Cache } from '@/misc/cache.js'; import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; +import { IObject, getApId } from './type.js'; +import { resolvePerson } from './models/person.js'; const publicKeyCache = new Cache<UserPublickey | null>(Infinity); const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity); +export type UriParseResult = { + /** wether the URI was generated by us */ + local: true; + /** id in DB */ + id: string; + /** hint of type, e.g. "notes", "users" */ + type: string; + /** any remaining text after type and id, not including the slash after id. undefined if empty */ + rest?: string; +} | { + /** wether the URI was generated by us */ + local: false; + /** uri in DB */ + uri: string; +}; + +export function parseUri(value: string | IObject): UriParseResult { + const uri = getApId(value); + + // the host part of a URL is case insensitive, so use the 'i' flag. + const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i'); + const matchLocal = uri.match(localRegex); + + if (matchLocal) { + return { + local: true, + type: matchLocal[1], + id: matchLocal[2], + rest: matchLocal[3], + }; + } else { + return { + local: false, + uri, + }; + } +} + export default class DbResolver { constructor() { } @@ -21,60 +59,54 @@ export default class DbResolver { * AP Note => Misskey Note in DB */ public async getNoteFromApId(value: string | IObject): Promise<Note | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; - if (parsed.id) { return await Notes.findOneBy({ id: parsed.id, }); - } - - if (parsed.uri) { + } else { return await Notes.findOneBy({ uri: parsed.uri, }); } - - return null; } public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'notes') return null; - if (parsed.id) { return await MessagingMessages.findOneBy({ id: parsed.id, }); - } - - if (parsed.uri) { + } else { return await MessagingMessages.findOneBy({ uri: parsed.uri, }); } - - return null; } /** * AP Person => Misskey User in DB */ public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> { - const parsed = this.parseUri(value); + const parsed = parseUri(value); + + if (parsed.local) { + if (parsed.type !== 'users') return null; - if (parsed.id) { return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({ id: parsed.id, }).then(x => x ?? undefined)) ?? null; - } - - if (parsed.uri) { + } else { return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({ uri: parsed.uri, })); } - - return null; } /** @@ -120,31 +152,4 @@ export default class DbResolver { key, }; } - - public parseUri(value: string | IObject): UriParseResult { - const uri = getApId(value); - - const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)'); - const matchLocal = uri.match(localRegex); - - if (matchLocal) { - return { - type: matchLocal[1], - id: matchLocal[2], - }; - } else { - return { - uri, - }; - } - } } - -type UriParseResult = { - /** id in DB (local object only) */ - id?: string; - /** uri in DB (remote object only) */ - uri?: string; - /** hint of type (local object only, ex: notes, users) */ - type?: string -}; diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts index 680749f4d8..759cb4ae83 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts @@ -9,6 +9,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { getApLock } from '@/misc/app-lock.js'; import { parseAudience } from '../../audience.js'; import { StatusError } from '@/misc/fetch.js'; +import { Notes } from '@/models/index.js'; const logger = apLogger; @@ -52,6 +53,8 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac throw e; } + if (!await Notes.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity'; + logger.info(`Creating the (Re)Note: ${uri}`); const activityAudience = await parseAudience(actor, activity.to, activity.cc); diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts index 4c06a9de0b..c7064f553b 100644 --- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts @@ -13,37 +13,37 @@ export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<st } // 削除対象objectのtype - let formarType: string | undefined; + let formerType: string | undefined; if (typeof activity.object === 'string') { // typeが不明だけど、どうせ消えてるのでremote resolveしない - formarType = undefined; + formerType = undefined; } else { const object = activity.object as IObject; if (isTombstone(object)) { - formarType = toSingle(object.formerType); + formerType = toSingle(object.formerType); } else { - formarType = toSingle(object.type); + formerType = toSingle(object.type); } } const uri = getApId(activity.object); // type不明でもactorとobjectが同じならばそれはPersonに違いない - if (!formarType && actor.uri === uri) { - formarType = 'Person'; + if (!formerType && actor.uri === uri) { + formerType = 'Person'; } // それでもなかったらおそらくNote - if (!formarType) { - formarType = 'Note'; + if (!formerType) { + formerType = 'Note'; } - if (validPost.includes(formarType)) { + if (validPost.includes(formerType)) { return await deleteNote(actor, uri); - } else if (validActor.includes(formarType)) { + } else if (validActor.includes(formerType)) { return await deleteActor(actor, uri); } else { - return `Unknown type ${formarType}`; + return `Unknown type ${formerType}`; } }; diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts deleted file mode 100644 index e69de29bb2..0000000000 --- a/packages/backend/src/remote/activitypub/kernel/move/index.ts +++ /dev/null diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts index c2ac31bf8d..417f39722f 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts @@ -8,6 +8,7 @@ export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnoun const note = await Notes.findOneBy({ uri, + userId: actor.id, }); if (!note) return 'skip: no such Announce'; diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts index 3800b40608..389039ebed 100644 --- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts +++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts @@ -3,8 +3,6 @@ import { Note } from '@/models/entities/note.js'; import { toHtml } from '../../../mfm/to-html.js'; export default function(note: Note) { - let html = note.text ? toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)) : null; - if (html == null) html = '<p>.</p>'; - - return html; + if (!note.text) return ''; + return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)); } diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index a160092969..13f77424ec 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -1,9 +1,9 @@ +import promiseLimit from 'promise-limit'; import { toArray, unique } from '@/prelude/array.js'; +import { CacheableUser, User } from '@/models/entities/user.js'; import { IObject, isMention, IApMention } from '../type.js'; -import { resolvePerson } from './person.js'; -import promiseLimit from 'promise-limit'; import Resolver from '../resolver.js'; -import { CacheableUser, User } from '@/models/entities/user.js'; +import { resolvePerson } from './person.js'; export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); @@ -12,7 +12,7 @@ export async function extractApMentions(tags: IObject | IObject[] | null | undef const limit = promiseLimit<CacheableUser | null>(2); const mentionedUsers = (await Promise.all( - hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))) + hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), )).filter((x): x is CacheableUser => x != null); return mentionedUsers; diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index 097a716614..56c1a483ad 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -3,9 +3,9 @@ import promiseLimit from 'promise-limit'; import config from '@/config/index.js'; import Resolver from '../resolver.js'; import post from '@/services/note/create.js'; -import { resolvePerson, updatePerson } from './person.js'; +import { resolvePerson } from './person.js'; import { resolveImage } from './image.js'; -import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; import { htmlToMfm } from '../misc/html-to-mfm.js'; import { extractApHashtags } from './tag.js'; import { unique, toArray, toSingle } from '@/prelude/array.js'; @@ -15,7 +15,7 @@ import { apLogger } from '../logger.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { extractDbHost, toPuny } from '@/misc/convert-host.js'; -import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js'; +import { Emojis, Polls, MessagingMessages } from '@/models/index.js'; import { Note } from '@/models/entities/note.js'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js'; import { Emoji } from '@/models/entities/emoji.js'; @@ -197,7 +197,14 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; // テキストのパース - const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null); + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content === 'string') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = htmlToMfm(note.content, note.tag); + } // vote if (reply && reply.hasPoll) { diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index 88661865da..6097e3b6ed 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -1,17 +1,8 @@ import { URL } from 'node:url'; import promiseLimit from 'promise-limit'; -import $, { Context } from 'cafy'; import config from '@/config/index.js'; -import Resolver from '../resolver.js'; -import { resolveImage } from './image.js'; -import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; -import { fromHtml } from '../../../mfm/from-html.js'; -import { htmlToMfm } from '../misc/html-to-mfm.js'; -import { resolveNote, extractEmojis } from './note.js'; import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js'; -import { extractApHashtags } from './tag.js'; -import { apLogger } from '../logger.js'; import { Note } from '@/models/entities/note.js'; import { updateUsertags } from '@/services/update-hashtag.js'; import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js'; @@ -32,6 +23,14 @@ import { StatusError } from '@/misc/fetch.js'; import { uriPersonCache } from '@/services/user-cache.js'; import { publishInternalEvent } from '@/services/stream.js'; import { db } from '@/db/postgre.js'; +import { apLogger } from '../logger.js'; +import { htmlToMfm } from '../misc/html-to-mfm.js'; +import { fromHtml } from '../../../mfm/from-html.js'; +import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js'; +import Resolver from '../resolver.js'; +import { extractApHashtags } from './tag.js'; +import { resolveNote, extractEmojis } from './note.js'; +import { resolveImage } from './image.js'; const logger = apLogger; @@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor { throw new Error(`invalid Actor type '${x.type}'`); } - const validate = (name: string, value: any, validater: Context) => { - const e = validater.test(value); - if (e) throw new Error(`invalid Actor: ${name} ${e.message}`); - }; + if (!(typeof x.id === 'string' && x.id.length > 0)) { + throw new Error('invalid Actor: wrong id'); + } - validate('id', x.id, $.default.str.min(1)); - validate('inbox', x.inbox, $.default.str.min(1)); - validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/)); + if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) { + throw new Error('invalid Actor: wrong inbox'); + } + + if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) { + throw new Error('invalid Actor: wrong username'); + } // These fields are only informational, and some AP software allows these // fields to be very long. If they are too long, we cut them off. This way // we can at least see these users and their activities. - validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str); - validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str); + if (x.name) { + if (!(typeof x.name === 'string' && x.name.length > 0)) { + throw new Error('invalid Actor: wrong name'); + } + x.name = truncate(x.name, nameLength); + } + if (x.summary) { + if (!(typeof x.summary === 'string' && x.summary.length > 0)) { + throw new Error('invalid Actor: wrong summary'); + } + x.summary = truncate(x.summary, summaryLength); + } const idHost = toPuny(new URL(x.id!).hostname); if (idHost !== expectHost) { @@ -271,7 +283,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us * @param resolver Resolver * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します) */ -export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: Record<string, unknown>): Promise<void> { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならスキップ @@ -289,7 +301,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint if (resolver == null) resolver = new Resolver(); - const object = hint || await resolver.resolve(uri) as any; + const object = hint || await resolver.resolve(uri); const person = validateActor(object, uri); @@ -400,10 +412,10 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C const services: { [x: string]: (id: string, username: string) => any } = { - 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), - 'misskey:authentication:github': (id, login) => ({ id, login }), - 'misskey:authentication:discord': (id, name) => $discord(id, name), -}; + 'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }), + 'misskey:authentication:github': (id, login) => ({ id, login }), + 'misskey:authentication:discord': (id, name) => $discord(id, name), + }; const $discord = (id: string, name: string) => { if (typeof name !== 'string') { @@ -461,7 +473,7 @@ export async function updateFeatured(userId: User['id']) { // Resolve to (Ordered)Collection Object const collection = await resolver.resolveCollection(user.featured); - if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`); + if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection'); // Resolve to Object(may be Note) arrays const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems; diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts index 10a4fde517..13815fb76f 100644 --- a/packages/backend/src/remote/activitypub/renderer/block.ts +++ b/packages/backend/src/remote/activitypub/renderer/block.ts @@ -1,8 +1,20 @@ import config from '@/config/index.js'; -import { ILocalUser, IRemoteUser } from '@/models/entities/user.js'; +import { Blocking } from '@/models/entities/blocking.js'; -export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ - type: 'Block', - actor: `${config.url}/users/${blocker.id}`, - object: blockee.uri, -}); +/** + * Renders a block into its ActivityPub representation. + * + * @param block The block to be rendered. The blockee relation must be loaded. + */ +export function renderBlock(block: Blocking) { + if (block.blockee?.url == null) { + throw new Error('renderBlock: missing blockee uri'); + } + + return { + type: 'Block', + id: `${config.url}/blocks/${block.id}`, + actor: `${config.url}/users/${block.blockerId}`, + object: block.blockee.uri, + }; +} diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts index 6fbc11580f..58eadddbaa 100644 --- a/packages/backend/src/remote/activitypub/renderer/flag.ts +++ b/packages/backend/src/remote/activitypub/renderer/flag.ts @@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js'; // to anonymise reporters, the reporting actor must be a system user // object has to be a uri or array of uris -export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => { +export const renderFlag = (user: ILocalUser, object: [string], content: string) => { return { type: 'Flag', actor: `${config.url}/users/${user.id}`, diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts index 9e9692b77a..00fac18ad5 100644 --- a/packages/backend/src/remote/activitypub/renderer/follow.ts +++ b/packages/backend/src/remote/activitypub/renderer/follow.ts @@ -4,12 +4,11 @@ import { Users } from '@/models/index.js'; export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => { const follow = { + id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`, type: 'Follow', actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri, object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri, } as any; - if (requestId) follow.id = requestId; - return follow; }; diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts index 5f69332266..f100b77ce5 100644 --- a/packages/backend/src/remote/activitypub/renderer/index.ts +++ b/packages/backend/src/remote/activitypub/renderer/index.ts @@ -8,7 +8,7 @@ import { User } from '@/models/entities/user.js'; export const renderActivity = (x: any): IActivity | null => { if (x == null) return null; - if (x !== null && typeof x === 'object' && x.id == null) { + if (typeof x === 'object' && x.id == null) { x.id = `${config.url}/${uuid()}`; } diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index 679c8bbfe4..df2ae65205 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -1,15 +1,15 @@ -import renderDocument from './document.js'; -import renderHashtag from './hashtag.js'; -import renderMention from './mention.js'; -import renderEmoji from './emoji.js'; +import { In, IsNull } from 'typeorm'; import config from '@/config/index.js'; -import toHtml from '../misc/get-note-html.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js'; -import { In, IsNull } from 'typeorm'; import { Emoji } from '@/models/entities/emoji.js'; import { Poll } from '@/models/entities/poll.js'; +import toHtml from '../misc/get-note-html.js'; +import renderEmoji from './emoji.js'; +import renderMention from './mention.js'; +import renderHashtag from './hashtag.js'; +import renderDocument from './document.js'; export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<Record<string, unknown>> { const getPromisedFiles = async (ids: string[]) => { @@ -82,15 +82,15 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const files = await getPromisedFiles(note.fileIds); - const text = note.text; - let poll: Poll | null; + // text should never be undefined + const text = note.text ?? null; + let poll: Poll | null = null; if (note.hasPoll) { poll = await Polls.findOneBy({ noteId: note.id }); } - let apText = text; - if (apText == null) apText = ''; + let apText = text ?? ''; if (quote) { apText += `\n\nRE: ${quote}`; @@ -138,6 +138,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false summary, content, _misskey_content: text, + source: { + content: text, + mediaType: "text/x.misskeymarkdown", + }, _misskey_quote: quote, quoteUrl: quote, published: note.createdAt.toISOString(), @@ -159,7 +163,7 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> { names.map(name => Emojis.findOneBy({ name, host: IsNull(), - })) + })), ); return emojis.filter(emoji => emoji != null) as Emoji[]; diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts index c1269c75c5..2f9af43c0c 100644 --- a/packages/backend/src/remote/activitypub/resolver.ts +++ b/packages/backend/src/remote/activitypub/resolver.ts @@ -2,10 +2,19 @@ import config from '@/config/index.js'; import { getJson } from '@/misc/fetch.js'; import { ILocalUser } from '@/models/entities/user.js'; import { getInstanceActor } from '@/services/instance-actor.js'; +import { fetchMeta } from '@/misc/fetch-meta.js'; +import { extractDbHost, isSelfHost } from '@/misc/convert-host.js'; import { signedGet } from './request.js'; import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js'; -import { fetchMeta } from '@/misc/fetch-meta.js'; -import { extractDbHost } from '@/misc/convert-host.js'; +import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js'; +import { parseUri } from './db-resolver.js'; +import renderNote from '@/remote/activitypub/renderer/note.js'; +import { renderLike } from '@/remote/activitypub/renderer/like.js'; +import { renderPerson } from '@/remote/activitypub/renderer/person.js'; +import renderQuestion from '@/remote/activitypub/renderer/question.js'; +import renderCreate from '@/remote/activitypub/renderer/create.js'; +import { renderActivity } from '@/remote/activitypub/renderer/index.js'; +import renderFollow from '@/remote/activitypub/renderer/follow.js'; export default class Resolver { private history: Set<string>; @@ -40,14 +49,25 @@ export default class Resolver { return value; } + if (value.includes('#')) { + // URLs with fragment parts cannot be resolved correctly because + // the fragment part does not get transmitted over HTTP(S). + // Avoid strange behaviour by not trying to resolve these at all. + throw new Error(`cannot resolve URL with fragment: ${value}`); + } + if (this.history.has(value)) { throw new Error('cannot resolve already resolved one'); } this.history.add(value); - const meta = await fetchMeta(); const host = extractDbHost(value); + if (isSelfHost(host)) { + return await this.resolveLocal(value); + } + + const meta = await fetchMeta(); if (meta.blockedHosts.includes(host)) { throw new Error('Instance is blocked'); } @@ -56,13 +76,13 @@ export default class Resolver { this.user = await getInstanceActor(); } - const object = this.user + const object = (this.user ? await signedGet(value, this.user) - : await getJson(value, 'application/activity+json, application/ld+json'); + : await getJson(value, 'application/activity+json, application/ld+json')) as IObject; if (object == null || ( Array.isArray(object['@context']) ? - !object['@context'].includes('https://www.w3.org/ns/activitystreams') : + !(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') : object['@context'] !== 'https://www.w3.org/ns/activitystreams' )) { throw new Error('invalid response'); @@ -70,4 +90,44 @@ export default class Resolver { return object; } + + private resolveLocal(url: string): Promise<IObject> { + const parsed = parseUri(url); + if (!parsed.local) throw new Error('resolveLocal: not local'); + + switch (parsed.type) { + case 'notes': + return Notes.findOneByOrFail({ id: parsed.id }) + .then(note => { + if (parsed.rest === 'activity') { + // this refers to the create activity and not the note itself + return renderActivity(renderCreate(renderNote(note))); + } else { + return renderNote(note); + } + }); + case 'users': + return Users.findOneByOrFail({ id: parsed.id }) + .then(user => renderPerson(user as ILocalUser)); + case 'questions': + // Polls are indexed by the note they are attached to. + return Promise.all([ + Notes.findOneByOrFail({ id: parsed.id }), + Polls.findOneByOrFail({ noteId: parsed.id }), + ]) + .then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll)); + case 'likes': + return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null }))); + case 'follows': + // rest should be <followee id> + if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); + + return Promise.all( + [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })) + ) + .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url))); + default: + throw new Error(`resolveLocal: type ${type} unhandled`); + } + } } diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index 2051d2624d..5d00481b75 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -2,7 +2,7 @@ export type obj = { [x: string]: any }; export type ApObject = IObject | string | (IObject | string)[]; export interface IObject { - '@context': string | obj | obj[]; + '@context': string | string[] | obj | obj[]; type: string | string[]; id?: string; summary?: string; @@ -48,7 +48,7 @@ export function getOneApId(value: ApObject): string { export function getApId(value: string | IObject): string { if (typeof value === 'string') return value; if (typeof value.id === 'string') return value.id; - throw new Error(`cannot detemine id`); + throw new Error('cannot detemine id'); } /** @@ -57,7 +57,7 @@ export function getApId(value: string | IObject): string { export function getApType(value: IObject): string { if (typeof value.type === 'string') return value.type; if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0]; - throw new Error(`cannot detect type`); + throw new Error('cannot detect type'); } export function getOneApHrefNullable(value: ApObject | undefined): string | undefined { @@ -106,7 +106,10 @@ export const isPost = (object: IObject): object is IPost => export interface IPost extends IObject { type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; _misskey_talk: boolean; @@ -114,7 +117,10 @@ export interface IPost extends IObject { export interface IQuestion extends IObject { type: 'Note' | 'Question'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; oneOf?: IQuestionChoice[]; diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 133dd36066..cd5f917c40 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -1,6 +1,6 @@ import Router from '@koa/router'; import json from 'koa-json-body'; -import httpSignature from 'http-signature'; +import httpSignature from '@peertube/http-signature'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; @@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js'; import { isSelfHost } from '@/misc/convert-host.js'; import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js'; import { ILocalUser, User } from '@/models/entities/user.js'; -import { In, IsNull } from 'typeorm'; +import { In, IsNull, Not } from 'typeorm'; import { renderLike } from '@/remote/activitypub/renderer/like.js'; import { getUserKeypair } from '@/misc/keypair-store.js'; +import renderFollow from '@/remote/activitypub/renderer/follow.js'; // Init router const router = new Router(); @@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => { setResponseType(ctx); }); +// follow +router.get('/follows/:follower/:followee', async ctx => { + // This may be used before the follow is completed, so we do not + // check if the following exists. + + const [follower, followee] = await Promise.all([ + Users.findOneBy({ + id: ctx.params.follower, + host: IsNull(), + }), + Users.findOneBy({ + id: ctx.params.followee, + host: Not(IsNull()), + }), + ]); + + if (follower == null || followee == null) { + ctx.status = 404; + return; + } + + ctx.body = renderActivity(renderFollow(follower, followee)); + ctx.set('Cache-Control', 'public, max-age=180'); + setResponseType(ctx); +}); + export default router; diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts index 4d4f733162..beb48713a6 100644 --- a/packages/backend/src/server/activitypub/followers.ts +++ b/packages/backend/src/server/activitypub/followers.ts @@ -1,32 +1,26 @@ import Router from '@koa/router'; +import { FindOptionsWhere, IsNull, LessThan } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import * as url from '@/prelude/url.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { IsNull, LessThan } from 'typeorm'; +import { Following } from '@/models/entities/following.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'cursor' parameter - const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor); - - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; - - // Validate parameters - if (cursorErr || pageErr) { + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { ctx.status = 400; return; } + const page = ctx.request.query.page === 'true'; + const user = await Users.findOneBy({ id: userId, host: IsNull(), @@ -57,7 +51,7 @@ export default async (ctx: Router.RouterContext) => { if (page) { const query = { followeeId: user.id, - } as any; + } as FindOptionsWhere<Following>; // カーソルが指定されている場合 if (cursor) { @@ -86,7 +80,7 @@ export default async (ctx: Router.RouterContext) => { inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts index 0af1f424f9..3a25a6316c 100644 --- a/packages/backend/src/server/activitypub/following.ts +++ b/packages/backend/src/server/activitypub/following.ts @@ -1,33 +1,26 @@ import Router from '@koa/router'; +import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import * as url from '@/prelude/url.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js'; -import { setResponseType } from '../activitypub.js'; import { Users, Followings, UserProfiles } from '@/models/index.js'; -import { LessThan, IsNull, FindOptionsWhere } from 'typeorm'; import { Following } from '@/models/entities/following.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'cursor' parameter - const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor); - - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; - - // Validate parameters - if (cursorErr || pageErr) { + const cursor = ctx.request.query.cursor; + if (cursor != null && typeof cursor !== 'string') { ctx.status = 400; return; } + const page = ctx.request.query.page === 'true'; + const user = await Users.findOneBy({ id: userId, host: IsNull(), @@ -87,7 +80,7 @@ export default async (ctx: Router.RouterContext) => { inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts index 6b9592bcf3..7a2586998a 100644 --- a/packages/backend/src/server/activitypub/outbox.ts +++ b/packages/backend/src/server/activitypub/outbox.ts @@ -1,36 +1,37 @@ import Router from '@koa/router'; +import { Brackets, IsNull } from 'typeorm'; import config from '@/config/index.js'; -import $ from 'cafy'; -import { ID } from '@/misc/cafy-id.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js'; import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js'; -import { setResponseType } from '../activitypub.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; import renderCreate from '@/remote/activitypub/renderer/create.js'; import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; import { countIf } from '@/prelude/array.js'; import * as url from '@/prelude/url.js'; import { Users, Notes } from '@/models/index.js'; -import { makePaginationQuery } from '../api/common/make-pagination-query.js'; -import { Brackets, IsNull } from 'typeorm'; import { Note } from '@/models/entities/note.js'; +import { makePaginationQuery } from '../api/common/make-pagination-query.js'; +import { setResponseType } from '../activitypub.js'; export default async (ctx: Router.RouterContext) => { const userId = ctx.params.user; - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.default.optional.type(ID).get(ctx.request.query.since_id); + const sinceId = ctx.request.query.since_id; + if (sinceId != null && typeof sinceId !== 'string') { + ctx.status = 400; + return; + } - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.default.optional.type(ID).get(ctx.request.query.until_id); + const untilId = ctx.request.query.until_id; + if (untilId != null && typeof untilId !== 'string') { + ctx.status = 400; + return; + } - // Get 'page' parameter - const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page); - const page: boolean = ctx.request.query.page === 'true'; + const page = ctx.request.query.page === 'true'; - // Validate parameters - if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) { + if (countIf(x => x != null, [sinceId, untilId]) > 1) { ctx.status = 400; return; } @@ -52,8 +53,8 @@ export default async (ctx: Router.RouterContext) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId) .andWhere('note.userId = :userId', { userId: user.id }) .andWhere(new Brackets(qb => { qb - .where(`note.visibility = 'public'`) - .orWhere(`note.visibility = 'home'`); + .where('note.visibility = \'public\'') + .orWhere('note.visibility = \'home\''); })) .andWhere('note.localOnly = FALSE'); @@ -76,7 +77,7 @@ export default async (ctx: Router.RouterContext) => { notes.length ? `${partOf}?${url.query({ page: 'true', until_id: notes[notes.length - 1].id, - })}` : undefined + })}` : undefined, ); ctx.body = renderActivity(rendered); @@ -85,7 +86,7 @@ export default async (ctx: Router.RouterContext) => { // index page const rendered = renderOrderedCollection(partOf, user.notesCount, `${partOf}?page=true`, - `${partOf}?page=true&since_id=000000000000000000000000` + `${partOf}?page=true&since_id=000000000000000000000000`, ); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'public, max-age=180'); diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts index dce8accaac..96b9316e47 100644 --- a/packages/backend/src/server/api/2fa.ts +++ b/packages/backend/src/server/api/2fa.ts @@ -1,6 +1,6 @@ import * as crypto from 'node:crypto'; -import config from '@/config/index.js'; import * as jsrsasign from 'jsrsasign'; +import config from '@/config/index.js'; const ECC_PRELUDE = Buffer.from([0x04]); const NULL_BYTE = Buffer.from([0]); @@ -145,7 +145,7 @@ export function verifyLogin({ export const procedures = { none: { - verify({ publicKey }: {publicKey: Map<number, Buffer>}) { + verify({ publicKey }: { publicKey: Map<number, Buffer> }) { const negTwo = publicKey.get(-2); if (!negTwo || negTwo.length !== 32) { diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index 9a85e4565b..cd3e0abc06 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -2,10 +2,11 @@ import Koa from 'koa'; import { performance } from 'perf_hooks'; import { limiter } from './limiter.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; -import endpoints, { IEndpoint } from './endpoints.js'; +import endpoints, { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; import { AccessToken } from '@/models/entities/access-token.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; const accessDenied = { message: 'Access denied.', @@ -15,6 +16,7 @@ const accessDenied = { export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => { const isSecure = user != null && token == null; + const isModerator = user != null && (user.isModerator || user.isAdmin); const ep = endpoints.find(e => e.name === endpoint); @@ -31,6 +33,32 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi throw new ApiError(accessDenied); } + if (ep.meta.limit && !isModerator) { + // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. + let limitActor: string; + if (user) { + limitActor = user.id; + } else { + limitActor = getIpHash(ctx!.ip); + } + + const limit = Object.assign({}, ep.meta.limit); + + if (!limit.key) { + limit.key = ep.name; + } + + // Rate limit + await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => { + throw new ApiError({ + message: 'Rate limit exceeded. Please try again later.', + code: 'RATE_LIMIT_EXCEEDED', + id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', + httpStatusCode: 429, + }); + }); + } + if (ep.meta.requireCredential && user == null) { throw new ApiError({ message: 'Credential required.', @@ -53,7 +81,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); } - if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) { + if (ep.meta.requireModerator && !isModerator) { throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); } @@ -65,18 +93,6 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi }); } - if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) { - // Rate limit - await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => { - throw new ApiError({ - message: 'Rate limit exceeded. Please try again later.', - code: 'RATE_LIMIT_EXCEEDED', - id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef', - httpStatusCode: 429, - }); - }); - } - // Cast non JSON input if (ep.meta.requireFile && ep.params.properties) { for (const k of Object.keys(ep.params.properties)) { diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts index 715982934c..b50b6812f4 100644 --- a/packages/backend/src/server/api/common/generate-visibility-query.ts +++ b/packages/backend/src/server/api/common/generate-visibility-query.ts @@ -3,6 +3,7 @@ import { Followings } from '@/models/index.js'; import { Brackets, SelectQueryBuilder } from 'typeorm'; export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) { + // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { q.andWhere(new Brackets(qb => { qb .where(`note.visibility = 'public'`) @@ -11,7 +12,7 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U } else { const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + .where('following.followerId = :meId'); q.andWhere(new Brackets(qb => { qb // 公開投稿である @@ -20,21 +21,22 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U .orWhere(`note.visibility = 'home'`); })) // または 自分自身 - .orWhere('note.userId = :userId1', { userId1: me.id }) + .orWhere('note.userId = :meId') // または 自分宛て - .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`) + .orWhere(':meId = ANY(note.visibleUserIds)') + .orWhere(':meId = ANY(note.mentions)') .orWhere(new Brackets(qb => { qb // または フォロワー宛ての投稿であり、 - .where('note.visibility = \'followers\'') + .where(`note.visibility = 'followers'`) .andWhere(new Brackets(qb => { qb // 自分がフォロワーである .where(`note.userId IN (${ followingQuery.getQuery() })`) // または 自分の投稿へのリプライ - .orWhere('note.replyUserId = :userId3', { userId3: me.id }); + .orWhere('note.replyUserId = :meId'); })); })); })); - q.setParameters(followingQuery.getParameters()); + q.setParameters({ meId: me.id }); } } diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts index 3638518e67..c4c18ffa06 100644 --- a/packages/backend/src/server/api/common/read-messaging-message.ts +++ b/packages/backend/src/server/api/common/read-messaging-message.ts @@ -1,6 +1,7 @@ import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; import { publishMessagingStream } from '@/services/stream.js'; import { publishMessagingIndexStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import { User, IRemoteUser } from '@/models/entities/user.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js'; @@ -50,6 +51,21 @@ export async function readUserMessagingMessage( if (!await Users.getHasUnreadMessagingMessage(userId)) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllMessagingMessages'); + pushNotification(userId, 'readAllMessagingMessages', undefined); + } else { + // そのユーザーとのメッセージで未読がなければイベント発行 + const count = await MessagingMessages.count({ + where: { + userId: otherpartyId, + recipientId: userId, + isRead: false, + }, + take: 1 + }); + + if (!count) { + pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId }); + } } } @@ -104,6 +120,19 @@ export async function readGroupMessagingMessage( if (!await Users.getHasUnreadMessagingMessage(userId)) { // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllMessagingMessages'); + pushNotification(userId, 'readAllMessagingMessages', undefined); + } else { + // そのグループにおいて未読がなければイベント発行 + const unreadExist = await MessagingMessages.createQueryBuilder('message') + .where(`message.groupId = :groupId`, { groupId: groupId }) + .andWhere('message.userId != :userId', { userId: userId }) + .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId }) + .andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない + .getOne().then(x => x != null); + + if (!unreadExist) { + pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId }); + } } } diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts index 1f575042a0..0dad35bcc2 100644 --- a/packages/backend/src/server/api/common/read-notification.ts +++ b/packages/backend/src/server/api/common/read-notification.ts @@ -1,4 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import { User } from '@/models/entities/user.js'; import { Notification } from '@/models/entities/notification.js'; import { Notifications, Users } from '@/models/index.js'; @@ -16,28 +17,29 @@ export async function readNotification( isRead: true, }); - post(userId); + if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId); + else return postReadNotifications(userId, notificationIds); } export async function readNotificationByQuery( userId: User['id'], query: Record<string, any> ) { - // Update documents - await Notifications.update({ + const notificationIds = await Notifications.find({ ...query, notifieeId: userId, isRead: false, - }, { - isRead: true, - }); + }).then(notifications => notifications.map(notification => notification.id)); + + return readNotification(userId, notificationIds); +} - post(userId); +function postReadAllNotifications(userId: User['id']) { + publishMainStream(userId, 'readAllNotifications'); + return pushNotification(userId, 'readAllNotifications', undefined); } -async function post(userId: User['id']) { - if (!await Users.getHasUnreadNotification(userId)) { - // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 - publishMainStream(userId, 'readAllNotifications'); - } +function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { + publishMainStream(userId, 'readNotifications', notificationIds); + return pushNotification(userId, 'readNotifications', { notificationIds }); } diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index e2db03f13a..1e7afd8cdd 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -654,7 +654,6 @@ export interface IEndpointMeta { /** * エンドポイントのリミテーションに関するやつ * 省略した場合はリミテーションは無いものとして解釈されます。 - * また、withCredential が false の場合はリミテーションを行うことはできません。 */ readonly limit?: { diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts index 1d8eb1d618..7a5758d75b 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts @@ -1,5 +1,6 @@ -import define from '../../../define.js'; import { Announcements, AnnouncementReads } from '@/models/index.js'; +import { Announcement } from '@/models/entities/announcement.js'; +import define from '../../../define.js'; import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { @@ -68,11 +69,21 @@ export default define(meta, paramDef, async (ps) => { const announcements = await query.take(ps.limit).getMany(); + const reads = new Map<Announcement, number>(); + for (const announcement of announcements) { - (announcement as any).reads = await AnnouncementReads.countBy({ + reads.set(announcement, await AnnouncementReads.countBy({ announcementId: announcement.id, - }); + })); } - return announcements; + return announcements.map(announcement => ({ + id: announcement.id, + createdAt: announcement.createdAt.toISOString(), + updatedAt: announcement.updatedAt?.toISOString() ?? null, + title: announcement.title, + text: announcement.text, + imageUrl: announcement.imageUrl, + reads: reads.get(announcement)!, + })); }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index bf6cc16532..78033aed58 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -1,5 +1,5 @@ +import { Signins, UserProfiles, Users } from '@/models/index.js'; import define from '../../define.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['admin'], @@ -23,9 +23,12 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, me) => { - const user = await Users.findOneBy({ id: ps.userId }); + const [user, profile] = await Promise.all([ + Users.findOneBy({ id: ps.userId }), + UserProfiles.findOneBy({ userId: ps.userId }) + ]); - if (user == null) { + if (user == null || profile == null) { throw new Error('user not found'); } @@ -34,8 +37,37 @@ export default define(meta, paramDef, async (ps, me) => { throw new Error('cannot show info of admin'); } + if (!_me.isAdmin) { + return { + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + }; + } + + const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken']; + Object.keys(profile.integrations).forEach(integration => { + maskedKeys.forEach(key => profile.integrations[integration][key] = '<MASKED>'); + }); + + const signins = await Signins.findBy({ userId: user.id }); + return { - ...user, - token: user.token != null ? '<MASKED>' : user.token, + email: profile.email, + emailVerified: profile.emailVerified, + autoAcceptFollowed: profile.autoAcceptFollowed, + noCrawle: profile.noCrawle, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + carefulBot: profile.carefulBot, + injectFeaturedNote: profile.injectFeaturedNote, + receiveAnnouncementEmail: profile.receiveAnnouncementEmail, + integrations: profile.integrations, + mutedWords: profile.mutedWords, + mutedInstances: profile.mutedInstances, + mutingNotificationTypes: profile.mutingNotificationTypes, + isModerator: user.isModerator, + isSilenced: user.isSilenced, + isSuspended: user.isSuspended, + signins, }; }); diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 2703b4b9db..1575d81d5d 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { Users } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['admin'], @@ -24,8 +24,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, - state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, username: { type: 'string', nullable: true, default: null }, hostname: { type: 'string', diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index b23ee9e3df..09e43301b7 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -27,7 +27,7 @@ export const paramDef = { blockedHosts: { type: 'array', nullable: true, items: { type: 'string', } }, - themeColor: { type: 'string', nullable: true }, + themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, errorImageUrl: { type: 'string', nullable: true }, diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 7ffe89a1e5..415a8cc693 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Find the notes to which the given file is attached.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 80293df5d9..bbae9bf4e4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Check if a given file exists.', + res: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 0939ae3365..7397fd9ce9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -20,6 +20,8 @@ export const meta = { kind: 'write:drive', + description: 'Upload a new drive file.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 61c56e6314..6108ae7da9 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Delete an existing drive file.', + errors: { noSuchFile: { message: 'No such file.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index f9b4ea89ea..f2bc7348c6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import { DriveFiles } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['drive'], @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by a hash of the contents.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 4938a69d11..245fb45a65 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by the given parameters.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index a2bc0c7aa4..2c604c54c8 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -1,7 +1,7 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { DriveFiles, Users } from '@/models/index.js'; +import define from '../../../define.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['drive'], @@ -10,6 +10,8 @@ export const meta = { kind: 'read:drive', + description: 'Show the properties of a drive file.', + res: { type: 'object', optional: false, nullable: false, @@ -51,7 +53,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - let file: DriveFile | undefined; + let file: DriveFile | null = null; if (ps.fileId) { file = await DriveFiles.findOneBy({ id: ps.fileId }); diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 4b3f5f2dc9..e3debe0b4f 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Update the properties of a drive file.', + errors: { invalidFileName: { message: 'Invalid file name.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 3bfecac802..53f2298f21 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -13,6 +13,8 @@ export const meta = { max: 60, }, + description: 'Request the server to download a new drive file from the specified URL.', + requireCredential: true, kind: 'write:drive', diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts index d5e1b19e54..33f5717728 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -2,8 +2,8 @@ import bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import config from '@/config/index.js'; -import define from '../../../define.js'; import { UserProfiles } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { requireCredential: true, @@ -40,15 +40,17 @@ export default define(meta, paramDef, async (ps, user) => { }); // Get the data URL of the authenticator URL - const dataUrl = await QRCode.toDataURL(speakeasy.otpauthURL({ + const url = speakeasy.otpauthURL({ secret: secret.base32, encoding: 'base32', label: user.username, issuer: config.host, - })); + }); + const dataUrl = await QRCode.toDataURL(url); return { qr: dataUrl, + url, secret: secret.base32, label: user.username, issuer: config.host, diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 9de05918c0..a133294169 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,15 +1,15 @@ import ms from 'ms'; +import { In } from 'typeorm'; import create from '@/services/note/create.js'; -import define from '../../define.js'; -import { ApiError } from '../../error.js'; import { User } from '@/models/entities/user.js'; import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js'; import { DriveFile } from '@/models/entities/drive-file.js'; import { Note } from '@/models/entities/note.js'; -import { noteVisibilities } from '../../../../types.js'; import { Channel } from '@/models/entities/channel.js'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; -import { In } from 'typeorm'; +import { noteVisibilities } from '../../../../types.js'; +import { ApiError } from '../../error.js'; +import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -83,7 +83,7 @@ export const meta = { export const paramDef = { type: 'object', properties: { - visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" }, + visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' }, visibleUserIds: { type: 'array', uniqueItems: true, items: { type: 'string', format: 'misskey:id', } }, @@ -134,7 +134,7 @@ export const paramDef = { { // (re)note with text, files and poll are optional properties: { - text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, + text: { type: 'string', minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false }, }, required: ['text'], }, @@ -149,7 +149,7 @@ export const paramDef = { { // (re)note with poll, text and files are optional properties: { - poll: { type: 'object', nullable: false, }, + poll: { type: 'object', nullable: false }, }, required: ['poll'], }, @@ -172,20 +172,24 @@ export default define(meta, paramDef, async (ps, user) => { let files: DriveFile[] = []; const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { - files = await DriveFiles.findBy({ - userId: user.id, - id: In(fileIds), - }); + files = await DriveFiles.createQueryBuilder('file') + .where('file.userId = :userId AND file.id IN (:...fileIds)', { + userId: user.id, + fileIds, + }) + .orderBy('array_position(ARRAY[:...fileIds], "id"::text)') + .setParameters({ fileIds }) + .getMany(); } - let renote: Note | null; + let renote: Note | null = null; if (ps.renoteId != null) { // Fetch renote to note renote = await Notes.findOneBy({ id: ps.renoteId }); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); - } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.poll) { + } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) { throw new ApiError(meta.errors.cannotReRenote); } @@ -201,14 +205,14 @@ export default define(meta, paramDef, async (ps, user) => { } } - let reply: Note | null; + let reply: Note | null = null; if (ps.replyId != null) { // Fetch reply reply = await Notes.findOneBy({ id: ps.replyId }); if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); - } else if (reply.renoteId && !reply.text && !reply.fileIds && !renote.poll) { + } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) { throw new ApiError(meta.errors.cannotReplyToPureRenote); } @@ -234,7 +238,7 @@ export default define(meta, paramDef, async (ps, user) => { } } - let channel: Channel | undefined; + let channel: Channel | null = null; if (ps.channelId != null) { channel = await Channels.findOneBy({ id: ps.channelId }); diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts index 3555424fa6..fbb065329c 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts @@ -1,8 +1,8 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { DeepPartial, FindOptionsWhere } from 'typeorm'; import { NoteReactions } from '@/models/index.js'; -import { DeepPartial } from 'typeorm'; import { NoteReaction } from '@/models/entities/note-reaction.js'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['notes', 'reactions'], @@ -45,7 +45,7 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const query = { noteId: ps.noteId, - } as DeepPartial<NoteReaction>; + } as FindOptionsWhere<NoteReaction>; if (ps.type) { // ローカルリアクションはホスト名が . とされているが diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts index c602981b30..5e40e7106f 100644 --- a/packages/backend/src/server/api/endpoints/notes/translate.ts +++ b/packages/backend/src/server/api/endpoints/notes/translate.ts @@ -1,12 +1,12 @@ -import define from '../../define.js'; -import { getNote } from '../../common/getters.js'; -import { ApiError } from '../../error.js'; +import { URLSearchParams } from 'node:url'; import fetch from 'node-fetch'; import config from '@/config/index.js'; import { getAgentByUrl } from '@/misc/fetch.js'; -import { URLSearchParams } from 'node:url'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Notes } from '@/models/index.js'; +import { ApiError } from '../../error.js'; +import { getNote } from '../../common/getters.js'; +import define from '../../define.js'; export const meta = { tags: ['notes'], @@ -80,7 +80,12 @@ export default define(meta, paramDef, async (ps, user) => { agent: getAgentByUrl, }); - const json = await res.json(); + const json = (await res.json()) as { + translations: { + detected_source_language: string; + text: string; + }[]; + }; return { sourceLang: json.translations[0].detected_source_language, diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index abefe07be6..4575cba43f 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,4 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; +import { pushNotification } from '@/services/push-notification.js'; import define from '../../define.js'; import { Notifications } from '@/models/index.js'; @@ -28,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => { // 全ての通知を読みましたよというイベントを発行 publishMainStream(user.id, 'readAllNotifications'); + pushNotification(user.id, 'readAllNotifications', undefined); }); diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index c7bc5dc0a5..65e96d4862 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -1,10 +1,12 @@ -import { publishMainStream } from '@/services/stream.js'; import define from '../../define.js'; -import { Notifications } from '@/models/index.js'; import { readNotification } from '../../common/read-notification.js'; -import { ApiError } from '../../error.js'; export const meta = { + desc: { + 'ja-JP': '通知を既読にします。', + 'en-US': 'Mark a notification as read.' + }, + tags: ['notifications', 'account'], requireCredential: true, @@ -21,23 +23,26 @@ export const meta = { } as const; export const paramDef = { - type: 'object', - properties: { - notificationId: { type: 'string', format: 'misskey:id' }, - }, - required: ['notificationId'], + oneOf: [ + { + type: 'object', + properties: { + notificationId: { type: 'string', format: 'misskey:id' }, + }, + required: ['notificationId'], + }, + { + type: 'object', + properties: { + notificationIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } }, + }, + required: ['notificationIds'], + }, + ], } as const; // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - const notification = await Notifications.findOneBy({ - notifieeId: user.id, - id: ps.notificationId, - }); - - if (notification == null) { - throw new ApiError(meta.errors.noSuchNotification); - } - - readNotification(user.id, [notification.id]); + if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]); + return readNotification(user.id, ps.notificationIds); }); diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts index 3dcce8550f..5d37e86b91 100644 --- a/packages/backend/src/server/api/endpoints/pages/show.ts +++ b/packages/backend/src/server/api/endpoints/pages/show.ts @@ -1,8 +1,8 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; +import { IsNull } from 'typeorm'; import { Pages, Users } from '@/models/index.js'; import { Page } from '@/models/entities/page.js'; -import { IsNull } from 'typeorm'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['pages'], @@ -45,7 +45,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { - let page: Page | undefined; + let page: Page | null = null; if (ps.pageId) { page = await Pages.findOneBy({ id: ps.pageId }); diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 046337f040..12ce7a9834 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -10,8 +10,12 @@ import { genId } from '@/misc/gen-id.js'; import { IsNull } from 'typeorm'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Request a users password to be reset.', + limit: { duration: ms('1hour'), max: 3, diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index dbe64e9a13..5ff115dab5 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -3,8 +3,12 @@ import { ApiError } from '../error.js'; import { resetDb } from '@/db/postgre.js'; export const meta = { + tags: ['non-productive'], + requireCredential: false, + description: 'Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 7acc545c40..3dcb0b9b83 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -5,8 +5,12 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; import { ApiError } from '../error.js'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Complete the password reset that was previously requested.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index a48973a0df..5bc3b9b6a1 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -8,6 +8,8 @@ export const meta = { requireCredential: true, + description: 'Register to receive push notifications.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 9748f2a222..c21856d28f 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -5,6 +5,8 @@ export const meta = { tags: ['account'], requireCredential: true, + + description: 'Unregister from receiving push notifications.', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 256da1a66f..9949237a7e 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,6 +1,10 @@ import define from '../define.js'; export const meta = { + tags: ['non-productive'], + + description: 'Endpoint for testing input validation.', + requireCredential: false, } as const; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 424c594749..37d4153950 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'clips'], + + description: 'Show all clips this user owns.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Clip', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 26b1f20df0..b1fb656208 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Show everyone that follows this user.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 42cf5216e8..429a5e80e5 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Show everyone that this user is following.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index d7c435256c..35bf2df598 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'gallery'], + + description: 'Show all gallery posts by the given user.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'GalleryPost', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 73cadc0df7..ab5837b3f3 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -10,6 +10,8 @@ export const meta = { requireCredential: false, + description: 'Get a list of other users that the specified user frequently replies to.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index fc775d7cc1..fcaf4af3c3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Create a new group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index f68006994c..1bf253ae3f 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 75c1acc302..eafd7f592c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Join a group the authenticated user has been invited to.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 46bc780ab0..08d3a3804b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group invitation for the authenticated user without joining the group.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 30a5beb1d9..cc82e43f21 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -13,6 +13,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Invite a user to an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 77dc59d3e5..6a2862ee5a 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is a member of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 33abd5439f..2343cdf857 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index b1289e601f..de030193cc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is the owner of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index b31990b2e3..703dad6d3b 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Removes a specified user from a group. The owner can not be removed.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 3ffb0f5ba9..e1cee5fcf7 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'Show the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 41ceee3b2e..1496e766ca 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Transfer ownership of a group from the authenticated user to another user.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 1016aa8926..43cf3e484e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Update the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index d5260256d5..d2941a0af5 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'write:account', + description: 'Create a new list of users.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index b7ad96eef0..8cd02ee02a 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Delete an existing list of users.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 78311292cb..b337f879b1 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:account', + description: 'Show all lists that the authenticated user has created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 76863f07d1..fa7033b02e 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Remove a user from a list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 260665c63a..1db10afc80 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Add a user to an existing list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 5f51980e95..94d24e1274 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:account', + description: 'Show the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 52353a14cc..c21cdcf679 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Update the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 16318d2225..57dcdfaa88 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -12,6 +12,8 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance export const meta = { tags: ['users', 'notes'], + description: 'Show all notes that this user created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b8b3e8192e..85d122c24f 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'pages'], + + description: 'Show all pages this user created.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Page', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index c2d1994343..64994aae49 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: false, + description: 'Show all reactions this user made.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index a8f18de522..6fff94ddcf 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'read:account', + description: 'Show users that the authenticated user might be interested to follow.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index c6262122d4..87cab5fcf1 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -6,6 +6,8 @@ export const meta = { requireCredential: true, + description: 'Show the different kinds of relations between the authenticated user and the specified user(s).', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 0be385dbbf..c7c7a3f591 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -13,6 +13,8 @@ export const meta = { requireCredential: true, + description: 'File a report.', + errors: { noSuchUser: { message: 'No such user.', diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index f74d80e2ae..6cbf12b3b5 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -9,6 +9,8 @@ export const meta = { requireCredential: false, + description: 'Search for a user by username and/or host.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index a72a58a843..19c1a2c690 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -8,6 +8,8 @@ export const meta = { requireCredential: false, + description: 'Search for users.', + res: { type: 'array', optional: false, nullable: false, @@ -61,7 +63,14 @@ export default define(meta, paramDef, async (ps, me) => { .getMany(); } else { const nameQuery = Users.createQueryBuilder('user') - .where('user.name ILIKE :query', { query: '%' + ps.query + '%' }) + .where(new Brackets(qb => { + qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' }); + + // Also search username if it qualifies as username + if (Users.validateLocalUsername(ps.query)) { + qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' }); + } + })) .andWhere(new Brackets(qb => { qb .where('user.updatedAt IS NULL') .orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold }); diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 183ff1b8bb..b31ca30647 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -11,6 +11,8 @@ export const meta = { requireCredential: false, + description: 'Show the properties of a user.', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index d138019a72..d17e8b64b5 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,12 +1,15 @@ import define from '../../define.js'; import { ApiError } from '../../error.js'; import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; +import { awaitAll } from '@/prelude/await-all.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show statistics about a user.', + errors: { noSuchUser: { message: 'No such user.', @@ -14,6 +17,94 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + notesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliesCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliedCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + followingCount: { + type: 'integer', + optional: false, nullable: false, + }, + followersCount: { + type: 'integer', + optional: false, nullable: false, + }, + sentReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + receivedReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + noteFavoritesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikedCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveFilesCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveUsage: { + type: 'integer', + optional: false, nullable: false, + description: 'Drive usage in bytes', + }, + }, + }, } as const; export const paramDef = { @@ -31,109 +122,72 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchUser); } - const [ - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - ] = await Promise.all([ - Notes.createQueryBuilder('note') + const result = await awaitAll({ + notesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + repliesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.replyId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + renotesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.renoteId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + repliedCount: Notes.createQueryBuilder('note') .where('note.replyUserId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + renotedCount: Notes.createQueryBuilder('note') .where('note.renoteUserId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotesCount: PollVotes.createQueryBuilder('vote') .where('vote.userId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotedCount: PollVotes.createQueryBuilder('vote') .innerJoin('vote.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Followings.createQueryBuilder('following') + localFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NOT NULL') .getCount(), - Followings.createQueryBuilder('following') + localFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NOT NULL') .getCount(), - NoteReactions.createQueryBuilder('reaction') + sentReactionsCount: NoteReactions.createQueryBuilder('reaction') .where('reaction.userId = :userId', { userId: user.id }) .getCount(), - NoteReactions.createQueryBuilder('reaction') + receivedReactionsCount: NoteReactions.createQueryBuilder('reaction') .innerJoin('reaction.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - NoteFavorites.createQueryBuilder('favorite') + noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') .where('favorite.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikesCount: PageLikes.createQueryBuilder('like') .where('like.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikedCount: PageLikes.createQueryBuilder('like') .innerJoin('like.page', 'page') .where('page.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.createQueryBuilder('file') + driveFilesCount: DriveFiles.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.calcDriveUsageOf(user), - ]); + driveUsage: DriveFiles.calcDriveUsageOf(user), + }); + + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; - return { - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - followingCount: localFollowingCount + remoteFollowingCount, - followersCount: localFollowersCount + remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - }; + return result; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index e74db8466e..23430cf8b6 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -1,25 +1,17 @@ import Limiter from 'ratelimiter'; import { redisClient } from '../../db/redis.js'; -import { IEndpoint } from './endpoints.js'; -import * as Acct from '@/misc/acct.js'; +import { IEndpointMeta } from './endpoints.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; const logger = new Logger('limiter'); -export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => { - const limitation = endpoint.meta.limit; - - const key = Object.prototype.hasOwnProperty.call(limitation, 'key') - ? limitation.key - : endpoint.name; - - const hasShortTermLimit = - Object.prototype.hasOwnProperty.call(limitation, 'minInterval'); +export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((ok, reject) => { + const hasShortTermLimit = typeof limitation.minInterval === 'number'; const hasLongTermLimit = - Object.prototype.hasOwnProperty.call(limitation, 'duration') && - Object.prototype.hasOwnProperty.call(limitation, 'max'); + typeof limitation.duration === 'number' && + typeof limitation.max === 'number'; if (hasShortTermLimit) { min(); @@ -32,7 +24,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp // Short-term limit function min(): void { const minIntervalLimiter = new Limiter({ - id: `${user.id}:${key}:min`, + id: `${actor}:${limitation.key}:min`, duration: limitation.minInterval, max: 1, db: redisClient, @@ -43,7 +35,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp return reject('ERR'); } - logger.debug(`@${Acct.toString(user)} ${endpoint.name} min remaining: ${info.remaining}`); + logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); if (info.remaining === 0) { reject('BRIEF_REQUEST_INTERVAL'); @@ -60,7 +52,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp // Long term limit function max(): void { const limiter = new Limiter({ - id: `${user.id}:${key}`, + id: `${actor}:${limitation.key}`, duration: limitation.duration, max: limitation.max, db: redisClient, @@ -71,7 +63,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp return reject('ERR'); } - logger.debug(`@${Acct.toString(user)} ${endpoint.name} max remaining: ${info.remaining}`); + logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { reject('RATE_LIMIT_EXCEEDED'); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index c6e557aefb..3929fff3f7 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -59,6 +59,18 @@ export function genOpenapiSpec(lang = 'ja-JP') { desc += ` / **Permission**: *${kind}*`; } + const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; + const schema = endpoint.params; + + if (endpoint.meta.requireFile) { + schema.properties.file = { + type: 'string', + format: 'binary', + description: 'The file contents.', + }; + schema.required.push('file'); + } + const info = { operationId: endpoint.name, summary: endpoint.name, @@ -78,8 +90,8 @@ export function genOpenapiSpec(lang = 'ja-JP') { requestBody: { required: true, content: { - 'application/json': { - schema: endpoint.params, + [requestType]: { + schema, }, }, }, diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts index 7b66657ad8..79b31764fd 100644 --- a/packages/backend/src/server/api/private/signin.ts +++ b/packages/backend/src/server/api/private/signin.ts @@ -9,6 +9,8 @@ import { genId } from '@/misc/gen-id.js'; import { verifyLogin, hash } from '../2fa.js'; import { randomBytes } from 'node:crypto'; import { IsNull } from 'typeorm'; +import { limiter } from '../limiter.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; export default async (ctx: Koa.Context) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -24,6 +26,21 @@ export default async (ctx: Koa.Context) => { ctx.body = { error }; } + try { + // not more than 1 attempt per second and not more than 10 attempts per hour + await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip)); + } catch (err) { + ctx.status = 429; + ctx.body = { + error: { + message: 'Too many failed attempts to sign in. Try again later.', + code: 'TOO_MANY_AUTHENTICATION_FAILURES', + id: '22d05606-fbcf-421a-a2db-b32610dcfd1b', + }, + }; + return; + } + if (typeof username !== 'string') { ctx.status = 400; return; diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts index 04197574c2..97cbcbecdb 100644 --- a/packages/backend/src/server/api/service/discord.ts +++ b/packages/backend/src/server/api/service/discord.ts @@ -1,16 +1,16 @@ import Koa from 'koa'; import Router from '@koa/router'; -import { getJson } from '@/misc/fetch.js'; import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { getJson } from '@/misc/fetch.js'; import config from '@/config/index.js'; import { publishMainStream } from '@/services/stream.js'; -import { redisClient } from '../../../db/redis.js'; -import { v4 as uuid } from 'uuid'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import { redisClient } from '../../../db/redis.js'; +import signin from '../common/signin.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -54,7 +54,7 @@ router.get('/disconnect/discord', async ctx => { integrations: profile.integrations, }); - ctx.body = `Discordの連携を解除しました :v:`; + ctx.body = 'Discordの連携を解除しました :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -140,7 +140,7 @@ router.get('/dc/cb', async ctx => { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -174,17 +174,17 @@ router.get('/dc/cb', async ctx => { } })); - const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, - }); + })) as Record<string, unknown>; - if (!id || !username || !discriminator) { + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { ctx.throw(400, 'invalid session'); return; } const profile = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'discord'->>'id' = :id`, { id: id }) + .where('"integrations"->\'discord\'->>\'id\' = :id', { id: id }) .andWhere('"userHost" IS NULL') .getOne(); @@ -211,7 +211,7 @@ router.get('/dc/cb', async ctx => { } else { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -245,10 +245,10 @@ router.get('/dc/cb', async ctx => { } })); - const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { + const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, { 'Authorization': `Bearer ${accessToken}`, - }); - if (!id || !username || !discriminator) { + })) as Record<string, unknown>; + if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts index 61bb768a63..04dbd1f7ab 100644 --- a/packages/backend/src/server/api/service/github.ts +++ b/packages/backend/src/server/api/service/github.ts @@ -1,16 +1,16 @@ import Koa from 'koa'; import Router from '@koa/router'; -import { getJson } from '@/misc/fetch.js'; import { OAuth2 } from 'oauth'; +import { v4 as uuid } from 'uuid'; +import { IsNull } from 'typeorm'; +import { getJson } from '@/misc/fetch.js'; import config from '@/config/index.js'; import { publishMainStream } from '@/services/stream.js'; -import { redisClient } from '../../../db/redis.js'; -import { v4 as uuid } from 'uuid'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import { redisClient } from '../../../db/redis.js'; +import signin from '../common/signin.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -54,7 +54,7 @@ router.get('/disconnect/github', async ctx => { integrations: profile.integrations, }); - ctx.body = `GitHubの連携を解除しました :v:`; + ctx.body = 'GitHubの連携を解除しました :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -138,7 +138,7 @@ router.get('/gh/cb', async ctx => { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -167,16 +167,16 @@ router.get('/gh/cb', async ctx => { } })); - const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, - }); - if (!login || !id) { + })) as Record<string, unknown>; + if (typeof login !== 'string' || typeof id !== 'string') { ctx.throw(400, 'invalid session'); return; } const link = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'github'->>'id' = :id`, { id: id }) + .where('"integrations"->\'github\'->>\'id\' = :id', { id: id }) .andWhere('"userHost" IS NULL') .getOne(); @@ -189,7 +189,7 @@ router.get('/gh/cb', async ctx => { } else { const code = ctx.query.code; - if (!code) { + if (!code || typeof code !== 'string') { ctx.throw(400, 'invalid session'); return; } @@ -219,11 +219,11 @@ router.get('/gh/cb', async ctx => { } })); - const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { + const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, { 'Authorization': `bearer ${accessToken}`, - }); + })) as Record<string, unknown>; - if (!login || !id) { + if (typeof login !== 'string' || typeof id !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts index e72b71e2f7..2b4f9f6daa 100644 --- a/packages/backend/src/server/api/service/twitter.ts +++ b/packages/backend/src/server/api/service/twitter.ts @@ -2,14 +2,14 @@ import Koa from 'koa'; import Router from '@koa/router'; import { v4 as uuid } from 'uuid'; import autwh from 'autwh'; -import { redisClient } from '../../../db/redis.js'; +import { IsNull } from 'typeorm'; import { publishMainStream } from '@/services/stream.js'; import config from '@/config/index.js'; -import signin from '../common/signin.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Users, UserProfiles } from '@/models/index.js'; import { ILocalUser } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import signin from '../common/signin.js'; +import { redisClient } from '../../../db/redis.js'; function getUserToken(ctx: Koa.BaseContext): string | null { return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1]; @@ -53,7 +53,7 @@ router.get('/disconnect/twitter', async ctx => { integrations: profile.integrations, }); - ctx.body = `Twitterの連携を解除しました :v:`; + ctx.body = 'Twitterの連携を解除しました :v:'; // Publish i updated event publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { @@ -132,10 +132,16 @@ router.get('/tw/cb', async ctx => { const twCtx = await get; - const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier); + const verifier = ctx.query.oauth_verifier; + if (!verifier || typeof verifier !== 'string') { + ctx.throw(400, 'invalid session'); + return; + } + + const result = await twAuth!.done(JSON.parse(twCtx), verifier); const link = await UserProfiles.createQueryBuilder() - .where(`"integrations"->'twitter'->>'userId' = :id`, { id: result.userId }) + .where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId }) .andWhere('"userHost" IS NULL') .getOne(); @@ -148,7 +154,7 @@ router.get('/tw/cb', async ctx => { } else { const verifier = ctx.query.oauth_verifier; - if (verifier == null) { + if (!verifier || typeof verifier !== 'string') { ctx.throw(400, 'invalid session'); return; } diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts index 043d03ab8d..b67600474b 100644 --- a/packages/backend/src/server/api/stream/channels/queue-stats.ts +++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Channel from '../channel.js'; -const ev = new Xev.default(); +const ev = new Xev(); export default class extends Channel { public readonly chName = 'queueStats'; diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts index 0da1895767..db75a6fa38 100644 --- a/packages/backend/src/server/api/stream/channels/server-stats.ts +++ b/packages/backend/src/server/api/stream/channels/server-stats.ts @@ -1,7 +1,7 @@ -import { default as Xev } from 'xev'; +import Xev from 'xev'; import Channel from '../channel.js'; -const ev = new Xev.default(); +const ev = new Xev(); export default class extends Channel { public readonly chName = 'serverStats'; diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts index b803478281..2d23145f14 100644 --- a/packages/backend/src/server/api/stream/index.ts +++ b/packages/backend/src/server/api/stream/index.ts @@ -1,27 +1,25 @@ +import { EventEmitter } from 'events'; import * as websocket from 'websocket'; -import { readNotification } from '../common/read-notification.js'; -import call from '../call.js'; import readNote from '@/services/note/read.js'; -import Channel from './channel.js'; -import channels from './channels/index.js'; -import { EventEmitter } from 'events'; import { User } from '@/models/entities/user.js'; import { Channel as ChannelModel } from '@/models/entities/channel.js'; import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js'; -import { ApiError } from '../error.js'; import { AccessToken } from '@/models/entities/access-token.js'; import { UserProfile } from '@/models/entities/user-profile.js'; import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js'; import { UserGroup } from '@/models/entities/user-group.js'; -import { StreamEventEmitter, StreamMessages } from './types.js'; import { Packed } from '@/misc/schema.js'; +import { readNotification } from '../common/read-notification.js'; +import channels from './channels/index.js'; +import Channel from './channel.js'; +import { StreamEventEmitter, StreamMessages } from './types.js'; /** * Main stream connection */ export default class Connection { public user?: User; - public userProfile?: UserProfile; + public userProfile?: UserProfile | null; public following: Set<User['id']> = new Set(); public muting: Set<User['id']> = new Set(); public blocking: Set<User['id']> = new Set(); // "被"blocking @@ -84,7 +82,7 @@ export default class Connection { this.muting.delete(data.body.id); break; - // TODO: block events + // TODO: block events case 'followChannel': this.followingChannels.add(data.body.id); @@ -126,7 +124,6 @@ export default class Connection { const { type, body } = obj; switch (type) { - case 'api': this.onApiRequest(body); break; case 'readNotification': this.onReadNotification(body); break; case 'subNote': this.onSubscribeNote(body); break; case 's': this.onSubscribeNote(body); break; // alias @@ -183,31 +180,6 @@ export default class Connection { } } - /** - * APIリクエスト要求時 - */ - private async onApiRequest(payload: any) { - // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null; - - const endpoint = payload.endpoint || payload.ep; // alias - - // 呼び出し - call(endpoint, user, this.token, payload.data).then(res => { - this.sendMessageToWs(`api:${payload.id}`, { res }); - }).catch((e: ApiError) => { - this.sendMessageToWs(`api:${payload.id}`, { - error: { - message: e.message, - code: e.code, - id: e.id, - kind: e.kind, - ...(e.info ? { info: e.info } : {}), - }, - }); - }); - } - private onReadNotification(payload: any) { if (!payload.id) return; readNotification(this.user!.id, [payload.id]); diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts index 2a34edac67..f8e42d27fe 100644 --- a/packages/backend/src/server/api/streaming.ts +++ b/packages/backend/src/server/api/streaming.ts @@ -1,4 +1,4 @@ -import * as http from 'http'; +import * as http from 'node:http'; import * as websocket from 'websocket'; import MainStreamConnection from './stream/index.js'; diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts index 6bc220b362..c34e043145 100644 --- a/packages/backend/src/server/file/send-drive-file.ts +++ b/packages/backend/src/server/file/send-drive-file.ts @@ -4,14 +4,14 @@ import { dirname } from 'node:path'; import Koa from 'koa'; import send from 'koa-send'; import rename from 'rename'; -import * as tmp from 'tmp'; import { serverLogger } from '../index.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { DriveFiles } from '@/models/index.js'; import { InternalStorage } from '@/services/drive/internal-storage.js'; +import { createTemp } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { detectType } from '@/misc/get-file-info.js'; -import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor.js'; +import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js'; import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js'; import { StatusError } from '@/misc/fetch.js'; import { FILE_TYPE_BROWSERSAFE } from '@/const.js'; @@ -50,12 +50,7 @@ export default async function(ctx: Koa.Context) { if (!file.storedInternal) { if (file.isLink && file.uri) { // 期限切れリモートファイル - const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.file((e, path, fd, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); + const [path, cleanup] = await createTemp(); try { await downloadUrl(file.uri, path); @@ -64,10 +59,8 @@ export default async function(ctx: Koa.Context) { const convertFile = async () => { if (isThumbnail) { - if (['image/jpeg', 'image/webp'].includes(mime)) { - return await convertToJpeg(path, 498, 280); - } else if (['image/png', 'image/svg+xml'].includes(mime)) { - return await convertToPngOrJpeg(path, 498, 280); + if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) { + return await convertToWebp(path, 498, 280); } else if (mime.startsWith('video/')) { return await GenerateVideoThumbnail(path); } diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts index b50e38a63b..f31de2b7f4 100644 --- a/packages/backend/src/server/index.ts +++ b/packages/backend/src/server/index.ts @@ -2,8 +2,9 @@ * Core Server */ +import cluster from 'node:cluster'; import * as fs from 'node:fs'; -import * as http from 'http'; +import * as http from 'node:http'; import Koa from 'koa'; import Router from '@koa/router'; import mount from 'koa-mount'; @@ -88,10 +89,10 @@ router.get('/avatar/@:acct', async ctx => { }); router.get('/identicon/:x', async ctx => { - const [temp] = await createTemp(); + const [temp, cleanup] = await createTemp(); await genIdenticon(ctx.params.x, fs.createWriteStream(temp)); ctx.set('Content-Type', 'image/png'); - ctx.body = fs.createReadStream(temp); + ctx.body = fs.createReadStream(temp).on('close', () => cleanup()); }); router.get('/verify-email/:code', async ctx => { @@ -142,5 +143,26 @@ export default () => new Promise(resolve => { initializeStreamingServer(server); + server.on('error', e => { + switch ((e as any).code) { + case 'EACCES': + serverLogger.error(`You do not have permission to listen on port ${config.port}.`); + break; + case 'EADDRINUSE': + serverLogger.error(`Port ${config.port} is already in use by another process.`); + break; + default: + serverLogger.error(e); + break; + } + + if (cluster.isWorker) { + process.send!('listenFailed'); + } else { + // disableClustering + process.exit(1); + } + }); + server.listen(config.port, resolve); }); diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts index 3cc5b827a6..48887bf12f 100644 --- a/packages/backend/src/server/proxy/proxy-media.ts +++ b/packages/backend/src/server/proxy/proxy-media.ts @@ -1,7 +1,7 @@ import * as fs from 'node:fs'; import Koa from 'koa'; import { serverLogger } from '../index.js'; -import { IImage, convertToPng, convertToJpeg } from '@/services/drive/image-processor.js'; +import { IImage, convertToWebp } from '@/services/drive/image-processor.js'; import { createTemp } from '@/misc/create-temp.js'; import { downloadUrl } from '@/misc/download-url.js'; import { detectType } from '@/misc/get-file-info.js'; @@ -27,11 +27,11 @@ export async function proxyMedia(ctx: Koa.Context) { let image: IImage; if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) { - image = await convertToPng(path, 498, 280); + image = await convertToWebp(path, 498, 280); } else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) { - image = await convertToJpeg(path, 200, 200); + image = await convertToWebp(path, 200, 200); } else if (['image/svg+xml'].includes(mime)) { - image = await convertToPng(path, 2048, 2048); + image = await convertToWebp(path, 2048, 2048, 1); } else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) { throw new StatusError('Rejected type', 403, 'Rejected type'); } else { diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index 751e8619bf..94329e11c9 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -54,19 +54,11 @@ //#endregion //#region Script - const salt = localStorage.getItem('salt') - ? `?salt=${localStorage.getItem('salt')}` - : ''; - - const script = document.createElement('script'); - script.setAttribute('src', `/assets/app.${v}.js${salt}`); - script.setAttribute('async', 'true'); - script.setAttribute('defer', 'true'); - script.addEventListener('error', async () => { - await checkUpdate(); - renderError('APP_FETCH_FAILED'); - }); - document.head.appendChild(script); + import(`/assets/${CLIENT_ENTRY}`) + .catch(async e => { + await checkUpdate(); + renderError('APP_FETCH_FAILED', JSON.stringify(e)); + }) //#endregion //#region Theme @@ -146,9 +138,6 @@ // eslint-disable-next-line no-inner-declarations function refresh() { - // Random - localStorage.setItem('salt', Math.random().toString().substr(2, 8)); - // Clear cache (service worker) try { navigator.serviceWorker.controller.postMessage('clear'); diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index 34d56cfd0c..2feee72be7 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -4,6 +4,7 @@ import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { PathOrFileDescriptor, readFileSync } from 'node:fs'; import ms from 'ms'; import Koa from 'koa'; import Router from '@koa/router'; @@ -14,7 +15,7 @@ import { createBullBoard } from '@bull-board/api'; import { BullAdapter } from '@bull-board/api/bullAdapter.js'; import { KoaAdapter } from '@bull-board/koa'; -import { IsNull } from 'typeorm'; +import { In, IsNull } from 'typeorm'; import { fetchMeta } from '@/misc/fetch-meta.js'; import config from '@/config/index.js'; import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js'; @@ -32,6 +33,7 @@ const _dirname = dirname(_filename); const staticAssets = `${_dirname}/../../../assets/`; const clientAssets = `${_dirname}/../../../../client/assets/`; const assets = `${_dirname}/../../../../../built/_client_dist_/`; +const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`; // Init app const app = new Koa(); @@ -72,6 +74,9 @@ app.use(views(_dirname + '/views', { extension: 'pug', options: { version: config.version, + getClientEntry: () => process.env.NODE_ENV === 'production' ? + config.clientEntry : + JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'], config, }, })); @@ -136,9 +141,10 @@ router.get('/twemoji/(.*)', async ctx => { }); // ServiceWorker -router.get('/sw.js', async ctx => { - await send(ctx as any, `/sw.${config.version}.js`, { - root: assets, +router.get(`/sw.js`, async ctx => { + await send(ctx as any, `/sw.js`, { + root: swAssets, + maxage: ms('10 minutes'), }); }); @@ -241,7 +247,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=30'); + ctx.set('Cache-Control', 'public, max-age=15'); } else { // リモートユーザーなので // モデレータがAPI経由で参照可能にするために404にはしない @@ -266,7 +272,10 @@ router.get('/users/:user', async ctx => { // Note router.get('/notes/:note', async (ctx, next) => { - const note = await Notes.findOneBy({ id: ctx.params.note }); + const note = await Notes.findOneBy({ + id: ctx.params.note, + visibility: In(['public', 'home']), + }); if (note) { const _note = await Notes.pack(note); @@ -283,11 +292,7 @@ router.get('/notes/:note', async (ctx, next) => { themeColor: meta.themeColor, }); - if (['public', 'home'].includes(note.visibility)) { - ctx.set('Cache-Control', 'public, max-age=180'); - } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - } + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -324,7 +329,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => { }); if (['public'].includes(page.visibility)) { - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); } else { ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); } @@ -355,7 +360,7 @@ router.get('/clips/:clip', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -380,7 +385,7 @@ router.get('/gallery/:post', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -404,7 +409,7 @@ router.get('/channels/:channel', async (ctx, next) => { themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=15'); return; } @@ -463,7 +468,7 @@ router.get('(.*)', async ctx => { icon: meta.iconUrl, themeColor: meta.themeColor, }); - ctx.set('Cache-Control', 'public, max-age=300'); + ctx.set('Cache-Control', 'public, max-age=15'); }); // Register router diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts index bcbf9b76a7..ee568b8077 100644 --- a/packages/backend/src/server/web/manifest.ts +++ b/packages/backend/src/server/web/manifest.ts @@ -1,16 +1,18 @@ import Koa from 'koa'; -import manifest from './manifest.json' assert { type: 'json' }; import { fetchMeta } from '@/misc/fetch-meta.js'; +import manifest from './manifest.json' assert { type: 'json' }; export const manifestHandler = async (ctx: Koa.Context) => { - const json = JSON.parse(JSON.stringify(manifest)); + // TODO + //const res = structuredClone(manifest); + const res = JSON.parse(JSON.stringify(manifest)); const instance = await fetchMeta(true); - json.short_name = instance.name || 'Misskey'; - json.name = instance.name || 'Misskey'; - if (instance.themeColor) json.theme_color = instance.themeColor; + res.short_name = instance.name || 'Misskey'; + res.name = instance.name || 'Misskey'; + if (instance.themeColor) res.theme_color = instance.themeColor; ctx.set('Cache-Control', 'max-age=300'); - ctx.body = json; + ctx.body = res; }; diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index 9c4cd4b9bf..d59f00fe16 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -39,28 +39,24 @@ html { width: 28px; height: 28px; transform: translateY(70px); + color: var(--accent); } - -#splashSpinner:before, -#splashSpinner:after { - content: " "; - display: block; - box-sizing: border-box; +#splashSpinner > .spinner { + position: absolute; + top: 0; + left: 0; width: 28px; height: 28px; - border-radius: 50%; - border: solid 4px; + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; } - -#splashSpinner:before { - border-color: currentColor; - opacity: 0.3; +#splashSpinner > .spinner.bg { + opacity: 0.275; } - -#splashSpinner:after { - position: absolute; - top: 0; - border-color: currentColor transparent transparent transparent; +#splashSpinner > .spinner.fg { animation: splashSpinner 0.5s linear infinite; } diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts index 6bd8ead5b5..1e259649f9 100644 --- a/packages/backend/src/server/web/url-preview.ts +++ b/packages/backend/src/server/web/url-preview.ts @@ -56,7 +56,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => { function wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) - ? `${config.url}/proxy/preview.jpg?${query({ + ? `${config.url}/proxy/preview.webp?${query({ url, preview: '1', })}` diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 1513208310..5bb156f0f4 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -1,17 +1,21 @@ block vars +block loadClientEntry + - const clientEntry = getClientEntry(); + doctype html -!= '<!--\n' -!= ' _____ _ _ \n' -!= ' | |_|___ ___| |_ ___ _ _ \n' -!= ' | | | | |_ -|_ -| \'_| -_| | |\n' -!= ' |_|_|_|_|___|___|_,_|___|_ |\n' -!= ' |___|\n' -!= ' Thank you for using Misskey!\n' -!= ' If you are reading this message... how about joining the development?\n' -!= ' https://github.com/misskey-dev/misskey' -!= '\n-->\n' +// + - + _____ _ _ + | |_|___ ___| |_ ___ _ _ + | | | | |_ -|_ -| '_| -_| | | + |_|_|_|_|___|___|_,_|___|_ | + |___| + Thank you for using Misskey! + If you are reading this message... how about joining the development? + https://github.com/misskey-dev/misskey + html @@ -30,8 +34,14 @@ html link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg') link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg') - link(rel='preload' href='/assets/fontawesome/css/all.css' as='style') link(rel='stylesheet' href='/assets/fontawesome/css/all.css') + link(rel='modulepreload' href=`/assets/${clientEntry.file}`) + + each href in clientEntry.css + link(rel='preload' href=`/assets/${href}` as='style') + + each href in clientEntry.css + link(rel='preload' href=`/assets/${href}` as='style') title block title @@ -50,6 +60,10 @@ html style include ../style.css + script. + var VERSION = "#{version}"; + var CLIENT_ENTRY = "#{clientEntry.file}"; + script include ../boot.js @@ -61,4 +75,14 @@ html div#splash img#splashIcon(src= icon || '/static-assets/splash.png') div#splashSpinner + <svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> + <svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1,0,0,1,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/> + </g> + </svg> block content diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts index 7530b4e0ba..1d094f2edd 100644 --- a/packages/backend/src/server/well-known.ts +++ b/packages/backend/src/server/well-known.ts @@ -41,6 +41,7 @@ router.options(allPath, async ctx => { router.get('/.well-known/host-meta', async ctx => { ctx.set('Content-Type', xrd); ctx.body = XRD({ element: 'Link', attributes: { + rel: 'lrdd', type: xrd, template: `${config.url}${webFingerPath}?resource={uri}`, } }); diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts index 5e96e5037f..a2c61cca22 100644 --- a/packages/backend/src/services/blocking/create.ts +++ b/packages/backend/src/services/blocking/create.ts @@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderFollow from '@/remote/activitypub/renderer/follow.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; -import renderBlock from '@/remote/activitypub/renderer/block.js'; +import { renderBlock } from '@/remote/activitypub/renderer/block.js'; import { deliver } from '@/queue/index.js'; import renderReject from '@/remote/activitypub/renderer/reject.js'; +import { Blocking } from '@/models/entities/blocking.js'; import { User } from '@/models/entities/user.js'; import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js'; import { perUserFollowingChart } from '@/services/chart/index.js'; @@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) { removeFromList(blockee, blocker), ]); - await Blockings.insert({ + const blocking = { id: genId(), createdAt: new Date(), + blocker, blockerId: blocker.id, + blockee, blockeeId: blockee.id, - }); + } as Blocking; + + await Blockings.insert(blocking); if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { - const content = renderActivity(renderBlock(blocker, blockee)); + const content = renderActivity(renderBlock(blocking)); deliver(blocker, content, blockee.inbox); } } @@ -95,17 +100,12 @@ async function unFollow(follower: User, followee: User) { return; } - Followings.delete(following.id); - - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); - //#endregion - - perUserFollowingChart.update(follower, followee, false); + await Promise.all([ + Followings.delete(following.id), + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + perUserFollowingChart.update(follower, followee, false), + ]); // Publish unfollow event if (Users.isLocalUser(follower)) { diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts index d7b5ddd5ff..cb16651bc0 100644 --- a/packages/backend/src/services/blocking/delete.ts +++ b/packages/backend/src/services/blocking/delete.ts @@ -1,5 +1,5 @@ import { renderActivity } from '@/remote/activitypub/renderer/index.js'; -import renderBlock from '@/remote/activitypub/renderer/block.js'; +import { renderBlock } from '@/remote/activitypub/renderer/block.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { deliver } from '@/queue/index.js'; import Logger from '../logger.js'; @@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) { return; } + // Since we already have the blocker and blockee, we do not need to fetch + // them in the query above and can just manually insert them here. + blocking.blocker = blocker; + blocking.blockee = blockee; + Blockings.delete(blocking.id); // deliver if remote bloking if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) { - const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker)); + const content = renderActivity(renderUndo(renderBlock(blocking), blocker)); deliver(blocker, content, blockee.inbox); } } diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index cf69e2194d..2960bac8f7 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -91,27 +91,20 @@ type ToJsonSchema<S> = { }; export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> { - const object = {}; - for (const [k, v] of Object.entries(schema)) { - nestedProperty.set(object, k, null); - } + const jsonSchema = { + type: 'object', + properties: {} as Record<string, unknown>, + required: [], + }; - function f(obj: Record<string, null | Record<string, unknown>>) { - const jsonSchema = { - type: 'object', - properties: {} as Record<string, unknown>, - required: [], + for (const k in schema) { + jsonSchema.properties[k] = { + type: 'array', + items: { type: 'number' }, }; - for (const [k, v] of Object.entries(obj)) { - jsonSchema.properties[k] = v === null ? { - type: 'array', - items: { type: 'number' }, - } : f(v as Record<string, null | Record<string, unknown>>); - } - return jsonSchema; } - return f(object) as ToJsonSchema<Unflatten<ChartResult<S>>>; + return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>; } /** diff --git a/packages/backend/src/services/chart/entities.ts b/packages/backend/src/services/chart/entities.ts index 13e994cb65..a9eeabd639 100644 --- a/packages/backend/src/services/chart/entities.ts +++ b/packages/backend/src/services/chart/entities.ts @@ -11,6 +11,11 @@ import { entity as PerUserFollowingChart } from './charts/entities/per-user-foll import { entity as PerUserDriveChart } from './charts/entities/per-user-drive.js'; import { entity as ApRequestChart } from './charts/entities/ap-request.js'; +import { entity as TestChart } from './charts/entities/test.js'; +import { entity as TestGroupedChart } from './charts/entities/test-grouped.js'; +import { entity as TestUniqueChart } from './charts/entities/test-unique.js'; +import { entity as TestIntersectionChart } from './charts/entities/test-intersection.js'; + export const entities = [ FederationChart.hour, FederationChart.day, NotesChart.hour, NotesChart.day, @@ -24,4 +29,11 @@ export const entities = [ PerUserFollowingChart.hour, PerUserFollowingChart.day, PerUserDriveChart.hour, PerUserDriveChart.day, ApRequestChart.hour, ApRequestChart.day, + + ...(process.env.NODE_ENV === 'test' ? [ + TestChart.hour, TestChart.day, + TestGroupedChart.hour, TestGroupedChart.day, + TestUniqueChart.hour, TestUniqueChart.day, + TestIntersectionChart.hour, TestIntersectionChart.day, + ] : []), ]; diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts index 9a53db1f38..d53a4235b8 100644 --- a/packages/backend/src/services/create-notification.ts +++ b/packages/backend/src/services/create-notification.ts @@ -1,5 +1,5 @@ import { publishMainStream } from '@/services/stream.js'; -import pushSw from './push-notification.js'; +import { pushNotification } from '@/services/push-notification.js'; import { Notifications, Mutings, UserProfiles, Users } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { User } from '@/models/entities/user.js'; @@ -52,8 +52,8 @@ export async function createNotification( //#endregion publishMainStream(notifieeId, 'unreadNotification', packed); + pushNotification(notifieeId, 'notification', packed); - pushSw(notifieeId, 'notification', packed); if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! })); }, 2000); diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts index 549b11c9fe..cfbcb60ddf 100644 --- a/packages/backend/src/services/drive/add-file.ts +++ b/packages/backend/src/services/drive/add-file.ts @@ -7,7 +7,7 @@ import { deleteFile } from './delete-file.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { GenerateVideoThumbnail } from './generate-video-thumbnail.js'; import { driveLogger } from './logger.js'; -import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor.js'; +import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js'; import { contentDisposition } from '@/misc/content-disposition.js'; import { getFileInfo } from '@/misc/get-file-info.js'; import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js'; @@ -179,6 +179,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool } let img: sharp.Sharp | null = null; + let satisfyWebpublic: boolean; try { img = sharp(path); @@ -192,6 +193,13 @@ export async function generateAlts(path: string, type: string, generateWeb: bool thumbnail: null, }; } + + satisfyWebpublic = !!( + type !== 'image/svg+xml' && type !== 'image/webp' && + !(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) && + metadata.width && metadata.width <= 2048 && + metadata.height && metadata.height <= 2048 + ); } catch (err) { logger.warn(`sharp failed: ${err}`); return { @@ -203,15 +211,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool // #region webpublic let webpublic: IImage | null = null; - if (generateWeb) { + if (generateWeb && !satisfyWebpublic) { logger.info(`creating web image`); try { - if (['image/jpeg'].includes(type)) { + if (['image/jpeg', 'image/webp'].includes(type)) { webpublic = await convertSharpToJpeg(img, 2048, 2048); - } else if (['image/webp'].includes(type)) { - webpublic = await convertSharpToWebp(img, 2048, 2048); - } else if (['image/png', 'image/svg+xml'].includes(type)) { + } else if (['image/png'].includes(type)) { + webpublic = await convertSharpToPng(img, 2048, 2048); + } else if (['image/svg+xml'].includes(type)) { webpublic = await convertSharpToPng(img, 2048, 2048); } else { logger.debug(`web image not created (not an required image)`); @@ -220,7 +228,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool logger.warn(`web image not created (an error occured)`, err as Error); } } else { - logger.info(`web image not created (from remote)`); + if (satisfyWebpublic) logger.info(`web image not created (original satisfies webpublic)`); + else logger.info(`web image not created (from remote)`); } // #endregion webpublic @@ -228,10 +237,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool let thumbnail: IImage | null = null; try { - if (['image/jpeg', 'image/webp'].includes(type)) { - thumbnail = await convertSharpToJpeg(img, 498, 280); - } else if (['image/png', 'image/svg+xml'].includes(type)) { - thumbnail = await convertSharpToPngOrJpeg(img, 498, 280); + if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) { + thumbnail = await convertSharpToWebp(img, 498, 280); } else { logger.debug(`thumbnail not created (not an required file)`); } diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts index 04a7a83346..ca12ab8d3d 100644 --- a/packages/backend/src/services/drive/generate-video-thumbnail.ts +++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts @@ -1,37 +1,31 @@ import * as fs from 'node:fs'; -import * as tmp from 'tmp'; +import * as path from 'node:path'; +import { createTemp } from '@/misc/create-temp.js'; import { IImage, convertToJpeg } from './image-processor.js'; -import * as FFmpeg from 'fluent-ffmpeg'; +import FFmpeg from 'fluent-ffmpeg'; -export async function GenerateVideoThumbnail(path: string): Promise<IImage> { - const [outDir, cleanup] = await new Promise<[string, any]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); - }); +export async function GenerateVideoThumbnail(source: string): Promise<IImage> { + const [file, cleanup] = await createTemp(); + const parsed = path.parse(file); - await new Promise((res, rej) => { - FFmpeg({ - source: path, - }) - .on('end', res) - .on('error', rej) - .screenshot({ - folder: outDir, - filename: 'output.png', - count: 1, - timestamps: ['5%'], + try { + await new Promise((res, rej) => { + FFmpeg({ + source, + }) + .on('end', res) + .on('error', rej) + .screenshot({ + folder: parsed.dir, + filename: parsed.base, + count: 1, + timestamps: ['5%'], + }); }); - }); - - const outPath = `${outDir}/output.png`; - - const thumbnail = await convertToJpeg(outPath, 498, 280); - - // cleanup - await fs.promises.unlink(outPath); - cleanup(); - return thumbnail; + // JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる) + return await convertToJpeg(498, 280); + } finally { + cleanup(); + } } diff --git a/packages/backend/src/services/drive/image-processor.ts b/packages/backend/src/services/drive/image-processor.ts index 146dcfb6ca..2c564ea595 100644 --- a/packages/backend/src/services/drive/image-processor.ts +++ b/packages/backend/src/services/drive/image-processor.ts @@ -38,11 +38,11 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig * Convert to WebP * with resize, remove metadata, resolve orientation, stop animation */ -export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToWebp(await sharp(path), width, height); +export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise<IImage> { + return convertSharpToWebp(await sharp(path), width, height, quality); } -export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { +export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise<IImage> { const data = await sharp .resize(width, height, { fit: 'inside', @@ -50,7 +50,7 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig }) .rotate() .webp({ - quality: 85, + quality, }) .toBuffer(); @@ -85,23 +85,3 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh type: 'image/png', }; } - -/** - * Convert to PNG or JPEG - * with resize, remove metadata, resolve orientation, stop animation - */ -export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise<IImage> { - return convertSharpToPngOrJpeg(await sharp(path), width, height); -} - -export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> { - const stats = await sharp.stats(); - const metadata = await sharp.metadata(); - - // 不透明で300x300pxの範囲を超えていればJPEG - if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) { - return await convertSharpToJpeg(sharp, width, height); - } else { - return await convertSharpToPng(sharp, width, height); - } -} diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts index 79b1b8c2e1..001fc49ee4 100644 --- a/packages/backend/src/services/drive/upload-from-url.ts +++ b/packages/backend/src/services/drive/upload-from-url.ts @@ -45,29 +45,20 @@ export async function uploadFromUrl({ // Create temp file const [path, cleanup] = await createTemp(); - // write content at URL to temp file - await downloadUrl(url, path); - - let driveFile: DriveFile; - let error; - try { - driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); + // write content at URL to temp file + await downloadUrl(url, path); + + const driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive }); logger.succ(`Got: ${driveFile.id}`); + return driveFile!; } catch (e) { - error = e; logger.error(`Failed to create drive file: ${e}`, { url: url, e: e, }); - } - - // clean-up - cleanup(); - - if (error) { - throw error; - } else { - return driveFile!; + throw e; + } finally { + cleanup(); } } diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index d5294c5fe8..029c388dc2 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -1,5 +1,6 @@ import { DOMWindow, JSDOM } from 'jsdom'; import fetch from 'node-fetch'; +import tinycolor from 'tinycolor2'; import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch.js'; import { Instance } from '@/models/entities/instance.js'; import { Instances } from '@/models/index.js'; @@ -208,16 +209,11 @@ async function fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | nul } async function getThemeColor(doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> { - if (doc) { - const themeColor = doc.querySelector('meta[name="theme-color"]')?.getAttribute('content'); + const themeColor = doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') || manifest?.theme_color; - if (themeColor) { - return themeColor; - } - } - - if (manifest) { - return manifest.theme_color; + if (themeColor) { + const color = new tinycolor(themeColor); + if (color.isValid()) return color.toHexString(); } return null; diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts index 7491c44f83..72c24676bb 100644 --- a/packages/backend/src/services/following/create.ts +++ b/packages/backend/src/services/following/create.ts @@ -67,8 +67,10 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[ if (alreadyFollowed) return; //#region Increment counts - Users.increment({ id: follower.id }, 'followingCount', 1); - Users.increment({ id: followee.id }, 'followersCount', 1); + await Promise.all([ + Users.increment({ id: follower.id }, 'followingCount', 1), + Users.increment({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts index 241f9606e5..91b5a3d61d 100644 --- a/packages/backend/src/services/following/delete.ts +++ b/packages/backend/src/services/following/delete.ts @@ -58,12 +58,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur } export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) { - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); + //#region Decrement following / followers counts + await Promise.all([ + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts index e5cd5a30d2..e6b3204922 100644 --- a/packages/backend/src/services/messages/create.ts +++ b/packages/backend/src/services/messages/create.ts @@ -5,7 +5,7 @@ import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/i import { genId } from '@/misc/gen-id.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '@/services/stream.js'; -import pushNotification from '../push-notification.js'; +import { pushNotification } from '@/services/push-notification.js'; import { Not } from 'typeorm'; import { Note } from '@/models/entities/note.js'; import renderNote from '@/remote/activitypub/renderer/note.js'; diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts index f14bc2059b..e2bf9d5b59 100644 --- a/packages/backend/src/services/note/create.ts +++ b/packages/backend/src/services/note/create.ts @@ -187,6 +187,8 @@ export default async (user: { id: User['id']; username: User['username']; host: if (data.text) { data.text = data.text.trim(); + } else { + data.text = null; } let tags = data.apHashtags; @@ -310,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host: endedPollNotificationQueue.add({ noteId: note.id, }, { - delay + delay, + removeOnComplete: true, }); } diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts index ffd609dd84..4963200161 100644 --- a/packages/backend/src/services/note/delete.ts +++ b/packages/backend/src/services/note/delete.ts @@ -1,3 +1,4 @@ +import { Brackets, In } from 'typeorm'; import { publishNoteStream } from '@/services/stream.js'; import renderDelete from '@/remote/activitypub/renderer/delete.js'; import renderAnnounce from '@/remote/activitypub/renderer/announce.js'; @@ -5,15 +6,14 @@ import renderUndo from '@/remote/activitypub/renderer/undo.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderTombstone from '@/remote/activitypub/renderer/tombstone.js'; import config from '@/config/index.js'; -import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js'; import { Notes, Users, Instances } from '@/models/index.js'; import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js'; import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js'; import { countSameRenotes } from '@/misc/count-same-renotes.js'; +import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js'; import { deliverToRelays } from '../relay.js'; -import { Brackets, In } from 'typeorm'; /** * 投稿を削除します。 @@ -40,7 +40,7 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us //#region ローカルの投稿なら削除アクティビティを配送 if (Users.isLocalUser(user) && !note.localOnly) { - let renote: Note | null; + let renote: Note | null = null; // if deletd note is renote if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) { @@ -113,7 +113,7 @@ async function getMentionedRemoteUsers(note: Note) { const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); if (uris.length > 0) { where.push( - { uri: In(uris) } + { uri: In(uris) }, ); } diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts index 5a0948bca9..83d302826a 100644 --- a/packages/backend/src/services/note/reaction/create.ts +++ b/packages/backend/src/services/note/reaction/create.ts @@ -27,6 +27,11 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, } } + // check visibility + if (!await Notes.isVisibleForMe(note, user.id)) { + throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.'); + } + // TODO: cache reaction = await toDbReaction(reaction, user.host); diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts index 41122c92e8..5c3bafbb34 100644 --- a/packages/backend/src/services/push-notification.ts +++ b/packages/backend/src/services/push-notification.ts @@ -5,8 +5,15 @@ import { fetchMeta } from '@/misc/fetch-meta.js'; import { Packed } from '@/misc/schema.js'; import { getNoteSummary } from '@/misc/get-note-summary.js'; -type notificationType = 'notification' | 'unreadMessagingMessage'; -type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>; +// Defined also packages/sw/types.ts#L14-L21 +type pushNotificationsTypes = { + 'notification': Packed<'Notification'>; + 'unreadMessagingMessage': Packed<'MessagingMessage'>; + 'readNotifications': { notificationIds: string[] }; + 'readAllNotifications': undefined; + 'readAllMessagingMessages': undefined; + 'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string }; +}; // プッシュメッセージサーバーには文字数制限があるため、内容を削減します function truncateNotification(notification: Packed<'Notification'>): any { @@ -17,12 +24,11 @@ function truncateNotification(notification: Packed<'Notification'>): any { ...notification.note, // textをgetNoteSummaryしたものに置き換える text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note), - ...{ - cw: undefined, - reply: undefined, - renote: undefined, - user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる - } + + cw: undefined, + reply: undefined, + renote: undefined, + user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる } }; } @@ -30,7 +36,7 @@ function truncateNotification(notification: Packed<'Notification'>): any { return notification; } -export default async function(userId: string, type: notificationType, body: notificationBody) { +export async function pushNotification<T extends keyof pushNotificationsTypes>(userId: string, type: T, body: pushNotificationsTypes[T]) { const meta = await fetchMeta(); if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts index 1ab45588da..6bc4304436 100644 --- a/packages/backend/src/services/relay.ts +++ b/packages/backend/src/services/relay.ts @@ -1,4 +1,4 @@ -import { createSystemUser } from './create-system-user.js'; +import { IsNull } from 'typeorm'; import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js'; import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js'; import renderUndo from '@/remote/activitypub/renderer/undo.js'; @@ -8,7 +8,7 @@ import { Users, Relays } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { Cache } from '@/misc/cache.js'; import { Relay } from '@/models/entities/relay.js'; -import { IsNull } from 'typeorm'; +import { createSystemUser } from './create-system-user.js'; const ACTOR_USERNAME = 'relay.actor' as const; @@ -88,6 +88,8 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act })); if (relays.length === 0) return; + // TODO + //const copy = structuredClone(activity); const copy = JSON.parse(JSON.stringify(activity)); if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public']; diff --git a/packages/backend/test/.eslintrc b/packages/backend/test/.eslintrc deleted file mode 100644 index cea1b11388..0000000000 --- a/packages/backend/test/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "node": true, - "mocha": true, - "commonjs": true - } -} diff --git a/packages/backend/test/.eslintrc.cjs b/packages/backend/test/.eslintrc.cjs new file mode 100644 index 0000000000..d83dc37d2f --- /dev/null +++ b/packages/backend/test/.eslintrc.cjs @@ -0,0 +1,11 @@ +module.exports = { + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'], + }, + extends: ['../.eslintrc.cjs'], + env: { + node: true, + mocha: true, + }, +}; diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts index 70f35cafd8..f4ae27e5ec 100644 --- a/packages/backend/test/activitypub.ts +++ b/packages/backend/test/activitypub.ts @@ -1,12 +1,14 @@ process.env.NODE_ENV = 'test'; -import rndstr from 'rndstr'; import * as assert from 'assert'; +import rndstr from 'rndstr'; +import { initDb } from '../src/db/postgre.js'; import { initTestDb } from './utils.js'; describe('ActivityPub', () => { before(async () => { - await initTestDb(); + //await initTestDb(); + await initDb(); }); describe('Parse minimum object', () => { @@ -57,8 +59,8 @@ describe('ActivityPub', () => { const note = await createNote(post.id, resolver, true); assert.deepStrictEqual(note?.uri, post.id); - assert.deepStrictEqual(note?.visibility, 'public'); - assert.deepStrictEqual(note?.text, post.content); + assert.deepStrictEqual(note.visibility, 'public'); + assert.deepStrictEqual(note.text, post.content); }); }); diff --git a/packages/backend/test/ap-request.ts b/packages/backend/test/ap-request.ts index 48f4fceb51..da95c421f3 100644 --- a/packages/backend/test/ap-request.ts +++ b/packages/backend/test/ap-request.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; +import httpSignature from 'http-signature'; import { genRsaKeyPair } from '../src/misc/gen-key-pair.js'; import { createSignedPost, createSignedGet } from '../src/remote/activitypub/ap-request.js'; -import httpSignature from 'http-signature'; export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => { return { @@ -13,7 +13,7 @@ export const buildParsedSignature = (signingString: string, signature: string, a signature: signature, }, signingString: signingString, - algorithm: algorithm?.toUpperCase(), + algorithm: algorithm.toUpperCase(), keyId: 'KeyID', // dummy, not used for verify }; }; @@ -26,7 +26,7 @@ describe('ap-request', () => { const activity = { a: 1 }; const body = JSON.stringify(activity); const headers = { - 'User-Agent': 'UA' + 'User-Agent': 'UA', }; const req = createSignedPost({ key, url, body, additionalHeaders: headers }); @@ -42,7 +42,7 @@ describe('ap-request', () => { const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey }; const url = 'https://example.com/outbox'; const headers = { - 'User-Agent': 'UA' + 'User-Agent': 'UA', }; const req = createSignedGet({ key, url, additionalHeaders: headers }); diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts index d946191be8..b155549f98 100644 --- a/packages/backend/test/api-visibility.ts +++ b/packages/backend/test/api-visibility.ts @@ -61,40 +61,40 @@ describe('API visibility', () => { const show = async (noteId: any, by: any) => { return await request('/notes/show', { - noteId + noteId, }, by); }; before(async () => { //#region prepare // signup - alice = await signup({ username: 'alice' }); + alice = await signup({ username: 'alice' }); follower = await signup({ username: 'follower' }); - other = await signup({ username: 'other' }); - target = await signup({ username: 'target' }); - target2 = await signup({ username: 'target2' }); + other = await signup({ username: 'other' }); + target = await signup({ username: 'target' }); + target2 = await signup({ username: 'target2' }); // follow alice <= follower await request('/following/create', { userId: alice.id }, follower); // normal posts - pub = await post(alice, { text: 'x', visibility: 'public' }); + pub = await post(alice, { text: 'x', visibility: 'public' }); home = await post(alice, { text: 'x', visibility: 'home' }); - fol = await post(alice, { text: 'x', visibility: 'followers' }); - spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] }); + fol = await post(alice, { text: 'x', visibility: 'followers' }); + spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] }); // replies tgt = await post(target, { text: 'y', visibility: 'public' }); - pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' }); + pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' }); homeR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'home' }); - folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' }); - speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' }); + folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' }); + speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' }); // mentions - pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' }); + pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' }); homeM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'home' }); - folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' }); - speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' }); + folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' }); + speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' }); //#endregion }); diff --git a/packages/backend/test/block.ts b/packages/backend/test/block.ts index 103eec991d..b3343813cd 100644 --- a/packages/backend/test/block.ts +++ b/packages/backend/test/block.ts @@ -25,7 +25,7 @@ describe('Block', () => { it('Block作成', async(async () => { const res = await request('/blocking/create', { - userId: bob.id + userId: bob.id, }, alice); assert.strictEqual(res.status, 200); diff --git a/packages/backend/test/chart.ts b/packages/backend/test/chart.ts index c8cea874f0..ac0844679f 100644 --- a/packages/backend/test/chart.ts +++ b/packages/backend/test/chart.ts @@ -2,30 +2,21 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as lolex from '@sinonjs/fake-timers'; -import { async, initTestDb } from './utils.js'; import TestChart from '../src/services/chart/charts/test.js'; import TestGroupedChart from '../src/services/chart/charts/test-grouped.js'; import TestUniqueChart from '../src/services/chart/charts/test-unique.js'; import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js'; -import * as _TestChart from '../src/services/chart/charts/entities/test.js'; -import * as _TestGroupedChart from '../src/services/chart/charts/entities/test-grouped.js'; -import * as _TestUniqueChart from '../src/services/chart/charts/entities/test-unique.js'; -import * as _TestIntersectionChart from '../src/services/chart/charts/entities/test-intersection.js'; +import { initDb } from '../src/db/postgre.js'; describe('Chart', () => { let testChart: TestChart; let testGroupedChart: TestGroupedChart; let testUniqueChart: TestUniqueChart; let testIntersectionChart: TestIntersectionChart; - let clock: lolex.Clock; + let clock: lolex.InstalledClock; - beforeEach(async(async () => { - await initTestDb(false, [ - _TestChart.entity.hour, _TestChart.entity.day, - _TestGroupedChart.entity.hour, _TestGroupedChart.entity.day, - _TestUniqueChart.entity.hour, _TestUniqueChart.entity.day, - _TestIntersectionChart.entity.hour, _TestIntersectionChart.entity.day, - ]); + beforeEach(async () => { + await initDb(true); testChart = new TestChart(); testGroupedChart = new TestGroupedChart(); @@ -33,15 +24,16 @@ describe('Chart', () => { testIntersectionChart = new TestIntersectionChart(); clock = lolex.install({ - now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)) + now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)), + shouldClearNativeTimers: true, }); - })); + }); - afterEach(async(async () => { + afterEach(() => { clock.uninstall(); - })); + }); - it('Can updates', async(async () => { + it('Can updates', async () => { await testChart.increment(); await testChart.save(); @@ -52,7 +44,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -60,12 +52,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can updates (dec)', async(async () => { + it('Can updates (dec)', async () => { await testChart.decrement(); await testChart.save(); @@ -76,7 +68,7 @@ describe('Chart', () => { foo: { dec: [1, 0, 0], inc: [0, 0, 0], - total: [-1, 0, 0] + total: [-1, 0, 0], }, }); @@ -84,12 +76,12 @@ describe('Chart', () => { foo: { dec: [1, 0, 0], inc: [0, 0, 0], - total: [-1, 0, 0] + total: [-1, 0, 0], }, }); - })); + }); - it('Empty chart', async(async () => { + it('Empty chart', async () => { const chartHours = await testChart.getChart('hour', 3, null); const chartDays = await testChart.getChart('day', 3, null); @@ -97,7 +89,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); @@ -105,12 +97,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); - })); + }); - it('Can updates at multiple times at same time', async(async () => { + it('Can updates at multiple times at same time', async () => { await testChart.increment(); await testChart.increment(); await testChart.increment(); @@ -123,7 +115,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [3, 0, 0], - total: [3, 0, 0] + total: [3, 0, 0], }, }); @@ -131,12 +123,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [3, 0, 0], - total: [3, 0, 0] + total: [3, 0, 0], }, }); - })); + }); - it('複数回saveされてもデータの更新は一度だけ', async(async () => { + it('複数回saveされてもデータの更新は一度だけ', async () => { await testChart.increment(); await testChart.save(); await testChart.save(); @@ -149,7 +141,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -157,12 +149,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can updates at different times', async(async () => { + it('Can updates at different times', async () => { await testChart.increment(); await testChart.save(); @@ -178,7 +170,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 1, 0], - total: [2, 1, 0] + total: [2, 1, 0], }, }); @@ -186,14 +178,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); // 仕様上はこうなってほしいけど、実装は難しそうなのでskip /* - it('Can updates at different times without save', async(async () => { + it('Can updates at different times without save', async () => { await testChart.increment(); clock.tick('01:00:00'); @@ -219,10 +211,10 @@ describe('Chart', () => { total: [2, 0, 0] }, }); - })); + }); */ - it('Can padding', async(async () => { + it('Can padding', async () => { await testChart.increment(); await testChart.save(); @@ -238,7 +230,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 1], - total: [2, 1, 1] + total: [2, 1, 1], }, }); @@ -246,13 +238,13 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); // 要求された範囲にログがひとつもない場合でもパディングできる - it('Can padding from past range', async(async () => { + it('Can padding from past range', async () => { await testChart.increment(); await testChart.save(); @@ -265,7 +257,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 1, 1] + total: [1, 1, 1], }, }); @@ -273,14 +265,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); // 要求された範囲の最も古い箇所に位置するログが存在しない場合でもパディングできる // Issue #3190 - it('Can padding from past range 2', async(async () => { + it('Can padding from past range 2', async () => { await testChart.increment(); await testChart.save(); @@ -296,7 +288,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [2, 1, 1] + total: [2, 1, 1], }, }); @@ -304,12 +296,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset', async(async () => { + it('Can specify offset', async () => { await testChart.increment(); await testChart.save(); @@ -325,7 +317,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -333,12 +325,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset (floor time)', async(async () => { + it('Can specify offset (floor time)', async () => { clock.tick('00:30:00'); await testChart.increment(); @@ -356,7 +348,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -364,13 +356,13 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [2, 0, 0], - total: [2, 0, 0] + total: [2, 0, 0], }, }); - })); + }); describe('Grouped', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testGroupedChart.increment('alice'); await testGroupedChart.save(); @@ -383,7 +375,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -391,7 +383,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -399,7 +391,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); @@ -407,14 +399,14 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [0, 0, 0] + total: [0, 0, 0], }, }); - })); + }); }); describe('Unique increment', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('bob'); @@ -430,10 +422,10 @@ describe('Chart', () => { assert.deepStrictEqual(chartDays, { foo: [2, 0, 0], }); - })); + }); describe('Intersection', () => { - it('条件が満たされていない場合はカウントされない', async(async () => { + it('条件が満たされていない場合はカウントされない', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -453,9 +445,9 @@ describe('Chart', () => { b: [1, 0, 0], aAndB: [0, 0, 0], }); - })); + }); - it('条件が満たされている場合にカウントされる', async(async () => { + it('条件が満たされている場合にカウントされる', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -476,12 +468,12 @@ describe('Chart', () => { b: [2, 0, 0], aAndB: [1, 0, 0], }); - })); + }); }); }); describe('Resync', () => { - it('Can resync', async(async () => { + it('Can resync', async () => { testChart.total = 1; await testChart.resync(); @@ -493,7 +485,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); @@ -501,12 +493,12 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 0, 0], - total: [1, 0, 0] + total: [1, 0, 0], }, }); - })); + }); - it('Can resync (2)', async(async () => { + it('Can resync (2)', async () => { await testChart.increment(); await testChart.save(); @@ -523,7 +515,7 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [0, 1, 0], - total: [100, 1, 0] + total: [100, 1, 0], }, }); @@ -531,9 +523,9 @@ describe('Chart', () => { foo: { dec: [0, 0, 0], inc: [1, 0, 0], - total: [100, 0, 0] + total: [100, 0, 0], }, }); - })); + }); }); }); diff --git a/packages/backend/test/extract-mentions.ts b/packages/backend/test/extract-mentions.ts index 9bfbc4192a..85afb098d8 100644 --- a/packages/backend/test/extract-mentions.ts +++ b/packages/backend/test/extract-mentions.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; -import { extractMentions } from '../src/misc/extract-mentions.js'; import { parse } from 'mfm-js'; +import { extractMentions } from '../src/misc/extract-mentions.js'; describe('Extract mentions', () => { it('simple', () => { @@ -10,15 +10,15 @@ describe('Extract mentions', () => { assert.deepStrictEqual(mentions, [{ username: 'foo', acct: '@foo', - host: null + host: null, }, { username: 'bar', acct: '@bar', - host: null + host: null, }, { username: 'baz', acct: '@baz', - host: null + host: null, }]); }); @@ -28,15 +28,15 @@ describe('Extract mentions', () => { assert.deepStrictEqual(mentions, [{ username: 'foo', acct: '@foo', - host: null + host: null, }, { username: 'bar', acct: '@bar', - host: null + host: null, }, { username: 'baz', acct: '@baz', - host: null + host: null, }]); }); }); diff --git a/packages/backend/test/fetch-resource.ts b/packages/backend/test/fetch-resource.ts index 4cb4b42562..ddb0e94b86 100644 --- a/packages/backend/test/fetch-resource.ts +++ b/packages/backend/test/fetch-resource.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js'; import * as openapi from '@redocly/openapi-core'; +import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js'; // Request Accept const ONLY_AP = 'application/activity+json'; @@ -26,7 +26,7 @@ describe('Fetch resource', () => { p = await startServer(); alice = await signup({ username: 'alice' }); alicesPost = await post(alice, { - text: 'test' + text: 'test', }); }); @@ -70,7 +70,7 @@ describe('Fetch resource', () => { const config = await openapi.loadConfig(); const result = await openapi.bundle({ config, - ref: `http://localhost:${port}/api.json` + ref: `http://localhost:${port}/api.json`, }); for (const problem of result.problems) { diff --git a/packages/backend/test/get-file-info.ts b/packages/backend/test/get-file-info.ts index 20061b8708..7ce98db50f 100644 --- a/packages/backend/test/get-file-info.ts +++ b/packages/backend/test/get-file-info.ts @@ -1,10 +1,15 @@ import * as assert from 'assert'; -import { async } from './utils.js'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; import { getFileInfo } from '../src/misc/get-file-info.js'; +import { async } from './utils.js'; + +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); describe('Get file info', () => { it('Empty file', async (async () => { - const path = `${__dirname}/resources/emptyfile`; + const path = `${_dirname}/resources/emptyfile`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -13,7 +18,7 @@ describe('Get file info', () => { md5: 'd41d8cd98f00b204e9800998ecf8427e', type: { mime: 'application/octet-stream', - ext: null + ext: null, }, width: undefined, height: undefined, @@ -22,7 +27,7 @@ describe('Get file info', () => { })); it('Generic JPEG', async (async () => { - const path = `${__dirname}/resources/Lenna.jpg`; + const path = `${_dirname}/resources/Lenna.jpg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -31,7 +36,7 @@ describe('Get file info', () => { md5: '091b3f259662aa31e2ffef4519951168', type: { mime: 'image/jpeg', - ext: 'jpg' + ext: 'jpg', }, width: 512, height: 512, @@ -40,7 +45,7 @@ describe('Get file info', () => { })); it('Generic APNG', async (async () => { - const path = `${__dirname}/resources/anime.png`; + const path = `${_dirname}/resources/anime.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -49,7 +54,7 @@ describe('Get file info', () => { md5: '08189c607bea3b952704676bb3c979e0', type: { mime: 'image/apng', - ext: 'apng' + ext: 'apng', }, width: 256, height: 256, @@ -58,7 +63,7 @@ describe('Get file info', () => { })); it('Generic AGIF', async (async () => { - const path = `${__dirname}/resources/anime.gif`; + const path = `${_dirname}/resources/anime.gif`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -67,7 +72,7 @@ describe('Get file info', () => { md5: '32c47a11555675d9267aee1a86571e7e', type: { mime: 'image/gif', - ext: 'gif' + ext: 'gif', }, width: 256, height: 256, @@ -76,7 +81,7 @@ describe('Get file info', () => { })); it('PNG with alpha', async (async () => { - const path = `${__dirname}/resources/with-alpha.png`; + const path = `${_dirname}/resources/with-alpha.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -85,7 +90,7 @@ describe('Get file info', () => { md5: 'f73535c3e1e27508885b69b10cf6e991', type: { mime: 'image/png', - ext: 'png' + ext: 'png', }, width: 256, height: 256, @@ -94,7 +99,7 @@ describe('Get file info', () => { })); it('Generic SVG', async (async () => { - const path = `${__dirname}/resources/image.svg`; + const path = `${_dirname}/resources/image.svg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -103,7 +108,7 @@ describe('Get file info', () => { md5: 'b6f52b4b021e7b92cdd04509c7267965', type: { mime: 'image/svg+xml', - ext: 'svg' + ext: 'svg', }, width: 256, height: 256, @@ -113,7 +118,7 @@ describe('Get file info', () => { it('SVG with XML definition', async (async () => { // https://github.com/misskey-dev/misskey/issues/4413 - const path = `${__dirname}/resources/with-xml-def.svg`; + const path = `${_dirname}/resources/with-xml-def.svg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -122,7 +127,7 @@ describe('Get file info', () => { md5: '4b7a346cde9ccbeb267e812567e33397', type: { mime: 'image/svg+xml', - ext: 'svg' + ext: 'svg', }, width: 256, height: 256, @@ -131,7 +136,7 @@ describe('Get file info', () => { })); it('Dimension limit', async (async () => { - const path = `${__dirname}/resources/25000x25000.png`; + const path = `${_dirname}/resources/25000x25000.png`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -140,7 +145,7 @@ describe('Get file info', () => { md5: '268c5dde99e17cf8fe09f1ab3f97df56', type: { mime: 'application/octet-stream', // do not treat as image - ext: null + ext: null, }, width: 25000, height: 25000, @@ -149,7 +154,7 @@ describe('Get file info', () => { })); it('Rotate JPEG', async (async () => { - const path = `${__dirname}/resources/rotate.jpg`; + const path = `${_dirname}/resources/rotate.jpg`; const info = await getFileInfo(path) as any; delete info.warnings; delete info.blurhash; @@ -158,7 +163,7 @@ describe('Get file info', () => { md5: '68d5b2d8d1d1acbbce99203e3ec3857e', type: { mime: 'image/jpeg', - ext: 'jpg' + ext: 'jpg', }, width: 512, height: 256, diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js index 016f32f1a8..6b21587e32 100644 --- a/packages/backend/test/loader.js +++ b/packages/backend/test/loader.js @@ -1,37 +1,34 @@ -import path from 'path' -import typescript from 'typescript' -import { createMatchPath } from 'tsconfig-paths' -import { resolve as BaseResolve, getFormat, transformSource } from 'ts-node/esm' +/** + * ts-node/esmローダーに投げる前にpath mappingを解決する + * 参考 + * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115 + * - https://nodejs.org/api/esm.html#loaders + * ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる + */ -const { readConfigFile, parseJsonConfigFileContent, sys } = typescript +import { resolve as resolveTs, load } from 'ts-node/esm'; +import { loadConfig, createMatchPath } from 'tsconfig-paths'; +import { pathToFileURL } from 'url'; -const __dirname = path.dirname(new URL(import.meta.url).pathname) +const tsconfig = loadConfig(); +const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths); -const configFile = readConfigFile('./test/tsconfig.json', sys.readFile) -if (typeof configFile.error !== 'undefined') { - throw new Error(`Failed to load tsconfig: ${configFile.error}`) +export function resolve(specifier, ctx, defaultResolve) { + let resolvedSpecifier; + if (specifier.endsWith('.js')) { + // maybe transpiled + const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length); + const matchedSpecifier = matchPath(specifierWithoutExtension); + if (matchedSpecifier) { + resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href; + } + } else { + const matchedSpecifier = matchPath(specifier); + if (matchedSpecifier) { + resolvedSpecifier = pathToFileURL(matchedSpecifier).href; + } + } + return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve); } -const { options } = parseJsonConfigFileContent( - configFile.config, - { - fileExists: sys.fileExists, - readFile: sys.readFile, - readDirectory: sys.readDirectory, - useCaseSensitiveFileNames: true, - }, - __dirname -) - -export { getFormat, transformSource } // こいつらはそのまま使ってほしいので re-export する - -const matchPath = createMatchPath(options.baseUrl, options.paths) - -export async function resolve(specifier, context, defaultResolve) { - const matchedSpecifier = matchPath(specifier.replace('.js', '.ts')) - return BaseResolve( // ts-node/esm の resolve に tsconfig-paths で解決したパスを渡す - matchedSpecifier ? `${matchedSpecifier}.ts` : specifier, - context, - defaultResolve - ) -} +export { load }; diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts index 5a46daf49f..ba89ac329a 100644 --- a/packages/backend/test/misc/mock-resolver.ts +++ b/packages/backend/test/misc/mock-resolver.ts @@ -11,7 +11,7 @@ export class MockResolver extends Resolver { public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') { this._rs.set(uri, { type, - content: typeof content === 'string' ? content : JSON.stringify(content) + content: typeof content === 'string' ? content : JSON.stringify(content), }); } @@ -22,9 +22,9 @@ export class MockResolver extends Resolver { if (!r) { throw { - name: `StatusError`, + name: 'StatusError', statusCode: 404, - message: `Not registed for mock` + message: 'Not registed for mock', }; } diff --git a/packages/backend/test/mute.ts b/packages/backend/test/mute.ts index 288e8a8055..2be70f2b65 100644 --- a/packages/backend/test/mute.ts +++ b/packages/backend/test/mute.ts @@ -25,7 +25,7 @@ describe('Mute', () => { it('ミュート作成', async(async () => { const res = await request('/mute/create', { - userId: carol.id + userId: carol.id, }, alice); assert.strictEqual(res.status, 204); @@ -117,7 +117,7 @@ describe('Mute', () => { const aliceNote = await post(alice); const carolNote = await post(carol); const bobNote = await post(bob, { - renoteId: carolNote.id + renoteId: carolNote.id, }); const res = await request('/notes/local-timeline', {}, alice); diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts index 942b2709df..1183e9e4f1 100644 --- a/packages/backend/test/note.ts +++ b/packages/backend/test/note.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js'; import { Note } from '../src/models/entities/note.js'; +import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js'; describe('Note', () => { let p: childProcess.ChildProcess; @@ -26,7 +26,7 @@ describe('Note', () => { it('投稿できる', async(async () => { const post = { - text: 'test' + text: 'test', }; const res = await request('/notes/create', post, alice); @@ -40,7 +40,7 @@ describe('Note', () => { const file = await uploadFile(alice); const res = await request('/notes/create', { - fileIds: [file.id] + fileIds: [file.id], }, alice); assert.strictEqual(res.status, 200); @@ -53,7 +53,7 @@ describe('Note', () => { const res = await request('/notes/create', { text: 'test', - fileIds: [file.id] + fileIds: [file.id], }, alice); assert.strictEqual(res.status, 200); @@ -64,7 +64,7 @@ describe('Note', () => { it('存在しないファイルは無視', async(async () => { const res = await request('/notes/create', { text: 'test', - fileIds: ['000000000000000000000000'] + fileIds: ['000000000000000000000000'], }, alice); assert.strictEqual(res.status, 200); @@ -74,19 +74,19 @@ describe('Note', () => { it('不正なファイルIDで怒られる', async(async () => { const res = await request('/notes/create', { - fileIds: ['kyoppie'] + fileIds: ['kyoppie'], }, alice); assert.strictEqual(res.status, 400); })); it('返信できる', async(async () => { const bobPost = await post(bob, { - text: 'foo' + text: 'foo', }); const alicePost = { text: 'bar', - replyId: bobPost.id + replyId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -100,11 +100,11 @@ describe('Note', () => { it('renoteできる', async(async () => { const bobPost = await post(bob, { - text: 'test' + text: 'test', }); const alicePost = { - renoteId: bobPost.id + renoteId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -117,12 +117,12 @@ describe('Note', () => { it('引用renoteできる', async(async () => { const bobPost = await post(bob, { - text: 'test' + text: 'test', }); const alicePost = { text: 'test', - renoteId: bobPost.id + renoteId: bobPost.id, }; const res = await request('/notes/create', alicePost, alice); @@ -136,7 +136,7 @@ describe('Note', () => { it('文字数ぎりぎりで怒られない', async(async () => { const post = { - text: '!'.repeat(500) + text: '!'.repeat(500), }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 200); @@ -144,7 +144,7 @@ describe('Note', () => { it('文字数オーバーで怒られる', async(async () => { const post = { - text: '!'.repeat(501) + text: '!'.repeat(501), }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -153,7 +153,7 @@ describe('Note', () => { it('存在しないリプライ先で怒られる', async(async () => { const post = { text: 'test', - replyId: '000000000000000000000000' + replyId: '000000000000000000000000', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -161,7 +161,7 @@ describe('Note', () => { it('存在しないrenote対象で怒られる', async(async () => { const post = { - renoteId: '000000000000000000000000' + renoteId: '000000000000000000000000', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -170,7 +170,7 @@ describe('Note', () => { it('不正なリプライ先IDで怒られる', async(async () => { const post = { text: 'test', - replyId: 'foo' + replyId: 'foo', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -178,7 +178,7 @@ describe('Note', () => { it('不正なrenote対象IDで怒られる', async(async () => { const post = { - renoteId: 'foo' + renoteId: 'foo', }; const res = await request('/notes/create', post, alice); assert.strictEqual(res.status, 400); @@ -186,7 +186,7 @@ describe('Note', () => { it('存在しないユーザーにメンションできる', async(async () => { const post = { - text: '@ghost yo' + text: '@ghost yo', }; const res = await request('/notes/create', post, alice); @@ -198,7 +198,7 @@ describe('Note', () => { it('同じユーザーに複数メンションしても内部的にまとめられる', async(async () => { const post = { - text: '@bob @bob @bob yo' + text: '@bob @bob @bob yo', }; const res = await request('/notes/create', post, alice); @@ -216,8 +216,8 @@ describe('Note', () => { const res = await request('/notes/create', { text: 'test', poll: { - choices: ['foo', 'bar'] - } + choices: ['foo', 'bar'], + }, }, alice); assert.strictEqual(res.status, 200); @@ -227,7 +227,7 @@ describe('Note', () => { it('投票の選択肢が無くて怒られる', async(async () => { const res = await request('/notes/create', { - poll: {} + poll: {}, }, alice); assert.strictEqual(res.status, 400); })); @@ -235,8 +235,8 @@ describe('Note', () => { it('投票の選択肢が無くて怒られる (空の配列)', async(async () => { const res = await request('/notes/create', { poll: { - choices: [] - } + choices: [], + }, }, alice); assert.strictEqual(res.status, 400); })); @@ -244,8 +244,8 @@ describe('Note', () => { it('投票の選択肢が1つで怒られる', async(async () => { const res = await request('/notes/create', { poll: { - choices: ['Strawberry Pasta'] - } + choices: ['Strawberry Pasta'], + }, }, alice); assert.strictEqual(res.status, 400); })); @@ -254,13 +254,13 @@ describe('Note', () => { const { body } = await request('/notes/create', { text: 'test', poll: { - choices: ['sakura', 'izumi', 'ako'] - } + choices: ['sakura', 'izumi', 'ako'], + }, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); assert.strictEqual(res.status, 204); @@ -270,18 +270,18 @@ describe('Note', () => { const { body } = await request('/notes/create', { text: 'test', poll: { - choices: ['sakura', 'izumi', 'ako'] - } + choices: ['sakura', 'izumi', 'ako'], + }, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 0 + choice: 0, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 2 + choice: 2, }, alice); assert.strictEqual(res.status, 400); @@ -292,23 +292,23 @@ describe('Note', () => { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], - multiple: true - } + multiple: true, + }, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 0 + choice: 0, }, alice); await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 2 + choice: 2, }, alice); assert.strictEqual(res.status, 204); @@ -319,15 +319,15 @@ describe('Note', () => { text: 'test', poll: { choices: ['sakura', 'izumi', 'ako'], - expiredAfter: 1 - } + expiredAfter: 1, + }, }, alice); await new Promise(x => setTimeout(x, 2)); const res = await request('/notes/polls/vote', { noteId: body.createdNote.id, - choice: 1 + choice: 1, }, alice); assert.strictEqual(res.status, 400); @@ -341,11 +341,11 @@ describe('Note', () => { }, alice); const replyOneRes = await request('/notes/create', { text: 'reply one', - replyId: mainNoteRes.body.createdNote.id + replyId: mainNoteRes.body.createdNote.id, }, alice); const replyTwoRes = await request('/notes/create', { text: 'reply two', - replyId: mainNoteRes.body.createdNote.id + replyId: mainNoteRes.body.createdNote.id, }, alice); const deleteOneRes = await request('/notes/delete', { @@ -353,7 +353,7 @@ describe('Note', () => { }, alice); assert.strictEqual(deleteOneRes.status, 204); - let mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + let mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id }); assert.strictEqual(mainNote.repliesCount, 1); const deleteTwoRes = await request('/notes/delete', { @@ -361,7 +361,7 @@ describe('Note', () => { }, alice); assert.strictEqual(deleteTwoRes.status, 204); - mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id}); + mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id }); assert.strictEqual(mainNote.repliesCount, 0); })); }); diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.ts index 84e43d26c2..df102c8dfe 100644 --- a/packages/backend/test/prelude/url.ts +++ b/packages/backend/test/prelude/url.ts @@ -6,7 +6,7 @@ describe('url', () => { const s = query({ foo: 'ふぅ', bar: 'b a r', - baz: undefined + baz: undefined, }); assert.deepStrictEqual(s, 'foo=%E3%81%B5%E3%81%85&bar=b%20a%20r'); }); diff --git a/packages/backend/test/streaming.ts b/packages/backend/test/streaming.ts index 8d22b6d3d3..f080b71dd4 100644 --- a/packages/backend/test/streaming.ts +++ b/packages/backend/test/streaming.ts @@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js'; import { Following } from '../src/models/entities/following.js'; +import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js'; describe('Streaming', () => { let p: childProcess.ChildProcess; @@ -30,7 +30,7 @@ describe('Streaming', () => { followerSharedInbox: null, followeeHost: followee.host, followeeInbox: null, - followeeSharedInbox: null + followeeSharedInbox: null, }); }; @@ -47,7 +47,7 @@ describe('Streaming', () => { }); post(alice, { - text: 'foo @bob bar' + text: 'foo @bob bar', }); })); @@ -55,7 +55,7 @@ describe('Streaming', () => { const alice = await signup({ username: 'alice' }); const bob = await signup({ username: 'bob' }); const bobNote = await post(bob, { - text: 'foo' + text: 'foo', }); const ws = await connectStream(bob, 'main', ({ type, body }) => { @@ -67,14 +67,14 @@ describe('Streaming', () => { }); post(alice, { - renoteId: bobNote.id + renoteId: bobNote.id, }); })); describe('Home Timeline', () => { it('自分の投稿が流れる', () => new Promise(async done => { const post = { - text: 'foo' + text: 'foo', }; const me = await signup(); @@ -96,7 +96,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { @@ -108,7 +108,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -125,7 +125,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -141,7 +141,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => { @@ -157,7 +157,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -168,7 +168,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -183,7 +183,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [carol.id] + visibleUserIds: [carol.id], }); setTimeout(() => { @@ -207,7 +207,7 @@ describe('Streaming', () => { }); post(me, { - text: 'foo' + text: 'foo', }); })); @@ -224,7 +224,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -241,7 +241,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -257,7 +257,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -269,7 +269,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -294,7 +294,7 @@ describe('Streaming', () => { // ホーム指定 post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -310,7 +310,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -325,7 +325,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); setTimeout(() => { @@ -350,7 +350,7 @@ describe('Streaming', () => { // フォロワー宛て投稿 post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -374,7 +374,7 @@ describe('Streaming', () => { }); post(me, { - text: 'foo' + text: 'foo', }); })); @@ -391,7 +391,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -411,7 +411,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -428,7 +428,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -444,7 +444,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => { @@ -460,7 +460,7 @@ describe('Streaming', () => { post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -470,7 +470,7 @@ describe('Streaming', () => { // Alice が Bob をフォロー await request('/following/create', { - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => { @@ -485,7 +485,7 @@ describe('Streaming', () => { // ホーム投稿 post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); })); @@ -504,7 +504,7 @@ describe('Streaming', () => { // ホーム投稿 post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -529,7 +529,7 @@ describe('Streaming', () => { // フォロワー宛て投稿 post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -554,7 +554,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -571,7 +571,7 @@ describe('Streaming', () => { }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -590,7 +590,7 @@ describe('Streaming', () => { // ホーム投稿 post(bob, { text: 'foo', - visibility: 'home' + visibility: 'home', }); setTimeout(() => { @@ -608,13 +608,13 @@ describe('Streaming', () => { // リスト作成 const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice が Bob をリスイン await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'userList', ({ type, body }) => { @@ -624,11 +624,11 @@ describe('Streaming', () => { done(); } }, { - listId: list.id + listId: list.id, }); post(bob, { - text: 'foo' + text: 'foo', }); })); @@ -638,7 +638,7 @@ describe('Streaming', () => { // リスト作成 const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); let fired = false; @@ -648,11 +648,11 @@ describe('Streaming', () => { fired = true; } }, { - listId: list.id + listId: list.id, }); post(bob, { - text: 'foo' + text: 'foo', }); setTimeout(() => { @@ -669,13 +669,13 @@ describe('Streaming', () => { // リスト作成 const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice が Bob をリスイン await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); const ws = await connectStream(alice, 'userList', ({ type, body }) => { @@ -686,14 +686,14 @@ describe('Streaming', () => { done(); } }, { - listId: list.id + listId: list.id, }); // Bob が Alice 宛てのダイレクト投稿 post(bob, { text: 'foo', visibility: 'specified', - visibleUserIds: [alice.id] + visibleUserIds: [alice.id], }); })); @@ -704,13 +704,13 @@ describe('Streaming', () => { // リスト作成 const list = await request('/users/lists/create', { - name: 'my list' + name: 'my list', }, alice).then(x => x.body); // Alice が Bob をリスイン await request('/users/lists/push', { listId: list.id, - userId: bob.id + userId: bob.id, }, alice); let fired = false; @@ -720,13 +720,13 @@ describe('Streaming', () => { fired = true; } }, { - listId: list.id + listId: list.id, }); // フォロワー宛て投稿 post(bob, { text: 'foo', - visibility: 'followers' + visibility: 'followers', }); setTimeout(() => { @@ -749,12 +749,12 @@ describe('Streaming', () => { } }, { q: [ - ['foo'] - ] + ['foo'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); })); @@ -773,20 +773,20 @@ describe('Streaming', () => { } }, { q: [ - ['foo', 'bar'] - ] + ['foo', 'bar'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); setTimeout(() => { @@ -816,24 +816,24 @@ describe('Streaming', () => { }, { q: [ ['foo'], - ['bar'] - ] + ['bar'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); post(me, { - text: '#piyo' + text: '#piyo', }); setTimeout(() => { @@ -866,28 +866,28 @@ describe('Streaming', () => { }, { q: [ ['foo', 'bar'], - ['piyo'] - ] + ['piyo'], + ], }); post(me, { - text: '#foo' + text: '#foo', }); post(me, { - text: '#bar' + text: '#bar', }); post(me, { - text: '#foo #bar' + text: '#foo #bar', }); post(me, { - text: '#piyo' + text: '#piyo', }); post(me, { - text: '#waaa' + text: '#waaa', }); setTimeout(() => { diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.ts index 25ffe04756..5b7933da67 100644 --- a/packages/backend/test/user-notes.ts +++ b/packages/backend/test/user-notes.ts @@ -2,8 +2,13 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; import { async, signup, request, post, uploadFile, startServer, shutdownServer } from './utils.js'; +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + describe('users/notes', () => { let p: childProcess.ChildProcess; @@ -15,16 +20,16 @@ describe('users/notes', () => { before(async () => { p = await startServer(); alice = await signup({ username: 'alice' }); - const jpg = await uploadFile(alice, __dirname + '/resources/Lenna.jpg'); - const png = await uploadFile(alice, __dirname + '/resources/Lenna.png'); + const jpg = await uploadFile(alice, _dirname + '/resources/Lenna.jpg'); + const png = await uploadFile(alice, _dirname + '/resources/Lenna.png'); jpgNote = await post(alice, { - fileIds: [jpg.id] + fileIds: [jpg.id], }); pngNote = await post(alice, { - fileIds: [png.id] + fileIds: [png.id], }); jpgPngNote = await post(alice, { - fileIds: [jpg.id, png.id] + fileIds: [jpg.id, png.id], }); }); @@ -35,7 +40,7 @@ describe('users/notes', () => { it('ファイルタイプ指定 (jpg)', async(async () => { const res = await request('/users/notes', { userId: alice.id, - fileType: ['image/jpeg'] + fileType: ['image/jpeg'], }, alice); assert.strictEqual(res.status, 200); @@ -48,7 +53,7 @@ describe('users/notes', () => { it('ファイルタイプ指定 (jpg or png)', async(async () => { const res = await request('/users/notes', { userId: alice.id, - fileType: ['image/jpeg', 'image/png'] + fileType: ['image/jpeg', 'image/png'], }, alice); assert.strictEqual(res.status, 200); diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts index 32a030f933..5eb4ed3b01 100644 --- a/packages/backend/test/utils.ts +++ b/packages/backend/test/utils.ts @@ -1,14 +1,20 @@ import * as fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname } from 'node:path'; +import * as childProcess from 'child_process'; +import * as http from 'node:http'; +import { SIGKILL } from 'constants'; import * as WebSocket from 'ws'; import * as misskey from 'misskey-js'; import fetch from 'node-fetch'; import FormData from 'form-data'; -import * as childProcess from 'child_process'; -import * as http from 'http'; +import { DataSource } from 'typeorm'; import loadConfig from '../src/config/load.js'; -import { SIGKILL } from 'constants'; import { entities } from '../src/db/postgre.js'; +const _filename = fileURLToPath(import.meta.url); +const _dirname = dirname(_filename); + const config = loadConfig(); export const port = config.port; @@ -22,29 +28,29 @@ export const async = (fn: Function) => (done: Function) => { export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => { const auth = me ? { - i: me.token + i: me.token, } : {}; const res = await fetch(`http://localhost:${port}/api${endpoint}`, { method: 'POST', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, - body: JSON.stringify(Object.assign(auth, params)) + body: JSON.stringify(Object.assign(auth, params)), }); const status = res.status; const body = res.status !== 204 ? await res.json().catch() : null; return { - body, status + body, status, }; }; export const signup = async (params?: any): Promise<any> => { const q = Object.assign({ username: 'test', - password: 'test' + password: 'test', }, params); const res = await request('/signup', q); @@ -54,7 +60,7 @@ export const signup = async (params?: any): Promise<any> => { export const post = async (user: any, params?: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => { const q = Object.assign({ - text: 'test' + text: 'test', }, params); const res = await request('/notes/create', q, user); @@ -65,26 +71,26 @@ export const post = async (user: any, params?: misskey.Endpoints['notes/create'] export const react = async (user: any, note: any, reaction: string): Promise<any> => { await request('/notes/reactions/create', { noteId: note.id, - reaction: reaction + reaction: reaction, }, user); }; export const uploadFile = (user: any, path?: string): Promise<any> => { - const formData = new FormData(); - formData.append('i', user.token); - formData.append('file', fs.createReadStream(path || __dirname + '/resources/Lenna.png')); + const formData = new FormData(); + formData.append('i', user.token); + formData.append('file', fs.createReadStream(path || _dirname + '/resources/Lenna.png')); - return fetch(`http://localhost:${port}/api/drive/files/create`, { - method: 'post', - body: formData, - timeout: 30 * 1000, - }).then(res => { - if (!res.ok) { - throw `${res.status} ${res.statusText}`; - } else { - return res.json(); - } - }); + return fetch(`http://localhost:${port}/api/drive/files/create`, { + method: 'post', + body: formData, + timeout: 30 * 1000, + }).then(res => { + if (!res.ok) { + throw `${res.status} ${res.statusText}`; + } else { + return res.json(); + } + }); }; export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> { @@ -94,9 +100,9 @@ export function connectStream(user: any, channel: string, listener: (message: Re ws.on('open', () => { ws.on('message', data => { const msg = JSON.parse(data.toString()); - if (msg.type == 'channel' && msg.body.id == 'a') { + if (msg.type === 'channel' && msg.body.id === 'a') { listener(msg.body); - } else if (msg.type == 'connected' && msg.body.id == 'a') { + } else if (msg.type === 'connected' && msg.body.id === 'a') { res(ws); } }); @@ -107,8 +113,8 @@ export function connectStream(user: any, channel: string, listener: (message: Re channel: channel, id: 'a', pong: true, - params: params - } + params: params, + }, })); }); }); @@ -119,8 +125,8 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? return await new Promise((resolve, reject) => { const req = http.request(`http://localhost:${port}${path}`, { headers: { - Accept: accept - } + Accept: accept, + }, }, res => { if (res.statusCode! >= 400) { reject(res); @@ -139,9 +145,9 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProcess) => void, moreProcess: () => Promise<void> = async () => {}) { return (done: (err?: Error) => any) => { - const p = childProcess.spawn('node', [__dirname + '/../index.js'], { + const p = childProcess.spawn('node', [_dirname + '/../index.js'], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env: { NODE_ENV: 'test', PATH: process.env.PATH } + env: { NODE_ENV: 'test', PATH: process.env.PATH }, }); callbackSpawnedProcess(p); p.on('message', message => { @@ -153,12 +159,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce export async function initTestDb(justBorrow = false, initEntities?: any[]) { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; - try { - const conn = await getConnection(); - await conn.close(); - } catch (e) {} - - return await createConnection({ + const db = new DataSource({ type: 'postgres', host: config.db.host, port: config.db.port, @@ -167,8 +168,12 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) { database: config.db.db, synchronize: true && !justBorrow, dropSchema: true && !justBorrow, - entities: initEntities || entities + entities: initEntities || entities, }); + + await db.initialize(); + + return db; } export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProcess> { @@ -178,9 +183,9 @@ export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProc rej('timeout to start'); }, timeout); - const p = childProcess.spawn('node', [__dirname + '/../built/index.js'], { + const p = childProcess.spawn('node', [_dirname + '/../built/index.js'], { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], - env: { NODE_ENV: 'test', PATH: process.env.PATH } + env: { NODE_ENV: 'test', PATH: process.env.PATH }, }); p.on('error', e => rej(e)); diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 3120851aae..22338a4976 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -25,9 +25,14 @@ "rootDir": "./src", "baseUrl": "./", "paths": { - "@/*": ["./src/*"] + "@/*": [ + "./src/*" + ] }, "outDir": "./built", + "types": [ + "node" + ], "typeRoots": [ "./node_modules/@types", "./src/@types" diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 5cd71acf9c..5ce87c34eb 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -12,20 +12,6 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99" integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw== -"@babel/runtime@^7.16.0": - version "7.16.3" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5" - integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ== - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/runtime@^7.6.2": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" - integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/types@^7.6.1", "@babel/types@^7.9.6": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" @@ -35,68 +21,63 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@bull-board/api@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.3.tgz#c6aad9f5cfb3acbe02c57e823ee81c1ae575849d" - integrity sha512-kV6EPwi9j71qBmozvDmtT01j986r4cFqNmBgq7HApYXW0G2U8Brmv0Ut0iMQZRc/X7aA5KYL3qXcEsriFnq+jw== +"@bull-board/api@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.11.1.tgz#98b2c9556f643718bb5bde4a1306e6706af8192e" + integrity sha512-ElwX7sM+Ng4ZL9KUsbDubRE+r2hu/gss85OsROeE9bmyfkW14jOJkgr5MKUyjTTgPEeMs1Mw55TgQs2vxoWBiA== dependencies: redis-info "^3.0.8" -"@bull-board/koa@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.3.tgz#b9f02629f96f056d6a038c3c58fc339d58e55abb" - integrity sha512-DK8m09MwcRwUR3tz3xI0iSK/Ih2huQ2MAWm8krYjO5deswP2yBaCWE4OtpiULLfVpf8z4zB3Oqa0xNJrKRHTOQ== +"@bull-board/koa@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.11.1.tgz#1872aba2c65d116d1183b3003e4a2cb2c1e2fbbf" + integrity sha512-F/thrTuC1JWpdBO7DPdKD/wr8c+d7MJGu0sr5ARsT1WXhng7sU7OqBEP/5Y7HhByurjDFXDxcgk/mc78Tmeb/Q== dependencies: - "@bull-board/api" "3.10.3" - "@bull-board/ui" "3.10.3" - ejs "^3.1.6" + "@bull-board/api" "3.11.1" + "@bull-board/ui" "3.11.1" + ejs "^3.1.7" koa "^2.13.1" koa-mount "^4.0.0" koa-router "^10.0.0" koa-static "^5.0.0" koa-views "^7.0.1" -"@bull-board/ui@3.10.3": - version "3.10.3" - resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.3.tgz#b921199d42b32d8ddd9bbf0e35c25be0d64403e9" - integrity sha512-6zYW3FqySg+4IKEeM1jt/5ixNVBKQjtZLG9W81ADVcHk8YceQ++7URWzDb8nQEct3rEW4bjR6nicVWNXMSN7Lw== +"@bull-board/ui@3.11.1": + version "3.11.1" + resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.11.1.tgz#17a2af5573f31811a543105b9a96249c95e93ce7" + integrity sha512-SRrfvxHF/WaBICiAFuWAoAlTvoBYUBmX94oRbSKzVILRFZMe3gs0hN071BFohrn4yOTFHAkWPN7cjMbaqHwCag== dependencies: - "@bull-board/api" "3.10.3" + "@bull-board/api" "3.11.1" -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: - "@cspotcode/source-map-consumer" "0.8.0" + "@jridgewell/trace-mapping" "0.3.9" "@cto.af/textdecoder@^0.0.0": version "0.0.0" resolved "https://registry.yarnpkg.com/@cto.af/textdecoder/-/textdecoder-0.0.0.tgz#e1e8d84c936c30a0f4619971f19ca41941af9fdc" integrity sha512-sJpx3F5xcVV/9jNYJQtvimo4Vfld/nD3ph+ZWtQzZ03Zo8rJC7QKQTRcIGS13Rcz80DwFNthCWMrd58vpY4ZAQ== -"@digitalbazaar/http-client@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-1.1.0.tgz#cac383b24ace04b18b919deab773462b03d3d7b0" - integrity sha512-ks7hqa6hm9NyULdbm9qL6TRS8rADzBw8R0lETvUgvdNXu9H62XG2YqoKRDThtfgWzWxLwRJ3Z2o4ev81dZZbyQ== +"@digitalbazaar/http-client@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-3.2.0.tgz#b85ea09028c7d0f288f976c852d0a8f3875f0fcf" + integrity sha512-NhYXcWE/JDE7AnJikNX7q0S6zNuUPA2NuIoRdUpmvHlarjmRqyr6hIO3Awu2FxlUzbdiI1uzuWrZyB9mD1tTvw== dependencies: - esm "^3.2.22" - ky "^0.25.1" - ky-universal "^0.8.2" + ky "^0.30.0" + ky-universal "^0.10.1" + undici "^5.2.0" -"@discordapp/twemoji@13.1.1": - version "13.1.1" - resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" - integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== +"@discordapp/twemoji@14.0.2": + version "14.0.2" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837" + integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA== dependencies: fs-extra "^8.0.1" jsonfile "^5.0.0" - twemoji-parser "13.1.0" + twemoji-parser "14.0.0" universalify "^0.1.2" "@elastic/elasticsearch@7.11.0": @@ -110,19 +91,19 @@ pump "^3.0.0" secure-json-parse "^2.1.0" -"@eslint/eslintrc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" - integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" + espree "^9.3.2" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@gar/promisify@^1.0.1": @@ -144,6 +125,24 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe" + integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.13" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" + integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@koa/cors@3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2" @@ -234,6 +233,15 @@ mkdirp "^1.0.4" rimraf "^3.0.2" +"@peertube/http-signature@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@peertube/http-signature/-/http-signature-1.6.0.tgz#22bef028384e6437e8dbd94052ba7b8bd7f7f1ae" + integrity sha512-Bx780c7FPYtkV4LgCoaJcXYcKQqaMef2iQR2V2r5klkYkIQWFxbTOpyhKxvVXYIBIFpj5Cb8DGVDAmhkm7aavg== + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.14.1" + "@redocly/ajv@^8.6.4": version "8.6.4" resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85" @@ -244,10 +252,10 @@ require-from-string "^2.0.2" uri-js "^4.2.2" -"@redocly/openapi-core@1.0.0-beta.93": - version "1.0.0-beta.93" - resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.93.tgz#882db8684598217f621adc7349288229253a0038" - integrity sha512-xQj7UnjPj3mKtkyRrm+bjzEoyo0CVNjGP4pV6BzQ0vgKf0Jqq7apFC703psyBH+JscYr7NKK1hPQU76ylhFDdg== +"@redocly/openapi-core@1.0.0-beta.97": + version "1.0.0-beta.97" + resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz#324ed46e9a9aee4c615be22ee348c53f7bb5f180" + integrity sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ== dependencies: "@redocly/ajv" "^8.6.4" "@types/node" "^14.11.8" @@ -255,7 +263,7 @@ js-levenshtein "^1.1.6" js-yaml "^4.1.0" lodash.isequal "^4.5.0" - minimatch "^3.0.4" + minimatch "^5.0.1" node-fetch "^2.6.1" pluralize "^8.0.0" yaml-ast-parser "0.0.43" @@ -277,10 +285,10 @@ dependencies: type-detect "4.0.8" -"@sinonjs/fake-timers@9.1.1": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.1.tgz#7b698e0b9d12d93611f06ee143c30ced848e2840" - integrity sha512-Wp5vwlZ0lOqpSYGKqr53INws9HLkt6JDc/pDZcPf7bchQnrXJMXPns8CXx0hFikMSGSWfvtvvpb2gtMVfkWagA== +"@sinonjs/fake-timers@9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== dependencies: "@sinonjs/commons" "^1.7.0" @@ -527,6 +535,11 @@ resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4" integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg== +"@types/jsrsasign@10.5.1": + version "10.5.1" + resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.1.tgz#6f9defd46dfcf324b1cff08a06be639858deee3b" + integrity sha512-QqM03IXHY6SX835mWdx7Vp8ZOxw/hcnMjGjapUQf+pgFPRyGdjg3jxFsr4p+rolKcdRhptm3mtVQNk4OMhCQcA== + "@types/keygrip@*": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72" @@ -649,10 +662,10 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw== -"@types/mocha@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5" - integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg== +"@types/mocha@9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node-fetch@3.0.3": version "3.0.3" @@ -666,10 +679,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50" integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA== -"@types/node@17.0.23": - version "17.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" - integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw== +"@types/node@17.0.41": + version "17.0.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b" + integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw== "@types/node@^14.11.8": version "14.17.9" @@ -700,11 +713,6 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== -"@types/portscanner@2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b" - integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q== - "@types/pug@2.0.6": version "2.0.6" resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6" @@ -777,6 +785,11 @@ dependencies: htmlparser2 "^6.0.0" +"@types/semver@7.3.9": + version "7.3.9" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" + integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== + "@types/serve-static@*": version "1.13.3" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1" @@ -785,10 +798,10 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/sharp@0.30.1": - version "0.30.1" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.1.tgz#31bd128f2437e8fc31424eb23d8284aa127bfa8d" - integrity sha512-LxzQsKo2YtvA2DlqACNXmlbLGMVJCSU/HhV4N9RrStClUEf02iN+AakD/zUOpZkbo1OG+lHk2LeqoHedLwln2w== +"@types/sharp@0.30.2": + version "0.30.2" + resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.2.tgz#df5ff34140b3bad165482e6f3d26b08e42a0503a" + integrity sha512-uLCBwjDg/BTcQit0dpNGvkIjvH3wsb8zpaJePCjvONBBSfaKHoxXBIuq1MT8DMQEfk2fKYnpC9QExCgFhkGkMQ== dependencies: "@types/node" "*" @@ -845,85 +858,85 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d" - integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A== +"@typescript-eslint/eslint-plugin@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758" + integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/type-utils" "5.18.0" - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/type-utils" "5.27.1" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" -"@typescript-eslint/type-utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74" - integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA== +"@typescript-eslint/type-utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166" + integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw== dependencies: - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855" - integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA== +"@typescript-eslint/utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f" + integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" "@ungap/promise-all-settled@1.1.2": version "1.1.2" @@ -963,10 +976,10 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^7.1.1: version "7.1.1" @@ -988,11 +1001,16 @@ acorn@^8.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== -acorn@^8.5.0, acorn@^8.7.0: +acorn@^8.5.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -1074,7 +1092,7 @@ ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.0.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== @@ -1082,6 +1100,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -1131,13 +1156,13 @@ archiver-utils@^2.1.0: normalize-path "^3.0.0" readable-stream "^2.0.0" -archiver@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba" - integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg== +archiver@5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6" + integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w== dependencies: archiver-utils "^2.1.0" - async "^3.2.0" + async "^3.2.3" buffer-crc32 "^0.2.1" readable-stream "^3.6.0" readdir-glob "^1.0.0" @@ -1227,27 +1252,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - -async@>=0.2.9: - version "3.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" - integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== - -async@^2.6.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - -async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== +async@>=0.2.9, async@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== asynckit@^0.4.0: version "0.4.0" @@ -1266,10 +1274,10 @@ autwh@0.1.0: dependencies: oauth "0.9.15" -aws-sdk@2.1111.0: - version "2.1111.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1111.0.tgz#02b1e5c530ef8140235ee7c48c710bb2dbd7dc84" - integrity sha512-WRyNcCckzmu1djTAWfR2r+BuI/PbuLrhG3oa+oH39v4NZ4EecYWFL1CoCPlC2kRUML4maSba5T4zlxjcNl7ELQ== +aws-sdk@2.1152.0: + version "2.1152.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1152.0.tgz#73e4fb81b3a9c289234b5d6848bcdb854f169bdf" + integrity sha512-Lqwk0bDhm3vzpYb3AAM9VgGHeDpbB8+o7UJnP9R+CO23kJfi/XRpKihAcbyKDD/AUQ+O1LJaUVpvaJYLS9Am7w== dependencies: buffer "4.9.2" events "1.1.1" @@ -1278,7 +1286,7 @@ aws-sdk@2.1111.0: querystring "0.2.0" sax "1.2.1" url "0.10.3" - uuid "3.3.2" + uuid "8.0.0" xml2js "0.4.19" axios@^0.24.0: @@ -1296,9 +1304,9 @@ babel-walk@3.0.0-canary-5: "@babel/types" "^7.9.6" balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== base32.js@0.0.1: version "0.0.1" @@ -1327,11 +1335,6 @@ bcryptjs@2.4.3: resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= -big-integer@^1.6.16: - version "1.6.48" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" - integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== - big-integer@^1.6.17: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -1397,27 +1400,20 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -broadcast-channel@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198" - integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ== - dependencies: - "@babel/runtime" "^7.16.0" - detect-node "^2.1.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" - p-queue "6.6.2" - rimraf "3.0.2" - unload "2.3.1" - browser-process-hrtime@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" @@ -1490,10 +1486,10 @@ bufferutil@^4.0.1: dependencies: node-gyp-build "~3.7.0" -bull@4.8.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.1.tgz#83daaefc3118876450b21d7a02bc11ea28a2440e" - integrity sha512-ojH5AfOchKQsQwwE+thViS1pMpvREGC+Ov1+3HXsQqn5Q27ZSGkgMriMqc6c9J9rvQ/+D732pZE+TN1+2LRWVg== +bull@4.8.3: + version "4.8.3" + resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.3.tgz#4ab67029fee1183dcb7185895b20dc08c02d6bf2" + integrity sha512-oOHr+KTLu3JM5V9TXsg18/1xyVQceoYCFiGrXZOpu9abZn3W3vXJtMBrwB6Yvl/RxSKVVBpoa25RF/ya3750qg== dependencies: cron-parser "^4.2.1" debuglog "^1.0.0" @@ -1586,11 +1582,6 @@ cacheable-request@^7.0.2: normalize-url "^6.0.1" responselike "^2.0.0" -cafy@15.2.1: - version "15.2.1" - resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a" - integrity sha512-g2zOmFb63p6XcZ/zeMWKYP8YKQYNWnhJmi6K71Ql4EAFTAay31xF0PBPtdBCCfQ0fiETgWTMxKtySAVI/Od6aQ== - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -1678,7 +1669,7 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2: +chalk@^4.0.2, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -1720,7 +1711,7 @@ cheerio@0.22.0: lodash.reject "^4.4.0" lodash.some "^4.4.0" -chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.2: +chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.3: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -1859,10 +1850,10 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884" - integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw== +color@^4.0.1: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" color-string "^1.9.0" @@ -1884,10 +1875,10 @@ commander@^2.19.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^8.2.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== compress-commons@^4.1.0: version "4.1.1" @@ -2079,11 +2070,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-uri-to-buffer@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" - integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== - data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" @@ -2124,6 +2110,13 @@ debug@4.3.3: dependencies: ms "2.1.2" +debug@4.3.4, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -2145,13 +2138,6 @@ debug@^4.3.2: dependencies: ms "2.1.2" -debug@^4.3.3: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debuglog@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -2261,26 +2247,16 @@ destroy@^1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= detect-libc@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204" integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw== -detect-libc@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" - integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== - -detect-node@2.1.0, detect-node@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - dicer@0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" @@ -2470,12 +2446,12 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= -ejs@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== +ejs@^3.1.7: + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: - jake "^10.6.1" + jake "^10.8.5" emoji-regex@^8.0.0: version "8.0.0" @@ -2700,22 +2676,17 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint-visitor-keys@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" - integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7" - integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ== +eslint@8.17.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21" + integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw== dependencies: - "@eslint/eslintrc" "^1.2.1" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2726,14 +2697,14 @@ eslint@8.13.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.1" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2742,7 +2713,7 @@ eslint@8.13.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -2751,18 +2722,13 @@ eslint@8.13.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -esm@^3.2.22: - version "3.2.25" - resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" - integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== - -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" + acorn "^8.7.1" + acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" esprima@^4.0.1: @@ -2809,7 +2775,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -eventemitter3@^4.0.4, eventemitter3@^4.0.7: +eventemitter3@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== @@ -2844,13 +2810,6 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2897,6 +2856,17 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2926,11 +2896,6 @@ feed@4.2.2: dependencies: xml-js "^1.6.11" -fetch-blob@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c" - integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow== - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717" @@ -2946,21 +2911,21 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -file-type@17.1.1: - version "17.1.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.1.tgz#24c59bc663df0c0c181b31dfacde25e06431afbe" - integrity sha512-heRUMZHby2Qj6wZAA3YHeMlRmZNQTcb6VxctkGmM+mcM6ROQKvHpr7SS6EgdfEhH+s25LDshBjvPx/Ecm+bOVQ== +file-type@17.1.2: + version "17.1.2" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.2.tgz#9257437a64e0c3623f70d9f27430522d978b1384" + integrity sha512-3thBUSfa9YEUEGO/NAAiQGvjujZxZiJTF6xNwyDn6kB0NcEtwMn5ttkGG9jGwm/Nt/t8U1bpBNqyBNZCz4F4ig== dependencies: readable-web-to-node-stream "^3.0.2" strtok3 "^7.0.0-alpha.7" token-types "^5.0.0-alpha.2" filelist@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" - integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + version "1.0.3" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83" + integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q== dependencies: - minimatch "^3.0.4" + minimatch "^5.0.1" fill-range@^7.0.1: version "7.0.1" @@ -2969,14 +2934,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-node-modules@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" - integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== - dependencies: - findup-sync "^4.0.0" - merge "^2.1.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -3000,16 +2957,6 @@ find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -3210,7 +3157,7 @@ github-from-package@0.0.0: resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3248,37 +3195,10 @@ glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.6.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" - integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== - dependencies: - type-fest "^0.20.2" - -globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -3294,6 +3214,18 @@ globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + got@11.5.1: version "11.5.1" resolved "https://registry.yarnpkg.com/got/-/got-11.5.1.tgz#bf098a270fe80b3fb88ffd5a043a59ebb0a391db" @@ -3311,10 +3243,10 @@ got@11.5.1: p-cancelable "^2.0.0" responselike "^2.0.0" -got@12.0.3: - version "12.0.3" - resolved "https://registry.yarnpkg.com/got/-/got-12.0.3.tgz#c7314daab26d42039e624adbf98f6d442e5de749" - integrity sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug== +got@12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4" + integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig== dependencies: "@sindresorhus/is" "^4.6.0" "@szmarczak/http-timer" "^5.0.1" @@ -3345,11 +3277,6 @@ graceful-fs@^4.2.6: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -3404,13 +3331,6 @@ highlight.js@^10.7.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - hpagent@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-0.1.2.tgz#cab39c66d4df2d4377dbd212295d878deb9bdaa9" @@ -3507,15 +3427,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http-signature@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" - integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw== - dependencies: - assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.14.1" - http2-wrapper@^1.0.0-beta.5.0: version "1.0.3" resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" @@ -3600,11 +3511,6 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.1.8: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== - ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -3700,10 +3606,10 @@ ip-address@^7.1.0: jsbn "1.1.0" sprintf-js "1.1.2" -ip-cidr@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375" - integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ== +ip-cidr@3.0.10: + version "3.0.10" + resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.10.tgz#e1a039705196d84b43858f81a243fd70def9cefc" + integrity sha512-PXSsrRYirsuaCI1qBVyVXRLUIpNzxm76eHd3UvN5NXTMUG85GWGZpr6P+70mimc5e7Nfh/tShmjk0oSywErMWg== dependencies: ip-address "^7.1.0" jsbn "^1.1.0" @@ -3848,13 +3754,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-like@^1.0.3: - version "1.0.8" - resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" - integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== - dependencies: - lodash.isfinite "^3.3.2" - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -3962,11 +3861,6 @@ is-whitespace@^0.3.0: resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38= -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -3982,13 +3876,13 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -jake@^10.6.1: - version "10.8.2" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b" - integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A== +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "0.9.x" - chalk "^2.4.2" + async "^3.2.3" + chalk "^4.0.2" filelist "^1.0.1" minimatch "^3.0.4" @@ -4117,7 +4011,7 @@ json5-loader@4.0.1: loader-utils "^2.0.0" schema-utils "^3.0.0" -json5@2.2.1: +json5@2.2.1, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -4152,30 +4046,30 @@ jsonfile@^5.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonld@5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-5.2.0.tgz#d1e8af38a334cb95edf0f2ae4e2b58baf8d2b5a9" - integrity sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw== +jsonld@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-6.0.0.tgz#560a8a871dce72aba5d4c6b08356438d863d62fb" + integrity sha512-1SkN2RXhMCTCSkX+bzHvr9ycM2HTmjWyV41hn2xG7k6BqlCgRjw0zHmuqfphjBRPqi1gKMIqgBCe/0RZMcWrAA== dependencies: - "@digitalbazaar/http-client" "^1.1.0" + "@digitalbazaar/http-client" "^3.2.0" canonicalize "^1.0.1" lru-cache "^6.0.0" rdf-canonize "^3.0.0" -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== +jsprim@^1.2.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" + integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== dependencies: assert-plus "1.0.0" extsprintf "1.3.0" json-schema "0.4.0" verror "1.10.0" -jsrsasign@8.0.20: - version "8.0.20" - resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-8.0.20.tgz#37d8029c9d8f794d8ac8d8998bce319921491f11" - integrity sha512-JTXt9+nqdynIB8wFsS6e8ffHhIjilhywXwdaEVHSj9OVmwldG2H0EoCqkQ+KXkm2tVqREfH/HEmklY4k1/6Rcg== +jsrsasign@10.5.24: + version "10.5.24" + resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.24.tgz#2d159e1756b2268682c6eb5e147184e33e946b1c" + integrity sha512-0i/UHRgJZifp/YmoXHyNQXUY4eKWiSd7YxuD7oKEw9mlqgr51hg9lZQw2nlEDvwHDh7pyj6ZjYlxldlW27xb/Q== jstransformer@1.0.0: version "1.0.0" @@ -4368,18 +4262,18 @@ koa@2.13.4, koa@^2.13.1: type-is "^1.6.16" vary "^1.1.2" -ky-universal@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.8.2.tgz#edc398d54cf495d7d6830aa1ab69559a3cc7f824" - integrity sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ== +ky-universal@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.10.1.tgz#778881e098f6e3c52a87b382d9acca54d22bb0d3" + integrity sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g== dependencies: abort-controller "^3.0.0" - node-fetch "3.0.0-beta.9" + node-fetch "^3.2.2" -ky@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc" - integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA== +ky@^0.30.0: + version "0.30.0" + resolved "https://registry.yarnpkg.com/ky/-/ky-0.30.0.tgz#a3d293e4f6c4604a9a4694eceb6ce30e73d27d64" + integrity sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog== lazystream@^1.0.0: version "1.0.1" @@ -4485,11 +4379,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.isfinite@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" - integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M= - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -4535,7 +4424,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -4573,11 +4462,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.4.0: - version "7.8.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb" - integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg== - luxon@^1.28.0: version "1.28.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" @@ -4625,27 +4509,22 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merge@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - methods@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -mfm-js@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" - integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== +mfm-js@0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2" + integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ== dependencies: - twemoji-parser "13.1.x" + twemoji-parser "14.0.x" micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" @@ -4655,10 +4534,13 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" mime-db@1.44.0: version "1.44.0" @@ -4704,21 +4586,14 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== +minimatch@5.0.1, minimatch@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" -minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -4815,40 +4690,38 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" - integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.3" - debug "4.3.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.2.0" - growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "4.2.1" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.1" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" moment@^2.22.2: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + version "2.29.3" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3" + integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw== ms@2.0.0: version "2.0.0" @@ -4899,10 +4772,10 @@ multer@1.4.4: type-is "^1.6.4" xtend "^4.0.0" -mylas@^2.1.4: - version "2.1.5" - resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.5.tgz#7ccf41ec5a93ab2d63fc3678abf1942c0e7bdeb1" - integrity sha512-7ZyrJux1lipSR45IxDvWz7zJOXWTazTFCqD4/p8XBF4O+mtJwf7QpMWTH+jE4lV9O2I38xcpS0KTIp7GwhUTmA== +mylas@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc" + integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg== mz@^2.4.0, mz@^2.7.0: version "2.7.0" @@ -4918,14 +4791,12 @@ nan@^2.14.2, nan@^2.15.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= - dependencies: - big-integer "^1.6.16" +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== -nanoid@3.3.1, nanoid@^3.1.30: +nanoid@^3.1.30: version "3.3.1" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== @@ -4976,7 +4847,7 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-addon-api@^4.3.0: +node-addon-api@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== @@ -4995,18 +4866,10 @@ node-fetch@*: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-fetch@3.0.0-beta.9: - version "3.0.0-beta.9" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b" - integrity sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg== - dependencies: - data-uri-to-buffer "^3.0.1" - fetch-blob "^2.1.1" - -node-fetch@3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.3.tgz#a03c9cc2044d21d1a021566bd52f080f333719a6" - integrity sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA== +node-fetch@3.2.6, node-fetch@^3.2.2: + version "3.2.6" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9" + integrity sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw== dependencies: data-uri-to-buffer "^4.0.0" fetch-blob "^3.1.4" @@ -5045,10 +4908,10 @@ node-gyp@^8.4.1: tar "^6.1.2" which "^2.0.2" -nodemailer@6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018" - integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g== +nodemailer@6.7.5: + version "6.7.5" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.5.tgz#b30b1566f5fa2249f7bd49ced4c58bec6b25915e" + integrity sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg== nofilter@^2.0.3: version "2.0.3" @@ -5175,11 +5038,6 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== - on-finished@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -5327,14 +5185,6 @@ p-map@^4.0.0: dependencies: aggregate-error "^3.0.0" -p-queue@6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -5364,11 +5214,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - parse-srcset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" @@ -5507,11 +5352,23 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +plimit-lit@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3" + integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw== + dependencies: + queue-lit "^1.2.7" + pluralize@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" @@ -5527,14 +5384,6 @@ pngjs@^5.0.0: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== -portscanner@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" - integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== - dependencies: - async "^2.6.0" - is-number-like "^1.0.3" - postcss@^8.3.11: version "8.3.11" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858" @@ -5566,10 +5415,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" - integrity sha512-QBSab31WqkyxpnMWQxubYAHR5S9B2+r81ucocew34Fkl98FhvKIF50jIJnNOBmAZfyNV7vE5T6gd3hTVWgY6tg== +prebuild-install@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.0.tgz#991b6ac16c81591ba40a6d5de93fb33673ac1370" + integrity sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -5828,6 +5677,11 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= +queue-lit@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763" + integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -6006,11 +5860,6 @@ reflect-metadata@0.1.13, reflect-metadata@^0.1.13: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== -regenerator-runtime@^0.13.4: - version "0.13.7" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" - integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== - regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -6053,14 +5902,6 @@ resolve-alpn@^1.2.0: resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -6115,7 +5956,7 @@ rimraf@2: dependencies: glob "^7.1.3" -rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -6213,12 +6054,12 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@7.3.6: - version "7.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b" - integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w== +semver@7.3.7, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: - lru-cache "^7.4.0" + lru-cache "^6.0.0" semver@^5.6.0: version "5.7.1" @@ -6274,17 +6115,17 @@ sha.js@^2.4.11: inherits "^2.0.1" safe-buffer "^5.0.1" -sharp@0.30.3: - version "0.30.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37" - integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg== +sharp@0.29.3: + version "0.29.3" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" + integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== dependencies: - color "^4.2.1" - detect-libc "^2.0.1" - node-addon-api "^4.3.0" - prebuild-install "^7.0.1" + color "^4.0.1" + detect-libc "^1.0.3" + node-addon-api "^4.2.0" + prebuild-install "^7.0.0" semver "^7.3.5" - simple-get "^4.0.1" + simple-get "^4.0.0" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -6329,7 +6170,7 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0, simple-get@^4.0.1: +simple-get@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== @@ -6588,16 +6429,17 @@ style-loader@3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -summaly@2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea" - integrity sha512-IzvO2s7yj/PUyH42qWjVjSPpIiPlgTRWGh33t4cIZKOqPQJ2INo7e83hXhHFr4hXTb3JRcIdCuM1ELjlrujiUQ== +summaly@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.1.tgz#742fe6631987f84ad2e95d2b0f7902ec57e0f6b3" + integrity sha512-WWvl7rLs3wm61Xc2JqgTbSuqtIOmGqKte+rkbnxe6ISy4089lQ+7F2ajooQNee6PWHl9kZ27SDd1ZMoL3/6R4A== dependencies: cheerio "0.22.0" debug "4.3.3" escape-regexp "0.0.1" got "11.5.1" html-entities "2.3.2" + iconv-lite "0.6.3" jschardet "3.0.0" koa "2.13.4" private-ip "2.3.3" @@ -6642,10 +6484,10 @@ syslog-pro@1.0.0: dependencies: moment "^2.22.2" -systeminformation@5.11.9: - version "5.11.9" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d" - integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA== +systeminformation@5.11.16: + version "5.11.16" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.16.tgz#5f6fda2447fafe204bd2ab543475f1ffa8c14a85" + integrity sha512-/a1VfP9WELKLT330yhAHJ4lWCXRYynel1kMMHKc/qdzCgDt3BIcMlo+3tKcTiRHFefjV3fz4AvqMx7dGO/72zw== tapable@^2.2.0: version "2.2.0" @@ -6803,22 +6645,22 @@ trace-redirect@1.0.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk= -ts-loader@9.2.8: - version "9.2.8" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48" - integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw== +ts-loader@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f" + integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" micromatch "^4.0.0" semver "^7.3.4" -ts-node@10.7.0: - version "10.7.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" - integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== +ts-node@10.8.1: + version "10.8.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066" + integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g== dependencies: - "@cspotcode/source-map-support" "0.7.0" + "@cspotcode/source-map-support" "^0.8.0" "@tsconfig/node10" "^1.0.7" "@tsconfig/node12" "^1.0.7" "@tsconfig/node14" "^1.0.0" @@ -6829,22 +6671,31 @@ ts-node@10.7.0: create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - v8-compile-cache-lib "^3.0.0" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tsc-alias@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.4.1.tgz#6a6075dd94267d9befdad1431f410bd0b8819805" - integrity sha512-nHTR8qvM/LiYI8Fx6UrzAQXRngAuE2PEK+n9uXmQY6fN+oLZhweNFkCLbyxKDmlLfYnclSuaR+dSuvRd7FUu8Q== +tsc-alias@1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa" + integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ== dependencies: - chokidar "^3.5.2" - commander "^8.2.0" - find-node-modules "^2.1.2" + chokidar "^3.5.3" + commander "^9.0.0" globby "^11.0.4" - mylas "^2.1.4" + mylas "^2.1.9" normalize-path "^3.0.0" + plimit-lit "^1.2.6" + +tsconfig-paths@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" -tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1: +tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== @@ -6888,12 +6739,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twemoji-parser@13.1.0, twemoji-parser@13.1.x: - version "13.1.0" - resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" - integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== - -twemoji-parser@14.0.0: +twemoji-parser@14.0.0, twemoji-parser@14.0.x: version "14.0.0" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== @@ -6952,10 +6798,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeorm@0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.5.tgz#8fe50d517de5ec6f4b38856ea0f180e4a60cf7e4" - integrity sha512-KL4c8nQqouHaXs4m1J3xh7oXWqX4+A9poExbceLxBRtlavpJQYqiSnqt3JYGpy7Tl9vD5DG5DrmZrSslTkkW5Q== +typeorm@0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.6.tgz#65203443a1b684bb746785913fe2b0877aa991c0" + integrity sha512-DRqgfqcelMiGgWSMbBmVoJNFN2nPNA3EeY2gC324ndr2DZoGRTb9ILtp2oGVGnlA+cu5zgQ6it5oqKFNkte7Aw== dependencies: "@sqltools/formatter" "^1.2.2" app-root-path "^3.0.0" @@ -6975,10 +6821,10 @@ typeorm@0.3.5: xml2js "^0.4.23" yargs "^17.3.1" -typescript@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== ulid@2.3.0: version "2.3.0" @@ -6995,6 +6841,11 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" +undici@^5.2.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.4.0.tgz#c474fae02743d4788b96118d46008a24195024d2" + integrity sha512-A1SRXysDg7J+mVP46jF+9cKANw0kptqSFZ8tGyL+HBiv0K1spjxPX8Z4EGu+Eu6pjClJUBdnUPlxrOafR668/g== + unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -7014,14 +6865,6 @@ universalify@^0.1.0, universalify@^0.1.2: resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unload@2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/unload/-/unload-2.3.1.tgz#9d16862d372a5ce5cb630ad1309c2fd6e35dacfe" - integrity sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA== - dependencies: - "@babel/runtime" "^7.6.2" - detect-node "2.1.0" - unpipe@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -7075,25 +6918,25 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== - uuid@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c" + integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw== + uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -v8-compile-cache-lib@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8" - integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-compile-cache@^2.0.3: version "2.2.0" @@ -7133,10 +6976,10 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" -web-push@3.4.5: - version "3.4.5" - resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.4.5.tgz#f94074ff150538872c7183e4d8881c8305920cf1" - integrity sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g== +web-push@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.5.0.tgz#4576533746052eda3bd50414b54a1b0a21eeaeae" + integrity sha512-JC0V9hzKTqlDYJ+LTZUXtW7B175qwwaqzbbMSWDxHWxZvd3xY0C2rcotMGDavub2nAAFw+sXTsqR65/KY2A5AQ== dependencies: asn1.js "^5.3.0" http_ece "1.1.0" @@ -7216,20 +7059,20 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2, which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -which@^1.1.1, which@^1.2.14: +which@^1.1.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" +which@^2.0.1, which@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" @@ -7259,10 +7102,10 @@ word-wrap@^1.2.3, word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: version "6.2.0" @@ -7287,20 +7130,20 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== ws@^8.2.3: version "8.4.2" resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b" integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA== -xev@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/xev/-/xev-2.0.1.tgz#24484173a22115bc8a990ef5d4d5129695b827a7" - integrity sha512-icDf9M67bDge0F2qf02WKZq+s7mMO/SbPv67ZQPym6JThLEOdlWWLdB7VTVgRJp3ekgaiVItCAyH6aoKCPvfIA== +xev@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/xev/-/xev-3.0.2.tgz#3f4080bd8bed0d3479c674050e3696da98d22a4d" + integrity sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw== xml-js@^1.6.11: version "1.6.11" diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index a6e23e5171..10f0e5a9cb 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -1,68 +1,85 @@ module.exports = { root: true, env: { - "node": false + 'node': false, }, - parser: "vue-eslint-parser", + parser: 'vue-eslint-parser', parserOptions: { - "parser": "@typescript-eslint/parser", + 'parser': '@typescript-eslint/parser', tsconfigRootDir: __dirname, - //project: ['./tsconfig.json'], + project: ['./tsconfig.json'], + extraFileExtensions: ['.vue'], }, extends: [ - //"../shared/.eslintrc.js", - "plugin:vue/vue3-recommended" + '../shared/.eslintrc.js', + 'plugin:vue/vue3-recommended', ], rules: { + '@typescript-eslint/no-empty-interface': [ + 'error', + { + 'allowSingleExtends': true, + }, + ], // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため // data の禁止理由: 抽象的すぎるため // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため - "id-denylist": ["error", "window", "data", "e"], + 'id-denylist': ['error', 'window', 'data', 'e'], 'eqeqeq': ['error', 'always', { 'null': 'ignore' }], - "no-shadow": ["warn"], - "vue/attributes-order": ["error", { - "alphabetical": false + 'no-shadow': ['warn'], + 'vue/attributes-order': ['error', { + 'alphabetical': false, }], - "vue/no-use-v-if-with-v-for": ["error", { - "allowUsingIterationVar": false + 'vue/no-use-v-if-with-v-for': ['error', { + 'allowUsingIterationVar': false, }], - "vue/no-ref-as-operand": "error", - "vue/no-multi-spaces": ["error", { - "ignoreProperties": false + 'vue/no-ref-as-operand': 'error', + 'vue/no-multi-spaces': ['error', { + 'ignoreProperties': false, }], - "vue/no-v-html": "error", - "vue/order-in-components": "error", - "vue/html-indent": ["warn", "tab", { - "attribute": 1, - "baseIndent": 0, - "closeBracket": 0, - "alignAttributesVertically": true, - "ignores": [] + 'vue/no-v-html': 'error', + 'vue/order-in-components': 'error', + 'vue/html-indent': ['warn', 'tab', { + 'attribute': 1, + 'baseIndent': 0, + 'closeBracket': 0, + 'alignAttributesVertically': true, + 'ignores': [], }], - "vue/html-closing-bracket-spacing": ["warn", { - "startTag": "never", - "endTag": "never", - "selfClosingTag": "never" + 'vue/html-closing-bracket-spacing': ['warn', { + 'startTag': 'never', + 'endTag': 'never', + 'selfClosingTag': 'never', }], - "vue/multi-word-component-names": "warn", - "vue/require-v-for-key": "warn", - "vue/no-unused-components": "warn", - "vue/valid-v-for": "warn", - "vue/return-in-computed-property": "warn", - "vue/no-setup-props-destructure": "warn", - "vue/max-attributes-per-line": "off", - "vue/html-self-closing": "off", - "vue/singleline-html-element-content-newline": "off", + 'vue/multi-word-component-names': 'warn', + 'vue/require-v-for-key': 'warn', + 'vue/no-unused-components': 'warn', + 'vue/valid-v-for': 'warn', + 'vue/return-in-computed-property': 'warn', + 'vue/no-setup-props-destructure': 'warn', + 'vue/max-attributes-per-line': 'off', + 'vue/html-self-closing': 'off', + 'vue/singleline-html-element-content-newline': 'off', }, globals: { - "require": false, - "_DEV_": false, - "_LANGS_": false, - "_VERSION_": false, - "_ENV_": false, - "_PERF_PREFIX_": false, - "_DATA_TRANSFER_DRIVE_FILE_": false, - "_DATA_TRANSFER_DRIVE_FOLDER_": false, - "_DATA_TRANSFER_DECK_COLUMN_": false - } -} + // Node.js + 'module': false, + 'require': false, + '__dirname': false, + + // Vue + '$$': false, + '$ref': false, + '$computed': false, + + // Misskey + '_DEV_': false, + '_LANGS_': false, + '_VERSION_': false, + '_ENV_': false, + '_PERF_PREFIX_': false, + '_DATA_TRANSFER_DRIVE_FILE_': false, + '_DATA_TRANSFER_DRIVE_FOLDER_': false, + '_DATA_TRANSFER_DECK_COLUMN_': false, + }, +}; diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts new file mode 100644 index 0000000000..67f724a9aa --- /dev/null +++ b/packages/client/@types/theme.d.ts @@ -0,0 +1,7 @@ +declare module '@/themes/*.json5' { + import { Theme } from "@/scripts/theme"; + + const theme: Theme; + + export default theme; +} diff --git a/packages/client/package.json b/packages/client/package.json index 9de500f3ab..83c8086e23 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,80 +1,52 @@ { "private": true, "scripts": { - "watch": "webpack --watch", - "build": "webpack", - "lint": "eslint --quiet 'src/**/*.{ts,vue}'" + "watch": "vite build --watch --mode development", + "build": "vite build", + "lint": "eslint --quiet \"src/**/*.{ts,vue}\"" }, "resolutions": { "chokidar": "^3.3.1", "lodash": "^4.17.21" }, "dependencies": { - "@discordapp/twemoji": "13.1.1", + "@discordapp/twemoji": "14.0.2", "@fortawesome/fontawesome-free": "6.1.1", + "@rollup/plugin-alias": "3.1.9", + "@rollup/plugin-json": "4.1.0", "@syuilo/aiscript": "0.11.1", - "@types/escape-regexp": "0.0.1", - "@types/glob": "7.2.0", - "@types/gulp": "4.0.9", - "@types/gulp-rename": "2.0.1", - "@types/is-url": "1.2.30", - "@types/katex": "0.14.0", - "@types/matter-js": "0.17.7", - "@types/mocha": "9.1.0", - "@types/oauth": "0.9.1", - "@types/parse5": "6.0.3", - "@types/punycode": "2.1.0", - "@types/qrcode": "1.4.2", - "@types/random-seed": "0.3.3", - "@types/seedrandom": "3.0.2", - "@types/throttle-debounce": "2.1.0", - "@types/tinycolor2": "1.4.3", - "@types/uuid": "8.3.4", - "@types/webpack": "5.28.0", - "@types/webpack-stream": "3.2.12", - "@types/websocket": "1.0.5", - "@types/ws": "8.5.3", - "@typescript-eslint/parser": "5.18.0", - "@vue/compiler-sfc": "3.2.31", + "@vitejs/plugin-vue": "2.3.3", + "@vue/compiler-sfc": "3.2.37", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", "autosize": "5.0.1", "autwh": "0.1.0", "blurhash": "1.1.5", - "broadcast-channel": "4.10.0", - "chart.js": "3.7.1", + "broadcast-channel": "4.13.0", + "browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2", + "chart.js": "3.8.0", "chartjs-adapter-date-fns": "2.0.0", - "chartjs-plugin-gradient": "0.2.2", + "chartjs-plugin-gradient": "0.5.0", "chartjs-plugin-zoom": "1.2.1", "compare-versions": "4.1.3", "content-disposition": "0.5.4", - "css-loader": "6.7.1", - "cssnano": "5.1.7", + "cropperjs": "2.0.0-beta", "date-fns": "2.28.0", "escape-regexp": "0.0.1", - "eslint": "8.13.0", - "eslint-plugin-vue": "8.6.0", "eventemitter3": "4.0.7", "feed": "4.2.2", - "glob": "7.2.0", "idb-keyval": "6.1.0", "insert-text-at-cursor": "0.3.0", - "ip-cidr": "3.0.4", "json5": "2.2.1", - "json5-loader": "4.0.1", - "katex": "0.15.3", + "katex": "0.15.6", "matter-js": "0.18.0", - "mfm-js": "0.21.0", + "mfm-js": "0.22.1", "misskey-js": "0.0.14", - "mocha": "9.2.2", + "mocha": "10.0.0", "ms": "2.1.3", "nested-property": "4.0.0", - "parse5": "6.0.1", - "photoswipe": "5.2.4", - "portscanner": "2.2.0", - "postcss": "8.4.12", - "postcss-loader": "6.2.1", - "prismjs": "1.27.0", + "photoswipe": "5.2.7", + "prismjs": "1.28.0", "private-ip": "2.3.3", "promise-limit": "2.7.0", "pug": "3.0.2", @@ -84,43 +56,58 @@ "random-seed": "0.3.0", "reflect-metadata": "0.1.13", "rndstr": "1.0.0", + "rollup": "2.75.6", "s-age": "1.1.2", - "sass": "1.50.0", - "sass-loader": "12.6.0", + "sass": "1.52.3", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", - "style-loader": "3.3.1", "syuilo-password-strength": "0.0.1", "textarea-caret": "3.1.0", - "three": "0.139.2", - "throttle-debounce": "4.0.0", + "three": "0.141.0", + "throttle-debounce": "5.0.0", "tinycolor2": "1.4.2", - "ts-loader": "9.2.8", - "tsc-alias": "1.5.0", - "tsconfig-paths": "3.14.1", + "tsc-alias": "1.6.9", + "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", - "typescript": "4.6.3", + "typescript": "4.7.3", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vue": "3.2.31", - "vue-loader": "17.0.0", + "vite": "2.9.10", + "vue": "3.2.37", "vue-prism-editor": "2.0.0-alpha.2", - "vue-router": "4.0.14", - "vue-style-loader": "4.1.3", - "vue-svg-loader": "0.17.0-beta.2", + "vue-router": "4.0.16", "vuedraggable": "4.0.1", - "webpack": "5.72.0", - "webpack-cli": "4.9.2", "websocket": "1.0.34", - "ws": "8.5.0" + "ws": "8.8.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "5.18.0", + "@types/escape-regexp": "0.0.1", + "@types/glob": "7.2.0", + "@types/gulp": "4.0.9", + "@types/gulp-rename": "2.0.1", + "@types/is-url": "1.2.30", + "@types/katex": "0.14.0", + "@types/matter-js": "0.17.7", + "@types/mocha": "9.1.1", + "@types/oauth": "0.9.1", + "@types/punycode": "2.1.0", + "@types/qrcode": "1.4.2", + "@types/random-seed": "0.3.3", + "@types/seedrandom": "3.0.2", + "@types/throttle-debounce": "5.0.0", + "@types/tinycolor2": "1.4.3", + "@types/uuid": "8.3.4", + "@types/websocket": "1.0.5", + "@types/ws": "8.5.3", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "cypress": "9.5.3", + "cypress": "10.0.3", + "eslint": "8.17.0", "eslint-plugin-import": "2.26.0", + "eslint-plugin-vue": "9.1.0", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts index f4dcab319c..ce4af61f18 100644 --- a/packages/client/src/account.ts +++ b/packages/client/src/account.ts @@ -1,5 +1,5 @@ import { del, get, set } from '@/scripts/idb-proxy'; -import { reactive } from 'vue'; +import { defineAsyncComponent, reactive } from 'vue'; import * as misskey from 'misskey-js'; import { apiUrl } from '@/config'; import { waiting, api, popup, popupMenu, success, alert } from '@/os'; @@ -11,10 +11,10 @@ import { i18n } from './i18n'; type Account = misskey.entities.MeDetailed; -const data = localStorage.getItem('account'); +const accountData = localStorage.getItem('account'); // TODO: 外部からはreadonlyに -export const $i = data ? reactive(JSON.parse(data) as Account) : null; +export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator); @@ -52,7 +52,7 @@ export async function signout() { return Promise.all(registrations.map(registration => registration.unregister())); }); } - } catch (e) {} + } catch (err) {} //#endregion document.cookie = `igi=; path=/`; @@ -104,8 +104,8 @@ function fetchAccount(token: string): Promise<Account> { }); } -export function updateAccount(data) { - for (const [key, value] of Object.entries(data)) { +export function updateAccount(accountData) { + for (const [key, value] of Object.entries(accountData)) { $i[key] = value; } localStorage.setItem('account', JSON.stringify($i)); @@ -141,7 +141,7 @@ export async function openAccountMenu(opts: { onChoose?: (account: misskey.entities.UserDetailed) => void; }, ev: MouseEvent) { function showSigninDialog() { - popup(import('@/components/signin-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, { done: res => { addAccount(res.id, res.i); success(); @@ -150,7 +150,7 @@ export async function openAccountMenu(opts: { } function createAccount() { - popup(import('@/components/signup-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, { done: res => { addAccount(res.id, res.i); switchAccountWithToken(res.i); diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue index f2cb369802..5114349620 100644 --- a/packages/client/src/components/abuse-report-window.vue +++ b/packages/client/src/components/abuse-report-window.vue @@ -37,7 +37,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const window = ref<InstanceType<typeof XWindow>>(); diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue index b67cef209b..a947406f88 100644 --- a/packages/client/src/components/abuse-report.vue +++ b/packages/client/src/components/abuse-report.vue @@ -2,7 +2,7 @@ <div class="bcekxzvu _card _gap"> <div class="_content target"> <MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/> - <MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId"> + <MkA v-user-preview="report.targetUserId" class="info" :to="userPage(report.targetUser)"> <MkUserName class="name" :user="report.targetUser"/> <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> </MkA> @@ -43,20 +43,20 @@ export default defineComponent({ MkSwitch, }, - emits: ['resolved'], - props: { report: { type: Object, required: true, } - } + }, + + emits: ['resolved'], data() { return { forward: this.report.forwarded, }; - } + }, methods: { acct, diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue index 59b8e97304..18dd1e3f41 100644 --- a/packages/client/src/components/analog-clock.vue +++ b/packages/client/src/components/analog-clock.vue @@ -42,7 +42,7 @@ <script lang="ts" setup> import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; withDefaults(defineProps<{ thickness: number; diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue index adeac4e050..1e4a4506f7 100644 --- a/packages/client/src/components/autocomplete.vue +++ b/packages/client/src/components/autocomplete.vue @@ -131,8 +131,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'done', v: { type: string; value: any }): void; - (e: 'closed'): void; + (event: 'done', value: { type: string; value: any }): void; + (event: 'closed'): void; }>(); const suggests = ref<Element>(); @@ -152,7 +152,7 @@ function complete(type: string, value: any) { emit('closed'); if (type === 'emoji') { let recents = defaultStore.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== value); + recents = recents.filter((emoji: any) => emoji !== value); recents.unshift(value); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); } @@ -232,7 +232,7 @@ function exec() { } else if (props.type === 'emoji') { if (!props.q || props.q === '') { // 最近使った絵文字をサジェスト - emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji === emoji)).filter(x => x) as EmojiDef[]; + emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; return; } @@ -269,17 +269,17 @@ function exec() { } } -function onMousedown(e: Event) { - if (!contains(rootEl.value, e.target) && (rootEl.value !== e.target)) props.close(); +function onMousedown(event: Event) { + if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close(); } -function onKeydown(e: KeyboardEvent) { +function onKeydown(event: KeyboardEvent) { const cancel = () => { - e.preventDefault(); - e.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); }; - switch (e.key) { + switch (event.key) { case 'Enter': if (select.value !== -1) { cancel(); @@ -310,7 +310,7 @@ function onKeydown(e: KeyboardEvent) { break; default: - e.stopPropagation(); + event.stopPropagation(); props.textarea.focus(); } } diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index ccd8880df8..183658471b 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -27,8 +27,7 @@ type CaptchaContainer = { }; declare global { - interface Window extends CaptchaContainer { - } + interface Window extends CaptchaContainer { } } const props = defineProps<{ diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue index 7bbf5ae663..dff02beec0 100644 --- a/packages/client/src/components/channel-follow-button.vue +++ b/packages/client/src/components/channel-follow-button.vue @@ -48,8 +48,8 @@ async function onClick() { }); isFollowing.value = true; } - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); } finally { wait.value = false; } diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index cc1aa9c20a..4e9c4e587a 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -7,8 +7,13 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref, watch, PropType, onUnmounted, shallowRef } from 'vue'; +<script lang="ts" setup> +/* eslint-disable id-denylist -- + Chart.js has a `data` attribute in most chart definitions, which triggers the + id-denylist violation when setting it. This is causing about 60+ lint issues. + As this is part of Chart.js's API it makes sense to disable the check here. +*/ +import { defineProps, onMounted, ref, watch, PropType, onUnmounted } from 'vue'; import { Chart, ArcElement, @@ -29,11 +34,53 @@ import { import 'chartjs-adapter-date-fns'; import { enUS } from 'date-fns/locale'; import zoomPlugin from 'chartjs-plugin-zoom'; -import gradient from 'chartjs-plugin-gradient'; +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114242002 +// We can't use gradient because Vite throws a error. +//import gradient from 'chartjs-plugin-gradient'; import * as os from '@/os'; import { defaultStore } from '@/store'; import MkChartTooltip from '@/components/chart-tooltip.vue'; +const props = defineProps({ + src: { + type: String, + required: true, + }, + args: { + type: Object, + required: false, + }, + limit: { + type: Number, + required: false, + default: 90 + }, + span: { + type: String as PropType<'hour' | 'day'>, + required: true, + }, + detailed: { + type: Boolean, + required: false, + default: false + }, + stacked: { + type: Boolean, + required: false, + default: false + }, + bar: { + type: Boolean, + required: false, + default: false + }, + aspectRatio: { + type: Number, + required: false, + default: null + }, +}); + Chart.register( ArcElement, LineElement, @@ -50,7 +97,7 @@ Chart.register( SubTitle, Filler, zoomPlugin, - gradient, + //gradient, ); const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); @@ -78,826 +125,777 @@ const getColor = (i) => { return colorSets[i % colorSets.length]; }; -export default defineComponent({ - props: { - src: { - type: String, - required: true, - }, - args: { - type: Object, - required: false, - }, - limit: { - type: Number, - required: false, - default: 90 - }, - span: { - type: String as PropType<'hour' | 'day'>, - required: true, - }, - detailed: { - type: Boolean, - required: false, - default: false - }, - stacked: { - type: Boolean, - required: false, - default: false - }, - bar: { - type: Boolean, - required: false, - default: false - }, - aspectRatio: { - type: Number, - required: false, - default: null - }, - }, +const now = new Date(); +let chartInstance: Chart = null; +let chartData: { + series: { + name: string; + type: 'line' | 'area'; + color?: string; + dashed?: boolean; + hidden?: boolean; + data: { + x: number; + y: number; + }[]; + }[]; +} = null; - setup(props) { - const now = new Date(); - let chartInstance: Chart = null; - let data: { - series: { - name: string; - type: 'line' | 'area'; - color?: string; - dashed?: boolean; - hidden?: boolean; - data: { - x: number; - y: number; - }[]; - }[]; - } = null; +const chartEl = ref<HTMLCanvasElement>(null); +const fetching = ref(true); - const chartEl = ref<HTMLCanvasElement>(null); - const fetching = ref(true); +const getDate = (ago: number) => { + const y = now.getFullYear(); + const m = now.getMonth(); + const d = now.getDate(); + const h = now.getHours(); - const getDate = (ago: number) => { - const y = now.getFullYear(); - const m = now.getMonth(); - const d = now.getDate(); - const h = now.getHours(); - - return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago); - }; + return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago); +}; - const format = (arr) => { - return arr.map((v, i) => ({ - x: getDate(i).getTime(), - y: v - })); - }; +const format = (arr) => { + return arr.map((v, i) => ({ + x: getDate(i).getTime(), + y: v + })); +}; - const tooltipShowing = ref(false); - const tooltipX = ref(0); - const tooltipY = ref(0); - const tooltipTitle = ref(null); - const tooltipSeries = ref(null); - let disposeTooltipComponent; +const tooltipShowing = ref(false); +const tooltipX = ref(0); +const tooltipY = ref(0); +const tooltipTitle = ref(null); +const tooltipSeries = ref(null); +let disposeTooltipComponent; - os.popup(MkChartTooltip, { - showing: tooltipShowing, - x: tooltipX, - y: tooltipY, - title: tooltipTitle, - series: tooltipSeries, - }, {}).then(({ dispose }) => { - disposeTooltipComponent = dispose; - }); +os.popup(MkChartTooltip, { + showing: tooltipShowing, + x: tooltipX, + y: tooltipY, + title: tooltipTitle, + series: tooltipSeries, +}, {}).then(({ dispose }) => { + disposeTooltipComponent = dispose; +}); - function externalTooltipHandler(context) { - if (context.tooltip.opacity === 0) { - tooltipShowing.value = false; - return; - } +function externalTooltipHandler(context) { + if (context.tooltip.opacity === 0) { + tooltipShowing.value = false; + return; + } - tooltipTitle.value = context.tooltip.title[0]; - tooltipSeries.value = context.tooltip.body.map((b, i) => ({ - backgroundColor: context.tooltip.labelColors[i].backgroundColor, - borderColor: context.tooltip.labelColors[i].borderColor, - text: b.lines[0], - })); + tooltipTitle.value = context.tooltip.title[0]; + tooltipSeries.value = context.tooltip.body.map((b, i) => ({ + backgroundColor: context.tooltip.labelColors[i].backgroundColor, + borderColor: context.tooltip.labelColors[i].borderColor, + text: b.lines[0], + })); - const rect = context.chart.canvas.getBoundingClientRect(); + const rect = context.chart.canvas.getBoundingClientRect(); - tooltipShowing.value = true; - tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX; - tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY; - } + tooltipShowing.value = true; + tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX; + tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY; +} - const render = () => { - if (chartInstance) { - chartInstance.destroy(); - } +const render = () => { + if (chartInstance) { + chartInstance.destroy(); + } - const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; - const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; + const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; + const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - // フォントカラー - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); + // フォントカラー + Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - const maxes = data.series.map((x, i) => Math.max(...x.data.map(d => d.y))); + const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y))); - chartInstance = new Chart(chartEl.value, { - type: props.bar ? 'bar' : 'line', - data: { - labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(), - datasets: data.series.map((x, i) => ({ - parsing: false, - label: x.name, - data: x.data.slice().reverse(), - tension: 0.3, - pointRadius: 0, - borderWidth: props.bar ? 0 : 2, - borderColor: x.color ? x.color : getColor(i), - borderDash: x.dashed ? [5, 5] : [], - borderJoinStyle: 'round', - borderRadius: props.bar ? 3 : undefined, - backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), - gradient: props.bar ? undefined : { - backgroundColor: { - axis: 'y', - colors: { - 0: alpha(x.color ? x.color : getColor(i), 0), - [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2), - }, - }, + chartInstance = new Chart(chartEl.value, { + type: props.bar ? 'bar' : 'line', + data: { + labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(), + datasets: chartData.series.map((x, i) => ({ + parsing: false, + label: x.name, + data: x.data.slice().reverse(), + tension: 0.3, + pointRadius: 0, + borderWidth: props.bar ? 0 : 2, + borderColor: x.color ? x.color : getColor(i), + borderDash: x.dashed ? [5, 5] : [], + borderJoinStyle: 'round', + borderRadius: props.bar ? 3 : undefined, + backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), + /*gradient: props.bar ? undefined : { + backgroundColor: { + axis: 'y', + colors: { + 0: alpha(x.color ? x.color : getColor(i), 0), + [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2), }, - barPercentage: 0.9, - categoryPercentage: 0.9, - fill: x.type === 'area', - clip: 8, - hidden: !!x.hidden, - })), + }, + },*/ + barPercentage: 0.9, + categoryPercentage: 0.9, + fill: x.type === 'area', + clip: 8, + hidden: !!x.hidden, + })), + }, + options: { + aspectRatio: props.aspectRatio || 2.5, + layout: { + padding: { + left: 0, + right: 8, + top: 0, + bottom: 0, }, - options: { - aspectRatio: props.aspectRatio || 2.5, - layout: { - padding: { - left: 0, - right: 8, - top: 0, - bottom: 0, - }, + }, + scales: { + x: { + type: 'time', + stacked: props.stacked, + offset: false, + time: { + stepSize: 1, + unit: props.span === 'day' ? 'month' : 'day', }, - scales: { - x: { - type: 'time', - stacked: props.stacked, - offset: false, - time: { - stepSize: 1, - unit: props.span === 'day' ? 'month' : 'day', - }, - grid: { - color: gridColor, - borderColor: 'rgb(0, 0, 0, 0)', - }, - ticks: { - display: props.detailed, - maxRotation: 0, - autoSkipPadding: 16, - }, - adapters: { - date: { - locale: enUS, - }, - }, - min: getDate(props.limit).getTime(), - }, - y: { - position: 'left', - stacked: props.stacked, - suggestedMax: 50, - grid: { - color: gridColor, - borderColor: 'rgb(0, 0, 0, 0)', - }, - ticks: { - display: props.detailed, - //mirror: true, - }, - }, + grid: { + color: gridColor, + borderColor: 'rgb(0, 0, 0, 0)', }, - interaction: { - intersect: false, - mode: 'index', + ticks: { + display: props.detailed, + maxRotation: 0, + autoSkipPadding: 16, }, - elements: { - point: { - hoverRadius: 5, - hoverBorderWidth: 2, + adapters: { + date: { + locale: enUS, }, }, - animation: false, - plugins: { - legend: { - display: props.detailed, - position: 'bottom', - labels: { - boxWidth: 16, - }, + min: getDate(props.limit).getTime(), + }, + y: { + position: 'left', + stacked: props.stacked, + suggestedMax: 50, + grid: { + color: gridColor, + borderColor: 'rgb(0, 0, 0, 0)', + }, + ticks: { + display: props.detailed, + //mirror: true, + }, + }, + }, + interaction: { + intersect: false, + mode: 'index', + }, + elements: { + point: { + hoverRadius: 5, + hoverBorderWidth: 2, + }, + }, + animation: false, + plugins: { + legend: { + display: props.detailed, + position: 'bottom', + labels: { + boxWidth: 16, + }, + }, + tooltip: { + enabled: false, + mode: 'index', + animation: { + duration: 0, + }, + external: externalTooltipHandler, + }, + zoom: props.detailed ? { + pan: { + enabled: true, + }, + zoom: { + wheel: { + enabled: true, }, - tooltip: { + pinch: { + enabled: true, + }, + drag: { enabled: false, - mode: 'index', - animation: { - duration: 0, - }, - external: externalTooltipHandler, }, - zoom: props.detailed ? { - pan: { - enabled: true, - }, - zoom: { - wheel: { - enabled: true, - }, - pinch: { - enabled: true, - }, - drag: { - enabled: false, - }, - mode: 'x', - }, - limits: { - x: { - min: 'original', - max: 'original', - }, - y: { - min: 'original', - max: 'original', - }, - } - } : undefined, - gradient, + mode: 'x', }, - }, - plugins: [{ - id: 'vLine', - beforeDraw(chart, args, options) { - if (chart.tooltip._active && chart.tooltip._active.length) { - const activePoint = chart.tooltip._active[0]; - const ctx = chart.ctx; - const x = activePoint.element.x; - const topY = chart.scales.y.top; - const bottomY = chart.scales.y.bottom; - - ctx.save(); - ctx.beginPath(); - ctx.moveTo(x, bottomY); - ctx.lineTo(x, topY); - ctx.lineWidth = 1; - ctx.strokeStyle = vLineColor; - ctx.stroke(); - ctx.restore(); - } + limits: { + x: { + min: 'original', + max: 'original', + }, + y: { + min: 'original', + max: 'original', + }, } - }] - }); - }; + } : undefined, + //gradient, + }, + }, + plugins: [{ + id: 'vLine', + beforeDraw(chart, args, options) { + if (chart.tooltip._active && chart.tooltip._active.length) { + const activePoint = chart.tooltip._active[0]; + const ctx = chart.ctx; + const x = activePoint.element.x; + const topY = chart.scales.y.top; + const bottomY = chart.scales.y.bottom; - const exportData = () => { - // TODO - }; + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x, bottomY); + ctx.lineTo(x, topY); + ctx.lineWidth = 1; + ctx.strokeStyle = vLineColor; + ctx.stroke(); + ctx.restore(); + } + } + }] + }); +}; - const fetchFederationChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Received', - type: 'area', - data: format(raw.inboxInstances), - color: colors.blue, - }, { - name: 'Delivered', - type: 'area', - data: format(raw.deliveredInstances), - color: colors.green, - }, { - name: 'Stalled', - type: 'area', - data: format(raw.stalled), - color: colors.red, - }, { - name: 'Pub Active', - type: 'line', - data: format(raw.pubActive), - color: colors.purple, - }, { - name: 'Sub Active', - type: 'line', - data: format(raw.subActive), - color: colors.orange, - }, { - name: 'Pub & Sub', - type: 'line', - data: format(raw.pubsub), - dashed: true, - color: colors.cyan, - }, { - name: 'Pub', - type: 'line', - data: format(raw.pub), - dashed: true, - color: colors.purple, - }, { - name: 'Sub', - type: 'line', - data: format(raw.sub), - dashed: true, - color: colors.orange, - }], - }; - }; +const exportData = () => { + // TODO +}; - const fetchApRequestChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'In', - type: 'area', - color: '#008FFB', - data: format(raw.inboxReceived) - }, { - name: 'Out (succ)', - type: 'area', - color: '#00E396', - data: format(raw.deliverSucceeded) - }, { - name: 'Out (fail)', - type: 'area', - color: '#FEB019', - data: format(raw.deliverFailed) - }] - }; - }; +const fetchFederationChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Received', + type: 'area', + data: format(raw.inboxInstances), + color: colors.blue, + }, { + name: 'Delivered', + type: 'area', + data: format(raw.deliveredInstances), + color: colors.green, + }, { + name: 'Stalled', + type: 'area', + data: format(raw.stalled), + color: colors.red, + }, { + name: 'Pub Active', + type: 'line', + data: format(raw.pubActive), + color: colors.purple, + }, { + name: 'Sub Active', + type: 'line', + data: format(raw.subActive), + color: colors.orange, + }, { + name: 'Pub & Sub', + type: 'line', + data: format(raw.pubsub), + dashed: true, + color: colors.cyan, + }, { + name: 'Pub', + type: 'line', + data: format(raw.pub), + dashed: true, + color: colors.purple, + }, { + name: 'Sub', + type: 'line', + data: format(raw.sub), + dashed: true, + color: colors.orange, + }], + }; +}; - const fetchNotesChart = async (type: string): Promise<typeof data> => { - const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'All', - type: 'line', - data: format(type == 'combined' - ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) - : sum(raw[type].inc, negate(raw[type].dec)) - ), - color: '#888888', - }, { - name: 'Renotes', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.renote, raw.remote.diffs.renote) - : raw[type].diffs.renote - ), - color: colors.green, - }, { - name: 'Replies', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.reply, raw.remote.diffs.reply) - : raw[type].diffs.reply - ), - color: colors.yellow, - }, { - name: 'Normal', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.normal, raw.remote.diffs.normal) - : raw[type].diffs.normal - ), - color: colors.blue, - }, { - name: 'With file', - type: 'area', - data: format(type == 'combined' - ? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile) - : raw[type].diffs.withFile - ), - color: colors.purple, - }], - }; - }; +const fetchApRequestChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'In', + type: 'area', + color: '#008FFB', + data: format(raw.inboxReceived) + }, { + name: 'Out (succ)', + type: 'area', + color: '#00E396', + data: format(raw.deliverSucceeded) + }, { + name: 'Out (fail)', + type: 'area', + color: '#FEB019', + data: format(raw.deliverFailed) + }] + }; +}; - const fetchNotesTotalChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Combined', - type: 'line', - data: format(sum(raw.local.total, raw.remote.total)), - }, { - name: 'Local', - type: 'area', - data: format(raw.local.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.total), - }], - }; - }; +const fetchNotesChart = async (type: string): Promise<typeof chartData> => { + const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'All', + type: 'line', + data: format(type === 'combined' + ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) + : sum(raw[type].inc, negate(raw[type].dec)) + ), + color: '#888888', + }, { + name: 'Renotes', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.renote, raw.remote.diffs.renote) + : raw[type].diffs.renote + ), + color: colors.green, + }, { + name: 'Replies', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.reply, raw.remote.diffs.reply) + : raw[type].diffs.reply + ), + color: colors.yellow, + }, { + name: 'Normal', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.normal, raw.remote.diffs.normal) + : raw[type].diffs.normal + ), + color: colors.blue, + }, { + name: 'With file', + type: 'area', + data: format(type === 'combined' + ? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile) + : raw[type].diffs.withFile + ), + color: colors.purple, + }], + }; +}; - const fetchUsersChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/users', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Combined', - type: 'line', - data: format(total - ? sum(raw.local.total, raw.remote.total) - : sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) - ), - }, { - name: 'Local', - type: 'area', - data: format(total - ? raw.local.total - : sum(raw.local.inc, negate(raw.local.dec)) - ), - }, { - name: 'Remote', - type: 'area', - data: format(total - ? raw.remote.total - : sum(raw.remote.inc, negate(raw.remote.dec)) - ), - }], - }; - }; +const fetchNotesTotalChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/notes', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Combined', + type: 'line', + data: format(sum(raw.local.total, raw.remote.total)), + }, { + name: 'Local', + type: 'area', + data: format(raw.local.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.total), + }], + }; +}; - const fetchActiveUsersChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Read & Write', - type: 'area', - data: format(raw.readWrite), - color: colors.orange, - }, { - name: 'Write', - type: 'area', - data: format(raw.write), - color: colors.lime, - }, { - name: 'Read', - type: 'area', - data: format(raw.read), - color: colors.blue, - }, { - name: '< Week', - type: 'area', - data: format(raw.registeredWithinWeek), - color: colors.green, - }, { - name: '< Month', - type: 'area', - data: format(raw.registeredWithinMonth), - color: colors.yellow, - }, { - name: '< Year', - type: 'area', - data: format(raw.registeredWithinYear), - color: colors.red, - }, { - name: '> Week', - type: 'area', - data: format(raw.registeredOutsideWeek), - color: colors.yellow, - }, { - name: '> Month', - type: 'area', - data: format(raw.registeredOutsideMonth), - color: colors.red, - }, { - name: '> Year', - type: 'area', - data: format(raw.registeredOutsideYear), - color: colors.purple, - }], - }; - }; +const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/users', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Combined', + type: 'line', + data: format(total + ? sum(raw.local.total, raw.remote.total) + : sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) + ), + }, { + name: 'Local', + type: 'area', + data: format(total + ? raw.local.total + : sum(raw.local.inc, negate(raw.local.dec)) + ), + }, { + name: 'Remote', + type: 'area', + data: format(total + ? raw.remote.total + : sum(raw.remote.inc, negate(raw.remote.dec)) + ), + }], + }; +}; - const fetchDriveChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); - return { - bytes: true, - series: [{ - name: 'All', - type: 'line', - dashed: true, - data: format( - sum( - raw.local.incSize, - negate(raw.local.decSize), - raw.remote.incSize, - negate(raw.remote.decSize) - ) - ), - }, { - name: 'Local +', - type: 'area', - data: format(raw.local.incSize), - }, { - name: 'Local -', - type: 'area', - data: format(negate(raw.local.decSize)), - }, { - name: 'Remote +', - type: 'area', - data: format(raw.remote.incSize), - }, { - name: 'Remote -', - type: 'area', - data: format(negate(raw.remote.decSize)), - }], - }; - }; +const fetchActiveUsersChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Read & Write', + type: 'area', + data: format(raw.readWrite), + color: colors.orange, + }, { + name: 'Write', + type: 'area', + data: format(raw.write), + color: colors.lime, + }, { + name: 'Read', + type: 'area', + data: format(raw.read), + color: colors.blue, + }, { + name: '< Week', + type: 'area', + data: format(raw.registeredWithinWeek), + color: colors.green, + }, { + name: '< Month', + type: 'area', + data: format(raw.registeredWithinMonth), + color: colors.yellow, + }, { + name: '< Year', + type: 'area', + data: format(raw.registeredWithinYear), + color: colors.red, + }, { + name: '> Week', + type: 'area', + data: format(raw.registeredOutsideWeek), + color: colors.yellow, + }, { + name: '> Month', + type: 'area', + data: format(raw.registeredOutsideMonth), + color: colors.red, + }, { + name: '> Year', + type: 'area', + data: format(raw.registeredOutsideYear), + color: colors.purple, + }], + }; +}; - const fetchDriveFilesChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); - return { - series: [{ - name: 'All', - type: 'line', - dashed: true, - data: format( - sum( - raw.local.incCount, - negate(raw.local.decCount), - raw.remote.incCount, - negate(raw.remote.decCount) - ) - ), - }, { - name: 'Local +', - type: 'area', - data: format(raw.local.incCount), - }, { - name: 'Local -', - type: 'area', - data: format(negate(raw.local.decCount)), - }, { - name: 'Remote +', - type: 'area', - data: format(raw.remote.incCount), - }, { - name: 'Remote -', - type: 'area', - data: format(negate(raw.remote.decCount)), - }], - }; - }; +const fetchDriveChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); + return { + bytes: true, + series: [{ + name: 'All', + type: 'line', + dashed: true, + data: format( + sum( + raw.local.incSize, + negate(raw.local.decSize), + raw.remote.incSize, + negate(raw.remote.decSize) + ) + ), + }, { + name: 'Local +', + type: 'area', + data: format(raw.local.incSize), + }, { + name: 'Local -', + type: 'area', + data: format(negate(raw.local.decSize)), + }, { + name: 'Remote +', + type: 'area', + data: format(raw.remote.incSize), + }, { + name: 'Remote -', + type: 'area', + data: format(negate(raw.remote.decSize)), + }], + }; +}; - const fetchInstanceRequestsChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'In', - type: 'area', - color: '#008FFB', - data: format(raw.requests.received) - }, { - name: 'Out (succ)', - type: 'area', - color: '#00E396', - data: format(raw.requests.succeeded) - }, { - name: 'Out (fail)', - type: 'area', - color: '#FEB019', - data: format(raw.requests.failed) - }] - }; - }; +const fetchDriveFilesChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/drive', { limit: props.limit, span: props.span }); + return { + series: [{ + name: 'All', + type: 'line', + dashed: true, + data: format( + sum( + raw.local.incCount, + negate(raw.local.decCount), + raw.remote.incCount, + negate(raw.remote.decCount) + ) + ), + }, { + name: 'Local +', + type: 'area', + data: format(raw.local.incCount), + }, { + name: 'Local -', + type: 'area', + data: format(negate(raw.local.decCount)), + }, { + name: 'Remote +', + type: 'area', + data: format(raw.remote.incCount), + }, { + name: 'Remote -', + type: 'area', + data: format(negate(raw.remote.decCount)), + }], + }; +}; - const fetchInstanceUsersChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Users', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.users.total - : sum(raw.users.inc, negate(raw.users.dec)) - ) - }] - }; - }; +const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'In', + type: 'area', + color: '#008FFB', + data: format(raw.requests.received) + }, { + name: 'Out (succ)', + type: 'area', + color: '#00E396', + data: format(raw.requests.succeeded) + }, { + name: 'Out (fail)', + type: 'area', + color: '#FEB019', + data: format(raw.requests.failed) + }] + }; +}; - const fetchInstanceNotesChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Notes', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.notes.total - : sum(raw.notes.inc, negate(raw.notes.dec)) - ) - }] - }; - }; +const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Users', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.users.total + : sum(raw.users.inc, negate(raw.users.dec)) + ) + }] + }; +}; - const fetchInstanceFfChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Following', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.following.total - : sum(raw.following.inc, negate(raw.following.dec)) - ) - }, { - name: 'Followers', - type: 'area', - color: '#00E396', - data: format(total - ? raw.followers.total - : sum(raw.followers.inc, negate(raw.followers.dec)) - ) - }] - }; - }; +const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Notes', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.notes.total + : sum(raw.notes.inc, negate(raw.notes.dec)) + ) + }] + }; +}; - const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - bytes: true, - series: [{ - name: 'Drive usage', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.drive.totalUsage - : sum(raw.drive.incUsage, negate(raw.drive.decUsage)) - ) - }] - }; - }; +const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Following', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.following.total + : sum(raw.following.inc, negate(raw.following.dec)) + ) + }, { + name: 'Followers', + type: 'area', + color: '#00E396', + data: format(total + ? raw.followers.total + : sum(raw.followers.inc, negate(raw.followers.dec)) + ) + }] + }; +}; - const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof data> => { - const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Drive files', - type: 'area', - color: '#008FFB', - data: format(total - ? raw.drive.totalFiles - : sum(raw.drive.incFiles, negate(raw.drive.decFiles)) - ) - }] - }; - }; +const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + bytes: true, + series: [{ + name: 'Drive usage', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.drive.totalUsage + : sum(raw.drive.incUsage, negate(raw.drive.decUsage)) + ) + }] + }; +}; - const fetchPerUserNotesChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [...(props.args.withoutAll ? [] : [{ - name: 'All', - type: 'line', - data: format(sum(raw.inc, negate(raw.dec))), - color: '#888888', - }]), { - name: 'With file', - type: 'area', - data: format(raw.diffs.withFile), - color: colors.purple, - }, { - name: 'Renotes', - type: 'area', - data: format(raw.diffs.renote), - color: colors.green, - }, { - name: 'Replies', - type: 'area', - data: format(raw.diffs.reply), - color: colors.yellow, - }, { - name: 'Normal', - type: 'area', - data: format(raw.diffs.normal), - color: colors.blue, - }], - }; - }; +const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => { + const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Drive files', + type: 'area', + color: '#008FFB', + data: format(total + ? raw.drive.totalFiles + : sum(raw.drive.incFiles, negate(raw.drive.decFiles)) + ) + }] + }; +}; - const fetchPerUserFollowingChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Local', - type: 'area', - data: format(raw.local.followings.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.followings.total), - }], - }; - }; +const fetchPerUserNotesChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [...(props.args.withoutAll ? [] : [{ + name: 'All', + type: 'line', + data: format(sum(raw.inc, negate(raw.dec))), + color: '#888888', + }]), { + name: 'With file', + type: 'area', + data: format(raw.diffs.withFile), + color: colors.purple, + }, { + name: 'Renotes', + type: 'area', + data: format(raw.diffs.renote), + color: colors.green, + }, { + name: 'Replies', + type: 'area', + data: format(raw.diffs.reply), + color: colors.yellow, + }, { + name: 'Normal', + type: 'area', + data: format(raw.diffs.normal), + color: colors.blue, + }], + }; +}; - const fetchPerUserFollowersChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Local', - type: 'area', - data: format(raw.local.followers.total), - }, { - name: 'Remote', - type: 'area', - data: format(raw.remote.followers.total), - }], - }; - }; +const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followings.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followings.total), + }], + }; +}; - const fetchPerUserDriveChart = async (): Promise<typeof data> => { - const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); - return { - series: [{ - name: 'Inc', - type: 'area', - data: format(raw.incSize), - }, { - name: 'Dec', - type: 'area', - data: format(raw.decSize), - }], - }; - }; +const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Local', + type: 'area', + data: format(raw.local.followers.total), + }, { + name: 'Remote', + type: 'area', + data: format(raw.remote.followers.total), + }], + }; +}; - const fetchAndRender = async () => { - const fetchData = () => { - switch (props.src) { - case 'federation': return fetchFederationChart(); - case 'ap-request': return fetchApRequestChart(); - case 'users': return fetchUsersChart(false); - case 'users-total': return fetchUsersChart(true); - case 'active-users': return fetchActiveUsersChart(); - case 'notes': return fetchNotesChart('combined'); - case 'local-notes': return fetchNotesChart('local'); - case 'remote-notes': return fetchNotesChart('remote'); - case 'notes-total': return fetchNotesTotalChart(); - case 'drive': return fetchDriveChart(); - case 'drive-files': return fetchDriveFilesChart(); - - case 'instance-requests': return fetchInstanceRequestsChart(); - case 'instance-users': return fetchInstanceUsersChart(false); - case 'instance-users-total': return fetchInstanceUsersChart(true); - case 'instance-notes': return fetchInstanceNotesChart(false); - case 'instance-notes-total': return fetchInstanceNotesChart(true); - case 'instance-ff': return fetchInstanceFfChart(false); - case 'instance-ff-total': return fetchInstanceFfChart(true); - case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false); - case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true); - case 'instance-drive-files': return fetchInstanceDriveFilesChart(false); - case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); +const fetchPerUserDriveChart = async (): Promise<typeof chartData> => { + const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span }); + return { + series: [{ + name: 'Inc', + type: 'area', + data: format(raw.incSize), + }, { + name: 'Dec', + type: 'area', + data: format(raw.decSize), + }], + }; +}; - case 'per-user-notes': return fetchPerUserNotesChart(); - case 'per-user-following': return fetchPerUserFollowingChart(); - case 'per-user-followers': return fetchPerUserFollowersChart(); - case 'per-user-drive': return fetchPerUserDriveChart(); - } - }; - fetching.value = true; - data = await fetchData(); - fetching.value = false; - render(); - }; +const fetchAndRender = async () => { + const fetchData = () => { + switch (props.src) { + case 'federation': return fetchFederationChart(); + case 'ap-request': return fetchApRequestChart(); + case 'users': return fetchUsersChart(false); + case 'users-total': return fetchUsersChart(true); + case 'active-users': return fetchActiveUsersChart(); + case 'notes': return fetchNotesChart('combined'); + case 'local-notes': return fetchNotesChart('local'); + case 'remote-notes': return fetchNotesChart('remote'); + case 'notes-total': return fetchNotesTotalChart(); + case 'drive': return fetchDriveChart(); + case 'drive-files': return fetchDriveFilesChart(); + case 'instance-requests': return fetchInstanceRequestsChart(); + case 'instance-users': return fetchInstanceUsersChart(false); + case 'instance-users-total': return fetchInstanceUsersChart(true); + case 'instance-notes': return fetchInstanceNotesChart(false); + case 'instance-notes-total': return fetchInstanceNotesChart(true); + case 'instance-ff': return fetchInstanceFfChart(false); + case 'instance-ff-total': return fetchInstanceFfChart(true); + case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false); + case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true); + case 'instance-drive-files': return fetchInstanceDriveFilesChart(false); + case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true); - watch(() => [props.src, props.span], fetchAndRender); + case 'per-user-notes': return fetchPerUserNotesChart(); + case 'per-user-following': return fetchPerUserFollowingChart(); + case 'per-user-followers': return fetchPerUserFollowersChart(); + case 'per-user-drive': return fetchPerUserDriveChart(); + } + }; + fetching.value = true; + chartData = await fetchData(); + fetching.value = false; + render(); +}; - onMounted(() => { - fetchAndRender(); - }); +watch(() => [props.src, props.span], fetchAndRender); - onUnmounted(() => { - if (disposeTooltipComponent) disposeTooltipComponent(); - }); +onMounted(() => { + fetchAndRender(); +}); - return { - chartEl, - fetching, - }; - }, +onUnmounted(() => { + if (disposeTooltipComponent) disposeTooltipComponent(); }); +/* eslint-enable id-denylist */ </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue new file mode 100644 index 0000000000..a8bde6ea05 --- /dev/null +++ b/packages/client/src/components/cropper-dialog.vue @@ -0,0 +1,175 @@ +<template> +<XModalWindow + ref="dialogEl" + :width="800" + :height="500" + :scroll="false" + :with-ok-button="true" + @close="cancel()" + @ok="ok()" + @closed="$emit('closed')" +> + <template #header>{{ $ts.cropImage }}</template> + <template #default="{ width, height }"> + <div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`"> + <Transition name="fade"> + <div v-if="loading" class="loading"> + <MkLoading/> + </div> + </Transition> + <div class="container"> + <img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad"> + </div> + </div> + </template> +</XModalWindow> +</template> + +<script lang="ts" setup> +import { nextTick, onMounted } from 'vue'; +import * as misskey from 'misskey-js'; +import Cropper from 'cropperjs'; +import tinycolor from 'tinycolor2'; +import XModalWindow from '@/components/ui/modal-window.vue'; +import * as os from '@/os'; +import { $i } from '@/account'; +import { defaultStore } from '@/store'; +import { apiUrl, url } from '@/config'; +import { query } from '@/scripts/url'; + +const emit = defineEmits<{ + (ev: 'ok', cropped: misskey.entities.DriveFile): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; +}>(); + +const props = defineProps<{ + file: misskey.entities.DriveFile; + aspectRatio: number; +}>(); + +const imgUrl = `${url}/proxy/image.webp?${query({ + url: props.file.url, +})}`; +let dialogEl = $ref<InstanceType<typeof XModalWindow>>(); +let imgEl = $ref<HTMLImageElement>(); +let cropper: Cropper | null = null; +let loading = $ref(true); + +const ok = async () => { + const promise = new Promise<misskey.entities.DriveFile>(async (res) => { + const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas(); + croppedCanvas.toBlob(blob => { + const formData = new FormData(); + formData.append('file', blob); + formData.append('i', $i.token); + if (defaultStore.state.uploadFolder) { + formData.append('folderId', defaultStore.state.uploadFolder); + } + + fetch(apiUrl + '/drive/files/create', { + method: 'POST', + body: formData, + }) + .then(response => response.json()) + .then(f => { + res(f); + }); + }); + }); + + os.promiseDialog(promise); + + const f = await promise; + + emit('ok', f); + dialogEl.close(); +}; + +const cancel = () => { + emit('cancel'); + dialogEl.close(); +}; + +const onImageLoad = () => { + loading = false; + + if (cropper) { + cropper.getCropperImage()!.$center('contain'); + cropper.getCropperSelection()!.$center(); + } +}; + +onMounted(() => { + cropper = new Cropper(imgEl, { + }); + + const computedStyle = getComputedStyle(document.documentElement); + + const selection = cropper.getCropperSelection()!; + selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + selection.aspectRatio = props.aspectRatio; + selection.initialAspectRatio = props.aspectRatio; + selection.outlined = true; + + window.setTimeout(() => { + cropper.getCropperImage()!.$center('contain'); + selection.$center(); + }, 100); + + // モーダルオープンアニメーションが終わったあとで再度調整 + window.setTimeout(() => { + cropper.getCropperImage()!.$center('contain'); + selection.$center(); + }, 500); +}); +</script> + +<style lang="scss" scoped> +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.5s ease 0.5s; +} +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +.mk-cropper-dialog { + display: flex; + flex-direction: column; + width: var(--vw); + height: var(--vh); + position: relative; + + > .loading { + position: absolute; + z-index: 10; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + -webkit-backdrop-filter: var(--blur, blur(10px)); + backdrop-filter: var(--blur, blur(10px)); + background: rgba(0, 0, 0, 0.5); + } + + > .container { + flex: 1; + width: 100%; + height: 100%; + + > ::v-deep(cropper-canvas) { + width: 100%; + height: 100%; + + > cropper-selection > cropper-handle[action="move"] { + background: transparent; + } + } + } +} +</style> diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue index e7c9aabe4e..dd906f9bf3 100644 --- a/packages/client/src/components/cw-button.vue +++ b/packages/client/src/components/cw-button.vue @@ -18,7 +18,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'update:modelValue', v: boolean): void; + (ev: 'update:modelValue', v: boolean): void; }>(); const label = computed(() => { diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue index 3e106a4f0c..b090f3cb4e 100644 --- a/packages/client/src/components/dialog.vue +++ b/packages/client/src/components/dialog.vue @@ -90,8 +90,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done', v: { canceled: boolean; result: any }): void; - (e: 'closed'): void; + (ev: 'done', v: { canceled: boolean; result: any }): void; + (ev: 'closed'): void; }>(); const modal = ref<InstanceType<typeof MkModal>>(); @@ -122,14 +122,14 @@ function onBgClick() { if (props.cancelableByBgClick) cancel(); } */ -function onKeydown(e: KeyboardEvent) { - if (e.key === 'Escape') cancel(); +function onKeydown(evt: KeyboardEvent) { + if (evt.key === 'Escape') cancel(); } -function onInputKeydown(e: KeyboardEvent) { - if (e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); +function onInputKeydown(evt: KeyboardEvent) { + if (evt.key === 'Enter') { + evt.preventDefault(); + evt.stopPropagation(); ok(); } } diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index 81b80e7e8e..dd24440e82 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -42,7 +42,7 @@ const is = computed(() => { "application/x-tar", "application/gzip", "application/x-7z-compressed" - ].some(e => e === props.file.type)) return 'archive'; + ].some(archiveType => archiveType === props.file.type)) return 'archive'; return 'unknown'; }); diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue index f6c59457d1..03974559d2 100644 --- a/packages/client/src/components/drive-select-dialog.vue +++ b/packages/client/src/components/drive-select-dialog.vue @@ -33,8 +33,8 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done', r?: Misskey.entities.DriveFile[]): void; - (e: 'closed'): void; + (ev: 'done', r?: Misskey.entities.DriveFile[]): void; + (ev: 'closed'): void; }>(); const dialog = ref<InstanceType<typeof XModalWindow>>(); diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue index d08c5fb674..5bbfca83c9 100644 --- a/packages/client/src/components/drive-window.vue +++ b/packages/client/src/components/drive-window.vue @@ -24,6 +24,6 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); </script> diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue index 262eae0de1..aaf7ca3ca3 100644 --- a/packages/client/src/components/drive.file.vue +++ b/packages/client/src/components/drive.file.vue @@ -31,7 +31,7 @@ </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; @@ -50,9 +50,9 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', r: Misskey.entities.DriveFile): void; - (e: 'dragstart'): void; - (e: 'dragend'): void; + (ev: 'chosen', r: Misskey.entities.DriveFile): void; + (ev: 'dragstart'): void; + (ev: 'dragend'): void; }>(); const isDragging = ref(false); @@ -99,14 +99,14 @@ function onClick(ev: MouseEvent) { } } -function onContextmenu(e: MouseEvent) { - os.contextMenu(getMenu(), e); +function onContextmenu(ev: MouseEvent) { + os.contextMenu(getMenu(), ev); } -function onDragstart(e: DragEvent) { - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); +function onDragstart(ev: DragEvent) { + if (ev.dataTransfer) { + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file)); } isDragging.value = true; @@ -133,11 +133,11 @@ function rename() { } function describe() { - os.popup(import('@/components/media-caption.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), { title: i18n.ts.describeFile, input: { placeholder: i18n.ts.inputNewDescription, - default: props.file.comment !== null ? props.file.comment : '', + default: props.file.comment != null ? props.file.comment : '', }, image: props.file }, { @@ -146,7 +146,7 @@ function describe() { let comment = result.result; os.api('drive/files/update', { fileId: props.file.id, - comment: comment.length == 0 ? null : comment + comment: comment.length === 0 ? null : comment }); } }, 'closed'); diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue index 57621bf097..3ccb5d6219 100644 --- a/packages/client/src/components/drive.folder.vue +++ b/packages/client/src/components/drive.folder.vue @@ -27,7 +27,7 @@ </template> <script lang="ts" setup> -import { computed, ref } from 'vue'; +import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os'; import { i18n } from '@/i18n'; @@ -71,7 +71,7 @@ function onMouseover() { } function onMouseout() { - hover.value = false + hover.value = false; } function onDragover(ev: DragEvent) { @@ -84,12 +84,12 @@ function onDragover(ev: DragEvent) { return; } - const isFile = ev.dataTransfer.items[0].kind == 'file'; - const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { ev.dataTransfer.dropEffect = 'none'; } @@ -118,7 +118,7 @@ function onDrop(ev: DragEvent) { //#region ドライブのファイル const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); emit('removeFile', file.id); os.api('drive/files/update', { @@ -130,11 +130,11 @@ function onDrop(ev: DragEvent) { //#region ドライブのフォルダ const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + if (driveFolder != null && driveFolder !== '') { const folder = JSON.parse(driveFolder); // 移動先が自分自身ならreject - if (folder.id == props.folder.id) return; + if (folder.id === props.folder.id) return; emit('removeFolder', folder.id); os.api('drive/folders/update', { @@ -204,7 +204,7 @@ function deleteFolder() { defaultStore.set('uploadFolder', null); } }).catch(err => { - switch(err.id) { + switch (err.id) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', @@ -230,7 +230,7 @@ function onContextmenu(ev: MouseEvent) { text: i18n.ts.openInWindow, icon: 'fas fa-window-restore', action: () => { - os.popup(import('./drive-window.vue'), { + os.popup(defineAsyncComponent(() => import('./drive-window.vue')), { initialFolder: props.folder }, { }, 'closed'); diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue index 67223267c1..5482703317 100644 --- a/packages/client/src/components/drive.nav-folder.vue +++ b/packages/client/src/components/drive.nav-folder.vue @@ -24,10 +24,10 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'move', v?: Misskey.entities.DriveFolder): void; - (e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; - (e: 'removeFile', v: Misskey.entities.DriveFile['id']): void; - (e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; + (ev: 'move', v?: Misskey.entities.DriveFolder): void; + (ev: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void; + (ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void; + (ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void; }>(); const hover = ref(false); @@ -45,22 +45,22 @@ function onMouseout() { hover.value = false; } -function onDragover(e: DragEvent) { - if (!e.dataTransfer) return; +function onDragover(ev: DragEvent) { + if (!ev.dataTransfer) return; // このフォルダがルートかつカレントディレクトリならドロップ禁止 if (props.folder == null && props.parentFolder == null) { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } return false; @@ -74,22 +74,22 @@ function onDragleave() { if (props.folder || props.parentFolder) draghover.value = false; } -function onDrop(e: DragEvent) { +function onDrop(ev: DragEvent) { draghover.value = false; - if (!e.dataTransfer) return; + if (!ev.dataTransfer) return; // ファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { emit('upload', file, props.folder); } return; } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); emit('removeFile', file.id); os.api('drive/files/update', { @@ -100,11 +100,11 @@ function onDrop(e: DragEvent) { //#endregion //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder !== '') { const folder = JSON.parse(driveFolder); // 移動先が自分自身ならreject - if (props.folder && folder.id == props.folder.id) return; + if (props.folder && folder.id === props.folder.id) return; emit('removeFolder', folder.id); os.api('drive/folders/update', { folderId: folder.id, diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue index e044c67523..6c2c8acad0 100644 --- a/packages/client/src/components/drive.vue +++ b/packages/client/src/components/drive.vue @@ -97,6 +97,7 @@ import * as os from '@/os'; import { stream } from '@/stream'; import { defaultStore } from '@/store'; import { i18n } from '@/i18n'; +import { uploadFile, uploads } from '@/scripts/upload'; const props = withDefaults(defineProps<{ initialFolder?: Misskey.entities.DriveFolder; @@ -109,11 +110,11 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; - (e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; - (e: 'move-root'): void; - (e: 'cd', v: Misskey.entities.DriveFolder | null): void; - (e: 'open-folder', v: Misskey.entities.DriveFolder): void; + (ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void; + (ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void; + (ev: 'move-root'): void; + (ev: 'cd', v: Misskey.entities.DriveFolder | null): void; + (ev: 'open-folder', v: Misskey.entities.DriveFolder): void; }>(); const loadMoreFiles = ref<InstanceType<typeof MkButton>>(); @@ -127,8 +128,9 @@ const moreFolders = ref(false); const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]); const selectedFiles = ref<Misskey.entities.DriveFile[]>([]); const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]); -const uploadings = os.uploads; +const uploadings = uploads; const connection = stream.useChannel('drive'); +const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい // ドロップされようとしているか const draghover = ref(false); @@ -141,7 +143,7 @@ const fetching = ref(true); const ilFilesObserver = new IntersectionObserver( (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles() -) +); watch(folder, () => emit('cd', folder.value)); @@ -151,7 +153,7 @@ function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) { const current = folder.value ? folder.value.id : null; - if (current != file.folderId) { + if (current !== file.folderId) { removeFile(file); } else { addFile(file, true); @@ -168,7 +170,7 @@ function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder) function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) { const current = folder.value ? folder.value.id : null; - if (current != updatedFolder.parentId) { + if (current !== updatedFolder.parentId) { removeFolder(updatedFolder); } else { addFolder(updatedFolder, true); @@ -179,23 +181,23 @@ function onStreamDriveFolderDeleted(folderId: string) { removeFolder(folderId); } -function onDragover(e: DragEvent): any { - if (!e.dataTransfer) return; +function onDragover(ev: DragEvent): any { + if (!ev.dataTransfer) return; // ドラッグ元が自分自身の所有するアイテムだったら if (isDragSource.value) { // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; return; } - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; - const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; + const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_; if (isFile || isDriveFile || isDriveFolder) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + ev.dataTransfer.dropEffect = 'none'; } return false; @@ -209,24 +211,24 @@ function onDragleave() { draghover.value = false; } -function onDrop(e: DragEvent): any { +function onDrop(ev: DragEvent): any { draghover.value = false; - if (!e.dataTransfer) return; + if (!ev.dataTransfer) return; // ドロップされてきたものがファイルだったら - if (e.dataTransfer.files.length > 0) { - for (const file of Array.from(e.dataTransfer.files)) { + if (ev.dataTransfer.files.length > 0) { + for (const file of Array.from(ev.dataTransfer.files)) { upload(file, folder.value); } return; } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); - if (files.value.some(f => f.id == file.id)) return; + if (files.value.some(f => f.id === file.id)) return; removeFile(file.id); os.api('drive/files/update', { fileId: file.id, @@ -236,13 +238,13 @@ function onDrop(e: DragEvent): any { //#endregion //#region ドライブのフォルダ - const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); - if (driveFolder != null && driveFolder != '') { + const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_); + if (driveFolder != null && driveFolder !== '') { const droppedFolder = JSON.parse(driveFolder); // 移動先が自分自身ならreject - if (folder.value && droppedFolder.id == folder.value.id) return false; - if (folders.value.some(f => f.id == droppedFolder.id)) return false; + if (folder.value && droppedFolder.id === folder.value.id) return false; + if (folders.value.some(f => f.id === droppedFolder.id)) return false; removeFolder(droppedFolder.id); os.api('drive/folders/update', { folderId: droppedFolder.id, @@ -330,7 +332,7 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) { // 削除時に親フォルダに移動 move(folderToDelete.parentId); }).catch(err => { - switch(err.id) { + switch (err.id) { case 'b0fc8a17-963c-405d-bfbc-859a487295e1': os.alert({ type: 'error', @@ -355,16 +357,16 @@ function onChangeFileInput() { } function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) { - os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => { + uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => { addFile(res, true); }); } function chooseFile(file: Misskey.entities.DriveFile) { - const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id); + const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id); if (props.multiple) { if (isAlreadySelected) { - selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id); + selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id); } else { selectedFiles.value.push(file); } @@ -380,10 +382,10 @@ function chooseFile(file: Misskey.entities.DriveFile) { } function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) { - const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id); + const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id); if (props.multiple) { if (isAlreadySelected) { - selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id); + selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id); } else { selectedFolders.value.push(folderToChoose); } @@ -402,7 +404,7 @@ function move(target?: Misskey.entities.DriveFolder) { if (!target) { goRoot(); return; - } else if (typeof target == 'object') { + } else if (typeof target === 'object') { target = target.id; } @@ -428,9 +430,9 @@ function move(target?: Misskey.entities.DriveFolder) { function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { const current = folder.value ? folder.value.id : null; - if (current != folderToAdd.parentId) return; + if (current !== folderToAdd.parentId) return; - if (folders.value.some(f => f.id == folderToAdd.id)) { + if (folders.value.some(f => f.id === folderToAdd.id)) { const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id); folders.value[exist] = folderToAdd; return; @@ -445,9 +447,9 @@ function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) { function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { const current = folder.value ? folder.value.id : null; - if (current != fileToAdd.folderId) return; + if (current !== fileToAdd.folderId) return; - if (files.value.some(f => f.id == fileToAdd.id)) { + if (files.value.some(f => f.id === fileToAdd.id)) { const exist = files.value.map(f => f.id).indexOf(fileToAdd.id); files.value[exist] = fileToAdd; return; @@ -462,12 +464,12 @@ function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) { function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) { const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove; - folders.value = folders.value.filter(f => f.id != folderIdToRemove); + folders.value = folders.value.filter(f => f.id !== folderIdToRemove); } function removeFile(file: Misskey.entities.DriveFile | string) { const fileId = typeof file === 'object' ? file.id : file; - files.value = files.value.filter(f => f.id != fileId); + files.value = files.value.filter(f => f.id !== fileId); } function appendFile(file: Misskey.entities.DriveFile) { @@ -510,7 +512,7 @@ async function fetch() { folderId: folder.value ? folder.value.id : null, limit: foldersMax + 1 }).then(fetchedFolders => { - if (fetchedFolders.length == foldersMax + 1) { + if (fetchedFolders.length === foldersMax + 1) { moreFolders.value = true; fetchedFolders.pop(); } @@ -522,7 +524,7 @@ async function fetch() { type: props.type, limit: filesMax + 1 }).then(fetchedFiles => { - if (fetchedFiles.length == filesMax + 1) { + if (fetchedFiles.length === filesMax + 1) { moreFiles.value = true; fetchedFiles.pop(); } @@ -549,7 +551,7 @@ function fetchMoreFiles() { untilId: files.value[files.value.length - 1].id, limit: max + 1 }).then(files => { - if (files.length == max + 1) { + if (files.length === max + 1) { moreFiles.value = true; files.pop(); } else { @@ -562,6 +564,10 @@ function fetchMoreFiles() { function getMenu() { return [{ + type: 'switch', + text: i18n.ts.keepOriginalUploading, + ref: keepOriginal, + }, null, { text: i18n.ts.addFile, type: 'label' }, { @@ -601,7 +607,7 @@ function onContextmenu(ev: MouseEvent) { onMounted(() => { if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) { nextTick(() => { - ilFilesObserver.observe(loadMoreFiles.value?.$el) + ilFilesObserver.observe(loadMoreFiles.value?.$el); }); } @@ -622,7 +628,7 @@ onMounted(() => { onActivated(() => { if (defaultStore.state.enableInfiniteScroll) { nextTick(() => { - ilFilesObserver.observe(loadMoreFiles.value?.$el) + ilFilesObserver.observe(loadMoreFiles.value?.$el); }); } }); diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue index 4d27fb48ba..610690d701 100644 --- a/packages/client/src/components/emoji-picker-window.vue +++ b/packages/client/src/components/emoji-picker-window.vue @@ -25,8 +25,8 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', v: any): void; - (e: 'closed'): void; + (ev: 'chosen', v: any): void; + (ev: 'closed'): void; }>(); function chosen(emoji: any) { diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue index 1026e894d1..52f7047487 100644 --- a/packages/client/src/components/emoji-picker.section.vue +++ b/packages/client/src/components/emoji-picker.section.vue @@ -24,7 +24,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'chosen', v: string, ev: MouseEvent): void; + (ev: 'chosen', v: string, event: MouseEvent): void; }>(); const shown = ref(!!props.initialShown); diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index 8601ea121c..64732e7033 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,6 +1,6 @@ <template> <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }"> - <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()"> + <input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()"> <div ref="emojis" class="emojis"> <section class="result"> <div v-if="searchResultCustom.length > 0"> @@ -61,7 +61,7 @@ </div> <div> <header class="_acrylic">{{ i18n.ts.emoji }}</header> - <XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> + <XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection> </div> </div> <div class="tabs"> @@ -97,7 +97,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'chosen', v: string): void; + (ev: 'chosen', v: string): void; }>(); const search = ref<HTMLInputElement>(); @@ -138,7 +138,7 @@ watch(q, () => { const emojis = customEmojis; const matches = new Set<Misskey.entities.CustomEmoji>(); - const exactMatch = emojis.find(e => e.name === newQ); + const exactMatch = emojis.find(emoji => emoji.name === newQ); if (exactMatch) matches.add(exactMatch); if (newQ.includes(' ')) { // AND検索 @@ -201,7 +201,7 @@ watch(q, () => { const emojis = emojilist; const matches = new Set<UnicodeEmojiDef>(); - const exactMatch = emojis.find(e => e.name === newQ); + const exactMatch = emojis.find(emoji => emoji.name === newQ); if (exactMatch) matches.add(exactMatch); if (newQ.includes(' ')) { // AND検索 @@ -295,7 +295,7 @@ function chosen(emoji: any, ev?: MouseEvent) { // 最近使った絵文字更新 if (!pinned.value.includes(key)) { let recents = defaultStore.state.recentlyUsedEmojis; - recents = recents.filter((e: any) => e !== key); + recents = recents.filter((emoji: any) => emoji !== key); recents.unshift(key); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); } @@ -313,12 +313,12 @@ function done(query?: any): boolean | void { if (query == null || typeof query !== 'string') return; const q2 = query.replace(/:/g, ''); - const exactMatchCustom = customEmojis.find(e => e.name === q2); + const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2); if (exactMatchCustom) { chosen(exactMatchCustom); return true; } - const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2); + const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2); if (exactMatchUnicode) { chosen(exactMatchUnicode); return true; diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue index 93c9e891c1..efee795e43 100644 --- a/packages/client/src/components/follow-button.vue +++ b/packages/client/src/components/follow-button.vue @@ -28,7 +28,7 @@ </template> <script lang="ts" setup> -import { onBeforeUnmount, onMounted, ref } from 'vue'; +import { onBeforeUnmount, onMounted } from 'vue'; import * as Misskey from 'misskey-js'; import * as os from '@/os'; import { stream } from '@/stream'; @@ -43,32 +43,30 @@ const props = withDefaults(defineProps<{ large: false, }); -const isFollowing = ref(props.user.isFollowing); -const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou); -const wait = ref(false); +let isFollowing = $ref(props.user.isFollowing); +let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou); +let wait = $ref(false); const connection = stream.useChannel('main'); if (props.user.isFollowing == null) { os.api('users/show', { userId: props.user.id - }).then(u => { - isFollowing.value = u.isFollowing; - hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou; - }); + }) + .then(onFollowChange); } function onFollowChange(user: Misskey.entities.UserDetailed) { - if (user.id == props.user.id) { - isFollowing.value = user.isFollowing; - hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou; + if (user.id === props.user.id) { + isFollowing = user.isFollowing; + hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou; } } async function onClick() { - wait.value = true; + wait = true; try { - if (isFollowing.value) { + if (isFollowing) { const { canceled } = await os.confirm({ type: 'warning', text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }), @@ -80,26 +78,22 @@ async function onClick() { userId: props.user.id }); } else { - if (hasPendingFollowRequestFromYou.value) { + if (hasPendingFollowRequestFromYou) { await os.api('following/requests/cancel', { userId: props.user.id }); - } else if (props.user.isLocked) { - await os.api('following/create', { - userId: props.user.id - }); - hasPendingFollowRequestFromYou.value = true; + hasPendingFollowRequestFromYou = false; } else { await os.api('following/create', { userId: props.user.id }); - hasPendingFollowRequestFromYou.value = true; + hasPendingFollowRequestFromYou = true; } } - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); } finally { - wait.value = false; + wait = false; } } diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue index 46cbf6bd70..19c1f23c85 100644 --- a/packages/client/src/components/forgot-password.vue +++ b/packages/client/src/components/forgot-password.vue @@ -41,8 +41,8 @@ import { instance } from '@/instance'; import { i18n } from '@/i18n'; const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; }>(); let dialog: InstanceType<typeof XModalWindow> = $ref(); diff --git a/packages/client/src/components/form-dialog.vue b/packages/client/src/components/form-dialog.vue index efd0da443d..11459f5937 100644 --- a/packages/client/src/components/form-dialog.vue +++ b/packages/client/src/components/form-dialog.vue @@ -44,7 +44,7 @@ <template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template> <template v-if="form[item].description" #caption>{{ form[item].description }}</template> </FormRange> - <MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)" class="_formBlock"> + <MkButton v-else-if="form[item].type === 'button'" class="_formBlock" @click="form[item].action($event, values)"> <span v-text="form[item].content || item"></span> </MkButton> </template> diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue index 571afe50c0..1b960657d7 100644 --- a/packages/client/src/components/form/folder.vue +++ b/packages/client/src/components/form/folder.vue @@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{ defaultOpen: boolean; }>(), { defaultOpen: false, -}) +}); let opened = $ref(props.defaultOpen); let openedAtLeastOnce = $ref(props.defaultOpen); diff --git a/packages/client/src/components/form/radios.vue b/packages/client/src/components/form/radios.vue index ff5d51f9c7..a52acae9e1 100644 --- a/packages/client/src/components/form/radios.vue +++ b/packages/client/src/components/form/radios.vue @@ -14,7 +14,7 @@ export default defineComponent({ data() { return { value: this.modelValue, - } + }; }, watch: { value() { diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue index a82348d317..07f2c23124 100644 --- a/packages/client/src/components/form/range.vue +++ b/packages/client/src/components/form/range.vue @@ -16,7 +16,7 @@ </template> <script lang="ts"> -import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue'; import * as os from '@/os'; export default defineComponent({ @@ -112,7 +112,7 @@ export default defineComponent({ ev.preventDefault(); const tooltipShowing = ref(true); - os.popup(import('@/components/ui/tooltip.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { showing: tooltipShowing, text: computed(() => { return props.textConverter(finalValue.value); diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue index b5a30d635c..fadb770aee 100644 --- a/packages/client/src/components/form/switch.vue +++ b/packages/client/src/components/form/switch.vue @@ -31,7 +31,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'update:modelValue', v: boolean): void; + (ev: 'update:modelValue', v: boolean): void; }>(); let button = $ref<HTMLElement>(); diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue index 52fef50f9b..5287d59b3e 100644 --- a/packages/client/src/components/global/a.vue +++ b/packages/client/src/components/global/a.vue @@ -5,14 +5,13 @@ </template> <script lang="ts" setup> -import { inject } from 'vue'; import * as os from '@/os'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import { router } from '@/router'; import { url } from '@/config'; import { popout as popout_ } from '@/scripts/popout'; import { i18n } from '@/i18n'; -import { defaultStore } from '@/store'; +import { MisskeyNavigator } from '@/scripts/navigate'; const props = withDefaults(defineProps<{ to: string; @@ -23,9 +22,7 @@ const props = withDefaults(defineProps<{ behavior: null, }); -type Navigate = (path: string, record?: boolean) => void; -const navHook = inject<null | Navigate>('navHook', null); -const sideViewHook = inject<null | Navigate>('sideViewHook', null); +const mkNav = new MisskeyNavigator(); const active = $computed(() => { if (props.activeClass == null) return false; @@ -48,11 +45,11 @@ function onContextmenu(ev) { action: () => { os.pageWindow(props.to); } - }, sideViewHook ? { + }, mkNav.sideViewHook ? { icon: 'fas fa-columns', text: i18n.ts.openInSideView, action: () => { - sideViewHook(props.to); + if (mkNav.sideViewHook) mkNav.sideViewHook(props.to); } } : undefined, { icon: 'fas fa-expand-alt', @@ -101,18 +98,6 @@ function nav() { } } - if (navHook) { - navHook(props.to); - } else { - if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') { - return sideViewHook(props.to); - } - - if (router.currentRoute.value.path === props.to) { - window.scroll({ top: 0, behavior: 'smooth' }); - } else { - router.push(props.to); - } - } + mkNav.push(props.to); } </script> diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue index 27cfb6e4d4..4868896c99 100644 --- a/packages/client/src/components/global/avatar.vue +++ b/packages/client/src/components/global/avatar.vue @@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'click', ev: MouseEvent): void; + (ev: 'click', v: MouseEvent): void; }>(); const url = $computed(() => defaultStore.state.disableShowingAnimatedImages diff --git a/packages/client/src/components/global/emoji.vue b/packages/client/src/components/global/emoji.vue index 92edb1caf9..0075e0867d 100644 --- a/packages/client/src/components/global/emoji.vue +++ b/packages/client/src/components/global/emoji.vue @@ -46,7 +46,7 @@ export default defineComponent({ const url = computed(() => { if (char.value) { let codes = Array.from(char.value).map(x => x.codePointAt(0).toString(16)); - if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f'); + if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f'); codes = codes.filter(x => x && x.length); return `${twemojiSvgBase}/${codes.join('-')}.svg`; } else { diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue index e558614c12..63db19a520 100644 --- a/packages/client/src/components/global/header.vue +++ b/packages/client/src/components/global/header.vue @@ -38,7 +38,7 @@ <script lang="ts"> import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; import { popupMenu } from '@/os'; import { url } from '@/config'; import { scrollToTop } from '@/scripts/scroll'; diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue index 43ea1395ed..5a7e362fcf 100644 --- a/packages/client/src/components/global/loading.vue +++ b/packages/client/src/components/global/loading.vue @@ -1,11 +1,24 @@ <template> -<div class="yxspomdl" :class="{ inline, colored, mini }"> - <div class="ring"></div> +<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini }]"> + <div :class="$style.container"> + <svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + <svg :class="[$style.spinner, $style.fg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg"> + <g transform="matrix(1.125,0,0,1.125,12,12)"> + <path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/> + </g> + </svg> + </div> </div> </template> <script lang="ts" setup> -import { } from 'vue'; +import { useCssModule } from 'vue'; + +useCssModule(); const props = withDefaults(defineProps<{ inline?: boolean; @@ -18,8 +31,8 @@ const props = withDefaults(defineProps<{ }); </script> -<style lang="scss" scoped> -@keyframes ring { +<style lang="scss" module> +@keyframes spinner { 0% { transform: rotate(0deg); } @@ -28,12 +41,12 @@ const props = withDefaults(defineProps<{ } } -.yxspomdl { +.root { padding: 32px; text-align: center; cursor: wait; - --size: 48px; + --size: 40px; &.colored { color: var(--accent); @@ -49,34 +62,33 @@ const props = withDefaults(defineProps<{ padding: 16px; --size: 32px; } +} - > .ring { - position: relative; - display: inline-block; - vertical-align: middle; +.container { + position: relative; + width: var(--size); + height: var(--size); + margin: 0 auto; +} - &:before, - &:after { - content: " "; - display: block; - box-sizing: border-box; - width: var(--size); - height: var(--size); - border-radius: 50%; - border: solid 4px; - } +.spinner { + position: absolute; + top: 0; + left: 0; + width: var(--size); + height: var(--size); + fill-rule: evenodd; + clip-rule: evenodd; + stroke-linecap: round; + stroke-linejoin: round; + stroke-miterlimit: 1.5; +} - &:before { - border-color: currentColor; - opacity: 0.3; - } +.bg { + opacity: 0.275; +} - &:after { - position: absolute; - top: 0; - border-color: currentColor transparent transparent transparent; - animation: ring 0.5s linear infinite; - } - } +.fg { + animation: spinner 0.5s linear infinite; } </style> diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue index 243d8614ba..70d0108e9f 100644 --- a/packages/client/src/components/global/misskey-flavored-markdown.vue +++ b/packages/client/src/components/global/misskey-flavored-markdown.vue @@ -31,6 +31,32 @@ const props = withDefaults(defineProps<{ } } +.mfm-x2 { + --mfm-zoom-size: 200%; +} + +.mfm-x3 { + --mfm-zoom-size: 400%; +} + +.mfm-x4 { + --mfm-zoom-size: 600%; +} + +.mfm-x2, .mfm-x3, .mfm-x4 { + font-size: var(--mfm-zoom-size); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* only half effective */ + font-size: calc(var(--mfm-zoom-size) / 2 + 50%); + + .mfm-x2, .mfm-x3, .mfm-x4 { + /* disabled */ + font-size: 100%; + } + } +} + @keyframes mfm-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue index 5748d9de61..a7f142f961 100644 --- a/packages/client/src/components/global/time.vue +++ b/packages/client/src/components/global/time.vue @@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{ mode: 'relative', }); -const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; +const _time = typeof props.time === 'string' ? new Date(props.time) : props.time; const absolute = _time.toLocaleString(); let now = $ref(new Date()); @@ -32,8 +32,7 @@ const relative = $computed(() => { ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : ago >= -1 ? i18n.ts._ago.justNow : - ago < -1 ? i18n.ts._ago.future : - i18n.ts._ago.unknown); + i18n.ts._ago.future); }); function tick() { diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue index 55f6c5d5f9..34ba9024cc 100644 --- a/packages/client/src/components/global/url.vue +++ b/packages/client/src/components/global/url.vue @@ -18,7 +18,7 @@ </template> <script lang="ts"> -import { defineComponent, ref } from 'vue'; +import { defineAsyncComponent, defineComponent, ref } from 'vue'; import { toUnicode as decodePunycode } from 'punycode/'; import { url as local } from '@/config'; import * as os from '@/os'; @@ -50,7 +50,7 @@ export default defineComponent({ const el = ref(); useTooltip(el, (showing) => { - os.popup(import('@/components/url-preview-popup.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), { showing, url: props.url, source: el.value, diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue index c39076df16..7bc88399ef 100644 --- a/packages/client/src/components/image-viewer.vue +++ b/packages/client/src/components/image-viewer.vue @@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const modal = $ref<InstanceType<typeof MkModal>>(); diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue index 9b0a18ec90..c32409ecf4 100644 --- a/packages/client/src/components/instance-ticker.vue +++ b/packages/client/src/components/instance-ticker.vue @@ -39,6 +39,19 @@ const bg = { border-radius: 4px 0 0 4px; overflow: hidden; color: #fff; + text-shadow: /* .866 ≈ sin(60deg) */ + 1px 0 1px #000, + .866px .5px 1px #000, + .5px .866px 1px #000, + 0 1px 1px #000, + -.5px .866px 1px #000, + -.866px .5px 1px #000, + -1px 0 1px #000, + -.866px -.5px 1px #000, + -.5px -.866px 1px #000, + 0 -1px 1px #000, + .5px -.866px 1px #000, + .866px -.5px 1px #000; > .icon { height: 100%; diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue index 317c931cec..846a9a3a76 100644 --- a/packages/client/src/components/link.vue +++ b/packages/client/src/components/link.vue @@ -8,7 +8,7 @@ </template> <script lang="ts" setup> -import { } from 'vue'; +import { defineAsyncComponent } from 'vue'; import { url as local } from '@/config'; import { useTooltip } from '@/scripts/use-tooltip'; import * as os from '@/os'; @@ -26,7 +26,7 @@ const target = self ? null : '_blank'; const el = $ref(); useTooltip($$(el), (showing) => { - os.popup(import('@/components/url-preview-popup.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), { showing, url: props.url, source: el, diff --git a/packages/client/src/components/media-caption.vue b/packages/client/src/components/media-caption.vue index ef546f3f70..feed3854f9 100644 --- a/packages/client/src/components/media-caption.vue +++ b/packages/client/src/components/media-caption.vue @@ -77,7 +77,7 @@ export default defineComponent({ computed: { remainingLength(): number { - if (typeof this.inputValue != "string") return 512; + if (typeof this.inputValue !== "string") return 512; return 512 - length(this.inputValue); } }, @@ -116,17 +116,17 @@ export default defineComponent({ } }, - onKeydown(e) { - if (e.which === 27) { // ESC + onKeydown(evt) { + if (evt.which === 27) { // ESC this.cancel(); } }, - onInputKeydown(e) { - if (e.which === 13) { // Enter - if (e.ctrlKey) { - e.preventDefault(); - e.stopPropagation(); + onInputKeydown(evt) { + if (evt.which === 13) { // Enter + if (evt.ctrlKey) { + evt.preventDefault(); + evt.stopPropagation(); this.ok(); } } diff --git a/packages/client/src/components/media-video.vue b/packages/client/src/components/media-video.vue index 680eb27e64..5c38691e69 100644 --- a/packages/client/src/components/media-video.vue +++ b/packages/client/src/components/media-video.vue @@ -8,7 +8,8 @@ <div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu"> <video :poster="video.thumbnailUrl" - :title="video.name" + :title="video.comment" + :alt="video.comment" preload="none" controls @contextmenu.stop diff --git a/packages/client/src/components/mention.vue b/packages/client/src/components/mention.vue index 479acfbc8f..70c2f49afa 100644 --- a/packages/client/src/components/mention.vue +++ b/packages/client/src/components/mention.vue @@ -1,22 +1,22 @@ <template> -<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="ldlomzub" :class="{ isMe }" :to="url" :style="{ background: bg }"> - <img class="icon" :src="`/avatar/@${username}@${host}`" alt=""> +<MkA v-if="url.startsWith('/')" v-user-preview="canonical" :class="[$style.root, { isMe }]" :to="url" :style="{ background: bg }"> + <img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt=""> <span class="main"> <span class="username">@{{ username }}</span> - <span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span> + <span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.mainHost">@{{ toUnicode(host) }}</span> </span> </MkA> -<a v-else class="ldlomzub" :href="url" target="_blank" rel="noopener" :style="{ background: bg }"> +<a v-else :class="$style.root" :href="url" target="_blank" rel="noopener" :style="{ background: bg }"> <span class="main"> <span class="username">@{{ username }}</span> - <span class="host">@{{ toUnicode(host) }}</span> + <span :class="$style.mainHost">@{{ toUnicode(host) }}</span> </span> </a> </template> <script lang="ts"> -import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import { defineComponent, useCssModule } from 'vue'; +import tinycolor from 'tinycolor2'; import { toUnicode } from 'punycode'; import { host as localHost } from '@/config'; import { $i } from '@/account'; @@ -45,6 +45,8 @@ export default defineComponent({ const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention')); bg.setAlpha(0.1); + useCssModule(); + return { localHost, isMe, @@ -57,8 +59,8 @@ export default defineComponent({ }); </script> -<style lang="scss" scoped> -.ldlomzub { +<style lang="scss" module> +.root { display: inline-block; padding: 4px 8px 4px 4px; border-radius: 999px; @@ -67,18 +69,18 @@ export default defineComponent({ &.isMe { color: var(--mentionMe); } +} - > .icon { - width: 1.5em; - margin: 0 0.2em 0 0; - vertical-align: bottom; - border-radius: 100%; - } +.icon { + width: 1.5em; + height: 1.5em; + object-fit: cover; + margin: 0 0.2em 0 0; + vertical-align: bottom; + border-radius: 100%; +} - > .main { - > .host { - opacity: 0.5; - } - } +.mainHost { + opacity: 0.5; } </style> diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts index 37076652fd..4556a82d55 100644 --- a/packages/client/src/components/mfm.ts +++ b/packages/client/src/components/mfm.ts @@ -91,7 +91,8 @@ export default defineComponent({ let style; switch (token.props.name) { case 'tada': { - style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : ''); + const speed = validTime(token.props.args.speed) || '1s'; + style = 'font-size: 150%;' + (this.$store.state.animatedMfm ? `animation: tada ${speed} linear infinite both;` : ''); break; } case 'jelly': { @@ -123,11 +124,13 @@ export default defineComponent({ break; } case 'jump': { - style = this.$store.state.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : ''; + const speed = validTime(token.props.args.speed) || '0.75s'; + style = this.$store.state.animatedMfm ? `animation: mfm-jump ${speed} linear infinite;` : ''; break; } case 'bounce': { - style = this.$store.state.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : ''; + const speed = validTime(token.props.args.speed) || '0.75s'; + style = this.$store.state.animatedMfm ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : ''; break; } case 'flip': { @@ -139,16 +142,19 @@ export default defineComponent({ break; } case 'x2': { - style = `font-size: 200%;`; - break; + return h('span', { + class: 'mfm-x2', + }, genEl(token.children)); } case 'x3': { - style = `font-size: 400%;`; - break; + return h('span', { + class: 'mfm-x3', + }, genEl(token.children)); } case 'x4': { - style = `font-size: 600%;`; - break; + return h('span', { + class: 'mfm-x4', + }, genEl(token.children)); } case 'font': { const family = @@ -168,7 +174,8 @@ export default defineComponent({ }, genEl(token.children)); } case 'rainbow': { - style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : ''; + const speed = validTime(token.props.args.speed) || '1s'; + style = this.$store.state.animatedMfm ? `animation: mfm-rainbow ${speed} linear infinite;` : ''; break; } case 'sparkle': { diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue index 2e17d5d030..21bdb657b7 100644 --- a/packages/client/src/components/modal-page-window.vue +++ b/packages/client/src/components/modal-page-window.vue @@ -39,8 +39,8 @@ export default defineComponent({ inject: { sideViewHook: { - default: null - } + default: null, + }, }, provide() { @@ -94,31 +94,31 @@ export default defineComponent({ }, { icon: 'fas fa-expand-alt', text: this.$ts.showInPage, - action: this.expand + action: this.expand, }, this.sideViewHook ? { icon: 'fas fa-columns', text: this.$ts.openInSideView, action: () => { this.sideViewHook(this.path); this.$refs.window.close(); - } + }, } : undefined, { icon: 'fas fa-external-link-alt', text: this.$ts.popout, - action: this.popout + action: this.popout, }, null, { icon: 'fas fa-external-link-alt', text: this.$ts.openInNewTab, action: () => { window.open(this.url, '_blank'); this.$refs.window.close(); - } + }, }, { icon: 'fas fa-link', text: this.$ts.copyLink, action: () => { copyToClipboard(this.url); - } + }, }]; }, }, @@ -155,7 +155,7 @@ export default defineComponent({ onContextmenu(ev: MouseEvent) { os.contextMenu(this.contextmenu, ev); - } + }, }, }); </script> diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue index d30284ca5f..6234b710d2 100644 --- a/packages/client/src/components/note-detailed.vue +++ b/packages/client/src/components/note-detailed.vue @@ -2,9 +2,9 @@ <div v-if="!muted" v-show="!isDeleted" + ref="el" v-hotkey="keymap" v-size="{ max: [500, 450, 350, 300] }" - ref="el" class="lxwezrsl _block" :tabindex="!isDeleted ? '-1' : null" :class="{ renote: isRenote }" @@ -197,7 +197,7 @@ const keymap = { 'q': () => renoteButton.value.renote(true), 'esc': blur, 'm|o': () => menu(true), - 's': () => showContent.value != showContent.value, + 's': () => showContent.value !== showContent.value, }; useNoteCapture({ @@ -222,7 +222,7 @@ function react(viaKeyboard = false): void { reactionPicker.show(reactButton.value, reaction => { os.api('notes/reactions/create', { noteId: appearNote.id, - reaction: reaction + reaction: reaction, }); }, () => { focus(); @@ -233,7 +233,7 @@ function undoReact(note): void { const oldReaction = note.myReaction; if (!oldReaction) return; os.api('notes/reactions/delete', { - noteId: note.id + noteId: note.id, }); } @@ -257,7 +257,7 @@ function onContextmenu(ev: MouseEvent): void { function menu(viaKeyboard = false): void { os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { - viaKeyboard + viaKeyboard, }).then(focus); } @@ -269,12 +269,12 @@ function showRenoteMenu(viaKeyboard = false): void { danger: true, action: () => { os.api('notes/delete', { - noteId: note.id + noteId: note.id, }); isDeleted.value = true; - } + }, }], renoteTime.value, { - viaKeyboard: viaKeyboard + viaKeyboard: viaKeyboard, }); } @@ -288,14 +288,14 @@ function blur() { os.api('notes/children', { noteId: appearNote.id, - limit: 30 + limit: 30, }).then(res => { replies.value = res; }); if (appearNote.replyId) { os.api('notes/conversation', { - noteId: appearNote.replyId + noteId: appearNote.replyId, }).then(res => { conversation.value = res.reverse(); }); diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue index c6907787b5..b813b9a2b9 100644 --- a/packages/client/src/components/note-simple.vue +++ b/packages/client/src/components/note-simple.vue @@ -5,7 +5,7 @@ <XNoteHeader class="header" :note="note" :mini="true"/> <div class="body"> <p v-if="note.cw != null" class="cw"> - <span v-if="note.cw != ''" class="text">{{ note.cw }}</span> + <Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/> <XCwButton v-model="showContent" :note="note"/> </p> <div v-show="note.cw == null || showContent" class="content"> diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue index 3cd7a819d4..e5744d1ce9 100644 --- a/packages/client/src/components/note.vue +++ b/packages/client/src/components/note.vue @@ -185,7 +185,7 @@ const keymap = { 'down|j|tab': focusAfter, 'esc': blur, 'm|o': () => menu(true), - 's': () => showContent.value != showContent.value, + 's': () => showContent.value !== showContent.value, }; useNoteCapture({ @@ -210,7 +210,7 @@ function react(viaKeyboard = false): void { reactionPicker.show(reactButton.value, reaction => { os.api('notes/reactions/create', { noteId: appearNote.id, - reaction: reaction + reaction: reaction, }); }, () => { focus(); @@ -221,7 +221,7 @@ function undoReact(note): void { const oldReaction = note.myReaction; if (!oldReaction) return; os.api('notes/reactions/delete', { - noteId: note.id + noteId: note.id, }); } @@ -245,7 +245,7 @@ function onContextmenu(ev: MouseEvent): void { function menu(viaKeyboard = false): void { os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, { - viaKeyboard + viaKeyboard, }).then(focus); } @@ -257,12 +257,12 @@ function showRenoteMenu(viaKeyboard = false): void { danger: true, action: () => { os.api('notes/delete', { - noteId: note.id + noteId: note.id, }); isDeleted.value = true; - } + }, }], renoteTime.value, { - viaKeyboard: viaKeyboard + viaKeyboard: viaKeyboard, }); } @@ -284,7 +284,7 @@ function focusAfter() { function readPromo() { os.api('promo/read', { - noteId: appearNote.id + noteId: appearNote.id, }); isDeleted.value = true; } diff --git a/packages/client/src/components/notification-setting-window.vue b/packages/client/src/components/notification-setting-window.vue index ec1efec261..64d828394b 100644 --- a/packages/client/src/components/notification-setting-window.vue +++ b/packages/client/src/components/notification-setting-window.vue @@ -1,5 +1,6 @@ <template> -<XModalWindow ref="dialog" +<XModalWindow + ref="dialog" :width="400" :height="450" :with-ok-button="true" @@ -28,18 +29,18 @@ <script lang="ts"> import { defineComponent, PropType } from 'vue'; -import XModalWindow from '@/components/ui/modal-window.vue'; +import { notificationTypes } from 'misskey-js'; import MkSwitch from './form/switch.vue'; import MkInfo from './ui/info.vue'; import MkButton from './ui/button.vue'; -import { notificationTypes } from 'misskey-js'; +import XModalWindow from '@/components/ui/modal-window.vue'; export default defineComponent({ components: { XModalWindow, MkSwitch, MkInfo, - MkButton + MkButton, }, props: { @@ -53,7 +54,7 @@ export default defineComponent({ type: Boolean, required: false, default: true, - } + }, }, emits: ['done', 'closed'], @@ -93,7 +94,7 @@ export default defineComponent({ for (const type in this.typesMap) { this.typesMap[type as typeof notificationTypes[number]] = true; } - } - } + }, + }, }); </script> diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 1a360f9905..cbfd809f37 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -16,7 +16,8 @@ <i v-else-if="notification.type === 'pollVote'" class="fas fa-poll-h"></i> <i v-else-if="notification.type === 'pollEnded'" class="fas fa-poll-h"></i> <!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 --> - <XReactionIcon v-else-if="notification.type === 'reaction'" + <XReactionIcon + v-else-if="notification.type === 'reaction'" ref="reactionRef" :reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" :custom-emojis="notification.note.emojis" @@ -72,12 +73,12 @@ </template> <script lang="ts"> -import { defineComponent, ref, onMounted, onUnmounted } from 'vue'; +import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue'; import * as misskey from 'misskey-js'; -import { getNoteSummary } from '@/scripts/get-note-summary'; import XReactionIcon from './reaction-icon.vue'; import MkFollowButton from './follow-button.vue'; import XReactionTooltip from './reaction-tooltip.vue'; +import { getNoteSummary } from '@/scripts/get-note-summary'; import { notePage } from '@/filters/note'; import { userPage } from '@/filters/user'; import { i18n } from '@/i18n'; @@ -87,7 +88,7 @@ import { useTooltip } from '@/scripts/use-tooltip'; export default defineComponent({ components: { - XReactionIcon, MkFollowButton + XReactionIcon, MkFollowButton, }, props: { @@ -116,7 +117,7 @@ export default defineComponent({ const readObserver = new IntersectionObserver((entries, observer) => { if (!entries.some(entry => entry.isIntersecting)) return; stream.send('readNotification', { - id: props.notification.id + id: props.notification.id, }); observer.disconnect(); }); @@ -126,6 +127,10 @@ export default defineComponent({ const connection = stream.useChannel('main'); connection.on('readAllNotifications', () => readObserver.disconnect()); + watch(props.notification.isRead, () => { + readObserver.disconnect(); + }); + onUnmounted(() => { readObserver.disconnect(); connection.dispose(); diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue index d522503a14..8eb569c369 100644 --- a/packages/client/src/components/notifications.vue +++ b/packages/client/src/components/notifications.vue @@ -19,8 +19,7 @@ <script lang="ts" setup> import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue'; import { notificationTypes } from 'misskey-js'; -import MkPagination from '@/components/ui/pagination.vue'; -import { Paging } from '@/components/ui/pagination.vue'; +import MkPagination, { Paging } from '@/components/ui/pagination.vue'; import XNotification from '@/components/notification.vue'; import XList from '@/components/date-separated-list.vue'; import XNote from '@/components/note.vue'; @@ -49,14 +48,14 @@ const onNotification = (notification) => { const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type); if (isMuted || document.visibilityState === 'visible') { stream.send('readNotification', { - id: notification.id + id: notification.id, }); } if (!isMuted) { pagingComponent.value.prepend({ ...notification, - isRead: document.visibilityState === 'visible' + isRead: document.visibilityState === 'visible', }); } }; @@ -64,6 +63,31 @@ const onNotification = (notification) => { onMounted(() => { const connection = stream.useChannel('main'); connection.on('notification', onNotification); + connection.on('readAllNotifications', () => { + if (pagingComponent.value) { + for (const item of pagingComponent.value.queue) { + item.isRead = true; + } + for (const item of pagingComponent.value.items) { + item.isRead = true; + } + } + }); + connection.on('readNotifications', notificationIds => { + if (pagingComponent.value) { + for (let i = 0; i < pagingComponent.value.queue.length; i++) { + if (notificationIds.includes(pagingComponent.value.queue[i].id)) { + pagingComponent.value.queue[i].isRead = true; + } + } + for (let i = 0; i < (pagingComponent.value.items || []).length; i++) { + if (notificationIds.includes(pagingComponent.value.items[i].id)) { + pagingComponent.value.items[i].isRead = true; + } + } + } + }); + onUnmounted(() => { connection.dispose(); }); diff --git a/packages/client/src/components/number-diff.vue b/packages/client/src/components/number-diff.vue index 9889c97ec3..e7d4a5472a 100644 --- a/packages/client/src/components/number-diff.vue +++ b/packages/client/src/components/number-diff.vue @@ -12,7 +12,7 @@ export default defineComponent({ props: { value: { type: Number, - required: true + required: true, }, }, @@ -26,7 +26,7 @@ export default defineComponent({ isZero, number, }; - } + }, }); </script> diff --git a/packages/client/src/components/page/page.image.vue b/packages/client/src/components/page/page.image.vue index 04ce74bd7c..6e38a9f424 100644 --- a/packages/client/src/components/page/page.image.vue +++ b/packages/client/src/components/page/page.image.vue @@ -1,34 +1,22 @@ <template> <div class="lzyxtsnt"> - <img v-if="image" :src="image.url"/> + <ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/> </div> </template> -<script lang="ts"> +<script lang="ts" setup> import { defineComponent, PropType } from 'vue'; +import ImgWithBlurhash from '@/components/img-with-blurhash.vue'; import * as os from '@/os'; import { ImageBlock } from '@/scripts/hpml/block'; import { Hpml } from '@/scripts/hpml/evaluator'; -export default defineComponent({ - props: { - block: { - type: Object as PropType<ImageBlock>, - required: true - }, - hpml: { - type: Object as PropType<Hpml>, - required: true - } - }, - setup(props, ctx) { - const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); +const props = defineProps<{ + block: PropType<ImageBlock>, + hpml: PropType<Hpml>, +}>(); - return { - image - }; - } -}); +const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId); </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue index 847da37c51..3401f945bd 100644 --- a/packages/client/src/components/page/page.post.vue +++ b/packages/client/src/components/page/page.post.vue @@ -52,21 +52,21 @@ export default defineComponent({ const promise = new Promise((ok) => { const canvas = this.hpml.canvases[this.block.canvasId]; canvas.toBlob(blob => { - const data = new FormData(); - data.append('file', blob); - data.append('i', this.$i.token); + const formData = new FormData(); + formData.append('file', blob); + formData.append('i', this.$i.token); if (this.$store.state.uploadFolder) { - data.append('folderId', this.$store.state.uploadFolder); + formData.append('folderId', this.$store.state.uploadFolder); } fetch(apiUrl + '/drive/files/create', { method: 'POST', - body: data + body: formData, }) .then(response => response.json()) .then(f => { ok(f); - }) + }); }); }); os.promiseDialog(promise); diff --git a/packages/client/src/components/page/page.vue b/packages/client/src/components/page/page.vue index e54147bbd0..a067762372 100644 --- a/packages/client/src/components/page/page.vue +++ b/packages/client/src/components/page/page.vue @@ -38,8 +38,8 @@ export default defineComponent({ let ast; try { ast = parse(props.page.script); - } catch (e) { - console.error(e); + } catch (err) { + console.error(err); /*os.alert({ type: 'error', text: 'Syntax error :(' @@ -48,11 +48,11 @@ export default defineComponent({ } hpml.aiscript.exec(ast).then(() => { hpml.eval(); - }).catch(e => { - console.error(e); + }).catch(err => { + console.error(err); /*os.alert({ type: 'error', - text: e + text: err });*/ }); } else { diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue index 6f3f23a2d3..9aa5510c7f 100644 --- a/packages/client/src/components/poll-editor.vue +++ b/packages/client/src/components/poll-editor.vue @@ -104,7 +104,7 @@ function add() { } function remove(i) { - choices.value = choices.value.filter((_, _i) => _i != i); + choices.value = choices.value.filter((_, _i) => _i !== i); } function get() { diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue index 9dd69a0ee5..6b9827407b 100644 --- a/packages/client/src/components/post-form-attaches.vue +++ b/packages/client/src/components/post-form-attaches.vue @@ -16,7 +16,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; -import MkDriveFileThumbnail from './drive-file-thumbnail.vue' +import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; import * as os from '@/os'; export default defineComponent({ @@ -88,7 +88,7 @@ export default defineComponent({ }, async describe(file) { - os.popup(import("@/components/media-caption.vue"), { + os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), { title: this.$ts.describeFile, input: { placeholder: this.$ts.inputNewDescription, @@ -98,7 +98,7 @@ export default defineComponent({ }, { done: result => { if (!result || result.canceled) return; - let comment = result.result.length == 0 ? null : result.result; + let comment = result.result.length === 0 ? null : result.result; os.api('drive/files/update', { fileId: file.id, comment: comment, @@ -114,19 +114,19 @@ export default defineComponent({ this.menu = os.popupMenu([{ text: this.$ts.renameFile, icon: 'fas fa-i-cursor', - action: () => { this.rename(file) } + action: () => { this.rename(file); } }, { text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye', - action: () => { this.toggleSensitive(file) } + action: () => { this.toggleSensitive(file); } }, { text: this.$ts.describeFile, icon: 'fas fa-i-cursor', - action: () => { this.describe(file) } + action: () => { this.describe(file); } }, { text: this.$ts.attachCancel, icon: 'fas fa-times-circle', - action: () => { this.detachMedia(file.id) } + action: () => { this.detachMedia(file.id); } }], ev.currentTarget ?? ev.target).then(() => this.menu = null); } } diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue index 656689ddcb..0197313e0e 100644 --- a/packages/client/src/components/post-form.vue +++ b/packages/client/src/components/post-form.vue @@ -62,7 +62,7 @@ </template> <script lang="ts" setup> -import { inject, watch, nextTick, onMounted } from 'vue'; +import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue'; import * as mfm from 'mfm-js'; import * as misskey from 'misskey-js'; import insertTextAtCursor from 'insert-text-at-cursor'; @@ -87,6 +87,7 @@ import MkInfo from '@/components/ui/info.vue'; import { i18n } from '@/i18n'; import { instance } from '@/instance'; import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account'; +import { uploadFile } from '@/scripts/upload'; const modal = inject('modal'); @@ -106,7 +107,7 @@ const props = withDefaults(defineProps<{ fixed?: boolean; autofocus?: boolean; }>(), { - initialVisibleUsers: [], + initialVisibleUsers: () => [], autofocus: true, }); @@ -227,7 +228,7 @@ if (props.mention) { text += ' '; } -if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) { +if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) { text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `; } @@ -238,16 +239,15 @@ if (props.reply && props.reply.text != null) { for (const x of extractMentions(ast)) { const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : - (otherHost == null || otherHost == host) ? + (otherHost == null || otherHost === host) ? `@${x.username}` : `@${x.username}@${toASCII(otherHost)}`; // 自分は除外 - if ($i.username == x.username && x.host == null) continue; - if ($i.username == x.username && x.host == host) continue; + if ($i.username === x.username && (x.host == null || x.host === host)) continue; // 重複は除外 - if (text.indexOf(`${mention} `) != -1) continue; + if (text.includes(`${mention} `)) continue; text += `${mention} `; } @@ -302,7 +302,7 @@ function checkMissingMention() { const ast = mfm.parse(text); for (const x of extractMentions(ast)) { - if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { hasNotSpecifiedMentions = true; return; } @@ -315,7 +315,7 @@ function addMissingMention() { const ast = mfm.parse(text); for (const x of extractMentions(ast)) { - if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) { + if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) { os.api('users/show', { username: x.username, host: x.host }).then(user => { visibleUsers.push(user); }); @@ -356,7 +356,7 @@ function chooseFileFrom(ev) { } function detachFile(id) { - files = files.filter(x => x.id != id); + files = files.filter(x => x.id !== id); } function updateFiles(_files) { @@ -372,7 +372,7 @@ function updateFileName(file, name) { } function upload(file: File, name?: string) { - os.upload(file, defaultStore.state.uploadFolder, name).then(res => { + uploadFile(file, defaultStore.state.uploadFolder, name).then(res => { files.push(res); }); } @@ -383,7 +383,7 @@ function setVisibility() { return; } - os.popup(import('./visibility-picker.vue'), { + os.popup(defineAsyncComponent(() => import('./visibility-picker.vue')), { currentVisibility: visibility, currentLocalOnly: localOnly, src: visibilityButton, @@ -426,24 +426,24 @@ function clear() { quoteId = null; } -function onKeydown(e: KeyboardEvent) { - if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post(); - if (e.which === 27) emit('esc'); +function onKeydown(ev: KeyboardEvent) { + if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post(); + if (ev.which === 27) emit('esc'); typing(); } -function onCompositionUpdate(e: CompositionEvent) { - imeText = e.data; +function onCompositionUpdate(ev: CompositionEvent) { + imeText = ev.data; typing(); } -function onCompositionEnd(e: CompositionEvent) { +function onCompositionEnd(ev: CompositionEvent) { imeText = ''; } -async function onPaste(e: ClipboardEvent) { - for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) { - if (item.kind == 'file') { +async function onPaste(ev: ClipboardEvent) { + for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) { + if (item.kind === 'file') { const file = item.getAsFile(); const lio = file.name.lastIndexOf('.'); const ext = lio >= 0 ? file.name.slice(lio) : ''; @@ -452,10 +452,10 @@ async function onPaste(e: ClipboardEvent) { } } - const paste = e.clipboardData.getData('text'); + const paste = ev.clipboardData.getData('text'); if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) { - e.preventDefault(); + ev.preventDefault(); os.confirm({ type: 'info', @@ -471,49 +471,49 @@ async function onPaste(e: ClipboardEvent) { } } -function onDragover(e) { - if (!e.dataTransfer.items[0]) return; - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; +function onDragover(ev) { + if (!ev.dataTransfer.items[0]) return; + const isFile = ev.dataTransfer.items[0].kind === 'file'; + const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.preventDefault(); + ev.preventDefault(); draghover = true; - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } } -function onDragenter(e) { +function onDragenter(ev) { draghover = true; } -function onDragleave(e) { +function onDragleave(ev) { draghover = false; } -function onDrop(e): void { +function onDrop(ev): void { draghover = false; // ファイルだったら - if (e.dataTransfer.files.length > 0) { - e.preventDefault(); - for (const x of Array.from(e.dataTransfer.files)) upload(x); + if (ev.dataTransfer.files.length > 0) { + ev.preventDefault(); + for (const x of Array.from(ev.dataTransfer.files)) upload(x); return; } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); files.push(file); - e.preventDefault(); + ev.preventDefault(); } //#endregion } function saveDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); - data[draftKey] = { + draftData[draftKey] = { updatedAt: new Date(), data: { text: text, @@ -526,20 +526,20 @@ function saveDraft() { } }; - localStorage.setItem('drafts', JSON.stringify(data)); + localStorage.setItem('drafts', JSON.stringify(draftData)); } function deleteDraft() { - const data = JSON.parse(localStorage.getItem('drafts') || '{}'); + const draftData = JSON.parse(localStorage.getItem('drafts') || '{}'); - delete data[draftKey]; + delete draftData[draftKey]; - localStorage.setItem('drafts', JSON.stringify(data)); + localStorage.setItem('drafts', JSON.stringify(draftData)); } async function post() { - let data = { - text: text == '' ? undefined : text, + let postData = { + text: text === '' ? undefined : text, fileIds: files.length > 0 ? files.map(f => f.id) : undefined, replyId: props.reply ? props.reply.id : undefined, renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined, @@ -548,18 +548,18 @@ async function post() { cw: useCw ? cw || '' : undefined, localOnly: localOnly, visibility: visibility, - visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined, + visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined, }; if (withHashtags && hashtags && hashtags.trim() !== '') { const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' '); - data.text = data.text ? `${data.text} ${hashtags_}` : hashtags_; + postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_; } // plugin if (notePostInterruptors.length > 0) { for (const interruptor of notePostInterruptors) { - data = await interruptor.handler(JSON.parse(JSON.stringify(data))); + postData = await interruptor.handler(JSON.parse(JSON.stringify(postData))); } } @@ -571,13 +571,13 @@ async function post() { } posting = true; - os.api('notes/create', data, token).then(() => { + os.api('notes/create', postData, token).then(() => { clear(); nextTick(() => { deleteDraft(); emit('posted'); - if (data.text && data.text != '') { - const hashtags_ = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); + if (postData.text && postData.text !== '') { + const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag); const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[]; localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history)))); } @@ -661,7 +661,7 @@ onMounted(() => { cw = draft.data.cw; visibility = draft.data.visibility; localOnly = draft.data.localOnly; - files = (draft.data.files || []).filter(e => e); + files = (draft.data.files || []).filter(draftFile => draftFile); if (draft.data.poll) { poll = draft.data.poll; } diff --git a/packages/client/src/components/queue-chart.vue b/packages/client/src/components/queue-chart.vue index 7e0ed58cbd..7bb548cf06 100644 --- a/packages/client/src/components/queue-chart.vue +++ b/packages/client/src/components/queue-chart.vue @@ -222,7 +222,7 @@ export default defineComponent({ return { chartEl, - } + }; }, }); </script> diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue index 7dc079fde6..91a90a6996 100644 --- a/packages/client/src/components/reactions-viewer.reaction.vue +++ b/packages/client/src/components/reactions-viewer.reaction.vue @@ -7,8 +7,8 @@ :class="{ reacted: note.myReaction == reaction, canToggle }" @click="toggleReaction()" > - <XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/> - <span>{{ count }}</span> + <XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/> + <span class="count">{{ count }}</span> </button> </template> @@ -141,12 +141,16 @@ export default defineComponent({ background: var(--accent); } - > span { + > .count { color: var(--fgOnAccent); } + + > .icon { + filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5)); + } } - > span { + > .count { font-size: 0.9em; line-height: 32px; margin: 0 0 0 4px; diff --git a/packages/client/src/components/sample.vue b/packages/client/src/components/sample.vue index 65249ff7e9..f80b9c96b7 100644 --- a/packages/client/src/components/sample.vue +++ b/packages/client/src/components/sample.vue @@ -52,7 +52,7 @@ export default defineComponent({ flag: true, radio: 'misskey', mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.` - } + }; }, methods: { diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue index 5c2048e7b0..848b11fada 100644 --- a/packages/client/src/components/signin-dialog.vue +++ b/packages/client/src/components/signin-dialog.vue @@ -2,12 +2,12 @@ <XModalWindow ref="dialog" :width="370" :height="400" - @close="dialog.close()" + @close="onClose" @closed="emit('closed')" > <template #header>{{ $ts.login }}</template> - <MkSignin :auto-set="autoSet" @login="onLogin"/> + <MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/> </XModalWindow> </template> @@ -18,17 +18,25 @@ import MkSignin from './signin.vue'; const props = withDefaults(defineProps<{ autoSet?: boolean; + message?: string, }>(), { autoSet: false, + message: '' }); const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; + (ev: 'cancelled'): void; }>(); const dialog = $ref<InstanceType<typeof XModalWindow>>(); +function onClose() { + emit('cancelled'); + dialog.close(); +} + function onLogin(res) { emit('done', res); dialog.close(); diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue index f640e948ad..b772d1479b 100644 --- a/packages/client/src/components/signin.vue +++ b/packages/client/src/components/signin.vue @@ -1,39 +1,42 @@ <template> <form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> <div class="auth _section _formRoot"> - <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }"></div> + <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div> + <MkInfo v-if="message"> + {{ message }} + </MkInfo> <div v-if="!totpLogin" class="normal-signin"> - <MkInput v-model="username" class="_formBlock" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> + <MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> <template #prefix>@</template> <template #suffix>@{{ host }}</template> </MkInput> - <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> + <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password> <template #prefix><i class="fas fa-lock"></i></template> - <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template> + <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> </MkInput> - <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> + <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> </div> <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }"> <div v-if="user && user.securityKeys" class="twofa-group tap-group"> - <p>{{ $ts.tapSecurityKey }}</p> + <p>{{ i18n.ts.tapSecurityKey }}</p> <MkButton v-if="!queryingKey" @click="queryKey"> - {{ $ts.retry }} + {{ i18n.ts.retry }} </MkButton> </div> <div v-if="user && user.securityKeys" class="or-hr"> - <p class="or-msg">{{ $ts.or }}</p> + <p class="or-msg">{{ i18n.ts.or }}</p> </div> <div class="twofa-group totp-group"> - <p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p> + <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p> <MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required> - <template #label>{{ $ts.password }}</template> + <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="fas fa-lock"></i></template> </MkInput> <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required> - <template #label>{{ $ts.token }}</template> + <template #label>{{ i18n.ts.token }}</template> <template #prefix><i class="fas fa-gavel"></i></template> </MkInput> - <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton> + <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> </div> </div> </div> @@ -45,190 +48,198 @@ </form> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import { toUnicode } from 'punycode/'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; -import { apiUrl, host } from '@/config'; +import MkInfo from '@/components/ui/info.vue'; +import { apiUrl, host as configHost } from '@/config'; import { byteify, hexify } from '@/scripts/2fa'; import * as os from '@/os'; import { login } from '@/account'; import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - }, +let signing = $ref(false); +let user = $ref(null); +let username = $ref(''); +let password = $ref(''); +let token = $ref(''); +let host = $ref(toUnicode(configHost)); +let totpLogin = $ref(false); +let credential = $ref(null); +let challengeData = $ref(null); +let queryingKey = $ref(false); +let hCaptchaResponse = $ref(null); +let reCaptchaResponse = $ref(null); - props: { - withAvatar: { - type: Boolean, - required: false, - default: true - }, - autoSet: { - type: Boolean, - required: false, - default: false, - } - }, +const meta = $computed(() => instance); - emits: ['login'], +const emit = defineEmits<{ + (ev: 'login', v: any): void; +}>(); - data() { - return { - signing: false, - user: null, - username: '', - password: '', - token: '', - apiUrl, - host: toUnicode(host), - totpLogin: false, - credential: null, - challengeData: null, - queryingKey: false, - }; +const props = defineProps({ + withAvatar: { + type: Boolean, + required: false, + default: true }, - - computed: { - meta() { - return this.$instance; - }, + autoSet: { + type: Boolean, + required: false, + default: false, }, + message: { + type: String, + required: false, + default: '' + } +}); - methods: { - onUsernameChange() { - os.api('users/show', { - username: this.username - }).then(user => { - this.user = user; - }, () => { - this.user = null; - }); - }, - - onLogin(res) { - if (this.autoSet) { - return login(res.i); - } else { - return; - } - }, - - queryKey() { - this.queryingKey = true; - return navigator.credentials.get({ - publicKey: { - challenge: byteify(this.challengeData.challenge, 'base64'), - allowCredentials: this.challengeData.securityKeys.map(key => ({ - id: byteify(key.id, 'hex'), - type: 'public-key', - transports: ['usb', 'nfc', 'ble', 'internal'] - })), - timeout: 60 * 1000 - } - }).catch(() => { - this.queryingKey = false; - return Promise.reject(null); - }).then(credential => { - this.queryingKey = false; - this.signing = true; - return os.api('signin', { - username: this.username, - password: this.password, - signature: hexify(credential.response.signature), - authenticatorData: hexify(credential.response.authenticatorData), - clientDataJSON: hexify(credential.response.clientDataJSON), - credentialId: credential.id, - challengeId: this.challengeData.challengeId - }); - }).then(res => { - this.$emit('login', res); - return this.onLogin(res); - }).catch(err => { - if (err === null) return; - os.alert({ - type: 'error', - text: this.$ts.signinFailed - }); - this.signing = false; - }); - }, +function onUsernameChange() { + os.api('users/show', { + username: username + }).then(userResponse => { + user = userResponse; + }, () => { + user = null; + }); +} - onSubmit() { - this.signing = true; - if (!this.totpLogin && this.user && this.user.twoFactorEnabled) { - if (window.PublicKeyCredential && this.user.securityKeys) { - os.api('signin', { - username: this.username, - password: this.password - }).then(res => { - this.totpLogin = true; - this.signing = false; - this.challengeData = res; - return this.queryKey(); - }).catch(this.loginFailed); - } else { - this.totpLogin = true; - this.signing = false; - } - } else { - os.api('signin', { - username: this.username, - password: this.password, - token: this.user && this.user.twoFactorEnabled ? this.token : undefined - }).then(res => { - this.$emit('login', res); - this.onLogin(res); - }).catch(this.loginFailed); - } - }, +function onLogin(res) { + if (props.autoSet) { + return login(res.i); + } +} - loginFailed(err) { - switch (err.id) { - case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: this.$ts.noSuchUser - }); - break; - } - case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: this.$ts.incorrectPassword, - }); - break; - } - case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { - showSuspendedDialog(); - break; - } - default: { - os.alert({ - type: 'error', - title: this.$ts.loginFailed, - text: JSON.stringify(err) - }); - } - } +function queryKey() { + queryingKey = true; + return navigator.credentials.get({ + publicKey: { + challenge: byteify(challengeData.challenge, 'base64'), + allowCredentials: challengeData.securityKeys.map(key => ({ + id: byteify(key.id, 'hex'), + type: 'public-key', + transports: ['usb', 'nfc', 'ble', 'internal'] + })), + timeout: 60 * 1000 + } + }).catch(() => { + queryingKey = false; + return Promise.reject(null); + }).then(credential => { + queryingKey = false; + signing = true; + return os.api('signin', { + username, + password, + signature: hexify(credential.response.signature), + authenticatorData: hexify(credential.response.authenticatorData), + clientDataJSON: hexify(credential.response.clientDataJSON), + credentialId: credential.id, + challengeId: challengeData.challengeId, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }); + }).then(res => { + emit('login', res); + return onLogin(res); + }).catch(err => { + if (err === null) return; + os.alert({ + type: 'error', + text: i18n.ts.signinFailed + }); + signing = false; + }); +} - this.challengeData = null; - this.totpLogin = false; - this.signing = false; - }, +function onSubmit() { + signing = true; + console.log('submit'); + if (!totpLogin && user && user.twoFactorEnabled) { + if (window.PublicKeyCredential && user.securityKeys) { + os.api('signin', { + username, + password, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + }).then(res => { + totpLogin = true; + signing = false; + challengeData = res; + return queryKey(); + }).catch(loginFailed); + } else { + totpLogin = true; + signing = false; + } + } else { + os.api('signin', { + username, + password, + 'hcaptcha-response': hCaptchaResponse, + 'g-recaptcha-response': reCaptchaResponse, + token: user && user.twoFactorEnabled ? token : undefined + }).then(res => { + emit('login', res); + onLogin(res); + }).catch(loginFailed); + } +} - resetPassword() { - os.popup(import('@/components/forgot-password.vue'), {}, { - }, 'closed'); +function loginFailed(err) { + switch (err.id) { + case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.noSuchUser + }); + break; + } + case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.incorrectPassword, + }); + break; + } + case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { + showSuspendedDialog(); + break; + } + case '22d05606-fbcf-421a-a2db-b32610dcfd1b': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.rateLimitExceeded, + }); + break; + } + default: { + console.log(err); + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: JSON.stringify(err) + }); } } -}); + + challengeData = null; + totpLogin = false; + signing = false; +} + +function resetPassword() { + os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, { + }, 'closed'); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue index bda2495ba7..6dad9257a4 100644 --- a/packages/client/src/components/signup-dialog.vue +++ b/packages/client/src/components/signup-dialog.vue @@ -27,8 +27,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'done'): void; - (e: 'closed'): void; + (ev: 'done'): void; + (ev: 'closed'): void; }>(); const dialog = $ref<InstanceType<typeof XModalWindow>>(); diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue index 38a9fd55f1..3f2af306e5 100644 --- a/packages/client/src/components/signup.vue +++ b/packages/client/src/components/signup.vue @@ -1,11 +1,11 @@ <template> -<form class="qlvuhzng _formRoot" :autocomplete="Math.random()" @submit.prevent="onSubmit"> +<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit"> <template v-if="meta"> - <MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :autocomplete="Math.random()" spellcheck="false" required> + <MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" spellcheck="false" required> <template #label>{{ $ts.invitationCode }}</template> <template #prefix><i class="fas fa-key"></i></template> </MkInput> - <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> + <MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername"> <template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> <template #prefix>@</template> <template #suffix>@{{ host }}</template> @@ -19,7 +19,7 @@ <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span> </template> </MkInput> - <MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> + <MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> <template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template> <template #prefix><i class="fas fa-envelope"></i></template> <template #caption> @@ -34,7 +34,7 @@ <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span> </template> </MkInput> - <MkInput v-model="password" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password @update:modelValue="onChangePassword"> + <MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> <template #label>{{ $ts.password }}</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -43,7 +43,7 @@ <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span> </template> </MkInput> - <MkInput v-model="retypedPassword" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> + <MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> <template #label>{{ $ts.password }} ({{ $ts.retype }})</template> <template #prefix><i class="fas fa-lock"></i></template> <template #caption> @@ -58,8 +58,8 @@ </template> </I18n> </MkSwitch> - <captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> - <captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> + <MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/> + <MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/> <MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton> </template> </form> @@ -67,7 +67,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; -const getPasswordStrength = require('syuilo-password-strength'); +const getPasswordStrength = await import('syuilo-password-strength'); import { toUnicode } from 'punycode/'; import { host, url } from '@/config'; import MkButton from './ui/button.vue'; @@ -81,7 +81,7 @@ export default defineComponent({ MkButton, MkInput, MkSwitch, - captcha: defineAsyncComponent(() => import('./captcha.vue')), + MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')), }, props: { @@ -111,7 +111,7 @@ export default defineComponent({ ToSAgreement: false, hCaptchaResponse: null, reCaptchaResponse: null, - } + }; }, computed: { @@ -124,20 +124,20 @@ export default defineComponent({ this.meta.tosUrl && !this.ToSAgreement || this.meta.enableHcaptcha && !this.hCaptchaResponse || this.meta.enableRecaptcha && !this.reCaptchaResponse || - this.passwordRetypeState == 'not-match'; + this.passwordRetypeState === 'not-match'; }, shouldShowProfileUrl(): boolean { - return (this.username != '' && - this.usernameState != 'invalid-format' && - this.usernameState != 'min-range' && - this.usernameState != 'max-range'); + return (this.username !== '' && + this.usernameState !== 'invalid-format' && + this.usernameState !== 'min-range' && + this.usernameState !== 'max-range'); } }, methods: { onChangeUsername() { - if (this.username == '') { + if (this.username === '') { this.usernameState = null; return; } @@ -165,7 +165,7 @@ export default defineComponent({ }, onChangeEmail() { - if (this.email == '') { + if (this.email === '') { this.emailState = null; return; } @@ -188,7 +188,7 @@ export default defineComponent({ }, onChangePassword() { - if (this.password == '') { + if (this.password === '') { this.passwordStrength = ''; return; } @@ -198,12 +198,12 @@ export default defineComponent({ }, onChangePasswordRetype() { - if (this.retypedPassword == '') { + if (this.retypedPassword === '') { this.passwordRetypeState = null; return; } - this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match'; + this.passwordRetypeState = this.password === this.retypedPassword ? 'match' : 'not-match'; }, onSubmit() { diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue index 59956b9526..a3fa27ab78 100644 --- a/packages/client/src/components/timeline.vue +++ b/packages/client/src/components/timeline.vue @@ -19,8 +19,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'note'): void; - (e: 'queue', count: number): void; + (ev: 'note'): void; + (ev: 'queue', count: number): void; }>(); provide('inChannel', computed(() => props.src === 'channel')); @@ -95,7 +95,7 @@ if (props.src === 'antenna') { visibility: 'specified' }; const onNote = note => { - if (note.visibility == 'specified') { + if (note.visibility === 'specified') { prepend(note); } }; diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue index 99933f3846..c9fad64eb6 100644 --- a/packages/client/src/components/toast.vue +++ b/packages/client/src/components/toast.vue @@ -19,7 +19,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); const zIndex = os.claimZIndex('high'); diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue index c7b6c8ba96..e6b20d9881 100644 --- a/packages/client/src/components/ui/button.vue +++ b/packages/client/src/components/ui/button.vue @@ -90,32 +90,32 @@ export default defineComponent({ } }, methods: { - onMousedown(e: MouseEvent) { + onMousedown(evt: MouseEvent) { function distance(p, q) { return Math.hypot(p.x - q.x, p.y - q.y); } function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) { - const origin = {x: circleCenterX, y: circleCenterY}; - const dist1 = distance({x: 0, y: 0}, origin); - const dist2 = distance({x: boxW, y: 0}, origin); - const dist3 = distance({x: 0, y: boxH}, origin); - const dist4 = distance({x: boxW, y: boxH }, origin); + const origin = { x: circleCenterX, y: circleCenterY }; + const dist1 = distance({ x: 0, y: 0 }, origin); + const dist2 = distance({ x: boxW, y: 0 }, origin); + const dist3 = distance({ x: 0, y: boxH }, origin); + const dist4 = distance({ x: boxW, y: boxH }, origin); return Math.max(dist1, dist2, dist3, dist4) * 2; } - const rect = e.target.getBoundingClientRect(); + const rect = evt.target.getBoundingClientRect(); const ripple = document.createElement('div'); - ripple.style.top = (e.clientY - rect.top - 1).toString() + 'px'; - ripple.style.left = (e.clientX - rect.left - 1).toString() + 'px'; + ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px'; + ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px'; this.$refs.ripples.appendChild(ripple); - const circleCenterX = e.clientX - rect.left; - const circleCenterY = e.clientY - rect.top; + const circleCenterX = evt.clientX - rect.left; + const circleCenterY = evt.clientY - rect.top; - const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY); + const scale = calcCircleScale(evt.target.clientWidth, evt.target.clientHeight, circleCenterX, circleCenterY); window.setTimeout(() => { ripple.style.transform = 'scale(' + (scale / 2) + ')'; diff --git a/packages/client/src/components/ui/context-menu.vue b/packages/client/src/components/ui/context-menu.vue index f491b43b46..e637d361cf 100644 --- a/packages/client/src/components/ui/context-menu.vue +++ b/packages/client/src/components/ui/context-menu.vue @@ -19,7 +19,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); let rootEl = $ref<HTMLDivElement>(); @@ -63,8 +63,8 @@ onBeforeUnmount(() => { } }); -function onMousedown(e: Event) { - if (!contains(rootEl, e.target) && (rootEl != e.target)) emit('closed'); +function onMousedown(evt: Event) { + if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed'); } </script> diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue index fe1602b2bb..7daa82cbd3 100644 --- a/packages/client/src/components/ui/folder.vue +++ b/packages/client/src/components/ui/folder.vue @@ -23,7 +23,7 @@ <script lang="ts"> import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; const localStoragePrefix = 'ui:folder:'; diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue index a93cc8cda8..dad5dfa8b0 100644 --- a/packages/client/src/components/ui/menu.vue +++ b/packages/client/src/components/ui/menu.vue @@ -1,5 +1,6 @@ <template> -<div ref="itemsEl" v-hotkey="keymap" +<div + ref="itemsEl" v-hotkey="keymap" class="rrevdjwt" :class="{ center: align === 'center', asDrawer }" :style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }" @@ -60,7 +61,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'close'): void; + (ev: 'close'): void; }>(); let itemsEl = $ref<HTMLDivElement>(); @@ -162,6 +163,15 @@ function focusDown() { position: relative; } + &:not(:disabled):hover { + color: var(--accent); + text-decoration: none; + + &:before { + background: var(--accentedBg); + } + } + &.danger { color: #ff2a2a; @@ -191,15 +201,6 @@ function focusDown() { } } - &:not(:disabled):hover { - color: var(--accent); - text-decoration: none; - - &:before { - background: var(--accentedBg); - } - } - &:not(:active):focus-visible { box-shadow: 0 0 0 2px var(--focus) inset; } diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue index b4b8c2b965..d2b2ccff7a 100644 --- a/packages/client/src/components/ui/modal-window.vue +++ b/packages/client/src/components/ui/modal-window.vue @@ -1,7 +1,7 @@ <template> -<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')"> - <div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> - <div class="header"> +<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')"> + <div ref="rootEl" class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown"> + <div ref="headerEl" class="header"> <button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button> <span class="title"> <slot name="header"></slot> @@ -11,82 +11,82 @@ </div> <div v-if="padding" class="body"> <div class="_section"> - <slot></slot> + <slot :width="bodyWidth" :height="bodyHeight"></slot> </div> </div> <div v-else class="body"> - <slot></slot> + <slot :width="bodyWidth" :height="bodyHeight"></slot> </div> </div> </MkModal> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted } from 'vue'; import MkModal from './modal.vue'; -export default defineComponent({ - components: { - MkModal - }, - props: { - withOkButton: { - type: Boolean, - required: false, - default: false - }, - okButtonDisabled: { - type: Boolean, - required: false, - default: false - }, - padding: { - type: Boolean, - required: false, - default: false - }, - width: { - type: Number, - required: false, - default: 400 - }, - height: { - type: Number, - required: false, - default: null - }, - canClose: { - type: Boolean, - required: false, - default: true, - }, - scroll: { - type: Boolean, - required: false, - default: true, - }, - }, +const props = withDefaults(defineProps<{ + withOkButton: boolean; + okButtonDisabled: boolean; + padding: boolean; + width: number; + height: number | null; + scroll: boolean; +}>(), { + withOkButton: false, + okButtonDisabled: false, + padding: false, + width: 400, + height: null, + scroll: true, +}); - emits: ['click', 'close', 'closed', 'ok'], +const emit = defineEmits<{ + (event: 'click'): void; + (event: 'close'): void; + (event: 'closed'): void; + (event: 'ok'): void; +}>(); - data() { - return { - }; - }, +let modal = $ref<InstanceType<typeof MkModal>>(); +let rootEl = $ref<HTMLElement>(); +let headerEl = $ref<HTMLElement>(); +let bodyWidth = $ref(0); +let bodyHeight = $ref(0); - methods: { - close() { - this.$refs.modal.close(); - }, +const close = () => { + modal.close(); +}; - onKeydown(e) { - if (e.which === 27) { // Esc - e.preventDefault(); - e.stopPropagation(); - this.close(); - } - }, +const onBgClick = () => { + emit('click'); +}; + +const onKeydown = (evt) => { + if (evt.which === 27) { // Esc + evt.preventDefault(); + evt.stopPropagation(); + close(); } +}; + +const ro = new ResizeObserver((entries, observer) => { + bodyWidth = rootEl.offsetWidth; + bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; +}); + +onMounted(() => { + bodyWidth = rootEl.offsetWidth; + bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight; + ro.observe(rootEl); +}); + +onUnmounted(() => { + ro.disconnect(); +}); + +defineExpose({ + close, }); </script> diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue index 1e4159055e..d6a29ec4b7 100644 --- a/packages/client/src/components/ui/modal.vue +++ b/packages/client/src/components/ui/modal.vue @@ -1,5 +1,5 @@ <template> -<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered"> +<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"> <div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }"> <div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div> <div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick"> @@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ (ev: 'opening'): void; + (ev: 'opened'): void; (ev: 'click'): void; (ev: 'esc'): void; (ev: 'close'): void; @@ -212,7 +213,9 @@ const align = () => { popover.style.top = top + 'px'; }; -const childRendered = () => { +const onOpened = () => { + emit('opened'); + // モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する const el = content.value!.children[0]; el.addEventListener('mousedown', ev => { @@ -234,10 +237,10 @@ onMounted(() => { } fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null); - await nextTick() + await nextTick(); align(); - }, { immediate: true, }); + }, { immediate: true }); nextTick(() => { const popover = content.value; diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue index 13f3215671..c081e06acd 100644 --- a/packages/client/src/components/ui/pagination.vue +++ b/packages/client/src/components/ui/pagination.vue @@ -14,8 +14,14 @@ </div> <div v-else ref="rootEl"> + <div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> + <MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead"> + {{ $ts.loadMore }} + </MkButton> + <MkLoading v-else class="loading"/> + </div> <slot :items="items"></slot> - <div v-show="more" key="_more_" class="cxiknjgy _gap"> + <div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap"> <MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore"> {{ $ts.loadMore }} </MkButton> @@ -62,7 +68,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'queue', count: number): void; + (ev: 'queue', count: number): void; }>(); type Item = { id: string; [another: string]: unknown; }; @@ -106,7 +112,7 @@ const init = async (): Promise<void> => { offset.value = res.length; error.value = false; fetching.value = false; - }, e => { + }, err => { error.value = true; fetching.value = false; }); @@ -149,7 +155,7 @@ const fetchMore = async (): Promise<void> => { } offset.value += res.length; moreFetching.value = false; - }, e => { + }, err => { moreFetching.value = false; }); }; @@ -177,7 +183,7 @@ const fetchMoreAhead = async (): Promise<void> => { } offset.value += res.length; moreFetching.value = false; - }, e => { + }, err => { moreFetching.value = false; }); }; @@ -244,6 +250,11 @@ const append = (item: Item): void => { items.value.push(item); }; +const removeItem = (finder: (item: Item) => boolean) => { + const i = items.value.findIndex(finder); + items.value.splice(i, 1); +}; + const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => { const i = items.value.findIndex(item => item.id === id); items.value[i] = replacer(items.value[i]); @@ -270,11 +281,12 @@ onDeactivated(() => { defineExpose({ items, + queue, backed, reload, - fetchMoreAhead, prepend, append, + removeItem, updateItem, }); </script> diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue index 8d6c1b5695..2bc7030d77 100644 --- a/packages/client/src/components/ui/popup-menu.vue +++ b/packages/client/src/components/ui/popup-menu.vue @@ -19,7 +19,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'closed'): void; + (ev: 'closed'): void; }>(); let modal = $ref<InstanceType<typeof MkModal>>(); diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue index ee1909554e..571d11ba3b 100644 --- a/packages/client/src/components/ui/tooltip.vue +++ b/packages/client/src/components/ui/tooltip.vue @@ -63,7 +63,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenBottom = () => { let left: number; @@ -84,7 +84,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenLeft = () => { let left: number; @@ -105,7 +105,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calcPosWhenRight = () => { let left: number; @@ -126,7 +126,7 @@ const setPosition = () => { } return [left, top]; - } + }; const calc = (): { left: number; @@ -172,7 +172,7 @@ const setPosition = () => { } return null as never; - } + }; const { left, top, transformOrigin } = calc(); el.value.style.transformOrigin = transformOrigin; diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue index fa32ecfdef..2066cf579d 100644 --- a/packages/client/src/components/ui/window.vue +++ b/packages/client/src/components/ui/window.vue @@ -139,10 +139,10 @@ export default defineComponent({ this.showing = false; }, - onKeydown(e) { - if (e.which === 27) { // Esc - e.preventDefault(); - e.stopPropagation(); + onKeydown(evt) { + if (evt.which === 27) { // Esc + evt.preventDefault(); + evt.stopPropagation(); this.close(); } }, @@ -162,15 +162,15 @@ export default defineComponent({ this.top(); }, - onHeaderMousedown(e) { + onHeaderMousedown(evt) { const main = this.$el as any; if (!contains(main, document.activeElement)) main.focus(); const position = main.getBoundingClientRect(); - const clickX = e.touches && e.touches.length > 0 ? e.touches[0].clientX : e.clientX; - const clickY = e.touches && e.touches.length > 0 ? e.touches[0].clientY : e.clientY; + const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX; + const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY; const moveBaseX = clickX - position.left; const moveBaseY = clickY - position.top; const browserWidth = window.innerWidth; @@ -204,10 +204,10 @@ export default defineComponent({ }, // 上ハンドル掴み時 - onTopHandleMousedown(e) { + onTopHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientY; + const base = evt.clientY; const height = parseInt(getComputedStyle(main, '').height, 10); const top = parseInt(getComputedStyle(main, '').top, 10); @@ -230,10 +230,10 @@ export default defineComponent({ }, // 右ハンドル掴み時 - onRightHandleMousedown(e) { + onRightHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientX; + const base = evt.clientX; const width = parseInt(getComputedStyle(main, '').width, 10); const left = parseInt(getComputedStyle(main, '').left, 10); const browserWidth = window.innerWidth; @@ -254,10 +254,10 @@ export default defineComponent({ }, // 下ハンドル掴み時 - onBottomHandleMousedown(e) { + onBottomHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientY; + const base = evt.clientY; const height = parseInt(getComputedStyle(main, '').height, 10); const top = parseInt(getComputedStyle(main, '').top, 10); const browserHeight = window.innerHeight; @@ -278,10 +278,10 @@ export default defineComponent({ }, // 左ハンドル掴み時 - onLeftHandleMousedown(e) { + onLeftHandleMousedown(evt) { const main = this.$el as any; - const base = e.clientX; + const base = evt.clientX; const width = parseInt(getComputedStyle(main, '').width, 10); const left = parseInt(getComputedStyle(main, '').left, 10); @@ -304,27 +304,27 @@ export default defineComponent({ }, // 左上ハンドル掴み時 - onTopLeftHandleMousedown(e) { - this.onTopHandleMousedown(e); - this.onLeftHandleMousedown(e); + onTopLeftHandleMousedown(evt) { + this.onTopHandleMousedown(evt); + this.onLeftHandleMousedown(evt); }, // 右上ハンドル掴み時 - onTopRightHandleMousedown(e) { - this.onTopHandleMousedown(e); - this.onRightHandleMousedown(e); + onTopRightHandleMousedown(evt) { + this.onTopHandleMousedown(evt); + this.onRightHandleMousedown(evt); }, // 右下ハンドル掴み時 - onBottomRightHandleMousedown(e) { - this.onBottomHandleMousedown(e); - this.onRightHandleMousedown(e); + onBottomRightHandleMousedown(evt) { + this.onBottomHandleMousedown(evt); + this.onRightHandleMousedown(evt); }, // 左下ハンドル掴み時 - onBottomLeftHandleMousedown(e) { - this.onBottomHandleMousedown(e); - this.onLeftHandleMousedown(e); + onBottomLeftHandleMousedown(evt) { + this.onBottomHandleMousedown(evt); + this.onLeftHandleMousedown(evt); }, // 高さを適用 diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue index c7bbd1fbd1..6c593c7b41 100644 --- a/packages/client/src/components/url-preview.vue +++ b/packages/client/src/components/url-preview.vue @@ -90,7 +90,7 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the sitename = info.sitename; fetching = false; player = info.player; - }) + }); }); function adjustTweetHeight(message: any) { diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue index 51c5330564..f80947f75a 100644 --- a/packages/client/src/components/user-preview.vue +++ b/packages/client/src/components/user-preview.vue @@ -70,7 +70,7 @@ export default defineComponent({ }, mounted() { - if (typeof this.q == 'object') { + if (typeof this.q === 'object') { this.user = this.q; this.fetched = true; } else { diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue index dbef34d547..b34d21af07 100644 --- a/packages/client/src/components/user-select-dialog.vue +++ b/packages/client/src/components/user-select-dialog.vue @@ -60,9 +60,9 @@ import * as os from '@/os'; import { defaultStore } from '@/store'; const emit = defineEmits<{ - (e: 'ok', selected: misskey.entities.UserDetailed): void; - (e: 'cancel'): void; - (e: 'closed'): void; + (ev: 'ok', selected: misskey.entities.UserDetailed): void; + (ev: 'cancel'): void; + (ev: 'closed'): void; }>(); let username = $ref(''); diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue index 4b20063a51..c717c3a461 100644 --- a/packages/client/src/components/visibility-picker.vue +++ b/packages/client/src/components/visibility-picker.vue @@ -57,9 +57,9 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; - (e: 'changeLocalOnly', v: boolean): void; - (e: 'closed'): void; + (ev: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void; + (ev: 'changeLocalOnly', v: boolean): void; + (ev: 'closed'): void; }>(); let v = $ref(props.currentVisibility); diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue index 7dfcc55695..9e631b55b1 100644 --- a/packages/client/src/components/waiting-dialog.vue +++ b/packages/client/src/components/waiting-dialog.vue @@ -21,8 +21,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'done'); - (e: 'closed'); + (ev: 'done'); + (ev: 'closed'); }>(); function done() { diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue index da9d935281..74dd79f733 100644 --- a/packages/client/src/components/widgets.vue +++ b/packages/client/src/components/widgets.vue @@ -2,11 +2,11 @@ <div class="vjoppmmu"> <template v-if="edit"> <header> - <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)"> + <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select"> <template #label>{{ $ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ $t(`_widgets.${widget}`) }}</option> </MkSelect> - <MkButton inline primary @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> + <MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> <MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> </header> <XDraggable @@ -19,7 +19,7 @@ <div class="customize-container"> <button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button> <button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button> - <component class="handle" :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/> + <component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="handle" :widget="element" @updateProps="updateWidget(element.id, $event)"/> </div> </template> </XDraggable> @@ -37,7 +37,7 @@ import { widgets as widgetDefs } from '@/widgets'; export default defineComponent({ components: { - XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), + XDraggable: defineAsyncComponent(() => import('vuedraggable')), MkSelect, MkButton, }, diff --git a/packages/client/src/directives/adaptive-border.ts b/packages/client/src/directives/adaptive-border.ts index fc426ca2cc..619c9f0b6d 100644 --- a/packages/client/src/directives/adaptive-border.ts +++ b/packages/client/src/directives/adaptive-border.ts @@ -9,7 +9,7 @@ export default { } else { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } - } + }; const parentBg = getBgColor(src.parentElement); diff --git a/packages/client/src/directives/get-size.ts b/packages/client/src/directives/get-size.ts index 1fcd0718dc..2c4e9c188d 100644 --- a/packages/client/src/directives/get-size.ts +++ b/packages/client/src/directives/get-size.ts @@ -25,12 +25,12 @@ function calc(src: Element) { return; } if (info.intersection) { - info.intersection.disconnect() + info.intersection.disconnect(); delete info.intersection; - }; + } info.fn(width, height); -}; +} export default { mounted(src, binding, vn) { diff --git a/packages/client/src/directives/panel.ts b/packages/client/src/directives/panel.ts index 5f9158db2e..d31dc41ed4 100644 --- a/packages/client/src/directives/panel.ts +++ b/packages/client/src/directives/panel.ts @@ -9,7 +9,7 @@ export default { } else { return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; } - } + }; const parentBg = getBgColor(src.parentElement); diff --git a/packages/client/src/directives/size.ts b/packages/client/src/directives/size.ts index 36f649f180..51855e0de5 100644 --- a/packages/client/src/directives/size.ts +++ b/packages/client/src/directives/size.ts @@ -60,9 +60,9 @@ function calc(el: Element) { return; } if (info.intersection) { - info.intersection.disconnect() + info.intersection.disconnect(); delete info.intersection; - }; + } mountings.set(el, Object.assign(info, { previousWidth: width })); diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts index dd715227a4..0e69da954e 100644 --- a/packages/client/src/directives/tooltip.ts +++ b/packages/client/src/directives/tooltip.ts @@ -1,7 +1,7 @@ // TODO: useTooltip関数使うようにしたい // ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明 -import { Directive, ref } from 'vue'; +import { defineAsyncComponent, Directive, ref } from 'vue'; import { isTouchUsing } from '@/scripts/touch'; import { popup, alert } from '@/os'; @@ -45,7 +45,7 @@ export default { if (self.text == null) return; const showing = ref(true); - popup(import('@/components/ui/tooltip.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { showing, text: self.text, targetElement: el, diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts index cdd2afa194..9d18a69877 100644 --- a/packages/client/src/directives/user-preview.ts +++ b/packages/client/src/directives/user-preview.ts @@ -1,4 +1,4 @@ -import { Directive, ref } from 'vue'; +import { defineAsyncComponent, Directive, ref } from 'vue'; import autobind from 'autobind-decorator'; import { popup } from '@/os'; @@ -24,7 +24,7 @@ export class UserPreview { const showing = ref(true); - popup(import('@/components/user-preview.vue'), { + popup(defineAsyncComponent(() => import('@/components/user-preview.vue')), { showing, q: this.user, source: this.el diff --git a/packages/client/src/emojilist.json b/packages/client/src/emojilist.json index 75c424ab4b..402e82e33b 100644 --- a/packages/client/src/emojilist.json +++ b/packages/client/src/emojilist.json @@ -96,6 +96,13 @@ { "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] }, { "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] }, { "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] }, + { "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] }, + { "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] }, + { "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] }, + { "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] }, + { "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] }, + { "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] }, + { "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] }, { "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] }, { "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] }, { "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] }, @@ -149,11 +156,19 @@ { "category": "people", "char": "🤞", "name": "crossed_fingers", "keywords": ["good", "lucky"] }, { "category": "people", "char": "🖖", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] }, { "category": "people", "char": "✍", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] }, + { "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] }, { "category": "people", "char": "🤏", "name": "pinching_hand", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "🤌", "name": "pinched_fingers", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "🤳", "name": "selfie", "keywords": ["camera", "phone"] }, { "category": "people", "char": "💅", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] }, { "category": "people", "char": "👄", "name": "lips", "keywords": ["mouth", "kiss"] }, + { "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] }, { "category": "people", "char": "🦷", "name": "tooth", "keywords": ["teeth", "dentist"] }, { "category": "people", "char": "👅", "name": "tongue", "keywords": ["mouth", "playful"] }, { "category": "people", "char": "👂", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] }, @@ -275,7 +290,11 @@ { "category": "people", "char": "🧚♀️", "name": "woman_fairy", "keywords": ["woman", "female"] }, { "category": "people", "char": "🧚♂️", "name": "man_fairy", "keywords": ["man", "male"] }, { "category": "people", "char": "👼", "name": "angel", "keywords": ["heaven", "wings", "halo"] }, + { "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] }, { "category": "people", "char": "🤰", "name": "pregnant_woman", "keywords": ["baby"] }, + { "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] }, { "category": "people", "char": "🤱", "name": "breastfeeding", "keywords": ["nursing", "baby"] }, { "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] }, { "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] }, @@ -459,7 +478,7 @@ { "category": "animals_and_nature", "char": "🐛", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] }, { "category": "animals_and_nature", "char": "🦋", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] }, { "category": "animals_and_nature", "char": "🐌", "name": "snail", "keywords": ["slow", "animal", "shell"] }, - { "category": "animals_and_nature", "char": "🐞", "name": "beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, + { "category": "animals_and_nature", "char": "🐞", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, { "category": "animals_and_nature", "char": "🐜", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] }, { "category": "animals_and_nature", "char": "🦗", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] }, { "category": "animals_and_nature", "char": "🕷", "name": "spider", "keywords": ["animal", "arachnid"] }, @@ -615,6 +634,10 @@ { "category": "animals_and_nature", "char": "💧", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] }, { "category": "animals_and_nature", "char": "💦", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] }, { "category": "animals_and_nature", "char": "🌊", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] }, { "category": "food_and_drink", "char": "🍏", "name": "green_apple", "keywords": ["fruit", "nature"] }, { "category": "food_and_drink", "char": "🍎", "name": "apple", "keywords": ["fruit", "mac", "school"] }, { "category": "food_and_drink", "char": "🍐", "name": "pear", "keywords": ["fruit", "nature", "food"] }, @@ -737,6 +760,9 @@ { "category": "food_and_drink", "char": "🥣", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] }, { "category": "food_and_drink", "char": "🥡", "name": "takeout_box", "keywords": ["food", "leftovers"] }, { "category": "food_and_drink", "char": "🥢", "name": "chopsticks", "keywords": ["food"] }, + { "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] }, { "category": "activity", "char": "⚽", "name": "soccer", "keywords": ["sports", "football"] }, { "category": "activity", "char": "🏀", "name": "basketball", "keywords": ["sports", "balls", "NBA"] }, { "category": "activity", "char": "🏈", "name": "football", "keywords": ["sports", "balls", "NFL"] }, @@ -844,6 +870,8 @@ { "category": "activity", "char": "🪄", "name": "magic_wand", "keywords": [] }, { "category": "activity", "char": "🪅", "name": "pinata", "keywords": [] }, { "category": "activity", "char": "🪆", "name": "nesting_dolls", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] }, { "category": "travel_and_places", "char": "🚗", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] }, { "category": "travel_and_places", "char": "🚕", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] }, { "category": "travel_and_places", "char": "🚙", "name": "blue_car", "keywords": ["transportation", "vehicle"] }, @@ -971,11 +999,12 @@ { "category": "travel_and_places", "char": "🕋", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] }, { "category": "travel_and_places", "char": "⛩", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] }, { "category": "travel_and_places", "char": "🛕", "name": "hindu_temple", "keywords": ["temple"] }, - { "category": "travel_and_places", "char": "🪨", "name": "rock", "keywords": [] }, { "category": "travel_and_places", "char": "🪵", "name": "wood", "keywords": [] }, { "category": "travel_and_places", "char": "🛖", "name": "hut", "keywords": [] }, - + { "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] }, { "category": "objects", "char": "⌚", "name": "watch", "keywords": ["time", "accessories"] }, { "category": "objects", "char": "📱", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] }, { "category": "objects", "char": "📲", "name": "calling", "keywords": ["iphone", "incoming"] }, @@ -1016,6 +1045,7 @@ { "category": "objects", "char": "⌛", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] }, { "category": "objects", "char": "📡", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] }, { "category": "objects", "char": "🔋", "name": "battery", "keywords": ["power", "energy", "sustain"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] }, { "category": "objects", "char": "🔌", "name": "electric_plug", "keywords": ["charger", "power"] }, { "category": "objects", "char": "💡", "name": "bulb", "keywords": ["light", "electricity", "idea"] }, { "category": "objects", "char": "🔦", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] }, @@ -1031,6 +1061,7 @@ { "category": "objects", "char": "💰", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "🪙", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "💳", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] }, { "category": "objects", "char": "💎", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] }, { "category": "objects", "char": "⚖", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] }, { "category": "objects", "char": "🧰", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] }, @@ -1077,6 +1108,8 @@ { "category": "objects", "char": "🩹", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "🩺", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "🪒", "name": "razor", "keywords": ["health"] }, + { "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] }, + { "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] }, { "category": "objects", "char": "🧬", "name": "dna", "keywords": ["biologist", "genetics", "life"] }, { "category": "objects", "char": "🧫", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] }, { "category": "objects", "char": "🧪", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] }, @@ -1111,6 +1144,7 @@ { "category": "objects", "char": "🪤", "name": "mouse_trap", "keywords": ["household"] }, { "category": "objects", "char": "🪣", "name": "bucket", "keywords": ["household"] }, { "category": "objects", "char": "🪥", "name": "toothbrush", "keywords": ["household"] }, + { "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] }, { "category": "objects", "char": "⛱", "name": "parasol_on_ground", "keywords": ["weather", "summer"] }, { "category": "objects", "char": "🗿", "name": "moyai", "keywords": ["rock", "easter island", "moai"] }, { "category": "objects", "char": "🛍", "name": "shopping", "keywords": ["mall", "buy", "purchase"] }, @@ -1404,6 +1438,7 @@ { "category": "symbols", "char": "➖", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] }, { "category": "symbols", "char": "➗", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] }, { "category": "symbols", "char": "✖️", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] }, + { "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] }, { "category": "symbols", "char": "♾", "name": "infinity", "keywords": ["forever"] }, { "category": "symbols", "char": "💲", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] }, { "category": "symbols", "char": "💱", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] }, @@ -1747,3 +1782,4 @@ { "category": "flags", "char": "🇺🇳", "name": "united_nations", "keywords": ["un", "flag", "banner"] }, { "category": "flags", "char": "🏴☠️", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] } ] + diff --git a/packages/client/src/filters/bytes.ts b/packages/client/src/filters/bytes.ts index 50e63534b6..c80f2f0ed2 100644 --- a/packages/client/src/filters/bytes.ts +++ b/packages/client/src/filters/bytes.ts @@ -1,7 +1,7 @@ export default (v, digits = 0) => { if (v == null) return '?'; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - if (v == 0) return '0'; + if (v === 0) return '0'; const isMinus = v < 0; if (isMinus) v = -v; const i = Math.floor(Math.log(v) / Math.log(1024)); diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts index ab3299d22b..bb6176e409 100644 --- a/packages/client/src/init.ts +++ b/packages/client/src/init.ts @@ -13,9 +13,9 @@ if (localStorage.getItem('accounts') != null) { } //#endregion -import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue'; +import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue'; import compareVersions from 'compare-versions'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; import widgets from '@/widgets'; import directives from '@/directives'; @@ -146,8 +146,7 @@ if ($i && $i.token) { try { document.body.innerHTML = '<div>Please wait...</div>'; await login(i); - location.reload(); - } catch (e) { + } catch (err) { // Render the error screen // TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな) document.body.innerHTML = '<div id="err">Oops!</div>'; @@ -169,14 +168,14 @@ fetchInstanceMetaPromise.then(() => { initializeSw(); }); -const app = createApp(await ( - window.location.search === '?zen' ? import('@/ui/zen.vue') : - !$i ? import('@/ui/visitor.vue') : - ui === 'deck' ? import('@/ui/deck.vue') : - ui === 'desktop' ? import('@/ui/desktop.vue') : - ui === 'classic' ? import('@/ui/classic.vue') : - import('@/ui/universal.vue') -).then(x => x.default)); +const app = createApp( + window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) : + !$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) : + ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) : + ui === 'desktop' ? defineAsyncComponent(() => import('@/ui/desktop.vue')) : + ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) : + defineAsyncComponent(() => import('@/ui/universal.vue')) +); if (_DEV_) { app.config.performance = true; @@ -204,8 +203,24 @@ if (splash) splash.addEventListener('transitionend', () => { splash.remove(); }); -const rootEl = document.createElement('div'); -document.body.appendChild(rootEl); +// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210 +// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する +const rootEl = (() => { + const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; + + const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID); + + if (currentEl) { + console.warn('multiple import detected'); + return currentEl; + } + + const rootEl = document.createElement('div'); + rootEl.id = MISSKEY_MOUNT_DIV_ID; + document.body.appendChild(rootEl); + return rootEl; +})(); + app.mount(rootEl); // boot.jsのやつを解除 @@ -231,10 +246,10 @@ if (lastVersion !== version) { if (lastVersion != null && compareVersions(version, lastVersion) === 1) { // ログインしてる場合だけ if ($i) { - popup(import('@/components/updated.vue'), {}, {}, 'closed'); + popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed'); } } - } catch (e) { + } catch (err) { } } @@ -319,7 +334,7 @@ stream.on('_disconnected_', async () => { } }); -stream.on('emojiAdded', data => { +stream.on('emojiAdded', emojiData => { // TODO //store.commit('instance/set', ); }); diff --git a/packages/client/src/instance.ts b/packages/client/src/instance.ts index 6e912aa2e5..d24eb2419a 100644 --- a/packages/client/src/instance.ts +++ b/packages/client/src/instance.ts @@ -4,11 +4,11 @@ import { api } from './os'; // TODO: 他のタブと永続化されたstateを同期 -const data = localStorage.getItem('instance'); +const instanceData = localStorage.getItem('instance'); // TODO: instanceをリアクティブにするかは再考の余地あり -export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : { +export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : { // TODO: set default values }); diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts index 43c110555f..14860465fa 100644 --- a/packages/client/src/os.ts +++ b/packages/client/src/os.ts @@ -1,6 +1,6 @@ // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する -import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue'; +import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue'; import { EventEmitter } from 'eventemitter3'; import insertTextAtCursor from 'insert-text-at-cursor'; import * as Misskey from 'misskey-js'; @@ -10,7 +10,6 @@ import MkWaitingDialog from '@/components/waiting-dialog.vue'; import { MenuItem } from '@/types/menu'; import { resolve } from '@/router'; import { $i } from '@/account'; -import { defaultStore } from '@/store'; export const pendingApiRequestsCount = ref(0); @@ -35,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s method: 'POST', body: JSON.stringify(data), credentials: 'omit', - cache: 'no-cache' + cache: 'no-cache', }).then(async (res) => { const body = res.status === 204 ? null : await res.json(); @@ -60,10 +59,10 @@ export const apiWithDialog = (( token?: string | null | undefined, ) => { const promise = api(endpoint, data, token); - promiseDialog(promise, null, (e) => { + promiseDialog(promise, null, (err) => { alert({ type: 'error', - text: e.message + '\n' + (e as any).id, + text: err.message + '\n' + (err as any).id, }); }); @@ -73,7 +72,7 @@ export const apiWithDialog = (( export function promiseDialog<T extends Promise<any>>( promise: T, onSuccess?: ((res: any) => void) | null, - onFailure?: ((e: Error) => void) | null, + onFailure?: ((err: Error) => void) | null, text?: string, ): T { const showing = ref(true); @@ -89,14 +88,14 @@ export function promiseDialog<T extends Promise<any>>( showing.value = false; }, 1000); } - }).catch(e => { + }).catch(err => { showing.value = false; if (onFailure) { - onFailure(e); + onFailure(err); } else { alert({ type: 'error', - text: e + text: err, }); } }); @@ -111,10 +110,6 @@ export function promiseDialog<T extends Promise<any>>( return promise; } -function isModule(x: any): x is typeof import('*.vue') { - return x.default != null; -} - let popupIdCount = 0; export const popups = ref([]) as Ref<{ id: any; @@ -132,10 +127,7 @@ export function claimZIndex(priority: 'low' | 'middle' | 'high' = 'low'): number return zIndexes[priority]; } -export async function popup(component: Component | typeof import('*.vue') | Promise<Component | typeof import('*.vue')>, props: Record<string, any>, events = {}, disposeEvent?: string) { - if (component.then) component = await component; - - if (isModule(component)) component = component.default; +export async function popup(component: Component, props: Record<string, any>, events = {}, disposeEvent?: string) { markRaw(component); const id = ++popupIdCount; @@ -150,7 +142,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom props, events: disposeEvent ? { ...events, - [disposeEvent]: dispose + [disposeEvent]: dispose, } : events, id, }; @@ -164,7 +156,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom export function pageWindow(path: string) { const { component, props } = resolve(path); - popup(import('@/components/page-window.vue'), { + popup(defineAsyncComponent(() => import('@/components/page-window.vue')), { initialPath: path, initialComponent: markRaw(component), initialProps: props, @@ -173,7 +165,7 @@ export function pageWindow(path: string) { export function modalPageWindow(path: string) { const { component, props } = resolve(path); - popup(import('@/components/modal-page-window.vue'), { + popup(defineAsyncComponent(() => import('@/components/modal-page-window.vue')), { initialPath: path, initialComponent: markRaw(component), initialProps: props, @@ -181,8 +173,8 @@ export function modalPageWindow(path: string) { } export function toast(message: string) { - popup(import('@/components/toast.vue'), { - message + popup(defineAsyncComponent(() => import('@/components/toast.vue')), { + message, }, {}, 'closed'); } @@ -192,7 +184,7 @@ export function alert(props: { text?: string | null; }): Promise<void> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), props, { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, { done: result => { resolve(); }, @@ -206,7 +198,7 @@ export function confirm(props: { text?: string | null; }): Promise<{ canceled: boolean }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { ...props, showCancelButton: true, }, { @@ -227,14 +219,14 @@ export function inputText(props: { canceled: false; result: string; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: props.type, placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -252,14 +244,14 @@ export function inputNumber(props: { canceled: false; result: number; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: 'number', placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -277,14 +269,14 @@ export function inputDate(props: { canceled: false; result: Date; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, input: { type: 'date', placeholder: props.placeholder, default: props.default, - } + }, }, { done: result => { resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true }); @@ -293,7 +285,7 @@ export function inputDate(props: { }); } -export function select<C extends any = any>(props: { +export function select<C = any>(props: { title?: string | null; text?: string | null; default?: string | null; @@ -314,14 +306,14 @@ export function select<C extends any = any>(props: { canceled: false; result: C; }> { return new Promise((resolve, reject) => { - popup(import('@/components/dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/dialog.vue')), { title: props.title, text: props.text, select: { items: props.items, groupedItems: props.groupedItems, default: props.default, - } + }, }, { done: result => { resolve(result ? result : { canceled: true }); @@ -336,9 +328,9 @@ export function success() { window.setTimeout(() => { showing.value = false; }, 1000); - popup(import('@/components/waiting-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { success: true, - showing: showing + showing: showing, }, { done: () => resolve(), }, 'closed'); @@ -348,9 +340,9 @@ export function success() { export function waiting() { return new Promise((resolve, reject) => { const showing = ref(true); - popup(import('@/components/waiting-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), { success: false, - showing: showing + showing: showing, }, { done: () => resolve(), }, 'closed'); @@ -359,7 +351,7 @@ export function waiting() { export function form(title, form) { return new Promise((resolve, reject) => { - popup(import('@/components/form-dialog.vue'), { title, form }, { + popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, { done: result => { resolve(result); }, @@ -369,7 +361,7 @@ export function form(title, form) { export async function selectUser() { return new Promise((resolve, reject) => { - popup(import('@/components/user-select-dialog.vue'), {}, { + popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, { ok: user => { resolve(user); }, @@ -379,9 +371,9 @@ export async function selectUser() { export async function selectDriveFile(multiple: boolean) { return new Promise((resolve, reject) => { - popup(import('@/components/drive-select-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { type: 'file', - multiple + multiple, }, { done: files => { if (files) { @@ -394,9 +386,9 @@ export async function selectDriveFile(multiple: boolean) { export async function selectDriveFolder(multiple: boolean) { return new Promise((resolve, reject) => { - popup(import('@/components/drive-select-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), { type: 'folder', - multiple + multiple, }, { done: folders => { if (folders) { @@ -409,9 +401,9 @@ export async function selectDriveFolder(multiple: boolean) { export async function pickEmoji(src: HTMLElement | null, opts) { return new Promise((resolve, reject) => { - popup(import('@/components/emoji-picker-dialog.vue'), { + popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { src, - ...opts + ...opts, }, { done: emoji => { resolve(emoji); @@ -420,6 +412,21 @@ export async function pickEmoji(src: HTMLElement | null, opts) { }); } +export async function cropImage(image: Misskey.entities.DriveFile, options: { + aspectRatio: number; +}): Promise<Misskey.entities.DriveFile> { + return new Promise((resolve, reject) => { + popup(defineAsyncComponent(() => import('@/components/cropper-dialog.vue')), { + file: image, + aspectRatio: options.aspectRatio, + }, { + ok: x => { + resolve(x); + }, + }, 'closed'); + }); +} + type AwaitType<T> = T extends Promise<infer U> ? U : T extends (...args: any[]) => Promise<infer V> ? V : @@ -459,9 +466,9 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: characterData: false, }); - openingEmojiPicker = await popup(import('@/components/emoji-picker-window.vue'), { + openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), { src, - ...opts + ...opts, }, { chosen: emoji => { insertTextAtCursor(activeTextarea, emoji); @@ -470,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: openingEmojiPicker!.dispose(); openingEmojiPicker = null; observer.disconnect(); - } + }, }); } @@ -481,12 +488,12 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement }) { return new Promise((resolve, reject) => { let dispose; - popup(import('@/components/ui/popup-menu.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/popup-menu.vue')), { items, src, width: options?.width, align: options?.align, - viaKeyboard: options?.viaKeyboard + viaKeyboard: options?.viaKeyboard, }, { closed: () => { resolve(); @@ -502,7 +509,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent) ev.preventDefault(); return new Promise((resolve, reject) => { let dispose; - popup(import('@/components/ui/context-menu.vue'), { + popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), { items, ev, }, { @@ -537,78 +544,6 @@ export function post(props: Record<string, any> = {}) { export const deckGlobalEvents = new EventEmitter(); -export const uploads = ref<{ - id: string; - name: string; - progressMax: number | undefined; - progressValue: number | undefined; - img: string; -}[]>([]); - -export function upload(file: File, folder?: any, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading): Promise<Misskey.entities.DriveFile> { - if (folder && typeof folder === 'object') folder = folder.id; - - return new Promise((resolve, reject) => { - const id = Math.random().toString(); - - const reader = new FileReader(); - reader.onload = (e) => { - const ctx = reactive({ - id: id, - name: name || file.name || 'untitled', - progressMax: undefined, - progressValue: undefined, - img: window.URL.createObjectURL(file) - }); - - uploads.value.push(ctx); - - console.log(keepOriginal); - - const data = new FormData(); - data.append('i', $i.token); - data.append('force', 'true'); - data.append('file', file); - - if (folder) data.append('folderId', folder); - if (name) data.append('name', name); - - const xhr = new XMLHttpRequest(); - xhr.open('POST', apiUrl + '/drive/files/create', true); - xhr.onload = (ev) => { - if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { - // TODO: 消すのではなくて再送できるようにしたい - uploads.value = uploads.value.filter(x => x.id != id); - - alert({ - type: 'error', - text: 'upload failed' - }); - - reject(); - return; - } - - const driveFile = JSON.parse(ev.target.response); - - resolve(driveFile); - - uploads.value = uploads.value.filter(x => x.id != id); - }; - - xhr.upload.onprogress = e => { - if (e.lengthComputable) { - ctx.progressMax = e.total; - ctx.progressValue = e.loaded; - } - }; - - xhr.send(data); - }; - reader.readAsArrayBuffer(file); - }); -} - /* export function checkExistence(fileData: ArrayBuffer): Promise<any> { return new Promise((resolve, reject) => { diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue index ff04ed84f2..691bc4f07b 100644 --- a/packages/client/src/pages/about-misskey.vue +++ b/packages/client/src/pages/about-misskey.vue @@ -150,6 +150,7 @@ const patrons = [ 'Weeble', '蝉暮せせせ', 'ThatOneCalculator', + 'pixeldesu', ]; let easterEggReady = false; diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue index 92f93797ce..e1d0361c0b 100644 --- a/packages/client/src/pages/admin/abuses.vue +++ b/packages/client/src/pages/admin/abuses.vue @@ -24,10 +24,10 @@ </div> <!-- TODO <div class="inputs" style="display: flex; padding-top: 1.2em;"> - <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()"> + <MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false"> <span>{{ $ts.username }}</span> </MkInput> - <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'"> + <MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'"> <span>{{ $ts.host }}</span> </MkInput> </div> @@ -41,8 +41,8 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed } from 'vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -50,45 +50,35 @@ import MkPagination from '@/components/ui/pagination.vue'; import XAbuseReport from '@/components/abuse-report.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkInput, - MkSelect, - MkPagination, - XAbuseReport, - }, +let reports = $ref<InstanceType<typeof MkPagination>>(); - emits: ['info'], +let state = $ref('unresolved'); +let reporterOrigin = $ref('combined'); +let targetUserOrigin = $ref('combined'); +let searchUsername = $ref(''); +let searchHost = $ref(''); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.abuseReports, - icon: 'fas fa-exclamation-circle', - bg: 'var(--bg)', - }, - searchUsername: '', - searchHost: '', - state: 'unresolved', - reporterOrigin: 'combined', - targetUserOrigin: 'combined', - pagination: { - endpoint: 'admin/abuse-user-reports' as const, - limit: 10, - params: computed(() => ({ - state: this.state, - reporterOrigin: this.reporterOrigin, - targetUserOrigin: this.targetUserOrigin, - })), - }, - } - }, +const pagination = { + endpoint: 'admin/abuse-user-reports' as const, + limit: 10, + params: computed(() => ({ + state, + reporterOrigin, + targetUserOrigin, + })), +}; - methods: { - resolved(reportId) { - this.$refs.reports.removeItem(item => item.id === reportId); - }, +function resolved(reportId) { + reports.removeItem(item => item.id === reportId); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.abuseReports, + icon: 'fas fa-exclamation-circle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue index 8f164caa99..b18e08db96 100644 --- a/packages/client/src/pages/admin/ads.vue +++ b/packages/client/src/pages/admin/ads.vue @@ -7,7 +7,7 @@ <template #label>URL</template> </MkInput> <MkInput v-model="ad.imageUrl" class="_formBlock"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> <FormRadios v-model="ad.place" class="_formBlock"> <template #label>Form</template> @@ -17,34 +17,34 @@ </FormRadios> <!-- <div style="margin: 32px 0;"> - {{ $ts.priority }} - <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> - <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> - <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> + {{ i18n.ts.priority }} + <MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio> + <MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio> + <MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio> </div> --> <FormSplit> <MkInput v-model="ad.ratio" type="number"> - <template #label>{{ $ts.ratio }}</template> + <template #label>{{ i18n.ts.ratio }}</template> </MkInput> <MkInput v-model="ad.expiresAt" type="date"> - <template #label>{{ $ts.expiration }}</template> + <template #label>{{ i18n.ts.expiration }}</template> </MkInput> </FormSplit> <MkTextarea v-model="ad.memo" class="_formBlock"> - <template #label>{{ $ts.memo }}</template> + <template #label>{{ i18n.ts.memo }}</template> </MkTextarea> <div class="buttons _formBlock"> - <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; @@ -52,81 +52,65 @@ import FormRadios from '@/components/form/radios.vue'; import FormSplit from '@/components/form/split.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - FormRadios, - FormSplit, - }, +let ads: any[] = $ref([]); - emits: ['info'], +os.api('admin/ad/list').then(adsResponse => { + ads = adsResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.ads, - icon: 'fas fa-audio-description', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - ads: [], - } - }, +function add() { + ads.unshift({ + id: null, + memo: '', + place: 'square', + priority: 'middle', + ratio: 1, + url: '', + imageUrl: null, + expiresAt: null, + }); +} - created() { - os.api('admin/ad/list').then(ads => { - this.ads = ads; +function remove(ad) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: ad.url }), + }).then(({ canceled }) => { + if (canceled) return; + ads = ads.filter(x => x !== ad); + os.apiWithDialog('admin/ad/delete', { + id: ad.id }); - }, - - methods: { - add() { - this.ads.unshift({ - id: null, - memo: '', - place: 'square', - priority: 'middle', - ratio: 1, - url: '', - imageUrl: null, - expiresAt: null, - }); - }, + }); +} - remove(ad) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: ad.url }), - }).then(({ canceled }) => { - if (canceled) return; - this.ads = this.ads.filter(x => x != ad); - os.apiWithDialog('admin/ad/delete', { - id: ad.id - }); - }); - }, +function save(ad) { + if (ad.id == null) { + os.apiWithDialog('admin/ad/create', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } else { + os.apiWithDialog('admin/ad/update', { + ...ad, + expiresAt: new Date(ad.expiresAt).getTime() + }); + } +} - save(ad) { - if (ad.id == null) { - os.apiWithDialog('admin/ad/create', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } else { - os.apiWithDialog('admin/ad/update', { - ...ad, - expiresAt: new Date(ad.expiresAt).getTime() - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.ads, + icon: 'fas fa-audio-description', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue index a0d720bb29..97774975de 100644 --- a/packages/client/src/pages/admin/announcements.vue +++ b/packages/client/src/pages/admin/announcements.vue @@ -3,112 +3,98 @@ <section v-for="announcement in announcements" class="_card _gap announcements"> <div class="_content announcement"> <MkInput v-model="announcement.title"> - <template #label>{{ $ts.title }}</template> + <template #label>{{ i18n.ts.title }}</template> </MkInput> <MkTextarea v-model="announcement.text"> - <template #label>{{ $ts.text }}</template> + <template #label>{{ i18n.ts.text }}</template> </MkTextarea> <MkInput v-model="announcement.imageUrl"> - <template #label>{{ $ts.imageUrl }}</template> + <template #label>{{ i18n.ts.imageUrl }}</template> </MkInput> - <p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p> + <p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p> <div class="buttons"> - <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> - <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> + <MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </div> </section> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkInput, - MkTextarea, - }, +let announcements: any[] = $ref([]); - emits: ['info'], +os.api('admin/announcements/list').then(announcementResponse => { + announcements = announcementResponse; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.announcements, - icon: 'fas fa-broadcast-tower', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.add, - handler: this.add, - }], - }, - announcements: [], - } - }, +function add() { + announcements.unshift({ + id: null, + title: '', + text: '', + imageUrl: null + }); +} - created() { - os.api('admin/announcements/list').then(announcements => { - this.announcements = announcements; - }); - }, +function remove(announcement) { + os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: announcement.title }), + }).then(({ canceled }) => { + if (canceled) return; + announcements = announcements.filter(x => x !== announcement); + os.api('admin/announcements/delete', announcement); + }); +} - methods: { - add() { - this.announcements.unshift({ - id: null, - title: '', - text: '', - imageUrl: null +function save(announcement) { + if (announcement.id == null) { + os.api('admin/announcements/create', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved }); - }, - - remove(announcement) { - os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: announcement.title }), - }).then(({ canceled }) => { - if (canceled) return; - this.announcements = this.announcements.filter(x => x != announcement); - os.api('admin/announcements/delete', announcement); + }).catch(err => { + os.alert({ + type: 'error', + text: err }); - }, + }); + } else { + os.api('admin/announcements/update', announcement).then(() => { + os.alert({ + type: 'success', + text: i18n.ts.saved + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err + }); + }); + } +} - save(announcement) { - if (announcement.id == null) { - os.api('admin/announcements/create', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } else { - os.api('admin/announcements/update', announcement).then(() => { - os.alert({ - type: 'success', - text: this.$ts.saved - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.announcements, + icon: 'fas fa-broadcast-tower', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.add, + handler: add, + }], } }); </script> diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue index 5e0cdd96a5..30fee5015a 100644 --- a/packages/client/src/pages/admin/bot-protection.vue +++ b/packages/client/src/pages/admin/bot-protection.vue @@ -43,8 +43,8 @@ </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent } from 'vue'; import FormRadios from '@/components/form/radios.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; @@ -54,64 +54,39 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormRadios, - FormInput, - FormButton, - FormSuspense, - FormSlot, - MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')), - }, +const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue')); - emits: ['info'], +let provider = $ref(null); +let hcaptchaSiteKey: string | null = $ref(null); +let hcaptchaSecretKey: string | null = $ref(null); +let recaptchaSiteKey: string | null = $ref(null); +let recaptchaSecretKey: string | null = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.botProtection, - icon: 'fas fa-shield-alt' - }, - provider: null, - enableHcaptcha: false, - hcaptchaSiteKey: null, - hcaptchaSecretKey: null, - enableRecaptcha: false, - recaptchaSiteKey: null, - recaptchaSecretKey: null, - } - }, +const enableHcaptcha = $computed(() => provider === 'hcaptcha'); +const enableRecaptcha = $computed(() => provider === 'recaptcha'); - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableHcaptcha = meta.enableHcaptcha; - this.hcaptchaSiteKey = meta.hcaptchaSiteKey; - this.hcaptchaSecretKey = meta.hcaptchaSecretKey; - this.enableRecaptcha = meta.enableRecaptcha; - this.recaptchaSiteKey = meta.recaptchaSiteKey; - this.recaptchaSecretKey = meta.recaptchaSecretKey; +async function init() { + const meta = await os.api('admin/meta'); + enableHcaptcha = meta.enableHcaptcha; + hcaptchaSiteKey = meta.hcaptchaSiteKey; + hcaptchaSecretKey = meta.hcaptchaSecretKey; + enableRecaptcha = meta.enableRecaptcha; + recaptchaSiteKey = meta.recaptchaSiteKey; + recaptchaSecretKey = meta.recaptchaSecretKey; - this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null; + provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null; +} - this.$watch(() => this.provider, () => { - this.enableHcaptcha = this.provider === 'hcaptcha'; - this.enableRecaptcha = this.provider === 'recaptcha'; - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableHcaptcha: this.enableHcaptcha, - hcaptchaSiteKey: this.hcaptchaSiteKey, - hcaptchaSecretKey: this.hcaptchaSecretKey, - enableRecaptcha: this.enableRecaptcha, - recaptchaSiteKey: this.recaptchaSiteKey, - recaptchaSecretKey: this.recaptchaSecretKey, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableHcaptcha, + hcaptchaSiteKey, + hcaptchaSecretKey, + enableRecaptcha, + recaptchaSiteKey, + recaptchaSecretKey, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue index 3a835eeafa..d3519922b1 100644 --- a/packages/client/src/pages/admin/database.vue +++ b/packages/client/src/pages/admin/database.vue @@ -9,36 +9,23 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import bytes from '@/filters/bytes'; import number from '@/filters/number'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - MkKeyValue, - }, +const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.database, - icon: 'fas fa-database', - bg: 'var(--bg)', - }, - databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)), - } - }, - - methods: { - bytes, number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.database, + icon: 'fas fa-database', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue index 7df0b6db1c..aa13043193 100644 --- a/packages/client/src/pages/admin/email-settings.vue +++ b/packages/client/src/pages/admin/email-settings.vue @@ -3,37 +3,37 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormSwitch v-model="enableEmail" class="_formBlock"> - <template #label>{{ $ts.enableEmail }}</template> - <template #caption>{{ $ts.emailConfigInfo }}</template> + <template #label>{{ i18n.ts.enableEmail }}</template> + <template #caption>{{ i18n.ts.emailConfigInfo }}</template> </FormSwitch> <template v-if="enableEmail"> <FormInput v-model="email" type="email" class="_formBlock"> - <template #label>{{ $ts.emailAddress }}</template> + <template #label>{{ i18n.ts.emailAddress }}</template> </FormInput> <FormSection> - <template #label>{{ $ts.smtpConfig }}</template> + <template #label>{{ i18n.ts.smtpConfig }}</template> <FormSplit :min-width="280"> <FormInput v-model="smtpHost" class="_formBlock"> - <template #label>{{ $ts.smtpHost }}</template> + <template #label>{{ i18n.ts.smtpHost }}</template> </FormInput> <FormInput v-model="smtpPort" type="number" class="_formBlock"> - <template #label>{{ $ts.smtpPort }}</template> + <template #label>{{ i18n.ts.smtpPort }}</template> </FormInput> </FormSplit> <FormSplit :min-width="280"> <FormInput v-model="smtpUser" class="_formBlock"> - <template #label>{{ $ts.smtpUser }}</template> + <template #label>{{ i18n.ts.smtpUser }}</template> </FormInput> <FormInput v-model="smtpPass" type="password" class="_formBlock"> - <template #label>{{ $ts.smtpPass }}</template> + <template #label>{{ i18n.ts.smtpPass }}</template> </FormInput> </FormSplit> - <FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo> <FormSwitch v-model="smtpSecure" class="_formBlock"> - <template #label>{{ $ts.smtpSecure }}</template> - <template #caption>{{ $ts.smtpSecureInfo }}</template> + <template #label>{{ i18n.ts.smtpSecure }}</template> + <template #caption>{{ i18n.ts.smtpSecureInfo }}</template> </FormSwitch> </FormSection> </template> @@ -42,8 +42,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -52,86 +52,71 @@ import FormSplit from '@/components/form/split.vue'; import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; +import { fetchInstance, instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSplit, - FormSection, - FormInfo, - FormSuspense, - }, +let enableEmail: boolean = $ref(false); +let email: any = $ref(null); +let smtpSecure: boolean = $ref(false); +let smtpHost: string = $ref(''); +let smtpPort: number = $ref(0); +let smtpUser: string = $ref(''); +let smtpPass: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + enableEmail = meta.enableEmail; + email = meta.email; + smtpSecure = meta.smtpSecure; + smtpHost = meta.smtpHost; + smtpPort = meta.smtpPort; + smtpUser = meta.smtpUser; + smtpPass = meta.smtpPass; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.emailServer, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - text: this.$ts.testEmail, - handler: this.testEmail, - }, { - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - enableEmail: false, - email: null, - smtpSecure: false, - smtpHost: '', - smtpPort: 0, - smtpUser: '', - smtpPass: '', - } - }, +async function testEmail() { + const { canceled, result: destination } = await os.inputText({ + title: i18n.ts.destination, + type: 'email', + placeholder: instance.maintainerEmail + }); + if (canceled) return; + os.apiWithDialog('admin/send-email', { + to: destination, + subject: 'Test email', + text: 'Yo' + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableEmail = meta.enableEmail; - this.email = meta.email; - this.smtpSecure = meta.smtpSecure; - this.smtpHost = meta.smtpHost; - this.smtpPort = meta.smtpPort; - this.smtpUser = meta.smtpUser; - this.smtpPass = meta.smtpPass; - }, +function save() { + os.apiWithDialog('admin/update-meta', { + enableEmail, + email, + smtpSecure, + smtpHost, + smtpPort, + smtpUser, + smtpPass, + }).then(() => { + fetchInstance(); + }); +} - async testEmail() { - const { canceled, result: destination } = await os.inputText({ - title: this.$ts.destination, - type: 'email', - placeholder: this.$instance.maintainerEmail - }); - if (canceled) return; - os.apiWithDialog('admin/send-email', { - to: destination, - subject: 'Test email', - text: 'Yo' - }); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - enableEmail: this.enableEmail, - email: this.email, - smtpSecure: this.smtpSecure, - smtpHost: this.smtpHost, - smtpPort: this.smtpPort, - smtpUser: this.smtpUser, - smtpPass: this.smtpPass, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.emailServer, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + text: i18n.ts.testEmail, + handler: testEmail, + }, { + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue index 2e3903426e..d482fa49e6 100644 --- a/packages/client/src/pages/admin/emoji-edit-dialog.vue +++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue @@ -27,85 +27,71 @@ </XModalWindow> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import * as os from '@/os'; import { unique } from '@/scripts/array'; +import { i18n } from '@/i18n'; +import { emojiCategories } from '@/instance'; -export default defineComponent({ - components: { - XModalWindow, - MkButton, - MkInput, - }, +const props = defineProps<{ + emoji: any, +}>(); - props: { - emoji: { - required: true, - } - }, - - emits: ['done', 'closed'], +let dialog = $ref(null); +let name: string = $ref(props.emoji.name); +let category: string = $ref(props.emoji.category); +let aliases: string = $ref(props.emoji.aliases.join(' ')); +let categories: string[] = $ref(emojiCategories); - data() { - return { - name: this.emoji.name, - category: this.emoji.category, - aliases: this.emoji.aliases?.join(' '), - categories: [], - } - }, +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean, updated?: any }): void, + (ev: 'closed'): void +}>(); - created() { - os.api('meta', { detail: false }).then(({ emojis }) => { - this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== '')); - }); - }, +function ok() { + update(); +} - methods: { - ok() { - this.update(); - }, +async function update() { + await os.apiWithDialog('admin/emoji/update', { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), + }); - async update() { - await os.apiWithDialog('admin/emoji/update', { - id: this.emoji.id, - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - }); + emit('done', { + updated: { + id: props.emoji.id, + name, + category, + aliases: aliases.split(' '), + } + }); - this.$emit('done', { - updated: { - name: this.name, - category: this.category, - aliases: this.aliases.split(' '), - } - }); - this.$refs.dialog.close(); - }, + dialog.close(); +} - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.emoji.name }), - }); - if (canceled) return; +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: name }), + }); + if (canceled) return; - os.api('admin/emoji/delete', { - id: this.emoji.id - }).then(() => { - this.$emit('done', { - deleted: true - }); - this.$refs.dialog.close(); - }); - }, - } -}); + os.api('admin/emoji/delete', { + id: props.emoji.id + }).then(() => { + emit('done', { + deleted: true + }); + dialog.close(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue index a080ee9c23..8ca5b3d65c 100644 --- a/packages/client/src/pages/admin/emojis.vue +++ b/packages/client/src/pages/admin/emojis.vue @@ -63,7 +63,7 @@ </template> <script lang="ts" setup> -import { computed, defineComponent, ref, toRef } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkPagination from '@/components/ui/pagination.vue'; @@ -130,17 +130,17 @@ const add = async (ev: MouseEvent) => { }; const edit = (emoji) => { - os.popup(import('./emoji-edit-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), { emoji: emoji }, { done: result => { if (result.updated) { - emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, { - ...emoji, + emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + ...oldEmoji, ...result.updated - }); + })); } else if (result.deleted) { - emojisPaginationComponent.value.removeItem(item => item.id === emoji.id); + emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id); } }, }, 'closed'); @@ -159,7 +159,7 @@ const remoteMenu = (emoji, ev: MouseEvent) => { }, { text: i18n.ts.import, icon: 'fas fa-plus', - action: () => { im(emoji) } + action: () => { im(emoji); } }], ev.currentTarget ?? ev.target); }; @@ -175,10 +175,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } @@ -195,10 +195,10 @@ const menu = (ev: MouseEvent) => { type: 'info', text: i18n.ts.importRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/admin/file-dialog.vue b/packages/client/src/pages/admin/file-dialog.vue index 4c33f62399..0765548aab 100644 --- a/packages/client/src/pages/admin/file-dialog.vue +++ b/packages/client/src/pages/admin/file-dialog.vue @@ -34,74 +34,52 @@ </XModalWindow> </template> -<script lang="ts"> -import { computed, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkSwitch from '@/components/form/switch.vue'; import XModalWindow from '@/components/ui/modal-window.vue'; import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue'; import bytes from '@/filters/bytes'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - MkSwitch, - XModalWindow, - MkDriveFileThumbnail, - }, +let file: any = $ref(null); +let info: any = $ref(null); +let isSensitive: boolean = $ref(false); - props: { - fileId: { - required: true, - } - }, - - emits: ['closed'], - - data() { - return { - file: null, - info: null, - isSensitive: false, - }; - }, +const props = defineProps<{ + fileId: string, +}>(); - created() { - this.fetch(); - }, - - methods: { - async fetch() { - this.file = await os.api('drive/files/show', { fileId: this.fileId }); - this.info = await os.api('admin/drive/show-file', { fileId: this.fileId }); - this.isSensitive = this.file.isSensitive; - }, +async function fetch() { + file = await os.api('drive/files/show', { fileId: props.fileId }); + info = await os.api('admin/drive/show-file', { fileId: props.fileId }); + isSensitive = file.isSensitive; +} - showUser() { - os.pageWindow(`/user-info/${this.file.userId}`); - }, +fetch(); - async del() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.file.name }), - }); - if (canceled) return; +function showUser() { + os.pageWindow(`/user-info/${file.userId}`); +} - os.apiWithDialog('drive/files/delete', { - fileId: this.file.id - }); - }, +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: file.name }), + }); + if (canceled) return; - async toggleIsSensitive(v) { - await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v }); - this.isSensitive = v; - }, + os.apiWithDialog('drive/files/delete', { + fileId: file.id + }); +} - bytes - } -}); +async function toggleIsSensitive(v) { + await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v }); + isSensitive = v; +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue index c62f053092..3cda688698 100644 --- a/packages/client/src/pages/admin/files.vue +++ b/packages/client/src/pages/admin/files.vue @@ -55,7 +55,7 @@ </template> <script lang="ts" setup> -import { computed } from 'vue'; +import { computed, defineAsyncComponent } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkSelect from '@/components/form/select.vue'; @@ -93,7 +93,7 @@ function clear() { } function show(file) { - os.popup(import('./file-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), { fileId: file.id }, {}, 'closed'); } diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue index 6b11650f48..9b7fa5678e 100644 --- a/packages/client/src/pages/admin/index.vue +++ b/packages/client/src/pages/admin/index.vue @@ -1,6 +1,6 @@ <template> <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <MkHeader :info="header"></MkHeader> <MkSpacer :content-max="700" :margin-min="16"> @@ -12,21 +12,21 @@ <MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo> <MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </MkSpacer> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <MkStickyContainer> <template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </MkStickyContainer> </div> </div> </template> -<script lang="ts"> -import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, nextTick, onMounted, onUnmounted, provide, watch } from 'vue'; import { i18n } from '@/i18n'; import MkSuperMenu from '@/components/ui/super-menu.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -35,292 +35,277 @@ import { instance } from '@/instance'; import * as symbols from '@/symbols'; import * as os from '@/os'; import { lookupUser } from '@/scripts/lookup-user'; +import { MisskeyNavigator } from '@/scripts/navigate'; -export default defineComponent({ - components: { - MkSuperMenu, - MkInfo, - }, +const isEmpty = (x: string | null) => x == null || x === ''; - provide: { - shouldOmitHeaderTitle: false, - }, +const nav = new MisskeyNavigator(); - props: { - initialPage: { - type: String, - required: false - } - }, +const indexInfo = { + title: i18n.ts.controlPanel, + icon: 'fas fa-cog', + bg: 'var(--bg)', + hideHeader: true, +}; - setup(props, context) { - const indexInfo = { - title: i18n.ts.controlPanel, - icon: 'fas fa-cog', - bg: 'var(--bg)', - hideHeader: true, - }; - const INFO = ref(indexInfo); - const childInfo = ref(null); - const page = ref(props.initialPage); - const narrow = ref(false); - const view = ref(null); - const el = ref(null); - const pageChanged = (page) => { - if (page == null) return; - const viewInfo = page[symbols.PAGE_INFO]; - if (isRef(viewInfo)) { - watch(viewInfo, () => { - childInfo.value = viewInfo.value; - }, { immediate: true }); - } else { - childInfo.value = viewInfo; - } - }; - const pageProps = ref({}); +const props = defineProps<{ + initialPage?: string, +}>(); - const isEmpty = (x: any) => x == null || x == ''; +provide('shouldOmitHeaderTitle', false); - const noMaintainerInformation = ref(false); - const noBotProtection = ref(false); +let INFO = $ref(indexInfo); +let childInfo = $ref(null); +let page = $ref(props.initialPage); +let narrow = $ref(false); +let view = $ref(null); +let el = $ref(null); +let pageProps = $ref({}); +let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail); +let noBotProtection = !instance.enableHcaptcha && !instance.enableRecaptcha; - os.api('meta', { detail: true }).then(meta => { - // TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する - noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail); - noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha; - }); +const NARROW_THRESHOLD = 600; +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); - const menuDef = computed(() => [{ - title: i18n.ts.quickAction, - items: [{ - type: 'button', - icon: 'fas fa-search', - text: i18n.ts.lookup, - action: lookup, - }, ...(instance.disableRegistration ? [{ - type: 'button', - icon: 'fas fa-user', - text: i18n.ts.invite, - action: invite, - }] : [])], - }, { - title: i18n.ts.administration, - items: [{ - icon: 'fas fa-tachometer-alt', - text: i18n.ts.dashboard, - to: '/admin/overview', - active: page.value === 'overview', - }, { - icon: 'fas fa-users', - text: i18n.ts.users, - to: '/admin/users', - active: page.value === 'users', - }, { - icon: 'fas fa-laugh', - text: i18n.ts.customEmojis, - to: '/admin/emojis', - active: page.value === 'emojis', - }, { - icon: 'fas fa-globe', - text: i18n.ts.federation, - to: '/admin/federation', - active: page.value === 'federation', - }, { - icon: 'fas fa-clipboard-list', - text: i18n.ts.jobQueue, - to: '/admin/queue', - active: page.value === 'queue', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.files, - to: '/admin/files', - active: page.value === 'files', - }, { - icon: 'fas fa-broadcast-tower', - text: i18n.ts.announcements, - to: '/admin/announcements', - active: page.value === 'announcements', - }, { - icon: 'fas fa-audio-description', - text: i18n.ts.ads, - to: '/admin/ads', - active: page.value === 'ads', - }, { - icon: 'fas fa-exclamation-circle', - text: i18n.ts.abuseReports, - to: '/admin/abuses', - active: page.value === 'abuses', - }], - }, { - title: i18n.ts.settings, - items: [{ - icon: 'fas fa-cog', - text: i18n.ts.general, - to: '/admin/settings', - active: page.value === 'settings', - }, { - icon: 'fas fa-envelope', - text: i18n.ts.emailServer, - to: '/admin/email-settings', - active: page.value === 'email-settings', - }, { - icon: 'fas fa-cloud', - text: i18n.ts.objectStorage, - to: '/admin/object-storage', - active: page.value === 'object-storage', - }, { - icon: 'fas fa-lock', - text: i18n.ts.security, - to: '/admin/security', - active: page.value === 'security', - }, { - icon: 'fas fa-globe', - text: i18n.ts.relays, - to: '/admin/relays', - active: page.value === 'relays', - }, { - icon: 'fas fa-share-alt', - text: i18n.ts.integration, - to: '/admin/integrations', - active: page.value === 'integrations', - }, { - icon: 'fas fa-ban', - text: i18n.ts.instanceBlocking, - to: '/admin/instance-block', - active: page.value === 'instance-block', - }, { - icon: 'fas fa-ghost', - text: i18n.ts.proxyAccount, - to: '/admin/proxy-account', - active: page.value === 'proxy-account', - }, { - icon: 'fas fa-cogs', - text: i18n.ts.other, - to: '/admin/other-settings', - active: page.value === 'other-settings', - }], - }, { - title: i18n.ts.info, - items: [{ - icon: 'fas fa-database', - text: i18n.ts.database, - to: '/admin/database', - active: page.value === 'database', - }], - }]); - const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { - case 'overview': return defineAsyncComponent(() => import('./overview.vue')); - case 'users': return defineAsyncComponent(() => import('./users.vue')); - case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); - case 'federation': return defineAsyncComponent(() => import('../federation.vue')); - case 'queue': return defineAsyncComponent(() => import('./queue.vue')); - case 'files': return defineAsyncComponent(() => import('./files.vue')); - case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); - case 'ads': return defineAsyncComponent(() => import('./ads.vue')); - case 'database': return defineAsyncComponent(() => import('./database.vue')); - case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); - case 'settings': return defineAsyncComponent(() => import('./settings.vue')); - case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); - case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); - case 'security': return defineAsyncComponent(() => import('./security.vue')); - case 'relays': return defineAsyncComponent(() => import('./relays.vue')); - case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); - case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); - case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); - case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); - } - }); +const menuDef = $computed(() => [{ + title: i18n.ts.quickAction, + items: [{ + type: 'button', + icon: 'fas fa-search', + text: i18n.ts.lookup, + action: lookup, + }, ...(instance.disableRegistration ? [{ + type: 'button', + icon: 'fas fa-user', + text: i18n.ts.invite, + action: invite, + }] : [])], +}, { + title: i18n.ts.administration, + items: [{ + icon: 'fas fa-tachometer-alt', + text: i18n.ts.dashboard, + to: '/admin/overview', + active: props.initialPage === 'overview', + }, { + icon: 'fas fa-users', + text: i18n.ts.users, + to: '/admin/users', + active: props.initialPage === 'users', + }, { + icon: 'fas fa-laugh', + text: i18n.ts.customEmojis, + to: '/admin/emojis', + active: props.initialPage === 'emojis', + }, { + icon: 'fas fa-globe', + text: i18n.ts.federation, + to: '/admin/federation', + active: props.initialPage === 'federation', + }, { + icon: 'fas fa-clipboard-list', + text: i18n.ts.jobQueue, + to: '/admin/queue', + active: props.initialPage === 'queue', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.files, + to: '/admin/files', + active: props.initialPage === 'files', + }, { + icon: 'fas fa-broadcast-tower', + text: i18n.ts.announcements, + to: '/admin/announcements', + active: props.initialPage === 'announcements', + }, { + icon: 'fas fa-audio-description', + text: i18n.ts.ads, + to: '/admin/ads', + active: props.initialPage === 'ads', + }, { + icon: 'fas fa-exclamation-circle', + text: i18n.ts.abuseReports, + to: '/admin/abuses', + active: props.initialPage === 'abuses', + }], +}, { + title: i18n.ts.settings, + items: [{ + icon: 'fas fa-cog', + text: i18n.ts.general, + to: '/admin/settings', + active: props.initialPage === 'settings', + }, { + icon: 'fas fa-envelope', + text: i18n.ts.emailServer, + to: '/admin/email-settings', + active: props.initialPage === 'email-settings', + }, { + icon: 'fas fa-cloud', + text: i18n.ts.objectStorage, + to: '/admin/object-storage', + active: props.initialPage === 'object-storage', + }, { + icon: 'fas fa-lock', + text: i18n.ts.security, + to: '/admin/security', + active: props.initialPage === 'security', + }, { + icon: 'fas fa-globe', + text: i18n.ts.relays, + to: '/admin/relays', + active: props.initialPage === 'relays', + }, { + icon: 'fas fa-share-alt', + text: i18n.ts.integration, + to: '/admin/integrations', + active: props.initialPage === 'integrations', + }, { + icon: 'fas fa-ban', + text: i18n.ts.instanceBlocking, + to: '/admin/instance-block', + active: props.initialPage === 'instance-block', + }, { + icon: 'fas fa-ghost', + text: i18n.ts.proxyAccount, + to: '/admin/proxy-account', + active: props.initialPage === 'proxy-account', + }, { + icon: 'fas fa-cogs', + text: i18n.ts.other, + to: '/admin/other-settings', + active: props.initialPage === 'other-settings', + }], +}, { + title: i18n.ts.info, + items: [{ + icon: 'fas fa-database', + text: i18n.ts.database, + to: '/admin/database', + active: props.initialPage === 'database', + }], +}]); - watch(component, () => { - pageProps.value = {}; +const component = $computed(() => { + if (props.initialPage == null) return null; + switch (props.initialPage) { + case 'overview': return defineAsyncComponent(() => import('./overview.vue')); + case 'users': return defineAsyncComponent(() => import('./users.vue')); + case 'emojis': return defineAsyncComponent(() => import('./emojis.vue')); + case 'federation': return defineAsyncComponent(() => import('../federation.vue')); + case 'queue': return defineAsyncComponent(() => import('./queue.vue')); + case 'files': return defineAsyncComponent(() => import('./files.vue')); + case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); + case 'ads': return defineAsyncComponent(() => import('./ads.vue')); + case 'database': return defineAsyncComponent(() => import('./database.vue')); + case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); + case 'settings': return defineAsyncComponent(() => import('./settings.vue')); + case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue')); + case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue')); + case 'security': return defineAsyncComponent(() => import('./security.vue')); + case 'relays': return defineAsyncComponent(() => import('./relays.vue')); + case 'integrations': return defineAsyncComponent(() => import('./integrations.vue')); + case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue')); + case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue')); + case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue')); + } +}); - nextTick(() => { - scroll(el.value, { top: 0 }); - }); - }, { immediate: true }); +watch(component, () => { + pageProps = {}; - watch(() => props.initialPage, () => { - if (props.initialPage == null && !narrow.value) { - page.value = 'overview'; - } else { - page.value = props.initialPage; - if (props.initialPage == null) { - INFO.value = indexInfo; - } - } - }); + nextTick(() => { + scroll(el, { top: 0 }); + }); +}, { immediate: true }); - onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'overview'; - } - }); +watch(() => props.initialPage, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } else { + if (props.initialPage == null) { + INFO = indexInfo; + } + } +}); - const invite = () => { - os.api('admin/invite').then(x => { - os.alert({ - type: 'info', - text: x.code - }); - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }; +watch(narrow, () => { + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); - const lookup = (ev) => { - os.popupMenu([{ - text: i18n.ts.user, - icon: 'fas fa-user', - action: () => { - lookupUser(); - } - }, { - text: i18n.ts.note, - icon: 'fas fa-pencil-alt', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.file, - icon: 'fas fa-cloud', - action: () => { - alert('TODO'); - } - }, { - text: i18n.ts.instance, - icon: 'fas fa-globe', - action: () => { - alert('TODO'); - } - }], ev.currentTarget ?? ev.target); - }; +onMounted(() => { + ro.observe(el); + + narrow = el.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow) { + nav.push('/admin/overview'); + } +}); + +onUnmounted(() => { + ro.disconnect(); +}); + +const pageChanged = (page) => { + if (page == null) { + childInfo = null; + } else { + childInfo = page[symbols.PAGE_INFO]; + } +}; - return { - [symbols.PAGE_INFO]: INFO, - menuDef, - header: { - title: i18n.ts.controlPanel, - }, - noMaintainerInformation, - noBotProtection, - page, - narrow, - view, - el, - pageChanged, - childInfo, - pageProps, - component, - invite, - lookup, - }; - }, +const invite = () => { + os.api('admin/invite').then(x => { + os.alert({ + type: 'info', + text: x.code + }); + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); +}; + +const lookup = (ev) => { + os.popupMenu([{ + text: i18n.ts.user, + icon: 'fas fa-user', + action: () => { + lookupUser(); + } + }, { + text: i18n.ts.note, + icon: 'fas fa-pencil-alt', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.file, + icon: 'fas fa-cloud', + action: () => { + alert('TODO'); + } + }, { + text: i18n.ts.instance, + icon: 'fas fa-globe', + action: () => { + alert('TODO'); + } + }], ev.currentTarget ?? ev.target); +}; + +defineExpose({ + [symbols.PAGE_INFO]: INFO, + header: { + title: i18n.ts.controlPanel, + } }); </script> diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue index 4cb8dc604e..3347846a80 100644 --- a/packages/client/src/pages/admin/instance-block.vue +++ b/packages/client/src/pages/admin/instance-block.vue @@ -2,57 +2,45 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <FormTextarea v-model="blockedHosts" class="_formBlock"> - <span>{{ $ts.blockedInstances }}</span> - <template #caption>{{ $ts.blockedInstancesDescription }}</template> + <span>{{ i18n.ts.blockedInstances }}</span> + <template #caption>{{ i18n.ts.blockedInstancesDescription }}</template> </FormTextarea> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormSuspense, - }, +let blockedHosts: string = $ref(''); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + blockedHosts = meta.blockedHosts.join('\n'); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceBlocking, - icon: 'fas fa-ban', - bg: 'var(--bg)', - }, - blockedHosts: '', - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + blockedHosts: blockedHosts.split('\n') || [], + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.blockedHosts = meta.blockedHosts.join('\n'); - }, - - save() { - os.apiWithDialog('admin/update-meta', { - blockedHosts: this.blockedHosts.split('\n') || [], - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceBlocking, + icon: 'fas fa-ban', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue index 6b50f1b0a9..9fdc51a6ca 100644 --- a/packages/client/src/pages/admin/integrations.discord.vue +++ b/packages/client/src/pages/admin/integrations.discord.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableDiscordIntegration: boolean = $ref(false); +let discordClientId: string | null = $ref(null); +let discordClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableDiscordIntegration = meta.enableDiscordIntegration; + discordClientId = meta.discordClientId; + discordClientSecret = meta.discordClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Discord', - icon: 'fab fa-discord' - }, - enableDiscordIntegration: false, - discordClientId: null, - discordClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - this.discordClientId = meta.discordClientId; - this.discordClientSecret = meta.discordClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableDiscordIntegration: this.enableDiscordIntegration, - discordClientId: this.discordClientId, - discordClientSecret: this.discordClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableDiscordIntegration, + discordClientId, + discordClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue index 67f299e1bc..b10ccb8394 100644 --- a/packages/client/src/pages/admin/integrations.github.vue +++ b/packages/client/src/pages/admin/integrations.github.vue @@ -24,57 +24,36 @@ </FormSuspense> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableGithubIntegration: boolean = $ref(false); +let githubClientId: string | null = $ref(null); +let githubClientSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableGithubIntegration = meta.enableGithubIntegration; + githubClientId = meta.githubClientId; + githubClientSecret = meta.githubClientSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'GitHub', - icon: 'fab fa-github' - }, - enableGithubIntegration: false, - githubClientId: null, - githubClientSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.githubClientId = meta.githubClientId; - this.githubClientSecret = meta.githubClientSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableGithubIntegration: this.enableGithubIntegration, - githubClientId: this.githubClientId, - githubClientSecret: this.githubClientSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableGithubIntegration, + githubClientId, + githubClientSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue index a389c71506..11b5fd86b2 100644 --- a/packages/client/src/pages/admin/integrations.twitter.vue +++ b/packages/client/src/pages/admin/integrations.twitter.vue @@ -24,7 +24,7 @@ </FormSuspense> </template> -<script lang="ts"> +<script lang="ts" setup> import { defineComponent } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; @@ -32,49 +32,28 @@ import FormButton from '@/components/ui/button.vue'; import FormInfo from '@/components/ui/info.vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormInfo, - FormButton, - FormSuspense, - }, +let uri: string = $ref(''); +let enableTwitterIntegration: boolean = $ref(false); +let twitterConsumerKey: string | null = $ref(null); +let twitterConsumerSecret: string | null = $ref(null); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + uri = meta.uri; + enableTwitterIntegration = meta.enableTwitterIntegration; + twitterConsumerKey = meta.twitterConsumerKey; + twitterConsumerSecret = meta.twitterConsumerSecret; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: 'Twitter', - icon: 'fab fa-twitter' - }, - enableTwitterIntegration: false, - twitterConsumerKey: null, - twitterConsumerSecret: null, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.uri = meta.uri; - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.twitterConsumerKey = meta.twitterConsumerKey; - this.twitterConsumerSecret = meta.twitterConsumerSecret; - }, - save() { - os.apiWithDialog('admin/update-meta', { - enableTwitterIntegration: this.enableTwitterIntegration, - twitterConsumerKey: this.twitterConsumerKey, - twitterConsumerSecret: this.twitterConsumerSecret, - }).then(() => { - fetchInstance(); - }); - } - } -}); +function save() { + os.apiWithDialog('admin/update-meta', { + enableTwitterIntegration, + twitterConsumerKey, + twitterConsumerSecret, + }).then(() => { + fetchInstance(); + }); +} </script> diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue index 4db8a9e0a9..d6061d0e51 100644 --- a/packages/client/src/pages/admin/integrations.vue +++ b/packages/client/src/pages/admin/integrations.vue @@ -4,69 +4,52 @@ <FormFolder class="_formBlock"> <template #icon><i class="fab fa-twitter"></i></template> <template #label>Twitter</template> - <template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XTwitter/> </FormFolder> - <FormFolder to="/admin/integrations/github" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-github"></i></template> <template #label>GitHub</template> - <template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XGithub/> </FormFolder> - <FormFolder to="/admin/integrations/discord" class="_formBlock"> + <FormFolder class="_formBlock"> <template #icon><i class="fab fa-discord"></i></template> <template #label>Discord</template> - <template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template> + <template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template> <XDiscord/> </FormFolder> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; -import FormSecion from '@/components/form/section.vue'; import FormSuspense from '@/components/form/suspense.vue'; import XTwitter from './integrations.twitter.vue'; import XGithub from './integrations.github.vue'; import XDiscord from './integrations.discord.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { fetchInstance } from '@/instance'; - -export default defineComponent({ - components: { - FormFolder, - FormSecion, - FormSuspense, - XTwitter, - XGithub, - XDiscord, - }, +import { i18n } from '@/i18n'; - emits: ['info'], +let enableTwitterIntegration: boolean = $ref(false); +let enableGithubIntegration: boolean = $ref(false); +let enableDiscordIntegration: boolean = $ref(false); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - enableTwitterIntegration: false, - enableGithubIntegration: false, - enableDiscordIntegration: false, - } - }, +async function init() { + const meta = await os.api('admin/meta'); + enableTwitterIntegration = meta.enableTwitterIntegration; + enableGithubIntegration = meta.enableGithubIntegration; + enableDiscordIntegration = meta.enableDiscordIntegration; +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.enableTwitterIntegration = meta.enableTwitterIntegration; - this.enableGithubIntegration = meta.enableGithubIntegration; - this.enableDiscordIntegration = meta.enableDiscordIntegration; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue index 1de297fd93..7e5f5bb094 100644 --- a/packages/client/src/pages/admin/metrics.vue +++ b/packages/client/src/pages/admin/metrics.vue @@ -132,7 +132,7 @@ export default defineComponent({ overviewHeight: '1fr', queueHeight: '1fr', paused: false, - } + }; }, computed: { diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue index a1ee0761c8..d109db9c38 100644 --- a/packages/client/src/pages/admin/object-storage.vue +++ b/packages/client/src/pages/admin/object-storage.vue @@ -2,32 +2,32 @@ <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> <div class="_formRoot"> - <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch> + <FormSwitch v-model="useObjectStorage" class="_formBlock">{{ i18n.ts.useObjectStorage }}</FormSwitch> <template v-if="useObjectStorage"> <FormInput v-model="objectStorageBaseUrl" class="_formBlock"> - <template #label>{{ $ts.objectStorageBaseUrl }}</template> - <template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBaseUrl }}</template> + <template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template> </FormInput> <FormInput v-model="objectStorageBucket" class="_formBlock"> - <template #label>{{ $ts.objectStorageBucket }}</template> - <template #caption>{{ $ts.objectStorageBucketDesc }}</template> + <template #label>{{ i18n.ts.objectStorageBucket }}</template> + <template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template> </FormInput> <FormInput v-model="objectStoragePrefix" class="_formBlock"> - <template #label>{{ $ts.objectStoragePrefix }}</template> - <template #caption>{{ $ts.objectStoragePrefixDesc }}</template> + <template #label>{{ i18n.ts.objectStoragePrefix }}</template> + <template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template> </FormInput> <FormInput v-model="objectStorageEndpoint" class="_formBlock"> - <template #label>{{ $ts.objectStorageEndpoint }}</template> - <template #caption>{{ $ts.objectStorageEndpointDesc }}</template> + <template #label>{{ i18n.ts.objectStorageEndpoint }}</template> + <template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template> </FormInput> <FormInput v-model="objectStorageRegion" class="_formBlock"> - <template #label>{{ $ts.objectStorageRegion }}</template> - <template #caption>{{ $ts.objectStorageRegionDesc }}</template> + <template #label>{{ i18n.ts.objectStorageRegion }}</template> + <template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template> </FormInput> <FormSplit :min-width="280"> @@ -43,17 +43,17 @@ </FormSplit> <FormSwitch v-model="objectStorageUseSSL" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseSSL }}</template> - <template #caption>{{ $ts.objectStorageUseSSLDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseSSL }}</template> + <template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageUseProxy" class="_formBlock"> - <template #label>{{ $ts.objectStorageUseProxy }}</template> - <template #caption>{{ $ts.objectStorageUseProxyDesc }}</template> + <template #label>{{ i18n.ts.objectStorageUseProxy }}</template> + <template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template> </FormSwitch> <FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock"> - <template #label>{{ $ts.objectStorageSetPublicRead }}</template> + <template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template> </FormSwitch> <FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock"> @@ -65,8 +65,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormGroup from '@/components/form/group.vue'; @@ -76,84 +76,70 @@ import FormSection from '@/components/form/section.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormGroup, - FormSuspense, - FormSplit, - FormSection, - }, +let useObjectStorage: boolean = $ref(false); +let objectStorageBaseUrl: string | null = $ref(null); +let objectStorageBucket: string | null = $ref(null); +let objectStoragePrefix: string | null = $ref(null); +let objectStorageEndpoint: string | null = $ref(null); +let objectStorageRegion: string | null = $ref(null); +let objectStoragePort: number | null = $ref(null); +let objectStorageAccessKey: string | null = $ref(null); +let objectStorageSecretKey: string | null = $ref(null); +let objectStorageUseSSL: boolean = $ref(false); +let objectStorageUseProxy: boolean = $ref(false); +let objectStorageSetPublicRead: boolean = $ref(false); +let objectStorageS3ForcePathStyle: boolean = $ref(true); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + useObjectStorage = meta.useObjectStorage; + objectStorageBaseUrl = meta.objectStorageBaseUrl; + objectStorageBucket = meta.objectStorageBucket; + objectStoragePrefix = meta.objectStoragePrefix; + objectStorageEndpoint = meta.objectStorageEndpoint; + objectStorageRegion = meta.objectStorageRegion; + objectStoragePort = meta.objectStoragePort; + objectStorageAccessKey = meta.objectStorageAccessKey; + objectStorageSecretKey = meta.objectStorageSecretKey; + objectStorageUseSSL = meta.objectStorageUseSSL; + objectStorageUseProxy = meta.objectStorageUseProxy; + objectStorageSetPublicRead = meta.objectStorageSetPublicRead; + objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.objectStorage, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - useObjectStorage: false, - objectStorageBaseUrl: null, - objectStorageBucket: null, - objectStoragePrefix: null, - objectStorageEndpoint: null, - objectStorageRegion: null, - objectStoragePort: null, - objectStorageAccessKey: null, - objectStorageSecretKey: null, - objectStorageUseSSL: false, - objectStorageUseProxy: false, - objectStorageSetPublicRead: false, - objectStorageS3ForcePathStyle: true, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + useObjectStorage, + objectStorageBaseUrl, + objectStorageBucket, + objectStoragePrefix, + objectStorageEndpoint, + objectStorageRegion, + objectStoragePort, + objectStorageAccessKey, + objectStorageSecretKey, + objectStorageUseSSL, + objectStorageUseProxy, + objectStorageSetPublicRead, + objectStorageS3ForcePathStyle, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.useObjectStorage = meta.useObjectStorage; - this.objectStorageBaseUrl = meta.objectStorageBaseUrl; - this.objectStorageBucket = meta.objectStorageBucket; - this.objectStoragePrefix = meta.objectStoragePrefix; - this.objectStorageEndpoint = meta.objectStorageEndpoint; - this.objectStorageRegion = meta.objectStorageRegion; - this.objectStoragePort = meta.objectStoragePort; - this.objectStorageAccessKey = meta.objectStorageAccessKey; - this.objectStorageSecretKey = meta.objectStorageSecretKey; - this.objectStorageUseSSL = meta.objectStorageUseSSL; - this.objectStorageUseProxy = meta.objectStorageUseProxy; - this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead; - this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle; - }, - save() { - os.apiWithDialog('admin/update-meta', { - useObjectStorage: this.useObjectStorage, - objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null, - objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null, - objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null, - objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null, - objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null, - objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null, - objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null, - objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null, - objectStorageUseSSL: this.objectStorageUseSSL, - objectStorageUseProxy: this.objectStorageUseProxy, - objectStorageSetPublicRead: this.objectStorageSetPublicRead, - objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.objectStorage, + icon: 'fas fa-cloud', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue index 99ea6a5f32..552b05f347 100644 --- a/packages/client/src/pages/admin/other-settings.vue +++ b/packages/client/src/pages/admin/other-settings.vue @@ -6,52 +6,35 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import FormSwitch from '@/components/form/switch.vue'; -import FormInput from '@/components/form/input.vue'; -import FormSection from '@/components/form/section.vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSection, - FormSuspense, - }, +async function init() { + await os.api('admin/meta'); +} - emits: ['info'], +function save() { + os.apiWithDialog('admin/update-meta').then(() => { + fetchInstance(); + }); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-cogs', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - } - }, - - methods: { - async init() { - const meta = await os.api('admin/meta'); - }, - save() { - os.apiWithDialog('admin/update-meta', { - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-cogs', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue index b8ae8ad9e1..cc69424c3b 100644 --- a/packages/client/src/pages/admin/overview.vue +++ b/packages/client/src/pages/admin/overview.vue @@ -5,20 +5,20 @@ <div class="label">Users</div> <div class="value _monospace"> {{ number(stats.originalUsersCount) }} - <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> <div class="number _panel"> <div class="label">Notes</div> <div class="value _monospace"> {{ number(stats.originalNotesCount) }} - <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> + <MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff> </div> </div> </div> <MkContainer :foldable="true" class="charts"> - <template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template> + <template #header><i class="fas fa-chart-bar"></i>{{ i18n.ts.charts }}</template> <div style="padding: 12px;"> <MkInstanceStats :chart-limit="500" :detailed="true"/> </div> @@ -38,7 +38,7 @@ <!--<XMetrics/>--> <MkFolder style="margin: var(--margin)"> - <template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template> + <template #header><i class="fas fa-info-circle"></i> {{ i18n.ts.info }}</template> <div class="cfcdecdf"> <div class="number _panel"> <div class="label">Misskey</div> @@ -65,103 +65,61 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, markRaw, version as vueVersion } from 'vue'; +<script lang="ts" setup> +import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkInstanceStats from '@/components/instance-stats.vue'; -import MkButton from '@/components/ui/button.vue'; -import MkSelect from '@/components/form/select.vue'; import MkNumberDiff from '@/components/number-diff.vue'; import MkContainer from '@/components/ui/container.vue'; import MkFolder from '@/components/ui/folder.vue'; import MkQueueChart from '@/components/queue-chart.vue'; import { version, url } from '@/config'; -import bytes from '@/filters/bytes'; import number from '@/filters/number'; import XMetrics from './metrics.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkNumberDiff, - MkInstanceStats, - MkContainer, - MkFolder, - MkQueueChart, - XMetrics, - }, +let stats: any = $ref(null); +let serverInfo: any = $ref(null); +let usersComparedToThePrevDay: any = $ref(null); +let notesComparedToThePrevDay: any = $ref(null); +const queueStatsConnection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +onMounted(async () => { + os.api('stats', {}).then(statsResponse => { + stats = statsResponse; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.dashboard, - icon: 'fas fa-tachometer-alt', - bg: 'var(--bg)', - }, - version, - vueVersion, - url, - stats: null, - meta: null, - serverInfo: null, - usersComparedToThePrevDay: null, - notesComparedToThePrevDay: null, - fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), - fetchModLogs: () => os.api('admin/show-moderation-logs', {}), - queueStatsConnection: markRaw(stream.useChannel('queueStats')), - } - }, - - async mounted() { - os.api('meta', { detail: true }).then(meta => { - this.meta = meta; + os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { + usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1]; }); - - os.api('stats', {}).then(stats => { - this.stats = stats; - - os.api('charts/users', { limit: 2, span: 'day' }).then(chart => { - this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1]; - }); - os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { - this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1]; - }); + os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => { + notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1]; }); + }); - os.api('admin/server-info', {}).then(serverInfo => { - this.serverInfo = serverInfo; - }); + os.api('admin/server-info').then(serverInfoResponse => { + serverInfo = serverInfoResponse; + }); - this.$nextTick(() => { - this.queueStatsConnection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); + nextTick(() => { + queueStatsConnection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, - - beforeUnmount() { - this.queueStatsConnection.dispose(); - }, - - methods: { - async showInstanceInfo(q) { - let instance = q; - if (typeof q === 'string') { - instance = await os.api('federation/show-instance', { - host: q - }); - } - // TODO - }, + }); +}); - bytes, +onBeforeUnmount(() => { + queueStatsConnection.dispose(); +}); - number, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.dashboard, + icon: 'fas fa-tachometer-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue index 00f14a176f..727e20e7e5 100644 --- a/packages/client/src/pages/admin/proxy-account.vue +++ b/packages/client/src/pages/admin/proxy-account.vue @@ -1,19 +1,19 @@ <template> <MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <FormSuspense :p="init"> - <MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts.proxyAccountDescription }}</MkInfo> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.proxyAccount }}</template> - <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template> + <template #key>{{ i18n.ts.proxyAccount }}</template> + <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template> </MkKeyValue> - <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton> + <FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</FormButton> </FormSuspense> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkKeyValue from '@/components/key-value.vue'; import FormButton from '@/components/ui/button.vue'; import MkInfo from '@/components/ui/info.vue'; @@ -21,53 +21,40 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkKeyValue, - FormButton, - MkInfo, - FormSuspense, - }, +let proxyAccount: any = $ref(null); +let proxyAccountId: any = $ref(null); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.proxyAccount, - icon: 'fas fa-ghost', - bg: 'var(--bg)', - }, - proxyAccount: null, - proxyAccountId: null, - } - }, +async function init() { + const meta = await os.api('admin/meta'); + proxyAccountId = meta.proxyAccountId; + if (proxyAccountId) { + proxyAccount = await os.api('users/show', { userId: proxyAccountId }); + } +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.proxyAccountId = meta.proxyAccountId; - if (this.proxyAccountId) { - this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId }); - } - }, +function chooseProxyAccount() { + os.selectUser().then(user => { + proxyAccount = user; + proxyAccountId = user.id; + save(); + }); +} - chooseProxyAccount() { - os.selectUser().then(user => { - this.proxyAccount = user; - this.proxyAccountId = user.id; - this.save(); - }); - }, +function save() { + os.apiWithDialog('admin/update-meta', { + proxyAccountId: proxyAccountId, + }).then(() => { + fetchInstance(); + }); +} - save() { - os.apiWithDialog('admin/update-meta', { - proxyAccountId: this.proxyAccountId, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.proxyAccount, + icon: 'fas fa-ghost', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/queue.chart.vue b/packages/client/src/pages/admin/queue.chart.vue index 136fb63bb6..be63830bdd 100644 --- a/packages/client/src/pages/admin/queue.chart.vue +++ b/packages/client/src/pages/admin/queue.chart.vue @@ -26,62 +26,40 @@ </div> </template> -<script lang="ts"> -import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue'; +<script lang="ts" setup> +import { onMounted, onUnmounted, ref } from 'vue'; import number from '@/filters/number'; import MkQueueChart from '@/components/queue-chart.vue'; import * as os from '@/os'; -export default defineComponent({ - components: { - MkQueueChart - }, +const activeSincePrevTick = ref(0); +const active = ref(0); +const waiting = ref(0); +const delayed = ref(0); +const jobs = ref([]); - props: { - domain: { - type: String, - required: true, - }, - connection: { - required: true, - }, - }, +const props = defineProps<{ + domain: string, + connection: any, +}>(); - setup(props) { - const activeSincePrevTick = ref(0); - const active = ref(0); - const waiting = ref(0); - const delayed = ref(0); - const jobs = ref([]); +onMounted(() => { + os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { + jobs.value = result; + }); - onMounted(() => { - os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => { - jobs.value = result; - }); + const onStats = (stats) => { + activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; + active.value = stats[props.domain].active; + waiting.value = stats[props.domain].waiting; + delayed.value = stats[props.domain].delayed; + }; - const onStats = (stats) => { - activeSincePrevTick.value = stats[props.domain].activeSincePrevTick; - active.value = stats[props.domain].active; - waiting.value = stats[props.domain].waiting; - delayed.value = stats[props.domain].delayed; - }; + props.connection.on('stats', onStats); - props.connection.on('stats', onStats); - - onUnmounted(() => { - props.connection.off('stats', onStats); - }); - }); - - return { - jobs, - activeSincePrevTick, - active, - waiting, - delayed, - number, - }; - }, + onUnmounted(() => { + props.connection.off('stats', onStats); + }); }); </script> diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue index 35fd618c82..656b18199f 100644 --- a/packages/client/src/pages/admin/queue.vue +++ b/packages/client/src/pages/admin/queue.vue @@ -6,71 +6,60 @@ <XQueue :connection="connection" domain="deliver"> <template #title>Out</template> </XQueue> - <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton> + <MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.clearQueue }}</MkButton> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent, markRaw } from 'vue'; +<script lang="ts" setup> +import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MkButton from '@/components/ui/button.vue'; import XQueue from './queue.chart.vue'; import * as os from '@/os'; import { stream } from '@/stream'; import * as symbols from '@/symbols'; import * as config from '@/config'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XQueue, - }, +const connection = markRaw(stream.useChannel('queueStats')); - emits: ['info'], +function clear() { + os.confirm({ + type: 'warning', + title: i18n.ts.clearQueueConfirmTitle, + text: i18n.ts.clearQueueConfirmText, + }).then(({ canceled }) => { + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.jobQueue, - icon: 'fas fa-clipboard-list', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-up-right-from-square', - text: this.$ts.dashboard, - handler: () => { - window.open(config.url + '/queue', '_blank'); - }, - }], - }, - connection: markRaw(stream.useChannel('queueStats')), - } - }, + os.apiWithDialog('admin/queue/clear'); + }); +} - mounted() { - this.$nextTick(() => { - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8), - length: 200 - }); +onMounted(() => { + nextTick(() => { + connection.send('requestLog', { + id: Math.random().toString().substr(2, 8), + length: 200 }); - }, - - beforeUnmount() { - this.connection.dispose(); - }, + }); +}); - methods: { - clear() { - os.confirm({ - type: 'warning', - title: this.$ts.clearQueueConfirmTitle, - text: this.$ts.clearQueueConfirmText, - }).then(({ canceled }) => { - if (canceled) return; +onBeforeUnmount(() => { + connection.dispose(); +}); - os.apiWithDialog('admin/queue/clear', {}); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.jobQueue, + icon: 'fas fa-clipboard-list', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-up-right-from-square', + text: i18n.ts.dashboard, + handler: () => { + window.open(config.url + '/queue', '_blank'); + }, + }], } }); </script> diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue index bb840db0a2..1a36bb4753 100644 --- a/packages/client/src/pages/admin/relays.vue +++ b/packages/client/src/pages/admin/relays.vue @@ -8,84 +8,71 @@ <i v-else class="fas fa-clock icon requesting"></i> <span>{{ $t(`_relayStatus.${relay.status}`) }}</span> </div> - <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton> + <MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton> </div> </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - }, +let relays: any[] = $ref([]); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.relays, - icon: 'fas fa-globe', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-plus', - text: this.$ts.addRelay, - handler: this.addRelay, - }], - }, - relays: [], - inbox: '', - } - }, +async function addRelay() { + const { canceled, result: inbox } = await os.inputText({ + title: i18n.ts.addRelay, + type: 'url', + placeholder: i18n.ts.inboxUrl + }); + if (canceled) return; + os.api('admin/relays/add', { + inbox + }).then((relay: any) => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - created() { - this.refresh(); - }, +function remove(inbox: string) { + os.api('admin/relays/remove', { + inbox + }).then(() => { + refresh(); + }).catch((err: any) => { + os.alert({ + type: 'error', + text: err.message || err + }); + }); +} - methods: { - async addRelay() { - const { canceled, result: inbox } = await os.inputText({ - title: this.$ts.addRelay, - type: 'url', - placeholder: this.$ts.inboxUrl - }); - if (canceled) return; - os.api('admin/relays/add', { - inbox - }).then((relay: any) => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, +function refresh() { + os.api('admin/relays/list').then((relayList: any) => { + relays = relayList; + }); +} - remove(inbox: string) { - os.api('admin/relays/remove', { - inbox - }).then(() => { - this.refresh(); - }).catch((e: any) => { - os.alert({ - type: 'error', - text: e.message || e - }); - }); - }, +refresh(); - refresh() { - os.api('admin/relays/list').then((relays: any) => { - this.relays = relays; - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.relays, + icon: 'fas fa-globe', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-plus', + text: i18n.ts.addRelay, + handler: addRelay, + }], } }); </script> diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue index d1c979b3e0..6b8f70cca5 100644 --- a/packages/client/src/pages/admin/security.vue +++ b/packages/client/src/pages/admin/security.vue @@ -4,10 +4,10 @@ <div class="_formRoot"> <FormFolder class="_formBlock"> <template #icon><i class="fas fa-shield-alt"></i></template> - <template #label>{{ $ts.botProtection }}</template> + <template #label>{{ i18n.ts.botProtection }}</template> <template v-if="enableHcaptcha" #suffix>hCaptcha</template> <template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template> - <template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template> + <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> <XBotProtection/> </FormFolder> @@ -21,7 +21,7 @@ <template #label>Summaly Proxy URL</template> </FormInput> - <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton> + <FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton> </div> </FormFolder> </div> @@ -29,8 +29,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormFolder from '@/components/form/folder.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInfo from '@/components/ui/info.vue'; @@ -42,49 +42,32 @@ import XBotProtection from './bot-protection.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormFolder, - FormSwitch, - FormInfo, - FormSection, - FormSuspense, - FormButton, - FormInput, - XBotProtection, - }, +let summalyProxy: string = $ref(''); +let enableHcaptcha: boolean = $ref(false); +let enableRecaptcha: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + summalyProxy = meta.summalyProxy; + enableHcaptcha = meta.enableHcaptcha; + enableRecaptcha = meta.enableRecaptcha; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - summalyProxy: '', - enableHcaptcha: false, - enableRecaptcha: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + summalyProxy, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.summalyProxy = meta.summalyProxy; - this.enableHcaptcha = meta.enableHcaptcha; - this.enableRecaptcha = meta.enableRecaptcha; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - summalyProxy: this.summalyProxy, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue index f2970d0459..6dc30fe50b 100644 --- a/packages/client/src/pages/admin/settings.vue +++ b/packages/client/src/pages/admin/settings.vue @@ -3,104 +3,104 @@ <FormSuspense :p="init"> <div class="_formRoot"> <FormInput v-model="name" class="_formBlock"> - <template #label>{{ $ts.instanceName }}</template> + <template #label>{{ i18n.ts.instanceName }}</template> </FormInput> <FormTextarea v-model="description" class="_formBlock"> - <template #label>{{ $ts.instanceDescription }}</template> + <template #label>{{ i18n.ts.instanceDescription }}</template> </FormTextarea> <FormInput v-model="tosUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.tosUrl }}</template> + <template #label>{{ i18n.ts.tosUrl }}</template> </FormInput> <FormSplit :min-width="300"> <FormInput v-model="maintainerName" class="_formBlock"> - <template #label>{{ $ts.maintainerName }}</template> + <template #label>{{ i18n.ts.maintainerName }}</template> </FormInput> <FormInput v-model="maintainerEmail" type="email" class="_formBlock"> <template #prefix><i class="fas fa-envelope"></i></template> - <template #label>{{ $ts.maintainerEmail }}</template> + <template #label>{{ i18n.ts.maintainerEmail }}</template> </FormInput> </FormSplit> <FormTextarea v-model="pinnedUsers" class="_formBlock"> - <template #label>{{ $ts.pinnedUsers }}</template> - <template #caption>{{ $ts.pinnedUsersDescription }}</template> + <template #label>{{ i18n.ts.pinnedUsers }}</template> + <template #caption>{{ i18n.ts.pinnedUsersDescription }}</template> </FormTextarea> <FormSection> <FormSwitch v-model="enableRegistration" class="_formBlock"> - <template #label>{{ $ts.enableRegistration }}</template> + <template #label>{{ i18n.ts.enableRegistration }}</template> </FormSwitch> <FormSwitch v-model="emailRequiredForSignup" class="_formBlock"> - <template #label>{{ $ts.emailRequiredForSignup }}</template> + <template #label>{{ i18n.ts.emailRequiredForSignup }}</template> </FormSwitch> </FormSection> <FormSection> - <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch> - <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch> - <FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo> + <FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ i18n.ts.enableLocalTimeline }}</FormSwitch> + <FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ i18n.ts.enableGlobalTimeline }}</FormSwitch> + <FormInfo class="_formBlock">{{ i18n.ts.disablingTimelinesInfo }}</FormInfo> </FormSection> <FormSection> - <template #label>{{ $ts.theme }}</template> + <template #label>{{ i18n.ts.theme }}</template> <FormInput v-model="iconUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.iconUrl }}</template> + <template #label>{{ i18n.ts.iconUrl }}</template> </FormInput> <FormInput v-model="bannerUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.bannerUrl }}</template> + <template #label>{{ i18n.ts.bannerUrl }}</template> </FormInput> <FormInput v-model="backgroundImageUrl" class="_formBlock"> <template #prefix><i class="fas fa-link"></i></template> - <template #label>{{ $ts.backgroundImageUrl }}</template> + <template #label>{{ i18n.ts.backgroundImageUrl }}</template> </FormInput> <FormInput v-model="themeColor" class="_formBlock"> <template #prefix><i class="fas fa-palette"></i></template> - <template #label>{{ $ts.themeColor }}</template> + <template #label>{{ i18n.ts.themeColor }}</template> <template #caption>#RRGGBB</template> </FormInput> <FormTextarea v-model="defaultLightTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultLightTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> <FormTextarea v-model="defaultDarkTheme" class="_formBlock"> - <template #label>{{ $ts.instanceDefaultDarkTheme }}</template> - <template #caption>{{ $ts.instanceDefaultThemeDescription }}</template> + <template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template> + <template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template> </FormTextarea> </FormSection> <FormSection> - <template #label>{{ $ts.files }}</template> + <template #label>{{ i18n.ts.files }}</template> <FormSwitch v-model="cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.cacheRemoteFiles }}</template> - <template #caption>{{ $ts.cacheRemoteFilesDescription }}</template> + <template #label>{{ i18n.ts.cacheRemoteFiles }}</template> + <template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template> </FormSwitch> <FormSplit :min-width="280"> <FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerLocalAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerLocalAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> <FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock"> - <template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template> + <template #label>{{ i18n.ts.driveCapacityPerRemoteAccount }}</template> <template #suffix>MB</template> - <template #caption>{{ $ts.inMb }}</template> + <template #caption>{{ i18n.ts.inMb }}</template> </FormInput> </FormSplit> </FormSection> @@ -109,8 +109,8 @@ <template #label>ServiceWorker</template> <FormSwitch v-model="enableServiceWorker" class="_formBlock"> - <template #label>{{ $ts.enableServiceworker }}</template> - <template #caption>{{ $ts.serviceworkerInfo }}</template> + <template #label>{{ i18n.ts.enableServiceworker }}</template> + <template #caption>{{ i18n.ts.serviceworkerInfo }}</template> </FormSwitch> <template v-if="enableServiceWorker"> @@ -142,8 +142,8 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -154,119 +154,103 @@ import FormSuspense from '@/components/form/suspense.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; import { fetchInstance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormInput, - FormSuspense, - FormTextarea, - FormInfo, - FormSection, - FormSplit, - }, +let name: string | null = $ref(null); +let description: string | null = $ref(null); +let tosUrl: string | null = $ref(null); +let maintainerName: string | null = $ref(null); +let maintainerEmail: string | null = $ref(null); +let iconUrl: string | null = $ref(null); +let bannerUrl: string | null = $ref(null); +let backgroundImageUrl: string | null = $ref(null); +let themeColor: any = $ref(null); +let defaultLightTheme: any = $ref(null); +let defaultDarkTheme: any = $ref(null); +let enableLocalTimeline: boolean = $ref(false); +let enableGlobalTimeline: boolean = $ref(false); +let pinnedUsers: string = $ref(''); +let cacheRemoteFiles: boolean = $ref(false); +let localDriveCapacityMb: any = $ref(0); +let remoteDriveCapacityMb: any = $ref(0); +let enableRegistration: boolean = $ref(false); +let emailRequiredForSignup: boolean = $ref(false); +let enableServiceWorker: boolean = $ref(false); +let swPublicKey: any = $ref(null); +let swPrivateKey: any = $ref(null); +let deeplAuthKey: string = $ref(''); +let deeplIsPro: boolean = $ref(false); - emits: ['info'], +async function init() { + const meta = await os.api('admin/meta'); + name = meta.name; + description = meta.description; + tosUrl = meta.tosUrl; + iconUrl = meta.iconUrl; + bannerUrl = meta.bannerUrl; + backgroundImageUrl = meta.backgroundImageUrl; + themeColor = meta.themeColor; + defaultLightTheme = meta.defaultLightTheme; + defaultDarkTheme = meta.defaultDarkTheme; + maintainerName = meta.maintainerName; + maintainerEmail = meta.maintainerEmail; + enableLocalTimeline = !meta.disableLocalTimeline; + enableGlobalTimeline = !meta.disableGlobalTimeline; + pinnedUsers = meta.pinnedUsers.join('\n'); + cacheRemoteFiles = meta.cacheRemoteFiles; + localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; + remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; + enableRegistration = !meta.disableRegistration; + emailRequiredForSignup = meta.emailRequiredForSignup; + enableServiceWorker = meta.enableServiceWorker; + swPublicKey = meta.swPublickey; + swPrivateKey = meta.swPrivateKey; + deeplAuthKey = meta.deeplAuthKey; + deeplIsPro = meta.deeplIsPro; +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cog', - bg: 'var(--bg)', - actions: [{ - asFullButton: true, - icon: 'fas fa-check', - text: this.$ts.save, - handler: this.save, - }], - }, - name: null, - description: null, - tosUrl: null as string | null, - maintainerName: null, - maintainerEmail: null, - iconUrl: null, - bannerUrl: null, - backgroundImageUrl: null, - themeColor: null, - defaultLightTheme: null, - defaultDarkTheme: null, - enableLocalTimeline: false, - enableGlobalTimeline: false, - pinnedUsers: '', - cacheRemoteFiles: false, - localDriveCapacityMb: 0, - remoteDriveCapacityMb: 0, - enableRegistration: false, - emailRequiredForSignup: false, - enableServiceWorker: false, - swPublicKey: null, - swPrivateKey: null, - deeplAuthKey: '', - deeplIsPro: false, - } - }, +function save() { + os.apiWithDialog('admin/update-meta', { + name, + description, + tosUrl, + iconUrl, + bannerUrl, + backgroundImageUrl, + themeColor: themeColor === '' ? null : themeColor, + defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme, + defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme, + maintainerName, + maintainerEmail, + disableLocalTimeline: !enableLocalTimeline, + disableGlobalTimeline: !enableGlobalTimeline, + pinnedUsers: pinnedUsers.split('\n'), + cacheRemoteFiles, + localDriveCapacityMb: parseInt(localDriveCapacityMb, 10), + remoteDriveCapacityMb: parseInt(remoteDriveCapacityMb, 10), + disableRegistration: !enableRegistration, + emailRequiredForSignup, + enableServiceWorker, + swPublicKey, + swPrivateKey, + deeplAuthKey, + deeplIsPro, + }).then(() => { + fetchInstance(); + }); +} - methods: { - async init() { - const meta = await os.api('admin/meta'); - this.name = meta.name; - this.description = meta.description; - this.tosUrl = meta.tosUrl; - this.iconUrl = meta.iconUrl; - this.bannerUrl = meta.bannerUrl; - this.backgroundImageUrl = meta.backgroundImageUrl; - this.themeColor = meta.themeColor; - this.defaultLightTheme = meta.defaultLightTheme; - this.defaultDarkTheme = meta.defaultDarkTheme; - this.maintainerName = meta.maintainerName; - this.maintainerEmail = meta.maintainerEmail; - this.enableLocalTimeline = !meta.disableLocalTimeline; - this.enableGlobalTimeline = !meta.disableGlobalTimeline; - this.pinnedUsers = meta.pinnedUsers.join('\n'); - this.cacheRemoteFiles = meta.cacheRemoteFiles; - this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb; - this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb; - this.enableRegistration = !meta.disableRegistration; - this.emailRequiredForSignup = meta.emailRequiredForSignup; - this.enableServiceWorker = meta.enableServiceWorker; - this.swPublicKey = meta.swPublickey; - this.swPrivateKey = meta.swPrivateKey; - this.deeplAuthKey = meta.deeplAuthKey; - this.deeplIsPro = meta.deeplIsPro; - }, - - save() { - os.apiWithDialog('admin/update-meta', { - name: this.name, - description: this.description, - tosUrl: this.tosUrl, - iconUrl: this.iconUrl, - bannerUrl: this.bannerUrl, - backgroundImageUrl: this.backgroundImageUrl, - themeColor: this.themeColor === '' ? null : this.themeColor, - defaultLightTheme: this.defaultLightTheme === '' ? null : this.defaultLightTheme, - defaultDarkTheme: this.defaultDarkTheme === '' ? null : this.defaultDarkTheme, - maintainerName: this.maintainerName, - maintainerEmail: this.maintainerEmail, - disableLocalTimeline: !this.enableLocalTimeline, - disableGlobalTimeline: !this.enableGlobalTimeline, - pinnedUsers: this.pinnedUsers.split('\n'), - cacheRemoteFiles: this.cacheRemoteFiles, - localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10), - remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10), - disableRegistration: !this.enableRegistration, - emailRequiredForSignup: this.emailRequiredForSignup, - enableServiceWorker: this.enableServiceWorker, - swPublicKey: this.swPublicKey, - swPrivateKey: this.swPrivateKey, - deeplAuthKey: this.deeplAuthKey, - deeplIsPro: this.deeplIsPro, - }).then(() => { - fetchInstance(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cog', + bg: 'var(--bg)', + actions: [{ + asFullButton: true, + icon: 'fas fa-check', + text: i18n.ts.save, + handler: save, + }], } }); </script> diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue index 142a3bee2e..88acbcd3a3 100644 --- a/packages/client/src/pages/api-console.vue +++ b/packages/client/src/pages/api-console.vue @@ -25,72 +25,60 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { ref } from 'vue'; +import JSON5 from 'json5'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { Endpoints } from 'misskey-js'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSwitch, - }, +const body = ref('{}'); +const endpoint = ref(''); +const endpoints = ref<any[]>([]); +const sending = ref(false); +const res = ref(''); +const withCredential = ref(true); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API console', - icon: 'fas fa-terminal' - }, +os.api('endpoints').then(endpointResponse => { + endpoints.value = endpointResponse; +}); - endpoint: '', - body: '{}', - res: null, - sending: false, - endpoints: [], - withCredential: true, +function send() { + sending.value = true; + const requestBody = JSON5.parse(body.value); + os.api(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => { + sending.value = false; + res.value = JSON5.stringify(resp, null, 2); + }, err => { + sending.value = false; + res.value = JSON5.stringify(err, null, 2); + }); +} - }; - }, +function onEndpointChange() { + os.api('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => { + const endpointBody = {}; + for (const p of resp.params) { + endpointBody[p.name] = + p.type === 'String' ? '' : + p.type === 'Number' ? 0 : + p.type === 'Boolean' ? false : + p.type === 'Array' ? [] : + p.type === 'Object' ? {} : + null; + } + body.value = JSON5.stringify(endpointBody, null, 2); + }); +} - created() { - os.api('endpoints').then(endpoints => { - this.endpoints = endpoints; - }); +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API console', + icon: 'fas fa-terminal' }, - - methods: { - send() { - this.sending = true; - const body = JSON5.parse(this.body); - os.api(this.endpoint, body, body.i || (this.withCredential ? undefined : null)).then(res => { - this.sending = false; - this.res = JSON5.stringify(res, null, 2); - }, err => { - this.sending = false; - this.res = JSON5.stringify(err, null, 2); - }); - }, - - onEndpointChange() { - os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => { - const body = {}; - for (const p of endpoint.params) { - body[p.name] = - p.type === 'String' ? '' : - p.type === 'Number' ? 0 : - p.type === 'Boolean' ? false : - p.type === 'Array' ? [] : - p.type === 'Object' ? {} : - null; - } - this.body = JSON5.stringify(body, null, 2); - }); - } - } }); </script> diff --git a/packages/client/src/pages/auth.form.vue b/packages/client/src/pages/auth.form.vue index bc719aebd3..5feff0149a 100644 --- a/packages/client/src/pages/auth.form.vue +++ b/packages/client/src/pages/auth.form.vue @@ -32,7 +32,7 @@ export default defineComponent({ computed: { name(): string { const el = document.createElement('div'); - el.textContent = this.app.name + el.textContent = this.app.name; return el.innerHTML; }, app(): any { diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue index 3818c7481a..ea3a5dab76 100644 --- a/packages/client/src/pages/channel-editor.vue +++ b/packages/client/src/pages/channel-editor.vue @@ -111,8 +111,8 @@ export default defineComponent({ } }, - setBannerImage(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { + setBannerImage(evt) { + selectFile(evt.currentTarget ?? evt.target, null).then(file => { this.bannerId = file.id; }); }, diff --git a/packages/client/src/pages/emojis.category.vue b/packages/client/src/pages/emojis.category.vue index 9a317418be..c47870f4d4 100644 --- a/packages/client/src/pages/emojis.category.vue +++ b/packages/client/src/pages/emojis.category.vue @@ -58,7 +58,7 @@ export default defineComponent({ tags: emojiTags, selectedTags: new Set(), searchEmojis: null, - } + }; }, watch: { @@ -79,9 +79,9 @@ export default defineComponent({ } if (this.selectedTags.size === 0) { - this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q)); + this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q)); } else { - this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t))); + this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t))); } }, diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue index 886b5f7119..f44b29df04 100644 --- a/packages/client/src/pages/emojis.vue +++ b/packages/client/src/pages/emojis.vue @@ -25,10 +25,10 @@ function menu(ev) { type: 'info', text: i18n.ts.exportRequested, }); - }).catch((e) => { + }).catch((err) => { os.alert({ type: 'error', - text: e.message, + text: err.message, }); }); } diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue index 5add2b5324..447918905b 100644 --- a/packages/client/src/pages/federation.vue +++ b/packages/client/src/pages/federation.vue @@ -127,7 +127,7 @@ function getStatus(instance) { if (instance.isSuspended) return 'suspended'; if (instance.isNotResponding) return 'error'; return 'alive'; -}; +} defineExpose({ [symbols.PAGE_INFO]: { diff --git a/packages/client/src/pages/follow.vue b/packages/client/src/pages/follow.vue index d8a6824dca..e69e0481e0 100644 --- a/packages/client/src/pages/follow.vue +++ b/packages/client/src/pages/follow.vue @@ -20,7 +20,7 @@ export default defineComponent({ uri: acct }); promise.then(res => { - if (res.type == 'User') { + if (res.type === 'User') { this.follow(res.object); } else if (res.type === 'Note') { this.$router.push(`/notes/${res.object.id}`); diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue index 25ee513186..bc87160c44 100644 --- a/packages/client/src/pages/gallery/edit.vue +++ b/packages/client/src/pages/gallery/edit.vue @@ -71,7 +71,7 @@ export default defineComponent({ description: null, title: null, isSensitive: false, - } + }; }, watch: { @@ -91,8 +91,8 @@ export default defineComponent({ }, methods: { - selectFile(e) { - selectFiles(e.currentTarget ?? e.target, null).then(files => { + selectFile(evt) { + selectFiles(evt.currentTarget ?? evt.target, null).then(files => { this.files = this.files.concat(files); }); }, diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue index 1755c23286..1ca3443e56 100644 --- a/packages/client/src/pages/gallery/post.vue +++ b/packages/client/src/pages/gallery/post.vue @@ -119,8 +119,8 @@ export default defineComponent({ postId: this.postId }).then(post => { this.post = post; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue index 88a1e07afc..7c1d3e3cbe 100644 --- a/packages/client/src/pages/messaging/index.vue +++ b/packages/client/src/pages/messaging/index.vue @@ -90,14 +90,14 @@ export default defineComponent({ getAcct: Acct.toString, isMe(message) { - return message.userId == this.$i.id; + return message.userId === this.$i.id; }, onMessage(message) { if (message.recipientId) { this.messages = this.messages.filter(m => !( - (m.recipientId == message.recipientId && m.userId == message.userId) || - (m.recipientId == message.userId && m.userId == message.recipientId))); + (m.recipientId === message.recipientId && m.userId === message.userId) || + (m.recipientId === message.userId && m.userId === message.recipientId))); this.messages.unshift(message); } else if (message.groupId) { @@ -108,7 +108,7 @@ export default defineComponent({ onRead(ids) { for (const id of ids) { - const found = this.messages.find(m => m.id == id); + const found = this.messages.find(m => m.id === id); if (found) { if (found.recipientId) { found.isRead = true; @@ -123,11 +123,11 @@ export default defineComponent({ os.popupMenu([{ text: this.$ts.messagingWithUser, icon: 'fas fa-user', - action: () => { this.startUser() } + action: () => { this.startUser(); } }, { text: this.$ts.messagingWithGroup, icon: 'fas fa-users', - action: () => { this.startGroup() } + action: () => { this.startGroup(); } }], ev.currentTarget ?? ev.target); }, diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue index 3863c8f82b..8e779c4f39 100644 --- a/packages/client/src/pages/messaging/messaging-room.form.vue +++ b/packages/client/src/pages/messaging/messaging-room.form.vue @@ -31,6 +31,7 @@ import * as os from '@/os'; import { stream } from '@/stream'; import { Autocomplete } from '@/scripts/autocomplete'; import { throttle } from 'throttle-debounce'; +import { uploadFile } from '@/scripts/upload'; export default defineComponent({ props: { @@ -58,7 +59,7 @@ export default defineComponent({ return this.user ? 'user:' + this.user.id : 'group:' + this.group.id; }, canSend(): boolean { - return (this.text != null && this.text != '') || this.file != null; + return (this.text != null && this.text !== '') || this.file != null; }, room(): any { return this.$parent; @@ -87,12 +88,11 @@ export default defineComponent({ } }, methods: { - async onPaste(e: ClipboardEvent) { - const data = e.clipboardData; - const items = data.items; + async onPaste(evt: ClipboardEvent) { + const items = evt.clipboardData.items; - if (items.length == 1) { - if (items[0].kind == 'file') { + if (items.length === 1) { + if (items[0].kind === 'file') { const file = items[0].getAsFile(); const lio = file.name.lastIndexOf('.'); const ext = lio >= 0 ? file.name.slice(lio) : ''; @@ -100,7 +100,7 @@ export default defineComponent({ if (formatted) this.upload(file, formatted); } } else { - if (items[0].kind == 'file') { + if (items[0].kind === 'file') { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -109,23 +109,23 @@ export default defineComponent({ } }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.preventDefault(); - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.preventDefault(); + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } }, - onDrop(e): void { + onDrop(evt): void { // ファイルだったら - if (e.dataTransfer.files.length == 1) { - e.preventDefault(); - this.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + evt.preventDefault(); + this.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { - e.preventDefault(); + } else if (evt.dataTransfer.files.length > 1) { + evt.preventDefault(); os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -134,17 +134,17 @@ export default defineComponent({ } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { this.file = JSON.parse(driveFile); - e.preventDefault(); + evt.preventDefault(); } //#endregion }, - onKeydown(e) { + onKeydown(evt) { this.typing(); - if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) { + if ((evt.which === 10 || evt.which === 13) && (evt.ctrlKey || evt.metaKey) && this.canSend) { this.send(); } }, @@ -153,8 +153,8 @@ export default defineComponent({ this.typing(); }, - chooseFile(e) { - selectFile(e.currentTarget ?? e.target, this.$ts.selectFile).then(file => { + chooseFile(evt) { + selectFile(evt.currentTarget ?? evt.target, this.$ts.selectFile).then(file => { this.file = file; }); }, @@ -164,7 +164,7 @@ export default defineComponent({ }, upload(file: File, name?: string) { - os.upload(file, this.$store.state.uploadFolder, name).then(res => { + uploadFile(file, this.$store.state.uploadFolder, name).then(res => { this.file = res; }); }, @@ -192,25 +192,25 @@ export default defineComponent({ }, saveDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - data[this.draftKey] = { + drafts[this.draftKey] = { updatedAt: new Date(), data: { text: this.text, file: this.file } - } + }; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, deleteDraft() { - const data = JSON.parse(localStorage.getItem('message_drafts') || '{}'); + const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}'); - delete data[this.draftKey]; + delete drafts[this.draftKey]; - localStorage.setItem('message_drafts', JSON.stringify(data)); + localStorage.setItem('message_drafts', JSON.stringify(drafts)); }, async insertEmoji(ev) { diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue index 2ecc68eb54..fd1962218a 100644 --- a/packages/client/src/pages/messaging/messaging-room.vue +++ b/packages/client/src/pages/messaging/messaging-room.vue @@ -166,23 +166,23 @@ const Component = defineComponent({ }); }, - onDragover(e) { - const isFile = e.dataTransfer.items[0].kind == 'file'; - const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_; + onDragover(evt) { + const isFile = evt.dataTransfer.items[0].kind === 'file'; + const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_; if (isFile || isDriveFile) { - e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move'; + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move'; } else { - e.dataTransfer.dropEffect = 'none'; + evt.dataTransfer.dropEffect = 'none'; } }, - onDrop(e): void { + onDrop(evt): void { // ファイルだったら - if (e.dataTransfer.files.length == 1) { - this.form.upload(e.dataTransfer.files[0]); + if (evt.dataTransfer.files.length === 1) { + this.form.upload(evt.dataTransfer.files[0]); return; - } else if (e.dataTransfer.files.length > 1) { + } else if (evt.dataTransfer.files.length > 1) { os.alert({ type: 'error', text: this.$ts.onlyOneFileCanBeAttached @@ -191,8 +191,8 @@ const Component = defineComponent({ } //#region ドライブのファイル - const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); - if (driveFile != null && driveFile != '') { + const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_); + if (driveFile != null && driveFile !== '') { const file = JSON.parse(driveFile); this.form.file = file; } @@ -209,7 +209,7 @@ const Component = defineComponent({ limit: max + 1, untilId: this.existMoreMessages ? this.messages[0].id : undefined }).then(messages => { - if (messages.length == max + 1) { + if (messages.length === max + 1) { this.existMoreMessages = true; messages.pop(); } else { @@ -235,7 +235,7 @@ const Component = defineComponent({ const _isBottom = isBottom(this.$el, 64); this.messages.push(message); - if (message.userId != this.$i.id && !document.hidden) { + if (message.userId !== this.$i.id && !document.hidden) { this.connection.send('read', { id: message.id }); @@ -246,7 +246,7 @@ const Component = defineComponent({ this.$nextTick(() => { this.scrollToBottom(); }); - } else if (message.userId != this.$i.id) { + } else if (message.userId !== this.$i.id) { // Notify this.notifyNewMessage(); } @@ -256,7 +256,7 @@ const Component = defineComponent({ if (this.user) { if (!Array.isArray(x)) x = [x]; for (const id of x) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], @@ -266,7 +266,7 @@ const Component = defineComponent({ } } else if (this.group) { for (const id of x.ids) { - if (this.messages.some(x => x.id == id)) { + if (this.messages.some(x => x.id === id)) { const exist = this.messages.map(x => x.id).indexOf(id); this.messages[exist] = { ...this.messages[exist], diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue index 83ae5741c3..2c10494ede 100644 --- a/packages/client/src/pages/mfm-cheat-sheet.vue +++ b/packages/client/src/pages/mfm-cheat-sheet.vue @@ -325,23 +325,23 @@ export default defineComponent({ preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)', preview_quote: `> ${this.$ts._mfm.dummy}`, preview_search: `${this.$ts._mfm.dummy} 検索`, - preview_jelly: `$[jelly 🍮]`, - preview_tada: `$[tada 🍮]`, - preview_jump: `$[jump 🍮]`, - preview_bounce: `$[bounce 🍮]`, - preview_shake: `$[shake 🍮]`, - preview_twitch: `$[twitch 🍮]`, - preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]`, + preview_jelly: `$[jelly 🍮] $[jelly.speed=5s 🍮]`, + preview_tada: `$[tada 🍮] $[tada.speed=5s 🍮]`, + preview_jump: `$[jump 🍮] $[jump.speed=5s 🍮]`, + preview_bounce: `$[bounce 🍮] $[bounce.speed=5s 🍮]`, + preview_shake: `$[shake 🍮] $[shake.speed=5s 🍮]`, + preview_twitch: `$[twitch 🍮] $[twitch.speed=5s 🍮]`, + preview_spin: `$[spin 🍮] $[spin.left 🍮] $[spin.alternate 🍮]\n$[spin.x 🍮] $[spin.x,left 🍮] $[spin.x,alternate 🍮]\n$[spin.y 🍮] $[spin.y,left 🍮] $[spin.y,alternate 🍮]\n\n$[spin.speed=5s 🍮]`, preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`, preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`, preview_x2: `$[x2 🍮]`, preview_x3: `$[x3 🍮]`, preview_x4: `$[x4 🍮]`, preview_blur: `$[blur ${this.$ts._mfm.dummy}]`, - preview_rainbow: `$[rainbow 🍮]`, + preview_rainbow: `$[rainbow 🍮] $[rainbow.speed=5s 🍮]`, preview_sparkle: `$[sparkle 🍮]`, preview_rotate: `$[rotate 🍮]`, - } + }; }, }); </script> diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue index 6e85b784ff..4032d7723e 100644 --- a/packages/client/src/pages/miauth.vue +++ b/packages/client/src/pages/miauth.vue @@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { login } from '@/account'; +import { appendQuery, query } from '@/scripts/url'; export default defineComponent({ components: { @@ -82,7 +83,9 @@ export default defineComponent({ this.state = 'accepted'; if (this.callback) { - location.href = `${this.callback}?session=${this.session}`; + location.href = appendQuery(this.callback, query({ + session: this.session + })); } }, deny() { diff --git a/packages/client/src/pages/my-antennas/edit.vue b/packages/client/src/pages/my-antennas/edit.vue index 04928c81a3..38e56ce35d 100644 --- a/packages/client/src/pages/my-antennas/edit.vue +++ b/packages/client/src/pages/my-antennas/edit.vue @@ -4,49 +4,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import MkButton from '@/components/ui/button.vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import XAntenna from './editor.vue'; import * as symbols from '@/symbols'; import * as os from '@/os'; +import { MisskeyNavigator } from '@/scripts/navigate'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - XAntenna, - }, +const nav = new MisskeyNavigator(); - props: { - antennaId: { - type: String, - required: true, - } - }, +let antenna: any = $ref(null); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - }, - antenna: null, - }; - }, +const props = defineProps<{ + antennaId: string +}>(); - watch: { - antennaId: { - async handler() { - this.antenna = await os.api('antennas/show', { antennaId: this.antennaId }); - }, - immediate: true, - } - }, +function onAntennaUpdated() { + nav.push('/my/antennas'); +} - methods: { - onAntennaUpdated() { - this.$router.push('/my/antennas'); - }, +os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => { + antenna = antennaResponse; +}); + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', } }); </script> diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue index 8c1d6148fe..6f3c4afbfe 100644 --- a/packages/client/src/pages/my-antennas/editor.vue +++ b/packages/client/src/pages/my-antennas/editor.vue @@ -44,135 +44,100 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import MkInput from '@/components/form/input.vue'; import MkTextarea from '@/components/form/textarea.vue'; import MkSelect from '@/components/form/select.vue'; import MkSwitch from '@/components/form/switch.vue'; -import * as Acct from 'misskey-js/built/acct'; import * as os from '@/os'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInput, MkTextarea, MkSelect, MkSwitch - }, +const props = defineProps<{ + antenna: any +}>(); - props: { - antenna: { - type: Object, - required: true - } - }, +const emit = defineEmits<{ + (ev: 'created'): void, + (ev: 'updated'): void, + (ev: 'deleted'): void, +}>(); - data() { - return { - name: '', - src: '', - userListId: null, - userGroupId: null, - users: '', - keywords: '', - excludeKeywords: '', - caseSensitive: false, - withReplies: false, - withFile: false, - notify: false, - userLists: null, - userGroups: null, - }; - }, +let name: string = $ref(props.antenna.name); +let src: string = $ref(props.antenna.src); +let userListId: any = $ref(props.antenna.userListId); +let userGroupId: any = $ref(props.antenna.userGroupId); +let users: string = $ref(props.antenna.users.join('\n')); +let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n')); +let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n')); +let caseSensitive: boolean = $ref(props.antenna.caseSensitive); +let withReplies: boolean = $ref(props.antenna.withReplies); +let withFile: boolean = $ref(props.antenna.withFile); +let notify: boolean = $ref(props.antenna.notify); +let userLists: any = $ref(null); +let userGroups: any = $ref(null); - watch: { - async src() { - if (this.src === 'list' && this.userLists === null) { - this.userLists = await os.api('users/lists/list'); - } +watch(() => src, async () => { + if (src === 'list' && userLists === null) { + userLists = await os.api('users/lists/list'); + } - if (this.src === 'group' && this.userGroups === null) { - const groups1 = await os.api('users/groups/owned'); - const groups2 = await os.api('users/groups/joined'); + if (src === 'group' && userGroups === null) { + const groups1 = await os.api('users/groups/owned'); + const groups2 = await os.api('users/groups/joined'); - this.userGroups = [...groups1, ...groups2]; - } - } - }, + userGroups = [...groups1, ...groups2]; + } +}); - created() { - this.name = this.antenna.name; - this.src = this.antenna.src; - this.userListId = this.antenna.userListId; - this.userGroupId = this.antenna.userGroupId; - this.users = this.antenna.users.join('\n'); - this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n'); - this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'); - this.caseSensitive = this.antenna.caseSensitive; - this.withReplies = this.antenna.withReplies; - this.withFile = this.antenna.withFile; - this.notify = this.antenna.notify; - }, +async function saveAntenna() { + const antennaData = { + name, + src, + userListId, + userGroupId, + withReplies, + withFile, + notify, + caseSensitive, + users: users.trim().split('\n').map(x => x.trim()), + keywords: keywords.trim().split('\n').map(x => x.trim().split(' ')), + excludeKeywords: excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), + }; - methods: { - async saveAntenna() { - if (this.antenna.id == null) { - await os.apiWithDialog('antennas/create', { - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('created'); - } else { - await os.apiWithDialog('antennas/update', { - antennaId: this.antenna.id, - name: this.name, - src: this.src, - userListId: this.userListId, - userGroupId: this.userGroupId, - withReplies: this.withReplies, - withFile: this.withFile, - notify: this.notify, - caseSensitive: this.caseSensitive, - users: this.users.trim().split('\n').map(x => x.trim()), - keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')), - excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')), - }); - this.$emit('updated'); - } - }, + if (props.antenna.id == null) { + await os.apiWithDialog('antennas/create', antennaData); + emit('created'); + } else { + antennaData['antennaId'] = props.antenna.id; + await os.apiWithDialog('antennas/update', antennaData); + emit('updated'); + } +} - async deleteAntenna() { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$t('removeAreYouSure', { x: this.antenna.name }), - }); - if (canceled) return; +async function deleteAntenna() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.t('removeAreYouSure', { x: props.antenna.name }), + }); + if (canceled) return; - await os.api('antennas/delete', { - antennaId: this.antenna.id, - }); + await os.api('antennas/delete', { + antennaId: props.antenna.id, + }); - os.success(); - this.$emit('deleted'); - }, + os.success(); + emit('deleted'); +} - addUser() { - os.selectUser().then(user => { - this.users = this.users.trim(); - this.users += '\n@' + Acct.toString(user); - this.users = this.users.trim(); - }); - } - } -}); +function addUser() { + os.selectUser().then(user => { + users = users.trim(); + users += '\n@' + Acct.toString(user as any); + users = users.trim(); + }); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue index 7138d269a9..a568f64c52 100644 --- a/packages/client/src/pages/my-antennas/index.vue +++ b/packages/client/src/pages/my-antennas/index.vue @@ -1,7 +1,7 @@ <template> <MkSpacer :content-max="700"> <div class="ieepwinx"> - <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton> + <MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ i18n.ts.add }}</MkButton> <div class=""> <MkPagination v-slot="{items}" ref="list" :pagination="pagination"> @@ -14,35 +14,24 @@ </MkSpacer> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { } from 'vue'; import MkPagination from '@/components/ui/pagination.vue'; import MkButton from '@/components/ui/button.vue'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkPagination, - MkButton, - }, +const pagination = { + endpoint: 'antennas/list' as const, + limit: 10, +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.manageAntennas, - icon: 'fas fa-satellite', - bg: 'var(--bg)', - action: { - icon: 'fas fa-plus', - handler: this.create - } - }, - pagination: { - endpoint: 'antennas/list' as const, - limit: 10, - }, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.manageAntennas, + icon: 'fas fa-satellite', + bg: 'var(--bg)' + } }); </script> diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue index 29261ec484..f0a18ecc36 100644 --- a/packages/client/src/pages/note.vue +++ b/packages/client/src/pages/note.vue @@ -108,6 +108,10 @@ export default defineComponent({ }, methods: { fetch() { + this.hasPrev = false; + this.hasNext = false; + this.showPrev = false; + this.showNext = false; this.note = null; os.api('notes/show', { noteId: this.noteId @@ -132,8 +136,8 @@ export default defineComponent({ this.hasPrev = prev.length !== 0; this.hasNext = next.length !== 0; }); - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); } } diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue index f302ac4f90..9566592618 100644 --- a/packages/client/src/pages/page-editor/page-editor.vue +++ b/packages/client/src/pages/page-editor/page-editor.vue @@ -114,7 +114,7 @@ export default defineComponent({ readonly: this.readonly, getScriptBlockList: this.getScriptBlockList, getPageBlockList: this.getPageBlockList - } + }; }, props: { diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue index b2c039a269..5bca971438 100644 --- a/packages/client/src/pages/page.vue +++ b/packages/client/src/pages/page.vue @@ -139,8 +139,8 @@ export default defineComponent({ username: this.username, }).then(page => { this.page = page; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue index 7d008ae75c..b3e2ca8d6f 100644 --- a/packages/client/src/pages/reset-password.vue +++ b/packages/client/src/pages/reset-password.vue @@ -12,7 +12,7 @@ </template> <script lang="ts" setup> -import { onMounted } from 'vue'; +import { defineAsyncComponent, onMounted } from 'vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; @@ -36,7 +36,7 @@ async function save() { onMounted(() => { if (props.token == null) { - os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed'); + os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {}, 'closed'); router.push('/'); } }); diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue index f871dc48e8..34a41b81a5 100644 --- a/packages/client/src/pages/scratchpad.vue +++ b/packages/client/src/pages/scratchpad.vue @@ -6,20 +6,20 @@ </div> <MkContainer :foldable="true" class="_gap"> - <template #header>{{ $ts.output }}</template> + <template #header>{{ i18n.ts.output }}</template> <div class="bepmlvbi"> <div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div> </div> </MkContainer> <div class="_gap"> - {{ $ts.scratchpadDescription }} + {{ i18n.ts.scratchpadDescription }} </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import 'prismjs'; import { highlight, languages } from 'prismjs/components/prism-core'; import 'prismjs/components/prism-clike'; @@ -27,103 +27,90 @@ import 'prismjs/components/prism-javascript'; import 'prismjs/themes/prism-okaidia.css'; import { PrismEditor } from 'vue-prism-editor'; import 'vue-prism-editor/dist/prismeditor.min.css'; -import { AiScript, parse, utils, values } from '@syuilo/aiscript'; +import { AiScript, parse, utils } from '@syuilo/aiscript'; import MkContainer from '@/components/ui/container.vue'; import MkButton from '@/components/ui/button.vue'; import { createAiScriptEnv } from '@/scripts/aiscript/api'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkContainer, - MkButton, - PrismEditor, - }, - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.scratchpad, - icon: 'fas fa-terminal', - }, - code: '', - logs: [], - } - }, - - watch: { - code() { - localStorage.setItem('scratchpad', this.code); - } - }, +const code = ref(''); +const logs = ref<any[]>([]); - created() { - const saved = localStorage.getItem('scratchpad'); - if (saved) { - this.code = saved; - } - }, +const saved = localStorage.getItem('scratchpad'); +if (saved) { + code.value = saved; +} - methods: { - async run() { - this.logs = []; - const aiscript = new AiScript(createAiScriptEnv({ - storageKey: 'scratchpad', - token: this.$i?.token, - }), { - in: (q) => { - return new Promise(ok => { - os.inputText({ - title: q, - }).then(({ canceled, result: a }) => { - ok(a); - }); - }); - }, - out: (value) => { - this.logs.push({ - id: Math.random(), - text: value.type === 'str' ? value.value : utils.valToString(value), - print: true - }); - }, - log: (type, params) => { - switch (type) { - case 'end': this.logs.push({ - id: Math.random(), - text: utils.valToString(params.val, true), - print: false - }); break; - default: break; - } - } - }); +watch(code, () => { + localStorage.setItem('scratchpad', code.value); +}); - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - try { - await aiscript.exec(ast); - } catch (e) { - os.alert({ - type: 'error', - text: e +async function run() { + logs.value = []; + const aiscript = new AiScript(createAiScriptEnv({ + storageKey: 'scratchpad', + token: $i?.token, + }), { + in: (q) => { + return new Promise(ok => { + os.inputText({ + title: q, + }).then(({ canceled, result: a }) => { + ok(a); }); - } + }); }, - - highlighter(code) { - return highlight(code, languages.js, 'javascript'); + out: (value) => { + logs.value.push({ + id: Math.random(), + text: value.type === 'str' ? value.value : utils.valToString(value), + print: true + }); }, + log: (type, params) => { + switch (type) { + case 'end': logs.value.push({ + id: Math.random(), + text: utils.valToString(params.val, true), + print: false + }); break; + default: break; + } + } + }); + + let ast; + try { + ast = parse(code.value); + } catch (error) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; } + try { + await aiscript.exec(ast); + } catch (error: any) { + os.alert({ + type: 'error', + text: error.message + }); + } +} + +function highlighter(code) { + return highlight(code, languages.js, 'javascript'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.scratchpad, + icon: 'fas fa-terminal', + }, }); </script> diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue index 10599d99ff..fb3a7a17f3 100644 --- a/packages/client/src/pages/settings/2fa.vue +++ b/packages/client/src/pages/settings/2fa.vue @@ -1,49 +1,49 @@ <template> <div> - <MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton> + <MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton> <template v-if="$i.twoFactorEnabled"> - <p>{{ $ts._2fa.alreadyRegistered }}</p> - <MkButton @click="unregister">{{ $ts.unregister }}</MkButton> + <p>{{ i18n.ts._2fa.alreadyRegistered }}</p> + <MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton> <template v-if="supportsCredentials"> <hr class="totp-method-sep"> - <h2 class="heading">{{ $ts.securityKey }}</h2> - <p>{{ $ts._2fa.securityKeyInfo }}</p> + <h2 class="heading">{{ i18n.ts.securityKey }}</h2> + <p>{{ i18n.ts._2fa.securityKeyInfo }}</p> <div class="key-list"> <div v-for="key in $i.securityKeysList" class="key"> <h3>{{ key.name }}</h3> - <div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> - <MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton> + <div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div> + <MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton> </div> </div> - <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ $ts.passwordLessLogin }}</MkSwitch> + <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch> - <MkInfo v-if="registration && registration.error" warn>{{ $ts.error }} {{ registration.error }}</MkInfo> - <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton> + <MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo> + <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton> <ol v-if="registration && !registration.error"> <li v-if="registration.stage >= 0"> - {{ $ts.tapSecurityKey }} + {{ i18n.ts.tapSecurityKey }} <i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i> </li> <li v-if="registration.stage >= 1"> <MkForm :disabled="registration.stage != 1 || registration.saving"> <MkInput v-model="keyName" :max="30"> - <template #label>{{ $ts.securityKeyName }}</template> + <template #label>{{ i18n.ts.securityKeyName }}</template> </MkInput> - <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ $ts.registerSecurityKey }}</MkButton> + <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton> <i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i> </MkForm> </li> </ol> </template> </template> - <div v-if="data && !$i.twoFactorEnabled"> + <div v-if="twoFactorData && !$i.twoFactorEnabled"> <ol style="margin: 0; padding: 0 0 0 1em;"> <li> - <I18n :src="$ts._2fa.step1" tag="span"> + <I18n :src="i18n.ts._2fa.step1" tag="span"> <template #a> <a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a> </template> @@ -52,19 +52,20 @@ </template> </I18n> </li> - <li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li> - <li>{{ $ts._2fa.step3 }}<br> - <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput> - <MkButton primary @click="submit">{{ $ts.done }}</MkButton> + <li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li> + <li> + {{ i18n.ts._2fa.step3 }}<br> + <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput> + <MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton> </li> </ol> - <MkInfo>{{ $ts._2fa.step4 }}</MkInfo> + <MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { ref } from 'vue'; import { hostname } from '@/config'; import { byteify, hexify, stringify } from '@/scripts/2fa'; import MkButton from '@/components/ui/button.vue'; @@ -72,155 +73,144 @@ import MkInfo from '@/components/ui/info.vue'; import MkInput from '@/components/form/input.vue'; import MkSwitch from '@/components/form/switch.vue'; import * as os from '@/os'; -import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, MkInfo, MkInput, MkSwitch - }, +const twoFactorData = ref<any>(null); +const supportsCredentials = ref(!!navigator.credentials); +const usePasswordLessLogin = ref($i!.usePasswordLessLogin); +const registration = ref<any>(null); +const keyName = ref(''); +const token = ref(null); - data() { - return { - data: null, - supportsCredentials: !!navigator.credentials, - usePasswordLessLogin: this.$i.usePasswordLessLogin, - registration: null, - keyName: '', - token: null, - }; - }, +function register() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register', { + password: password + }).then(data => { + twoFactorData.value = data; + }); + }); +} - methods: { - register() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register', { - password: password - }).then(data => { - this.data = data; - }); - }); - }, +function unregister() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/unregister', { + password: password + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + $i!.twoFactorEnabled = false; + }); + }); +} - unregister() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/unregister', { - password: password - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = false; - }); - }); - }, +function submit() { + os.api('i/2fa/done', { + token: token.value + }).then(() => { + os.success(); + $i!.twoFactorEnabled = true; + }).catch(err => { + os.alert({ + type: 'error', + text: err, + }); + }); +} - submit() { - os.api('i/2fa/done', { - token: this.token - }).then(() => { - os.success(); - this.$i.twoFactorEnabled = true; - }).catch(e => { - os.alert({ - type: 'error', - text: e - }); - }); - }, +function registerKey() { + registration.value.saving = true; + os.api('i/2fa/key-done', { + password: registration.value.password, + name: keyName.value, + challengeId: registration.value.challengeId, + // we convert each 16 bits to a string to serialise + clientDataJSON: stringify(registration.value.credential.response.clientDataJSON), + attestationObject: hexify(registration.value.credential.response.attestationObject) + }).then(key => { + registration.value = null; + key.lastUsed = new Date(); + os.success(); + }); +} - registerKey() { - this.registration.saving = true; - os.api('i/2fa/key-done', { - password: this.registration.password, - name: this.keyName, - challengeId: this.registration.challengeId, - // we convert each 16 bits to a string to serialise - clientDataJSON: stringify(this.registration.credential.response.clientDataJSON), - attestationObject: hexify(this.registration.credential.response.attestationObject) - }).then(key => { - this.registration = null; - key.lastUsed = new Date(); - os.success(); - }) - }, +function unregisterKey(key) { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + return os.api('i/2fa/remove-key', { + password, + credentialId: key.id + }).then(() => { + usePasswordLessLogin.value = false; + updatePasswordLessLogin(); + }).then(() => { + os.success(); + }); + }); +} - unregisterKey(key) { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - return os.api('i/2fa/remove-key', { - password, - credentialId: key.id - }).then(() => { - this.usePasswordLessLogin = false; - this.updatePasswordLessLogin(); - }).then(() => { - os.success(); - }); +function addSecurityKey() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/2fa/register-key', { + password + }).then(reg => { + registration.value = { + password, + challengeId: reg!.challengeId, + stage: 0, + publicKeyOptions: { + challenge: byteify(reg!.challenge, 'base64'), + rp: { + id: hostname, + name: 'Misskey' + }, + user: { + id: byteify($i!.id, 'ascii'), + name: $i!.username, + displayName: $i!.name, + }, + pubKeyCredParams: [{ alg: -7, type: 'public-key' }], + timeout: 60000, + attestation: 'direct' + }, + saving: true + }; + return navigator.credentials.create({ + publicKey: registration.value.publicKeyOptions }); - }, + }).then(credential => { + registration.value.credential = credential; + registration.value.saving = false; + registration.value.stage = 1; + }).catch(err => { + console.warn('Error while registering?', err); + registration.value.error = err.message; + registration.value.stage = -1; + }); + }); +} - addSecurityKey() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/2fa/register-key', { - password - }).then(registration => { - this.registration = { - password, - challengeId: registration.challengeId, - stage: 0, - publicKeyOptions: { - challenge: byteify(registration.challenge, 'base64'), - rp: { - id: hostname, - name: 'Misskey' - }, - user: { - id: byteify(this.$i.id, 'ascii'), - name: this.$i.username, - displayName: this.$i.name, - }, - pubKeyCredParams: [{ alg: -7, type: 'public-key' }], - timeout: 60000, - attestation: 'direct' - }, - saving: true - }; - return navigator.credentials.create({ - publicKey: this.registration.publicKeyOptions - }); - }).then(credential => { - this.registration.credential = credential; - this.registration.saving = false; - this.registration.stage = 1; - }).catch(err => { - console.warn('Error while registering?', err); - this.registration.error = err.message; - this.registration.stage = -1; - }); - }); - }, - - updatePasswordLessLogin() { - os.api('i/2fa/password-less', { - value: !!this.usePasswordLessLogin - }); - } - } -}); +async function updatePasswordLessLogin() { + await os.api('i/2fa/password-less', { + value: !!usePasswordLessLogin.value + }); +} </script> diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue index c98ad056f6..12142b4dc1 100644 --- a/packages/client/src/pages/settings/account-info.vue +++ b/packages/client/src/pages/settings/account-info.vue @@ -7,163 +7,150 @@ <FormSection> <MkKeyValue> - <template #key>{{ $ts.registeredDate }}</template> + <template #key>{{ i18n.ts.registeredDate }}</template> <template #value><MkTime :time="$i.createdAt" mode="detail"/></template> </MkKeyValue> </FormSection> <FormSection v-if="stats"> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.notesCount }}</template> + <template #key>{{ i18n.ts.notesCount }}</template> <template #value>{{ number(stats.notesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliesCount }}</template> + <template #key>{{ i18n.ts.repliesCount }}</template> <template #value>{{ number(stats.repliesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotesCount }}</template> + <template #key>{{ i18n.ts.renotesCount }}</template> <template #value>{{ number(stats.renotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.repliedCount }}</template> + <template #key>{{ i18n.ts.repliedCount }}</template> <template #value>{{ number(stats.repliedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.renotedCount }}</template> + <template #key>{{ i18n.ts.renotedCount }}</template> <template #value>{{ number(stats.renotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotesCount }}</template> + <template #key>{{ i18n.ts.pollVotesCount }}</template> <template #value>{{ number(stats.pollVotesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pollVotedCount }}</template> + <template #key>{{ i18n.ts.pollVotedCount }}</template> <template #value>{{ number(stats.pollVotedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.sentReactionsCount }}</template> + <template #key>{{ i18n.ts.sentReactionsCount }}</template> <template #value>{{ number(stats.sentReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.receivedReactionsCount }}</template> + <template #key>{{ i18n.ts.receivedReactionsCount }}</template> <template #value>{{ number(stats.receivedReactionsCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.noteFavoritesCount }}</template> + <template #key>{{ i18n.ts.noteFavoritesCount }}</template> <template #value>{{ number(stats.noteFavoritesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }}</template> + <template #key>{{ i18n.ts.followingCount }}</template> <template #value>{{ number(stats.followingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowingCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }}</template> + <template #key>{{ i18n.ts.followersCount }}</template> <template #value>{{ number(stats.followersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template> <template #value>{{ number(stats.localFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template> + <template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template> <template #value>{{ number(stats.remoteFollowersCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikesCount }}</template> + <template #key>{{ i18n.ts.pageLikesCount }}</template> <template #value>{{ number(stats.pageLikesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.pageLikedCount }}</template> + <template #key>{{ i18n.ts.pageLikedCount }}</template> <template #value>{{ number(stats.pageLikedCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveFilesCount }}</template> + <template #key>{{ i18n.ts.driveFilesCount }}</template> <template #value>{{ number(stats.driveFilesCount) }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> - <template #key>{{ $ts.driveUsage }}</template> + <template #key>{{ i18n.ts.driveUsage }}</template> <template #value>{{ bytes(stats.driveUsage) }}</template> </MkKeyValue> </FormSection> <FormSection> - <template #label>{{ $ts.other }}</template> + <template #label>{{ i18n.ts.other }}</template> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>emailVerified</template> - <template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>twoFactorEnabled</template> - <template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>securityKeys</template> - <template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>usePasswordLessLogin</template> - <template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isModerator</template> - <template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> <MkKeyValue oneline style="margin: 1em 0;"> <template #key>isAdmin</template> - <template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template> + <template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template> </MkKeyValue> </FormSection> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref } from 'vue'; import FormSection from '@/components/form/section.vue'; import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import number from '@/filters/number'; import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkKeyValue, - }, +const stats = ref<any>({}); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accountInfo, - icon: 'fas fa-info-circle' - }, - stats: null - } - }, - - mounted() { - os.api('users/stats', { - userId: this.$i.id - }).then(stats => { - this.stats = stats; - }); - }, +onMounted(() => { + os.api('users/stats', { + userId: $i!.id + }).then(response => { + stats.value = response; + }); +}); - methods: { - number, - bytes, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accountInfo, + icon: 'fas fa-info-circle' } }); </script> diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue index a744a031d4..5e75639c55 100644 --- a/packages/client/src/pages/settings/accounts.vue +++ b/packages/client/src/pages/settings/accounts.vue @@ -1,7 +1,7 @@ <template> <div class="_formRoot"> <FormSuspense :p="init"> - <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton> + <FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ i18n.ts.addAccount }}</FormButton> <div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)"> <div class="avatar"> @@ -20,90 +20,89 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormSuspense from '@/components/form/suspense.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; -import { getAccounts, addAccount, login } from '@/account'; +import { getAccounts, addAccount as addAccounts, login, $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSuspense, - FormButton, - }, +const storedAccounts = ref<any>(null); +const accounts = ref<any>(null); - emits: ['info'], +const init = async () => { + getAccounts().then(accounts => { + storedAccounts.value = accounts.filter(x => x.id !== $i!.id); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.accounts, - icon: 'fas fa-users', - bg: 'var(--bg)', - }, - storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)), - accounts: null, - init: async () => os.api('users/show', { - userIds: (await this.storedAccounts).map(x => x.id) - }).then(accounts => { - this.accounts = accounts; - }), - }; - }, + console.log(storedAccounts.value); - methods: { - menu(account, ev) { - os.popupMenu([{ - text: this.$ts.switch, - icon: 'fas fa-exchange-alt', - action: () => this.switchAccount(account), - }, { - text: this.$ts.remove, - icon: 'fas fa-trash-alt', - danger: true, - action: () => this.removeAccount(account), - }], ev.currentTarget ?? ev.target); - }, + return os.api('users/show', { + userIds: storedAccounts.value.map(x => x.id) + }); + }).then(response => { + accounts.value = response; + console.log(accounts.value); + }); +}; - addAccount(ev) { - os.popupMenu([{ - text: this.$ts.existingAccount, - action: () => { this.addExistingAccount(); }, - }, { - text: this.$ts.createAccount, - action: () => { this.createAccount(); }, - }], ev.currentTarget ?? ev.target); - }, +function menu(account, ev) { + os.popupMenu([{ + text: i18n.ts.switch, + icon: 'fas fa-exchange-alt', + action: () => switchAccount(account), + }, { + text: i18n.ts.remove, + icon: 'fas fa-trash-alt', + danger: true, + action: () => removeAccount(account), + }], ev.currentTarget ?? ev.target); +} - addExistingAccount() { - os.popup(import('@/components/signin-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - os.success(); - }, - }, 'closed'); - }, +function addAccount(ev) { + os.popupMenu([{ + text: i18n.ts.existingAccount, + action: () => { addExistingAccount(); }, + }, { + text: i18n.ts.createAccount, + action: () => { createAccount(); }, + }], ev.currentTarget ?? ev.target); +} - createAccount() { - os.popup(import('@/components/signup-dialog.vue'), {}, { - done: res => { - addAccount(res.id, res.i); - this.switchAccountWithToken(res.i); - }, - }, 'closed'); +function addExistingAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + os.success(); }, + }, 'closed'); +} - async switchAccount(account: any) { - const storedAccounts = await getAccounts(); - const token = storedAccounts.find(x => x.id === account.id).token; - this.switchAccountWithToken(token); +function createAccount() { + os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, { + done: res => { + addAccounts(res.id, res.i); + switchAccountWithToken(res.i); }, + }, 'closed'); +} - switchAccountWithToken(token: string) { - login(token); - }, +async function switchAccount(account: any) { + const fetchedAccounts: any[] = await getAccounts(); + const token = fetchedAccounts.find(x => x.id === account.id).token; + switchAccountWithToken(token); +} + +function switchAccountWithToken(token: string) { + login(token); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.accounts, + icon: 'fas fa-users', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue index 20ff2a8d96..e6375763f1 100644 --- a/packages/client/src/pages/settings/api.vue +++ b/packages/client/src/pages/settings/api.vue @@ -1,56 +1,45 @@ <template> <div class="_formRoot"> - <FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton> - <FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink> + <FormButton primary class="_formBlock" @click="generateToken">{{ i18n.ts.generateAccessToken }}</FormButton> + <FormLink to="/settings/apps" class="_formBlock">{{ i18n.ts.manageAccessTokens }}</FormLink> <FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormLink, - }, +const isDesktop = ref(window.innerWidth >= 1100); - emits: ['info'], +function generateToken() { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, + }); - data() { - return { - [symbols.PAGE_INFO]: { - title: 'API', - icon: 'fas fa-key', - bg: 'var(--bg)', - }, - isDesktop: window.innerWidth >= 1100, - }; - }, - - methods: { - generateToken() { - os.popup(import('@/components/token-generate-window.vue'), {}, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - os.alert({ - type: 'success', - title: this.$ts.token, - text: token - }); - }, - }, 'closed'); + os.alert({ + type: 'success', + title: i18n.ts.token, + text: token + }); }, + }, 'closed'); +} + +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'API', + icon: 'fas fa-key', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue index 9c0fa8a54d..7b0b5548d5 100644 --- a/packages/client/src/pages/settings/apps.vue +++ b/packages/client/src/pages/settings/apps.vue @@ -4,7 +4,7 @@ <template #empty> <div class="_fullinfo"> <img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/> - <div>{{ $ts.nothing }}</div> + <div>{{ i18n.ts.nothing }}</div> </div> </template> <template v-slot="{items}"> @@ -14,18 +14,18 @@ <div class="name">{{ token.name }}</div> <div class="description">{{ token.description }}</div> <div class="_keyValue"> - <div>{{ $ts.installedDate }}:</div> + <div>{{ i18n.ts.installedDate }}:</div> <div><MkTime :time="token.createdAt"/></div> </div> <div class="_keyValue"> - <div>{{ $ts.lastUsedDate }}:</div> + <div>{{ i18n.ts.lastUsedDate }}:</div> <div><MkTime :time="token.lastUsedAt"/></div> </div> <div class="actions"> <button class="_button" @click="revoke(token)"><i class="fas fa-trash-alt"></i></button> </div> <details> - <summary>{{ $ts.details }}</summary> + <summary>{{ i18n.ts.details }}</summary> <ul> <li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li> </ul> @@ -37,42 +37,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import FormPagination from '@/components/ui/pagination.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormPagination, - }, +const list = ref<any>(null); - emits: ['info'], +const pagination = { + endpoint: 'i/apps' as const, + limit: 100, + params: { + sort: '+lastUsedAt' + } +}; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.installedApps, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/apps' as const, - limit: 100, - params: { - sort: '+lastUsedAt' - } - }, - }; - }, +function revoke(token) { + os.api('i/revoke-token', { tokenId: token.id }).then(() => { + list.value.reload(); + }); +} - methods: { - revoke(token) { - os.api('i/revoke-token', { tokenId: token.id }).then(() => { - this.$refs.list.reload(); - }); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.installedApps, + icon: 'fas fa-plug', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue index 556ee30c1d..20db077ceb 100644 --- a/packages/client/src/pages/settings/custom-css.vue +++ b/packages/client/src/pages/settings/custom-css.vue @@ -1,6 +1,6 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts.customCssWarn }}</FormInfo> <FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;"> <template #label>CSS</template> @@ -8,50 +8,38 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; -import { defaultStore } from '@/store'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormInfo, - }, +const localCustomCss = ref(localStorage.getItem('customCss') ?? ''); - emits: ['info'], +async function apply() { + localStorage.setItem('customCss', localCustomCss.value); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.customCss, - icon: 'fas fa-code', - bg: 'var(--bg)', - }, - localCustomCss: localStorage.getItem('customCss') - } - }, + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - mounted() { - this.$watch('localCustomCss', this.apply); - }, + unisonReload(); +} - methods: { - async apply() { - localStorage.setItem('customCss', this.localCustomCss); - - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; +watch(localCustomCss, async () => { + await apply(); +}); - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.customCss, + icon: 'fas fa-code', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue index 46b90d3d1a..2d868aa0a7 100644 --- a/packages/client/src/pages/settings/deck.vue +++ b/packages/client/src/pages/settings/deck.vue @@ -1,36 +1,36 @@ <template> <div class="_formRoot"> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="navWindow">{{ i18n.ts.openInWindow }}</FormSwitch> </FormGroup> - <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch> + <FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch> <FormRadios v-model="columnAlign" class="_formBlock"> - <template #label>{{ $ts._deck.columnAlign }}</template> - <option value="left">{{ $ts.left }}</option> - <option value="center">{{ $ts.center }}</option> + <template #label>{{ i18n.ts._deck.columnAlign }}</template> + <option value="left">{{ i18n.ts.left }}</option> + <option value="center">{{ i18n.ts.center }}</option> </FormRadios> <FormRadios v-model="columnHeaderHeight" class="_formBlock"> - <template #label>{{ $ts._deck.columnHeaderHeight }}</template> - <option :value="42">{{ $ts.narrow }}</option> - <option :value="45">{{ $ts.medium }}</option> - <option :value="48">{{ $ts.wide }}</option> + <template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template> + <option :value="42">{{ i18n.ts.narrow }}</option> + <option :value="45">{{ i18n.ts.medium }}</option> + <option :value="48">{{ i18n.ts.wide }}</option> </FormRadios> <FormInput v-model="columnMargin" type="number" class="_formBlock"> - <template #label>{{ $ts._deck.columnMargin }}</template> + <template #label>{{ i18n.ts._deck.columnMargin }}</template> <template #suffix>px</template> </FormInput> - <FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> + <FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormLink from '@/components/form/link.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -40,59 +40,41 @@ import { deckStore } from '@/ui/deck/deck-store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSwitch, - FormLink, - FormInput, - FormRadios, - FormGroup, - }, +const navWindow = computed(deckStore.makeGetterSetter('navWindow')); +const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn')); +const columnAlign = computed(deckStore.makeGetterSetter('columnAlign')); +const columnMargin = computed(deckStore.makeGetterSetter('columnMargin')); +const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight')); +const profile = computed(deckStore.makeGetterSetter('profile')); - emits: ['info'], +watch(navWindow, async () => { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.deck, - icon: 'fas fa-columns', - bg: 'var(--bg)', - }, - } - }, - - computed: { - navWindow: deckStore.makeGetterSetter('navWindow'), - alwaysShowMainColumn: deckStore.makeGetterSetter('alwaysShowMainColumn'), - columnAlign: deckStore.makeGetterSetter('columnAlign'), - columnMargin: deckStore.makeGetterSetter('columnMargin'), - columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'), - profile: deckStore.makeGetterSetter('profile'), - }, - - watch: { - async navWindow() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; + unisonReload(); +}); - unisonReload(); - } - }, +async function setProfile() { + const { canceled, result: name } = await os.inputText({ + title: i18n.ts._deck.profile, + allowEmpty: false + }); + if (canceled) return; + + profile.value = name; + unisonReload(); +} - methods: { - async setProfile() { - const { canceled, result: name } = await os.inputText({ - title: this.$ts._deck.profile, - allowEmpty: false - }); - if (canceled) return; - this.profile = name; - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.deck, + icon: 'fas fa-columns', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue index 7edc81a309..e9f19aaf0b 100644 --- a/packages/client/src/pages/settings/delete-account.vue +++ b/packages/client/src/pages/settings/delete-account.vue @@ -1,64 +1,52 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo> - <FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo> - <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton> - <FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton> + <FormInfo warn class="_formBlock">{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo> + <FormInfo class="_formBlock">{{ i18n.ts._accountDelete.sendEmail }}</FormInfo> + <FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</FormButton> + <FormButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</FormButton> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormInfo from '@/components/ui/info.vue'; import FormButton from '@/components/ui/button.vue'; import * as os from '@/os'; import { signout } from '@/account'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._accountDelete.accountDelete, - icon: 'fas fa-exclamation-triangle', - bg: 'var(--bg)', - }, - } - }, +async function deleteAccount() { + { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.deleteAccountConfirm, + }); + if (canceled) return; + } - methods: { - async deleteAccount() { - { - const { canceled } = await os.confirm({ - type: 'warning', - text: this.$ts.deleteAccountConfirm, - }); - if (canceled) return; - } + const { canceled, result: password } = await os.inputText({ + title: i18n.ts.password, + type: 'password' + }); + if (canceled) return; - const { canceled, result: password } = await os.inputText({ - title: this.$ts.password, - type: 'password' - }); - if (canceled) return; + await os.apiWithDialog('i/delete-account', { + password: password + }); - await os.apiWithDialog('i/delete-account', { - password: password - }); + await os.alert({ + title: i18n.ts._accountDelete.started, + }); - await os.alert({ - title: this.$ts._accountDelete.started, - }); + await signout(); +} - signout(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._accountDelete.accountDelete, + icon: 'fas fa-exclamation-triangle', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue index 9309eb5ec7..09a2537ed5 100644 --- a/packages/client/src/pages/settings/drive.vue +++ b/packages/client/src/pages/settings/drive.vue @@ -1,41 +1,41 @@ <template> <div class="_formRoot"> <FormSection v-if="!fetching"> - <template #label>{{ $ts.usageAmount }}</template> + <template #label>{{ i18n.ts.usageAmount }}</template> <div class="_formBlock uawsfosz"> <div class="meter"><div :style="meterStyle"></div></div> </div> <FormSplit> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.capacity }}</template> + <template #key>{{ i18n.ts.capacity }}</template> <template #value>{{ bytes(capacity, 1) }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.inUse }}</template> + <template #key>{{ i18n.ts.inUse }}</template> <template #value>{{ bytes(usage, 1) }}</template> </MkKeyValue> </FormSplit> </FormSection> <FormSection> - <template #label>{{ $ts.statistics }}</template> + <template #label>{{ i18n.ts.statistics }}</template> <MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/> </FormSection> <FormSection> <FormLink @click="chooseUploadFolder()"> - {{ $ts.uploadFolder }} + {{ i18n.ts.uploadFolder }} <template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template> <template #suffixIcon><i class="fas fa-folder-open"></i></template> </FormLink> - <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ $ts.keepOriginalUploading }}<template #caption>{{ $ts.keepOriginalUploadingDescription }}</template></FormSwitch> + <FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ i18n.ts.keepOriginalUploading }}<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template></FormSwitch> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as tinycolor from 'tinycolor2'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import tinycolor from 'tinycolor2'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -46,80 +46,59 @@ import bytes from '@/filters/bytes'; import * as symbols from '@/symbols'; import { defaultStore } from '@/store'; import MkChart from '@/components/chart.vue'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkKeyValue, - FormSplit, - MkChart, - }, +const fetching = ref(true); +const usage = ref<any>(null); +const capacity = ref<any>(null); +const uploadFolder = ref<any>(null); - emits: ['info'], +const meterStyle = computed(() => { + return { + width: `${usage.value / capacity.value * 100}%`, + background: tinycolor({ + h: 180 - (usage.value / capacity.value * 180), + s: 0.7, + l: 0.5 + }) + }; +}); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.drive, - icon: 'fas fa-cloud', - bg: 'var(--bg)', - }, - fetching: true, - usage: null, - capacity: null, - uploadFolder: null, - } - }, +const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading')); - computed: { - meterStyle(): any { - return { - width: `${this.usage / this.capacity * 100}%`, - background: tinycolor({ - h: 180 - (this.usage / this.capacity * 180), - s: 0.7, - l: 0.5 - }) - }; - }, - keepOriginalUploading: defaultStore.makeGetterSetter('keepOriginalUploading'), - }, +os.api('drive').then(info => { + capacity.value = info.capacity; + usage.value = info.usage; + fetching.value = false; +}); - async created() { - os.api('drive').then(info => { - this.capacity = info.capacity; - this.usage = info.usage; - this.fetching = false; - this.$nextTick(() => { - this.renderChart(); - }); - }); +if (defaultStore.state.uploadFolder) { + os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder + }).then(response => { + uploadFolder.value = response; + }); +} - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder +function chooseUploadFolder() { + os.selectDriveFolder(false).then(async folder => { + defaultStore.set('uploadFolder', folder ? folder.id : null); + os.success(); + if (defaultStore.state.uploadFolder) { + uploadFolder.value = await os.api('drive/folders/show', { + folderId: defaultStore.state.uploadFolder }); + } else { + uploadFolder.value = null; } - }, - - methods: { - chooseUploadFolder() { - os.selectDriveFolder(false).then(async folder => { - this.$store.set('uploadFolder', folder ? folder.id : null); - os.success(); - if (this.$store.state.uploadFolder) { - this.uploadFolder = await os.api('drive/folders/show', { - folderId: this.$store.state.uploadFolder - }); - } else { - this.uploadFolder = null; - } - }); - }, + }); +} - bytes +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.drive, + icon: 'fas fa-cloud', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue index 4697fec9b7..37f14068e2 100644 --- a/packages/client/src/pages/settings/email.vue +++ b/packages/client/src/pages/settings/email.vue @@ -39,8 +39,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref, watch } from 'vue'; +<script lang="ts" setup> +import { defineExpose, onMounted, ref, watch } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormInput from '@/components/form/input.vue'; import FormSwitch from '@/components/form/switch.vue'; @@ -49,79 +49,62 @@ import * as symbols from '@/symbols'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormInput, - }, +const emailAddress = ref($i!.email); - emits: ['info'], +const onChangeReceiveAnnouncementEmail = (v) => { + os.api('i/update', { + receiveAnnouncementEmail: v + }); +}; - setup(props, context) { - const emailAddress = ref($i.email); - - const INFO = { - title: i18n.ts.email, - icon: 'fas fa-envelope', - bg: 'var(--bg)', - }; - - const onChangeReceiveAnnouncementEmail = (v) => { - os.api('i/update', { - receiveAnnouncementEmail: v - }); - }; - - const saveEmailAddress = () => { - os.inputText({ - title: i18n.ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.apiWithDialog('i/update-email', { - password: password, - email: emailAddress.value, - }); - }); - }; +const saveEmailAddress = () => { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.apiWithDialog('i/update-email', { + password: password, + email: emailAddress.value, + }); + }); +}; - const emailNotification_mention = ref($i.emailNotificationTypes.includes('mention')); - const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply')); - const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote')); - const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow')); - const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest')); - const emailNotification_groupInvited = ref($i.emailNotificationTypes.includes('groupInvited')); +const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention')); +const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply')); +const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote')); +const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow')); +const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest')); +const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited')); - const saveNotificationSettings = () => { - os.api('i/update', { - emailNotificationTypes: [ - ...[emailNotification_mention.value ? 'mention' : null], - ...[emailNotification_reply.value ? 'reply' : null], - ...[emailNotification_quote.value ? 'quote' : null], - ...[emailNotification_follow.value ? 'follow' : null], - ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], - ...[emailNotification_groupInvited.value ? 'groupInvited' : null], - ].filter(x => x != null) - }); - }; +const saveNotificationSettings = () => { + os.api('i/update', { + emailNotificationTypes: [ + ...[emailNotification_mention.value ? 'mention' : null], + ...[emailNotification_reply.value ? 'reply' : null], + ...[emailNotification_quote.value ? 'quote' : null], + ...[emailNotification_follow.value ? 'follow' : null], + ...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null], + ...[emailNotification_groupInvited.value ? 'groupInvited' : null], + ].filter(x => x != null) + }); +}; - watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { - saveNotificationSettings(); - }); +watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => { + saveNotificationSettings(); +}); - onMounted(() => { - watch(emailAddress, () => { - saveEmailAddress(); - }); - }); +onMounted(() => { + watch(emailAddress, () => { + saveEmailAddress(); + }); +}); - return { - [symbols.PAGE_INFO]: INFO, - emailAddress, - onChangeReceiveAnnouncementEmail, - emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.email, + icon: 'fas fa-envelope', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue index c8f6f58322..64b8cc3106 100644 --- a/packages/client/src/pages/settings/general.vue +++ b/packages/client/src/pages/settings/general.vue @@ -1,10 +1,10 @@ <template> <div class="_formRoot"> <FormSelect v-model="lang" class="_formBlock"> - <template #label>{{ $ts.uiLanguage }}</template> + <template #label>{{ i18n.ts.uiLanguage }}</template> <option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option> <template #caption> - <I18n :src="$ts.i18nInfo" tag="span"> + <I18n :src="i18n.ts.i18nInfo" tag="span"> <template #link> <MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink> </template> @@ -13,48 +13,48 @@ </FormSelect> <FormRadios v-model="overridedDeviceKind" class="_formBlock"> - <template #label>{{ $ts.overridedDeviceKind }}</template> - <option :value="null">{{ $ts.auto }}</option> - <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ $ts.smartphone }}</option> - <option value="tablet"><i class="fas fa-tablet-alt"/> {{ $ts.tablet }}</option> - <option value="desktop"><i class="fas fa-desktop"/> {{ $ts.desktop }}</option> + <template #label>{{ i18n.ts.overridedDeviceKind }}</template> + <option :value="null">{{ i18n.ts.auto }}</option> + <option value="smartphone"><i class="fas fa-mobile-alt"/> {{ i18n.ts.smartphone }}</option> + <option value="tablet"><i class="fas fa-tablet-alt"/> {{ i18n.ts.tablet }}</option> + <option value="desktop"><i class="fas fa-desktop"/> {{ i18n.ts.desktop }}</option> </FormRadios> - <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ $ts.showFixedPostForm }}</FormSwitch> + <FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ i18n.ts.showFixedPostForm }}</FormSwitch> <FormSection> - <template #label>{{ $ts.behavior }}</template> - <FormSwitch v-model="imageNewTab" class="_formBlock">{{ $ts.openImageInNewTab }}</FormSwitch> - <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ $ts.enableInfiniteScroll }}</FormSwitch> - <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch> - <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ $ts.disablePagesScript }}</FormSwitch> + <template #label>{{ i18n.ts.behavior }}</template> + <FormSwitch v-model="imageNewTab" class="_formBlock">{{ i18n.ts.openImageInNewTab }}</FormSwitch> + <FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ i18n.ts.enableInfiniteScroll }}</FormSwitch> + <FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch> + <FormSwitch v-model="disablePagesScript" class="_formBlock">{{ i18n.ts.disablePagesScript }}</FormSwitch> <FormSelect v-model="serverDisconnectedBehavior" class="_formBlock"> - <template #label>{{ $ts.whenServerDisconnected }}</template> - <option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option> - <option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option> - <option value="quiet">{{ $ts._serverDisconnectedBehavior.quiet }}</option> + <template #label>{{ i18n.ts.whenServerDisconnected }}</template> + <option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option> + <option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option> + <option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option> </FormSelect> </FormSection> <FormSection> - <template #label>{{ $ts.appearance }}</template> - <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ $ts.disableAnimatedMfm }}</FormSwitch> - <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ $ts.reduceUiAnimation }}</FormSwitch> - <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ $ts.useBlurEffect }}</FormSwitch> - <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ $ts.useBlurEffectForModal }}</FormSwitch> - <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch> - <FormSwitch v-model="loadRawImages" class="_formBlock">{{ $ts.loadRawImages }}</FormSwitch> - <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ $ts.disableShowingAnimatedImages }}</FormSwitch> - <FormSwitch v-model="squareAvatars" class="_formBlock">{{ $ts.squareAvatars }}</FormSwitch> - <FormSwitch v-model="useSystemFont" class="_formBlock">{{ $ts.useSystemFont }}</FormSwitch> - <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ $ts.useOsNativeEmojis }} + <template #label>{{ i18n.ts.appearance }}</template> + <FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ i18n.ts.disableAnimatedMfm }}</FormSwitch> + <FormSwitch v-model="reduceAnimation" class="_formBlock">{{ i18n.ts.reduceUiAnimation }}</FormSwitch> + <FormSwitch v-model="useBlurEffect" class="_formBlock">{{ i18n.ts.useBlurEffect }}</FormSwitch> + <FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ i18n.ts.useBlurEffectForModal }}</FormSwitch> + <FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch> + <FormSwitch v-model="loadRawImages" class="_formBlock">{{ i18n.ts.loadRawImages }}</FormSwitch> + <FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch> + <FormSwitch v-model="squareAvatars" class="_formBlock">{{ i18n.ts.squareAvatars }}</FormSwitch> + <FormSwitch v-model="useSystemFont" class="_formBlock">{{ i18n.ts.useSystemFont }}</FormSwitch> + <FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ i18n.ts.useOsNativeEmojis }} <div><Mfm :key="useOsNativeEmojis" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div> </FormSwitch> - <FormSwitch v-model="disableDrawer" class="_formBlock">{{ $ts.disableDrawer }}</FormSwitch> + <FormSwitch v-model="disableDrawer" class="_formBlock">{{ i18n.ts.disableDrawer }}</FormSwitch> <FormRadios v-model="fontSize" class="_formBlock"> - <template #label>{{ $ts.fontSize }}</template> + <template #label>{{ i18n.ts.fontSize }}</template> <option value="small"><span style="font-size: 14px;">Aa</span></option> <option :value="null"><span style="font-size: 16px;">Aa</span></option> <option value="large"><span style="font-size: 18px;">Aa</span></option> @@ -63,36 +63,36 @@ </FormSection> <FormSection> - <FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch> + <FormSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</FormSwitch> </FormSection> <FormSelect v-model="instanceTicker" class="_formBlock"> - <template #label>{{ $ts.instanceTicker }}</template> - <option value="none">{{ $ts._instanceTicker.none }}</option> - <option value="remote">{{ $ts._instanceTicker.remote }}</option> - <option value="always">{{ $ts._instanceTicker.always }}</option> + <template #label>{{ i18n.ts.instanceTicker }}</template> + <option value="none">{{ i18n.ts._instanceTicker.none }}</option> + <option value="remote">{{ i18n.ts._instanceTicker.remote }}</option> + <option value="always">{{ i18n.ts._instanceTicker.always }}</option> </FormSelect> <FormSelect v-model="nsfw" class="_formBlock"> - <template #label>{{ $ts.nsfw }}</template> - <option value="respect">{{ $ts._nsfw.respect }}</option> - <option value="ignore">{{ $ts._nsfw.ignore }}</option> - <option value="force">{{ $ts._nsfw.force }}</option> + <template #label>{{ i18n.ts.nsfw }}</template> + <option value="respect">{{ i18n.ts._nsfw.respect }}</option> + <option value="ignore">{{ i18n.ts._nsfw.ignore }}</option> + <option value="force">{{ i18n.ts._nsfw.force }}</option> </FormSelect> <FormGroup> - <template #label>{{ $ts.defaultNavigationBehaviour }}</template> - <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> + <template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template> + <FormSwitch v-model="defaultSideView">{{ i18n.ts.openInSideView }}</FormSwitch> </FormGroup> - <FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink> + <FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink> - <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink> + <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -102,122 +102,87 @@ import FormLink from '@/components/form/link.vue'; import MkLink from '@/components/link.vue'; import { langs } from '@/config'; import { defaultStore } from '@/store'; -import { ColdDeviceStorage } from '@/store'; import * as os from '@/os'; import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkLink, - FormSwitch, - FormSelect, - FormRadios, - FormGroup, - FormLink, - FormSection, - }, +const lang = ref(localStorage.getItem('lang')); +const fontSize = ref(localStorage.getItem('fontSize')); +const useSystemFont = ref(localStorage.getItem('useSystemFont') != null); - emits: ['info'], +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting, + }); + if (canceled) return; - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.general, - icon: 'fas fa-cogs', - bg: 'var(--bg)' - }, - langs, - lang: localStorage.getItem('lang'), - fontSize: localStorage.getItem('fontSize'), - useSystemFont: localStorage.getItem('useSystemFont') != null, - } - }, + unisonReload(); +} - computed: { - overridedDeviceKind: defaultStore.makeGetterSetter('overridedDeviceKind'), - serverDisconnectedBehavior: defaultStore.makeGetterSetter('serverDisconnectedBehavior'), - reduceAnimation: defaultStore.makeGetterSetter('animation', v => !v, v => !v), - useBlurEffectForModal: defaultStore.makeGetterSetter('useBlurEffectForModal'), - useBlurEffect: defaultStore.makeGetterSetter('useBlurEffect'), - showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'), - disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v), - useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'), - disableDrawer: defaultStore.makeGetterSetter('disableDrawer'), - disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'), - loadRawImages: defaultStore.makeGetterSetter('loadRawImages'), - imageNewTab: defaultStore.makeGetterSetter('imageNewTab'), - nsfw: defaultStore.makeGetterSetter('nsfw'), - disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'), - showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'), - defaultSideView: defaultStore.makeGetterSetter('defaultSideView'), - instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), - enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), - useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'), - squareAvatars: defaultStore.makeGetterSetter('squareAvatars'), - aiChanMode: defaultStore.makeGetterSetter('aiChanMode'), - }, +const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind')); +const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior')); +const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v)); +const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal')); +const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect')); +const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline')); +const disableAnimatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v)); +const useOsNativeEmojis = computed(defaultStore.makeGetterSetter('useOsNativeEmojis')); +const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer')); +const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages')); +const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages')); +const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab')); +const nsfw = computed(defaultStore.makeGetterSetter('nsfw')); +const disablePagesScript = computed(defaultStore.makeGetterSetter('disablePagesScript')); +const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm')); +const defaultSideView = computed(defaultStore.makeGetterSetter('defaultSideView')); +const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker')); +const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll')); +const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu')); +const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars')); +const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode')); - watch: { - lang() { - localStorage.setItem('lang', this.lang); - localStorage.removeItem('locale'); - this.reloadAsk(); - }, - - fontSize() { - if (this.fontSize == null) { - localStorage.removeItem('fontSize'); - } else { - localStorage.setItem('fontSize', this.fontSize); - } - this.reloadAsk(); - }, - - useSystemFont() { - if (this.useSystemFont) { - localStorage.setItem('useSystemFont', 't'); - } else { - localStorage.removeItem('useSystemFont'); - } - this.reloadAsk(); - }, - - enableInfiniteScroll() { - this.reloadAsk(); - }, - - squareAvatars() { - this.reloadAsk(); - }, - - aiChanMode() { - this.reloadAsk(); - }, - - showGapBetweenNotesInTimeline() { - this.reloadAsk(); - }, +watch(lang, () => { + localStorage.setItem('lang', lang.value as string); + localStorage.removeItem('locale'); +}); - instanceTicker() { - this.reloadAsk(); - }, +watch(fontSize, () => { + if (fontSize.value == null) { + localStorage.removeItem('fontSize'); + } else { + localStorage.setItem('fontSize', fontSize.value); + } +}); - overridedDeviceKind() { - this.reloadAsk(); - }, - }, +watch(useSystemFont, () => { + if (useSystemFont.value) { + localStorage.setItem('useSystemFont', 't'); + } else { + localStorage.removeItem('useSystemFont'); + } +}); - methods: { - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - }); - if (canceled) return; +watch([ + lang, + fontSize, + useSystemFont, + enableInfiniteScroll, + squareAvatars, + aiChanMode, + showGapBetweenNotesInTimeline, + instanceTicker, + overridedDeviceKind +], async () => { + await reloadAsk(); +}); - unisonReload(); - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.general, + icon: 'fas fa-cogs', + bg: 'var(--bg)' } }); </script> diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue index c153b4d28c..127cbcd4c1 100644 --- a/packages/client/src/pages/settings/import-export.vue +++ b/packages/client/src/pages/settings/import-export.vue @@ -37,8 +37,8 @@ </div> </template> -<script lang="ts"> -import { defineComponent, onMounted, ref } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormSection from '@/components/form/section.vue'; import FormGroup from '@/components/form/group.vue'; @@ -48,108 +48,80 @@ import { selectFile } from '@/scripts/select-file'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormGroup, - FormSwitch, - MkButton, - }, +const excludeMutingUsers = ref(false); +const excludeInactiveUsers = ref(false); - emits: ['info'], +const onExportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.exportRequested, + }); +}; - setup(props, context) { - const INFO = { - title: i18n.ts.importAndExport, - icon: 'fas fa-boxes', - bg: 'var(--bg)', - }; +const onImportSuccess = () => { + os.alert({ + type: 'info', + text: i18n.ts.importRequested, + }); +}; - const excludeMutingUsers = ref(false); - const excludeInactiveUsers = ref(false); +const onError = (ev) => { + os.alert({ + type: 'error', + text: ev.message, + }); +}; - const onExportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.exportRequested, - }); - }; +const exportNotes = () => { + os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); +}; - const onImportSuccess = () => { - os.alert({ - type: 'info', - text: i18n.ts.importRequested, - }); - }; +const exportFollowing = () => { + os.api('i/export-following', { + excludeMuting: excludeMutingUsers.value, + excludeInactive: excludeInactiveUsers.value, + }) + .then(onExportSuccess).catch(onError); +}; - const onError = (e) => { - os.alert({ - type: 'error', - text: e.message, - }); - }; +const exportBlocking = () => { + os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); +}; - const exportNotes = () => { - os.api('i/export-notes', {}).then(onExportSuccess).catch(onError); - }; +const exportUserLists = () => { + os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); +}; - const exportFollowing = () => { - os.api('i/export-following', { - excludeMuting: excludeMutingUsers.value, - excludeInactive: excludeInactiveUsers.value, - }) - .then(onExportSuccess).catch(onError); - }; +const exportMuting = () => { + os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); +}; - const exportBlocking = () => { - os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError); - }; +const importFollowing = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportUserLists = () => { - os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError); - }; +const importUserLists = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const exportMuting = () => { - os.api('i/export-mute', {}).then(onExportSuccess).catch(onError); - }; +const importMuting = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importFollowing = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; +const importBlocking = async (ev) => { + const file = await selectFile(ev.currentTarget ?? ev.target); + os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); +}; - const importUserLists = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - const importMuting = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - const importBlocking = async (ev) => { - const file = await selectFile(ev.currentTarget ?? ev.target); - os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError); - }; - - return { - [symbols.PAGE_INFO]: INFO, - excludeMutingUsers, - excludeInactiveUsers, - - exportNotes, - exportFollowing, - exportBlocking, - exportUserLists, - exportMuting, - - importFollowing, - importUserLists, - importMuting, - importBlocking, - }; - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.importAndExport, + icon: 'fas fa-boxes', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue index 44c3be62fe..e6670ea930 100644 --- a/packages/client/src/pages/settings/index.vue +++ b/packages/client/src/pages/settings/index.vue @@ -2,19 +2,22 @@ <MkSpacer :content-max="900" :margin-min="20" :margin-max="32"> <div ref="el" class="vvcocwet" :class="{ wide: !narrow }"> <div class="header"> - <div class="title">{{ $ts.settings }}</div> + <div class="title"> + <MkA v-if="narrow" to="/settings">{{ $ts.settings }}</MkA> + <template v-else>{{ $ts.settings }}</template> + </div> <div v-if="childInfo" class="subtitle">{{ childInfo.title }}</div> </div> <div class="body"> - <div v-if="!narrow || page == null" class="nav"> + <div v-if="!narrow || initialPage == null" class="nav"> <div class="baaadecd"> <MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo> - <MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu> + <MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu> </div> </div> - <div class="main"> + <div v-if="!(narrow && initialPage == null)" class="main"> <div class="bkzroven"> - <component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/> + <component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/> </div> </div> </div> @@ -23,7 +26,7 @@ </template> <script setup lang="ts"> -import { computed, defineAsyncComponent, nextTick, onMounted, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { i18n } from '@/i18n'; import MkInfo from '@/components/ui/info.vue'; import MkSuperMenu from '@/components/ui/super-menu.vue'; @@ -33,6 +36,7 @@ import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; import { instance } from '@/instance'; import { $i } from '@/account'; +import { MisskeyNavigator } from '@/scripts/navigate'; const props = defineProps<{ initialPage?: string @@ -45,53 +49,61 @@ const indexInfo = { hideHeader: true, }; const INFO = ref(indexInfo); -const page = ref(props.initialPage); -const narrow = ref(false); -const view = ref(null); const el = ref<HTMLElement | null>(null); const childInfo = ref(null); + +const nav = new MisskeyNavigator(); + +const narrow = ref(false); +const NARROW_THRESHOLD = 600; + +const ro = new ResizeObserver((entries, observer) => { + if (entries.length === 0) return; + narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD; +}); + const menuDef = computed(() => [{ title: i18n.ts.basicSettings, items: [{ icon: 'fas fa-user', text: i18n.ts.profile, to: '/settings/profile', - active: page.value === 'profile', + active: props.initialPage === 'profile', }, { icon: 'fas fa-lock-open', text: i18n.ts.privacy, to: '/settings/privacy', - active: page.value === 'privacy', + active: props.initialPage === 'privacy', }, { icon: 'fas fa-laugh', text: i18n.ts.reaction, to: '/settings/reaction', - active: page.value === 'reaction', + active: props.initialPage === 'reaction', }, { icon: 'fas fa-cloud', text: i18n.ts.drive, to: '/settings/drive', - active: page.value === 'drive', + active: props.initialPage === 'drive', }, { icon: 'fas fa-bell', text: i18n.ts.notifications, to: '/settings/notifications', - active: page.value === 'notifications', + active: props.initialPage === 'notifications', }, { icon: 'fas fa-envelope', text: i18n.ts.email, to: '/settings/email', - active: page.value === 'email', + active: props.initialPage === 'email', }, { icon: 'fas fa-share-alt', text: i18n.ts.integration, to: '/settings/integration', - active: page.value === 'integration', + active: props.initialPage === 'integration', }, { icon: 'fas fa-lock', text: i18n.ts.security, to: '/settings/security', - active: page.value === 'security', + active: props.initialPage === 'security', }], }, { title: i18n.ts.clientSettings, @@ -99,27 +111,27 @@ const menuDef = computed(() => [{ icon: 'fas fa-cogs', text: i18n.ts.general, to: '/settings/general', - active: page.value === 'general', + active: props.initialPage === 'general', }, { icon: 'fas fa-palette', text: i18n.ts.theme, to: '/settings/theme', - active: page.value === 'theme', + active: props.initialPage === 'theme', }, { icon: 'fas fa-list-ul', text: i18n.ts.menu, to: '/settings/menu', - active: page.value === 'menu', + active: props.initialPage === 'menu', }, { icon: 'fas fa-music', text: i18n.ts.sounds, to: '/settings/sounds', - active: page.value === 'sounds', + active: props.initialPage === 'sounds', }, { icon: 'fas fa-plug', text: i18n.ts.plugins, to: '/settings/plugin', - active: page.value === 'plugin', + active: props.initialPage === 'plugin', }], }, { title: i18n.ts.otherSettings, @@ -127,37 +139,37 @@ const menuDef = computed(() => [{ icon: 'fas fa-boxes', text: i18n.ts.importAndExport, to: '/settings/import-export', - active: page.value === 'import-export', + active: props.initialPage === 'import-export', }, { icon: 'fas fa-volume-mute', text: i18n.ts.instanceMute, to: '/settings/instance-mute', - active: page.value === 'instance-mute', + active: props.initialPage === 'instance-mute', }, { icon: 'fas fa-ban', text: i18n.ts.muteAndBlock, to: '/settings/mute-block', - active: page.value === 'mute-block', + active: props.initialPage === 'mute-block', }, { icon: 'fas fa-comment-slash', text: i18n.ts.wordMute, to: '/settings/word-mute', - active: page.value === 'word-mute', + active: props.initialPage === 'word-mute', }, { icon: 'fas fa-key', text: 'API', to: '/settings/api', - active: page.value === 'api', + active: props.initialPage === 'api', }, { icon: 'fas fa-bolt', text: 'Webhook', to: '/settings/webhook', - active: page.value === 'webhook', + active: props.initialPage === 'webhook', }, { icon: 'fas fa-ellipsis-h', text: i18n.ts.other, to: '/settings/other', - active: page.value === 'other', + active: props.initialPage === 'other', }], }, { items: [{ @@ -182,8 +194,8 @@ const menuDef = computed(() => [{ const pageProps = ref({}); const component = computed(() => { - if (page.value == null) return null; - switch (page.value) { + if (props.initialPage == null) return null; + switch (props.initialPage) { case 'accounts': return defineAsyncComponent(() => import('./accounts.vue')); case 'profile': return defineAsyncComponent(() => import('./profile.vue')); case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); @@ -230,27 +242,41 @@ watch(component, () => { watch(() => props.initialPage, () => { if (props.initialPage == null && !narrow.value) { - page.value = 'profile'; + nav.push('/settings/profile'); } else { - page.value = props.initialPage; if (props.initialPage == null) { INFO.value = indexInfo; } } }); +watch(narrow, () => { + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); + } +}); + onMounted(() => { - narrow.value = el.value.offsetWidth < 800; - if (!narrow.value) { - page.value = 'profile'; + ro.observe(el.value); + + narrow.value = el.value.offsetWidth < NARROW_THRESHOLD; + if (props.initialPage == null && !narrow.value) { + nav.push('/settings/profile'); } }); +onUnmounted(() => { + ro.disconnect(); +}); + const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified)); const pageChanged = (page) => { - if (page == null) return; - childInfo.value = page[symbols.PAGE_INFO]; + if (page == null) { + childInfo.value = null; + } else { + childInfo.value = page[symbols.PAGE_INFO]; + } }; defineExpose({ @@ -267,6 +293,7 @@ defineExpose({ font-weight: bold; > .title { + display: block; width: 34%; } diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue index f84a209b60..bcc2ee85ad 100644 --- a/packages/client/src/pages/settings/instance-mute.vue +++ b/packages/client/src/pages/settings/instance-mute.vue @@ -1,67 +1,51 @@ <template> <div class="_formRoot"> - <MkInfo>{{ $ts._instanceMute.title }}</MkInfo> + <MkInfo>{{ i18n.ts._instanceMute.title }}</MkInfo> <FormTextarea v-model="instanceMutes" class="_formBlock"> - <template #label>{{ $ts._instanceMute.heading }}</template> - <template #caption>{{ $ts._instanceMute.instanceMuteDescription }}<br>{{ $ts._instanceMute.instanceMuteDescription2 }}</template> + <template #label>{{ i18n.ts._instanceMute.heading }}</template> + <template #caption>{{ i18n.ts._instanceMute.instanceMuteDescription }}<br>{{ i18n.ts._instanceMute.instanceMuteDescription2 }}</template> </FormTextarea> - <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkInfo from '@/components/ui/info.vue'; import MkButton from '@/components/ui/button.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkInfo, - }, +const instanceMutes = ref($i!.mutedInstances.join('\n')); +const changed = ref(false); - emits: ['info'], +async function save() { + let mutes = instanceMutes.value + .trim().split('\n') + .map(el => el.trim()) + .filter(el => el); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.instanceMute, - icon: 'fas fa-volume-mute' - }, - tab: 'soft', - instanceMutes: '', - changed: false, - } - }, + await os.api('i/update', { + mutedInstances: mutes, + }); - watch: { - instanceMutes: { - handler() { - this.changed = true; - }, - deep: true - }, - }, + changed.value = false; - async created() { - this.instanceMutes = this.$i.mutedInstances.join('\n'); - }, + // Refresh filtered list to signal to the user how they've been saved + instanceMutes.value = mutes.join('\n'); +} - methods: { - async save() { - let mutes = this.instanceMutes.trim().split('\n').map(el => el.trim()).filter(el => el); - await os.api('i/update', { - mutedInstances: mutes, - }); - this.changed = false; +watch(instanceMutes, () => { + changed.value = true; +}); - // Refresh filtered list to signal to the user how they've been saved - this.instanceMutes = mutes.join('\n'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.instanceMute, + icon: 'fas fa-volume-mute' } -}) +}); </script> diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue index ca36c91665..75c6200944 100644 --- a/packages/client/src/pages/settings/integration.vue +++ b/packages/client/src/pages/settings/integration.vue @@ -1,133 +1,98 @@ <template> <div class="_formRoot"> - <FormSection v-if="enableTwitterIntegration"> + <FormSection v-if="instance.enableTwitterIntegration"> <template #label><i class="fab fa-twitter"></i> Twitter</template> - <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> - <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> + <MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableDiscordIntegration"> + <FormSection v-if="instance.enableDiscordIntegration"> <template #label><i class="fab fa-discord"></i> Discord</template> - <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> - <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.discord">{{ i18n.ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> + <MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectDiscord">{{ i18n.ts.connectService }}</MkButton> </FormSection> - <FormSection v-if="enableGithubIntegration"> + <FormSection v-if="instance.enableGithubIntegration"> <template #label><i class="fab fa-github"></i> GitHub</template> - <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> - <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton> - <MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton> + <p v-if="integrations.github">{{ i18n.ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> + <MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton> + <MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, onMounted, ref, watch } from 'vue'; import { apiUrl } from '@/config'; import FormSection from '@/components/form/section.vue'; import MkButton from '@/components/ui/button.vue'; -import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { instance } from '@/instance'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - MkButton - }, +const twitterForm = ref<Window | null>(null); +const discordForm = ref<Window | null>(null); +const githubForm = ref<Window | null>(null); - emits: ['info'], +const integrations = computed(() => $i!.integrations); - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.integration, - icon: 'fas fa-share-alt', - bg: 'var(--bg)', - }, - apiUrl, - twitterForm: null, - discordForm: null, - githubForm: null, - enableTwitterIntegration: false, - enableDiscordIntegration: false, - enableGithubIntegration: false, - }; - }, +function openWindow(service: string, type: string) { + return window.open(`${apiUrl}/${type}/${service}`, + `${service}_${type}_window`, + 'height=570, width=520' + ); +} - computed: { - integrations() { - return this.$i.integrations; - }, - - meta() { - return this.$instance; - }, - }, +function connectTwitter() { + twitterForm.value = openWindow('twitter', 'connect'); +} - created() { - this.enableTwitterIntegration = this.meta.enableTwitterIntegration; - this.enableDiscordIntegration = this.meta.enableDiscordIntegration; - this.enableGithubIntegration = this.meta.enableGithubIntegration; - }, +function disconnectTwitter() { + openWindow('twitter', 'disconnect'); +} - mounted() { - document.cookie = `igi=${this.$i.token}; path=/;` + - ` max-age=31536000;` + - (document.location.protocol.startsWith('https') ? ' secure' : ''); +function connectDiscord() { + discordForm.value = openWindow('discord', 'connect'); +} - this.$watch('integrations', () => { - if (this.integrations.twitter) { - if (this.twitterForm) this.twitterForm.close(); - } - if (this.integrations.discord) { - if (this.discordForm) this.discordForm.close(); - } - if (this.integrations.github) { - if (this.githubForm) this.githubForm.close(); - } - }, { - deep: true - }); - }, +function disconnectDiscord() { + openWindow('discord', 'disconnect'); +} - methods: { - connectTwitter() { - this.twitterForm = window.open(apiUrl + '/connect/twitter', - 'twitter_connect_window', - 'height=570, width=520'); - }, +function connectGithub() { + githubForm.value = openWindow('github', 'connect'); +} - disconnectTwitter() { - window.open(apiUrl + '/disconnect/twitter', - 'twitter_disconnect_window', - 'height=570, width=520'); - }, +function disconnectGithub() { + openWindow('github', 'disconnect'); +} - connectDiscord() { - this.discordForm = window.open(apiUrl + '/connect/discord', - 'discord_connect_window', - 'height=570, width=520'); - }, +onMounted(() => { + document.cookie = `igi=${$i!.token}; path=/;` + + ` max-age=31536000;` + + (document.location.protocol.startsWith('https') ? ' secure' : ''); - disconnectDiscord() { - window.open(apiUrl + '/disconnect/discord', - 'discord_disconnect_window', - 'height=570, width=520'); - }, - - connectGithub() { - this.githubForm = window.open(apiUrl + '/connect/github', - 'github_connect_window', - 'height=570, width=520'); - }, + watch(integrations, () => { + if (integrations.value.twitter) { + if (twitterForm.value) twitterForm.value.close(); + } + if (integrations.value.discord) { + if (discordForm.value) discordForm.value.close(); + } + if (integrations.value.github) { + if (githubForm.value) githubForm.value.close(); + } + }); +}); - disconnectGithub() { - window.open(apiUrl + '/disconnect/github', - 'github_disconnect_window', - 'height=570, width=520'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.integration, + icon: 'fas fa-share-alt', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue index 6e38cd5dfe..2288c3f718 100644 --- a/packages/client/src/pages/settings/menu.vue +++ b/packages/client/src/pages/settings/menu.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormTextarea v-model="items" tall manual-save class="_formBlock"> - <template #label>{{ $ts.menu }}</template> - <template #caption><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template> + <template #label>{{ i18n.ts.menu }}</template> + <template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template> </FormTextarea> <FormRadios v-model="menuDisplay" class="_formBlock"> - <template #label>{{ $ts.display }}</template> - <option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option> - <option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option> - <option value="top">{{ $ts._menuDisplay.top }}</option> - <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 --> + <template #label>{{ i18n.ts.display }}</template> + <option value="sideFull">{{ i18n.ts._menuDisplay.sideFull }}</option> + <option value="sideIcon">{{ i18n.ts._menuDisplay.sideIcon }}</option> + <option value="top">{{ i18n.ts._menuDisplay.top }}</option> + <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 --> </FormRadios> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormRadios from '@/components/form/radios.vue'; import FormButton from '@/components/ui/button.vue'; @@ -27,81 +27,60 @@ import { menuDef } from '@/menu'; import { defaultStore } from '@/store'; import * as symbols from '@/symbols'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormButton, - FormTextarea, - FormRadios, - }, +const items = ref(defaultStore.state.menu.join('\n')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.menu, - icon: 'fas fa-list-ul', - bg: 'var(--bg)', - }, - menuDef: menuDef, - items: defaultStore.state.menu.join('\n'), - } - }, +const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== '')); +const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); - computed: { - splited(): string[] { - return this.items.trim().split('\n').filter(x => x.trim() !== ''); - }, +async function reloadAsk() { + const { canceled } = await os.confirm({ + type: 'info', + text: i18n.ts.reloadToApplySetting + }); + if (canceled) return; - menuDisplay: defaultStore.makeGetterSetter('menuDisplay') - }, + unisonReload(); +} - watch: { - menuDisplay() { - this.reloadAsk(); - }, +async function addItem() { + const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k)); + const { canceled, result: item } = await os.select({ + title: i18n.ts.addItem, + items: [...menu.map(k => ({ + value: k, text: i18n.ts[menuDef[k].title] + })), { + value: '-', text: i18n.ts.divider + }] + }); + if (canceled) return; + items.value = [...split.value, item].join('\n'); +} - items() { - this.save(); - }, - }, +async function save() { + defaultStore.set('menu', split.value); + await reloadAsk(); +} - methods: { - async addItem() { - const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k)); - const { canceled, result: item } = await os.select({ - title: this.$ts.addItem, - items: [...menu.map(k => ({ - value: k, text: this.$ts[this.menuDef[k].title] - })), ...[{ - value: '-', text: this.$ts.divider - }]] - }); - if (canceled) return; - this.items = [...this.splited, item].join('\n'); - }, +function reset() { + defaultStore.reset('menu'); + items.value = defaultStore.state.menu.join('\n'); +} - save() { - this.$store.set('menu', this.splited); - this.reloadAsk(); - }, - - reset() { - this.$store.reset('menu'); - this.items = this.$store.state.menu.join('\n'); - }, +watch(items, async () => { + await save(); +}); - async reloadAsk() { - const { canceled } = await os.confirm({ - type: 'info', - text: this.$ts.reloadToApplySetting, - showCancelButton: true - }); - if (canceled) return; +watch(menuDisplay, async () => { + await reloadAsk(); +}); - unisonReload(); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.menu, + icon: 'fas fa-list-ul', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue index 12171530bb..b8fff95a8d 100644 --- a/packages/client/src/pages/settings/notifications.vue +++ b/packages/client/src/pages/settings/notifications.vue @@ -1,71 +1,59 @@ <template> <div class="_formRoot"> - <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ $ts.notificationSetting }}</FormLink> + <FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ i18n.ts.notificationSetting }}</FormLink> <FormSection> - <FormLink class="_formBlock" @click="readAllNotifications">{{ $ts.markAsReadAllNotifications }}</FormLink> - <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ $ts.markAsReadAllUnreadNotes }}</FormLink> - <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ $ts.markAsReadAllTalkMessages }}</FormLink> + <FormLink class="_formBlock" @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink> + <FormLink class="_formBlock" @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink> + <FormLink class="_formBlock" @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineAsyncComponent, defineExpose } from 'vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import { notificationTypes } from 'misskey-js'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormSection, - }, +async function readAllUnreadNotes() { + await os.api('i/read-all-unread-notes'); +} - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.notifications, - icon: 'fas fa-bell', - bg: 'var(--bg)', - }, - } - }, - - methods: { - readAllUnreadNotes() { - os.api('i/read-all-unread-notes'); - }, +async function readAllMessagingMessages() { + await os.api('i/read-all-messaging-messages'); +} - readAllMessagingMessages() { - os.api('i/read-all-messaging-messages'); - }, +async function readAllNotifications() { + await os.api('notifications/mark-all-as-read'); +} - readAllNotifications() { - os.api('notifications/mark-all-as-read'); - }, +function configure() { + const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x)); + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { + includingTypes, + showGlobalToggle: false, + }, { + done: async (res) => { + const { includingTypes: value } = res; + await os.apiWithDialog('i/update', { + mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), + }).then(i => { + $i!.mutingNotificationTypes = i.mutingNotificationTypes; + }); + } + }, 'closed'); +} - configure() { - const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x)); - os.popup(import('@/components/notification-setting-window.vue'), { - includingTypes, - showGlobalToggle: false, - }, { - done: async (res) => { - const { includingTypes: value } = res; - await os.apiWithDialog('i/update', { - mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)), - }).then(i => { - this.$i.mutingNotificationTypes = i.mutingNotificationTypes; - }); - } - }, 'closed'); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.notifications, + icon: 'fas fa-bell', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue index a9903acc7e..82e174a5b4 100644 --- a/packages/client/src/pages/settings/other.vue +++ b/packages/client/src/pages/settings/other.vue @@ -1,66 +1,44 @@ <template> <div class="_formRoot"> - <FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> - {{ $ts.showFeaturedNotesInTimeline }} + <FormSwitch v-model="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote"> + {{ i18n.ts.showFeaturedNotesInTimeline }} </FormSwitch> <!-- - <FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch> + <FormSwitch v-model="reportError" class="_formBlock">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></FormSwitch> --> - <FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink> + <FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink> - <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink> + <FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink> </div> </template> -<script lang="ts"> -import { defineAsyncComponent, defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose } from 'vue'; import FormSwitch from '@/components/form/switch.vue'; -import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import * as os from '@/os'; -import { debug } from '@/config'; import { defaultStore } from '@/store'; -import { unisonReload } from '@/scripts/unison-reload'; import * as symbols from '@/symbols'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormSwitch, - FormLink, - }, +const reportError = computed(defaultStore.makeGetterSetter('reportError')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.other, - icon: 'fas fa-ellipsis-h', - bg: 'var(--bg)', - }, - debug, - } - }, +function onChangeInjectFeaturedNote(v) { + os.api('i/update', { + injectFeaturedNote: v + }).then((i) => { + $i!.injectFeaturedNote = i.injectFeaturedNote; + }); +} - computed: { - reportError: defaultStore.makeGetterSetter('reportError'), - }, - - methods: { - changeDebug(v) { - console.log(v); - localStorage.setItem('debug', v.toString()); - unisonReload(); - }, - - onChangeInjectFeaturedNote(v) { - os.api('i/update', { - injectFeaturedNote: v - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.other, + icon: 'fas fa-ellipsis-h', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue index d35d20d17a..96c0abfd99 100644 --- a/packages/client/src/pages/settings/plugin.install.vue +++ b/packages/client/src/pages/settings/plugin.install.vue @@ -1,19 +1,19 @@ <template> <div class="_formRoot"> - <FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo> + <FormInfo warn class="_formBlock">{{ i18n.ts._plugin.installWarn }}</FormInfo> <FormTextarea v-model="code" tall class="_formBlock"> - <template #label>{{ $ts.code }}</template> + <template #label>{{ i18n.ts.code }}</template> </FormTextarea> <div class="_formBlock"> - <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton> + <FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ i18n.ts.install }}</FormButton> </div> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, defineAsyncComponent, nextTick, ref } from 'vue'; import { AiScript, parse } from '@syuilo/aiscript'; import { serialize } from '@syuilo/aiscript/built/serializer'; import { v4 as uuid } from 'uuid'; @@ -23,111 +23,101 @@ import FormInfo from '@/components/ui/info.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormTextarea, - FormButton, - FormInfo, - }, +const code = ref(null); - emits: ['info'], +function installPlugin({ id, meta, ast, token }) { + ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ + ...meta, + id, + active: true, + configData: {}, + token: token, + ast: ast + })); +} - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._plugin.install, - icon: 'fas fa-download', - bg: 'var(--bg)', - }, - code: null, - } - }, +async function install() { + let ast; + try { + ast = parse(code.value); + } catch (err) { + os.alert({ + type: 'error', + text: 'Syntax error :(' + }); + return; + } - methods: { - installPlugin({ id, meta, ast, token }) { - ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({ - ...meta, - id, - active: true, - configData: {}, - token: token, - ast: ast - })); - }, + const meta = AiScript.collectMetadata(ast); + if (meta == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } - async install() { - let ast; - try { - ast = parse(this.code); - } catch (e) { - os.alert({ - type: 'error', - text: 'Syntax error :(' - }); - return; - } - const meta = AiScript.collectMetadata(ast); - if (meta == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const data = meta.get(null); - if (data == null) { - os.alert({ - type: 'error', - text: 'No metadata found :(' - }); - return; - } - const { name, version, author, description, permissions, config } = data; - if (name == null || version == null || author == null) { - os.alert({ - type: 'error', - text: 'Required property not found :(' + const metadata = meta.get(null); + if (metadata == null) { + os.alert({ + type: 'error', + text: 'No metadata found :(' + }); + return; + } + + const { name, version, author, description, permissions, config } = metadata; + if (name == null || version == null || author == null) { + os.alert({ + type: 'error', + text: 'Required property not found :(' + }); + return; + } + + const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { + os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), { + title: i18n.ts.tokenRequested, + information: i18n.ts.pluginTokenRequestedDescription, + initialName: name, + initialPermissions: permissions + }, { + done: async result => { + const { name, permissions } = result; + const { token } = await os.api('miauth/gen-token', { + session: null, + name: name, + permission: permissions, }); - return; + res(token); } + }, 'closed'); + }); - const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => { - os.popup(import('@/components/token-generate-window.vue'), { - title: this.$ts.tokenRequested, - information: this.$ts.pluginTokenRequestedDescription, - initialName: name, - initialPermissions: permissions - }, { - done: async result => { - const { name, permissions } = result; - const { token } = await os.api('miauth/gen-token', { - session: null, - name: name, - permission: permissions, - }); - - res(token); - } - }, 'closed'); - }); + installPlugin({ + id: uuid(), + meta: { + name, version, author, description, permissions, config + }, + token, + ast: serialize(ast) + }); - this.installPlugin({ - id: uuid(), - meta: { - name, version, author, description, permissions, config - }, - token, - ast: serialize(ast) - }); + os.success(); - os.success(); + nextTick(() => { + unisonReload(); + }); +} - this.$nextTick(() => { - unisonReload(); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._plugin.install, + icon: 'fas fa-download', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue index 7a3ab9d152..873a022cbc 100644 --- a/packages/client/src/pages/settings/plugin.vue +++ b/packages/client/src/pages/settings/plugin.vue @@ -1,38 +1,38 @@ <template> <div class="_formRoot"> - <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink> + <FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink> <FormSection> - <template #label>{{ $ts.manage }}</template> + <template #label>{{ i18n.ts.manage }}</template> <div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;"> <span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span> - <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch> + <FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</FormSwitch> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.author }}</template> + <template #key>{{ i18n.ts.author }}</template> <template #value>{{ plugin.author }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.description }}</template> + <template #key>{{ i18n.ts.description }}</template> <template #value>{{ plugin.description }}</template> </MkKeyValue> <MkKeyValue class="_formBlock"> - <template #key>{{ $ts.permission }}</template> + <template #key>{{ i18n.ts.permission }}</template> <template #value>{{ plugin.permission }}</template> </MkKeyValue> <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> - <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton> - <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton> + <MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ i18n.ts.settings }}</MkButton> + <MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</MkButton> </div> </div> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, nextTick, ref } from 'vue'; import FormLink from '@/components/form/link.vue'; import FormSwitch from '@/components/form/switch.vue'; import FormSection from '@/components/form/section.vue'; @@ -41,67 +41,54 @@ import MkKeyValue from '@/components/key-value.vue'; import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import * as symbols from '@/symbols'; +import { unisonReload } from '@/scripts/unison-reload'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormLink, - FormSwitch, - FormSection, - MkButton, - MkKeyValue, - }, +const plugins = ref(ColdDeviceStorage.get('plugins')); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.plugins, - icon: 'fas fa-plug', - bg: 'var(--bg)', - }, - plugins: ColdDeviceStorage.get('plugins'), - } - }, +function uninstall(plugin) { + ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id)); + os.success(); + nextTick(() => { + unisonReload(); + }); +} - methods: { - uninstall(plugin) { - ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id)); - os.success(); - this.$nextTick(() => { - unisonReload(); - }); - }, +// TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする +async function config(plugin) { + const config = plugin.config; + for (const key in plugin.configData) { + config[key].default = plugin.configData[key]; + } - // TODO: この処理をstore側にactionとして移動し、設定画面を開くAiScriptAPIを実装できるようにする - async config(plugin) { - const config = plugin.config; - for (const key in plugin.configData) { - config[key].default = plugin.configData[key]; - } + const { canceled, result } = await os.form(plugin.name, config); + if (canceled) return; - const { canceled, result } = await os.form(plugin.name, config); - if (canceled) return; + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.configData = result; + ColdDeviceStorage.set('plugins', coldPlugins); - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).configData = result; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - }, +function changeActive(plugin, active) { + const coldPlugins = ColdDeviceStorage.get('plugins'); + coldPlugins.find(p => p.id === plugin.id)!.active = active; + ColdDeviceStorage.set('plugins', coldPlugins); - changeActive(plugin, active) { - const plugins = ColdDeviceStorage.get('plugins'); - plugins.find(p => p.id === plugin.id).active = active; - ColdDeviceStorage.set('plugins', plugins); + nextTick(() => { + location.reload(); + }); +} - this.$nextTick(() => { - location.reload(); - }); - } - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.plugins, + icon: 'fas fa-plug', + bg: 'var(--bg)', + } }); </script> diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue index e991d725b6..b64dc93cc7 100644 --- a/packages/client/src/pages/settings/profile.vue +++ b/packages/client/src/pages/settings/profile.vue @@ -62,7 +62,7 @@ </template> <script lang="ts" setup> -import { defineComponent, reactive, watch } from 'vue'; +import { reactive, watch } from 'vue'; import MkButton from '@/components/ui/button.vue'; import FormInput from '@/components/form/input.vue'; import FormTextarea from '@/components/form/textarea.vue'; @@ -132,8 +132,21 @@ function save() { function changeAvatar(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 1, + }); + } + const i = await os.apiWithDialog('i/update', { - avatarId: file.id, + avatarId: originalOrCropped.id, }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; @@ -142,8 +155,21 @@ function changeAvatar(ev) { function changeBanner(ev) { selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => { + let originalOrCropped = file; + + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.t('cropImageAsk'), + }); + + if (!canceled) { + originalOrCropped = await os.cropImage(file, { + aspectRatio: 2, + }); + } + const i = await os.apiWithDialog('i/update', { - bannerId: file.id, + bannerId: originalOrCropped.id, }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index a188ba353d..963ac81dfa 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -54,7 +54,7 @@ </template> <script lang="ts" setup> -import { watch } from 'vue'; +import { defineAsyncComponent, watch } from 'vue'; import XDraggable from 'vuedraggable'; import FormInput from '@/components/form/input.vue'; import FormRadios from '@/components/form/radios.vue'; @@ -88,7 +88,7 @@ function remove(reaction, ev: MouseEvent) { } function preview(ev: MouseEvent) { - os.popup(import('@/components/emoji-picker-dialog.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { asReactionPicker: true, src: ev.currentTarget ?? ev.target, }, {}, 'closed'); diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue index 6fb3f1c413..401648790a 100644 --- a/packages/client/src/pages/settings/security.vue +++ b/packages/client/src/pages/settings/security.vue @@ -1,17 +1,17 @@ <template> <div class="_formRoot"> <FormSection> - <template #label>{{ $ts.password }}</template> - <FormButton primary @click="change()">{{ $ts.changePassword }}</FormButton> + <template #label>{{ i18n.ts.password }}</template> + <FormButton primary @click="change()">{{ i18n.ts.changePassword }}</FormButton> </FormSection> <FormSection> - <template #label>{{ $ts.twoStepAuthentication }}</template> + <template #label>{{ i18n.ts.twoStepAuthentication }}</template> <X2fa/> </FormSection> <FormSection> - <template #label>{{ $ts.signinHistory }}</template> + <template #label>{{ i18n.ts.signinHistory }}</template> <MkPagination :pagination="pagination"> <template v-slot="{items}"> <div> @@ -30,15 +30,15 @@ <FormSection> <FormSlot> - <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ $ts.regenerateLoginToken }}</FormButton> - <template #caption>{{ $ts.regenerateLoginTokenDescription }}</template> + <FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ i18n.ts.regenerateLoginToken }}</FormButton> + <template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template> </FormSlot> </FormSection> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose } from 'vue'; import FormSection from '@/components/form/section.vue'; import FormSlot from '@/components/form/slot.vue'; import FormButton from '@/components/ui/button.vue'; @@ -46,77 +46,63 @@ import MkPagination from '@/components/ui/pagination.vue'; import X2fa from './2fa.vue'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormSection, - FormButton, - MkPagination, - FormSlot, - X2fa, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.security, - icon: 'fas fa-lock', - bg: 'var(--bg)', - }, - pagination: { - endpoint: 'i/signin-history' as const, - limit: 5, - }, - } - }, +const pagination = { + endpoint: 'i/signin-history' as const, + limit: 5, +}; - methods: { - async change() { - const { canceled: canceled1, result: currentPassword } = await os.inputText({ - title: this.$ts.currentPassword, - type: 'password' - }); - if (canceled1) return; +async function change() { + const { canceled: canceled1, result: currentPassword } = await os.inputText({ + title: i18n.ts.currentPassword, + type: 'password' + }); + if (canceled1) return; - const { canceled: canceled2, result: newPassword } = await os.inputText({ - title: this.$ts.newPassword, - type: 'password' - }); - if (canceled2) return; + const { canceled: canceled2, result: newPassword } = await os.inputText({ + title: i18n.ts.newPassword, + type: 'password' + }); + if (canceled2) return; - const { canceled: canceled3, result: newPassword2 } = await os.inputText({ - title: this.$ts.newPasswordRetype, - type: 'password' - }); - if (canceled3) return; + const { canceled: canceled3, result: newPassword2 } = await os.inputText({ + title: i18n.ts.newPasswordRetype, + type: 'password' + }); + if (canceled3) return; - if (newPassword !== newPassword2) { - os.alert({ - type: 'error', - text: this.$ts.retypedNotMatch - }); - return; - } - - os.apiWithDialog('i/change-password', { - currentPassword, - newPassword - }); - }, + if (newPassword !== newPassword2) { + os.alert({ + type: 'error', + text: i18n.ts.retypedNotMatch + }); + return; + } + + os.apiWithDialog('i/change-password', { + currentPassword, + newPassword + }); +} + +function regenerateToken() { + os.inputText({ + title: i18n.ts.password, + type: 'password' + }).then(({ canceled, result: password }) => { + if (canceled) return; + os.api('i/regenerate_token', { + password: password + }); + }); +} - regenerateToken() { - os.inputText({ - title: this.$ts.password, - type: 'password' - }).then(({ canceled, result: password }) => { - if (canceled) return; - os.api('i/regenerate_token', { - password: password - }); - }); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.security, + icon: 'fas fa-lock', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue index 490a1b5514..d01e87c1f8 100644 --- a/packages/client/src/pages/settings/sounds.vue +++ b/packages/client/src/pages/settings/sounds.vue @@ -1,24 +1,24 @@ <template> <div class="_formRoot"> <FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`" class="_formBlock"> - <template #label>{{ $ts.masterVolume }}</template> + <template #label>{{ i18n.ts.masterVolume }}</template> </FormRange> <FormSection> - <template #label>{{ $ts.sounds }}</template> + <template #label>{{ i18n.ts.sounds }}</template> <FormLink v-for="type in Object.keys(sounds)" :key="type" style="margin-bottom: 8px;" @click="edit(type)"> {{ $t('_sfx.' + type) }} - <template #suffix>{{ sounds[type].type || $ts.none }}</template> + <template #suffix>{{ sounds[type].type || i18n.ts.none }}</template> <template #suffixIcon><i class="fas fa-chevron-down"></i></template> </FormLink> </FormSection> - <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton> + <FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; import FormRange from '@/components/form/range.vue'; import FormButton from '@/components/ui/button.vue'; import FormLink from '@/components/form/link.vue'; @@ -27,6 +27,28 @@ import * as os from '@/os'; import { ColdDeviceStorage } from '@/store'; import { playFile } from '@/scripts/sound'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; + +const masterVolume = computed({ + get: () => { + return ColdDeviceStorage.get('sound_masterVolume'); + }, + set: (value) => { + ColdDeviceStorage.set('sound_masterVolume', value); + } +}); + +const volumeIcon = computed(() => masterVolume.value === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'); + +const sounds = ref({ + note: ColdDeviceStorage.get('sound_note'), + noteMy: ColdDeviceStorage.get('sound_noteMy'), + notification: ColdDeviceStorage.get('sound_notification'), + chat: ColdDeviceStorage.get('sound_chat'), + chatBg: ColdDeviceStorage.get('sound_chatBg'), + antenna: ColdDeviceStorage.get('sound_antenna'), + channel: ColdDeviceStorage.get('sound_channel'), +}); const soundsTypes = [ null, @@ -55,94 +77,58 @@ const soundsTypes = [ 'noizenecio/kick_gaba2', ]; -export default defineComponent({ - components: { - FormLink, - FormButton, - FormRange, - FormSection, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.sounds, - icon: 'fas fa-music', - bg: 'var(--bg)', - }, - sounds: {}, - } - }, - - computed: { - masterVolume: { // TODO: (外部)関数にcomputedを使うのはアレなので直す - get() { return ColdDeviceStorage.get('sound_masterVolume'); }, - set(value) { ColdDeviceStorage.set('sound_masterVolume', value); } +async function edit(type) { + const { canceled, result } = await os.form(i18n.t('_sfx.' + type), { + type: { + type: 'enum', + enum: soundsTypes.map(x => ({ + value: x, + label: x == null ? i18n.ts.none : x, + })), + label: i18n.ts.sound, + default: sounds.value[type].type, + }, + volume: { + type: 'range', + mim: 0, + max: 1, + step: 0.05, + textConverter: (v) => `${Math.floor(v * 100)}%`, + label: i18n.ts.volume, + default: sounds.value[type].volume }, - volumeIcon() { - return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up'; + listen: { + type: 'button', + content: i18n.ts.listen, + action: (_, values) => { + playFile(values.type, values.volume); + } } - }, + }); + if (canceled) return; - created() { - this.sounds.note = ColdDeviceStorage.get('sound_note'); - this.sounds.noteMy = ColdDeviceStorage.get('sound_noteMy'); - this.sounds.notification = ColdDeviceStorage.get('sound_notification'); - this.sounds.chat = ColdDeviceStorage.get('sound_chat'); - this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg'); - this.sounds.antenna = ColdDeviceStorage.get('sound_antenna'); - this.sounds.channel = ColdDeviceStorage.get('sound_channel'); - }, + const v = { + type: result.type, + volume: result.volume, + }; - methods: { - async edit(type) { - const { canceled, result } = await os.form(this.$t('_sfx.' + type), { - type: { - type: 'enum', - enum: soundsTypes.map(x => ({ - value: x, - label: x == null ? this.$ts.none : x, - })), - label: this.$ts.sound, - default: this.sounds[type].type, - }, - volume: { - type: 'range', - mim: 0, - max: 1, - step: 0.05, - textConverter: (v) => `${Math.floor(v * 100)}%`, - label: this.$ts.volume, - default: this.sounds[type].volume - }, - listen: { - type: 'button', - content: this.$ts.listen, - action: (_, values) => { - playFile(values.type, values.volume); - } - } - }); - if (canceled) return; + ColdDeviceStorage.set('sound_' + type, v); + sounds.value[type] = v; +} - const v = { - type: result.type, - volume: result.volume, - }; - - ColdDeviceStorage.set('sound_' + type, v); - this.sounds[type] = v; - }, +function reset() { + for (const sound of Object.keys(sounds.value)) { + const v = ColdDeviceStorage.default['sound_' + sound]; + ColdDeviceStorage.set('sound_' + sound, v); + sounds.value[sound] = v; + } +} - reset() { - for (const sound of Object.keys(this.sounds)) { - const v = ColdDeviceStorage.default['sound_' + sound]; - ColdDeviceStorage.set('sound_' + sound, v); - this.sounds[sound] = v; - } - } +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.sounds, + icon: 'fas fa-music', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue index 2d3514342e..25fa6c012b 100644 --- a/packages/client/src/pages/settings/theme.install.vue +++ b/packages/client/src/pages/settings/theme.install.vue @@ -13,7 +13,7 @@ <script lang="ts" setup> import { } from 'vue'; -import * as JSON5 from 'json5'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormButton from '@/components/ui/button.vue'; import { applyTheme, validateTheme } from '@/scripts/theme'; @@ -29,7 +29,7 @@ function parseThemeCode(code: string) { try { theme = JSON5.parse(code); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: i18n.ts._theme.invalid diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue index a1e849b540..94b2d24455 100644 --- a/packages/client/src/pages/settings/theme.manage.vue +++ b/packages/client/src/pages/settings/theme.manage.vue @@ -1,95 +1,77 @@ <template> <div class="_formRoot"> <FormSelect v-model="selectedThemeId" class="_formBlock"> - <template #label>{{ $ts.theme }}</template> - <optgroup :label="$ts._theme.installedThemes"> + <template #label>{{ i18n.ts.theme }}</template> + <optgroup :label="i18n.ts._theme.installedThemes"> <option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> - <optgroup :label="$ts._theme.builtinThemes"> + <optgroup :label="i18n.ts._theme.builtinThemes"> <option v-for="x in builtinThemes" :key="x.id" :value="x.id">{{ x.name }}</option> </optgroup> </FormSelect> <template v-if="selectedTheme"> - <FormInput readonly :modelValue="selectedTheme.author" class="_formBlock"> - <template #label>{{ $ts.author }}</template> + <FormInput readonly :model-value="selectedTheme.author" class="_formBlock"> + <template #label>{{ i18n.ts.author }}</template> </FormInput> - <FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock"> - <template #label>{{ $ts._theme.description }}</template> + <FormTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc" class="_formBlock"> + <template #label>{{ i18n.ts._theme.description }}</template> </FormTextarea> - <FormTextarea readonly tall :modelValue="selectedThemeCode" class="_formBlock"> - <template #label>{{ $ts._theme.code }}</template> - <template #caption><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template> + <FormTextarea readonly tall :model-value="selectedThemeCode" class="_formBlock"> + <template #label>{{ i18n.ts._theme.code }}</template> + <template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template> </FormTextarea> - <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton> + <FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</FormButton> </template> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, defineExpose, ref } from 'vue'; +import JSON5 from 'json5'; import FormTextarea from '@/components/form/textarea.vue'; import FormSelect from '@/components/form/select.vue'; import FormInput from '@/components/form/input.vue'; import FormButton from '@/components/ui/button.vue'; -import { Theme, builtinThemes } from '@/scripts/theme'; +import { Theme, getBuiltinThemesRef } from '@/scripts/theme'; import copyToClipboard from '@/scripts/copy-to-clipboard'; import * as os from '@/os'; -import { ColdDeviceStorage } from '@/store'; import { getThemes, removeTheme } from '@/theme-store'; import * as symbols from '@/symbols'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - FormTextarea, - FormSelect, - FormInput, - FormButton, - }, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const selectedThemeId = ref(null); - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts._theme.manage, - icon: 'fas fa-folder-open', - bg: 'var(--bg)', - }, - installedThemes: getThemes(), - builtinThemes, - selectedThemeId: null, - } - }, +const themes = computed(() => [ ...installedThemes.value, ...builtinThemes.value ]); - computed: { - themes(): Theme[] { - return this.builtinThemes.concat(this.installedThemes); - }, - - selectedTheme() { - if (this.selectedThemeId == null) return null; - return this.themes.find(x => x.id === this.selectedThemeId); - }, +const selectedTheme = computed(() => { + if (selectedThemeId.value == null) return null; + return themes.value.find(x => x.id === selectedThemeId.value); +}); + +const selectedThemeCode = computed(() => { + if (selectedTheme.value == null) return null; + return JSON5.stringify(selectedTheme.value, null, '\t'); +}); - selectedThemeCode() { - if (this.selectedTheme == null) return null; - return JSON5.stringify(this.selectedTheme, null, '\t'); - }, - }, +function copyThemeCode() { + copyToClipboard(selectedThemeCode.value); + os.success(); +} - methods: { - copyThemeCode() { - copyToClipboard(this.selectedThemeCode); - os.success(); - }, +function uninstall() { + removeTheme(selectedTheme.value as Theme); + installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value); + selectedThemeId.value = null; + os.success(); +} - uninstall() { - removeTheme(this.selectedTheme); - this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId); - this.selectedThemeId = null; - os.success(); - }, +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts._theme.manage, + icon: 'fas fa-folder-open', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue index d134a092b6..5e7ffcff4b 100644 --- a/packages/client/src/pages/settings/theme.vue +++ b/packages/client/src/pages/settings/theme.vue @@ -85,116 +85,94 @@ </div> </template> -<script lang="ts"> -import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue'; -import * as JSON5 from 'json5'; +<script lang="ts" setup> +import { computed, onActivated, ref, watch } from 'vue'; +import JSON5 from 'json5'; import FormSwitch from '@/components/form/switch.vue'; import FormSelect from '@/components/form/select.vue'; -import FormGroup from '@/components/form/group.vue'; import FormSection from '@/components/form/section.vue'; import FormLink from '@/components/form/link.vue'; import FormButton from '@/components/ui/button.vue'; -import { builtinThemes } from '@/scripts/theme'; +import { getBuiltinThemesRef } from '@/scripts/theme'; import { selectFile } from '@/scripts/select-file'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode'; import { ColdDeviceStorage } from '@/store'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { instance } from '@/instance'; -import { concat, uniqueBy } from '@/scripts/array'; +import { uniqueBy } from '@/scripts/array'; import { fetchThemes, getThemes } from '@/theme-store'; import * as symbols from '@/symbols'; -export default defineComponent({ - components: { - FormSwitch, - FormSelect, - FormGroup, - FormSection, - FormLink, - FormButton, - }, +const installedThemes = ref(getThemes()); +const builtinThemes = getBuiltinThemesRef(); +const instanceThemes = []; - emits: ['info'], +if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); +if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); - setup(props, { emit }) { - const INFO = { - title: i18n.ts.theme, - icon: 'fas fa-palette', - bg: 'var(--bg)', - }; +const themes = computed(() => uniqueBy([ ...instanceThemes, ...builtinThemes.value, ...installedThemes.value ], theme => theme.id)); +const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); +const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); +const darkTheme = ColdDeviceStorage.ref('darkTheme'); +const darkThemeId = computed({ + get() { + return darkTheme.value.id; + }, + set(id) { + ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)); + } +}); +const lightTheme = ColdDeviceStorage.ref('lightTheme'); +const lightThemeId = computed({ + get() { + return lightTheme.value.id; + }, + set(id) { + ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)); + } +}); +const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); +const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); +const wallpaper = ref(localStorage.getItem('wallpaper')); +const themesCount = installedThemes.value.length; - const installedThemes = ref(getThemes()); - const instanceThemes = []; - if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme)); - if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme)); - const themes = computed(() => uniqueBy(instanceThemes.concat(builtinThemes.concat(installedThemes.value)), theme => theme.id)); - const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark')); - const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light')); - const darkTheme = ColdDeviceStorage.ref('darkTheme'); - const darkThemeId = computed({ - get() { - return darkTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id)) - } - }); - const lightTheme = ColdDeviceStorage.ref('lightTheme'); - const lightThemeId = computed({ - get() { - return lightTheme.value.id; - }, - set(id) { - ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id)) - } - }); - const darkMode = computed(defaultStore.makeGetterSetter('darkMode')); - const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode')); - const wallpaper = ref(localStorage.getItem('wallpaper')); - const themesCount = installedThemes.value.length; +watch(syncDeviceDarkMode, () => { + if (syncDeviceDarkMode.value) { + defaultStore.set('darkMode', isDeviceDarkmode()); + } +}); - watch(syncDeviceDarkMode, () => { - if (syncDeviceDarkMode.value) { - defaultStore.set('darkMode', isDeviceDarkmode()); - } - }); +watch(wallpaper, () => { + if (wallpaper.value == null) { + localStorage.removeItem('wallpaper'); + } else { + localStorage.setItem('wallpaper', wallpaper.value); + } + location.reload(); +}); - watch(wallpaper, () => { - if (wallpaper.value == null) { - localStorage.removeItem('wallpaper'); - } else { - localStorage.setItem('wallpaper', wallpaper.value); - } - location.reload(); - }); +onActivated(() => { + fetchThemes().then(() => { + installedThemes.value = getThemes(); + }); +}); - onActivated(() => { - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); - }); +fetchThemes().then(() => { + installedThemes.value = getThemes(); +}); - fetchThemes().then(() => { - installedThemes.value = getThemes(); - }); +function setWallpaper(event) { + selectFile(event.currentTarget ?? event.target, null).then(file => { + wallpaper.value = file.url; + }); +} - return { - [symbols.PAGE_INFO]: INFO, - darkThemes, - lightThemes, - darkThemeId, - lightThemeId, - darkMode, - syncDeviceDarkMode, - themesCount, - wallpaper, - setWallpaper(e) { - selectFile(e.currentTarget ?? e.target, null).then(file => { - wallpaper.value = file.url; - }); - }, - }; +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.theme, + icon: 'fas fa-palette', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue index bb3a25407e..3690526b41 100644 --- a/packages/client/src/pages/settings/webhook.edit.vue +++ b/packages/client/src/pages/settings/webhook.edit.vue @@ -43,6 +43,14 @@ import * as os from '@/os'; import * as symbols from '@/symbols'; import { i18n } from '@/i18n'; +defineExpose({ + [symbols.PAGE_INFO]: { + title: 'Edit webhook', + icon: 'fas fa-bolt', + bg: 'var(--bg)', + }, +}); + const webhook = await os.api('i/webhooks/show', { webhookId: new URLSearchParams(window.location.search).get('id') }); @@ -78,12 +86,4 @@ async function save(): Promise<void> { active, }); } - -defineExpose({ - [symbols.PAGE_INFO]: { - title: 'Edit webhook', - icon: 'fas fa-bolt', - bg: 'var(--bg)', - }, -}); </script> diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue index c11707b6cf..6e1a4b2ccb 100644 --- a/packages/client/src/pages/settings/word-mute.vue +++ b/packages/client/src/pages/settings/word-mute.vue @@ -1,35 +1,35 @@ <template> <div class="_formRoot"> <MkTab v-model="tab" class="_formBlock"> - <option value="soft">{{ $ts._wordMute.soft }}</option> - <option value="hard">{{ $ts._wordMute.hard }}</option> + <option value="soft">{{ i18n.ts._wordMute.soft }}</option> + <option value="hard">{{ i18n.ts._wordMute.hard }}</option> </MkTab> <div class="_formBlock"> <div v-show="tab === 'soft'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.softDescription }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.softDescription }}</MkInfo> <FormTextarea v-model="softMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> </div> <div v-show="tab === 'hard'"> - <MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo> + <MkInfo class="_formBlock">{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo> <FormTextarea v-model="hardMutedWords" class="_formBlock"> - <span>{{ $ts._wordMute.muteWords }}</span> - <template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template> + <span>{{ i18n.ts._wordMute.muteWords }}</span> + <template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template> </FormTextarea> <MkKeyValue v-if="hardWordMutedNotesCount != null" class="_formBlock"> - <template #key>{{ $ts._wordMute.mutedNotes }}</template> + <template #key>{{ i18n.ts._wordMute.mutedNotes }}</template> <template #value>{{ number(hardWordMutedNotesCount) }}</template> </MkKeyValue> </div> </div> - <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton> + <MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton> </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { defineExpose, ref, watch } from 'vue'; import FormTextarea from '@/components/form/textarea.vue'; import MkKeyValue from '@/components/key-value.vue'; import MkButton from '@/components/ui/button.vue'; @@ -38,114 +38,90 @@ import MkTab from '@/components/tab.vue'; import * as os from '@/os'; import number from '@/filters/number'; import * as symbols from '@/symbols'; +import { defaultStore } from '@/store'; +import { $i } from '@/account'; +import { i18n } from '@/i18n'; -export default defineComponent({ - components: { - MkButton, - FormTextarea, - MkKeyValue, - MkTab, - MkInfo, - }, - - emits: ['info'], - - data() { - return { - [symbols.PAGE_INFO]: { - title: this.$ts.wordMute, - icon: 'fas fa-comment-slash', - bg: 'var(--bg)', - }, - tab: 'soft', - softMutedWords: '', - hardMutedWords: '', - hardWordMutedNotesCount: null, - changed: false, - } - }, +const render = (mutedWords) => mutedWords.map(x => { + if (Array.isArray(x)) { + return x.join(' '); + } else { + return x; + } +}).join('\n'); - watch: { - softMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - hardMutedWords: { - handler() { - this.changed = true; - }, - deep: true - }, - }, +const tab = ref('soft'); +const softMutedWords = ref(render(defaultStore.state.mutedWords)); +const hardMutedWords = ref(render($i!.mutedWords)); +const hardWordMutedNotesCount = ref(null); +const changed = ref(false); - async created() { - const render = (mutedWords) => mutedWords.map(x => { - if (Array.isArray(x)) { - return x.join(' '); - } else { - return x; - } - }).join('\n'); +os.api('i/get-word-muted-notes-count', {}).then(response => { + hardWordMutedNotesCount.value = response?.count; +}); - this.softMutedWords = render(this.$store.state.mutedWords); - this.hardMutedWords = render(this.$i.mutedWords); +watch(softMutedWords, () => { + changed.value = true; +}); - this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count; - }, +watch(hardMutedWords, () => { + changed.value = true; +}); - methods: { - async save() { - const parseMutes = (mutes, tab) => { - // split into lines, remove empty lines and unnecessary whitespace - let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line != ''); +async function save() { + const parseMutes = (mutes, tab) => { + // split into lines, remove empty lines and unnecessary whitespace + let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== ''); - // check each line if it is a RegExp or not - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - const regexp = line.match(/^\/(.+)\/(.*)$/); - if (regexp) { - // check that the RegExp is valid - try { - new RegExp(regexp[1], regexp[2]); - // note that regex lines will not be split by spaces! - } catch (err) { - // invalid syntax: do not save, do not reset changed flag - os.alert({ - type: 'error', - title: this.$ts.regexpError, - text: this.$t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() - }); - // re-throw error so these invalid settings are not saved - throw err; - } - } else { - lines[i] = line.split(' '); - } + // check each line if it is a RegExp or not + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const regexp = line.match(/^\/(.+)\/(.*)$/); + if (regexp) { + // check that the RegExp is valid + try { + new RegExp(regexp[1], regexp[2]); + // note that regex lines will not be split by spaces! + } catch (err: any) { + // invalid syntax: do not save, do not reset changed flag + os.alert({ + type: 'error', + title: i18n.ts.regexpError, + text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString() + }); + // re-throw error so these invalid settings are not saved + throw err; } + } else { + lines[i] = line.split(' '); + } + } - return lines; - }; + return lines; + }; - let softMutes, hardMutes; - try { - softMutes = parseMutes(this.softMutedWords, this.$ts._wordMute.soft); - hardMutes = parseMutes(this.hardMutedWords, this.$ts._wordMute.hard); - } catch (err) { - // already displayed error message in parseMutes - return; - } + let softMutes, hardMutes; + try { + softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft); + hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard); + } catch (err) { + // already displayed error message in parseMutes + return; + } - this.$store.set('mutedWords', softMutes); - await os.api('i/update', { - mutedWords: hardMutes, - }); + defaultStore.set('mutedWords', softMutes); + await os.api('i/update', { + mutedWords: hardMutes, + }); - this.changed = false; - }, + changed.value = false; +} - number +defineExpose({ + [symbols.PAGE_INFO]: { + title: i18n.ts.wordMute, + icon: 'fas fa-comment-slash', + bg: 'var(--bg)', } }); </script> diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue index 4d77de5819..1700944f82 100644 --- a/packages/client/src/pages/share.vue +++ b/packages/client/src/pages/share.vue @@ -56,7 +56,7 @@ export default defineComponent({ localOnly: null as boolean | null, files: [] as Misskey.entities.DriveFile[], visibleUsers: [] as Misskey.entities.User[], - } + }; }, async created() { @@ -153,11 +153,11 @@ export default defineComponent({ ); } //#endregion - } catch (e) { + } catch (err) { os.alert({ type: 'error', - title: e.message, - text: e.name + title: err.message, + text: err.name }); } diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue index a53e23c1c5..2a11c07fd2 100644 --- a/packages/client/src/pages/theme-editor.vue +++ b/packages/client/src/pages/theme-editor.vue @@ -67,15 +67,17 @@ <script lang="ts" setup> import { watch } from 'vue'; import { toUnicode } from 'punycode/'; -import * as tinycolor from 'tinycolor2'; -import { v4 as uuid} from 'uuid'; -import * as JSON5 from 'json5'; +import tinycolor from 'tinycolor2'; +import { v4 as uuid } from 'uuid'; +import JSON5 from 'json5'; import FormButton from '@/components/ui/button.vue'; import FormTextarea from '@/components/form/textarea.vue'; import FormFolder from '@/components/form/folder.vue'; -import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme'; +import { Theme, applyTheme } from '@/scripts/theme'; +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; import { host } from '@/config'; import * as os from '@/os'; import { ColdDeviceStorage, defaultStore } from '@/store'; @@ -124,11 +126,11 @@ let changed = $ref(false); useLeaveGuard($$(changed)); function showPreview() { - os.pageWindow('preview'); + os.pageWindow('/preview'); } function setBgColor(color: typeof bgColors[number]) { - if (theme.base != color.kind) { + if (theme.base !== color.kind) { const base = color.kind === 'dark' ? darkTheme : lightTheme; for (const prop of Object.keys(base.props)) { if (prop === 'accent') continue; diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue index 79f00c4b44..fe3dbc3cff 100644 --- a/packages/client/src/pages/timeline.vue +++ b/packages/client/src/pages/timeline.vue @@ -20,7 +20,7 @@ <script lang="ts"> export default { name: 'MkTimelinePage', -} +}; </script> <script lang="ts" setup> diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue index 516ab4d440..54e1f13021 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -54,6 +54,9 @@ <FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton> </FormSection> + <MkObjectView v-if="info && $i.isAdmin" tall :value="info"> + </MkObjectView> + <MkObjectView tall :value="user"> </MkObjectView> </div> @@ -232,10 +235,10 @@ export default defineComponent({ await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id }); os.success(); }; - await process().catch(e => { + await process().catch(err => { os.alert({ type: 'error', - text: e.toString() + text: err.toString(), }); }); await this.refreshUser(); diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue index 10a86243f9..a024dd28bc 100644 --- a/packages/client/src/pages/user/index.vue +++ b/packages/client/src/pages/user/index.vue @@ -125,7 +125,7 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent, computed } from 'vue'; -import * as age from 's-age'; +import age from 's-age'; import XUserTimeline from './index.timeline.vue'; import XNote from '@/components/note.vue'; import MkFollowButton from '@/components/follow-button.vue'; @@ -141,6 +141,7 @@ import number from '@/filters/number'; import { userPage, acct as getAcct } from '@/filters/user'; import * as os from '@/os'; import * as symbols from '@/symbols'; +import { MisskeyNavigator } from '@/scripts/navigate'; export default defineComponent({ components: { @@ -190,33 +191,34 @@ export default defineComponent({ active: this.page === 'index', title: this.$ts.overview, icon: 'fas fa-home', - onClick: () => { this.$router.push('/@' + getAcct(this.user)); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user)); }, }, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{ active: this.page === 'reactions', title: this.$ts.reaction, icon: 'fas fa-laugh', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/reactions'); }, }] : [], { active: this.page === 'clips', title: this.$ts.clips, icon: 'fas fa-paperclip', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/clips'); }, }, { active: this.page === 'pages', title: this.$ts.pages, icon: 'fas fa-file-alt', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/pages'); }, }, { active: this.page === 'gallery', title: this.$ts.gallery, icon: 'fas fa-icons', - onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); }, + onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/gallery'); }, }], } : null), user: null, error: null, parallaxAnimationId: null, narrow: null, + mkNav: new MisskeyNavigator(), }; }, @@ -258,8 +260,8 @@ export default defineComponent({ this.user = null; os.api('users/show', Acct.parse(this.acct)).then(user => { this.user = user; - }).catch(e => { - this.error = e; + }).catch(err => { + this.error = err; }); }, diff --git a/packages/client/src/pages/welcome.setup.vue b/packages/client/src/pages/welcome.setup.vue index ec23b76e29..1a2f460283 100644 --- a/packages/client/src/pages/welcome.setup.vue +++ b/packages/client/src/pages/welcome.setup.vue @@ -41,7 +41,7 @@ export default defineComponent({ password: '', submitting: false, host, - } + }; }, methods: { diff --git a/packages/client/src/pages/welcome.timeline.vue b/packages/client/src/pages/welcome.timeline.vue index 38a85f67b1..bec9481ffd 100644 --- a/packages/client/src/pages/welcome.timeline.vue +++ b/packages/client/src/pages/welcome.timeline.vue @@ -39,7 +39,7 @@ export default defineComponent({ return { notes: [], isScrolling: false, - } + }; }, created() { diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 839841f0fe..db39dd741c 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -1,4 +1,4 @@ -import { defineAsyncComponent, markRaw } from 'vue'; +import { AsyncComponentLoader, defineAsyncComponent, markRaw } from 'vue'; import { createRouter, createWebHistory } from 'vue-router'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; @@ -6,8 +6,9 @@ import MkTimeline from '@/pages/timeline.vue'; import { $i, iAmModerator } from './account'; import { ui } from '@/config'; -const page = (path: string, ui?: string) => defineAsyncComponent({ - loader: ui ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`), +// pathに/が入るとrollupが解決してくれないので、() => import('*.vue')を指定すること +const page = (path: string | AsyncComponentLoader<any>, uiName?: string) => defineAsyncComponent({ + loader: typeof path === 'string' ? uiName ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`) : path, loadingComponent: MkLoading, errorComponent: MkError, }); @@ -17,10 +18,10 @@ let indexScrollPos = 0; const defaultRoutes = [ // NOTE: MkTimelineをdynamic importするとAsyncComponentWrapperが間に入るせいでkeep-aliveのコンポーネント指定が効かなくなる { path: '/', name: 'index', component: $i ? MkTimeline : page('welcome') }, - { path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, + { path: '/@:acct/:page?', name: 'user', component: page(() => import('./pages/user/index.vue')), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) }, { path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, - { path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, - { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, + { path: '/@:user/pages/:pageName/view-source', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, + { path: '/settings/:page(.*)?', name: 'settings', component: page(() => import('./pages/settings/index.vue')), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, { path: '/announcements', component: page('announcements') }, @@ -35,12 +36,12 @@ const defaultRoutes = [ { path: '/emojis', component: page('emojis') }, { path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) }, { path: '/pages', name: 'pages', component: page('pages') }, - { path: '/pages/new', component: page('page-editor/page-editor') }, - { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) }, - { path: '/gallery', component: page('gallery/index') }, - { path: '/gallery/new', component: page('gallery/edit') }, - { path: '/gallery/:postId/edit', component: page('gallery/edit'), props: route => ({ postId: route.params.postId }) }, - { path: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) }, + { path: '/pages/new', component: page(() => import('./pages/page-editor/page-editor.vue')) }, + { path: '/pages/edit/:pageId', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initPageId: route.params.pageId }) }, + { path: '/gallery', component: page(() => import('./pages/gallery/index.vue')) }, + { path: '/gallery/new', component: page(() => import('./pages/gallery/edit.vue')) }, + { path: '/gallery/:postId/edit', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) }, + { path: '/gallery/:postId', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) }, { path: '/channels', component: page('channels') }, { path: '/channels/new', component: page('channel-editor') }, { path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, @@ -52,23 +53,23 @@ const defaultRoutes = [ { path: '/my/favorites', component: page('favorites') }, { path: '/my/messages', component: page('messages') }, { path: '/my/mentions', component: page('mentions') }, - { path: '/my/messaging', name: 'messaging', component: page('messaging/index') }, - { path: '/my/messaging/:user', component: page('messaging/messaging-room'), props: route => ({ userAcct: route.params.user }) }, - { path: '/my/messaging/group/:group', component: page('messaging/messaging-room'), props: route => ({ groupId: route.params.group }) }, + { path: '/my/messaging', name: 'messaging', component: page(() => import('./pages/messaging/index.vue')) }, + { path: '/my/messaging/:user', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ userAcct: route.params.user }) }, + { path: '/my/messaging/group/:group', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ groupId: route.params.group }) }, { path: '/my/drive', name: 'drive', component: page('drive') }, { path: '/my/drive/folder/:folder', component: page('drive') }, { path: '/my/follow-requests', component: page('follow-requests') }, - { path: '/my/lists', component: page('my-lists/index') }, - { path: '/my/lists/:list', component: page('my-lists/list') }, - { path: '/my/groups', component: page('my-groups/index') }, - { path: '/my/groups/:group', component: page('my-groups/group'), props: route => ({ groupId: route.params.group }) }, - { path: '/my/antennas', component: page('my-antennas/index') }, - { path: '/my/antennas/create', component: page('my-antennas/create') }, - { path: '/my/antennas/:antennaId', component: page('my-antennas/edit'), props: true }, - { path: '/my/clips', component: page('my-clips/index') }, + { path: '/my/lists', component: page(() => import('./pages/my-lists/index.vue')) }, + { path: '/my/lists/:list', component: page(() => import('./pages/my-lists/list.vue')) }, + { path: '/my/groups', component: page(() => import('./pages/my-groups/index.vue')) }, + { path: '/my/groups/:group', component: page(() => import('./pages/my-groups/group.vue')), props: route => ({ groupId: route.params.group }) }, + { path: '/my/antennas', component: page(() => import('./pages/my-antennas/index.vue')) }, + { path: '/my/antennas/create', component: page(() => import('./pages/my-antennas/create.vue')) }, + { path: '/my/antennas/:antennaId', component: page(() => import('./pages/my-antennas/edit.vue')), props: true }, + { path: '/my/clips', component: page(() => import('./pages/my-clips/index.vue')) }, { path: '/scratchpad', component: page('scratchpad') }, - { path: '/admin/:page(.*)?', component: iAmModerator ? page('admin/index') : page('not-found'), props: route => ({ initialPage: route.params.page || null }) }, - { path: '/admin', component: iAmModerator ? page('admin/index') : page('not-found') }, + { path: '/admin/:page(.*)?', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found'), props: route => ({ initialPage: route.params.page || null }) }, + { path: '/admin', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found') }, { path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) }, { path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) }, { path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) }, diff --git a/packages/client/src/scripts/2fa.ts b/packages/client/src/scripts/2fa.ts index 00363cffa6..d1b9581e72 100644 --- a/packages/client/src/scripts/2fa.ts +++ b/packages/client/src/scripts/2fa.ts @@ -1,11 +1,11 @@ -export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { +export function byteify(string: string, encoding: 'ascii' | 'base64' | 'hex') { switch (encoding) { case 'ascii': - return Uint8Array.from(data, c => c.charCodeAt(0)); + return Uint8Array.from(string, c => c.charCodeAt(0)); case 'base64': return Uint8Array.from( atob( - data + string .replace(/-/g, '+') .replace(/_/g, '/') ), @@ -13,7 +13,7 @@ export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') { ); case 'hex': return new Uint8Array( - data + string .match(/.{1,2}/g) .map(byte => parseInt(byte, 16)) ); diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts index f4a3a4c0fc..8d9bdee8f5 100644 --- a/packages/client/src/scripts/autocomplete.ts +++ b/packages/client/src/scripts/autocomplete.ts @@ -1,5 +1,5 @@ -import { nextTick, Ref, ref } from 'vue'; -import * as getCaretCoordinates from 'textarea-caret'; +import { nextTick, Ref, ref, defineAsyncComponent } from 'vue'; +import getCaretCoordinates from 'textarea-caret'; import { toASCII } from 'punycode/'; import { popup } from '@/os'; @@ -74,21 +74,21 @@ export class Autocomplete { emojiIndex, mfmTagIndex); - if (max == -1) { + if (max === -1) { this.close(); return; } - const isMention = mentionIndex != -1; - const isHashtag = hashtagIndex != -1; - const isMfmTag = mfmTagIndex != -1; - const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); + const isMention = mentionIndex !== -1; + const isHashtag = hashtagIndex !== -1; + const isMfmTag = mfmTagIndex !== -1; + const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':'); let opened = false; if (isMention) { const username = text.substr(mentionIndex + 1); - if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) { + if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) { this.open('user', username); opened = true; } else if (username === '') { @@ -130,7 +130,7 @@ export class Autocomplete { * サジェストを提示します。 */ private async open(type: string, q: string | null) { - if (type != this.currentType) { + if (type !== this.currentType) { this.close(); } if (this.opening) return; @@ -157,7 +157,7 @@ export class Autocomplete { const _y = ref(y); const _q = ref(q); - const { dispose } = await popup(import('@/components/autocomplete.vue'), { + const { dispose } = await popup(defineAsyncComponent(() => import('@/components/autocomplete.vue')), { textarea: this.textarea, close: this.close, type: type, @@ -201,7 +201,7 @@ export class Autocomplete { const caret = this.textarea.selectionStart; - if (type == 'user') { + if (type === 'user') { const source = this.text; const before = source.substr(0, caret); @@ -219,7 +219,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (acct.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'hashtag') { + } else if (type === 'hashtag') { const source = this.text; const before = source.substr(0, caret); @@ -235,7 +235,7 @@ export class Autocomplete { const pos = trimmedBefore.length + (value.length + 2); this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'emoji') { + } else if (type === 'emoji') { const source = this.text; const before = source.substr(0, caret); @@ -251,7 +251,7 @@ export class Autocomplete { const pos = trimmedBefore.length + value.length; this.textarea.setSelectionRange(pos, pos); }); - } else if (type == 'mfmTag') { + } else if (type === 'mfmTag') { const source = this.text; const before = source.substr(0, caret); diff --git a/packages/client/src/scripts/contains.ts b/packages/client/src/scripts/contains.ts index 770bda63bb..256e09d293 100644 --- a/packages/client/src/scripts/contains.ts +++ b/packages/client/src/scripts/contains.ts @@ -2,7 +2,7 @@ export default (parent, child, checkSame = true) => { if (checkSame && parent === child) return true; let node = child.parentNode; while (node) { - if (node == parent) return true; + if (node === parent) return true; node = node.parentNode; } return false; diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index bd8689e4f8..4196170d24 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -8,4 +8,4 @@ export type UnicodeEmojiDef = { } // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb -export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[]; +export const emojilist = (await import('../emojilist.json')).default as UnicodeEmojiDef[]; diff --git a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts index 123ab7a06d..af517f2672 100644 --- a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts +++ b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts @@ -1,5 +1,5 @@ export function extractAvgColorFromBlurhash(hash: string) { - return typeof hash == 'string' + return typeof hash === 'string' ? '#' + [...hash.slice(2, 6)] .map(x => '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'.indexOf(x)) .reduce((a, c) => a * 83 + c, 0) diff --git a/packages/client/src/scripts/format-time-string.ts b/packages/client/src/scripts/format-time-string.ts index bfb2c397ae..fb4718c007 100644 --- a/packages/client/src/scripts/format-time-string.ts +++ b/packages/client/src/scripts/format-time-string.ts @@ -13,7 +13,7 @@ const defaultLocaleStringFormats: {[index: string]: string} = { function formatLocaleString(date: Date, format: string): string { return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => { if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) { - return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]}); + return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] }); } else { return match; } @@ -24,8 +24,8 @@ export function formatDateTimeString(date: Date, format: string): string { return format .replace(/yyyy/g, date.getFullYear().toString()) .replace(/yy/g, date.getFullYear().toString().slice(-2)) - .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'})) - .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'})) + .replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long' })) + .replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short' })) .replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2)) .replace(/M/g, (date.getMonth() + 1).toString()) .replace(/dd/g, (`0${date.getDate()}`).slice(-2)) diff --git a/packages/client/src/scripts/get-account-from-id.ts b/packages/client/src/scripts/get-account-from-id.ts index ba3adceecc..1da897f176 100644 --- a/packages/client/src/scripts/get-account-from-id.ts +++ b/packages/client/src/scripts/get-account-from-id.ts @@ -3,5 +3,5 @@ import { get } from '@/scripts/idb-proxy'; export async function getAccountFromId(id: string) { const accounts = await get('accounts') as { token: string; id: string; }[]; if (!accounts) console.log('Accounts are not recorded'); - return accounts.find(e => e.id === id); + return accounts.find(account => account.id === id); } diff --git a/packages/client/src/scripts/get-md5.ts b/packages/client/src/scripts/get-md5.ts deleted file mode 100644 index b002d762b1..0000000000 --- a/packages/client/src/scripts/get-md5.ts +++ /dev/null @@ -1,10 +0,0 @@ -// スクリプトサイズがデカい -//import * as crypto from 'crypto'; - -export default (data: ArrayBuffer) => { - //const buf = new Buffer(data); - //const hash = crypto.createHash('md5'); - //hash.update(buf); - //return hash.digest('hex'); - return ''; -}; diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts index b19656d3cc..78749ad6bb 100644 --- a/packages/client/src/scripts/get-note-menu.ts +++ b/packages/client/src/scripts/get-note-menu.ts @@ -1,4 +1,4 @@ -import { Ref } from 'vue'; +import { defineAsyncComponent, Ref } from 'vue'; import * as misskey from 'misskey-js'; import { $i } from '@/account'; import { i18n } from '@/i18n'; @@ -22,7 +22,7 @@ export function getNoteMenu(props: { props.note.poll == null ); - let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; + const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note; function del(): void { os.confirm({ @@ -83,8 +83,8 @@ export function getNoteMenu(props: { function togglePin(pin: boolean): void { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id - }, undefined, null, e => { - if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { + }, undefined, null, res => { + if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { os.alert({ type: 'error', text: i18n.ts.pinLimitExceeded @@ -209,7 +209,7 @@ export function getNoteMenu(props: { text: i18n.ts.clip, action: () => clip() }, - (appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? { + (appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? { icon: 'fas fa-eye-slash', text: i18n.ts.unwatch, action: () => toggleWatch(false) @@ -227,7 +227,7 @@ export function getNoteMenu(props: { text: i18n.ts.muteThread, action: () => toggleThreadMute(true) }), - appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { + appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? { icon: 'fas fa-thumbtack', text: i18n.ts.unpin, action: () => togglePin(false) @@ -246,14 +246,14 @@ export function getNoteMenu(props: { }] : [] ),*/ - ...(appearNote.userId != $i.id ? [ + ...(appearNote.userId !== $i.id ? [ null, { icon: 'fas fa-exclamation-circle', text: i18n.ts.reportAbuse, action: () => { const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`; - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: appearNote.user, initialComment: `Note: ${u}\n-----\n` }, {}, 'closed'); @@ -261,9 +261,9 @@ export function getNoteMenu(props: { }] : [] ), - ...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [ + ...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [ null, - appearNote.userId == $i.id ? { + appearNote.userId === $i.id ? { icon: 'fas fa-edit', text: i18n.ts.deleteAndEdit, action: delEdit diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts index 54b8d109d6..d57e1c3029 100644 --- a/packages/client/src/scripts/get-note-summary.ts +++ b/packages/client/src/scripts/get-note-summary.ts @@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => { } // ファイルが添付されているとき - if ((note.files || []).length != 0) { + if ((note.files || []).length !== 0) { summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`; } diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts index 192d14b83e..091338efd6 100644 --- a/packages/client/src/scripts/get-user-menu.ts +++ b/packages/client/src/scripts/get-user-menu.ts @@ -6,6 +6,7 @@ import * as os from '@/os'; import { userActions } from '@/store'; import { router } from '@/router'; import { $i, iAmModerator } from '@/account'; +import { defineAsyncComponent } from 'vue'; export function getUserMenu(user) { const meId = $i ? $i.id : null; @@ -127,7 +128,7 @@ export function getUserMenu(user) { } function reportAbuse() { - os.popup(import('@/components/abuse-report-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), { user: user, }, {}, 'closed'); } @@ -147,7 +148,7 @@ export function getUserMenu(user) { userId: user.id }).then(() => { user.isFollowed = !user.isFollowed; - }) + }); } let menu = [{ @@ -168,7 +169,7 @@ export function getUserMenu(user) { action: () => { os.post({ specified: user }); } - }, meId != user.id ? { + }, meId !== user.id ? { type: 'link', icon: 'fas fa-comments', text: i18n.ts.startMessaging, @@ -177,13 +178,13 @@ export function getUserMenu(user) { icon: 'fas fa-list-ul', text: i18n.ts.addToList, action: pushList - }, meId != user.id ? { + }, meId !== user.id ? { icon: 'fas fa-users', text: i18n.ts.inviteToGroup, action: inviteGroup } : undefined] as any; - if ($i && meId != user.id) { + if ($i && meId !== user.id) { menu = menu.concat([null, { icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash', text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute, diff --git a/packages/client/src/scripts/get-user-name.ts b/packages/client/src/scripts/get-user-name.ts new file mode 100644 index 0000000000..d499ea0203 --- /dev/null +++ b/packages/client/src/scripts/get-user-name.ts @@ -0,0 +1,3 @@ +export default function(user: { name?: string | null, username: string }): string { + return user.name || user.username; +} diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts index 2b3f491fd8..fd9c74f6c8 100644 --- a/packages/client/src/scripts/hotkey.ts +++ b/packages/client/src/scripts/hotkey.ts @@ -53,34 +53,34 @@ const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, c const ignoreElemens = ['input', 'textarea']; -function match(e: KeyboardEvent, patterns: Action['patterns']): boolean { - const key = e.code.toLowerCase(); +function match(ev: KeyboardEvent, patterns: Action['patterns']): boolean { + const key = ev.code.toLowerCase(); return patterns.some(pattern => pattern.which.includes(key) && - pattern.ctrl === e.ctrlKey && - pattern.shift === e.shiftKey && - pattern.alt === e.altKey && - !e.metaKey + pattern.ctrl === ev.ctrlKey && + pattern.shift === ev.shiftKey && + pattern.alt === ev.altKey && + !ev.metaKey ); } export const makeHotkey = (keymap: Keymap) => { const actions = parseKeymap(keymap); - return (e: KeyboardEvent) => { + return (ev: KeyboardEvent) => { if (document.activeElement) { if (ignoreElemens.some(el => document.activeElement!.matches(el))) return; if (document.activeElement.attributes['contenteditable']) return; } for (const action of actions) { - const matched = match(e, action.patterns); + const matched = match(ev, action.patterns); if (matched) { - if (!action.allowRepeat && e.repeat) return; + if (!action.allowRepeat && ev.repeat) return; - e.preventDefault(); - e.stopPropagation(); - action.callback(e); + ev.preventDefault(); + ev.stopPropagation(); + action.callback(ev); break; } } diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts index 6329c0860e..8106687b61 100644 --- a/packages/client/src/scripts/hpml/evaluator.ts +++ b/packages/client/src/scripts/hpml/evaluator.ts @@ -36,7 +36,7 @@ export class Hpml { if (this.opts.enableAiScript) { this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({ storageKey: 'pages:' + this.page.id - }), ...initAiLib(this)}, { + }), ...initAiLib(this) }, { in: (q) => { return new Promise(ok => { os.inputText({ @@ -85,7 +85,7 @@ export class Hpml { public eval() { try { this.vars.value = this.evaluateVars(); - } catch (e) { + } catch (err) { //this.onError(e); } } @@ -103,7 +103,7 @@ export class Hpml { public callAiScript(fn: string) { try { if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []); - } catch (e) {} + } catch (err) {} } @autobind @@ -185,7 +185,7 @@ export class Hpml { if (this.aiscript) { try { return utils.valToJs(this.aiscript.scope.get(expr.value)); - } catch (e) { + } catch (err) { return null; } } else { @@ -194,7 +194,7 @@ export class Hpml { } // Define user function - if (expr.type == 'fn') { + if (expr.type === 'fn') { return { slots: expr.value.slots.map(x => x.name), exec: (slotArg: Record<string, any>) => { diff --git a/packages/client/src/scripts/hpml/lib.ts b/packages/client/src/scripts/hpml/lib.ts index 2a1ac73a40..01a44ffcdf 100644 --- a/packages/client/src/scripts/hpml/lib.ts +++ b/packages/client/src/scripts/hpml/lib.ts @@ -1,9 +1,9 @@ -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; import { Hpml } from './evaluator'; import { values, utils } from '@syuilo/aiscript'; import { Fn, HpmlScope } from '.'; import { Expr } from './expr'; -import * as seedrandom from 'seedrandom'; +import seedrandom from 'seedrandom'; /* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs diff --git a/packages/client/src/scripts/idb-proxy.ts b/packages/client/src/scripts/idb-proxy.ts index 5f76ae30bb..d462a0d7ce 100644 --- a/packages/client/src/scripts/idb-proxy.ts +++ b/packages/client/src/scripts/idb-proxy.ts @@ -13,8 +13,8 @@ let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true; if (idbAvailable) { try { await iset('idb-test', 'test'); - } catch (e) { - console.error('idb error', e); + } catch (err) { + console.error('idb error', err); idbAvailable = false; } } diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts index d6dbd5dbd4..7bacfbdf00 100644 --- a/packages/client/src/scripts/initialize-sw.ts +++ b/packages/client/src/scripts/initialize-sw.ts @@ -4,26 +4,26 @@ import { api } from '@/os'; import { lang } from '@/config'; export async function initializeSw() { - if (instance.swPublickey && - ('serviceWorker' in navigator) && - ('PushManager' in window) && - $i && $i.token) { - navigator.serviceWorker.register(`/sw.js`); + if (!('serviceWorker' in navigator)) return; - navigator.serviceWorker.ready.then(registration => { - registration.active?.postMessage({ - msg: 'initialize', - lang, - }); + navigator.serviceWorker.register(`/sw.js`, { scope: '/', type: 'classic' }); + navigator.serviceWorker.ready.then(registration => { + registration.active?.postMessage({ + msg: 'initialize', + lang, + }); + + if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) { // SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(instance.swPublickey) - }).then(subscription => { + }) + .then(subscription => { function encode(buffer: ArrayBuffer | null) { return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))); } - + // Register api('sw/register', { endpoint: subscription.endpoint, @@ -37,15 +37,15 @@ export async function initializeSw() { if (err.name === 'NotAllowedError') { return; } - + // 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが // 既に存在していることが原因でエラーになった可能性があるので、 // そのサブスクリプションを解除しておく const subscription = await registration.pushManager.getSubscription(); if (subscription) subscription.unsubscribe(); }); - }); - } + } + }); } /** diff --git a/packages/client/src/scripts/lookup-user.ts b/packages/client/src/scripts/lookup-user.ts index 8de5c84ce8..2d00e51621 100644 --- a/packages/client/src/scripts/lookup-user.ts +++ b/packages/client/src/scripts/lookup-user.ts @@ -25,12 +25,12 @@ export async function lookupUser() { _notFound = true; } }; - usernamePromise.then(show).catch(e => { - if (e.code === 'NO_SUCH_USER') { + usernamePromise.then(show).catch(err => { + if (err.code === 'NO_SUCH_USER') { notFound(); } }); - idPromise.then(show).catch(e => { + idPromise.then(show).catch(err => { notFound(); }); } diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts new file mode 100644 index 0000000000..08b891ec5b --- /dev/null +++ b/packages/client/src/scripts/navigate.ts @@ -0,0 +1,34 @@ +import { inject } from 'vue'; +import { router } from '@/router'; +import { defaultStore } from '@/store'; + +export type Navigate = (path: string, record?: boolean) => void; + +export class MisskeyNavigator { + public readonly navHook: Navigate | null = null; + public readonly sideViewHook: Navigate | null = null; + + // It should be constructed during vue creating in order for inject function to work + constructor() { + this.navHook = inject<Navigate | null>('navHook', null); + this.sideViewHook = inject<Navigate | null>('sideViewHook', null); + } + + // Use this method instead of router.push() + public push(path: string, record = true) { + if (this.navHook) { + this.navHook(path, record); + } else { + if (defaultStore.state.defaultSideView && this.sideViewHook && path !== '/') { + return this.sideViewHook(path, record); + } + + if (router.currentRoute.value.path === path) { + window.scroll({ top: 0, behavior: 'smooth' }); + } else { + if (record) router.push(path); + else router.replace(path); + } + } + } +} diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts index 36e476b6f9..9e657906c2 100644 --- a/packages/client/src/scripts/physics.ts +++ b/packages/client/src/scripts/physics.ts @@ -41,9 +41,9 @@ export function physics(container: HTMLElement) { const groundThickness = 1024; const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, { - isStatic: true, - restitution: 0.1, - friction: 2 + isStatic: true, + restitution: 0.1, + friction: 2 }); //const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts); diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts index aeaafa124b..e21a6d2ed3 100644 --- a/packages/client/src/scripts/please-login.ts +++ b/packages/client/src/scripts/please-login.ts @@ -1,14 +1,21 @@ +import { defineAsyncComponent } from 'vue'; import { $i } from '@/account'; import { i18n } from '@/i18n'; -import { alert } from '@/os'; +import { popup } from '@/os'; -export function pleaseLogin() { +export function pleaseLogin(path?: string) { if ($i) return; - alert({ - title: i18n.ts.signinRequired, - text: null - }); + popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), { + autoSet: true, + message: i18n.ts.signinRequired + }, { + cancelled: () => { + if (path) { + window.location.href = path; + } + }, + }, 'closed'); throw new Error('signin required'); } diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts index b8286a2a76..580031d0a3 100644 --- a/packages/client/src/scripts/popout.ts +++ b/packages/client/src/scripts/popout.ts @@ -1,8 +1,9 @@ import * as config from '@/config'; +import { appendQuery } from './url'; export function popout(path: string, w?: HTMLElement) { - let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path; - url += '?zen'; + let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path; + url = appendQuery(url, 'zen'); if (w) { const position = w.getBoundingClientRect(); const width = parseInt(getComputedStyle(w, '').width, 10); diff --git a/packages/client/src/scripts/reaction-picker.ts b/packages/client/src/scripts/reaction-picker.ts index 3ac1f63430..b7699cae4a 100644 --- a/packages/client/src/scripts/reaction-picker.ts +++ b/packages/client/src/scripts/reaction-picker.ts @@ -1,4 +1,4 @@ -import { Ref, ref } from 'vue'; +import { defineAsyncComponent, Ref, ref } from 'vue'; import { popup } from '@/os'; class ReactionPicker { @@ -12,7 +12,7 @@ class ReactionPicker { } public async init() { - await popup(import('@/components/emoji-picker-dialog.vue'), { + await popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), { src: this.src, asReactionPicker: true, manualShowing: this.manualShowing diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts index 23df4edf54..461d613b42 100644 --- a/packages/client/src/scripts/select-file.ts +++ b/packages/client/src/scripts/select-file.ts @@ -4,6 +4,7 @@ import { stream } from '@/stream'; import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; import { DriveFile } from 'misskey-js/built/entities'; +import { uploadFile } from '@/scripts/upload'; function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> { return new Promise((res, rej) => { @@ -14,14 +15,14 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv input.type = 'file'; input.multiple = multiple; input.onchange = () => { - const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); + const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value)); Promise.all(promises).then(driveFiles => { res(multiple ? driveFiles : driveFiles[0]); - }).catch(e => { + }).catch(err => { os.alert({ type: 'error', - text: e + text: err }); }); @@ -53,9 +54,9 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv const marker = Math.random().toString(); // TODO: UUIDとか使う const connection = stream.useChannel('main'); - connection.on('urlUploadFinished', data => { - if (data.marker === marker) { - res(multiple ? [data.file] : data.file); + connection.on('urlUploadFinished', urlResponse => { + if (urlResponse.marker === marker) { + res(multiple ? [urlResponse.file] : urlResponse.file); connection.dispose(); } }); diff --git a/packages/client/src/scripts/theme-editor.ts b/packages/client/src/scripts/theme-editor.ts index 3d69d2836a..2c917e280d 100644 --- a/packages/client/src/scripts/theme-editor.ts +++ b/packages/client/src/scripts/theme-editor.ts @@ -1,4 +1,4 @@ -import { v4 as uuid} from 'uuid'; +import { v4 as uuid } from 'uuid'; import { themeProps, Theme } from './theme'; diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts index 2cb78fae5c..dec9fb355c 100644 --- a/packages/client/src/scripts/theme.ts +++ b/packages/client/src/scripts/theme.ts @@ -1,5 +1,6 @@ +import { ref } from 'vue'; import { globalEvents } from '@/events'; -import * as tinycolor from 'tinycolor2'; +import tinycolor from 'tinycolor2'; export type Theme = { id: string; @@ -10,30 +11,38 @@ export type Theme = { props: Record<string, string>; }; -export const lightTheme: Theme = require('@/themes/_light.json5'); -export const darkTheme: Theme = require('@/themes/_dark.json5'); +import lightTheme from '@/themes/_light.json5'; +import darkTheme from '@/themes/_dark.json5'; export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X')); -export const builtinThemes = [ - require('@/themes/l-light.json5'), - require('@/themes/l-coffee.json5'), - require('@/themes/l-apricot.json5'), - require('@/themes/l-rainy.json5'), - require('@/themes/l-vivid.json5'), - require('@/themes/l-cherry.json5'), - require('@/themes/l-sushi.json5'), +export const getBuiltinThemes = () => Promise.all( + [ + 'l-light', + 'l-coffee', + 'l-apricot', + 'l-rainy', + 'l-vivid', + 'l-cherry', + 'l-sushi', - require('@/themes/d-dark.json5'), - require('@/themes/d-persimmon.json5'), - require('@/themes/d-astro.json5'), - require('@/themes/d-future.json5'), - require('@/themes/d-botanical.json5'), - require('@/themes/d-cherry.json5'), - require('@/themes/d-ice.json5'), - require('@/themes/d-pumpkin.json5'), - require('@/themes/d-black.json5'), -] as Theme[]; + 'd-dark', + 'd-persimmon', + 'd-astro', + 'd-future', + 'd-botanical', + 'd-cherry', + 'd-ice', + 'd-pumpkin', + 'd-black', + ].map(name => import(`../themes/${name}.json5`).then(({ default: _default }): Theme => _default)) +); + +export const getBuiltinThemesRef = () => { + const builtinThemes = ref<Theme[]>([]); + getBuiltinThemes().then(themes => builtinThemes.value = themes); + return builtinThemes; +}; let timeout = null; diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts new file mode 100644 index 0000000000..2f7b30b58d --- /dev/null +++ b/packages/client/src/scripts/upload.ts @@ -0,0 +1,114 @@ +import { reactive, ref } from 'vue'; +import { defaultStore } from '@/store'; +import { apiUrl } from '@/config'; +import * as Misskey from 'misskey-js'; +import { $i } from '@/account'; +import { readAndCompressImage } from 'browser-image-resizer'; +import { alert } from '@/os'; + +type Uploading = { + id: string; + name: string; + progressMax: number | undefined; + progressValue: number | undefined; + img: string; +}; +export const uploads = ref<Uploading[]>([]); + +const compressTypeMap = { + 'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/webp': { quality: 0.85, mimeType: 'image/jpeg' }, + 'image/svg+xml': { quality: 1, mimeType: 'image/png' }, +} as const; + +const mimeTypeMap = { + 'image/webp': 'webp', + 'image/jpeg': 'jpg', + 'image/png': 'png', +} as const; + +export function uploadFile( + file: File, + folder?: any, + name?: string, + keepOriginal: boolean = defaultStore.state.keepOriginalUploading +): Promise<Misskey.entities.DriveFile> { + if (folder && typeof folder === 'object') folder = folder.id; + + return new Promise((resolve, reject) => { + const id = Math.random().toString(); + + const reader = new FileReader(); + reader.onload = async (ev) => { + const ctx = reactive<Uploading>({ + id: id, + name: name || file.name || 'untitled', + progressMax: undefined, + progressValue: undefined, + img: window.URL.createObjectURL(file) + }); + + uploads.value.push(ctx); + + let resizedImage: any; + if (!keepOriginal && file.type in compressTypeMap) { + const imgConfig = compressTypeMap[file.type]; + + const config = { + maxWidth: 2048, + maxHeight: 2048, + debug: true, + ...imgConfig, + }; + + try { + resizedImage = await readAndCompressImage(file, config); + ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].mimeType]}` : ctx.name; + } catch (err) { + console.error('Failed to resize image', err); + } + } + + const formData = new FormData(); + formData.append('i', $i.token); + formData.append('force', 'true'); + formData.append('file', resizedImage || file); + formData.append('name', ctx.name); + if (folder) formData.append('folderId', folder); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', apiUrl + '/drive/files/create', true); + xhr.onload = (ev) => { + if (xhr.status !== 200 || ev.target == null || ev.target.response == null) { + // TODO: 消すのではなくて再送できるようにしたい + uploads.value = uploads.value.filter(x => x.id !== id); + + alert({ + type: 'error', + title: 'Failed to upload', + text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}` + }); + + reject(); + return; + } + + const driveFile = JSON.parse(ev.target.response); + + resolve(driveFile); + + uploads.value = uploads.value.filter(x => x.id !== id); + }; + + xhr.upload.onprogress = ev => { + if (ev.lengthComputable) { + ctx.progressMax = ev.total; + ctx.progressValue = ev.loaded; + } + }; + + xhr.send(formData); + }; + reader.readAsArrayBuffer(file); + }); +} diff --git a/packages/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts index c7f2b7c1e7..542b00e0f0 100644 --- a/packages/client/src/scripts/url.ts +++ b/packages/client/src/scripts/url.ts @@ -4,7 +4,7 @@ export function query(obj: {}): string { .reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>); return Object.entries(params) - .map((e) => `${e[0]}=${encodeURIComponent(e[1])}`) + .map((p) => `${p[0]}=${encodeURIComponent(p[1])}`) .join('&'); } diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts index b2a96062c7..f1f976693e 100644 --- a/packages/client/src/scripts/use-note-capture.ts +++ b/packages/client/src/scripts/use-note-capture.ts @@ -11,8 +11,8 @@ export function useNoteCapture(props: { const note = props.note; const connection = $i ? stream : null; - function onStreamNoteUpdated(data): void { - const { type, id, body } = data; + function onStreamNoteUpdated(noteData): void { + const { type, id, body } = noteData; if (id !== note.value.id) return; diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index b9800ec607..deee23951e 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -255,10 +255,13 @@ type Plugin = { /** * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ) */ +import lightTheme from '@/themes/l-light.json5'; +import darkTheme from '@/themes/d-dark.json5'; + export class ColdDeviceStorage { public static default = { - lightTheme: require('@/themes/l-light.json5') as Theme, - darkTheme: require('@/themes/d-dark.json5') as Theme, + lightTheme, + darkTheme, syncDeviceDarkMode: true, plugins: [] as Plugin[], mediaVolume: 0.5, diff --git a/packages/client/src/sw/compose-notification.ts b/packages/client/src/sw/compose-notification.ts deleted file mode 100644 index e271d30949..0000000000 --- a/packages/client/src/sw/compose-notification.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Notification composer of Service Worker - */ -declare var self: ServiceWorkerGlobalScope; - -import * as misskey from 'misskey-js'; - -function getUserName(user: misskey.entities.User): string { - return user.name || user.username; -} - -export default async function(type, data, i18n): Promise<[string, NotificationOptions] | null | undefined> { - if (!i18n) { - console.log('no i18n'); - return; - } - - switch (type) { - case 'driveFileCreated': // TODO (Server Side) - return [i18n.t('_notification.fileUploaded'), { - body: data.name, - icon: data.url - }]; - case 'notification': - switch (data.type) { - case 'mention': - return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'reply': - return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'renote': - return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'quote': - return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'reaction': - return [`${data.reaction} ${getUserName(data.user)}`, { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'pollVote': - return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), { - body: data.note.text, - icon: data.user.avatarUrl - }]; - - case 'pollEnded': - return [i18n.t('_notification.pollEnded'), { - body: data.note.text, - }]; - - case 'follow': - return [i18n.t('_notification.youWereFollowed'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'receiveFollowRequest': - return [i18n.t('_notification.youReceivedFollowRequest'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'followRequestAccepted': - return [i18n.t('_notification.yourFollowRequestAccepted'), { - body: getUserName(data.user), - icon: data.user.avatarUrl - }]; - - case 'groupInvited': - return [i18n.t('_notification.youWereInvitedToGroup'), { - body: data.group.name - }]; - - default: - return null; - } - case 'unreadMessagingMessage': - if (data.groupId === null) { - return [i18n.t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.user) }), { - icon: data.user.avatarUrl, - tag: `messaging:user:${data.user.id}` - }]; - } - return [i18n.t('_notification.youGotMessagingMessageFromGroup', { name: data.group.name }), { - icon: data.user.avatarUrl, - tag: `messaging:group:${data.group.id}` - }]; - default: - return null; - } -} diff --git a/packages/client/src/sw/sw.ts b/packages/client/src/sw/sw.ts deleted file mode 100644 index 68c650c771..0000000000 --- a/packages/client/src/sw/sw.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Service Worker - */ -declare var self: ServiceWorkerGlobalScope; - -import { get, set } from 'idb-keyval'; -import composeNotification from '@/sw/compose-notification'; -import { I18n } from '@/scripts/i18n'; - -//#region Variables -const version = _VERSION_; -const cacheName = `mk-cache-${version}`; - -let lang: string; -let i18n: I18n<any>; -let pushesPool: any[] = []; -//#endregion - -//#region Startup -get('lang').then(async prelang => { - if (!prelang) return; - lang = prelang; - return fetchLocale(); -}); -//#endregion - -//#region Lifecycle: Install -self.addEventListener('install', ev => { - self.skipWaiting(); -}); -//#endregion - -//#region Lifecycle: Activate -self.addEventListener('activate', ev => { - ev.waitUntil( - caches.keys() - .then(cacheNames => Promise.all( - cacheNames - .filter((v) => v !== cacheName) - .map(name => caches.delete(name)) - )) - .then(() => self.clients.claim()) - ); -}); -//#endregion - -//#region When: Fetching -self.addEventListener('fetch', ev => { - // Nothing to do -}); -//#endregion - -//#region When: Caught Notification -self.addEventListener('push', ev => { - // クライアント取得 - ev.waitUntil(self.clients.matchAll({ - includeUncontrolled: true - }).then(async clients => { - // クライアントがあったらストリームに接続しているということなので通知しない - if (clients.length != 0) return; - - const { type, body } = ev.data?.json(); - - // localeを読み込めておらずi18nがundefinedだった場合はpushesPoolにためておく - if (!i18n) return pushesPool.push({ type, body }); - - const n = await composeNotification(type, body, i18n); - if (n) return self.registration.showNotification(...n); - })); -}); -//#endregion - -//#region When: Caught a message from the client -self.addEventListener('message', ev => { - switch(ev.data) { - case 'clear': - return; // TODO - default: - break; - } - - if (typeof ev.data === 'object') { - // E.g. '[object Array]' → 'array' - const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); - - if (otype === 'object') { - if (ev.data.msg === 'initialize') { - lang = ev.data.lang; - set('lang', lang); - fetchLocale(); - } - } - } -}); -//#endregion - -//#region Function: (Re)Load i18n instance -async function fetchLocale() { - //#region localeファイルの読み込み - // Service Workerは何度も起動しそのたびにlocaleを読み込むので、CacheStorageを使う - const localeUrl = `/assets/locales/${lang}.${version}.json`; - let localeRes = await caches.match(localeUrl); - - if (!localeRes) { - localeRes = await fetch(localeUrl); - const clone = localeRes?.clone(); - if (!clone?.clone().ok) return; - - caches.open(cacheName).then(cache => cache.put(localeUrl, clone)); - } - - i18n = new I18n(await localeRes.json()); - //#endregion - - //#region i18nをきちんと読み込んだ後にやりたい処理 - for (const { type, body } of pushesPool) { - const n = await composeNotification(type, body, i18n); - if (n) self.registration.showNotification(...n); - } - pushesPool = []; - //#endregion -} -//#endregion diff --git a/packages/client/src/theme-store.ts b/packages/client/src/theme-store.ts index e7962e7e8e..fdc92ed793 100644 --- a/packages/client/src/theme-store.ts +++ b/packages/client/src/theme-store.ts @@ -14,9 +14,9 @@ export async function fetchThemes(): Promise<void> { try { const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' }); localStorage.setItem(lsCacheKey, JSON.stringify(themes)); - } catch (e) { - if (e.code === 'NO_SUCH_KEY') return; - throw e; + } catch (err) { + if (err.code === 'NO_SUCH_KEY') return; + throw err; } } @@ -28,7 +28,7 @@ export async function addTheme(theme: Theme): Promise<void> { } export async function removeTheme(theme: Theme): Promise<void> { - const themes = getThemes().filter(t => t.id != theme.id); + const themes = getThemes().filter(t => t.id !== theme.id); await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes }); localStorage.setItem(lsCacheKey, JSON.stringify(themes)); } diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue index 05688d7c53..9f7388db53 100644 --- a/packages/client/src/ui/_common_/common.vue +++ b/packages/client/src/ui/_common_/common.vue @@ -17,9 +17,11 @@ <script lang="ts"> import { defineAsyncComponent, defineComponent } from 'vue'; -import { popup, popups, uploads, pendingApiRequestsCount } from '@/os'; +import { popup, popups, pendingApiRequestsCount } from '@/os'; +import { uploads } from '@/scripts/upload'; import * as sound from '@/scripts/sound'; import { $i } from '@/account'; +import { swInject } from './sw-inject'; import { stream } from '@/stream'; export default defineComponent({ @@ -37,7 +39,7 @@ export default defineComponent({ id: notification.id }); - popup(import('@/components/notification-toast.vue'), { + popup(defineAsyncComponent(() => import('@/components/notification-toast.vue')), { notification }, {}, 'closed'); } @@ -48,6 +50,11 @@ export default defineComponent({ if ($i) { const connection = stream.useChannel('main', null, 'UI'); connection.on('notification', onNotification); + + //#region Listen message from SW + if ('serviceWorker' in navigator) { + swInject(); + } } return { diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue index afcc50725b..41d0837233 100644 --- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue +++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue @@ -33,7 +33,7 @@ </template> <script lang="ts"> -import { computed, defineComponent, ref, toRef, watch } from 'vue'; +import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -61,13 +61,13 @@ export default defineComponent({ otherMenuItemIndicated, post: os.post, search, - openAccountMenu:(ev) => { + openAccountMenu: (ev) => { openAccountMenu({ withExtraOperation: true, }, ev); }, more: () => { - os.popup(import('@/components/launch-pad.vue'), {}, { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {}, { }, 'closed'); }, }; diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue index a23b7d4152..d65e776d86 100644 --- a/packages/client/src/ui/_common_/sidebar.vue +++ b/packages/client/src/ui/_common_/sidebar.vue @@ -33,7 +33,7 @@ </template> <script lang="ts" setup> -import { computed, ref, watch } from 'vue'; +import { computed, defineAsyncComponent, ref, watch } from 'vue'; import * as os from '@/os'; import { menuDef } from '@/menu'; import { $i, openAccountMenu as openAccountMenu_ } from '@/account'; @@ -69,7 +69,7 @@ function openAccountMenu(ev: MouseEvent) { } function more(ev: MouseEvent) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, }, { }, 'closed'); diff --git a/packages/client/src/ui/_common_/sw-inject.ts b/packages/client/src/ui/_common_/sw-inject.ts new file mode 100644 index 0000000000..371f80ca15 --- /dev/null +++ b/packages/client/src/ui/_common_/sw-inject.ts @@ -0,0 +1,44 @@ +import { inject } from 'vue'; +import { post } from '@/os'; +import { $i, login } from '@/account'; +import { defaultStore } from '@/store'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; +import { router } from '@/router'; + +export function swInject() { + const navHook = inject('navHook', null); + const sideViewHook = inject('sideViewHook', null); + + navigator.serviceWorker.addEventListener('message', ev => { + if (_DEV_) { + console.log('sw msg', ev.data); + } + + if (ev.data.type !== 'order') return; + + if (ev.data.loginId !== $i?.id) { + return getAccountFromId(ev.data.loginId).then(account => { + if (!account) return; + return login(account.token, ev.data.url); + }); + } + + switch (ev.data.order) { + case 'post': + return post(ev.data.options); + case 'push': + if (router.currentRoute.value.path === ev.data.url) { + return window.scroll({ top: 0, behavior: 'smooth' }); + } + if (navHook) { + return navHook(ev.data.url); + } + if (sideViewHook && defaultStore.state.defaultSideView && ev.data.url !== '/') { + return sideViewHook(ev.data.url); + } + return router.push(ev.data.url); + default: + return; + } + }); +} diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue index ab7678a505..f3703d0e8f 100644 --- a/packages/client/src/ui/_common_/upload.vue +++ b/packages/client/src/ui/_common_/upload.vue @@ -20,8 +20,8 @@ <script lang="ts" setup> import { } from 'vue'; import * as os from '@/os'; +import { uploads } from '@/scripts/upload'; -const uploads = os.uploads; const zIndex = os.claimZIndex('high'); </script> diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue index c7fddbc491..57008aeaed 100644 --- a/packages/client/src/ui/classic.header.vue +++ b/packages/client/src/ui/classic.header.vue @@ -39,7 +39,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -101,7 +101,7 @@ export default defineComponent({ }, more(ev) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, anchor: { x: 'center', y: 'bottom' }, }, { diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue index 3364ee39be..6c0ce023e4 100644 --- a/packages/client/src/ui/classic.sidebar.vue +++ b/packages/client/src/ui/classic.sidebar.vue @@ -41,7 +41,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineAsyncComponent, defineComponent } from 'vue'; import { host } from '@/config'; import { search } from '@/scripts/search'; import * as os from '@/os'; @@ -121,12 +121,12 @@ export default defineComponent({ }, more(ev) { - os.popup(import('@/components/launch-pad.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), { src: ev.currentTarget ?? ev.target, }, {}, 'closed'); }, - openAccountMenu:(ev) => { + openAccountMenu: (ev) => { openAccountMenu({ withExtraOperation: true, }, ev); diff --git a/packages/client/src/ui/classic.widgets.vue b/packages/client/src/ui/classic.widgets.vue index f42f27e926..6f9d18bde5 100644 --- a/packages/client/src/ui/classic.widgets.vue +++ b/packages/client/src/ui/classic.widgets.vue @@ -44,13 +44,13 @@ export default defineComponent({ }, removeWidget(widget) { - this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); + this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id !== widget.id)); }, updateWidget({ id, data }) { this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { ...w, - data: data + data, } : w)); }, diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue index 1e0d9a1652..e538a93f06 100644 --- a/packages/client/src/ui/deck.vue +++ b/packages/client/src/ui/deck.vue @@ -17,7 +17,7 @@ :key="ids[0]" class="column" :column="columns.find(c => c.id === ids[0])" - :is-stacked="false" + :is-stacked="false" :style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }" @parent-focus="moveFocus(ids[0], $event)" /> diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue index e0f56c2800..f12f5c6b25 100644 --- a/packages/client/src/ui/deck/antenna-column.vue +++ b/packages/client/src/ui/deck/antenna-column.vue @@ -22,8 +22,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let timeline = $ref<InstanceType<typeof XTimeline>>(); diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue index 485e89a062..2667b6d745 100644 --- a/packages/client/src/ui/deck/column-core.vue +++ b/packages/client/src/ui/deck/column-core.vue @@ -29,7 +29,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); /* diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue index 5f8da8cf8f..6db3549fbb 100644 --- a/packages/client/src/ui/deck/column.vue +++ b/packages/client/src/ui/deck/column.vue @@ -61,8 +61,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; - (e: 'change-active-state', v: boolean): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'change-active-state', v: boolean): void; }>(); let body = $ref<HTMLDivElement>(); @@ -94,7 +94,6 @@ onBeforeUnmount(() => { os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd); }); - function onOtherDragStart() { dropready = true; } @@ -193,9 +192,9 @@ function goTop() { }); } -function onDragstart(e) { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); +function onDragstart(ev) { + ev.dataTransfer.effectAllowed = 'move'; + ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id); // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately @@ -204,35 +203,34 @@ function onDragstart(e) { }, 10); } -function onDragend(e) { +function onDragend(ev) { dragging = false; } -function onDragover(e) { +function onDragover(ev) { // 自分自身がドラッグされている場合 if (dragging) { // 自分自身にはドロップさせない - e.dataTransfer.dropEffect = 'none'; - return; - } + ev.dataTransfer.dropEffect = 'none'; + } else { + const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_; - const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; + ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; - - if (!dragging && isDeckColumn) draghover = true; + if (isDeckColumn) draghover = true; + } } function onDragleave() { draghover = false; } -function onDrop(e) { +function onDrop(ev) { draghover = false; os.deckGlobalEvents.emit('column.dragEnd'); - const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); - if (id != null && id != '') { + const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); + if (id != null && id !== '') { swapColumn(props.column.id, id); } } diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts index f7c39ad8fd..c847bf2b43 100644 --- a/packages/client/src/ui/deck/deck-store.ts +++ b/packages/client/src/ui/deck/deck-store.ts @@ -72,8 +72,8 @@ export const loadDeck = async () => { scope: ['client', 'deck', 'profiles'], key: deckStore.state.profile, }); - } catch (e) { - if (e.code === 'NO_SUCH_KEY') { + } catch (err) { + if (err.code === 'NO_SUCH_KEY') { // 後方互換性のため if (deckStore.state.profile === 'default') { saveDeck(); @@ -94,7 +94,7 @@ export const loadDeck = async () => { deckStore.set('layout', [['a'], ['b']]); return; } - throw e; + throw err; } deckStore.set('columns', deck.columns); @@ -114,7 +114,7 @@ export const saveDeck = throttle(1000, () => { }); export function addColumn(column: Column) { - if (column.name == undefined) column.name = null; + if (column.name === undefined) column.name = null; deckStore.push('columns', column); deckStore.push('layout', [column.id]); saveDeck(); @@ -129,10 +129,10 @@ export function removeColumn(id: Column['id']) { } export function swapColumn(a: Column['id'], b: Column['id']) { - const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) != -1); - const aY = deckStore.state.layout[aX].findIndex(id => id == a); - const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) != -1); - const bY = deckStore.state.layout[bX].findIndex(id => id == b); + const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1); + const aY = deckStore.state.layout[aX].findIndex(id => id === a); + const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1); + const bY = deckStore.state.layout[bX].findIndex(id => id === b); const layout = copy(deckStore.state.layout); layout[aX][aY] = b; layout[bX][bY] = a; @@ -259,7 +259,7 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const column = copy(deckStore.state.columns[columnIndex]); if (column == null) return; - column.widgets = column.widgets.filter(w => w.id != widget.id); + column.widgets = column.widgets.filter(w => w.id !== widget.id); columns[columnIndex] = column; deckStore.set('columns', columns); saveDeck(); @@ -276,14 +276,14 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { saveDeck(); } -export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) { +export function updateColumnWidget(id: Column['id'], widgetId: string, WidgetData: any) { const columns = copy(deckStore.state.columns); const columnIndex = deckStore.state.columns.findIndex(c => c.id === id); const column = copy(deckStore.state.columns[columnIndex]); if (column == null) return; column.widgets = column.widgets.map(w => w.id === widgetId ? { ...w, - data: data + data: widgetData, } : w); columns[columnIndex] = column; deckStore.set('columns', columns); diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue index ebaba574f4..4837c0ce38 100644 --- a/packages/client/src/ui/deck/direct-column.vue +++ b/packages/client/src/ui/deck/direct-column.vue @@ -18,7 +18,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); const pagination = { diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue index b990516d05..843a3bd1cb 100644 --- a/packages/client/src/ui/deck/list-column.vue +++ b/packages/client/src/ui/deck/list-column.vue @@ -22,8 +22,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let timeline = $ref<InstanceType<typeof XTimeline>>(); diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue index 57caab44cb..3c97cd4867 100644 --- a/packages/client/src/ui/deck/main-column.vue +++ b/packages/client/src/ui/deck/main-column.vue @@ -35,7 +35,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let pageInfo = $ref<Record<string, any> | null>(null); diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue index a7a012a7fb..0b6ca3a239 100644 --- a/packages/client/src/ui/deck/mentions-column.vue +++ b/packages/client/src/ui/deck/mentions-column.vue @@ -18,7 +18,7 @@ defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); const pagination = { diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue index 50ee12a275..6dd040cb8d 100644 --- a/packages/client/src/ui/deck/notifications-column.vue +++ b/packages/client/src/ui/deck/notifications-column.vue @@ -7,7 +7,7 @@ </template> <script lang="ts" setup> -import { } from 'vue'; +import { defineAsyncComponent } from 'vue'; import XColumn from './column.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; @@ -20,11 +20,11 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); function func() { - os.popup(import('@/components/notification-setting-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { includingTypes: props.column.includingTypes, }, { done: async (res) => { diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue index 02b9ef83a1..f3ecda5aa4 100644 --- a/packages/client/src/ui/deck/tl-column.vue +++ b/packages/client/src/ui/deck/tl-column.vue @@ -35,8 +35,8 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'loaded'): void; - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'loaded'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let disabled = $ref(false); diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue index a2edc38357..10c6f5adf6 100644 --- a/packages/client/src/ui/deck/widgets-column.vue +++ b/packages/client/src/ui/deck/widgets-column.vue @@ -3,7 +3,7 @@ <template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template> <div class="wtdtxvec"> - <XWidgets v-if="column.widgets" :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> + <XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> </div> </XColumn> </template> @@ -20,7 +20,7 @@ const props = defineProps<{ }>(); const emit = defineEmits<{ - (e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; + (ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void; }>(); let edit = $ref(false); diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue index 2660e80368..7aed083886 100644 --- a/packages/client/src/ui/universal.widgets.vue +++ b/packages/client/src/ui/universal.widgets.vue @@ -3,7 +3,7 @@ <XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button> - <button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> + <button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button> </div> </template> @@ -14,7 +14,7 @@ import { i18n } from '@/i18n'; import { defaultStore } from '@/store'; const emit = defineEmits<{ - (e: 'mounted', el: Element): void; + (ev: 'mounted', el: Element): void; }>(); let editMode = $ref(false); @@ -32,13 +32,13 @@ function addWidget(widget) { } function removeWidget(widget) { - defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id)); + defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id !== widget.id)); } function updateWidget({ id, data }) { defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? { ...w, - data: data + data, } : w)); } diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue index b833bd65ca..33b95b00db 100644 --- a/packages/client/src/widgets/activity.calendar.vue +++ b/packages/client/src/widgets/activity.calendar.vue @@ -1,13 +1,13 @@ <template> <svg viewBox="0 0 21 7"> - <rect v-for="record in data" class="day" + <rect v-for="record in activity" class="day" width="1" height="1" :x="record.x" :y="record.date.weekday" rx="1" ry="1" fill="transparent"> <title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title> </rect> - <rect v-for="record in data" class="day" + <rect v-for="record in activity" class="day" :width="record.v" :height="record.v" :x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)" rx="1" ry="1" @@ -15,7 +15,7 @@ style="pointer-events: none;"/> <rect class="today" width="1" height="1" - :x="data[0].x" :y="data[0].date.weekday" + :x="activity[0].x" :y="activity[0].date.weekday" rx="1" ry="1" fill="none" stroke-width="0.1" @@ -23,45 +23,41 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +const props = defineProps<{ + activity: any[] +}>(); -export default defineComponent({ - props: ['data'], - created() { - for (const d of this.data) { - d.total = d.notes + d.replies + d.renotes; - } - const peak = Math.max.apply(null, this.data.map(d => d.total)); +for (const d of props.activity) { + d.total = d.notes + d.replies + d.renotes; +} +const peak = Math.max(...props.activity.map(d => d.total)); - const now = new Date(); - const year = now.getFullYear(); - const month = now.getMonth(); - const day = now.getDate(); +const now = new Date(); +const year = now.getFullYear(); +const month = now.getMonth(); +const day = now.getDate(); - let x = 20; - this.data.slice().forEach((d, i) => { - d.x = x; +let x = 20; +props.activity.slice().forEach((d, i) => { + d.x = x; - const date = new Date(year, month, day - i); - d.date = { - year: date.getFullYear(), - month: date.getMonth(), - day: date.getDate(), - weekday: date.getDay() - }; + const date = new Date(year, month, day - i); + d.date = { + year: date.getFullYear(), + month: date.getMonth(), + day: date.getDate(), + weekday: date.getDay() + }; - d.v = peak === 0 ? 0 : d.total / (peak / 2); - if (d.v > 1) d.v = 1; - const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170; - const cs = d.v * 100; - const cl = 15 + ((1 - d.v) * 80); - d.color = `hsl(${ch}, ${cs}%, ${cl}%)`; + d.v = peak === 0 ? 0 : d.total / (peak / 2); + if (d.v > 1) d.v = 1; + const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170; + const cs = d.v * 100; + const cl = 15 + ((1 - d.v) * 80); + d.color = `hsl(${ch}, ${cs}%, ${cl}%)`; - if (d.date.weekday === 0) x--; - }); - } + if (d.date.weekday === 0) x--; }); </script> diff --git a/packages/client/src/widgets/activity.chart.vue b/packages/client/src/widgets/activity.chart.vue index 9702d66663..b7db2af580 100644 --- a/packages/client/src/widgets/activity.chart.vue +++ b/packages/client/src/widgets/activity.chart.vue @@ -24,9 +24,19 @@ </svg> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; -import * as os from '@/os'; +<script lang="ts" setup> +const props = defineProps<{ + activity: any[] +}>(); + +let viewBoxX: number = $ref(147); +let viewBoxY: number = $ref(60); +let zoom: number = $ref(1); +let pos: number = $ref(0); +let pointsNote: any = $ref(null); +let pointsReply: any = $ref(null); +let pointsRenote: any = $ref(null); +let pointsTotal: any = $ref(null); function dragListen(fn) { window.addEventListener('mousemove', fn); @@ -40,60 +50,35 @@ function dragClear(fn) { window.removeEventListener('mouseup', dragClear); } -export default defineComponent({ - props: ['data'], - data() { - return { - viewBoxX: 147, - viewBoxY: 60, - zoom: 1, - pos: 0, - pointsNote: null, - pointsReply: null, - pointsRenote: null, - pointsTotal: null - }; - }, - created() { - for (const d of this.data) { - d.total = d.notes + d.replies + d.renotes; - } +function onMousedown(ev) { + const clickX = ev.clientX; + const clickY = ev.clientY; + const baseZoom = zoom; + const basePos = pos; - this.render(); - }, - methods: { - render() { - const peak = Math.max.apply(null, this.data.map(d => d.total)); - if (peak != 0) { - const data = this.data.slice().reverse(); - this.pointsNote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '); - this.pointsReply = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '); - this.pointsRenote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '); - this.pointsTotal = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' '); - } - }, - onMousedown(e) { - const clickX = e.clientX; - const clickY = e.clientY; - const baseZoom = this.zoom; - const basePos = this.pos; + // 動かした時 + dragListen(me => { + let moveLeft = me.clientX - clickX; + let moveTop = me.clientY - clickY; - // 動かした時 - dragListen(me => { - let moveLeft = me.clientX - clickX; - let moveTop = me.clientY - clickY; + zoom = Math.max(1, baseZoom + (-moveTop / 20)); + pos = Math.min(0, basePos + moveLeft); + if (pos < -(((props.activity.length - 1) * zoom) - viewBoxX)) pos = -(((props.activity.length - 1) * zoom) - viewBoxX); - this.zoom = baseZoom + (-moveTop / 20); - this.pos = basePos + moveLeft; - if (this.zoom < 1) this.zoom = 1; - if (this.pos > 0) this.pos = 0; - if (this.pos < -(((this.data.length - 1) * this.zoom) - this.viewBoxX)) this.pos = -(((this.data.length - 1) * this.zoom) - this.viewBoxX); + render(); + }); +} - this.render(); - }); - } +function render() { + const peak = Math.max(...props.activity.map(d => d.total)); + if (peak !== 0) { + const activity = props.activity.slice().reverse(); + pointsNote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.notes / peak)) * viewBoxY}`).join(' '); + pointsReply = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.replies / peak)) * viewBoxY}`).join(' '); + pointsRenote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.renotes / peak)) * viewBoxY}`).join(' '); + pointsTotal = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.total / peak)) * viewBoxY}`).join(' '); } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue index acbbb7a97a..7fb9f5894c 100644 --- a/packages/client/src/widgets/activity.vue +++ b/packages/client/src/widgets/activity.vue @@ -1,13 +1,13 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" class="mkw-activity"> <template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template> <template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template> <div> <MkLoading v-if="fetching"/> <template v-else> - <XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/> - <XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/> + <XCalendar v-show="widgetProps.view === 0" :activity="[].concat(activity)"/> + <XChart v-show="widgetProps.view === 1" :activity="[].concat(activity)"/> </template> </div> </MkContainer> @@ -47,7 +47,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue index 03e394b976..cdd367cc84 100644 --- a/packages/client/src/widgets/aichan.vue +++ b/packages/client/src/widgets/aichan.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-aichan"> <iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe> </MkContainer> </template> @@ -24,7 +24,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue index 0a5c0d614d..9fed292a69 100644 --- a/packages/client/src/widgets/aiscript.vue +++ b/packages/client/src/widgets/aiscript.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscript"> <template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template> <div class="uylguesu _monospace"> @@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -94,7 +94,7 @@ const run = async () => { let ast; try { ast = parse(widgetProps.script); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: 'Syntax error :(', @@ -103,10 +103,10 @@ const run = async () => { } try { await aiscript.exec(ast); - } catch (e) { + } catch (err) { os.alert({ type: 'error', - text: e, + text: err, }); } }; diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue index a33afd6e7a..ee4e9c6423 100644 --- a/packages/client/src/widgets/button.vue +++ b/packages/client/src/widgets/button.vue @@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, @@ -73,7 +73,7 @@ const run = async () => { let ast; try { ast = parse(widgetProps.script); - } catch (e) { + } catch (err) { os.alert({ type: 'error', text: 'Syntax error :(', @@ -82,10 +82,10 @@ const run = async () => { } try { await aiscript.exec(ast); - } catch (e) { + } catch (err) { os.alert({ type: 'error', - text: e, + text: err, }); } }; diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue index c6a69b3fb8..2a2b035541 100644 --- a/packages/client/src/widgets/calendar.vue +++ b/packages/client/src/widgets/calendar.vue @@ -53,7 +53,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue index 6acb10d74d..fbd2f9e899 100644 --- a/packages/client/src/widgets/clock.vue +++ b/packages/client/src/widgets/clock.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :naked="widgetProps.transparent" :show-header="false"> +<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock"> <div class="vubelbmv"> <MkAnalogClock class="clock" :thickness="widgetProps.thickness"/> </div> @@ -39,7 +39,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue index 62f052a692..a17ed040c9 100644 --- a/packages/client/src/widgets/digital-clock.vue +++ b/packages/client/src/widgets/digital-clock.vue @@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue index 5f1131dce1..a3862077bb 100644 --- a/packages/client/src/widgets/federation.vue +++ b/packages/client/src/widgets/federation.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable"> +<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" class="mkw-federation"> <template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template> <div class="wbrkwalb"> @@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue index 4a2a3cf233..8897f240bd 100644 --- a/packages/client/src/widgets/job-queue.vue +++ b/packages/client/src/widgets/job-queue.vue @@ -73,7 +73,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue index 450598f65a..8670cb2bac 100644 --- a/packages/client/src/widgets/memo.vue +++ b/packages/client/src/widgets/memo.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-memo"> <template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template> <div class="otgbylcu"> @@ -32,7 +32,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue index 8cf29c9271..18c546ee74 100644 --- a/packages/client/src/widgets/notifications.vue +++ b/packages/client/src/widgets/notifications.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true"> +<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" class="mkw-notifications"> <template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template> <template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template> @@ -15,6 +15,7 @@ import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExp import MkContainer from '@/components/ui/container.vue'; import XNotifications from '@/components/notifications.vue'; import * as os from '@/os'; +import { defineAsyncComponent } from 'vue'; const name = 'notifications'; @@ -40,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -49,7 +50,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name, ); const configureNotification = () => { - os.popup(import('@/components/notification-setting-window.vue'), { + os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), { includingTypes: widgetProps.includingTypes, }, { done: async (res) => { diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue index 1746a8314e..eb3184fe9d 100644 --- a/packages/client/src/widgets/online-users.vue +++ b/packages/client/src/widgets/online-users.vue @@ -27,7 +27,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue index 8f948dc643..5d9b9e2984 100644 --- a/packages/client/src/widgets/photos.vue +++ b/packages/client/src/widgets/photos.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null"> +<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" class="mkw-photos"> <template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template> <div class=""> @@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue index 51aa8fcf6b..b542913357 100644 --- a/packages/client/src/widgets/post-form.vue +++ b/packages/client/src/widgets/post-form.vue @@ -1,5 +1,5 @@ <template> -<XPostForm class="_panel" :fixed="true" :autofocus="false"/> +<XPostForm class="_panel mkw-postForm" :fixed="true" :autofocus="false"/> </template> <script lang="ts" setup> @@ -19,7 +19,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue index 9e2e503602..fc65f11813 100644 --- a/packages/client/src/widgets/rss.vue +++ b/packages/client/src/widgets/rss.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-rss"> <template #header><i class="fas fa-rss-square"></i>RSS</template> <template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template> @@ -38,7 +38,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue index ad9e6a8b0f..00c3a10c9b 100644 --- a/packages/client/src/widgets/server-metric/cpu-mem.vue +++ b/packages/client/src/widgets/server-metric/cpu-mem.vue @@ -69,79 +69,72 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import { v4 as uuid } from 'uuid'; -export default defineComponent({ - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - viewBoxX: 50, - viewBoxY: 30, - stats: [], - cpuGradientId: uuid(), - cpuMaskId: uuid(), - memGradientId: uuid(), - memMaskId: uuid(), - cpuPolylinePoints: '', - memPolylinePoints: '', - cpuPolygonPoints: '', - memPolygonPoints: '', - cpuHeadX: null, - cpuHeadY: null, - memHeadX: null, - memHeadY: null, - cpuP: '', - memP: '' - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8) - }); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - }, - methods: { - onStats(stats) { - this.stats.push(stats); - if (this.stats.length > 50) this.stats.shift(); +const props = defineProps<{ + connection: any, + meta: any +}>(); - const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu) * this.viewBoxY]); - const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.active / this.meta.mem.total)) * this.viewBoxY]); - this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); - this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); +let viewBoxX: number = $ref(50); +let viewBoxY: number = $ref(30); +let stats: any[] = $ref([]); +const cpuGradientId = uuid(); +const cpuMaskId = uuid(); +const memGradientId = uuid(); +const memMaskId = uuid(); +let cpuPolylinePoints: string = $ref(''); +let memPolylinePoints: string = $ref(''); +let cpuPolygonPoints: string = $ref(''); +let memPolygonPoints: string = $ref(''); +let cpuHeadX: any = $ref(null); +let cpuHeadY: any = $ref(null); +let memHeadX: any = $ref(null); +let memHeadY: any = $ref(null); +let cpuP: string = $ref(''); +let memP: string = $ref(''); - this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; +onMounted(() => { + props.connection.on('stats', onStats); + props.connection.on('statsLog', onStatsLog); + props.connection.send('requestLog', { + id: Math.random().toString().substr(2, 8) + }); +}); - this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0]; - this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1]; - this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0]; - this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1]; +onBeforeUnmount(() => { + props.connection.off('stats', onStats); + props.connection.off('statsLog', onStatsLog); +}); - this.cpuP = (stats.cpu * 100).toFixed(0); - this.memP = (stats.mem.active / this.meta.mem.total * 100).toFixed(0); - }, - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - } +function onStats(connStats) { + stats.push(connStats); + if (stats.length > 50) stats.shift(); + + let cpuPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - s.cpu) * viewBoxY]); + let memPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY]); + cpuPolylinePoints = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + memPolylinePoints = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + + cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`; + memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`; + + cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0]; + cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1]; + memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0]; + memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1]; + + cpuP = (connStats.cpu * 100).toFixed(0); + memP = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0); +} + +function onStatsLog(statsLog) { + for (const revStats of [...statsLog].reverse()) { + onStats(revStats); } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue index 4478ee3065..baf802cb8f 100644 --- a/packages/client/src/widgets/server-metric/cpu.vue +++ b/packages/client/src/widgets/server-metric/cpu.vue @@ -9,38 +9,27 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import XPie from './pie.vue'; -export default defineComponent({ - components: { - XPie - }, - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - usage: 0, - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - }, - methods: { - onStats(stats) { - this.usage = stats.cpu; - } - } +const props = defineProps<{ + connection: any, + meta: any +}>(); + +let usage: number = $ref(0); + +function onStats(stats) { + usage = stats.cpu; +} + +onMounted(() => { + props.connection.on('stats', onStats); +}); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); }); </script> diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue index 2caa73fa74..9e86b811d1 100644 --- a/packages/client/src/widgets/server-metric/index.vue +++ b/packages/client/src/widgets/server-metric/index.vue @@ -50,7 +50,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -65,7 +65,7 @@ os.api('server-info', {}).then(res => { }); const toggleView = () => { - if (widgetProps.view == 4) { + if (widgetProps.view === 4) { widgetProps.view = 0; } else { widgetProps.view++; diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue index a6ca7b1175..6018eb4265 100644 --- a/packages/client/src/widgets/server-metric/mem.vue +++ b/packages/client/src/widgets/server-metric/mem.vue @@ -10,46 +10,34 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import XPie from './pie.vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - components: { - XPie - }, - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - usage: 0, - total: 0, - used: 0, - free: 0, - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - }, - methods: { - onStats(stats) { - this.usage = stats.mem.active / this.meta.mem.total; - this.total = this.meta.mem.total; - this.used = stats.mem.active; - this.free = this.meta.mem.total - stats.mem.active; - }, - bytes - } +const props = defineProps<{ + connection: any, + meta: any +}>(); + +let usage: number = $ref(0); +let total: number = $ref(0); +let used: number = $ref(0); +let free: number = $ref(0); + +function onStats(stats) { + usage = stats.mem.active / props.meta.mem.total; + total = props.meta.mem.total; + used = stats.mem.active; + free = total - used; +} + +onMounted(() => { + props.connection.on('stats', onStats); +}); + +onBeforeUnmount(() => { + props.connection.off('stats', onStats); }); </script> diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue index 23c148eeb6..b698953f97 100644 --- a/packages/client/src/widgets/server-metric/net.vue +++ b/packages/client/src/widgets/server-metric/net.vue @@ -43,79 +43,71 @@ </div> </template> -<script lang="ts"> -import { defineComponent } from 'vue'; +<script lang="ts" setup> +import { onMounted, onBeforeUnmount } from 'vue'; import bytes from '@/filters/bytes'; -export default defineComponent({ - props: { - connection: { - required: true, - }, - meta: { - required: true, - } - }, - data() { - return { - viewBoxX: 50, - viewBoxY: 30, - stats: [], - inPolylinePoints: '', - outPolylinePoints: '', - inPolygonPoints: '', - outPolygonPoints: '', - inHeadX: null, - inHeadY: null, - outHeadX: null, - outHeadY: null, - inRecent: 0, - outRecent: 0 - }; - }, - mounted() { - this.connection.on('stats', this.onStats); - this.connection.on('statsLog', this.onStatsLog); - this.connection.send('requestLog', { - id: Math.random().toString().substr(2, 8) - }); - }, - beforeUnmount() { - this.connection.off('stats', this.onStats); - this.connection.off('statsLog', this.onStatsLog); - }, - methods: { - onStats(stats) { - this.stats.push(stats); - if (this.stats.length > 50) this.stats.shift(); +const props = defineProps<{ + connection: any, + meta: any +}>(); + +let viewBoxX: number = $ref(50); +let viewBoxY: number = $ref(30); +let stats: any[] = $ref([]); +let inPolylinePoints: string = $ref(''); +let outPolylinePoints: string = $ref(''); +let inPolygonPoints: string = $ref(''); +let outPolygonPoints: string = $ref(''); +let inHeadX: any = $ref(null); +let inHeadY: any = $ref(null); +let outHeadX: any = $ref(null); +let outHeadY: any = $ref(null); +let inRecent: number = $ref(0); +let outRecent: number = $ref(0); + +onMounted(() => { + props.connection.on('stats', onStats); + props.connection.on('statsLog', onStatsLog); + props.connection.send('requestLog', { + id: Math.random().toString().substr(2, 8) + }); +}); - const inPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.rx))); - const outPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.tx))); +onBeforeUnmount(() => { + props.connection.off('stats', onStats); + props.connection.off('statsLog', onStatsLog); +}); - const inPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * this.viewBoxY]); - const outPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * this.viewBoxY]); - this.inPolylinePoints = inPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); - this.outPolylinePoints = outPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); +function onStats(connStats) { + stats.push(connStats); + if (stats.length > 50) stats.shift(); - this.inPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.inPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; - this.outPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.outPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; + const inPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.rx))); + const outPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.tx))); - this.inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0]; - this.inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1]; - this.outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0]; - this.outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1]; + let inPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY]); + let outPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY]); + inPolylinePoints = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); + outPolylinePoints = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' '); - this.inRecent = stats.net.rx; - this.outRecent = stats.net.tx; - }, - onStatsLog(statsLog) { - for (const stats of [...statsLog].reverse()) { - this.onStats(stats); - } - }, - bytes + inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`; + outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`; + + inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0]; + inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1]; + outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0]; + outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1]; + + inRecent = connStats.net.rx; + outRecent = connStats.net.tx; +} + +function onStatsLog(statsLog) { + for (const revStats of [...statsLog].reverse()) { + onStats(revStats); } -}); +} </script> <style lang="scss" scoped> diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue index 7b2e539685..fd78edbe40 100644 --- a/packages/client/src/widgets/slideshow.vue +++ b/packages/client/src/widgets/slideshow.vue @@ -1,5 +1,5 @@ <template> -<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }"> +<div class="kvausudm _panel mkw-slideshow" :style="{ height: widgetProps.height + 'px' }"> <div @click="choose"> <p v-if="widgetProps.folderId == null"> {{ $ts.folder }} @@ -37,7 +37,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -51,7 +51,7 @@ const slideA = ref<HTMLElement>(); const slideB = ref<HTMLElement>(); const change = () => { - if (images.value.length == 0) return; + if (images.value.length === 0) return; const index = Math.floor(Math.random() * images.value.length); const img = `url(${ images.value[index].url })`; diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue index 34e3b20e36..3bcad1ae29 100644 --- a/packages/client/src/widgets/timeline.vue +++ b/packages/client/src/widgets/timeline.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true"> +<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" class="mkw-timeline"> <template #header> <button class="_button" @click="choose"> <i v-if="widgetProps.src === 'home'" class="fas fa-home"></i> @@ -63,7 +63,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure, save } = useWidgetPropsManager(name, widgetPropsDef, @@ -103,19 +103,19 @@ const choose = async (ev) => { os.popupMenu([{ text: i18n.ts._timelines.home, icon: 'fas fa-home', - action: () => { setSrc('home') } + action: () => { setSrc('home'); } }, { text: i18n.ts._timelines.local, icon: 'fas fa-comments', - action: () => { setSrc('local') } + action: () => { setSrc('local'); } }, { text: i18n.ts._timelines.social, icon: 'fas fa-share-alt', - action: () => { setSrc('social') } + action: () => { setSrc('social'); } }, { text: i18n.ts._timelines.global, icon: 'fas fa-globe', - action: () => { setSrc('global') } + action: () => { setSrc('global'); } }, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => { menuOpened.value = false; }); diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue index a34710eae7..9680f1c892 100644 --- a/packages/client/src/widgets/trends.vue +++ b/packages/client/src/widgets/trends.vue @@ -1,5 +1,5 @@ <template> -<MkContainer :show-header="widgetProps.showHeader"> +<MkContainer :show-header="widgetProps.showHeader" class="mkw-trends"> <template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template> <div class="wbrkwala"> @@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>; //const props = defineProps<WidgetComponentProps<WidgetProps>>(); //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>(); const props = defineProps<{ widget?: Widget<WidgetProps>; }>(); -const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>(); +const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>(); const { widgetProps, configure } = useWidgetPropsManager(name, widgetPropsDef, diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts index 81239bfb3b..9626d01619 100644 --- a/packages/client/src/widgets/widget.ts +++ b/packages/client/src/widgets/widget.ts @@ -13,7 +13,7 @@ export type WidgetComponentProps<P extends Record<string, unknown>> = { }; export type WidgetComponentEmits<P extends Record<string, unknown>> = { - (e: 'updateProps', props: P); + (ev: 'updateProps', props: P); }; export type WidgetComponentExpose = { @@ -45,7 +45,7 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default: }, { deep: true, immediate: true, }); const save = throttle(3000, () => { - emit('updateProps', widgetProps) + emit('updateProps', widgetProps); }); const configure = async () => { diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index b44cf2f895..f7320a7251 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -18,6 +18,9 @@ "strictNullChecks": true, "experimentalDecorators": true, "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "isolatedModules": true, + "useDefineForClassFields": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"], @@ -26,14 +29,17 @@ "node_modules/@types", "@types", ], + "types": [ + "vite/client", + ], "lib": [ "esnext", - "dom", - "webworker" + "dom" ] }, "compileOnSave": false, "include": [ + ".eslintrc.js", "./**/*.ts", "./**/*.vue" ] diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts new file mode 100644 index 0000000000..af13e646c6 --- /dev/null +++ b/packages/client/vite.config.ts @@ -0,0 +1,72 @@ +import * as fs from 'fs'; +import pluginVue from '@vitejs/plugin-vue'; +import pluginJson5 from './vite.json5'; +import { defineConfig } from 'vite'; + +import locales from '../../locales'; +import meta from '../../package.json'; + +const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue']; + +export default defineConfig(({ command, mode }) => { + fs.mkdirSync(__dirname + '/../../built', { recursive: true }); + fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8'); + + return { + base: '/assets/', + + plugins: [ + pluginVue({ + reactivityTransform: true, + }), + pluginJson5(), + ], + + resolve: { + extensions, + alias: { + '@/': __dirname + '/src/', + '/client-assets/': __dirname + '/assets/', + '/static-assets/': __dirname + '/../backend/assets/', + }, + }, + + define: { + _VERSION_: JSON.stringify(meta.version), + _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _ENV_: JSON.stringify(process.env.NODE_ENV), + _DEV_: process.env.NODE_ENV !== 'production', + _PERF_PREFIX_: JSON.stringify('Misskey:'), + _DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'), + _DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'), + _DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'), + __VUE_OPTIONS_API__: true, + __VUE_PROD_DEVTOOLS__: false, + }, + + build: { + target: [ + 'chrome100', + 'firefox100', + 'safari15', + ], + manifest: 'manifest.json', + rollupOptions: { + input: { + app: './src/init.ts', + }, + output: { + manualChunks: { + vue: ['vue', 'vue-router'], + }, + }, + }, + cssCodeSplit: true, + outDir: __dirname + '/../../built/_client_dist_', + assetsDir: '.', + emptyOutDir: false, + sourcemap: process.env.NODE_ENV !== 'production', + reportCompressedSize: false, + }, + } +}); diff --git a/packages/client/vite.json5.ts b/packages/client/vite.json5.ts new file mode 100644 index 0000000000..693ee7be06 --- /dev/null +++ b/packages/client/vite.json5.ts @@ -0,0 +1,38 @@ +// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json + +import JSON5 from 'json5'; +import { Plugin } from 'rollup'; +import { createFilter, dataToEsm } from '@rollup/pluginutils'; +import { RollupJsonOptions } from '@rollup/plugin-json'; + +export default function json5(options: RollupJsonOptions = {}): Plugin { + const filter = createFilter(options.include, options.exclude); + const indent = 'indent' in options ? options.indent : '\t'; + + return { + name: 'json5', + + // eslint-disable-next-line no-shadow + transform(json, id) { + if (id.slice(-6) !== '.json5' || !filter(id)) return null; + + try { + const parsed = JSON5.parse(json); + return { + code: dataToEsm(parsed, { + preferConst: options.preferConst, + compact: options.compact, + namedExports: options.namedExports, + indent + }), + map: { mappings: '' } + }; + } catch (err) { + const message = 'Could not parse JSON file'; + const position = parseInt(/[\d]/.exec(err.message)[0], 10); + this.warn({ message, id, position }); + return null; + } + } + }; +} diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js deleted file mode 100644 index a50851e17f..0000000000 --- a/packages/client/webpack.config.js +++ /dev/null @@ -1,193 +0,0 @@ -/** - * webpack configuration - */ - -const fs = require('fs'); -const webpack = require('webpack'); -const { VueLoaderPlugin } = require('vue-loader'); - -class WebpackOnBuildPlugin { - constructor(callback) { - this.callback = callback; - } - - apply(compiler) { - compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback); - } -} - -const isProduction = process.env.NODE_ENV === 'production'; - -const locales = require('../../locales'); -const meta = require('../../package.json'); - -const postcss = { - loader: 'postcss-loader', - options: { - postcssOptions: { - plugins: [ - require('cssnano')({ - preset: 'default' - }) - ] - } - }, -}; - -module.exports = { - entry: { - app: './src/init.ts', - sw: './src/sw/sw.ts' - }, - module: { - rules: [{ - test: /\.vue$/, - exclude: /node_modules/, - use: [{ - loader: 'vue-loader', - options: { - cssSourceMap: false, - reactivityTransform: true, - compilerOptions: { - preserveWhitespace: false - } - } - }] - }, { - test: /\.scss?$/, - exclude: /node_modules/, - oneOf: [{ - resourceQuery: /module/, - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - modules: true, - esModule: false, // TODO: trueにすると壊れる。Vue3移行の折にはtrueにできるかもしれない - url: false, - } - }, postcss, { - loader: 'sass-loader', - options: { - implementation: require('sass'), - sassOptions: { - fiber: false - } - } - }] - }, { - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - url: false, - esModule: false, // TODO: trueにすると壊れる。Vue3移行の折にはtrueにできるかもしれない - } - }, postcss, { - loader: 'sass-loader', - options: { - implementation: require('sass'), - sassOptions: { - fiber: false - } - } - }] - }] - }, { - test: /\.css$/, - oneOf: [{ - resourceQuery: /module/, - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - modules: true, - esModule: false, // TODO: trueにすると壊れる。Vue3移行の折にはtrueにできるかもしれない - } - }, postcss] - }, { - use: [{ - loader: 'vue-style-loader' - }, { - loader: 'css-loader', - options: { - esModule: false, // TODO: trueにすると壊れる。Vue3移行の折にはtrueにできるかもしれない - } - }, postcss] - }] - }, { - test: /\.svg$/, - use: [ - 'vue-loader', - 'vue-svg-loader', - ], - }, { - test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/, - type: 'asset/resource' - }, { - test: /\.json5$/, - loader: 'json5-loader', - options: { - esModule: false, - }, - type: 'javascript/auto' - }, { - test: /\.ts$/, - exclude: /node_modules/, - use: [{ - loader: 'ts-loader', - options: { - happyPackMode: true, - transpileOnly: true, - configFile: __dirname + '/tsconfig.json', - appendTsSuffixTo: [/\.vue$/] - } - }] - }] - }, - plugins: [ - new webpack.ProgressPlugin({}), - new webpack.DefinePlugin({ - _VERSION_: JSON.stringify(meta.version), - _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), - _ENV_: JSON.stringify(process.env.NODE_ENV), - _DEV_: process.env.NODE_ENV !== 'production', - _PERF_PREFIX_: JSON.stringify('Misskey:'), - _DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'), - _DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'), - _DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'), - __VUE_OPTIONS_API__: true, - __VUE_PROD_DEVTOOLS__: false, - }), - new VueLoaderPlugin(), - new WebpackOnBuildPlugin(() => { - fs.mkdirSync(__dirname + '/../../built', { recursive: true }); - fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8'); - }), - ], - output: { - path: __dirname + '/../../built/_client_dist_', - filename: `[name].${meta.version}.js`, - publicPath: `/assets/`, - pathinfo: false, - }, - resolve: { - extensions: [ - '.js', '.ts', '.json' - ], - alias: { - '@': __dirname + '/src/', - } - }, - resolveLoader: { - modules: ['node_modules'] - }, - experiments: { - topLevelAwait: true - }, - devtool: false, //'source-map', - mode: isProduction ? 'production' : 'development' -}; diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index a4ac6d8712..796c72304a 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -2,27 +2,11 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" - integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== - dependencies: - "@babel/highlight" "^7.12.13" - "@babel/helper-validator-identifier@^7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed" integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw== -"@babel/highlight@^7.12.13": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c" - integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww== - dependencies: - "@babel/helper-validator-identifier" "^7.12.11" - chalk "^2.0.0" - js-tokens "^4.0.0" - "@babel/parser@^7.16.4": version "7.16.6" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314" @@ -56,6 +40,105 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@cropper/element-canvas@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-canvas/-/element-canvas-2.0.0-beta.tgz#9501e6a2512a78c7503f2974b1fc65f90c7fecca" + integrity sha512-cKbox0AsUx3pMCjT7mQZx3i5FoZTR/Lzz9awuRR8/EciViMN4KkfodGHWSUrIX3zSr0fECsrb2CyNKV8DKZdpQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-crosshair@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-crosshair/-/element-crosshair-2.0.0-beta.tgz#9d6ee1e6ed90196b6d4d2425f84909b83ffc66df" + integrity sha512-V58xxH3+8TrT9PrUzNouRhcyucyX/xBV5hBv03g0zCu09C5p0BZjrhaPo3hkt8oQvnhYT9SbMTe+k5hIoZgkbQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-grid@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-grid/-/element-grid-2.0.0-beta.tgz#af6f3fce213307403ad83d9935839bde39c9beeb" + integrity sha512-F+qVLrjuHjJbaut1Gd6qSruMqYOHudhDB/r0dcLtnRW4b1yPd/QyhM5F0KLtCX7Lh6GUvpz2V9Vb/EYQLZuOkw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-handle@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-handle/-/element-handle-2.0.0-beta.tgz#bd55667e133df402616d44a694110fd0e61eef0b" + integrity sha512-Ty12mLpiUM8XRGQN0lRNB7TKP5SOXbTWaW2Uvli1Tu3Y6iLTtXUvs2VZ/fGR8XvhB7v7Lvo+OPfzuxIRx4gwKg== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-image@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-image/-/element-image-2.0.0-beta.tgz#170dbdfbeef75de2f2c0089d4739ad980d69390a" + integrity sha512-CrHEMBo5svjj72qePBPGV4ut70RTI6n5U2k2YKcZihHSNU2h6SUEx8zkN8lNIgelsv2Bpb/PvSd1eu26BrJbtA== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-selection@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-selection/-/element-selection-2.0.0-beta.tgz#7e1e498773bc26bb09ddaf09b0cafbe5b359ed7b" + integrity sha512-MEK+pn2Bma5cXf1N9mC3fRKNvzi6Aj9V2TdhaCl6KdOn6Bp10a+SR8y555MXd80zzFAU/eR1e7TMTyJiPRJFcw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-shade@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-shade/-/element-shade-2.0.0-beta.tgz#55400aec3e352d959a706bfff1b82afca955d33e" + integrity sha512-vfKTTkRFio/bi0ueIbdyg2ukhS35/ufsgA13dfzOgkyUT/TUsqTLONNJA2fxO0WLKSajTtvrl1ShdrSXE+EKCQ== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element-viewer@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element-viewer/-/element-viewer-2.0.0-beta.tgz#9a83b670f5cc667d7fc0071f08a1476817e0ed4e" + integrity sha512-ZsqdOWJ8OIrK1JR00ibmYrvVMYQVFXOudXezYtf8C5lc7DdtN4elmjVOfLQQM2kxG0WvflIVo6oqqyOzFnsAFg== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" + +"@cropper/element@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/element/-/element-2.0.0-beta.tgz#7833a92471a16e8860530e10658add42e8781959" + integrity sha512-seS8oDe2+Vpsy+yyqUIHzjIP6WUQRxwhFjLml/s2e+L6jF9o+g0KHzLJkBCV/ASKBnyb00aLjAt0dBXPLW/KgQ== + dependencies: + "@cropper/utils" "^2.0.0-beta" + +"@cropper/elements@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/elements/-/elements-2.0.0-beta.tgz#e73a4edaeff7e41dcca8d096bd1bc2bdc6a376e9" + integrity sha512-Huyptek2Q6141fRiuejhOyec/viX4zmUeMnpi+5h7OBuorTYUowZ823mmfgBZ4bb7+VPdAl79vUECV9EYq/ciw== + dependencies: + "@cropper/element" "^2.0.0-beta" + "@cropper/element-canvas" "^2.0.0-beta" + "@cropper/element-crosshair" "^2.0.0-beta" + "@cropper/element-grid" "^2.0.0-beta" + "@cropper/element-handle" "^2.0.0-beta" + "@cropper/element-image" "^2.0.0-beta" + "@cropper/element-selection" "^2.0.0-beta" + "@cropper/element-shade" "^2.0.0-beta" + "@cropper/element-viewer" "^2.0.0-beta" + +"@cropper/utils@^2.0.0-beta": + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/@cropper/utils/-/utils-2.0.0-beta.tgz#7290b03c8c1dc7a2f33406c8aecc80b339425f0e" + integrity sha512-Bb3hCyHK2w0l0i8OtRw6C9Q5ytUC5qN+l+kx7F3GiAAFZMX7jGyfPB0uLiZ2TwDm5mosnWjyLVXmCGDcTUnYaQ== + "@cypress/request@^2.88.10": version "2.88.10" resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce" @@ -88,34 +171,29 @@ debug "^3.1.0" lodash.once "^4.1.1" -"@discordapp/twemoji@13.1.1": - version "13.1.1" - resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" - integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== +"@discordapp/twemoji@14.0.2": + version "14.0.2" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837" + integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA== dependencies: fs-extra "^8.0.1" jsonfile "^5.0.0" - twemoji-parser "13.1.0" + twemoji-parser "14.0.0" universalify "^0.1.2" -"@discoveryjs/json-ext@^0.5.0": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" - integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg== - -"@eslint/eslintrc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" - integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ== +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.3.1" - globals "^13.9.0" + espree "^9.3.2" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" "@fortawesome/fontawesome-free@6.1.1": @@ -170,6 +248,29 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@rollup/plugin-alias@3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz#a5d267548fe48441f34be8323fb64d1d4a1b3fdf" + integrity sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw== + dependencies: + slash "^3.0.0" + +"@rollup/plugin-json@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" + integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + +"@rollup/pluginutils@^3.0.8": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@sideway/address@^4.1.0": version "4.1.2" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1" @@ -198,16 +299,6 @@ stringz "2.1.0" uuid "7.0.3" -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/anymatch@*": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a" - integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA== - "@types/color-name@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" @@ -218,39 +309,10 @@ resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e" integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw== -"@types/eslint-scope@^3.7.0": - version "3.7.0" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" - integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint-scope@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "7.2.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.0.tgz#eb5c5b575237334df24c53195e37b53d66478d7b" - integrity sha512-LpUXkr7fnmPXWGxB0ZuLEzNeTURuHPavkC5zuU4sg62/TgL5ZEjamr5Y8b6AftwHtx2bPJasI+CL0TT2JwQ7aA== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^0.0.46": - version "0.0.46" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" - integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== - -"@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== "@types/events@*": version "3.0.0" @@ -309,21 +371,6 @@ resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a" integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw== -"@types/json-schema@*": - version "7.0.5" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd" - integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ== - -"@types/json-schema@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" - integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== - -"@types/json-schema@^7.0.7": - version "7.0.8" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" - integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== - "@types/json-schema@^7.0.9": version "7.0.9" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" @@ -349,10 +396,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/mocha@9.1.0": - version "9.1.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5" - integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg== +"@types/mocha@9.1.1": + version "9.1.1" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4" + integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw== "@types/node@*": version "16.6.2" @@ -371,26 +418,11 @@ dependencies: "@types/node" "*" -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/parse5@6.0.3": - version "6.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" - integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== - "@types/punycode@2.1.0": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83" integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g== -"@types/q@^1.5.1": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" - integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== - "@types/qrcode@1.4.2": version "1.4.2" resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" @@ -418,33 +450,16 @@ resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef" integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ== -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@^1": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4" - integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ== - -"@types/throttle-debounce@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776" - integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ== +"@types/throttle-debounce@5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz#8208087f0af85107bcc681c50fa837fc9505483e" + integrity sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw== "@types/tinycolor2@1.4.3": version "1.4.3" resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706" integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ== -"@types/uglify-js@*": - version "3.9.0" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.0.tgz#4490a140ca82aa855ad68093829e7fd6ae94ea87" - integrity sha512-3ZcoyPYHVOCcLpnfZwD47KFLr8W/mpUcgjpf1M4Q78TMJIw7KMAHSjiCLJp1z3ZrBR9pTLbe191O0TldFK5zcw== - dependencies: - source-map "^0.6.1" - "@types/undertaker-registry@*": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz#4306d4a03d7acedb974b66530832b90729e1d1da" @@ -479,44 +494,6 @@ "@types/expect" "^1.20.4" "@types/node" "*" -"@types/webpack-sources@*": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141" - integrity sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.6.1" - -"@types/webpack-stream@3.2.12": - version "3.2.12" - resolved "https://registry.yarnpkg.com/@types/webpack-stream/-/webpack-stream-3.2.12.tgz#cf13e64067a662a7acd8cd0524b3f64c86b0ecb6" - integrity sha512-znMUl4kKT0V0SwkUgRgwUNSAO7J5I/jdTCBNy3utkCsgMJ3IHp4FBTDwsQC+tfQ73TWeKIH05QNmbUYmeGThGw== - dependencies: - "@types/node" "*" - "@types/webpack" "^4" - -"@types/webpack@5.28.0": - version "5.28.0" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03" - integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w== - dependencies: - "@types/node" "*" - tapable "^2.2.0" - webpack "^5" - -"@types/webpack@^4": - version "4.41.27" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.27.tgz#f47da488c8037e7f1b2dbf2714fbbacb61ec0ffc" - integrity sha512-wK/oi5gcHi72VMTbOaQ70VcDxSQ1uX8S2tukBK9ARuGXrYM/+u4ou73roc7trXDNmCxCoerE8zruQqX/wuHszA== - dependencies: - "@types/anymatch" "*" - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - source-map "^0.6.0" - "@types/websocket@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" @@ -538,454 +515,190 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d" - integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A== +"@typescript-eslint/eslint-plugin@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758" + integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/type-utils" "5.18.0" - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/type-utils" "5.27.1" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" functional-red-black-tree "^1.0.1" - ignore "^5.1.8" + ignore "^5.2.0" regexpp "^3.2.0" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6" - integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ== +"@typescript-eslint/parser@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639" + integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ== dependencies: - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505" - integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ== +"@typescript-eslint/scope-manager@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d" + integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" -"@typescript-eslint/type-utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74" - integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA== +"@typescript-eslint/type-utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166" + integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw== dependencies: - "@typescript-eslint/utils" "5.18.0" - debug "^4.3.2" + "@typescript-eslint/utils" "5.27.1" + debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e" - integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw== +"@typescript-eslint/types@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1" + integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg== -"@typescript-eslint/typescript-estree@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474" - integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ== +"@typescript-eslint/typescript-estree@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808" + integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw== dependencies: - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/visitor-keys" "5.18.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/visitor-keys" "5.27.1" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" + semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855" - integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA== +"@typescript-eslint/utils@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f" + integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w== dependencies: "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.18.0" - "@typescript-eslint/types" "5.18.0" - "@typescript-eslint/typescript-estree" "5.18.0" + "@typescript-eslint/scope-manager" "5.27.1" + "@typescript-eslint/types" "5.27.1" + "@typescript-eslint/typescript-estree" "5.27.1" eslint-scope "^5.1.1" eslint-utils "^3.0.0" -"@typescript-eslint/visitor-keys@5.18.0": - version "5.18.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60" - integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg== +"@typescript-eslint/visitor-keys@5.27.1": + version "5.27.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af" + integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ== dependencies: - "@typescript-eslint/types" "5.18.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "5.27.1" + eslint-visitor-keys "^3.3.0" "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== -"@vue/compiler-core@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89" - integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ== +"@vitejs/plugin-vue@2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz#fbf80cc039b82ac21a1acb0f0478de8f61fbf600" + integrity sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw== + +"@vue/compiler-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a" + integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg== dependencies: "@babel/parser" "^7.16.4" - "@vue/shared" "3.2.31" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" source-map "^0.6.1" -"@vue/compiler-dom@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e" - integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg== +"@vue/compiler-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5" + integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ== dependencies: - "@vue/compiler-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" -"@vue/compiler-sfc@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f" - integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ== +"@vue/compiler-sfc@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4" + integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.31" - "@vue/compiler-dom" "3.2.31" - "@vue/compiler-ssr" "3.2.31" - "@vue/reactivity-transform" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-ssr" "3.2.37" + "@vue/reactivity-transform" "3.2.37" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" magic-string "^0.25.7" postcss "^8.1.10" source-map "^0.6.1" -"@vue/compiler-ssr@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c" - integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw== +"@vue/compiler-ssr@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff" + integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw== dependencies: - "@vue/compiler-dom" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-dom" "3.2.37" + "@vue/shared" "3.2.37" "@vue/devtools-api@^6.0.0": version "6.0.12" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.12.tgz#7b57cce215ae9f37a86984633b3aa3d595aa5b46" integrity sha512-iO/4FIezHKXhiDBdKySCvJVh8/mZPxHpiQrTy+PXVqJZgpTPTdHy4q8GXulaY+UKEagdkBb0onxNQZ0LNiqVhw== -"@vue/reactivity-transform@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911" - integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA== +"@vue/reactivity-transform@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca" + integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg== dependencies: "@babel/parser" "^7.16.4" - "@vue/compiler-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-core" "3.2.37" + "@vue/shared" "3.2.37" estree-walker "^2.0.2" magic-string "^0.25.7" -"@vue/reactivity@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd" - integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw== +"@vue/reactivity@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848" + integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A== dependencies: - "@vue/shared" "3.2.31" + "@vue/shared" "3.2.37" -"@vue/runtime-core@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a" - integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA== +"@vue/runtime-core@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3" + integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ== dependencies: - "@vue/reactivity" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/reactivity" "3.2.37" + "@vue/shared" "3.2.37" -"@vue/runtime-dom@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff" - integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g== +"@vue/runtime-dom@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd" + integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw== dependencies: - "@vue/runtime-core" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/runtime-core" "3.2.37" + "@vue/shared" "3.2.37" csstype "^2.6.8" -"@vue/server-renderer@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141" - integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg== - dependencies: - "@vue/compiler-ssr" "3.2.31" - "@vue/shared" "3.2.31" - -"@vue/shared@3.2.31": - version "3.2.31" - resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e" - integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ== - -"@webassemblyjs/ast@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f" - integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg== +"@vue/server-renderer@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc" + integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA== dependencies: - "@webassemblyjs/helper-numbers" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" + "@vue/compiler-ssr" "3.2.37" + "@vue/shared" "3.2.37" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c" - integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA== - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4" - integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642" - integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9" - integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1" - integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA== - -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== - -"@webassemblyjs/helper-wasm-section@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b" - integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - -"@webassemblyjs/ieee754@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf" - integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b" - integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf" - integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw== - -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78" - integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/helper-wasm-section" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-opt" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - "@webassemblyjs/wast-printer" "1.11.0" - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe" - integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978" - integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-buffer" "1.11.0" - "@webassemblyjs/wasm-gen" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754" - integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/helper-api-error" "1.11.0" - "@webassemblyjs/helper-wasm-bytecode" "1.11.0" - "@webassemblyjs/ieee754" "1.11.0" - "@webassemblyjs/leb128" "1.11.0" - "@webassemblyjs/utf8" "1.11.0" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.0": - version "1.11.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e" - integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ== - dependencies: - "@webassemblyjs/ast" "1.11.0" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^1.1.1": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356" - integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg== - -"@webpack-cli/info@^1.4.1": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea" - integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA== - dependencies: - envinfo "^7.7.3" - -"@webpack-cli/serve@^1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe" - integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@vue/shared@3.2.37": + version "3.2.37" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702" + integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw== abort-controller@3.0.0: version "3.0.0" @@ -994,40 +707,20 @@ abort-controller@3.0.0: dependencies: event-target-shim "^5.0.0" -acorn-import-assertions@^1.7.6: - version "1.7.6" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78" - integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA== - -acorn-jsx@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" - integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^7.1.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4: - version "8.1.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" - integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== - -acorn@^8.4.1: - version "8.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c" - integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA== - -acorn@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== - -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== +acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== aggregate-error@^3.0.0: version "3.1.0" @@ -1037,12 +730,7 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.4: version "6.12.5" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da" integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag== @@ -1069,13 +757,6 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" @@ -1097,13 +778,6 @@ arch@^2.2.0: resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1161,17 +835,10 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async@^2.6.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" - integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== - dependencies: - lodash "^4.17.14" - async@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8" - integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg== + version "3.2.3" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" + integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== asynckit@^0.4.0: version "0.4.0" @@ -1241,16 +908,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -big-integer@^1.6.16: - version "1.6.48" - resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e" - integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w== - -big.js@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" - integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== - binary-extensions@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" @@ -1271,7 +928,7 @@ blurhash@1.1.5: resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.5.tgz#3034104cd5dce5a3e5caa871ae2f0f1f2d0ab566" integrity sha512-a+LO3A2DfxTaTztsmkbLYmUzUeApi0LZuKalwbNmqAHR6HhJGMt1qSV/R3wc+w4DL28holjqO3Bg74aUGavGjg== -boolbase@^1.0.0, boolbase@~1.0.0: +boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= @@ -1284,53 +941,47 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1, braces@~3.0.2: +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -broadcast-channel@4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198" - integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ== +broadcast-channel@4.13.0: + version "4.13.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.13.0.tgz#21387b2602b9e9ec3b97b03bd8a8d2c198352ff6" + integrity sha512-fcDr8QNJ4SOb6jyjUNZatVNmcHtSWfW4PFcs4xIEFZAtorKCIFoEYtjIjaQ4c0jrbr/Bl8NIwOWiLSyspoAnEQ== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" - microseconds "0.2.0" - nano-time "1.0.0" - oblivious-set "1.0.0" + microtime "3.0.0" + oblivious-set "1.1.1" p-queue "6.6.2" rimraf "3.0.2" unload "2.3.1" +"browser-image-resizer@git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2": + version "2.2.1-misskey.2" + resolved "git+https://github.com/misskey-dev/browser-image-resizer#a58834f5fe2af9f9f31ff115121aef3de6f9d416" + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6: - version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" - integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== - dependencies: - caniuse-lite "^1.0.30001286" - electron-to-chromium "^1.4.17" - escalade "^3.1.1" - node-releases "^2.0.1" - picocolors "^1.0.0" - buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1374,21 +1025,6 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286: - version "1.0.30001311" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz#682ef3f4e617f1a177ad943de59775ed3032e511" - integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A== - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -1402,15 +1038,6 @@ chalk@4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^2.0.0, chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -1431,20 +1058,20 @@ character-parser@^2.2.0: dependencies: is-regex "^1.0.3" -chart.js@3.7.1: - version "3.7.1" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada" - integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA== +chart.js@3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94" + integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg== chartjs-adapter-date-fns@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b" integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw== -chartjs-plugin-gradient@0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.2.2.tgz#e271d8cbaa9cb52581addc99f2facc4adae40e43" - integrity sha512-fb38h1Zl5DDkHvpempZ/rY/lWg9/dgF0I56GI1dbqRj5P/ZoHSX4hx+P+5Az/JMNZ1s1/2zo5TmTTHQo8xJUXQ== +chartjs-plugin-gradient@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.5.0.tgz#907b15102ce164fc32640d43f9c3bad2f5ae3fd5" + integrity sha512-VHys58pMPNYRXngCrN5kvQZb1EiAvl/BhU3G9wNXxf2hETWiPYgN63Ud6RK1hyST+nZdZ61x4us546djZX2rYQ== chartjs-plugin-zoom@1.2.1: version "1.2.1" @@ -1458,7 +1085,7 @@ check-more-types@2.24.0, check-more-types@^2.24.0: resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= -chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2: +chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.3: version "3.3.1" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -1473,13 +1100,6 @@ chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2: optionalDependencies: fsevents "~2.1.2" -chrome-trace-event@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" - integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== - dependencies: - tslib "^1.9.0" - ci-info@^3.1.1: version "3.2.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" @@ -1532,31 +1152,6 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -coa@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" - integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== - dependencies: - "@types/q" "^1.5.1" - chalk "^2.4.1" - q "^1.1.2" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -1564,31 +1159,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -colord@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e" - integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw== - colorette@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== -colorette@^2.0.14: - version "2.0.15" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5" - integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA== - colors@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -1601,31 +1181,21 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commander@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2" - integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commander@^8.0.0, commander@^8.3.0: +commander@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== +commander@^9.0.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" + integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== + common-tags@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937" @@ -1661,16 +1231,13 @@ core-util-is@1.0.2: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= -cosmiconfig@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" - integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== +cropperjs@2.0.0-beta: + version "2.0.0-beta" + resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-2.0.0-beta.tgz#bf3f9c19c426657d63c1e6dd55f635546ccec0a5" + integrity sha512-mwupI1Ct84PUynnC9S7KenCtgXiuRYAfLwzxPlJwc392iNX8fZUPP6a8gEpmRQTgvsE9Ubme1tXLM6/HLXksiQ== dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" + "@cropper/elements" "^2.0.0-beta" + "@cropper/utils" "^2.0.0-beta" cross-env@7.0.3: version "7.0.3" @@ -1688,162 +1255,20 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -css-declaration-sorter@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02" - integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg== - -css-loader@6.7.1: - version "6.7.1" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" - integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== - dependencies: - icss-utils "^5.1.0" - postcss "^8.4.7" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - semver "^7.3.5" - -css-select-base-adapter@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" - integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== - -css-select@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" - integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ== - dependencies: - boolbase "^1.0.0" - css-what "^3.2.1" - domutils "^1.7.0" - nth-check "^1.0.2" - -css-select@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" - integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== - dependencies: - boolbase "^1.0.0" - css-what "^5.0.0" - domhandler "^4.2.0" - domutils "^2.6.0" - nth-check "^2.0.0" - -css-tree@1.0.0-alpha.37: - version "1.0.0-alpha.37" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" - integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== - dependencies: - mdn-data "2.0.4" - source-map "^0.6.1" - -css-tree@1.0.0-alpha.39: - version "1.0.0-alpha.39" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" - integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== - dependencies: - mdn-data "2.0.6" - source-map "^0.6.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" - integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== - -css-what@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -cssnano-preset-default@^5.2.7: - version "5.2.7" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.7.tgz#791e3603fb8f1b46717ac53b47e3c418e950f5f3" - integrity sha512-JiKP38ymZQK+zVKevphPzNSGHSlTI+AOwlasoSRtSVMUU285O7/6uZyd5NbW92ZHp41m0sSHe6JoZosakj63uA== - dependencies: - css-declaration-sorter "^6.2.2" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.0" - postcss-discard-comments "^5.1.1" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.4" - postcss-merge-rules "^5.1.1" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.2" - postcss-minify-selectors "^5.2.0" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.0" - postcss-normalize-repeat-style "^5.1.0" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.0" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.1" - postcss-reduce-initial "^5.1.0" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@5.1.7: - version "5.1.7" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.7.tgz#99858bef6c76c9240f0cdc9239570bc7db8368be" - integrity sha512-pVsUV6LcTXif7lvKKW9ZrmX+rGRzxkEdJuVJcp5ftUjWITgwam5LMZOgaTvUrWPkcORBey6he7JKb4XAJvrpKg== - dependencies: - cssnano-preset-default "^5.2.7" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903" - integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ== - dependencies: - css-tree "1.0.0-alpha.39" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - csstype@^2.6.8: version "2.6.13" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== -cypress@9.5.3: - version "9.5.3" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e" - integrity sha512-ItelIVmqMTnKYbo1JrErhsGgQGjWOxCpHT1TfMvwnIXKXN/OSlPjEK7rbCLYDZhejQL99PmUqul7XORI24Ik0A== +cypress@10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.0.3.tgz#889b4bef863b7d1ef1b608b85b964394ad350c5f" + integrity sha512-8C82XTybsEmJC9POYSNITGUhMLCRwB9LadP0x33H+52QVoBjhsWvIzrI+ybCe0+TyxaF0D5/9IL2kSTgjqCB9A== dependencies: "@cypress/request" "^2.88.10" "@cypress/xvfb" "^1.2.4" @@ -1920,10 +1345,10 @@ debug@4.3.2, debug@^4.3.2: dependencies: ms "2.1.2" -debug@4.3.3: - version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" - integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== +debug@4.3.4, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" @@ -1963,7 +1388,7 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -define-properties@^1.1.2, define-properties@^1.1.3: +define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== @@ -1975,11 +1400,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - detect-node@2.1.0, detect-node@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -2021,69 +1441,6 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -dom-serializer@0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" - integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== - dependencies: - domelementtype "^2.0.1" - entities "^2.0.0" - -dom-serializer@^1.0.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be" - integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.0.0" - entities "^2.0.0" - -domelementtype@1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== - -domelementtype@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d" - integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ== - -domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== - -domhandler@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43" - integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ== - dependencies: - domelementtype "^2.2.0" - -domhandler@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" - integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== - dependencies: - domelementtype "^2.2.0" - -domutils@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" - integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^2.6.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -2097,21 +1454,11 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -electron-to-chromium@^1.4.17: - version "1.4.68" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz#d79447b6bd1bec9183f166bb33d4bef0d5e4e568" - integrity sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - encode-utf8@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" @@ -2124,30 +1471,6 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.0.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz#d9deae58f9d3773b6a111a5a46831da5be5c9ac0" - integrity sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^5.7.0: - version "5.7.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c" - integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -enhanced-resolve@^5.9.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" - integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -2155,40 +1478,6 @@ enquirer@^2.3.6: dependencies: ansi-colors "^4.1.1" -entities@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4" - integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw== - -envinfo@^7.7.3: - version "7.7.3" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc" - integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5: - version "1.17.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" - integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== - dependencies: - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - is-callable "^1.1.5" - is-regex "^1.0.5" - object-inspect "^1.7.0" - object-keys "^1.1.1" - object.assign "^4.1.0" - string.prototype.trimleft "^2.1.1" - string.prototype.trimright "^2.1.1" - es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -2215,16 +1504,6 @@ es-abstract@^1.19.0, es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" -es-module-lexer@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.0.tgz#21f4181cc8b7eee06855f1c59e6087c7bc4f77b0" - integrity sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ== - -es-module-lexer@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.0.tgz#fe4c4621977bc668e285c5f1f70ca3b451095fda" - integrity sha512-qU2eN/XHsrl3E4y7mK1wdWnyy5c8gXtCbfP6Xcsemm7fPUR1PIV1JhZfP7ojcN0Fzp69CfrS3u76h2tusvfKiQ== - es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -2260,6 +1539,132 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +esbuild-android-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64" + integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw== + +esbuild-android-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8" + integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA== + +esbuild-darwin-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46" + integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA== + +esbuild-darwin-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9" + integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ== + +esbuild-freebsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e" + integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig== + +esbuild-freebsd-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6" + integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ== + +esbuild-linux-32@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70" + integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g== + +esbuild-linux-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519" + integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q== + +esbuild-linux-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a" + integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA== + +esbuild-linux-arm@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986" + integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA== + +esbuild-linux-mips64le@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5" + integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ== + +esbuild-linux-ppc64le@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47" + integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q== + +esbuild-linux-riscv64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2" + integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ== + +esbuild-linux-s390x@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0" + integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ== + +esbuild-netbsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95" + integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q== + +esbuild-openbsd-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd" + integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ== + +esbuild-sunos-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b" + integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA== + +esbuild-windows-32@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1" + integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw== + +esbuild-windows-64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107" + integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw== + +esbuild-windows-arm64@0.14.38: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54" + integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw== + +esbuild@^0.14.27: + version "0.14.38" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.38.tgz#99526b778cd9f35532955e26e1709a16cca2fb30" + integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA== + optionalDependencies: + esbuild-android-64 "0.14.38" + esbuild-android-arm64 "0.14.38" + esbuild-darwin-64 "0.14.38" + esbuild-darwin-arm64 "0.14.38" + esbuild-freebsd-64 "0.14.38" + esbuild-freebsd-arm64 "0.14.38" + esbuild-linux-32 "0.14.38" + esbuild-linux-64 "0.14.38" + esbuild-linux-arm "0.14.38" + esbuild-linux-arm64 "0.14.38" + esbuild-linux-mips64le "0.14.38" + esbuild-linux-ppc64le "0.14.38" + esbuild-linux-riscv64 "0.14.38" + esbuild-linux-s390x "0.14.38" + esbuild-netbsd-64 "0.14.38" + esbuild-openbsd-64 "0.14.38" + esbuild-sunos-64 "0.14.38" + esbuild-windows-32 "0.14.38" + esbuild-windows-64 "0.14.38" + esbuild-windows-arm64 "0.14.38" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -2315,17 +1720,20 @@ eslint-plugin-import@2.26.0: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-vue@8.6.0: - version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.6.0.tgz#fbdf0f13f8d208a4cba752bf54042661a1aec5c3" - integrity sha512-abXiF2J18n/7ZPy9foSlJyouKf54IqpKlNvNmzhM93N0zs3QUxZG/oBd3tVPOJTKg7SlhBUtPxugpqzNbgGpQQ== +eslint-plugin-vue@9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.1.0.tgz#b528941325e26a24bc5d5c5030c0a8996c36659c" + integrity sha512-EPCeInPicQ/YyfOWJDr1yfEeSNoFCMzUus107lZyYi37xejdOolNzS5MXGXp8+9bkoKZMdv/1AcZzQebME6r+g== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" + nth-check "^2.0.1" + postcss-selector-parser "^6.0.9" semver "^7.3.5" - vue-eslint-parser "^8.0.1" + vue-eslint-parser "^9.0.1" + xml-name-validator "^4.0.0" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -2333,14 +1741,6 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978" - integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" @@ -2361,22 +1761,17 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint-visitor-keys@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" - integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@8.13.0: - version "8.13.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7" - integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ== +eslint@8.17.0: + version "8.17.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21" + integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw== dependencies: - "@eslint/eslintrc" "^1.2.1" + "@eslint/eslintrc" "^1.3.0" "@humanwhocodes/config-array" "^0.9.2" ajv "^6.10.0" chalk "^4.0.0" @@ -2387,14 +1782,14 @@ eslint@8.13.0: eslint-scope "^7.1.1" eslint-utils "^3.0.0" eslint-visitor-keys "^3.3.0" - espree "^9.3.1" + espree "^9.3.2" esquery "^1.4.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" functional-red-black-tree "^1.0.1" glob-parent "^6.0.1" - globals "^13.6.0" + globals "^13.15.0" ignore "^5.2.0" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -2403,7 +1798,7 @@ eslint@8.13.0: json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" optionator "^0.9.1" regexpp "^3.2.0" @@ -2412,29 +1807,15 @@ eslint@8.13.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090" - integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ== +espree@^9.3.1, espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== dependencies: - acorn "^8.5.0" - acorn-jsx "^5.3.1" - eslint-visitor-keys "^3.0.0" - -espree@^9.3.1: - version "9.3.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd" - integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ== - dependencies: - acorn "^8.7.0" - acorn-jsx "^5.3.1" + acorn "^8.7.1" + acorn-jsx "^5.3.2" eslint-visitor-keys "^3.3.0" -esprima@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -2464,6 +1845,11 @@ estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + estree-walker@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" @@ -2502,11 +1888,6 @@ eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" - integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== - execa@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" @@ -2537,21 +1918,6 @@ execa@5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376" - integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -2559,13 +1925,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - ext@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" @@ -2621,6 +1980,17 @@ fast-glob@^3.1.1: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -2631,11 +2001,6 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fastest-levenshtein@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" - integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== - fastq@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481" @@ -2678,14 +2043,6 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" -find-node-modules@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" - integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== - dependencies: - findup-sync "^4.0.0" - merge "^2.1.0" - find-up@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" @@ -2701,7 +2058,7 @@ find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -2709,16 +2066,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -2790,6 +2137,11 @@ fsevents@~2.1.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e" integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -2848,7 +2200,7 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob-parent@^5.1.0, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2862,11 +2214,6 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - glob@7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -2898,37 +2245,10 @@ global-dirs@^3.0.0: dependencies: ini "2.0.0" -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^13.6.0: - version "13.7.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795" - integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA== - dependencies: - type-fest "^0.20.2" - -globals@^13.9.0: - version "13.9.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb" - integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA== +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== dependencies: type-fest "^0.20.2" @@ -2944,7 +2264,19 @@ globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4: +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graceful-fs@^4.1.6: version "4.2.4" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== @@ -2954,16 +2286,6 @@ graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== -graceful-fs@^4.2.9: - version "4.2.9" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" - integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== - -growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== - hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" @@ -2974,17 +2296,12 @@ has-bigints@^1.0.1: resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.0, has-symbols@^1.0.1: +has-symbols@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== @@ -3008,28 +2325,11 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash-sum@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" - integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ= - -hash-sum@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" - integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== - he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - http-signature@~1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9" @@ -3049,11 +2349,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - idb-keyval@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" @@ -3071,11 +2366,6 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== -ignore@^5.1.8: - version "5.1.9" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb" - integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ== - ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -3094,14 +2384,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1: parent-module "^1.0.0" resolve-from "^4.0.0" -import-local@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6" - integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" @@ -3112,11 +2394,6 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indexes-of@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" - integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -3135,11 +2412,6 @@ ini@2.0.0: resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== -ini@^1.3.4: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== - insert-text-at-cursor@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da" @@ -3154,27 +2426,6 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" -interpret@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" - integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== - -ip-address@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-7.1.0.tgz#4a9c699e75b51cbeb18b38de8ed216efa1a490c5" - integrity sha512-V9pWC/VJf2lsXqP7IWJ+pe3P1/HCYGBMZrrnT62niLGjAfCbeiwXMUxaeHvnVlz19O27pvXP4azs+Pj/A0x+SQ== - dependencies: - jsbn "1.1.0" - sprintf-js "1.1.2" - -ip-cidr@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375" - integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ== - dependencies: - ip-address "^7.1.0" - jsbn "^1.1.0" - ip-regex@^4.0.0, ip-regex@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" @@ -3185,11 +2436,6 @@ ipaddr.js@^2.0.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -3212,7 +2458,7 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-callable@^1.1.4, is-callable@^1.1.5: +is-callable@^1.1.4: version "1.1.5" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== @@ -3229,13 +2475,6 @@ is-ci@^3.0.0: dependencies: ci-info "^3.1.1" -is-core-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d" - integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw== - dependencies: - has "^1.0.3" - is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -3300,13 +2539,6 @@ is-negative-zero@^2.0.1: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== -is-number-like@^1.0.3: - version "1.0.8" - resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3" - integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA== - dependencies: - lodash.isfinite "^3.3.2" - is-number-object@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0" @@ -3329,19 +2561,12 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-promise@^2.0.0: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.0.3, is-regex@^1.0.5: +is-regex@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== @@ -3404,44 +2629,16 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.0" -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^27.0.2: - version "27.0.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed" - integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - joi@^17.4.0: version "17.4.2" resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7" @@ -3458,11 +2655,6 @@ js-stringify@^1.0.2: resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db" integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds= -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" @@ -3470,34 +2662,11 @@ js-yaml@4.1.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@1.1.0, jsbn@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA= - jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -3518,16 +2687,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5-loader@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/json5-loader/-/json5-loader-4.0.1.tgz#6d17a1181e8f3c3d9204dca2a4ce4627306c8498" - integrity sha512-c9viNZlZTz0MTIcf/4qvek5Dz1/PU3DNCB4PwUhlEZIV3qb1bSD6vQQymlV17/Wm6ncra1aCvmIPsuRj+KfEEg== - dependencies: - json5 "^2.1.3" - loader-utils "^2.0.0" - schema-utils "^3.0.0" - -json5@2.2.1: +json5@2.2.1, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -3539,13 +2699,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== - dependencies: - minimist "^1.2.5" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -3589,28 +2742,13 @@ jstransformer@1.0.0: is-promise "^2.0.0" promise "^7.0.1" -katex@0.15.3: - version "0.15.3" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.3.tgz#08781a7ed26800b20380d959d1ffcd62bca0ec14" - integrity sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ== +katex@0.15.6: + version "0.15.6" + resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.6.tgz#c4e2f6ced2ac4de1ef6f737fe7c67d3026baa0e5" + integrity sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA== dependencies: commander "^8.0.0" -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== - -klona@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" - integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== - lazy-ass@1.6.0, lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" @@ -3624,16 +2762,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -lilconfig@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd" - integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg== - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - listr2@^3.8.3: version "3.11.0" resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.0.tgz#9771b02407875aa78e73d6e0ff6541bbec0aaee9" @@ -3647,29 +2775,6 @@ listr2@^3.8.3: through "^2.3.8" wrap-ansi "^7.0.0" -loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== - -loader-utils@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" - integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - -loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^2.1.2" - locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -3692,16 +2797,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.isfinite@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3" - integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M= - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -3712,12 +2807,7 @@ lodash.once@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - -lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21: +lodash@^4.17.19, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3764,44 +2854,24 @@ matter-js@0.18.0: resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.18.0.tgz#083ced04eb6768f7664dc7ca8948a10e46ad3ed6" integrity sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg== -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdn-data@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" - integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== - -mdn-data@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" - integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== - merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0: +merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merge@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - -mfm-js@0.21.0: - version "0.21.0" - resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772" - integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA== +mfm-js@0.22.1: + version "0.22.1" + resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2" + integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ== dependencies: - twemoji-parser "13.1.x" + twemoji-parser "14.0.x" -micromatch@^4.0.0, micromatch@^4.0.2: +micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -3809,17 +2879,28 @@ micromatch@^4.0.0, micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" -microseconds@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" - integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +microtime@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/microtime/-/microtime-3.0.0.tgz#d140914bde88aa89b4f9fd2a18620b435af0f39b" + integrity sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ== + dependencies: + node-addon-api "^1.2.0" + node-gyp-build "^3.8.0" mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19: +mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -3831,12 +2912,12 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4" - integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g== +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: - brace-expansion "^1.1.7" + brace-expansion "^2.0.1" minimatch@^3.0.4: version "3.0.4" @@ -3866,39 +2947,30 @@ misskey-js@0.0.14: eventemitter3 "^4.0.7" reconnecting-websocket "^4.4.0" -mkdirp@~0.5.1: - version "0.5.5" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" - integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== - dependencies: - minimist "^1.2.5" - -mocha@9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9" - integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g== +mocha@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9" + integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA== dependencies: "@ungap/promise-all-settled" "1.1.2" ansi-colors "4.1.1" browser-stdout "1.3.1" chokidar "3.5.3" - debug "4.3.3" + debug "4.3.4" diff "5.0.0" escape-string-regexp "4.0.0" find-up "5.0.0" glob "7.2.0" - growl "1.10.5" he "1.2.0" js-yaml "4.1.0" log-symbols "4.1.0" - minimatch "4.2.1" + minimatch "5.0.1" ms "2.1.3" - nanoid "3.3.1" + nanoid "3.3.3" serialize-javascript "6.0.0" strip-json-comments "3.1.1" supports-color "8.1.1" - which "2.0.2" - workerpool "6.2.0" + workerpool "6.2.1" yargs "16.2.0" yargs-parser "20.2.4" yargs-unparser "2.0.0" @@ -3918,33 +2990,21 @@ ms@2.1.3, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -mylas@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800" - integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ== +mylas@^2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc" + integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg== -nano-time@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" - integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8= - dependencies: - big-integer "^1.6.16" - -nanoid@3.3.1, nanoid@^3.1.20, nanoid@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" - integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== +nanoid@3.3.3, nanoid@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - nested-property@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-4.0.0.tgz#a67b5a31991e701e03cdbaa6453bc5b1011bb88d" @@ -3960,26 +3020,26 @@ next-tick@~1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= +node-addon-api@^1.2.0: + version "1.7.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" + integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== + +node-gyp-build@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" + integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== + node-gyp-build@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== -node-releases@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" - integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== - normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^4.0.0, npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -3987,17 +3047,10 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -nth-check@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" - integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== - dependencies: - boolbase "~1.0.0" - -nth-check@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125" - integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q== +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== dependencies: boolbase "^1.0.0" @@ -4016,26 +3069,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" - integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== - -object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: +object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -4046,24 +3084,6 @@ object.assign@^4.1.2: has-symbols "^1.0.1" object-keys "^1.1.1" -object.getownpropertydescriptors@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" - integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -object.values@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" - integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - function-bind "^1.1.1" - has "^1.0.3" - object.values@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" @@ -4073,10 +3093,10 @@ object.values@^1.1.5: define-properties "^1.1.3" es-abstract "^1.19.1" -oblivious-set@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" - integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== +oblivious-set@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.1.1.tgz#d9d38e9491d51f27a5c3ec1681d2ba40aa81e98b" + integrity sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w== once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" @@ -4135,13 +3155,6 @@ p-limit@^3.0.2: dependencies: p-try "^2.0.0" -p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -4202,26 +3215,6 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646" - integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -4269,10 +3262,10 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -photoswipe@5.2.4: - version "5.2.4" - resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.4.tgz#918fd64c6b41b6a693743e5d70ee1a59747f491d" - integrity sha512-7p+VH7ELUnW9/3rCULCmyXcUCEuZwcsxvxPQYzR4wk3EaXcLCiINMCspc9Qq9AJYNsqYo1qGVL1y1Tch8uKAjw== +photoswipe@5.2.7: + version "5.2.7" + resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.7.tgz#9ff2aaf2a3e03c817ac2835dc6dee0f901e8159d" + integrity sha512-AogMba7W/O5gOtDIZ8cQuou1ltwxlaLNoZY1qi1s+kbYXpZk9D6rXxnNGAfDppl+bfe+sKLW2w2sx+3uQ8oPzg== picocolors@^1.0.0: version "1.0.0" @@ -4284,267 +3277,28 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.2.2, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== +plimit-lit@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3" + integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw== dependencies: - find-up "^4.0.0" + queue-lit "^1.2.7" pngjs@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== -portscanner@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1" - integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw== - dependencies: - async "^2.6.0" - is-number-like "^1.0.3" - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10" - integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369" - integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-loader@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" - integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== - dependencies: - cosmiconfig "^7.0.0" - klona "^2.0.5" - semver "^7.3.5" - -postcss-merge-longhand@^5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.4.tgz#0f46f8753989a33260efc47de9a0cdc571f2ec5c" - integrity sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.0" - -postcss-merge-rules@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162" - integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c" - integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g== - dependencies: - browserslist "^4.16.6" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c" - integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458" - integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398" - integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" - integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== - dependencies: - browserslist "^4.16.6" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb" - integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" - integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3" - integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw== - dependencies: - cssesc "^3.0.0" - indexes-of "^1.0.1" - uniq "^1.0.1" - util-deprecate "^1.0.2" - -postcss-selector-parser@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea" - integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - postcss-selector-parser@^6.0.9: version "6.0.9" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" @@ -4553,55 +3307,12 @@ postcss-selector-parser@^6.0.9: cssesc "^3.0.0" util-deprecate "^1.0.2" -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" - integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== - -postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@8.4.12: - version "8.4.12" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" - integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== +postcss@^8.1.10, postcss@^8.4.13: + version "8.4.13" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575" + integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA== dependencies: - nanoid "^3.3.1" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -postcss@^8.1.10: - version "8.2.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece" - integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw== - dependencies: - colorette "^1.2.2" - nanoid "^3.1.20" - source-map "^0.6.1" - -postcss@^8.4.7: - version "8.4.8" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032" - integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ== - dependencies: - nanoid "^3.3.1" + nanoid "^3.3.3" picocolors "^1.0.0" source-map-js "^1.0.2" @@ -4615,10 +3326,10 @@ pretty-bytes@^5.6.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== -prismjs@1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" - integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== +prismjs@1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== private-ip@2.3.3: version "2.3.3" @@ -4775,11 +3486,6 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@^1.1.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - qrcode@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" @@ -4800,6 +3506,11 @@ querystring@0.2.1: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== +queue-lit@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763" + integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw== + random-seed@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd" @@ -4826,13 +3537,6 @@ readdirp@~3.3.0: dependencies: picomatch "^2.0.7" -rechoir@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca" - integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q== - dependencies: - resolve "^1.9.0" - reconnecting-websocket@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" @@ -4870,31 +3574,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - resolve@^1.15.1, resolve@^1.20.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -4912,14 +3596,6 @@ resolve@^1.22.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.9.0: - version "1.18.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130" - integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA== - dependencies: - is-core-module "^2.0.0" - path-parse "^1.0.6" - restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -4948,6 +3624,20 @@ rndstr@1.0.0: rangestr "0.0.1" seedrandom "2.4.2" +rollup@2.75.6: + version "2.75.6" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.6.tgz#ac4dc8600f95942a0180f61c7c9d6200e374b439" + integrity sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA== + optionalDependencies: + fsevents "~2.3.2" + +rollup@^2.59.0: + version "2.70.2" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d" + integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg== + optionalDependencies: + fsevents "~2.3.2" + run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -4992,46 +3682,20 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-loader@12.6.0: - version "12.6.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" - integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== - dependencies: - klona "^2.0.4" - neo-async "^2.6.2" - -sass@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.0.tgz#3e407e2ebc53b12f1e35ce45efb226ea6063c7c8" - integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ== +sass@1.52.3: + version "1.52.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.3.tgz#b7cc7ffea2341ccc9a0c4fd372bf1b3f9be1b6cb" + integrity sha512-LNNPJ9lafx+j1ArtA7GyEJm9eawXN8KlA1+5dF6IZyoONg1Tyo/g+muOsENWJH/2Q1FHbbV4UwliU0cXMa/VIA== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sax@^1.2.4, sax@~1.2.4: +sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -schema-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" - integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== - dependencies: - "@types/json-schema" "^7.0.6" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.0.tgz#95986eb604f66daadeed56e379bfe7a7f963cdb9" - integrity sha512-tTEaeYkyIhEZ9uWgAjDerWov3T9MgX8dhhy2r0IGeeX4W8ngtGl1++dUve/RUqzuaASSh7shwCDJjEzthxki8w== - dependencies: - "@types/json-schema" "^7.0.7" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - seedrandom@2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.2.tgz#18d78c41287d13aff8eadb29e235938b248aa9ff" @@ -5042,7 +3706,7 @@ seedrandom@3.0.5: resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== -semver@^7.3.2, semver@^7.3.4: +semver@^7.3.2: version "7.3.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== @@ -5056,32 +3720,25 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -serialize-javascript@6.0.0, serialize-javascript@^6.0.0: +semver@^7.3.6, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +serialize-javascript@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" -serialize-javascript@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" - integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== - dependencies: - randombytes "^2.1.0" - set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -5136,39 +3793,16 @@ sortablejs@1.10.2: resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290" integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A== -source-list-map@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" - integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== - -"source-map-js@>=0.6.2 <2.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf" - integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA== - -source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-support@~0.5.19: - version "0.5.19" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" - integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: +source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -5181,16 +3815,6 @@ split@0.3: dependencies: through "2" -sprintf-js@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" - integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - sshpk@^1.14.1: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -5206,11 +3830,6 @@ sshpk@^1.14.1: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - start-server-and-test@1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.14.0.tgz#c57f04f73eac15dd51733b551d775b40837fdde3" @@ -5245,14 +3864,6 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string.prototype.trimend@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" - integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -5261,32 +3872,6 @@ string.prototype.trimend@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string.prototype.trimleft@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc" - integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart "^1.0.0" - -string.prototype.trimright@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3" - integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimend "^1.0.0" - -string.prototype.trimstart@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" - integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.5" - string.prototype.trimstart@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed" @@ -5331,34 +3916,14 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -style-loader@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" - integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== - -stylehacks@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" - integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== - dependencies: - browserslist "^4.16.6" - postcss-selector-parser "^6.0.4" - -supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: +supports-color@8.1.1, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -5370,90 +3935,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svgo@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167" - integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw== - dependencies: - chalk "^2.4.1" - coa "^2.0.2" - css-select "^2.0.0" - css-select-base-adapter "^0.1.1" - css-tree "1.0.0-alpha.37" - csso "^4.0.2" - js-yaml "^3.13.1" - mkdirp "~0.5.1" - object.values "^1.1.0" - sax "~1.2.4" - stable "^0.1.8" - unquote "~1.1.1" - util.promisify "~1.0.0" - -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - syuilo-password-strength@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/syuilo-password-strength/-/syuilo-password-strength-0.0.1.tgz#08f71a8f0ecb77db649f3d9a6424510d9d945f52" integrity sha1-CPcajw7Ld9tknz2aZCRRDZ2UX1I= -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b" - integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw== - -terser-webpack-plugin@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673" - integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q== - dependencies: - jest-worker "^26.6.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^5.0.1" - source-map "^0.6.1" - terser "^5.5.1" - -terser-webpack-plugin@^5.1.3: - version "5.1.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1" - integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA== - dependencies: - jest-worker "^27.0.2" - p-limit "^3.1.0" - schema-utils "^3.0.0" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.0" - -terser@^5.5.1: - version "5.5.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" - integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - -terser@^5.7.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784" - integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.19" - text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -5464,15 +3950,15 @@ textarea-caret@3.1.0: resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f" integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q== -three@0.139.2: - version "0.139.2" - resolved "https://registry.yarnpkg.com/three/-/three-0.139.2.tgz#b110799a15736df673b9293e31653a4ac73648dd" - integrity sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg== +three@0.141.0: + version "0.141.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.141.0.tgz#16677a12b9dd0c3e1568ebad0fd09de15d5a8216" + integrity sha512-JaSDAPWuk4RTzG5BYRQm8YZbERUxTfTDVouWgHMisS2to4E5fotMS9F2zPFNOIJyEFTTQDDKPpsgZVThKU3pXA== -throttle-debounce@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ec763b1c050c3a8f73eddd2e853a720893102a40" - integrity sha512-bO2OiH++k8Z3cTNZccOJRlxY5Sk3Tx3Kz6cQl3VY5pTRcEgqbPxwEKtrC00whFAo2jIBQlaH1ZG5mtfrBef3qw== +throttle-debounce@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933" + integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg== throttleit@^1.0.0: version "1.0.0" @@ -5521,29 +4007,28 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -ts-loader@9.2.8: - version "9.2.8" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48" - integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw== +tsc-alias@1.6.9: + version "1.6.9" + resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa" + integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ== dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.0.0" - micromatch "^4.0.0" - semver "^7.3.4" - -tsc-alias@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.5.0.tgz#bc26f8dccf96e4ea350adc3f64ad3d2325cad967" - integrity sha512-Pb3y7ZjULKFHEV2US5dS58/hV76sE9Sn5iehiPjYqHcm/lx4eCGAJYoSmrVXQMPX+6baTnDFJD0MGOyqn94dIg== - dependencies: - chokidar "^3.5.2" - commander "^8.3.0" - find-node-modules "^2.1.2" + chokidar "^3.5.3" + commander "^9.0.0" globby "^11.0.4" - mylas "^2.1.6" + mylas "^2.1.9" normalize-path "^3.0.0" + plimit-lit "^1.2.6" -tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1: +tsconfig-paths@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64" + integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q== + dependencies: + json5 "^2.2.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsconfig-paths@^3.14.1: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== @@ -5582,12 +4067,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= -twemoji-parser@13.1.0, twemoji-parser@13.1.x: - version "13.1.0" - resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" - integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== - -twemoji-parser@14.0.0: +twemoji-parser@14.0.0, twemoji-parser@14.0.x: version "14.0.0" resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62" integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA== @@ -5626,10 +4106,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@4.7.3: + version "4.7.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d" + integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA== unbox-primitive@^1.0.1: version "1.0.1" @@ -5641,11 +4121,6 @@ unbox-primitive@^1.0.1: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -uniq@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" - integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= - universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -5664,11 +4139,6 @@ unload@2.3.1: "@babel/runtime" "^7.6.2" detect-node "2.1.0" -unquote@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" - integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= - untildify@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" @@ -5693,16 +4163,6 @@ util-deprecate@^1.0.2: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee" - integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.2" - has-symbols "^1.0.1" - object.getownpropertydescriptors "^2.1.0" - uuid@7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" @@ -5737,72 +4197,58 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +vite@2.9.10: + version "2.9.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.10.tgz#f574d96655622c2e0fbc662edd0ed199c60fe91a" + integrity sha512-TwZRuSMYjpTurLqXspct+HZE7ONiW9d+wSWgvADGxhDPPyoIcNywY+RX4ng+QpK30DCa1l/oZgi2PLZDibhzbQ== + dependencies: + esbuild "^0.14.27" + postcss "^8.4.13" + resolve "^1.22.0" + rollup "^2.59.0" + optionalDependencies: + fsevents "~2.3.2" + void-elements@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk= -vue-eslint-parser@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13" - integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A== +vue-eslint-parser@^9.0.1: + version "9.0.2" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.2.tgz#d2535516f3f55adb387939427fe741065eb7948a" + integrity sha512-uCPQwTGjOtAYrwnU+76pYxalhjsh7iFBsHwBqDHiOPTxtICDaraO4Szw54WFTNZTAEsgHHzqFOu1mmnBOBRzDA== dependencies: - debug "^4.3.2" - eslint-scope "^6.0.0" - eslint-visitor-keys "^3.0.0" - espree "^9.0.0" + debug "^4.3.4" + eslint-scope "^7.1.1" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" lodash "^4.17.21" - semver "^7.3.5" - -vue-loader@17.0.0: - version "17.0.0" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb" - integrity sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg== - dependencies: - chalk "^4.1.0" - hash-sum "^2.0.0" - loader-utils "^2.0.0" + semver "^7.3.6" vue-prism-editor@2.0.0-alpha.2: version "2.0.0-alpha.2" resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69" integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w== -vue-router@4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.14.tgz#ce2028c1c5c33e30c7287950c973f397fce1bd65" - integrity sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw== +vue-router@4.0.16: + version "4.0.16" + resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.16.tgz#9477beeeef36e80e04d041a1738801a55e6e862e" + integrity sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA== dependencies: "@vue/devtools-api" "^6.0.0" -vue-style-loader@4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35" - integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== +vue@3.2.37: + version "3.2.37" + resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e" + integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ== dependencies: - hash-sum "^1.0.2" - loader-utils "^1.0.2" - -vue-svg-loader@0.17.0-beta.2: - version "0.17.0-beta.2" - resolved "https://registry.yarnpkg.com/vue-svg-loader/-/vue-svg-loader-0.17.0-beta.2.tgz#954b2a08b5488998dd81ec371ab5fb5ea4182ef7" - integrity sha512-iMUGJTKEcuNAG8VXOchjA8443IqEmEi2Aw6EVIHWma2cC4TUQ7Oet5Yry9IFfqXQXXvyzXz5EyttVvfRGTNH4Q== - dependencies: - loader-utils "^2.0.0" - semver "^7.3.2" - svgo "^1.3.2" - -vue@3.2.31: - version "3.2.31" - resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6" - integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw== - dependencies: - "@vue/compiler-dom" "3.2.31" - "@vue/compiler-sfc" "3.2.31" - "@vue/runtime-dom" "3.2.31" - "@vue/server-renderer" "3.2.31" - "@vue/shared" "3.2.31" + "@vue/compiler-dom" "3.2.37" + "@vue/compiler-sfc" "3.2.37" + "@vue/runtime-dom" "3.2.37" + "@vue/server-renderer" "3.2.37" + "@vue/shared" "3.2.37" vuedraggable@4.0.1: version "4.0.1" @@ -5822,120 +4268,6 @@ wait-on@6.0.0: minimist "^1.2.5" rxjs "^7.1.0" -watchpack@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.0.tgz#b12248f32f0fd4799b7be0802ad1f6573a45955c" - integrity sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -watchpack@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" - integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -webpack-cli@4.9.2: - version "4.9.2" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d" - integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^1.1.1" - "@webpack-cli/info" "^1.4.1" - "@webpack-cli/serve" "^1.6.1" - colorette "^2.0.14" - commander "^7.0.0" - execa "^5.0.0" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^2.2.0" - rechoir "^0.7.0" - webpack-merge "^5.7.3" - -webpack-merge@^5.7.3: - version "5.7.3" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213" - integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^2.1.1: - version "2.2.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" - integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== - dependencies: - source-list-map "^2.0.1" - source-map "^0.6.1" - -webpack-sources@^3.2.3: - version "3.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" - integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== - -webpack@5.72.0: - version "5.72.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28" - integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.2" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.3.1" - webpack-sources "^3.2.3" - -webpack@^5: - version "5.33.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.33.2.tgz#c049717c9b038febf5a72fd2f53319ad59a8c1fc" - integrity sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.46" - "@webassemblyjs/ast" "1.11.0" - "@webassemblyjs/wasm-edit" "1.11.0" - "@webassemblyjs/wasm-parser" "1.11.0" - acorn "^8.0.4" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.7.0" - es-module-lexer "^0.4.0" - eslint-scope "^5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.0.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.1" - watchpack "^2.0.0" - webpack-sources "^2.1.1" - websocket@1.0.34: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" @@ -5964,25 +4296,13 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@2.0.2, which@^2.0.1: +which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - with@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac" @@ -5998,10 +4318,10 @@ word-wrap@^1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== -workerpool@6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" - integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: version "6.2.0" @@ -6026,10 +4346,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== +ws@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== xml-js@^1.6.11: version "1.6.11" @@ -6038,6 +4358,11 @@ xml-js@^1.6.11: dependencies: sax "^1.2.4" +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + y18n@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" @@ -6058,16 +4383,6 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" - integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== - -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - yargs-parser@20.2.4, yargs-parser@^20.2.2: version "20.2.4" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" @@ -6128,8 +4443,3 @@ yauzl@^2.10.0: dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js index 2d3356c3a6..4d9d6f2c85 100644 --- a/packages/shared/.eslintrc.js +++ b/packages/shared/.eslintrc.js @@ -68,5 +68,8 @@ module.exports = { }], 'import/no-unresolved': ['off'], 'import/no-default-export': ['warn'], + 'import/order': ['warn', { + 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'], + }] }, }; diff --git a/packages/sw/.eslintrc.js b/packages/sw/.eslintrc.js new file mode 100644 index 0000000000..9d56daca83 --- /dev/null +++ b/packages/sw/.eslintrc.js @@ -0,0 +1,22 @@ +module.exports = { + root: true, + env: { + "node": false + }, + parserOptions: { + "parser": "@typescript-eslint/parser", + tsconfigRootDir: __dirname, + //project: ['./tsconfig.json'], + }, + extends: [ + //"../shared/.eslintrc.js", + ], + globals: { + "require": false, + "_DEV_": false, + "_LANGS_": false, + "_VERSION_": false, + "_ENV_": false, + "_PERF_PREFIX_": false, + } +} diff --git a/packages/sw/.npmrc b/packages/sw/.npmrc new file mode 100644 index 0000000000..6b5f38e890 --- /dev/null +++ b/packages/sw/.npmrc @@ -0,0 +1,2 @@ +save-exact = true +package-lock = false diff --git a/packages/sw/.yarnrc b/packages/sw/.yarnrc new file mode 100644 index 0000000000..788570fcd5 --- /dev/null +++ b/packages/sw/.yarnrc @@ -0,0 +1 @@ +network-timeout 600000 diff --git a/packages/sw/build.js b/packages/sw/build.js new file mode 100644 index 0000000000..72d9db9c0f --- /dev/null +++ b/packages/sw/build.js @@ -0,0 +1,37 @@ +const esbuild = require('esbuild'); +const locales = require('../../locales'); +const meta = require('../../package.json'); +const watch = process.argv[2]?.includes('watch'); + +console.log('Starting SW building...'); + +esbuild.build({ + entryPoints: [ `${__dirname}/src/sw.ts` ], + bundle: true, + format: 'esm', + treeShaking: true, + minify: process.env.NODE_ENV === 'production', + absWorkingDir: __dirname, + outbase: `${__dirname}/src`, + outdir: `${__dirname}/../../built/_sw_dist_`, + loader: { + '.ts': 'ts' + }, + tsconfig: `${__dirname}/tsconfig.json`, + define: { + _VERSION_: JSON.stringify(meta.version), + _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _ENV_: JSON.stringify(process.env.NODE_ENV), + _DEV_: process.env.NODE_ENV !== 'production', + _PERF_PREFIX_: JSON.stringify('Misskey:'), + }, + watch: watch ? { + onRebuild(error, result) { + if (error) console.error('SW: watch build failed:', error); + else console.log('SW: watch build succeeded:', result); + }, + } : false, +}).then(result => { + if (watch) console.log('watching...'); + else console.log('done,', JSON.stringify(result)); +}); diff --git a/packages/sw/package.json b/packages/sw/package.json new file mode 100644 index 0000000000..41dfe19b85 --- /dev/null +++ b/packages/sw/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "scripts": { + "watch": "node build.js watch", + "build": "node build.js", + "lint": "eslint --quiet src/**/*.{ts}" + }, + "resolutions": {}, + "dependencies": { + "esbuild": "^0.14.13", + "idb-keyval": "^6.0.3", + "misskey-js": "0.0.14" + }, + "devDependencies": { + "eslint": "^8.2.0" + } +} diff --git a/packages/sw/src/filters/user.ts b/packages/sw/src/filters/user.ts new file mode 100644 index 0000000000..09437eb19a --- /dev/null +++ b/packages/sw/src/filters/user.ts @@ -0,0 +1,14 @@ +import * as misskey from 'misskey-js'; +import * as Acct from 'misskey-js/built/acct'; + +export const acct = (user: misskey.Acct) => { + return Acct.toString(user); +}; + +export const userName = (user: misskey.entities.User) => { + return user.name || user.username; +}; + +export const userPage = (user: misskey.Acct, path?, absolute = false) => { + return `${absolute ? origin : ''}/@${acct(user)}${(path ? `/${path}` : '')}`; +}; diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts new file mode 100644 index 0000000000..6d7ba7d524 --- /dev/null +++ b/packages/sw/src/scripts/create-notification.ts @@ -0,0 +1,237 @@ +/* + * Notification manager for SW + */ +declare var self: ServiceWorkerGlobalScope; + +import { swLang } from '@/scripts/lang'; +import { cli } from '@/scripts/operations'; +import { pushNotificationDataMap } from '@/types'; +import getUserName from '@/scripts/get-user-name'; +import { I18n } from '@/scripts/i18n'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; + +export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { + const n = await composeNotification(data); + + if (n) { + return self.registration.showNotification(...n); + } else { + console.error('Could not compose notification', data); + return createEmptyNotification(); + } +} + +async function composeNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]): Promise<[string, NotificationOptions] | null> { + if (!swLang.i18n) swLang.fetchLocale(); + const i18n = await swLang.i18n as I18n<any>; + const { t } = i18n; + switch (data.type) { + /* + case 'driveFileCreated': // TODO (Server Side) + return [t('_notification.fileUploaded'), { + body: body.name, + icon: body.url, + data + }]; + */ + case 'notification': + switch (data.body.type) { + case 'follow': + // users/showの型定義をswos.apiへ当てはめるのが困難なのでapiFetch.requestを直接使用 + const account = await getAccountFromId(data.userId); + if (!account) return null; + const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token); + return [t('_notification.youWereFollowed'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + actions: userDetail.isFollowing ? [] : [ + { + action: 'follow', + title: t('_notification._actions.followBack') + } + ], + }]; + + case 'mention': + return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + } + ], + }]; + + case 'reply': + return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + } + ], + }]; + + case 'renote': + return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'showUser', + title: getUserName(data.body.user) + } + ], + }]; + + case 'quote': + return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'reply', + title: t('_notification._actions.reply') + }, + ...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [ + { + action: 'renote', + title: t('_notification._actions.renote') + } + ] : []) + ], + }]; + + case 'reaction': + return [`${data.body.reaction} ${getUserName(data.body.user)}`, { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'showUser', + title: getUserName(data.body.user) + } + ], + }]; + + case 'pollVote': + return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), { + body: data.body.note.text || '', + icon: data.body.user.avatarUrl, + data, + }]; + + case 'pollEnded': + return [t('_notification.pollEnded'), { + body: data.body.note.text || '', + data, + }]; + + case 'receiveFollowRequest': + return [t('_notification.youReceivedFollowRequest'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + actions: [ + { + action: 'accept', + title: t('accept') + }, + { + action: 'reject', + title: t('reject') + } + ], + }]; + + case 'followRequestAccepted': + return [t('_notification.yourFollowRequestAccepted'), { + body: getUserName(data.body.user), + icon: data.body.user.avatarUrl, + data, + }]; + + case 'groupInvited': + return [t('_notification.youWereInvitedToGroup', { userName: getUserName(data.body.user) }), { + body: data.body.invitation.group.name, + data, + actions: [ + { + action: 'accept', + title: t('accept') + }, + { + action: 'reject', + title: t('reject') + } + ], + }]; + + case 'app': + return [data.body.header || data.body.body, { + body: data.body.header && data.body.body, + icon: data.body.icon, + data + }]; + + default: + return null; + } + case 'unreadMessagingMessage': + if (data.body.groupId === null) { + return [t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.body.user) }), { + icon: data.body.user.avatarUrl, + tag: `messaging:user:${data.body.userId}`, + data, + renotify: true, + }]; + } + return [t('_notification.youGotMessagingMessageFromGroup', { name: data.body.group.name }), { + icon: data.body.user.avatarUrl, + tag: `messaging:group:${data.body.groupId}`, + data, + renotify: true, + }]; + default: + return null; + } +} + +export async function createEmptyNotification() { + return new Promise<void>(async res => { + if (!swLang.i18n) swLang.fetchLocale(); + const i18n = await swLang.i18n as I18n<any>; + const { t } = i18n; + + await self.registration.showNotification( + t('_notification.emptyPushNotificationMessage'), + { + silent: true, + tag: 'read_notification', + } + ); + + res(); + + setTimeout(async () => { + for (const n of + [ + ...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })), + ...(await self.registration.getNotifications({ tag: 'read_notification' })) + ] + ) { + n.close(); + } + }, 1000); + }); +} diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts new file mode 100644 index 0000000000..be4cfaeba4 --- /dev/null +++ b/packages/sw/src/scripts/get-account-from-id.ts @@ -0,0 +1,7 @@ +import { get } from 'idb-keyval'; + +export async function getAccountFromId(id: string) { + const accounts = await get('accounts') as { token: string; id: string; }[]; + if (!accounts) console.log('Accounts are not recorded'); + return accounts.find(e => e.id === id); +} diff --git a/packages/sw/src/scripts/get-user-name.ts b/packages/sw/src/scripts/get-user-name.ts new file mode 100644 index 0000000000..d499ea0203 --- /dev/null +++ b/packages/sw/src/scripts/get-user-name.ts @@ -0,0 +1,3 @@ +export default function(user: { name?: string | null, username: string }): string { + return user.name || user.username; +} diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts new file mode 100644 index 0000000000..3fe88e5514 --- /dev/null +++ b/packages/sw/src/scripts/i18n.ts @@ -0,0 +1,29 @@ +export class I18n<T extends Record<string, any>> { + public ts: T; + + constructor(locale: T) { + this.ts = locale; + + //#region BIND + this.t = this.t.bind(this); + //#endregion + } + + // string にしているのは、ドット区切りでのパス指定を許可するため + // なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも + public t(key: string, args?: Record<string, string>): string { + try { + let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; + + if (args) { + for (const [k, v] of Object.entries(args)) { + str = str.replace(`{${k}}`, v); + } + } + return str; + } catch (err) { + console.warn(`missing localization '${key}'`); + return key; + } + } +} diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts new file mode 100644 index 0000000000..2d05404ef9 --- /dev/null +++ b/packages/sw/src/scripts/lang.ts @@ -0,0 +1,47 @@ +/* + * Language manager for SW + */ +declare var self: ServiceWorkerGlobalScope; + +import { get, set } from 'idb-keyval'; +import { I18n } from '@/scripts/i18n'; + +class SwLang { + public cacheName = `mk-cache-${_VERSION_}`; + + public lang: Promise<string> = get('lang').then(async prelang => { + if (!prelang) return 'en-US'; + return prelang; + }); + + public setLang(newLang: string) { + this.lang = Promise.resolve(newLang); + set('lang', newLang); + return this.fetchLocale(); + } + + public i18n: Promise<I18n<any>> | null = null; + + public fetchLocale() { + return this.i18n = this._fetch(); + } + + private async _fetch() { + // Service Workerは何度も起動しそのたびにlocaleを読み込むので、CacheStorageを使う + const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`; + let localeRes = await caches.match(localeUrl); + + // _DEV_がtrueの場合は常に最新化 + if (!localeRes || _DEV_) { + localeRes = await fetch(localeUrl); + const clone = localeRes?.clone(); + if (!clone?.clone().ok) Error('locale fetching error'); + + caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone)); + } + + return new I18n(await localeRes.json()); + } +} + +export const swLang = new SwLang(); diff --git a/packages/sw/src/scripts/login-id.ts b/packages/sw/src/scripts/login-id.ts new file mode 100644 index 0000000000..0f9c6be4a9 --- /dev/null +++ b/packages/sw/src/scripts/login-id.ts @@ -0,0 +1,11 @@ +export function getUrlWithLoginId(url: string, loginId: string) { + const u = new URL(url, origin); + u.searchParams.append('loginId', loginId); + return u.toString(); +} + +export function getUrlWithoutLoginId(url: string) { + const u = new URL(url); + u.searchParams.delete('loginId'); + return u.toString(); +} diff --git a/packages/sw/src/scripts/notification-read.ts b/packages/sw/src/scripts/notification-read.ts new file mode 100644 index 0000000000..8433f902b4 --- /dev/null +++ b/packages/sw/src/scripts/notification-read.ts @@ -0,0 +1,50 @@ +declare var self: ServiceWorkerGlobalScope; + +import { get } from 'idb-keyval'; +import { pushNotificationDataMap } from '@/types'; +import { api } from '@/scripts/operations'; + +type Accounts = { + [x: string]: { + queue: string[], + timeout: number | null + } +}; + +class SwNotificationReadManager { + private accounts: Accounts = {}; + + public async construct() { + const accounts = await get('accounts'); + if (!accounts) Error('Accounts are not recorded'); + + this.accounts = accounts.reduce((acc, e) => { + acc[e.id] = { + queue: [], + timeout: null + }; + return acc; + }, {} as Accounts); + + return this; + } + + // プッシュ通知の既読をサーバーに送信 + public async read<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) { + if (data.type !== 'notification' || !(data.userId in this.accounts)) return; + + const account = this.accounts[data.userId]; + + account.queue.push(data.body.id as string); + + // 最後の呼び出しから200ms待ってまとめて処理する + if (account.timeout) clearTimeout(account.timeout); + account.timeout = setTimeout(() => { + account.timeout = null; + + api('notifications/read', data.userId, { notificationIds: account.queue }); + }, 200); + } +} + +export const swNotificationRead = (new SwNotificationReadManager()).construct(); diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts new file mode 100644 index 0000000000..02cf0d96cf --- /dev/null +++ b/packages/sw/src/scripts/operations.ts @@ -0,0 +1,70 @@ +/* + * Operations + * 各種操作 + */ +declare var self: ServiceWorkerGlobalScope; + +import * as Misskey from 'misskey-js'; +import { SwMessage, swMessageOrderType } from '@/types'; +import { acct as getAcct } from '@/filters/user'; +import { getAccountFromId } from '@/scripts/get-account-from-id'; +import { getUrlWithLoginId } from '@/scripts/login-id'; + +export const cli = new Misskey.api.APIClient({ origin, fetch: (...args) => fetch(...args) }); + +export async function api<E extends keyof Misskey.Endpoints>(endpoint: E, userId: string, options?: Misskey.Endpoints[E]['req']) { + const account = await getAccountFromId(userId); + if (!account) return; + + return cli.request(endpoint, options, account.token); +} + +// rendered acctからユーザーを開く +export function openUser(acct: string, loginId: string) { + return openClient('push', `/@${acct}`, loginId, { acct }); +} + +// noteIdからノートを開く +export function openNote(noteId: string, loginId: string) { + return openClient('push', `/notes/${noteId}`, loginId, { noteId }); +} + +export async function openChat(body: any, loginId: string) { + if (body.groupId === null) { + return openClient('push', `/my/messaging/${getAcct(body.user)}`, loginId, { body }); + } else { + return openClient('push', `/my/messaging/group/${body.groupId}`, loginId, { body }); + } +} + +// post-formのオプションから投稿フォームを開く +export async function openPost(options: any, loginId: string) { + // クエリを作成しておく + let url = `/share?`; + if (options.initialText) url += `text=${options.initialText}&`; + if (options.reply) url += `replyId=${options.reply.id}&`; + if (options.renote) url += `renoteId=${options.renote.id}&`; + + return openClient('post', url, loginId, { options }); +} + +export async function openClient(order: swMessageOrderType, url: string, loginId: string, query: any = {}) { + const client = await findClient(); + + if (client) { + client.postMessage({ type: 'order', ...query, order, loginId, url } as SwMessage); + return client; + } + + return self.clients.openWindow(getUrlWithLoginId(url, loginId)); +} + +export async function findClient() { + const clients = await self.clients.matchAll({ + type: 'window' + }); + for (const c of clients) { + if (c.url.indexOf('?zen') < 0) return c; + } + return null; +} diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts new file mode 100644 index 0000000000..0ba6a6e4af --- /dev/null +++ b/packages/sw/src/sw.ts @@ -0,0 +1,200 @@ +declare var self: ServiceWorkerGlobalScope; + +import { createEmptyNotification, createNotification } from '@/scripts/create-notification'; +import { swLang } from '@/scripts/lang'; +import { swNotificationRead } from '@/scripts/notification-read'; +import { pushNotificationDataMap } from '@/types'; +import * as swos from '@/scripts/operations'; +import { acct as getAcct } from '@/filters/user'; + +self.addEventListener('install', ev => { + ev.waitUntil(self.skipWaiting()); +}); + +self.addEventListener('activate', ev => { + ev.waitUntil( + caches.keys() + .then(cacheNames => Promise.all( + cacheNames + .filter((v) => v !== swLang.cacheName) + .map(name => caches.delete(name)) + )) + .then(() => self.clients.claim()) + ); +}); + +self.addEventListener('fetch', ev => { + ev.respondWith( + fetch(ev.request) + .catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 })) + ); +}); + +self.addEventListener('push', ev => { + // クライアント取得 + ev.waitUntil(self.clients.matchAll({ + includeUncontrolled: true, + type: 'window' + }).then(async <K extends keyof pushNotificationDataMap>(clients: readonly WindowClient[]) => { + const data: pushNotificationDataMap[K] = ev.data?.json(); + + switch (data.type) { + // case 'driveFileCreated': + case 'notification': + case 'unreadMessagingMessage': + // クライアントがあったらストリームに接続しているということなので通知しない + if (clients.length != 0) return; + return createNotification(data); + case 'readAllNotifications': + for (const n of await self.registration.getNotifications()) { + if (n?.data?.type === 'notification') n.close(); + } + break; + case 'readAllMessagingMessages': + for (const n of await self.registration.getNotifications()) { + if (n?.data?.type === 'unreadMessagingMessage') n.close(); + } + break; + case 'readNotifications': + for (const n of await self.registration.getNotifications()) { + if (data.body?.notificationIds?.includes(n.data.body.id)) { + n.close(); + } + } + break; + case 'readAllMessagingMessagesOfARoom': + for (const n of await self.registration.getNotifications()) { + if (n.data.type === 'unreadMessagingMessage' + && ('userId' in data.body + ? data.body.userId === n.data.body.userId + : data.body.groupId === n.data.body.groupId) + ) { + n.close(); + } + } + break; + } + + return createEmptyNotification(); + })); +}); + +self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => { + ev.waitUntil((async () => { + if (_DEV_) { + console.log('notificationclick', ev.action, ev.notification.data); + } + + const { action, notification } = ev; + const data: pushNotificationDataMap[K] = notification.data; + const { userId: id } = data; + let client: WindowClient | null = null; + + switch (data.type) { + case 'notification': + switch (action) { + case 'follow': + if ('userId' in data.body) await swos.api('following/create', id, { userId: data.body.userId }); + break; + case 'showUser': + if ('user' in data.body) client = await swos.openUser(getAcct(data.body.user), id); + break; + case 'reply': + if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, id); + break; + case 'renote': + if ('note' in data.body) await swos.api('notes/create', id, { renoteId: data.body.note.id }); + break; + case 'accept': + switch (data.body.type) { + case 'receiveFollowRequest': + await swos.api('following/requests/accept', id, { userId: data.body.userId }); + break; + case 'groupInvited': + await swos.api('users/groups/invitations/accept', id, { invitationId: data.body.invitation.id }); + break; + } + break; + case 'reject': + switch (data.body.type) { + case 'receiveFollowRequest': + await swos.api('following/requests/reject', id, { userId: data.body.userId }); + break; + case 'groupInvited': + await swos.api('users/groups/invitations/reject', id, { invitationId: data.body.invitation.id }); + break; + } + break; + case 'showFollowRequests': + client = await swos.openClient('push', '/my/follow-requests', id); + break; + default: + switch (data.body.type) { + case 'receiveFollowRequest': + client = await swos.openClient('push', '/my/follow-requests', id); + break; + case 'groupInvited': + client = await swos.openClient('push', '/my/groups', id); + break; + case 'reaction': + client = await swos.openNote(data.body.note.id, id); + break; + default: + if ('note' in data.body) { + client = await swos.openNote(data.body.note.id, id); + } else if ('user' in data.body) { + client = await swos.openUser(getAcct(data.body.user), id); + } + break; + } + } + break; + case 'unreadMessagingMessage': + client = await swos.openChat(data.body, id); + break; + } + + if (client) { + client.focus(); + } + if (data.type === 'notification') { + swNotificationRead.then(that => that.read(data)); + } + + notification.close(); + + })()); +}); + +self.addEventListener('notificationclose', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => { + const data: pushNotificationDataMap[K] = ev.notification.data; + + if (data.type === 'notification') { + swNotificationRead.then(that => that.read(data)); + } +}); + +self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => { + ev.waitUntil((async () => { + switch (ev.data) { + case 'clear': + // Cache Storage全削除 + await caches.keys() + .then(cacheNames => Promise.all( + cacheNames.map(name => caches.delete(name)) + )); + return; // TODO + } + + if (typeof ev.data === 'object') { + // E.g. '[object Array]' → 'array' + const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase(); + + if (otype === 'object') { + if (ev.data.msg === 'initialize') { + swLang.setLang(ev.data.lang); + } + } + } + })()); +}); diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts new file mode 100644 index 0000000000..6aa3726eac --- /dev/null +++ b/packages/sw/src/types.ts @@ -0,0 +1,31 @@ +import * as Misskey from 'misskey-js'; + +export type swMessageOrderType = 'post' | 'push'; + +export type SwMessage = { + type: 'order'; + order: swMessageOrderType; + loginId: string; + url: string; + [x: string]: any; +}; + +// Defined also @/services/push-notification.ts#L7-L14 +type pushNotificationDataSourceMap = { + notification: Misskey.entities.Notification; + unreadMessagingMessage: Misskey.entities.MessagingMessage; + readNotifications: { notificationIds: string[] }; + readAllNotifications: undefined; + readAllMessagingMessages: undefined; + readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string }; +}; + +export type pushNotificationData<K extends keyof pushNotificationDataSourceMap> = { + type: K; + body: pushNotificationDataSourceMap[K]; + userId: string; +}; + +export type pushNotificationDataMap = { + [K in keyof pushNotificationDataSourceMap]: pushNotificationData<K>; +}; diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json new file mode 100644 index 0000000000..c3a845f12a --- /dev/null +++ b/packages/sw/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "allowJs": true, + "noEmitOnError": false, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedParameters": false, + "noUnusedLocals": true, + "noFallthroughCasesInSwitch": true, + "declaration": false, + "sourceMap": false, + "target": "es2017", + "module": "esnext", + "moduleResolution": "node", + "removeComments": false, + "noLib": false, + "strict": true, + "strictNullChecks": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + "isolatedModules": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + }, + "typeRoots": [ + "node_modules/@types", + "@types", + ], + "lib": [ + "esnext", + "webworker" + ] + }, + "compileOnSave": false, + "include": [ + "./**/*.ts" + ] +} diff --git a/packages/sw/yarn.lock b/packages/sw/yarn.lock new file mode 100644 index 0000000000..e6d683bc42 --- /dev/null +++ b/packages/sw/yarn.lock @@ -0,0 +1,710 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@eslint/eslintrc@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.2.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e" + integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +acorn-jsx@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.7.0: + version "8.7.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== + +ajv@^6.10.0, ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +autobind-decorator@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c" + integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.1, debug@^4.3.2: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +esbuild-android-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.17.tgz#7216810cb8d5b8cd03ce70bdc241dcdd90c34755" + integrity sha512-y7EJm8ADC9qKbo/dJ2zBXwNdIILJ76tTv7JDGvOkbLT8HJXIsgbpa0NJk7iFhyvP4GpsYvXTbvEQNn0DhyBhLA== + +esbuild-darwin-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.17.tgz#1419e020f41814f8a74ce92b2dcab29a6d47e510" + integrity sha512-V2JAP8yyVbW6qR4SVXsEDqRicYM0x5niUuB05IFiE5itPI45k8j2dA2l+DtirR2SGXr+LEqgX347+2VA6eyTiA== + +esbuild-darwin-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.17.tgz#95acf1022066d48346a63ffc5e4d36a07b83c9b0" + integrity sha512-ENkSKpjF4SImyA2TdHhKiZqtYc1DkMykICe1KSBw0YNF1sentjFI6wu+CRiYMpC7REf/3TQXoems2XPqIqDMlQ== + +esbuild-freebsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.17.tgz#a3455199862110854937b05a0eecbed3e1aeec41" + integrity sha512-2i0nTNJM8ftNTvtR00vdqkru8XpHwAbkR2MBLoK2IDSzjsLStwCj+mxf6v83eVM9Abe3QA8xP+irqOdBlwDQ2g== + +esbuild-freebsd-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.17.tgz#8a70f2a36f5b0da7d2efdd6fd02aa78611007fd0" + integrity sha512-QOmRi1n+uly2G7BbMbHb86YiFA5aM7B2T96A6OF1VG57LNwXwy8LPVM0PVjl7f9cV3pE3fy3VtXPJHJo8XggTA== + +esbuild-linux-32@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.17.tgz#b7123f6e4780687e017454604d909fbe558862e9" + integrity sha512-qG5NDk7FHHUVw01rjHESON0HvigF2X80b645TUlgTKsWRlrbzzHhMCmQguA01O5PiCimKnyoxti8aJIFNHpQnQ== + +esbuild-linux-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.17.tgz#47a6b510c2f7faef595a4d6257a629e65385fdc3" + integrity sha512-De8OcmNvfNyFfQRLWbfuZqau6NpYBJxNTLP7Ls/PqQcw0HAwfaYThutY8ozHpPbKFPa7wgqabXlIC4NVSWT0/A== + +esbuild-linux-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.17.tgz#dfd9022b7215ca660d464fcb20597b88887c7e64" + integrity sha512-WDEOD/YRA4J1lxhETKZff3gRxGYqqZEiVwIOqNfvCh2YcwWU2y6UmNGZsxcuKk18wot4dAXCXQyNZgBkVUTCLw== + +esbuild-linux-arm@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.17.tgz#e6f6bb9fe52def5260d7d49b790fbec0e7c6d9cb" + integrity sha512-ZwsgFUk3gR2pEMJdh5z4Ds18fvGETgElPqmNdx1NtZTCOVlFMAwFB5u/tOR2FrXbMFv+LkGnNxPDh48PYPDz9A== + +esbuild-linux-mips64le@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.17.tgz#bceaad33ff18a822b6da0396c6497a231397b6c3" + integrity sha512-Lf4X9NB7r6imzp/11TaGs4kWL0DUn1JxI9gAAKotnKh6T8Y/0sLvZSvQS8WvSZcr0V8RRCrRZwiQqjOALUU/9g== + +esbuild-linux-ppc64le@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.17.tgz#9562f094d1e5e6c3b61b776b15a9bbd657042654" + integrity sha512-aExhxbrK7/Mh9FArdiC9MbvrQz2bGCDI8cBALKJbmhKg0h7LNt6y1E1S9GGBZ/ZXkHDvV9FFVrXXZKFVU5Qpiw== + +esbuild-linux-s390x@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.17.tgz#2963cfe62c227bbf1da64e36d4ca0b23db8008fe" + integrity sha512-b0T20rNcS7POi5YLw5dFlsiC+riobR5IfppQGn5NWer6QiIkdL1vOx9eC9CUD3z1itpkLboRAZYieZfKfhCA2Q== + +esbuild-netbsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.17.tgz#1d156023f9ae6be79b8627ab0cda2d7feb7f3a48" + integrity sha512-pFgTaAa2JF18nqNfCND9wOu1jbZ/mbDSaMxUp5fTkLlofyHhXeb5aChgXUkeipty2Pgq0OwOnxjHmiAxMI7N4g== + +esbuild-openbsd-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.17.tgz#3fc44102c9b65375385112f4ce5899ae5e38f349" + integrity sha512-K5+plb6gsAfBcFqB0EG4KvLbgBKslVAfEyJggicwt/QoDwQGJAzao4M6zOA4PG7LlXOwWSqv7VmSFbH+b6DyKw== + +esbuild-sunos-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.17.tgz#5bd24e7a7e863ea89d7e4eafd5364a155c9ea507" + integrity sha512-o1FINkbHRi9JB1YteOSXZdkDOmVUbmnCxRmTLkHvk8pfCFNpv/5/7ktt95teYKbEiJna2dEt3M4ckJ/+UVnW+w== + +esbuild-windows-32@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.17.tgz#8bda31c550fb6b425707114141d2c6ba034dab9b" + integrity sha512-Qutilz0I7OADWBtWrC/FD+2O/TNAkhwbZ+wIns7kF87lxIMtmqpBt3KnMk1e4F47aTrZRr0oH55Zhztd7m2PAA== + +esbuild-windows-64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.17.tgz#50b42c06908d3ce9fab8f0f9673199de5d0f9cbc" + integrity sha512-b21/oRV+PHrav0HkRpKjbM2yNRVe34gAfbdMppbZFea416wa8SrjcmVfSd7n4jgqoTQG0xe+MGgOpwXtjiB3DQ== + +esbuild-windows-arm64@0.14.17: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.17.tgz#62d3921a810b64a03fcace76dad4db51d2128b45" + integrity sha512-4HN9E1idllewYvptcrrdfTA6DIWgg11kK0Zrv6yjxstJZLJeKxfilGBEaksLGs4Pst2rAYMx3H2vbYq7AWLQNA== + +esbuild@^0.14.13: + version "0.14.17" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.17.tgz#6a634e56447aa0e90b34c42091d472d802d399e5" + integrity sha512-JLgyC6Uv31mv9T9Mm2xF1LntUMCNBSzvg2n32d8cTKZMwFr1wmMFY2FkVum98TSoEsDff0cR+Aj49H2sbBcjKQ== + optionalDependencies: + esbuild-android-arm64 "0.14.17" + esbuild-darwin-64 "0.14.17" + esbuild-darwin-arm64 "0.14.17" + esbuild-freebsd-64 "0.14.17" + esbuild-freebsd-arm64 "0.14.17" + esbuild-linux-32 "0.14.17" + esbuild-linux-64 "0.14.17" + esbuild-linux-arm "0.14.17" + esbuild-linux-arm64 "0.14.17" + esbuild-linux-mips64le "0.14.17" + esbuild-linux-ppc64le "0.14.17" + esbuild-linux-s390x "0.14.17" + esbuild-netbsd-64 "0.14.17" + esbuild-openbsd-64 "0.14.17" + esbuild-sunos-64 "0.14.17" + esbuild-windows-32 "0.14.17" + esbuild-windows-64 "0.14.17" + esbuild-windows-arm64 "0.14.17" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-scope@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1" + integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ== + +eslint@^8.2.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" + integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== + dependencies: + "@eslint/eslintrc" "^1.0.5" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.0" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.2.0" + espree "^9.3.0" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.6.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.2.0, espree@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== + dependencies: + acorn "^8.7.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^3.1.0" + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" + integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^13.6.0, globals@^13.9.0: + version "13.12.1" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb" + integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw== + dependencies: + type-fest "^0.20.2" + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +idb-keyval@^6.0.3: + version "6.1.0" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041" + integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw== + dependencies: + safari-14-idb-fix "^3.0.0" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +misskey-js@0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d" + integrity sha512-bvLx6U3OwQwqHfp/WKwIVwdvNYAAPk0+YblXyxmSG3dwlzCgBRRLcB8o6bNruUDyJgh3t73pLDcOz3myxcUmww== + dependencies: + autobind-decorator "^2.4.0" + eventemitter3 "^4.0.7" + reconnecting-websocket "^4.4.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +reconnecting-websocket@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783" + integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng== + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +safari-14-idb-fix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440" + integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= |