From 2c1689c7989b2d02b52c95049a93c9c576c9ad13 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 15 Apr 2022 15:05:43 +0900
Subject: [PATCH 001/258] chore: add import/order rule for eslint

---
 packages/backend/.eslintrc.cjs | 12 ++++++++++++
 packages/shared/.eslintrc.js   |  3 +++
 2 files changed, 15 insertions(+)

diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs
index e2e31e9e33..dfc9d04950 100644
--- a/packages/backend/.eslintrc.cjs
+++ b/packages/backend/.eslintrc.cjs
@@ -6,4 +6,16 @@ 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'
+				}
+			],
+		}]
+	},
 };
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'],
+		}]
 	},
 };

From e598d46c3191be107dfafdd2cd0c1e65348e6a3c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 15 Apr 2022 21:33:00 +0900
Subject: [PATCH 002/258] Update settings.json

---
 packages/backend/.vscode/settings.json | 4 ++++
 1 file changed, 4 insertions(+)

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
 	}
 }

From 1d193b9a04cd766362336abbd67d05830f70f28c Mon Sep 17 00:00:00 2001
From: rinsuki <428rinsuki+git@gmail.com>
Date: Sat, 16 Apr 2022 01:28:59 +0900
Subject: [PATCH 003/258] refactor: move typings to devDependencies (#8500)

---
 package.json                  |  4 +-
 packages/backend/package.json | 86 +++++++++++++++++------------------
 packages/client/package.json  | 42 ++++++++---------
 3 files changed, 66 insertions(+), 66 deletions(-)

diff --git a/package.json b/package.json
index 13d70929bc..361c4096da 100644
--- a/package.json
+++ b/package.json
@@ -30,8 +30,6 @@
 		"cleanall": "npm run clean-all"
 	},
 	"dependencies": {
-		"@types/gulp": "4.0.9",
-		"@types/gulp-rename": "2.0.1",
 		"execa": "5.1.1",
 		"gulp": "4.0.2",
 		"gulp-cssnano": "2.1.3",
@@ -41,6 +39,8 @@
 		"js-yaml": "4.1.0"
 	},
 	"devDependencies": {
+		"@types/gulp": "4.0.9",
+		"@types/gulp-rename": "2.0.1",
 		"@typescript-eslint/parser": "5.18.0",
 		"cross-env": "7.0.3",
 		"cypress": "9.5.3",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 314818f80b..7fe3757eb3 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -21,49 +21,6 @@
 		"@koa/router": "9.0.1",
 		"@sinonjs/fake-timers": "9.1.1",
 		"@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",
@@ -172,7 +129,50 @@
 	},
 	"devDependencies": {
 		"@redocly/openapi-core": "1.0.0-beta.93",
+		"@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/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",
 		"cross-env": "7.0.3",
 		"execa": "6.1.0"
 	}
diff --git a/packages/client/package.json b/packages/client/package.json
index 9de500f3ab..7b8ee0cf37 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -13,27 +13,6 @@
 		"@discordapp/twemoji": "13.1.1",
 		"@fortawesome/fontawesome-free": "6.1.1",
 		"@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",
 		"abort-controller": "3.0.0",
@@ -117,6 +96,27 @@
 		"ws": "8.5.0"
 	},
 	"devDependencies": {
+		"@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/eslint-plugin": "5.18.0",
 		"cross-env": "7.0.3",
 		"cypress": "9.5.3",

From 0d9f5306cdfbf60dad7e8228891b237a293b16a4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 16 Apr 2022 13:31:12 +0900
Subject: [PATCH 004/258] remove unused locale

---
 locales/eo-UY.yml | 126 ----------------------------------------------
 locales/index.js  |   1 -
 2 files changed, 127 deletions(-)
 delete mode 100644 locales/eo-UY.yml

diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml
deleted file mode 100644
index 65a5931574..0000000000
--- a/locales/eo-UY.yml
+++ /dev/null
@@ -1,126 +0,0 @@
----
-ok: "Bone"
-exportRequested: "Vi petis elporton. Ĝi povas bezoni longan tempon. Elportaĵo estos aldonita al disko post elportado."
-importRequested: "Vi petis enportadon. Ĝi povas bezoni longan tempon. "
-pageLoadError: "Fuŝiĝis enlegi paĝon."
-pageLoadErrorDescription: "Ordinare tio okazas pro la retkondiĉo aŭ la staplo de retumilo.\nPurigu la staplon, aŭ refaru poste. "
-youShouldUpgradeClient: "Por montri ĉi paĝon, bonvolu enlegi refoje kaj uzi klienton novversian."
-flagAsCatDescription: "Flagu por montri ke la konton havas kato."
-flagShowTimelineReplies: "Montri respondon de notoj en templinio."
-intro: "Instalado de Misskey finiĝis! Kreu administran konton."
-whenServerDisconnected: "Kiam vi malligiĝas de servilo"
-caseSensitive: "Distingi usklecon"
-markAsReadAllUnreadNotes: "Marki ĉiujn afiŝojn kiel legita"
-checking: "kontrolante..."
-signinFailed: "Fuŝiĝis ensaluti. Bonvolu kontroli uzantnomon kaj pasvorton."
-promote: "Reklamata"
-updateRemoteUser: "Aktualigi informon de foraj uzantoj"
-yourAccountSuspendedTitle: "La konto estas frostigita."
-tokenRequested: "Alirpermeso al konto"
-abuseReports: "Raportoj"
-reportAbuse: "Raportoj"
-reportAbuseOf: "raporti {name}n"
-reporter: "Informanto"
-reporterOrigin: "Raportanto"
-pollVotesCount: "Nombro de voĉdonado"
-invalidValue: "Nevalida valoro"
-notRecommended: "Evitindaj"
-switchAccount: "Ŝanĝi konton"
-configure: "Agordi"
-popularPosts: "Populara noto"
-expiration: "Limtempo"
-priority: "Prioritato"
-squareAvatars: "Montri bildsimbolon kiel kvadrata"
-misskeyUpdated: "Misskey ĝisdatiĝis!"
-whatIsNew: "Montri novaĵojn"
-accountDeletionInProgress: "La konto estas forviŝanta."
-resolved: "Solvita"
-unresolved: "Nesolvita"
-filter: "Filtrilo"
-deleteAccountConfirm: "La konto estos forviŝita. Ĉu vi daŭrigas fari?"
-voteConfirm: "Ĉu vi voĉdonas {choice}n?"
-hide: "Kaŝi"
-overridedDeviceKind: "tipo de aparato"
-size: "Grandeco"
-searchByGoogle: "Serĉi en Guglo-Serĉilo"
-mutePeriod: "Daŭro de silentigo"
-indefinitely: "Sen limdato"
-tenMinutes: "Je 10 minutoj"
-oneHour: "Je 1 horo"
-oneDay: "Je 1 tago"
-oneWeek: "Je 1 semajno"
-failedToFetchAccountInformation: "Malsukcesas akiri informon de konto"
-_emailUnavailable:
-  mx: "Ĉi retpoŝto-servilo ne estas uzebla."
-_signup:
-  almostThere: "Preskaŭ plenumita"
-_accountDelete:
-  requestAccountDelete: "Peti forviŝi konton"
-  started: "Forviŝado komenciĝis."
-_gallery:
-  my: "Mia afiŝo"
-_aboutMisskey:
-  donate: "Mondonaci al Misskey"
-_channel:
-  notesCount: "{n} notoj"
-_theme:
-  explore: "Serĉi koloraron"
-  install: "Instali koloraron"
-  installedThemes: "Instalita koloraro"
-  make: "Krei koloraron"
-  addConstant: "Aldoni konstanton"
-  constant: "Konstanto"
-  keys:
-    shadow: "Ombro"
-    infoBg: "Fono de informo"
-_widgets:
-  photos: "Fotoj"
-_cw:
-  chars: "{count} literoj"
-_poll:
-  expiration: "Limtempo"
-_pages:
-  script:
-    blocks:
-      _add:
-        arg1: "A"
-        arg2: "B"
-      _subtract:
-        arg1: "A"
-        arg2: "B"
-      _multiply:
-        arg1: "A"
-        arg2: "B"
-      _divide:
-        arg1: "A"
-        arg2: "B"
-      _mod:
-        arg1: "A"
-        arg2: "B"
-      _eq:
-        arg1: "A"
-        arg2: "B"
-      _notEq:
-        arg1: "A"
-        arg2: "B"
-      _and:
-        arg1: "A"
-        arg2: "B"
-      _or:
-        arg1: "A"
-        arg2: "B"
-      _lt:
-        arg1: "A"
-        arg2: "B"
-      _gt:
-        arg1: "A"
-        arg2: "B"
-      _ltEq:
-        arg1: "A"
-        arg2: "B"
-      _gtEq:
-        arg1: "A"
-        arg2: "B"
-_notification:
-  _types:
-    pollEnded: "Enketo finiĝis"
diff --git a/locales/index.js b/locales/index.js
index b271b79b73..98c30fe016 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -19,7 +19,6 @@ const languages = [
 	'da-DK',
 	'de-DE',
 	'en-US',
-	'eo-UY',
 	'es-ES',
 	'fr-FR',
 	'id-ID',

From 4907dc91f7ebad802d29b5d42483b02f6702eb16 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 16 Apr 2022 17:18:51 +0900
Subject: [PATCH 005/258] lint

---
 .../src/remote/activitypub/models/person.ts   | 26 +++++++++----------
 1 file changed, 13 insertions(+), 13 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 88661865da..4267f46fb3 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -3,15 +3,7 @@ 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 +24,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;
 
@@ -400,10 +400,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 +461,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;

From 44a01c4b5a46d2b8b27cb6315babac8484051f7b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 16 Apr 2022 17:19:30 +0900
Subject: [PATCH 006/258] refactoe

---
 packages/backend/src/@types/http-signature.d.ts |  2 +-
 packages/backend/src/misc/fetch.ts              | 10 +++++-----
 packages/backend/src/server/api/streaming.ts    |  2 +-
 packages/backend/src/server/index.ts            |  2 +-
 packages/backend/test/utils.ts                  |  2 +-
 5 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts
index 8d484312dc..0426cb8bcd 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';
+	import { IncomingMessage, ClientRequest } from 'node:http';
 
 	interface ISignature {
 		keyId: string;
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/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/index.ts b/packages/backend/src/server/index.ts
index a68cebfeb2..d00bf8996f 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -3,7 +3,7 @@
  */
 
 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';
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 32a030f933..09e812f437 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -4,7 +4,7 @@ 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 * as http from 'node:http';
 import loadConfig from '../src/config/load.js';
 import { SIGKILL } from 'constants';
 import { entities } from '../src/db/postgre.js';

From d39465085c86919c54a2defb8c0cb1f79e5e0ff8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 12:59:41 +0900
Subject: [PATCH 007/258] refactor: fix type

---
 .../src/models/repositories/drive-file.ts     | 45 ++++++++++++++++---
 .../backend/src/models/repositories/page.ts   |  6 +--
 .../src/remote/activitypub/models/mention.ts  | 10 ++---
 .../api/endpoints/drive/files/find-by-hash.ts |  2 +-
 .../server/api/endpoints/drive/files/show.ts  |  6 +--
 5 files changed, 51 insertions(+), 18 deletions(-)

diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index 69dc1721c2..c15f5b6058 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,
@@ -111,7 +111,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 +178,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/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/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 { toArray, unique } from '@/prelude/array.js';
-import { IObject, isMention, IApMention } from '../type.js';
-import { resolvePerson } from './person.js';
 import promiseLimit from 'promise-limit';
-import Resolver from '../resolver.js';
+import { toArray, unique } from '@/prelude/array.js';
 import { CacheableUser, User } from '@/models/entities/user.js';
+import { IObject, isMention, IApMention } from '../type.js';
+import Resolver from '../resolver.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/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
index f9b4ea89ea..0b74cb9f01 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'],
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..fb19345fee 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'],
@@ -51,7 +51,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 });

From e0a4864bea2cad19c4baf4b986bec8c6edd481ff Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:01:30 +0900
Subject: [PATCH 008/258] refactor: fix type

---
 .../backend/src/server/api/endpoints/notes/reactions.ts   | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

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 { DeepPartial, FindOptionsWhere } from 'typeorm';
+import { NoteReactions } from '@/models/index.js';
+import { NoteReaction } from '@/models/entities/note-reaction.js';
 import define from '../../define.js';
 import { ApiError } from '../../error.js';
-import { NoteReactions } from '@/models/index.js';
-import { DeepPartial } from 'typeorm';
-import { NoteReaction } from '@/models/entities/note-reaction.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) {
 		// ローカルリアクションはホスト名が . とされているが

From ddd655c0c1c585fb367151191662af2afa8f35e9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:14:29 +0900
Subject: [PATCH 009/258] refactor: fix type

---
 packages/backend/src/remote/activitypub/renderer/flag.ts | 2 +-
 packages/backend/src/remote/activitypub/type.ts          | 4 ++--
 packages/backend/src/server/api/endpoints/pages/show.ts  | 8 ++++----
 3 files changed, 7 insertions(+), 7 deletions(-)

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/type.ts b/packages/backend/src/remote/activitypub/type.ts
index 2051d2624d..124f67cfb1 100644
--- a/packages/backend/src/remote/activitypub/type.ts
+++ b/packages/backend/src/remote/activitypub/type.ts
@@ -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 {
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 });

From 3770bb65768693334d0783265c67b419ed733814 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:19:05 +0900
Subject: [PATCH 010/258] =?UTF-8?q?Streaming=E7=B5=8C=E7=94=B1=E3=81=A7?=
 =?UTF-8?q?=E3=81=AEAPI=E3=83=AA=E3=82=AF=E3=82=A8=E3=82=B9=E3=83=88?=
 =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../backend/src/server/api/stream/index.ts    | 42 ++++---------------
 1 file changed, 7 insertions(+), 35 deletions(-)

diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index b803478281..7077047b76 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,20 +1,18 @@
-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 * as websocket from 'websocket';
+import readNote from '@/services/note/read.js';
 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
@@ -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]);

From 1ee757cc5fbee24fa59b529a6fd022b5ec956e6e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:21:07 +0900
Subject: [PATCH 011/258] refactor: fix type

---
 .../src/server/api/endpoints/notes/translate.ts   | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

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,

From 2d2b3edaafeef5097eb34b45163c1966b54c42b0 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:26:31 +0900
Subject: [PATCH 012/258] refactor: fix type

---
 packages/backend/src/remote/activitypub/resolver.ts | 10 +++++-----
 packages/backend/src/remote/activitypub/type.ts     |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts
index c1269c75c5..334eae9843 100644
--- a/packages/backend/src/remote/activitypub/resolver.ts
+++ b/packages/backend/src/remote/activitypub/resolver.ts
@@ -2,10 +2,10 @@ 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 { 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 { signedGet } from './request.js';
+import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
 
 export default class Resolver {
 	private history: Set<string>;
@@ -56,13 +56,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');
diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts
index 124f67cfb1..ef5b98b59e 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;

From a671f9102d5d3e212d1e621a435dd6a147c4e191 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 13:31:45 +0900
Subject: [PATCH 013/258] refactor: fix type

---
 .../api/endpoints/admin/announcements/list.ts | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

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)!,
+	}));
 });

From 31b216f66741aad3f63df0117aaeb31270265558 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 14:42:13 +0900
Subject: [PATCH 014/258] refactor: fix type

---
 packages/backend/package.json                       |  5 +++--
 packages/backend/src/boot/index.ts                  |  4 ++--
 packages/backend/src/daemons/queue-stats.ts         |  4 ++--
 packages/backend/src/daemons/server-stats.ts        |  4 ++--
 .../src/server/api/stream/channels/queue-stats.ts   |  4 ++--
 .../src/server/api/stream/channels/server-stats.ts  |  4 ++--
 packages/backend/yarn.lock                          | 13 +++++++++----
 7 files changed, 22 insertions(+), 16 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 7fe3757eb3..f07a15bc54 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -14,6 +14,7 @@
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
+		"@bull-board/koa": "3.10.3",
 		"@discordapp/twemoji": "13.1.1",
 		"@elastic/elasticsearch": "7.11.0",
 		"@koa/cors": "3.1.0",
@@ -23,7 +24,6 @@
 		"@syuilo/aiscript": "0.11.1",
 		"@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",
@@ -125,10 +125,11 @@
 		"web-push": "3.4.5",
 		"websocket": "1.0.34",
 		"ws": "8.5.0",
-		"xev": "2.0.1"
+		"xev": "3.0.2"
 	},
 	"devDependencies": {
 		"@redocly/openapi-core": "1.0.0-beta.93",
+		"@types/semver": "7.3.9",
 		"@types/bcryptjs": "2.4.2",
 		"@types/bull": "3.15.8",
 		"@types/cbor": "6.0.0",
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/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/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/yarn.lock b/packages/backend/yarn.lock
index 5cd71acf9c..e4421db9d7 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -777,6 +777,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"
@@ -7297,10 +7302,10 @@ ws@^8.2.3:
   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"

From 6b31ea19924d0defe5ddadcc088df5706a10ff97 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 17:30:27 +0900
Subject: [PATCH 015/258] refactor: fix type

---
 .../backend/src/server/api/service/discord.ts | 30 +++++++++----------
 .../backend/src/server/api/service/github.ts  | 30 +++++++++----------
 .../backend/src/server/api/service/twitter.ts | 20 ++++++++-----
 3 files changed, 43 insertions(+), 37 deletions(-)

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;
 		}

From 02bb36cdc47028bdd783b5ddc2a73ee31e8db068 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 20:44:21 +0900
Subject: [PATCH 016/258] refactor: fix type

---
 .../remote/activitypub/kernel/move/index.ts   |  0
 .../src/remote/activitypub/models/person.ts   |  4 +-
 .../src/server/activitypub/followers.ts       | 24 +++++-------
 .../src/server/activitypub/following.ts       | 21 ++++-------
 .../backend/src/server/activitypub/outbox.ts  | 37 ++++++++++---------
 .../server/api/endpoints/admin/show-users.ts  |  6 +--
 .../backend/src/server/api/stream/index.ts    |  2 +-
 7 files changed, 41 insertions(+), 53 deletions(-)
 delete mode 100644 packages/backend/src/remote/activitypub/kernel/move/index.ts

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
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 4267f46fb3..f722e74702 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -271,7 +271,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 +289,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);
 
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/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/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index 7077047b76..2d23145f14 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -19,7 +19,7 @@ import { StreamEventEmitter, StreamMessages } from './types.js';
  */
 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

From ff8313b48b3838005c3c0430c4fa64fc5f1a30ef Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 20:51:59 +0900
Subject: [PATCH 017/258] refactor

---
 .../src/server/api/endpoints/notes/create.ts  | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 9de05918c0..40a3ba73ca 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',
 		} },
@@ -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'],
 		},
@@ -178,14 +178,14 @@ export default define(meta, paramDef, async (ps, user) => {
 		});
 	}
 
-	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 +201,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 +234,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 });
 

From 31e5c5f69a565b6aaef3f79bcba4bf5e2beee170 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 20:58:37 +0900
Subject: [PATCH 018/258] refactor

Resolve #8467
---
 packages/backend/package.json                 |  1 -
 packages/backend/src/misc/cafy-id.ts          | 33 -------------------
 .../src/remote/activitypub/models/person.ts   | 32 ++++++++++++------
 packages/backend/yarn.lock                    |  5 ---
 4 files changed, 22 insertions(+), 49 deletions(-)
 delete mode 100644 packages/backend/src/misc/cafy-id.ts

diff --git a/packages/backend/package.json b/packages/backend/package.json
index f07a15bc54..2354c95646 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -35,7 +35,6 @@
 		"broadcast-channel": "4.10.0",
 		"bull": "4.8.1",
 		"cacheable-lookup": "6.0.4",
-		"cafy": "15.2.1",
 		"cbor": "8.1.0",
 		"chalk": "5.0.1",
 		"chalk-template": "0.4.0",
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/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index f722e74702..6097e3b6ed 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -1,7 +1,6 @@
 import { URL } from 'node:url';
 import promiseLimit from 'promise-limit';
 
-import $, { Context } from 'cafy';
 import config from '@/config/index.js';
 import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
 import { Note } from '@/models/entities/note.js';
@@ -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) {
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index e4421db9d7..981c359dd7 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1591,11 +1591,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"

From ce51ef5df531dc652ec73028bed1216ea49eea67 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 21:01:02 +0900
Subject: [PATCH 019/258] refactor

---
 .../src/remote/activitypub/renderer/note.ts      | 16 ++++++++--------
 packages/backend/src/services/note/delete.ts     |  8 ++++----
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index 679c8bbfe4..e8d429e5de 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[]) => {
@@ -83,7 +83,7 @@ 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;
+	let poll: Poll | null = null;
 
 	if (note.hasPoll) {
 		poll = await Polls.findOneBy({ noteId: note.id });
@@ -159,7 +159,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/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) },
 		);
 	}
 

From d338ea2591f724e485aa84c824268371e77a6ad4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 17 Apr 2022 21:18:18 +0900
Subject: [PATCH 020/258] fix ogp rendering and refactor

---
 packages/backend/src/models/entities/user.ts  |  4 +--
 .../backend/src/models/repositories/user.ts   | 28 +++++++++++++------
 .../backend/src/server/api/common/signin.ts   |  2 +-
 packages/backend/src/server/index.ts          | 20 ++++++-------
 packages/backend/src/server/web/feed.ts       |  6 ++--
 packages/backend/src/server/web/index.ts      | 19 ++++++++-----
 .../backend/src/server/web/views/clip.pug     |  2 +-
 .../backend/src/server/web/views/note.pug     |  2 +-
 .../backend/src/server/web/views/page.pug     |  2 +-
 .../backend/src/server/web/views/user.pug     |  3 +-
 10 files changed, 51 insertions(+), 37 deletions(-)

diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index c76824c977..29d9a0c2ca 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -1,6 +1,6 @@
 import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
-import { DriveFile } from './drive-file.js';
 import { id } from '../id.js';
+import { DriveFile } from './drive-file.js';
 
 @Entity()
 @Index(['usernameLower', 'host'], { unique: true })
@@ -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/user.ts b/packages/backend/src/models/repositories/user.ts
index 2f4b7d6787..541fbaf003 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -1,7 +1,6 @@
 import { EntityRepository, Repository, In, Not } from 'typeorm';
 import Ajv from 'ajv';
 import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
-import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
 import config from '@/config/index.js';
 import { Packed } from '@/misc/schema.js';
 import { awaitAll, Promiseable } from '@/prelude/await-all.js';
@@ -9,8 +8,9 @@ import { populateEmojis } from '@/misc/populate-emojis.js';
 import { getAntennas } from '@/misc/antenna-cache.js';
 import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
 import { Cache } from '@/misc/cache.js';
-import { Instance } from '../entities/instance.js';
 import { db } from '@/db/postgre.js';
+import { Instance } from '../entities/instance.js';
+import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances, DriveFiles } from '../index.js';
 
 const userInstanceCache = new Cache<Instance | null>(1000 * 60 * 60 * 3);
 
@@ -112,7 +112,7 @@ export const UserRepository = db.getRepository(User).extend({
 		const joinings = await UserGroupJoinings.findBy({ userId: userId });
 
 		const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
-			.where(`message.groupId = :groupId`, { groupId: j.userGroupId })
+			.where('message.groupId = :groupId', { groupId: j.userGroupId })
 			.andWhere('message.userId != :userId', { userId: userId })
 			.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
 			.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
@@ -204,8 +204,18 @@ export const UserRepository = db.getRepository(User).extend({
 		);
 	},
 
-	getAvatarUrl(user: User): string {
-		// TODO: avatarIdがあるがavatarがない(JOINされてない)場合のハンドリング
+	async getAvatarUrl(user: User): Promise<string> {
+		if (user.avatar) {
+			return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
+		} else if (user.avatarId) {
+			const avatar = await DriveFiles.findOneByOrFail({ id: user.avatarId });
+			return DriveFiles.getPublicUrl(avatar, true) || this.getIdenticonUrl(user.id);
+		} else {
+			return this.getIdenticonUrl(user.id);
+		}
+	},
+
+	getAvatarUrlSync(user: User): string {
 		if (user.avatar) {
 			return DriveFiles.getPublicUrl(user.avatar, true) || this.getIdenticonUrl(user.id);
 		} else {
@@ -223,7 +233,7 @@ export const UserRepository = db.getRepository(User).extend({
 		options?: {
 			detail?: D,
 			includeSecrets?: boolean,
-		}
+		},
 	): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
 		const opts = Object.assign({
 			detail: false,
@@ -274,7 +284,7 @@ export const UserRepository = db.getRepository(User).extend({
 			name: user.name,
 			username: user.username,
 			host: user.host,
-			avatarUrl: this.getAvatarUrl(user),
+			avatarUrl: this.getAvatarUrlSync(user),
 			avatarBlurhash: user.avatar?.blurhash || null,
 			avatarColor: null, // 後方互換性のため
 			isAdmin: user.isAdmin || falsy,
@@ -283,7 +293,7 @@ export const UserRepository = db.getRepository(User).extend({
 			isCat: user.isCat || falsy,
 			instance: user.host ? userInstanceCache.fetch(user.host,
 				() => Instances.findOneBy({ host: user.host! }),
-				v => v != null
+				v => v != null,
 			).then(instance => instance ? {
 				name: instance.name,
 				softwareName: instance.softwareName,
@@ -403,7 +413,7 @@ export const UserRepository = db.getRepository(User).extend({
 		options?: {
 			detail?: D,
 			includeSecrets?: boolean,
-		}
+		},
 	): Promise<IsUserDetailed<D>[]> {
 		return Promise.all(users.map(u => this.pack(u, me, options)));
 	},
diff --git a/packages/backend/src/server/api/common/signin.ts b/packages/backend/src/server/api/common/signin.ts
index f1dccee2ce..038fd8d96e 100644
--- a/packages/backend/src/server/api/common/signin.ts
+++ b/packages/backend/src/server/api/common/signin.ts
@@ -9,7 +9,7 @@ import { publishMainStream } from '@/services/stream.js';
 export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
 	if (redirect) {
 		//#region Cookie
-		ctx.cookies.set('igi', user.token, {
+		ctx.cookies.set('igi', user.token!, {
 			path: '/',
 			// SEE: https://github.com/koajs/koa/issues/974
 			// When using a SSL proxy it should be configured to add the "X-Forwarded-Proto: https" header
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index d00bf8996f..c1a2a6dfff 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -10,23 +10,23 @@ import mount from 'koa-mount';
 import koaLogger from 'koa-logger';
 import * as slow from 'koa-slow';
 
-import activityPub from './activitypub.js';
-import nodeinfo from './nodeinfo.js';
-import wellKnown from './well-known.js';
+import { IsNull } from 'typeorm';
 import config from '@/config/index.js';
-import apiServer from './api/index.js';
-import fileServer from './file/index.js';
-import proxyServer from './proxy/index.js';
-import webServer from './web/index.js';
 import Logger from '@/services/logger.js';
-import { envOption } from '../env.js';
 import { UserProfiles, Users } from '@/models/index.js';
 import { genIdenticon } from '@/misc/gen-identicon.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { publishMainStream } from '@/services/stream.js';
 import * as Acct from '@/misc/acct.js';
+import { envOption } from '../env.js';
+import activityPub from './activitypub.js';
+import nodeinfo from './nodeinfo.js';
+import wellKnown from './well-known.js';
+import apiServer from './api/index.js';
+import fileServer from './file/index.js';
+import proxyServer from './proxy/index.js';
+import webServer from './web/index.js';
 import { initializeStreamingServer } from './api/streaming.js';
-import { IsNull } from 'typeorm';
 
 export const serverLogger = new Logger('server', 'gray', false);
 
@@ -81,7 +81,7 @@ router.get('/avatar/@:acct', async ctx => {
 	});
 
 	if (user) {
-		ctx.redirect(Users.getAvatarUrl(user));
+		ctx.redirect(Users.getAvatarUrlSync(user));
 	} else {
 		ctx.redirect('/static-assets/user-unknown.png');
 	}
diff --git a/packages/backend/src/server/web/feed.ts b/packages/backend/src/server/web/feed.ts
index eba8dc58d4..4abe2885cf 100644
--- a/packages/backend/src/server/web/feed.ts
+++ b/packages/backend/src/server/web/feed.ts
@@ -1,8 +1,8 @@
 import { Feed } from 'feed';
+import { In, IsNull } from 'typeorm';
 import config from '@/config/index.js';
 import { User } from '@/models/entities/user.js';
-import { Notes, DriveFiles, UserProfiles } from '@/models/index.js';
-import { In, IsNull } from 'typeorm';
+import { Notes, DriveFiles, UserProfiles, Users } from '@/models/index.js';
 
 export default async function(user: User) {
 	const author = {
@@ -29,7 +29,7 @@ export default async function(user: User) {
 		generator: 'Misskey',
 		description: `${user.notesCount} Notes, ${profile.ffVisibility === 'public' ? user.followingCount : '?'} Following, ${profile.ffVisibility === 'public' ? user.followersCount : '?'} Followers${profile.description ? ` · ${profile.description}` : ''}`,
 		link: author.link,
-		image: user.avatarUrl ? user.avatarUrl : undefined,
+		image: await Users.getAvatarUrl(user),
 		feedLinks: {
 			json: `${author.link}.json`,
 			atom: `${author.link}.atom`,
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 48bf6f7338..34d56cfd0c 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -11,20 +11,20 @@ import send from 'koa-send';
 import favicon from 'koa-favicon';
 import views from 'koa-views';
 import { createBullBoard } from '@bull-board/api';
-import { BullAdapter  } from '@bull-board/api/bullAdapter.js';
+import { BullAdapter } from '@bull-board/api/bullAdapter.js';
 import { KoaAdapter } from '@bull-board/koa';
 
-import packFeed from './feed.js';
+import { IsNull } from 'typeorm';
 import { fetchMeta } from '@/misc/fetch-meta.js';
-import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
 import config from '@/config/index.js';
 import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js';
 import * as Acct from '@/misc/acct.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
+import { queues } from '@/queue/queues.js';
+import { genOpenapiSpec } from '../api/openapi/gen-spec.js';
 import { urlPreviewHandler } from './url-preview.js';
 import { manifestHandler } from './manifest.js';
-import { queues } from '@/queue/queues.js';
-import { IsNull } from 'typeorm';
+import packFeed from './feed.js';
 
 const _filename = fileURLToPath(import.meta.url);
 const _dirname = dirname(_filename);
@@ -127,7 +127,7 @@ router.get('/twemoji/(.*)', async ctx => {
 		return;
 	}
 
-	ctx.set('Content-Security-Policy', `default-src 'none'; style-src 'unsafe-inline'`);
+	ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
 
 	await send(ctx as any, path, {
 		root: `${_dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`,
@@ -235,6 +235,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
 
 		await ctx.render('user', {
 			user, profile, me,
+			avatarUrl: await Users.getAvatarUrl(user),
 			sub: ctx.params.sub,
 			instanceName: meta.name || 'Misskey',
 			icon: meta.iconUrl,
@@ -274,6 +275,7 @@ router.get('/notes/:note', async (ctx, next) => {
 		await ctx.render('note', {
 			note: _note,
 			profile,
+			avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
 			// TODO: Let locale changeable by instance setting
 			summary: getNoteSummary(_note),
 			instanceName: meta.name || 'Misskey',
@@ -315,6 +317,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
 		await ctx.render('page', {
 			page: _page,
 			profile,
+			avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: page.userId })),
 			instanceName: meta.name || 'Misskey',
 			icon: meta.iconUrl,
 			themeColor: meta.themeColor,
@@ -346,6 +349,7 @@ router.get('/clips/:clip', async (ctx, next) => {
 		await ctx.render('clip', {
 			clip: _clip,
 			profile,
+			avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: clip.userId })),
 			instanceName: meta.name || 'Misskey',
 			icon: meta.iconUrl,
 			themeColor: meta.themeColor,
@@ -370,6 +374,7 @@ router.get('/gallery/:post', async (ctx, next) => {
 		await ctx.render('gallery-post', {
 			post: _post,
 			profile,
+			avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: post.userId })),
 			instanceName: meta.name || 'Misskey',
 			icon: meta.iconUrl,
 			themeColor: meta.themeColor,
@@ -434,7 +439,7 @@ router.get('/cli', async ctx => {
 	});
 });
 
-const override = (source: string, target: string, depth: number = 0) =>
+const override = (source: string, target: string, depth = 0) =>
 	[, ...target.split('/').filter(x => x), ...source.split('/').filter(x => x).splice(depth)].join('/');
 
 router.get('/flush', async ctx => {
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
index 7a84d50f6c..4c692bf59b 100644
--- a/packages/backend/src/server/web/views/clip.pug
+++ b/packages/backend/src/server/web/views/clip.pug
@@ -16,7 +16,7 @@ block og
 	meta(property='og:title'       content= title)
 	meta(property='og:description' content= clip.description)
 	meta(property='og:url'         content= url)
-	meta(property='og:image'       content= user.avatarUrl)
+	meta(property='og:image'       content= avatarUrl)
 
 block meta
 	if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 34b03f9833..65696ea138 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -17,7 +17,7 @@ block og
 	meta(property='og:title'       content= title)
 	meta(property='og:description' content= summary)
 	meta(property='og:url'         content= url)
-	meta(property='og:image'       content= user.avatarUrl)
+	meta(property='og:image'       content= avatarUrl)
 
 block meta
 	if user.host || isRenote || profile.noCrawle
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index b6c9548025..4219e76a52 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -16,7 +16,7 @@ block og
 	meta(property='og:title'       content= title)
 	meta(property='og:description' content= page.summary)
 	meta(property='og:url'         content= url)
-	meta(property='og:image'       content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : user.avatarUrl)
+	meta(property='og:image'       content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
 
 block meta
 	if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
index 2adec0f889..119993fdb5 100644
--- a/packages/backend/src/server/web/views/user.pug
+++ b/packages/backend/src/server/web/views/user.pug
@@ -3,7 +3,6 @@ extends ./base
 block vars
 	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
 	- const url = `${config.url}/@${(user.host ? `${user.username}@${user.host}` : user.username)}`;
-	- const img = user.avatarUrl || null;
 
 block title
 	= `${title} | ${instanceName}`
@@ -16,7 +15,7 @@ block og
 	meta(property='og:title'       content= title)
 	meta(property='og:description' content= profile.description)
 	meta(property='og:url'         content= url)
-	meta(property='og:image'       content= img)
+	meta(property='og:image'       content= avatarUrl)
 
 block meta
 	if user.host || profile.noCrawle

From 3cf226d534d10261c693ed51ba3db440fe8e8be9 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 19 Apr 2022 11:46:41 +0200
Subject: [PATCH 021/258] await promises (#8519)

---
 .../backend/src/services/blocking/create.ts     | 17 ++++++-----------
 .../backend/src/services/following/create.ts    |  6 ++++--
 .../backend/src/services/following/delete.ts    | 11 +++++------
 3 files changed, 15 insertions(+), 19 deletions(-)

diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index 5e96e5037f..d4b28d8d77 100644
--- a/packages/backend/src/services/blocking/create.ts
+++ b/packages/backend/src/services/blocking/create.ts
@@ -95,17 +95,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 Promises.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/following/create.ts b/packages/backend/src/services/following/create.ts
index 7491c44f83..f521118d48 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 Promises.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..1e425c2689 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 Promises.all([
+		Users.decrement({ id: follower.id }, 'followingCount', 1),
+		Users.decrement({ id: followee.id }, 'followersCount', 1),
+	]);
 	//#endregion
 
 	//#region Update instance stats

From 60620bdb2571749d66d847ad7baa5c377165e37a Mon Sep 17 00:00:00 2001
From: xianon <xianon@hotmail.co.jp>
Date: Tue, 19 Apr 2022 22:59:39 +0900
Subject: [PATCH 022/258] =?UTF-8?q?fix:=20=E3=82=A2=E3=83=B3=E3=83=86?=
 =?UTF-8?q?=E3=83=8A=E3=80=81=E3=82=AF=E3=83=AA=E3=83=83=E3=83=97=E3=80=81?=
 =?UTF-8?q?=E3=83=AA=E3=82=B9=E3=83=88=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92?=
 =?UTF-8?q?=E9=80=9F=E3=81=8F=E3=81=99=E3=82=8B=20(#8518)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* アンテナノートを取得するクエリがタイムアウトしないように速くする

* テーブル名を直接指定しないようにする

* クリップの取得を速くする

* リストの取得を速くする
---
 .../backend/src/server/api/endpoints/antennas/notes.ts    | 8 ++------
 packages/backend/src/server/api/endpoints/clips/notes.ts  | 8 ++------
 .../src/server/api/endpoints/notes/user-list-timeline.ts  | 8 ++------
 3 files changed, 6 insertions(+), 18 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 004e4c131d..8aac55b4a0 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -57,13 +57,9 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new ApiError(meta.errors.noSuchAntenna);
 	}
 
-	const antennaQuery = AntennaNotes.createQueryBuilder('joining')
-		.select('joining.noteId')
-		.where('joining.antennaId = :antennaId', { antennaId: antenna.id });
-
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'),
 			ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-		.andWhere(`note.id IN (${ antennaQuery.getQuery() })`)
+		.innerJoin(AntennaNotes.metadata.targetName, 'antennaNote', 'antennaNote.noteId = note.id')
 		.innerJoinAndSelect('note.user', 'user')
 		.leftJoinAndSelect('user.avatar', 'avatar')
 		.leftJoinAndSelect('user.banner', 'banner')
@@ -75,7 +71,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		.leftJoinAndSelect('renote.user', 'renoteUser')
 		.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
 		.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
-		.setParameters(antennaQuery.getParameters());
+		.andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
 
 	generateVisibilityQuery(query, user);
 	generateMutedUserQuery(query, user);
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index 4b6782fca0..4ace747efe 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -57,12 +57,8 @@ export default define(meta, paramDef, async (ps, user) => {
 		throw new ApiError(meta.errors.noSuchClip);
 	}
 
-	const clipQuery = ClipNotes.createQueryBuilder('joining')
-		.select('joining.noteId')
-		.where('joining.clipId = :clipId', { clipId: clip.id });
-
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
-		.andWhere(`note.id IN (${ clipQuery.getQuery() })`)
+		.innerJoin(ClipNotes.metadata.targetName, 'clipNote', 'clipNote.noteId = note.id')
 		.innerJoinAndSelect('note.user', 'user')
 		.leftJoinAndSelect('user.avatar', 'avatar')
 		.leftJoinAndSelect('user.banner', 'banner')
@@ -74,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		.leftJoinAndSelect('renote.user', 'renoteUser')
 		.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
 		.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
-		.setParameters(clipQuery.getParameters());
+		.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
 
 	if (user) {
 		generateVisibilityQuery(query, user);
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 6c6402603a..fd4a879035 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -63,12 +63,8 @@ export default define(meta, paramDef, async (ps, user) => {
 	}
 
 	//#region Construct query
-	const listQuery = UserListJoinings.createQueryBuilder('joining')
-		.select('joining.userId')
-		.where('joining.userListId = :userListId', { userListId: list.id });
-
 	const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
-		.andWhere(`note.userId IN (${ listQuery.getQuery() })`)
+		.innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
 		.innerJoinAndSelect('note.user', 'user')
 		.leftJoinAndSelect('user.avatar', 'avatar')
 		.leftJoinAndSelect('user.banner', 'banner')
@@ -80,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		.leftJoinAndSelect('renote.user', 'renoteUser')
 		.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
 		.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
-		.setParameters(listQuery.getParameters());
+		.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
 
 	generateVisibilityQuery(query, user);
 

From e0c5401241a5cc3dcd3692a257a0da733c399927 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 19 Apr 2022 22:03:15 +0200
Subject: [PATCH 023/258] make emoji stand out more on reaction button

a slight shadow makes them easier to see
---
 packages/client/src/components/reactions-viewer.reaction.vue | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue
index 7dc079fde6..0688887f17 100644
--- a/packages/client/src/components/reactions-viewer.reaction.vue
+++ b/packages/client/src/components/reactions-viewer.reaction.vue
@@ -144,6 +144,10 @@ export default defineComponent({
 		> span {
 			color: var(--fgOnAccent);
 		}
+
+		> .mk-emoji {
+			filter: drop-shadow(0 0 3px var(--bg));
+		}
 	}
 
 	> span {

From f02508c25962dfa9190ffdf659b4b769bb528ca6 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 20 Apr 2022 09:30:29 +0900
Subject: [PATCH 024/258] Revert "make emoji stand out more on reaction button"

This reverts commit e0c5401241a5cc3dcd3692a257a0da733c399927.
---
 packages/client/src/components/reactions-viewer.reaction.vue | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue
index 0688887f17..7dc079fde6 100644
--- a/packages/client/src/components/reactions-viewer.reaction.vue
+++ b/packages/client/src/components/reactions-viewer.reaction.vue
@@ -144,10 +144,6 @@ export default defineComponent({
 		> span {
 			color: var(--fgOnAccent);
 		}
-
-		> .mk-emoji {
-			filter: drop-shadow(0 0 3px var(--bg));
-		}
 	}
 
 	> span {

From 9f3650b0efa1a1724b26e4f317b1746fa378d26b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 12:29:26 +0900
Subject: [PATCH 025/258] update deps

---
 packages/backend/package.json |  32 +--
 packages/backend/yarn.lock    | 303 +++++++++++++--------------
 packages/client/package.json  |  27 ++-
 packages/client/yarn.lock     | 383 ++++++++++++++++------------------
 4 files changed, 361 insertions(+), 384 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 2354c95646..3d0d5dd308 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -14,7 +14,7 @@
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
-		"@bull-board/koa": "3.10.3",
+		"@bull-board/koa": "3.10.4",
 		"@discordapp/twemoji": "13.1.1",
 		"@elastic/elasticsearch": "7.11.0",
 		"@koa/cors": "3.1.0",
@@ -22,18 +22,18 @@
 		"@koa/router": "9.0.1",
 		"@sinonjs/fake-timers": "9.1.1",
 		"@syuilo/aiscript": "0.11.1",
-		"@typescript-eslint/eslint-plugin": "5.18.0",
-		"@typescript-eslint/parser": "5.18.0",
+		"@typescript-eslint/eslint-plugin": "5.20.0",
+		"@typescript-eslint/parser": "5.20.0",
 		"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.1120.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.10.0",
-		"bull": "4.8.1",
+		"broadcast-channel": "4.11.0",
+		"bull": "4.8.2",
 		"cacheable-lookup": "6.0.4",
 		"cbor": "8.1.0",
 		"chalk": "5.0.1",
@@ -44,7 +44,7 @@
 		"date-fns": "2.28.0",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.13.0",
+		"eslint": "8.14.0",
 		"eslint-plugin-import": "2.26.0",
 		"feed": "4.2.2",
 		"file-type": "17.1.1",
@@ -52,7 +52,7 @@
 		"got": "12.0.3",
 		"hpagent": "0.1.2",
 		"http-signature": "1.3.6",
-		"ip-cidr": "3.0.4",
+		"ip-cidr": "3.0.7",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
 		"jsdom": "19.0.0",
@@ -100,15 +100,15 @@
 		"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.30.4",
 		"speakeasy": "2.0.0",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"style-loader": "3.3.1",
 		"summaly": "2.5.0",
 		"syslog-pro": "1.0.0",
-		"systeminformation": "5.11.9",
+		"systeminformation": "5.11.14",
 		"tinycolor2": "1.4.2",
 		"tmp": "0.2.1",
 		"ts-loader": "9.2.8",
@@ -116,7 +116,7 @@
 		"tsc-alias": "1.4.1",
 		"tsconfig-paths": "3.14.1",
 		"twemoji-parser": "14.0.0",
-		"typeorm": "0.3.5",
+		"typeorm": "0.3.6",
 		"typescript": "4.6.3",
 		"ulid": "2.3.0",
 		"unzipper": "0.10.11",
@@ -149,8 +149,8 @@
 		"@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/mocha": "9.1.1",
+		"@types/node": "17.0.25",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.4",
 		"@types/oauth": "0.9.1",
@@ -164,7 +164,7 @@
 		"@types/redis": "4.0.11",
 		"@types/rename": "1.0.4",
 		"@types/sanitize-html": "2.6.2",
-		"@types/sharp": "0.30.1",
+		"@types/sharp": "0.30.2",
 		"@types/sinonjs__fake-timers": "8.1.2",
 		"@types/speakeasy": "2.0.7",
 		"@types/tinycolor2": "1.4.3",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 981c359dd7..f7838b8b11 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -35,20 +35,20 @@
     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.10.4":
+  version "3.10.4"
+  resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.4.tgz#f29d95a9624224ceec0f3ff26ef2c2bba8106921"
+  integrity sha512-JJjMg8O/ELeaqkuL1Wsdn6rdQfH+/2+BfnFD0B7j4ZCtLVAPfsOUZYpLqSKUgaNizwp1nTw0e3L/EI0yvX5aiw==
   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.10.4":
+  version "3.10.4"
+  resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.4.tgz#8e54600bfd8e003a8d5838ae6e65f9ec8c9979f7"
+  integrity sha512-NO0kzgVrl5lGNnX6maBAuP6aecGvROGka3RJSALubDfsrQ3aWNuY2BjUMUvm4ZDVfAeYT3wPaak8rdRCwxYE2g==
   dependencies:
-    "@bull-board/api" "3.10.3"
-    "@bull-board/ui" "3.10.3"
+    "@bull-board/api" "3.10.4"
+    "@bull-board/ui" "3.10.4"
     ejs "^3.1.6"
     koa "^2.13.1"
     koa-mount "^4.0.0"
@@ -56,12 +56,12 @@
     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.10.4":
+  version "3.10.4"
+  resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.4.tgz#6455b4e75fdbec1bc2ee84fde2a6a283b3c77bc9"
+  integrity sha512-nqnE3wqqpso7ORPcmcGVesYeFkHwv3AsBdRV2W0VLtfBPGzMdqZ1sJeSTAmlanFZnvTprU4Eg/G0DcEeMUTGhA==
   dependencies:
-    "@bull-board/api" "3.10.3"
+    "@bull-board/api" "3.10.4"
 
 "@cspotcode/source-map-consumer@0.8.0":
   version "0.8.0"
@@ -110,10 +110,10 @@
     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.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
+  integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
@@ -649,10 +649,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 +666,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.25":
+  version "17.0.25"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
+  integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
 
 "@types/node@^14.11.8":
   version "14.17.9"
@@ -790,10 +790,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" "*"
 
@@ -850,14 +850,14 @@
   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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
+  integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/type-utils" "5.18.0"
-    "@typescript-eslint/utils" "5.18.0"
+    "@typescript-eslint/scope-manager" "5.20.0"
+    "@typescript-eslint/type-utils" "5.20.0"
+    "@typescript-eslint/utils" "5.20.0"
     debug "^4.3.2"
     functional-red-black-tree "^1.0.1"
     ignore "^5.1.8"
@@ -865,69 +865,69 @@
     semver "^7.3.5"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
+  integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
   dependencies:
-    "@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.20.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/typescript-estree" "5.20.0"
     debug "^4.3.2"
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
+  integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/visitor-keys" "5.20.0"
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
+  integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
   dependencies:
-    "@typescript-eslint/utils" "5.18.0"
+    "@typescript-eslint/utils" "5.20.0"
     debug "^4.3.2"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
+  integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
+  integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/visitor-keys" "5.20.0"
     debug "^4.3.2"
     globby "^11.0.4"
     is-glob "^4.0.3"
     semver "^7.3.5"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
+  integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
   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.20.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/typescript-estree" "5.20.0"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
+  integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
     eslint-visitor-keys "^3.0.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -1136,13 +1136,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"
@@ -1249,10 +1249,10 @@ async@^2.6.0:
   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@^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"
@@ -1271,10 +1271,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.1120.0:
+  version "2.1120.0"
+  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1120.0.tgz#a299f595448019c4b4b69fa9aa57fd58658497a6"
+  integrity sha512-3cKXUFxC3CDBbJ/JlXEKmJZKFZhqGii7idGaLxvV5/OzqEDUstYkHGX3TCJdQRHrRwpFvRVOekXSwLxBltqXuQ==
   dependencies:
     buffer "4.9.2"
     events "1.1.1"
@@ -1332,11 +1332,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"
@@ -1409,15 +1404,14 @@ braces@^3.0.1, braces@~3.0.2:
   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.11.0:
+  version "4.11.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e"
+  integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
-    microseconds "0.2.0"
-    nano-time "1.0.0"
+    microtime "3.0.0"
     oblivious-set "1.0.0"
     p-queue "6.6.2"
     rimraf "3.0.2"
@@ -1495,10 +1489,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.2:
+  version "4.8.2"
+  resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.2.tgz#0d02fe389777abe29d50fd46d123bc62e074cfcd"
+  integrity sha512-S7CNIL9+vsbLKwOGkUI6mawY5iABKQJLZn5a7KPnxAZrDhFXkrxsHHXLCKUR/+Oqys3Vk5ElWdj0SLtK84b1Nw==
   dependencies:
     cron-parser "^4.2.1"
     debuglog "^1.0.0"
@@ -1859,10 +1853,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.2.3:
+  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"
@@ -2710,12 +2704,12 @@ eslint-visitor-keys@^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.14.0:
+  version "8.14.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
+  integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
   dependencies:
-    "@eslint/eslintrc" "^1.2.1"
+    "@eslint/eslintrc" "^1.2.2"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -3700,10 +3694,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.7:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.7.tgz#22708dd4f2d3f6397c0fb7d647b44e3c565937e9"
+  integrity sha512-0cBBICDnmmpAdULMbMVdi4f0mSG+VWY/QBPL/OIIjuom14x7Y63VhpS/uSAOycasXOeGXah5y0eu//PDU51aNw==
   dependencies:
     ip-address "^7.1.0"
     jsbn "^1.1.0"
@@ -4573,11 +4567,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"
@@ -4655,10 +4644,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==
+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"
@@ -4918,13 +4910,6 @@ 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.1, nanoid@^3.1.30:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
@@ -4976,6 +4961,11 @@ node-abi@^3.3.0:
   dependencies:
     semver "^7.3.5"
 
+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-addon-api@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
@@ -5019,6 +5009,11 @@ node-fetch@^2.6.1:
   dependencies:
     whatwg-url "^5.0.0"
 
+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@^4.2.3:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@@ -6213,12 +6208,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,16 +6269,16 @@ 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.30.4:
+  version "0.30.4"
+  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.4.tgz#73d9daa63bbc20da189c9328d75d5d395fc8fb73"
+  integrity sha512-3Onig53Y6lji4NIZo69s14mERXXY/GV++6CzOYx/Rd8bnTwbhFbL09WZd7Ag/CCnA0WxFID8tkY0QReyfL6v0Q==
   dependencies:
-    color "^4.2.1"
+    color "^4.2.3"
     detect-libc "^2.0.1"
     node-addon-api "^4.3.0"
     prebuild-install "^7.0.1"
-    semver "^7.3.5"
+    semver "^7.3.7"
     simple-get "^4.0.1"
     tar-fs "^2.1.1"
     tunnel-agent "^0.6.0"
@@ -6642,10 +6637,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.14:
+  version "5.11.14"
+  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.14.tgz#21fcb6f05d33e17d69c236b9c1b3d9c53d1d2b3a"
+  integrity sha512-m8CJx3fIhKohanB0ExTk5q53uI1J0g5B09p77kU+KxnxRVpADVqTAwCg1PFelqKsj4LHd+qmVnumb511Hg4xow==
 
 tapable@^2.2.0:
   version "2.2.0"
@@ -6952,10 +6947,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"
diff --git a/packages/client/package.json b/packages/client/package.json
index 7b8ee0cf37..bf492a4978 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -13,14 +13,14 @@
 		"@discordapp/twemoji": "13.1.1",
 		"@fortawesome/fontawesome-free": "6.1.1",
 		"@syuilo/aiscript": "0.11.1",
-		"@typescript-eslint/parser": "5.18.0",
-		"@vue/compiler-sfc": "3.2.31",
+		"@typescript-eslint/parser": "5.20.0",
+		"@vue/compiler-sfc": "3.2.33",
 		"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",
+		"broadcast-channel": "4.11.0",
 		"chart.js": "3.7.1",
 		"chartjs-adapter-date-fns": "2.0.0",
 		"chartjs-plugin-gradient": "0.2.2",
@@ -31,14 +31,13 @@
 		"cssnano": "5.1.7",
 		"date-fns": "2.28.0",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.13.0",
-		"eslint-plugin-vue": "8.6.0",
+		"eslint": "8.14.0",
+		"eslint-plugin-vue": "8.7.1",
 		"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",
@@ -53,7 +52,7 @@
 		"portscanner": "2.2.0",
 		"postcss": "8.4.12",
 		"postcss-loader": "6.2.1",
-		"prismjs": "1.27.0",
+		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
 		"promise-limit": "2.7.0",
 		"pug": "3.0.2",
@@ -64,7 +63,7 @@
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
-		"sass": "1.50.0",
+		"sass": "1.50.1",
 		"sass-loader": "12.6.0",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
@@ -73,7 +72,7 @@
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
 		"three": "0.139.2",
-		"throttle-debounce": "4.0.0",
+		"throttle-debounce": "4.0.1",
 		"tinycolor2": "1.4.2",
 		"ts-loader": "9.2.8",
 		"tsc-alias": "1.5.0",
@@ -83,7 +82,7 @@
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vue": "3.2.31",
+		"vue": "3.2.33",
 		"vue-loader": "17.0.0",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vue-router": "4.0.14",
@@ -103,23 +102,23 @@
 		"@types/is-url": "1.2.30",
 		"@types/katex": "0.14.0",
 		"@types/matter-js": "0.17.7",
-		"@types/mocha": "9.1.0",
+		"@types/mocha": "9.1.1",
 		"@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/throttle-debounce": "4.0.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/eslint-plugin": "5.18.0",
+		"@typescript-eslint/eslint-plugin": "5.20.0",
 		"cross-env": "7.0.3",
-		"cypress": "9.5.3",
+		"cypress": "9.5.4",
 		"eslint-plugin-import": "2.26.0",
 		"start-server-and-test": "1.14.0"
 	}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index a4ac6d8712..05b586eb17 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -103,10 +103,10 @@
   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.2.2":
+  version "1.2.2"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
+  integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
@@ -349,10 +349,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"
@@ -428,10 +428,10 @@
   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@4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ae62a652c914f46276c78730f53fab85b4825f09"
+  integrity sha512-BZ1Y2nf1U3qj9MNWrlTG3+FWfFS8xfG2DA0ypiZ1pBUiO8WEvEjNTmQuecnYDtQvegRojeKh+iwNK2NjJ8a3YQ==
 
 "@types/tinycolor2@1.4.3":
   version "1.4.3"
@@ -538,14 +538,14 @@
   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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
+  integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/type-utils" "5.18.0"
-    "@typescript-eslint/utils" "5.18.0"
+    "@typescript-eslint/scope-manager" "5.20.0"
+    "@typescript-eslint/type-utils" "5.20.0"
+    "@typescript-eslint/utils" "5.20.0"
     debug "^4.3.2"
     functional-red-black-tree "^1.0.1"
     ignore "^5.1.8"
@@ -553,69 +553,69 @@
     semver "^7.3.5"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
+  integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
   dependencies:
-    "@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.20.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/typescript-estree" "5.20.0"
     debug "^4.3.2"
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
+  integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/visitor-keys" "5.20.0"
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
+  integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
   dependencies:
-    "@typescript-eslint/utils" "5.18.0"
+    "@typescript-eslint/utils" "5.20.0"
     debug "^4.3.2"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
+  integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
 
-"@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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
+  integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/visitor-keys" "5.20.0"
     debug "^4.3.2"
     globby "^11.0.4"
     is-glob "^4.0.3"
     semver "^7.3.5"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
+  integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
   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.20.0"
+    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/typescript-estree" "5.20.0"
     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.20.0":
+  version "5.20.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
+  integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
+    "@typescript-eslint/types" "5.20.0"
     eslint-visitor-keys "^3.0.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -623,100 +623,100 @@
   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==
+"@vue/compiler-core@3.2.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz#e915d59cce85898f5c5cfebe4c09e539278c3d59"
+  integrity sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.31"
+    "@vue/shared" "3.2.33"
     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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz#6db84296f949f18e5d3e7fd5e80f943dbed7d5ec"
+  integrity sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==
   dependencies:
-    "@vue/compiler-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-core" "3.2.33"
+    "@vue/shared" "3.2.33"
 
-"@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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz#7ce01dc947a8b76c099811dc6ca58494d4dc773d"
+  integrity sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==
   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.33"
+    "@vue/compiler-dom" "3.2.33"
+    "@vue/compiler-ssr" "3.2.33"
+    "@vue/reactivity-transform" "3.2.33"
+    "@vue/shared" "3.2.33"
     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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz#3e820267e4eea48fde9519f006dedca3f5e42e71"
+  integrity sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==
   dependencies:
-    "@vue/compiler-dom" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-dom" "3.2.33"
+    "@vue/shared" "3.2.33"
 
 "@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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz#286063f44ca56150ae9b52f8346a26e5913fa699"
+  integrity sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-core" "3.2.33"
+    "@vue/shared" "3.2.33"
     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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.33.tgz#c84eedb5225138dbfc2472864c151d3efbb4b673"
+  integrity sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==
   dependencies:
-    "@vue/shared" "3.2.31"
+    "@vue/shared" "3.2.33"
 
-"@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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2"
+  integrity sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==
   dependencies:
-    "@vue/reactivity" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/reactivity" "3.2.33"
+    "@vue/shared" "3.2.33"
 
-"@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.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848"
+  integrity sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==
   dependencies:
-    "@vue/runtime-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/runtime-core" "3.2.33"
+    "@vue/shared" "3.2.33"
     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==
+"@vue/server-renderer@3.2.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz#4b45d6d2ae10ea4e3d2cf8e676804cf60f331979"
+  integrity sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==
   dependencies:
-    "@vue/compiler-ssr" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-ssr" "3.2.33"
+    "@vue/shared" "3.2.33"
 
-"@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==
+"@vue/shared@3.2.33":
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e"
+  integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==
 
 "@webassemblyjs/ast@1.11.0":
   version "1.11.0"
@@ -1241,11 +1241,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"
@@ -1291,15 +1286,14 @@ braces@^3.0.1, braces@~3.0.2:
   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.11.0:
+  version "4.11.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e"
+  integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
-    microseconds "0.2.0"
-    nano-time "1.0.0"
+    microtime "3.0.0"
     oblivious-set "1.0.0"
     p-queue "6.6.2"
     rimraf "3.0.2"
@@ -1840,10 +1834,10 @@ csstype@^2.6.8:
   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@9.5.4:
+  version "9.5.4"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.4.tgz#49d9272f62eba12f2314faf29c2a865610e87550"
+  integrity sha512-6AyJAD8phe7IMvOL4oBsI9puRNOWxZjl8z1lgixJMcgJ85JJmyKeP6uqNA0dI1z14lmJ7Qklf2MOgP/xdAqJ/Q==
   dependencies:
     "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
@@ -2315,13 +2309,15 @@ 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@8.7.1:
+  version "8.7.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz#f13c53547a0c9d64588a675cc5ecc6ccaf63703f"
+  integrity sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==
   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"
 
@@ -2371,12 +2367,12 @@ eslint-visitor-keys@^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.14.0:
+  version "8.14.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
+  integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
   dependencies:
-    "@eslint/eslintrc" "^1.2.1"
+    "@eslint/eslintrc" "^1.2.2"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -3159,22 +3155,6 @@ interpret@^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"
@@ -3478,11 +3458,6 @@ js-yaml@^3.13.1:
     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"
@@ -3809,10 +3784,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==
+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"
@@ -3923,13 +3901,6 @@ mylas@^2.1.6:
   resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800"
   integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ==
 
-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"
@@ -3960,6 +3931,16 @@ 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"
@@ -4001,6 +3982,13 @@ nth-check@^2.0.0:
   dependencies:
     boolbase "^1.0.0"
 
+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"
+
 oauth@0.9.15:
   version "0.9.15"
   resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1"
@@ -4615,10 +4603,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"
@@ -5000,10 +4988,10 @@ sass-loader@12.6.0:
     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.50.1:
+  version "1.50.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.1.tgz#e9b078a1748863013c4712d2466ce8ca4e4ed292"
+  integrity sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
@@ -5181,11 +5169,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"
@@ -5469,10 +5452,10 @@ three@0.139.2:
   resolved "https://registry.yarnpkg.com/three/-/three-0.139.2.tgz#b110799a15736df673b9293e31653a4ac73648dd"
   integrity sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg==
 
-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@4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.1.tgz#f86656fe9c8a6b8218952ef36c3bf225089b1baf"
+  integrity sha512-s3PedbXdZtr8v3J5Sxd5T/GmWG80BcK5GVpwDdvgEaUXsaMqQe4zxgmC4TA7B8luSDCPxo3CeSBS3F9rF1CZwg==
 
 throttleit@^1.0.0:
   version "1.0.0"
@@ -5793,16 +5776,16 @@ vue-svg-loader@0.17.0-beta.2:
     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==
+vue@3.2.33:
+  version "3.2.33"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.33.tgz#7867eb16a3293a28c4d190a837bc447878bd64c2"
+  integrity sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==
   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.33"
+    "@vue/compiler-sfc" "3.2.33"
+    "@vue/runtime-dom" "3.2.33"
+    "@vue/server-renderer" "3.2.33"
+    "@vue/shared" "3.2.33"
 
 vuedraggable@4.0.1:
   version "4.0.1"

From 70958a9f7757745cc06b21b961dadeae071151e2 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 12:37:44 +0900
Subject: [PATCH 026/258] update node to 18

---
 .node-version                  |  2 +-
 CHANGELOG.md                   | 10 ++++++++++
 Dockerfile                     | 14 +++++++-------
 packages/backend/tsconfig.json |  7 ++++++-
 4 files changed, 24 insertions(+), 9 deletions(-)

diff --git a/.node-version b/.node-version
index bf79505bb8..658984787f 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v16.14.0
+v18.0.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2117101347..11636249d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,16 @@
 You should also include the user name that made the change.
 -->
 
+## 12.x.x (unreleased)
+### NOTE
+- From this version, Node 18.0.0 or later is required.
+
+### Improvements
+- 
+
+### Bugfixes
+- 
+
 ## 12.110.0 (2022/04/11)
 
 ### Improvements
diff --git a/Dockerfile b/Dockerfile
index e4959756e8..174e2e9bc7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:16.14.0-alpine3.15 AS base
+FROM node:18.0.0-alpine3.15 AS base
 
 ENV NODE_ENV=production
 
@@ -11,16 +11,16 @@ FROM base AS builder
 COPY . ./
 
 RUN apk add --no-cache $BUILD_DEPS && \
-    git submodule update --init && \
-    yarn install && \
-    yarn build && \
-    rm -rf .git
+	git submodule update --init && \
+	yarn install && \
+	yarn build && \
+	rm -rf .git
 
 FROM base AS runner
 
 RUN apk add --no-cache \
-    ffmpeg \
-    tini
+	ffmpeg \
+	tini
 
 ENTRYPOINT ["/sbin/tini", "--"]
 
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"

From 84b183a9f621fc91527f8e55fed91cba31ba5264 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 12:38:02 +0900
Subject: [PATCH 027/258] refactor: use structuredClone for deep clone

---
 .../backend/src/models/repositories/drive-file.ts    |  2 +-
 packages/backend/src/server/web/manifest.ts          | 12 ++++++------
 packages/backend/src/services/relay.ts               |  2 +-
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index c15f5b6058..b626359d98 100644
--- a/packages/backend/src/models/repositories/drive-file.ts
+++ b/packages/backend/src/models/repositories/drive-file.ts
@@ -29,7 +29,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
 
 	getPublicProperties(file: DriveFile): DriveFile['properties'] {
 		if (file.properties.orientation != null) {
-			const properties = JSON.parse(JSON.stringify(file.properties));
+			const properties = structuredClone(file.properties);
 			if (file.properties.orientation >= 5) {
 				[properties.width, properties.height] = [properties.height, properties.width];
 			}
diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts
index bcbf9b76a7..61d7660066 100644
--- a/packages/backend/src/server/web/manifest.ts
+++ b/packages/backend/src/server/web/manifest.ts
@@ -1,16 +1,16 @@
 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));
+	const res = structuredClone(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/services/relay.ts b/packages/backend/src/services/relay.ts
index 1ab45588da..08bf72cc26 100644
--- a/packages/backend/src/services/relay.ts
+++ b/packages/backend/src/services/relay.ts
@@ -88,7 +88,7 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
 	}));
 	if (relays.length === 0) return;
 
-	const copy = JSON.parse(JSON.stringify(activity));
+	const copy = structuredClone(activity);
 	if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
 
 	const signed = await attachLdSignature(copy, user);

From 29b9d8998a1241c49e7d9ca20c158599a63e1d18 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 23 Apr 2022 12:39:44 +0900
Subject: [PATCH 028/258] chore(deps): bump moment from 2.24.0 to 2.29.3 in
 /packages/backend (#8531)

Bumps [moment](https://github.com/moment/moment) from 2.24.0 to 2.29.3.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/2.29.3/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.24.0...2.29.3)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 packages/backend/yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index f7838b8b11..10ea673e11 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -4838,9 +4838,9 @@ mocha@9.2.2:
     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"

From 92762223ea685327ab00692198305efb4e6f606d Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sat, 23 Apr 2022 05:41:04 +0200
Subject: [PATCH 029/258] refactor(meta): split package lints into separate
 workflows (#8530)

---
 .github/workflows/lint.yml | 64 +++++++++++++++++++++++---------------
 1 file changed, 39 insertions(+), 25 deletions(-)

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index da2c73a656..9da27f4678 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,25 +1,39 @@
-name: Lint
-
-on:
-  push:
-    branches:
-      - master
-      - develop
-  pull_request:
-
-jobs:
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-      with:
-        submodules: true
-    - uses: actions/setup-node@v3
-      with:
-        node-version: 16.x
-        cache: 'yarn'
-        cache-dependency-path: |
-          packages/backend/yarn.lock
-          packages/client/yarn.lock
-    - run: yarn install
-    - run: yarn lint
+name: Lint
+
+on:
+  push:
+    branches:
+      - master
+      - develop
+  pull_request:
+
+jobs:
+  backend:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-node@v3
+      with:
+        node-version: 16.x
+        cache: 'yarn'
+        cache-dependency-path: |
+          packages/backend/yarn.lock
+    - run: yarn install
+    - run: yarn --cwd ./packages/backend lint
+
+  client:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-node@v3
+      with:
+        node-version: 16.x
+        cache: 'yarn'
+        cache-dependency-path: |
+          packages/client/yarn.lock
+    - run: yarn install
+    - run: yarn --cwd ./packages/client lint

From 92d249210da27b9aa966d203cd8b296287a76ab1 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 23 Apr 2022 05:45:36 +0200
Subject: [PATCH 030/258] chore(lint): fix type definitions for jsrsasign
 (#8528)

* fix type definitions for jsrsasign

The @types/jsrsasign is not available in exactly the same version as the jsrsa
package misskey uses, so i used an earlier patch version of the same package.

* update yarn.lock
---
 packages/backend/package.json              |   1 +
 packages/backend/src/@types/jsrsasign.d.ts | 800 ---------------------
 packages/backend/yarn.lock                 |   5 +
 3 files changed, 6 insertions(+), 800 deletions(-)
 delete mode 100644 packages/backend/src/@types/jsrsasign.d.ts

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 3d0d5dd308..40844afa08 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -138,6 +138,7 @@
 		"@types/js-yaml": "4.0.5",
 		"@types/jsdom": "16.2.14",
 		"@types/jsonld": "1.5.6",
+		"@types/jsrsasign": "8.0.12",
 		"@types/koa": "2.13.4",
 		"@types/koa-bodyparser": "4.3.7",
 		"@types/koa-cors": "0.0.2",
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/yarn.lock b/packages/backend/yarn.lock
index 10ea673e11..38b4e2789d 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -527,6 +527,11 @@
   resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4"
   integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg==
 
+"@types/jsrsasign@8.0.12":
+  version "8.0.12"
+  resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-8.0.12.tgz#6bfebbbde57e72748d801642bba9b431d049b952"
+  integrity sha512-FLXKbwbB+4fsJECYOpIiYX2GSqSHYnkO/UnrFqlZn6crpyyOtk4LRab+G1HC7dTbT1NB7spkHecZRQGXoCWiJQ==
+
 "@types/keygrip@*":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"

From fd13173eaf12b9b0adf021120c6eb5dc9451f84e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 12:48:26 +0900
Subject: [PATCH 031/258] bump jsrsasign

---
 packages/backend/package.json          |  4 ++--
 packages/backend/src/server/api/2fa.ts |  4 ++--
 packages/backend/yarn.lock             | 16 ++++++++--------
 3 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 40844afa08..5950cd23ff 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -59,7 +59,7 @@
 		"json5": "2.2.1",
 		"json5-loader": "4.0.1",
 		"jsonld": "5.2.0",
-		"jsrsasign": "8.0.20",
+		"jsrsasign": "10.5.19",
 		"koa": "2.13.4",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
@@ -138,7 +138,7 @@
 		"@types/js-yaml": "4.0.5",
 		"@types/jsdom": "16.2.14",
 		"@types/jsonld": "1.5.6",
-		"@types/jsrsasign": "8.0.12",
+		"@types/jsrsasign": "10.2.1",
 		"@types/koa": "2.13.4",
 		"@types/koa-bodyparser": "4.3.7",
 		"@types/koa-cors": "0.0.2",
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/yarn.lock b/packages/backend/yarn.lock
index 38b4e2789d..8fbfa6459b 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -527,10 +527,10 @@
   resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4"
   integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg==
 
-"@types/jsrsasign@8.0.12":
-  version "8.0.12"
-  resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-8.0.12.tgz#6bfebbbde57e72748d801642bba9b431d049b952"
-  integrity sha512-FLXKbwbB+4fsJECYOpIiYX2GSqSHYnkO/UnrFqlZn6crpyyOtk4LRab+G1HC7dTbT1NB7spkHecZRQGXoCWiJQ==
+"@types/jsrsasign@10.2.1":
+  version "10.2.1"
+  resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.2.1.tgz#b82882523dfb5c476673dbef344ad838f96fb43d"
+  integrity sha512-piCIOMY0+d2wwRNcRw56VBqFYCYYeZ1c/NlUKVHTV3Y9j1RE2qpgCQvClI6yhH2sk8OoXiah43i9FmnC5tL2RQ==
 
 "@types/keygrip@*":
   version "1.0.2"
@@ -4171,10 +4171,10 @@ jsprim@^2.0.2:
     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.19:
+  version "10.5.19"
+  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.19.tgz#61cd378190c3e65bd1a26a088696736e4437a806"
+  integrity sha512-GgOdly2Ee9nS+qxOjLkQKaoSTKqlk6lFKcKLPlNJOApoOUcqL2z+l4dAcBzYnZkA3tg+LwFOyQnqbuFn5IPdvw==
 
 jstransformer@1.0.0:
   version "1.0.0"

From c2cae877ced2c814c7802bed7b620824a903d552 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 12:50:37 +0900
Subject: [PATCH 032/258] chore: fix lint command for windows

---
 packages/client/package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/package.json b/packages/client/package.json
index bf492a4978..21093cdb7c 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -3,7 +3,7 @@
 	"scripts": {
 		"watch": "webpack --watch",
 		"build": "webpack",
-		"lint": "eslint --quiet 'src/**/*.{ts,vue}'"
+		"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
 	},
 	"resolutions": {
 		"chokidar": "^3.3.1",

From eac71ae1d7f7d1ee4c06c4060979b7b292c0e57e Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 23 Apr 2022 19:17:15 +0900
Subject: [PATCH 033/258] fix: Fix settings page (#8508)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix settings page

* nanka iroiro

* clean up

* clean up

* インデックスに戻ってもタイトルが残ってしまうのを修正
---
 packages/client/src/components/global/a.vue  |  25 +----
 packages/client/src/pages/settings/index.vue | 105 ++++++++++++-------
 packages/client/src/scripts/navigate.ts      |  34 ++++++
 3 files changed, 105 insertions(+), 59 deletions(-)
 create mode 100644 packages/client/src/scripts/navigate.ts

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/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;
 		}
 	}
 });
 
-onMounted(() => {
-	narrow.value = el.value.offsetWidth < 800;
-	if (!narrow.value) {
-		page.value = 'profile';
+watch(narrow, () => {
+	if (props.initialPage == null && !narrow.value) {
+		nav.push('/settings/profile');
 	}
 });
 
+onMounted(() => {
+	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/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);
+			}
+		}
+	}
+}

From f23d5a75f4cb64328a99e25f8a17150fe5b8a4d3 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 23 Apr 2022 19:56:17 +0900
Subject: [PATCH 034/258] Squashed commit of the following:
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

commit 3658f19d9800f331b3331080ac700dbae5db987a
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Apr 23 19:54:09 2022 +0900

    12.110.1

commit e213c2e8446971549ba8cb8969a3b38d15530b4e
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Apr 16 13:31:12 2022 +0900

    remove unused locale

commit dd86397e857f36fbc06d77feadef00f6c92a3e21
Author: xianon <xianon@hotmail.co.jp>
Date:   Tue Apr 19 22:59:39 2022 +0900

    fix: アンテナ、クリップ、リストの表示を速くする (#8518)

    * アンテナノートを取得するクエリがタイムアウトしないように速くする

    * テーブル名を直接指定しないようにする

    * クリップの取得を速くする

    * リストの取得を速くする

commit 6d33b366f8f18b0724c4a3d8cbfa6a7a4112b805
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Apr 17 21:18:18 2022 +0900

    fix ogp rendering and refactor

commit 33c22b5f3efa4110c9b517c224c9fdfba7e6c64b
Merge: 090f8eff6 16c7ef41f
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Apr 11 23:13:18 2022 +0900

    Merge branch 'develop'

commit 090f8eff670b841499348fc0ccfb0e7b42928e2a
Merge: 5abe05d57 395fe7eb4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Apr 3 14:01:19 2022 +0900

    Merge branch 'develop'

commit 5abe05d572d27f54bda0acfa99b78764c65ff4e7
Merge: 2375359d1 7722fc4d3
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Apr 2 16:56:35 2022 +0900

    Merge branch 'develop'

commit 2375359d129b63988b0658f735e1d9c014c10d71
Merge: 2cd2d6522 68d462b30
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Apr 2 15:34:03 2022 +0900

    Merge branch 'develop'

commit 2cd2d6522e096bfbca244010e30241828d13a3cb
Merge: 6fc35868f b81b66912
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Mar 12 19:35:17 2022 +0900

    Merge branch 'develop'

commit 6fc35868ff89c51720aad6e13676d10aa0785cf8
Merge: 6e7e11e06 6cd3ff584
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Mar 9 23:04:16 2022 +0900

    Merge branch 'develop'

commit 6e7e11e061deba94d3ff21b1391c10785b94f6e5
Merge: 0589171ce 8d568d533
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Feb 12 17:36:42 2022 +0900

    Merge branch 'develop'

commit 0589171ceb51cda21f1d93bbe3bbd26f8e1a6197
Merge: eef8f63dc 3cf9c3097
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Feb 11 22:17:17 2022 +0900

    Merge branch 'develop'

commit eef8f63dc6f8920bc3fec505e09c99afde71121e
Merge: 32500faf6 de6e3d64b
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Feb 11 21:40:15 2022 +0900

    Merge branch 'develop'

commit 32500faf6d2b6ed316ccbf87f4b5d4442f309d6f
Merge: 1ce8da66c fd5a30482
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Feb 11 19:47:41 2022 +0900

    Merge branch 'develop'

commit 1ce8da66c2a38fea9bf7aa314717412539fbab6d
Merge: 08da5e9e0 c07e4c347
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Feb 11 17:58:04 2022 +0900

    Merge branch 'develop'

commit 08da5e9e0d82dc20ffd9c8bd99079fc5f741ef2a
Merge: 5f985ee83 25cac3307
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Feb 9 21:44:39 2022 +0900

    Merge branch 'develop'

commit 5f985ee832eed61e31ceb51eaa1c51810ad6de39
Merge: 1fd6c9753 419072059
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Feb 9 14:50:38 2022 +0900

    Merge branch 'develop'

commit 1fd6c97532305d1ec6f4118145f4a77e27384d7d
Merge: 9c2f5ee04 74cef67e9
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Feb 2 01:33:18 2022 +0900

    Merge branch 'develop'

commit 9c2f5ee0413ccab907f11e7e94c158eaf77ca020
Merge: 97885d3de c6a15024f
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Feb 2 00:40:00 2022 +0900

    Merge branch 'develop'

commit 97885d3def2ec2daa7e2706971f96052b60fbe79
Merge: 5f5f68cdc 990fef599
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Thu Jan 27 18:00:32 2022 +0900

    Merge branch 'develop'

commit 5f5f68cdcd31653cef2ae6bd29ce8bfcf60113ff
Merge: af6d52e4c 40b7230bd
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Thu Jan 27 00:17:13 2022 +0900

    Merge branch 'develop'

commit af6d52e4c86695ca36147e1c830b7374ded65c02
Merge: 621fc5a71 99eb919f4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Dec 29 17:25:24 2021 +0900

    Merge branch 'develop'

commit 621fc5a715e372064bb178a24f07c8aa960f7f50
Merge: 1b956af85 b14e347da
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Dec 29 13:42:15 2021 +0900

    Merge branch 'develop'

commit 1b956af85502fe592bfaf686da6b67cc35a96225
Merge: 80c88e13f a3dceee7c
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Dec 18 20:59:16 2021 +0900

    Merge branch 'develop'

commit 80c88e13ff730ab627787b498f71a0a5ed3c37a1
Merge: ad9e6a4ec 35cd9e621
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Dec 17 19:43:00 2021 +0900

    Merge branch 'develop'

commit ad9e6a4ec5d2ff3a05b59f83ef106574d89ffe39
Merge: 504f18244 ac8243501
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Dec 17 16:18:29 2021 +0900

    Merge branch 'develop'

commit 504f1824486523ffe4f56bb18225076128a48a40
Merge: fd5999378 cf5fe2d10
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Dec 14 23:28:29 2021 +0900

    Merge branch 'develop'

commit fd5999378bbf64f9682f65a306c8218abd6f10ae
Merge: 8451436cb 1894df882
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Dec 14 23:13:41 2021 +0900

    Merge branch 'develop'

commit 8451436cb8ddbc17aad84abcabb58010f28d780a
Merge: 37628953c 6319dd1bf
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Dec 14 23:03:56 2021 +0900

    Merge branch 'develop'

commit 37628953c53dd6307276c18f7673c33cc0152803
Merge: 83a77f106 13288e1ed
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Dec 14 22:38:31 2021 +0900

    Merge branch 'develop'

commit 83a77f1064e47633b7b952b69e3a067a9ed7d1e9
Merge: 99640a35a 75c087b79
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Dec 3 22:30:10 2021 +0900

    Merge branch 'develop'

commit 99640a35a35c75fa333e578cee2d05375a3aa975
Merge: 88cdbc2ad 24681bbe1
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Nov 19 20:41:40 2021 +0900

    Merge branch 'develop'

commit 88cdbc2ad656baea238d4af24215f20677665a55
Merge: db10103d8 a0c6ae2cb
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Nov 19 20:39:59 2021 +0900

    Merge branch 'develop'

commit db10103d8e7ae4c17bbb246b9440825bd189d2a6
Merge: 2795fe457 7f5299cf4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Nov 13 17:08:15 2021 +0900

    Merge branch 'develop'

commit 2795fe457909c687f668d020ef65d52abc3182fb
Merge: 54631026d 4ab773eb9
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Nov 13 12:23:49 2021 +0900

    Merge branch 'develop'

commit 54631026de9edb28c842f612a37511f82f1f749e
Merge: 80783199a 944250f7f
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Oct 31 20:21:50 2021 +0900

    Merge branch 'develop'

commit 80783199a9ec2629e502cddb9b7fd7a4c3322114
Merge: 6d557269c f418eaecf
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Oct 25 03:57:09 2021 +0900

    Merge branch 'develop'

commit 6d557269c13f023d80aeeb28ddceb7337aca134c
Merge: 26b268588 a905188e9
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Oct 25 02:34:58 2021 +0900

    Merge branch 'develop'

commit 26b268588f0c727ec5029b76c71c6a669f72f099
Merge: a1af83c0a 781b57585
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Oct 23 11:36:50 2021 +0900

    Merge branch 'develop'

commit a1af83c0ab30c01fa3a0990b1486987e536d46fb
Merge: d0d5068f7 4168addbb
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Oct 23 02:46:44 2021 +0900

    Merge branch 'develop'

commit d0d5068f728e13f3ebe1dc227ddaacf380817ec4
Merge: 8a1f3a4c0 d70b8275b
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Oct 23 01:08:45 2021 +0900

    Merge branch 'develop'

commit 8a1f3a4c0b5732d0f08f0788d93c5934de8960c8
Merge: 338793d89 8b646822f
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Oct 16 19:55:44 2021 +0900

    Merge branch 'develop'

commit 338793d891d1657f158cd4dc83f998e124bd7e45
Merge: c82ce9233 78ac41a61
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Sep 22 22:53:41 2021 +0900

    Merge branch 'develop'

commit c82ce9233b8633f65d313e042f4b5490dd9e596d
Merge: 4b48ba4e8 cc7562097
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Sep 5 16:26:34 2021 +0900

    Merge branch 'develop'

commit 4b48ba4e8cff670ce4726417fe9b176cda0c4e76
Merge: 7115bd46f bf7baccea
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Sep 4 20:38:20 2021 +0900

    Merge branch 'develop'

commit 7115bd46ff1dcae14455633ae89eb3e1fffc0aec
Merge: e967d9ded f84483896
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Aug 24 14:40:22 2021 +0900

    Merge branch 'develop'

commit e967d9ded35cbdf937bbc232e05a5fa0966458a7
Merge: f00ceedae c3b55b684
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Aug 24 13:20:30 2021 +0900

    Merge branch 'develop'

commit f00ceedae48e7969ca9e80f0af2280bf060421ec
Merge: df67836c1 7387e010c
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Aug 21 17:59:29 2021 +0900

    Merge branch 'develop'

commit df67836c1ad281d2622b52bdf7c767b2dfc0e6a5
Merge: 9fd0e9085 6a3a8ba4d
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Aug 17 22:01:46 2021 +0900

    Merge branch 'develop'

commit 9fd0e9085004fe01529ef9d3853a3c12c5d1bd8d
Merge: 42c4ea38c ab01cf188
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Thu Aug 12 12:48:58 2021 +0900

    Merge branch 'develop'

commit 42c4ea38ccf09ba37ff552e37778ba1b702b781c
Merge: df5396830 c0ba71c36
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Aug 11 22:36:59 2021 +0900

    Merge branch 'develop'

commit df53968306859ebe21075b32969156a73ef11dad
Merge: df530bb66 19f753c15
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Aug 9 21:47:52 2021 +0900

    Merge branch 'develop'

commit df530bb66d85353c0799c12d8f8afa766769fbc3
Merge: c52e30e8e fa49427df
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Aug 9 21:47:23 2021 +0900

    Merge branch 'develop'

commit c52e30e8e0fb0e84a30f5d422585de492bab59ca
Merge: 5e6e1e237 0cb04ded3
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Aug 8 23:25:21 2021 +0900

    Merge branch 'develop'

commit 5e6e1e237a2de1c4a1db87f6df66bba380a9c2f0
Merge: 943a1940e 41fe364b4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Jul 26 11:15:42 2021 +0900

    Merge branch 'develop'

commit 943a1940e20d62820a548208e414085cba51c6bd
Merge: 12913a16f 15d166e30
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Jul 23 22:43:47 2021 +0900

    Merge branch 'develop'

commit 12913a16fdbfe06818650cc5ec7fac7e11348d77
Merge: acb924420 e23ad7833
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Jul 23 21:37:09 2021 +0900

    Merge branch 'develop'

commit acb92442058fa2458967425efb7324ab0646a335
Merge: d04014f87 3182606e9
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue Jul 20 12:11:07 2021 +0900

    Merge branch 'develop'

commit d04014f875a03ae9b8f0f36338fd2446e7eb3150
Merge: 929e54551 e1247b3e4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Thu Jun 10 14:03:28 2021 +0900

    Merge branch 'develop'

commit 929e5455149fd770c53dc03dabd9572f586772c1
Merge: 942c80243 3e7a87f75
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon May 31 13:06:40 2021 +0900

    Merge branch 'develop'

commit 942c8024312e098ba14462ab7e9a3551f2e9d705
Merge: 70d02cf1b e6754eb88
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri May 21 17:28:39 2021 +0900

    Merge branch 'develop'

commit 70d02cf1be55130f9e3b979633cfb568165a9c29
Merge: f96c60c1a ef646b957
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri May 21 17:27:47 2021 +0900

    Merge branch 'develop'

commit f96c60c1a0374aabeecbe82c15d7e3373efed8eb
Merge: 8accb78fa 99234632b
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue May 11 14:39:40 2021 +0900

    Merge branch 'develop'

commit 8accb78fa97becfd96173fba6eb69073aa643b25
Merge: 05203e2cf 3d7c3c39f
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed May 5 19:05:50 2021 +0900

    Merge branch 'develop'

commit 05203e2cf0f7129dbc542d8d5abfa3c36a75e410
Merge: b6c9ab0c1 80f8c2de7
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed May 5 15:17:53 2021 +0900

    Merge branch 'develop'

commit b6c9ab0c1531572a8cc472d65961050aded2f3db
Merge: cdef5cd1a 1a8f41010
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue May 4 23:12:53 2021 +0900

    Merge branch 'develop'

commit cdef5cd1ad7c1195d0273f6d78fc6aafe990007b
Merge: ea7d4d323 2efae80b9
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Tue May 4 22:53:25 2021 +0900

    Merge branch 'develop'

commit ea7d4d323eafb809715b6e629cd072354a5b4fc1
Merge: 17fff8c66 18da55bd8
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Apr 28 18:37:48 2021 +0900

    Merge branch 'develop'

commit 17fff8c66525d62721bd1fa29d11360908904ec3
Merge: 92977f303 fa9f0d9ff
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Apr 26 13:00:10 2021 +0900

    Merge branch 'develop'

commit 92977f303ddc3d004418b3166606f16b1d8dec52
Merge: 8043409d3 0038f3b24
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Apr 25 15:20:39 2021 +0900

    Merge branch 'develop'

commit 8043409d386d5e08c85d27c720ecca2b3f8030ab
Merge: 37dc1c9a8 6d145bc4c
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Apr 24 23:04:59 2021 +0900

    Merge branch 'develop'

commit 37dc1c9a82c72ca1d28ab05a55269eac650133db
Merge: 631091940 3286c93c8
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Apr 23 18:25:44 2021 +0900

    Merge branch 'develop'

commit 631091940ba13f23dd705596a243c8c585f0d64e
Merge: 938fcb3e5 53d257ef0
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Apr 18 23:25:55 2021 +0900

    Merge branch 'develop'

commit 938fcb3e5ecc6862e40c9cb85b8010af63c69181
Merge: 5e1d17dff 3553f3be4
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Apr 18 00:07:33 2021 +0900

    Merge branch 'develop'

commit 5e1d17dff24fefd8e306ae597601f0593fa40b09
Merge: 449dc17df 526838c77
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Fri Apr 16 00:21:56 2021 +0900

    Merge branch 'develop'

commit 449dc17df8fe3b6cc220aaabd576b0f04f6028da
Merge: 3e1101122 5cb3d86a1
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Apr 14 16:39:53 2021 +0900

    Merge branch 'develop'

commit 3e11011229ef8459747acdf6d3008dc145280fec
Merge: 52d577c7d e5ba47514
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Wed Mar 24 11:34:29 2021 +0900

    Merge branch 'develop'

commit 52d577c7dd7bf87b3fae34f539bb6e656c7c0ed2
Merge: 18693fb38 cf757ed01
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Mon Mar 22 15:27:08 2021 +0900

    Merge branch 'develop'

commit 18693fb38008affba4115e60f78cd32cc1a3713d
Merge: f7e9725e5 cf9242053
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sun Mar 7 14:43:00 2021 +0900

    Merge branch 'develop'

commit f7e9725e59dd241b11fda729cc5c96a64d7e2545
Merge: 9a4a534c9 17dc50c24
Author: syuilo <Syuilotan@yahoo.co.jp>
Date:   Sat Mar 6 23:23:54 2021 +0900

    Merge branch 'develop'

commit 9a4a534c92f6c9c112438f2a494de9d0d707d46d
Merge: b090ff999 0e89a9f41
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Wed Mar 3 01:04:45 2021 +0900

    Merge branch 'develop'

commit b090ff99942f54ac5855f455bae1ba247598dc76
Merge: 3d68a0988 68ace4a31
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Sun Feb 28 13:14:26 2021 +0900

    Merge branch 'develop'

commit 3d68a0988bb40567e71a23cbe809d9eff4e1a2c0
Merge: d6c8b9b99 3c7a02af1
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Sun Feb 21 13:38:29 2021 +0900

    Merge branch 'develop'

commit d6c8b9b99470db45c201229b5c9235e7be3067de
Merge: 49e6c2ed7 8bd80eb2a
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Fri Feb 19 21:42:47 2021 +0900

    Merge branch 'develop'

commit 49e6c2ed75a7b960615a8d680046b5aab11072f1
Merge: e4bcdd7b4 f24c7c81c
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Sun Feb 7 18:23:23 2021 +0900

    Merge branch 'develop'

commit e4bcdd7b4dd8711864adcd1b45e11887b2a05571
Merge: 7747ec5b6 bce65e214
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Sat Jan 23 20:06:22 2021 +0900

    Merge branch 'develop'

commit 7747ec5b6de17e8ce20e1cf59649e5e98aec9991
Author: syuilo <syuilotan@yahoo.co.jp>
Date:   Sat Jan 23 20:05:44 2021 +0900

    Update ja-JP.yml
---
 CHANGELOG.md | 6 ++++++
 package.json | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11636249d1..c3e871319b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,12 @@ You should also include the user name that made the change.
 ### Bugfixes
 - 
 
+## 12.110.1 (2022/04/23)
+
+### Bugfixes
+- Fix GOP rendering @syuilo
+- Improve performance of antenna, clip, and list @xianon
+
 ## 12.110.0 (2022/04/11)
 
 ### Improvements
diff --git a/package.json b/package.json
index 361c4096da..9c3c39bf93 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "12.110.0",
+	"version": "12.110.1",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 1b2ba09be05338b1680d664970ef28b5ea6464a6 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 24 Apr 2022 11:43:15 +0900
Subject: [PATCH 035/258]  fix: Fix schema key type error #8517 (#8538)

---
 packages/backend/src/misc/schema.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 5b69812090..9da13d599b 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;

From 7e28c396b9d300325e4401bc8dabb44e633ba26c Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 24 Apr 2022 07:17:09 +0200
Subject: [PATCH 036/258] enhance: only render public notes in HTML template
 (#8527)

* only render public notes in HTML template

* fix missing import
---
 packages/backend/src/server/web/index.ts | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 34d56cfd0c..e80bf45d14 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -14,7 +14,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';
@@ -266,7 +266,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 +286,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=180');
 
 		return;
 	}

From 3f9a914718e13c99cbef51944938ab687d81d6c2 Mon Sep 17 00:00:00 2001
From: futchitwo <74236683+futchitwo@users.noreply.github.com>
Date: Sun, 24 Apr 2022 14:21:46 +0900
Subject: [PATCH 037/258] Fix(client): fix profile tab link (#8536)

---
 packages/client/src/pages/user/index.vue | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue
index 10a86243f9..405494ec23 100644
--- a/packages/client/src/pages/user/index.vue
+++ b/packages/client/src/pages/user/index.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(),
 		};
 	},
 

From b9e326719876345446d4e84d6de51e64c6e9925f Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Mon, 25 Apr 2022 08:14:13 +0200
Subject: [PATCH 038/258] fix: Promises -> Promise (#8545)

---
 packages/backend/src/services/blocking/create.ts  | 2 +-
 packages/backend/src/services/following/create.ts | 2 +-
 packages/backend/src/services/following/delete.ts | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index d4b28d8d77..b2be78b220 100644
--- a/packages/backend/src/services/blocking/create.ts
+++ b/packages/backend/src/services/blocking/create.ts
@@ -95,7 +95,7 @@ async function unFollow(follower: User, followee: User) {
 		return;
 	}
 
-	await Promises.all([
+	await Promise.all([
 		Followings.delete(following.id),
 		Users.decrement({ id: follower.id }, 'followingCount', 1),
 		Users.decrement({ id: followee.id }, 'followersCount', 1),
diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts
index f521118d48..72c24676bb 100644
--- a/packages/backend/src/services/following/create.ts
+++ b/packages/backend/src/services/following/create.ts
@@ -67,7 +67,7 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
 	if (alreadyFollowed) return;
 
 	//#region Increment counts
-	await Promises.all([
+	await Promise.all([
 		Users.increment({ id: follower.id }, 'followingCount', 1),
 		Users.increment({ id: followee.id }, 'followersCount', 1),
 	]);
diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts
index 1e425c2689..91b5a3d61d 100644
--- a/packages/backend/src/services/following/delete.ts
+++ b/packages/backend/src/services/following/delete.ts
@@ -59,7 +59,7 @@ 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 / followers counts
-	await Promises.all([
+	await Promise.all([
 		Users.decrement({ id: follower.id }, 'followingCount', 1),
 		Users.decrement({ id: followee.id }, 'followersCount', 1),
 	]);

From 065324d30bddd2cf1ec48cb539cbb4b43c7b4169 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Wed, 27 Apr 2022 10:49:00 +0900
Subject: [PATCH 039/258] Fix #8535 Excessive stack ... 'SchemaTypeDef<?>'
 (#8547)

* Fix #8535 Excessive stack ... 'SchemaTypeDef<?>'

Co-authored-by: acid-chicken <root@acid-chicken.com>

* add comment

* clean up

Co-authored-by: acid-chicken <root@acid-chicken.com>
---
 packages/backend/src/misc/schema.ts | 35 ++++++++++++++++-------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 9da13d599b..fdecc278d4 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -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' ? (

From a99cd645bbd08ca6abd8d23555e69e0a22515a75 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 27 Apr 2022 08:17:49 +0200
Subject: [PATCH 040/258] refactor: use composition API (#8541)

---
 packages/client/src/pages/admin/abuses.vue | 68 +++++++++-------------
 1 file changed, 29 insertions(+), 39 deletions(-)

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>

From d0443f9de130292bc6dc6e276915a837deaf8dc4 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 28 Apr 2022 03:56:18 +0200
Subject: [PATCH 041/258] fix(client): fix lint issues in autocomplete (#8548)

---
 .../client/src/components/autocomplete.vue    | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

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();
 	}
 }

From 12a3c6872f0a31c923bf0cd7c183cb8776d58dda Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 28 Apr 2022 11:14:03 +0900
Subject: [PATCH 042/258] =?UTF-8?q?enhance:=20=E3=83=89=E3=83=A9=E3=82=A4?=
 =?UTF-8?q?=E3=83=96=E3=81=AB=E7=94=BB=E5=83=8F=E3=83=95=E3=82=A1=E3=82=A4?=
 =?UTF-8?q?=E3=83=AB=E3=82=92=E3=82=A2=E3=83=83=E3=83=97=E3=83=AD=E3=83=BC?=
 =?UTF-8?q?=E3=83=89=E3=81=99=E3=82=8B=E3=81=A8=E3=81=8D=E3=82=AA=E3=83=AA?=
 =?UTF-8?q?=E3=82=B8=E3=83=8A=E3=83=AB=E7=94=BB=E5=83=8F=E3=82=92=E7=A0=B4?=
 =?UTF-8?q?=E6=A3=84=E3=81=97=E3=81=A6webpublic=E3=81=AE=E3=81=BF=E4=BF=9D?=
 =?UTF-8?q?=E6=8C=81=E3=81=99=E3=82=8B=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7?=
 =?UTF-8?q?=E3=83=B3=20(#8216)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* wip

* Update packages/client/src/os.ts

Co-authored-by: tamaina <tamaina@hotmail.co.jp>

* メニューをComposition API化、switchアイテム追加
クライアントサイド画像圧縮の準備

* メニュー型定義を分離 (TypeScriptの型支援が効かないので)

* disabled

* make keepOriginal to follow setting value

* :v:

* fix

* fix

* :v:

* WEBP

* aaa

* :v:

* webp

* lazy load browser-image-resizer

* rename

* rename 2

* Fix

* clean up

* add comment

* clean up

* jpeg, pngにもどす

* fix

* fix name

* webpでなくする ただしサムネやプレビューはwebpのまま (テスト)

* 動画サムネイルはjpegに

* エラーハンドリング

* :v:

* v2.2.1-misskey-beta.2

* browser-image-resizer#v2.2.1-misskey.1

* :v:

* fix alert

* update browser-image-resizer to v2.2.1-misskey.2

* lockfile

Co-authored-by: mei23 <m@m544.net>
Co-authored-by: MeiMei <30769358+mei23@users.noreply.github.com>
---
 packages/backend/src/misc/populate-emojis.ts  |   2 +-
 .../src/server/file/send-drive-file.ts        |   8 +-
 .../backend/src/server/proxy/proxy-media.ts   |   8 +-
 .../backend/src/server/web/url-preview.ts     |   2 +-
 .../backend/src/services/drive/add-file.ts    |  29 +++--
 .../drive/generate-video-thumbnail.ts         |   1 +
 .../src/services/drive/image-processor.ts     |  28 +----
 packages/client/package.json                  |   1 +
 packages/client/src/components/drive.vue      |  10 +-
 packages/client/src/components/post-form.vue  |   3 +-
 packages/client/src/os.ts                     |  75 +-----------
 .../pages/messaging/messaging-room.form.vue   |   3 +-
 packages/client/src/scripts/select-file.ts    |   3 +-
 packages/client/src/scripts/upload.ts         | 114 ++++++++++++++++++
 packages/client/src/ui/_common_/common.vue    |   3 +-
 packages/client/src/ui/_common_/upload.vue    |   2 +-
 packages/client/yarn.lock                     |   4 +
 17 files changed, 169 insertions(+), 127 deletions(-)
 create mode 100644 packages/client/src/scripts/upload.ts

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/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 6bc220b362..027d078ce1 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -11,7 +11,7 @@ import { DriveFiles } from '@/models/index.js';
 import { InternalStorage } from '@/services/drive/internal-storage.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';
@@ -64,10 +64,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/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/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/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..da93bc97c7 100644
--- a/packages/backend/src/services/drive/generate-video-thumbnail.ts
+++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts
@@ -27,6 +27,7 @@ export async function GenerateVideoThumbnail(path: string): Promise<IImage> {
 
 	const outPath = `${outDir}/output.png`;
 
+	// JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる)
 	const thumbnail = await convertToJpeg(outPath, 498, 280);
 
 	// 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/client/package.json b/packages/client/package.json
index 21093cdb7c..e533e1fb87 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -21,6 +21,7 @@
 		"autwh": "0.1.0",
 		"blurhash": "1.1.5",
 		"broadcast-channel": "4.11.0",
+		"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
 		"chart.js": "3.7.1",
 		"chartjs-adapter-date-fns": "2.0.0",
 		"chartjs-plugin-gradient": "0.2.2",
diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue
index e044c67523..2ec885b00c 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;
@@ -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);
@@ -355,7 +357,7 @@ 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);
 	});
 }
@@ -562,6 +564,10 @@ function fetchMoreFiles() {
 
 function getMenu() {
 	return [{
+		type: 'switch',
+		text: i18n.ts.keepOriginalUploading,
+		ref: keepOriginal,
+	}, null, {
 		text: i18n.ts.addFile,
 		type: 'label'
 	}, {
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 656689ddcb..241c726c11 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -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');
 
@@ -372,7 +373,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);
 	});
 }
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 43c110555f..b8a3f94cc8 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 } 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);
 
@@ -537,78 +536,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/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
index 3863c8f82b..35cb75743f 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: {
@@ -164,7 +165,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;
 			});
 		},
diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts
index 23df4edf54..49a46f0bb2 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,7 +15,7 @@ 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]);
diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts
new file mode 100644
index 0000000000..7e4f793b44
--- /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 (e) => {
+			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 (e) {
+					console.error('Failed to resize image', e);
+				}
+			}
+
+			const data = new FormData();
+			data.append('i', $i.token);
+			data.append('force', 'true');
+			data.append('file', resizedImage || file);
+			data.append('name', ctx.name);
+			if (folder) data.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 = e => {
+				if (e.lengthComputable) {
+					ctx.progressMax = e.total;
+					ctx.progressValue = e.loaded;
+				}
+			};
+
+			xhr.send(data);
+		};
+		reader.readAsArrayBuffer(file);
+	});
+}
diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue
index 05688d7c53..50d95539d1 100644
--- a/packages/client/src/ui/_common_/common.vue
+++ b/packages/client/src/ui/_common_/common.vue
@@ -17,7 +17,8 @@
 
 <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 { stream } from '@/stream';
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/yarn.lock b/packages/client/yarn.lock
index 05b586eb17..59abe67862 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -1299,6 +1299,10 @@ broadcast-channel@4.11.0:
     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"

From 74079c7a0c95f39e339e92a4fa84a8039bb2e2f0 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
Date: Wed, 27 Apr 2022 19:49:43 -0700
Subject: [PATCH 043/258] Remove patreon section from README (#8476)

Patrons are now mentioned in Misskey itself, and the list hasn't been updated for 2 years.
---
 README.md | 116 ------------------------------------------------------
 1 file changed, 116 deletions(-)

diff --git a/README.md b/README.md
index c7bc9ef219..19e953aee8 100644
--- a/README.md
+++ b/README.md
@@ -50,119 +50,3 @@
 <div align="center">
 	<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
 </div>
-
-## Backers
-<!-- PATREON_START -->
-<table><tr>
-<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/27648259" alt="みなしま " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
-<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
-<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
-<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
-<td><a href="https://www.patreon.com/user?u=27648259">みなしま </a></td>
-<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/4.jpg?token-time=2145916800&token-hash=BslMqDjTjz8KYANLvxL87agHTugHa0dMPUzT-hwR6Vk%3D" alt="Nesakko" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/776209" alt="Demogrognard" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/36813045/29876ea679d443bcbba3c3f16edab8c2/2.jpeg?token-time=2145916800&token-hash=YCKWnIhrV9rjUCV9KqtJnEqjy_uGYF3WMXftjUdpi7o%3D" alt="Wataru Manji (manji0)" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
-<td><a href="https://www.patreon.com/user?u=776209">Demogrognard</a></td>
-<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
-<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td>
-<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td>
-<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td>
-<td><a href="https://www.patreon.com/osapon">osapon </a></td>
-<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ </a></td>
-<td><a href="https://www.patreon.com/user?u=36813045">Wataru Manji (manji0)</a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61 " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/38837364/9421361c54c645ac8f5fc442a40c32e9/1.png?token-time=2145916800&token-hash=TUZB48Nem3BeUPLBH6s3P6WyKBnQOy0xKaDSTBBUNzA%3D" alt="xianon" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
-<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
-<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
-<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
-<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td>
-<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
-<td><a href="https://www.patreon.com/user?u=38837364">xianon</a></td>
-<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26144593/9514b10a5c1b42a3af58621aee213d1d/1.png?token-time=2145916800&token-hash=v1PYRsjzu4c_mndN4Hvi_dlispZJsuGRCQeNS82pUSM%3D" alt="EBISUME" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
-<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
-<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </a></td>
-<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
-<td><a href="https://www.patreon.com/takimura">takimura </a></td>
-<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
-<td><a href="https://www.patreon.com/user?u=9109588">nafuchoco </a></td>
-<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
-<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
-<td><a href="https://www.patreon.com/user?u=26144593">EBISUME</a></td>
-<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/14661394" alt="Chandler " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Corset">CG </a></td>
-<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
-<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
-<td><a href="https://www.patreon.com/user?u=14661394">Chandler </a></td>
-<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
-<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td>
-<td><a href="https://www.patreon.com/efertone">Efertone </a></td>
-<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
-</tr></table>
-
-**Last updated:** Sun, 26 Jul 2020 07:00:10 UTC
-<!-- PATREON_END -->
-
-[backer-url]: #backers
-[backer-badge]: https://opencollective.com/misskey/backers/badge.svg
-[backers-image]: https://opencollective.com/misskey/backers.svg
-[sponsor-url]: #sponsors
-[sponsor-badge]: https://opencollective.com/misskey/sponsors/badge.svg
-[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
-[support-url]: https://opencollective.com/misskey#support
-
-[syuilo-link]:      https://syuilo.com
-[syuilo-icon]:      https://avatars2.githubusercontent.com/u/4439005?v=3&s=70

From 9fe0452016bedab27bc7b7b2317f7aec583de357 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 28 Apr 2022 08:51:47 +0200
Subject: [PATCH 044/258] update changelog

add user facing changes to changelog
---
 CHANGELOG.md | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3e871319b..088b7118a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,16 +15,19 @@ You should also include the user name that made the change.
 - From this version, Node 18.0.0 or later is required.
 
 ### Improvements
-- 
+- enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
 
 ### Bugfixes
-- 
+- Client: fix settings page @tamaina
+- Client: fix profile tabs @futchitwo
+- Server: await promises when following or unfollowing users @Johann150
+- Client: fix abuse reports page to be able to show all reports @Johann150
 
 ## 12.110.1 (2022/04/23)
 
 ### Bugfixes
 - Fix GOP rendering @syuilo
-- Improve performance of antenna, clip, and list @xianon
+- Improve performance of antenna, clip, and list @xianonn
 
 ## 12.110.0 (2022/04/11)
 

From 6a44616725c8474990610b31f9b049e83c0786d4 Mon Sep 17 00:00:00 2001
From: Balazs Nadasdi <balazs@weave.works>
Date: Fri, 29 Apr 2022 03:17:03 +0200
Subject: [PATCH 045/258] chore(deps): Update github actions to use the same
 version as defined in .node-version (#8563)

---
 .github/workflows/lint.yml | 4 ++--
 .github/workflows/test.yml | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 9da27f4678..4e42fa9314 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,7 +16,7 @@ jobs:
         submodules: true
     - uses: actions/setup-node@v3
       with:
-        node-version: 16.x
+        node-version: 18.x
         cache: 'yarn'
         cache-dependency-path: |
           packages/backend/yarn.lock
@@ -31,7 +31,7 @@ jobs:
         submodules: true
     - uses: actions/setup-node@v3
       with:
-        node-version: 16.x
+        node-version: 18.x
         cache: 'yarn'
         cache-dependency-path: |
           packages/client/yarn.lock
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d57d85c874..2d858daa7c 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [16.x]
+        node-version: [18.x]
 
     services:
       postgres:
@@ -57,7 +57,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        node-version: [16.x]
+        node-version: [18.x]
         browser: [chrome]
 
     services:

From 8f32064fea395b3dbea66a4d0c9404ff569a4b5d Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Fri, 29 Apr 2022 03:21:02 +0200
Subject: [PATCH 046/258] refactor(client): refactor api-console to use
 Composition API (#8566)

---
 packages/client/src/pages/api-console.vue | 98 ++++++++++-------------
 1 file changed, 43 insertions(+), 55 deletions(-)

diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue
index 142a3bee2e..7f174a6318 100644
--- a/packages/client/src/pages/api-console.vue
+++ b/packages/client/src/pages/api-console.vue
@@ -25,8 +25,8 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref } from 'vue';
 import * as JSON5 from 'json5';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
@@ -34,63 +34,51 @@ 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);
+	});
+}
 
-		};
-	},
-
-	created() {
-		os.api('endpoints').then(endpoints => {
-			this.endpoints = endpoints;
-		});
-	},
-
-	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);
-			});
+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);
+	});
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: 'API console',
+		icon: 'fas fa-terminal'
+	},
 });
 </script>

From 5ad42d1d855872d629fb361a9eea47260c4401e9 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Fri, 29 Apr 2022 05:26:24 +0200
Subject: [PATCH 047/258] refactor(client): refactor scratchpad to use
 Composition API (#8565)

---
 packages/client/src/pages/scratchpad.vue | 163 +++++++++++------------
 1 file changed, 75 insertions(+), 88 deletions(-)

diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue
index f871dc48e8..eb91938db2 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,
-	},
+const code = ref('');
+const logs = ref<any[]>([]);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.scratchpad,
-				icon: 'fas fa-terminal',
-			},
-			code: '',
-			logs: [],
-		}
-	},
+const saved = localStorage.getItem('scratchpad');
+if (saved) {
+	code.value = saved;
+}
 
-	watch: {
-		code() {
-			localStorage.setItem('scratchpad', this.code);
-		}
-	},
+watch(code, () => {
+	localStorage.setItem('scratchpad', code.value);
+});
 
-	created() {
-		const saved = localStorage.getItem('scratchpad');
-		if (saved) {
-			this.code = 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;
-					}
-				}
+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);
+				});
 			});
-
-			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
-				});
-			}
 		},
-
-		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>
 

From 1c6d5ddf813315b58fcd99e3aa4ae060cfcd43e9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 30 Apr 2022 00:16:40 +0900
Subject: [PATCH 048/258] chore(deps): bump ejs from 3.1.6 to 3.1.7 in
 /packages/backend (#8560)

Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.7)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 packages/backend/yarn.lock | 73 +++++++++++++++++++++-----------------
 1 file changed, 41 insertions(+), 32 deletions(-)

diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 8fbfa6459b..fd91be84af 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1084,7 +1084,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==
@@ -1092,6 +1092,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"
@@ -1237,11 +1244,6 @@ 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"
@@ -1306,9 +1308,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"
@@ -1402,6 +1404,13 @@ brace-expansion@^1.1.7:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
+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:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -1677,7 +1686,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==
@@ -2470,11 +2479,11 @@ ee-first@1.1.1:
   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==
+  version "3.1.7"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006"
+  integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==
   dependencies:
-    jake "^10.6.1"
+    jake "^10.8.5"
 
 emoji-regex@^8.0.0:
   version "8.0.0"
@@ -2955,11 +2964,11 @@ file-type@17.1.1:
     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"
@@ -3981,13 +3990,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"
 
@@ -4708,20 +4717,20 @@ minimatch@4.2.1:
   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==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-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==
   dependencies:
     brace-expansion "^1.1.7"
 
+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 "^2.0.1"
+
 minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
   version "1.2.6"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"

From 766559c6e91deec660e39783badb42d88bbaac56 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 30 Apr 2022 21:52:07 +0900
Subject: [PATCH 049/258] feat: Improve Push Notification (#7667)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* clean up

* ev => data

* refactor

* clean up

* add type

* antenna

* channel

* fix

* add Packed type

* add PackedRef

* fix lint

* add emoji schema

* add reversiGame

* add reversiMatching

* remove signin schema (use Signin entity)

* add schemas refs, fix Packed type

* wip PackedHoge => Packed<'Hoge'>

* add Packed type

* note-reaction

* user

* user-group

* user-list

* note

* app, messaging-message

* notification

* drive-file

* drive-folder

* following

* muting

* blocking

* hashtag

* page

* app (with modifying schema)

* import user?

* channel

* antenna

* clip

* gallery-post

* emoji

* Packed

* reversi-matching

* update stream.ts

* https://github.com/misskey-dev/misskey/pull/7769#issuecomment-917542339

* fix lint

* clean up?

* add app

* fix

* nanka iroiro

* wip

* wip

* fix lint

* fix loginId

* fix

* refactor

* refactor

* remove follow action

* clean up

* Revert "remove follow action"

This reverts commit defbb416480905af2150d1c92f10d8e1d1288c0a.

* Revert "clean up"

This reverts commit f94919cb9cff41e274044fc69c56ad36a33974f2.

* remove fetch specification

* renoteの条件追加

* apiFetch => cli

* bypass fetch?

* fix

* refactor: use path alias

* temp: add submodule

* remove submodule

* enhane: unison-reloadに指定したパスに移動できるように

* null

* null

* feat: ログインするアカウントのIDをクエリ文字列で指定する機能

* null

* await?

* rename

* rename

* Update read.ts

* merge

* get-note-summary

* fix

* swパッケージに

* add missing packages

* fix getNoteSummary

* add webpack-cli

* :v:

* remove plugins

* sw-inject分離したがテストしてない

* fix notification.vue

* remove a blank line

* disconnect intersection observer

* disconnect2

* fix notification.vue

* remove a blank line

* disconnect intersection observer

* disconnect2

* fix

* :v:

* clean up config

* typesを戻した

* Update packages/client/src/components/notification.vue

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* disconnect

* oops

* Failed to load the script unexpectedly回避
sw.jsとlib.tsを分離してみた

* truncate notification

* Update packages/client/src/ui/_common_/common.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* clean up

* clean up

* キャッシュ対策

* Truncate push notification message

* クライアントがあったらストリームに接続しているということなので通知しない判定の位置を修正

* components/drive-file-thumbnail.vue

* components/drive-select-dialog.vue

* components/drive-window.vue

* merge

* fix

* Service Workerのビルドにesbuildを使うようにする

* return createEmptyNotification()

* fix

* i18n.ts

* update

* :v:

* remove ts-loader

* fix

* fix

* enhance: Service Workerを常に登録するように

* pollEnded

* URLをsw.jsに戻す

* clean up

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                  |   4 +-
 locales/ja-JP.yml                             |  10 +-
 .../api/common/read-messaging-message.ts      |  29 +
 .../server/api/common/read-notification.ts    |  26 +-
 .../notifications/mark-all-as-read.ts         |   2 +
 .../api/endpoints/notifications/read.ts       |  41 +-
 packages/backend/src/server/web/index.ts      |   8 +-
 .../src/services/create-notification.ts       |   4 +-
 .../backend/src/services/messages/create.ts   |   2 +-
 .../backend/src/services/push-notification.ts |  24 +-
 .../client/src/components/notification.vue    |   6 +-
 .../client/src/components/notifications.vue   |  25 +
 .../client/src/components/ui/pagination.vue   |   1 +
 packages/client/src/init.ts                   |   1 -
 packages/client/src/scripts/get-user-name.ts  |   3 +
 packages/client/src/scripts/initialize-sw.ts  |  30 +-
 .../client/src/sw/compose-notification.ts     | 107 ---
 packages/client/src/sw/sw.ts                  | 123 ---
 packages/client/src/ui/_common_/common.vue    |   6 +
 packages/client/src/ui/_common_/sw-inject.ts  |  45 ++
 packages/client/tsconfig.json                 |   3 +-
 packages/client/webpack.config.js             |   1 -
 packages/sw/.eslintrc.js                      |  22 +
 packages/sw/.npmrc                            |   2 +
 packages/sw/.yarnrc                           |   1 +
 packages/sw/build.js                          |  37 +
 packages/sw/package.json                      |  17 +
 packages/sw/src/filters/user.ts               |  14 +
 .../sw/src/scripts/create-notification.ts     | 237 ++++++
 .../sw/src/scripts/get-account-from-id.ts     |   7 +
 packages/sw/src/scripts/get-user-name.ts      |   3 +
 packages/sw/src/scripts/i18n.ts               |  29 +
 packages/sw/src/scripts/lang.ts               |  47 ++
 packages/sw/src/scripts/login-id.ts           |  11 +
 packages/sw/src/scripts/notification-read.ts  |  50 ++
 packages/sw/src/scripts/operations.ts         |  70 ++
 packages/sw/src/sw.ts                         | 200 +++++
 packages/sw/src/types.ts                      |  31 +
 packages/sw/tsconfig.json                     |  39 +
 packages/sw/webpack.config.js                 |  71 ++
 packages/sw/yarn.lock                         | 710 ++++++++++++++++++
 scripts/build.js                              |   8 +
 scripts/clean-all.js                          |   3 +
 scripts/clean.js                              |   1 +
 scripts/dev.js                                |   6 +
 scripts/install-packages.js                   |   8 +
 scripts/lint.js                               |   7 +
 47 files changed, 1834 insertions(+), 298 deletions(-)
 create mode 100644 packages/client/src/scripts/get-user-name.ts
 delete mode 100644 packages/client/src/sw/compose-notification.ts
 delete mode 100644 packages/client/src/sw/sw.ts
 create mode 100644 packages/client/src/ui/_common_/sw-inject.ts
 create mode 100644 packages/sw/.eslintrc.js
 create mode 100644 packages/sw/.npmrc
 create mode 100644 packages/sw/.yarnrc
 create mode 100644 packages/sw/build.js
 create mode 100644 packages/sw/package.json
 create mode 100644 packages/sw/src/filters/user.ts
 create mode 100644 packages/sw/src/scripts/create-notification.ts
 create mode 100644 packages/sw/src/scripts/get-account-from-id.ts
 create mode 100644 packages/sw/src/scripts/get-user-name.ts
 create mode 100644 packages/sw/src/scripts/i18n.ts
 create mode 100644 packages/sw/src/scripts/lang.ts
 create mode 100644 packages/sw/src/scripts/login-id.ts
 create mode 100644 packages/sw/src/scripts/notification-read.ts
 create mode 100644 packages/sw/src/scripts/operations.ts
 create mode 100644 packages/sw/src/sw.ts
 create mode 100644 packages/sw/src/types.ts
 create mode 100644 packages/sw/tsconfig.json
 create mode 100644 packages/sw/webpack.config.js
 create mode 100644 packages/sw/yarn.lock

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 088b7118a9..b07e9002cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,9 @@
 ## 12.x.x (unreleased)
 
 ### Improvements
-- 
+- API: notifications/readは配列でも受け付けるように
+- /share のクエリでリプライやファイル等の情報を渡せるように
+- ページロードエラーページにリロードボタンを追加
 
 ### Bugfixes
 - 
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6326094dd8..4d04cd28c8 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -356,7 +356,7 @@ antennaExcludeKeywords: "除外キーワード"
 antennaKeywordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります"
 notifyAntenna: "新しいノートを通知する"
 withFileAntenna: "ファイルが添付されたノートのみ"
-enableServiceworker: "ServiceWorkerを有効にする"
+enableServiceworker: "ブラウザへのプッシュ通知を有効にする"
 antennaUsersDescription: "ユーザー名を改行で区切って指定します"
 caseSensitive: "大文字小文字を区別する"
 withReplies: "返信を含む"
@@ -1668,8 +1668,9 @@ _notification:
   youWereFollowed: "フォローされました"
   youReceivedFollowRequest: "フォローリクエストが来ました"
   yourFollowRequestAccepted: "フォローリクエストが承認されました"
-  youWereInvitedToGroup: "グループに招待されました"
+  youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
   pollEnded: "アンケートの結果が出ました"
+  emptyPushNotificationMessage: "プッシュ通知の更新をしました"
 
   _types:
     all: "すべて"
@@ -1686,6 +1687,11 @@ _notification:
     groupInvited: "グループに招待された"
     app: "連携アプリからの通知"
 
+  _actions:
+    followBack: "フォローバック"
+    reply: "返信"
+    renote: "Renote"
+
 _deck:
   alwaysShowMainColumn: "常にメインカラムを表示"
   columnAlign: "カラムの寄せ"
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));
 
-	post(userId);
+	return readNotification(userId, notificationIds);
 }
 
-async function post(userId: User['id']) {
-	if (!await Users.getHasUnreadNotification(userId)) {
-		// 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行
-		publishMainStream(userId, 'readAllNotifications');
-	}
+function postReadAllNotifications(userId: User['id']) {
+	publishMainStream(userId, 'readAllNotifications');
+	return pushNotification(userId, 'readAllNotifications', undefined);
+}
+
+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/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/web/index.ts b/packages/backend/src/server/web/index.ts
index e80bf45d14..061ea50609 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -32,6 +32,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();
@@ -136,9 +137,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'),
 	});
 });
 
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/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/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/client/src/components/notification.vue b/packages/client/src/components/notification.vue
index 1a360f9905..3791c576ee 100644
--- a/packages/client/src/components/notification.vue
+++ b/packages/client/src/components/notification.vue
@@ -72,7 +72,7 @@
 </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';
@@ -126,6 +126,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..dc900a670d 100644
--- a/packages/client/src/components/notifications.vue
+++ b/packages/client/src/components/notifications.vue
@@ -64,6 +64,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/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 13f3215671..ac6f59c332 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -270,6 +270,7 @@ onDeactivated(() => {
 
 defineExpose({
 	items,
+	queue,
 	backed,
 	reload,
 	fetchMoreAhead,
diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts
index ab3299d22b..49dfd8c06f 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -146,7 +146,6 @@ if ($i && $i.token) {
 		try {
 			document.body.innerHTML = '<div>Please wait...</div>';
 			await login(i);
-			location.reload();
 		} catch (e) {
 			// Render the error screen
 			// TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな)
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/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/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/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue
index 50d95539d1..62e97a11e1 100644
--- a/packages/client/src/ui/_common_/common.vue
+++ b/packages/client/src/ui/_common_/common.vue
@@ -21,6 +21,7 @@ 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({
@@ -49,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_/sw-inject.ts b/packages/client/src/ui/_common_/sw-inject.ts
new file mode 100644
index 0000000000..e3e2ddd7e6
--- /dev/null
+++ b/packages/client/src/ui/_common_/sw-inject.ts
@@ -0,0 +1,45 @@
+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);
+		}
+
+		const data = ev.data; // as SwMessage
+		if (data.type !== 'order') return;
+
+		if (data.loginId !== $i?.id) {
+			return getAccountFromId(data.loginId).then(account => {
+				if (!account) return;
+				return login(account.token, data.url);
+			});
+		}
+
+		switch (data.order) {
+			case 'post':
+				return post(data.options);
+			case 'push':
+				if (router.currentRoute.value.path === data.url) {
+					return window.scroll({ top: 0, behavior: 'smooth' });
+				}
+				if (navHook) {
+					return navHook(data.url);
+				}
+				if (sideViewHook && defaultStore.state.defaultSideView && data.url !== '/') {
+					return sideViewHook(data.url);
+				}
+				return router.push(data.url);
+			default:
+				return;
+		}
+	});
+}
diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json
index b44cf2f895..db91b99e7d 100644
--- a/packages/client/tsconfig.json
+++ b/packages/client/tsconfig.json
@@ -28,8 +28,7 @@
 		],
 		"lib": [
 			"esnext",
-			"dom",
-			"webworker"
+			"dom"
 		]
 	},
 	"compileOnSave": false,
diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js
index a50851e17f..adb5fb81b5 100644
--- a/packages/client/webpack.config.js
+++ b/packages/client/webpack.config.js
@@ -37,7 +37,6 @@ const postcss = {
 module.exports = {
 	entry: {
 		app: './src/init.ts',
-		sw: './src/sw/sw.ts'
 	},
 	module: {
 		rules: [{
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/webpack.config.js b/packages/sw/webpack.config.js
new file mode 100644
index 0000000000..a4bcf96ddc
--- /dev/null
+++ b/packages/sw/webpack.config.js
@@ -0,0 +1,71 @@
+/**
+ * webpack configuration
+ */
+
+const fs = require('fs');
+const webpack = require('webpack');
+
+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');
+
+module.exports = {
+	target: 'webworker',
+	entry: {
+		['sw-lib']: './src/lib.ts'
+	},
+	module: {
+		rules: [{
+			test: /\.ts$/,
+			exclude: /node_modules/,
+			use: [{
+				loader: 'ts-loader',
+				options: {
+					happyPackMode: true,
+					transpileOnly: true,
+					configFile: __dirname + '/tsconfig.json',
+				}
+			}]
+		}]
+	},
+	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:'),
+		}),
+	],
+	output: {
+		path: __dirname + '/../../built/_sw_dist_',
+		filename: `[name].js`,
+		publicPath: `/`,
+		pathinfo: false,
+	},
+	resolve: {
+		extensions: [
+			'.js', '.ts', '.json'
+		],
+		alias: {
+			'@': __dirname + '/src/',
+		}
+	},
+	resolveLoader: {
+		modules: ['node_modules']
+	},
+	devtool: false, //'source-map',
+	mode: isProduction ? 'production' : 'development'
+};
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=
diff --git a/scripts/build.js b/scripts/build.js
index 783af78271..608648b953 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -17,6 +17,14 @@ const execa = require('execa');
 		stderr: process.stderr,
 	});
 
+	console.log('building packages/sw ...');
+
+	await execa('npm', ['run', 'build'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
+
 	console.log('build finishing ...');
 
 	await execa('npm', ['run', 'gulp'], {
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index 814ff3f257..456b88032b 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -7,6 +7,9 @@ const fs = require('fs');
 	fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true });
 
+	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
+
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
 })();
diff --git a/scripts/clean.js b/scripts/clean.js
index a14f1fb35b..70b9d882b5 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -3,5 +3,6 @@ const fs = require('fs');
 (async () => {
 	fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 })();
diff --git a/scripts/dev.js b/scripts/dev.js
index b7dd870c41..c5dbb7b35a 100644
--- a/scripts/dev.js
+++ b/scripts/dev.js
@@ -25,6 +25,12 @@ const execa = require('execa');
 		stderr: process.stderr,
 	});
 
+	execa('npm', ['run', 'watch'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
+
 	const start = async () => {
 		try {
 			await execa('npm', ['run', 'start'], {
diff --git a/scripts/install-packages.js b/scripts/install-packages.js
index c25063b29a..bc8e016a3c 100644
--- a/scripts/install-packages.js
+++ b/scripts/install-packages.js
@@ -16,4 +16,12 @@ const execa = require('execa');
 		stdout: process.stdout,
 		stderr: process.stderr,
 	});
+
+	console.log('installing dependencies of packages/sw ...');
+
+	await execa('yarn', ['install'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
 })();
diff --git a/scripts/lint.js b/scripts/lint.js
index 11aa4909b1..72a63f4ba3 100644
--- a/scripts/lint.js
+++ b/scripts/lint.js
@@ -14,4 +14,11 @@ const execa = require('execa');
 		stdout: process.stdout,
 		stderr: process.stderr,
 	});
+
+	console.log('linting packages/sw ...');
+	await execa('npm', ['run', 'lint'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
 })();

From 3dc027bcd5b351ac2d3307f3d9a406f7f4ed2850 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sun, 1 May 2022 04:23:12 +0200
Subject: [PATCH 050/258] Refactor integration to use Composition API (#8581)

* refactor(client): refactor integration to use Composition API

* fix(client): drop superfluous enable* constants

* refactor(client): deduplicate window opening for services
---
 .../client/src/pages/settings/integration.vue | 167 +++++++-----------
 1 file changed, 66 insertions(+), 101 deletions(-)

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');
-		},
+	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();
+		}
+	});
+});
 
-		connectGithub() {
-			this.githubForm = window.open(apiUrl + '/connect/github',
-				'github_connect_window',
-				'height=570, width=520');
-		},
-
-		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>

From 475b7556d817072955ca8ff27fe3e36d980e91c3 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sun, 1 May 2022 04:52:19 +0200
Subject: [PATCH 051/258] Refactor instance-mute to use Composition API (#8580)

* refactor(client): refactor instance-mute to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../src/pages/settings/instance-mute.vue      | 76 ++++++++-----------
 1 file changed, 30 insertions(+), 46 deletions(-)

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>

From 274ca6f7e639fe85017d901b7cf63c6ee55a2df5 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sun, 1 May 2022 04:55:15 +0200
Subject: [PATCH 052/258] refactor(client): refactor import-export to use
 Composition API (#8579)

---
 .../src/pages/settings/import-export.vue      | 154 +++++++-----------
 1 file changed, 63 insertions(+), 91 deletions(-)

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>
 

From a00a1fd6b5b6b0d34ea40f39e5db188a6c2a4793 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sun, 1 May 2022 08:50:09 +0200
Subject: [PATCH 053/258] Refactor custom-css to use Composition API (#8571)

* refactor(client): refactor custom-css to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/pages/settings/custom-css.vue  | 56 ++++++++-----------
 1 file changed, 22 insertions(+), 34 deletions(-)

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);
+watch(localCustomCss, async () => {
+	await apply();
+});
 
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.customCss,
+		icon: 'fas fa-code',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From b00bf5740a01051dfb04b9aed3b5381c59d52c5c Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 1 May 2022 06:59:43 +0000
Subject: [PATCH 054/258] modify CHANGELOG.md

---
 CHANGELOG.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b07e9002cf..510bf2121f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,6 @@
 ## 12.x.x (unreleased)
 
 ### Improvements
-- API: notifications/readは配列でも受け付けるように
-- /share のクエリでリプライやファイル等の情報を渡せるように
-- ページロードエラーページにリロードボタンを追加
 
 ### Bugfixes
 - 
@@ -18,6 +15,9 @@ You should also include the user name that made the change.
 
 ### Improvements
 - enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
+- enhance: API: notifications/readは配列でも受け付けるように #7667 @tamaina
+- enhance: プッシュ通知を複数アカウント対応に #7667 @tamaina
+- enhance: プッシュ通知にクリックやactionを設定 #7667 @tamaina
 
 ### Bugfixes
 - Client: fix settings page @tamaina

From 60391ff37e147c906a8736dbfede9d3b5e7ede3a Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Sun, 1 May 2022 19:14:14 +0900
Subject: [PATCH 055/258] fix: Add rel attribute to host-meta (#8583)

* Add rel attribute to host-meta

* CHANGELOG
---
 CHANGELOG.md                              | 1 +
 packages/backend/src/server/well-known.ts | 1 +
 2 files changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 510bf2121f..a13686b438 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ You should also include the user name that made the change.
 - Client: fix profile tabs @futchitwo
 - Server: await promises when following or unfollowing users @Johann150
 - Client: fix abuse reports page to be able to show all reports @Johann150
+- Federation: Add rel attribute to host-meta @mei23
 
 ## 12.110.1 (2022/04/23)
 
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}`,
 	} });

From 6ed010b19238be79435238df604a78b2e10e9c4b Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 1 May 2022 12:23:34 +0200
Subject: [PATCH 056/258] fix _misskey_content of quote renotes (#8533)

---
 packages/backend/src/services/note/create.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index f14bc2059b..ceb5e8cc71 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;

From a89003b57a27379f056e4f1be907e41e91b7b598 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 1 May 2022 22:51:07 +0900
Subject: [PATCH 057/258] refactor: use Vite to build instead of webpack
 (#8575)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* update stream.ts

* https://github.com/misskey-dev/misskey/pull/7769#issuecomment-917542339

* fix lint

* clean up?

* add app

* fix

* nanka iroiro

* wip

* wip

* fix lint

* fix loginId

* fix

* refactor

* refactor

* remove follow action

* clean up

* Revert "remove follow action"

This reverts commit defbb416480905af2150d1c92f10d8e1d1288c0a.

* Revert "clean up"

This reverts commit f94919cb9cff41e274044fc69c56ad36a33974f2.

* remove fetch specification

* renoteの条件追加

* apiFetch => cli

* bypass fetch?

* fix

* refactor: use path alias

* temp: add submodule

* remove submodule

* enhane: unison-reloadに指定したパスに移動できるように

* null

* null

* feat: ログインするアカウントのIDをクエリ文字列で指定する機能

* null

* await?

* rename

* rename

* Update read.ts

* merge

* get-note-summary

* fix

* swパッケージに

* add missing packages

* fix getNoteSummary

* add webpack-cli

* :v:

* remove plugins

* sw-inject分離したがテストしてない

* fix notification.vue

* remove a blank line

* disconnect intersection observer

* disconnect2

* fix notification.vue

* remove a blank line

* disconnect intersection observer

* disconnect2

* fix

* :v:

* clean up config

* typesを戻した

* Update packages/client/src/components/notification.vue

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

* disconnect

* oops

* Failed to load the script unexpectedly回避
sw.jsとlib.tsを分離してみた

* truncate notification

* Update packages/client/src/ui/_common_/common.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>

* clean up

* clean up

* キャッシュ対策

* Truncate push notification message

* クライアントがあったらストリームに接続しているということなので通知しない判定の位置を修正

* components/drive-file-thumbnail.vue

* components/drive-select-dialog.vue

* components/drive-window.vue

* merge

* fix

* Service Workerのビルドにesbuildを使うようにする

* return createEmptyNotification()

* fix

* i18n.ts

* update

* :v:

* remove ts-loader

* fix

* fix

* enhance: Service Workerを常に登録するように

* pollEnded

* URLをsw.jsに戻す

* clean up

* wip

* wip

* wip

* wip

* wip

* wip

* :v:

* use import

* fix

* install rollup

* use defineAsyncComponent.

* fix emojilist

* wip use defineAsyncComponent

* popup(import -> popup(defineAsyncComponent(() => import

* draggable?

* fix init import

* clean up

* fix router

* add comment

* :v:

* :v:

* :v:

* remove webpack

* update vite

* fix boot sequence

* Revert "fix boot sequence"

This reverts commit e893dbf37aed83bf9f12e427d98c78a7065b4a39.

* revert boot import

* never make two app div

* ;

* remove console.log

* change clientEntry sequence

* fix

* Revert "fix"

This reverts commit 12741b3d89950a31dbb1bb81477ddb27b0e9951a.

* fix

* add comment https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210

* add log

* add comment

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 gulpfile.js                                   |    1 -
 packages/backend/src/config/load.ts           |    2 +
 packages/backend/src/config/types.ts          |    1 +
 packages/backend/src/server/web/boot.js       |   14 +-
 packages/backend/src/server/web/index.ts      |    4 +
 .../backend/src/server/web/views/base.pug     |    4 +
 packages/client/@types/theme.d.ts             |    5 +
 packages/client/package.json                  |   24 +-
 packages/client/src/account.ts                |    6 +-
 .../client/src/components/abuse-report.vue    |    8 +-
 .../client/src/components/analog-clock.vue    |    2 +-
 packages/client/src/components/chart.vue      |   12 +-
 packages/client/src/components/drive.file.vue |    4 +-
 .../client/src/components/drive.folder.vue    |    4 +-
 .../client/src/components/emoji-picker.vue    |    2 +-
 packages/client/src/components/form/range.vue |    4 +-
 .../client/src/components/global/header.vue   |    2 +-
 packages/client/src/components/global/url.vue |    4 +-
 packages/client/src/components/link.vue       |    4 +-
 packages/client/src/components/mention.vue    |    2 +-
 .../src/components/post-form-attaches.vue     |    2 +-
 packages/client/src/components/post-form.vue  |    4 +-
 packages/client/src/components/signin.vue     |    4 +-
 packages/client/src/components/signup.vue     |    2 +-
 packages/client/src/components/ui/folder.vue  |    2 +-
 packages/client/src/components/widgets.vue    |    2 +-
 packages/client/src/directives/tooltip.ts     |    4 +-
 .../client/src/directives/user-preview.ts     |    4 +-
 packages/client/src/init.ts                   |   42 +-
 packages/client/src/os.ts                     |   47 +-
 packages/client/src/pages/admin/emojis.vue    |    4 +-
 packages/client/src/pages/admin/files.vue     |    4 +-
 packages/client/src/pages/api-console.vue     |    4 +-
 packages/client/src/pages/reset-password.vue  |    4 +-
 .../client/src/pages/settings/accounts.vue    |    6 +-
 packages/client/src/pages/settings/api.vue    |    4 +-
 packages/client/src/pages/settings/drive.vue  |    2 +-
 .../src/pages/settings/notifications.vue      |    4 +-
 .../src/pages/settings/plugin.install.vue     |    4 +-
 .../client/src/pages/settings/reaction.vue    |    4 +-
 .../src/pages/settings/theme.install.vue      |    2 +-
 .../src/pages/settings/theme.manage.vue       |    2 +-
 packages/client/src/pages/settings/theme.vue  |    2 +-
 packages/client/src/pages/theme-editor.vue    |    4 +-
 packages/client/src/pages/user/index.vue      |    2 +-
 packages/client/src/router.ts                 |   51 +-
 packages/client/src/scripts/autocomplete.ts   |    6 +-
 packages/client/src/scripts/emojilist.ts      |    2 +-
 packages/client/src/scripts/get-note-menu.ts  |    4 +-
 packages/client/src/scripts/get-user-menu.ts  |    3 +-
 packages/client/src/scripts/hpml/lib.ts       |    4 +-
 .../client/src/scripts/reaction-picker.ts     |    4 +-
 packages/client/src/scripts/theme.ts          |   38 +-
 packages/client/src/store.ts                  |    7 +-
 packages/client/src/ui/_common_/common.vue    |    2 +-
 .../src/ui/_common_/sidebar-for-mobile.vue    |    4 +-
 packages/client/src/ui/_common_/sidebar.vue   |    4 +-
 packages/client/src/ui/classic.header.vue     |    4 +-
 packages/client/src/ui/classic.sidebar.vue    |    4 +-
 .../src/ui/deck/notifications-column.vue      |    4 +-
 packages/client/src/widgets/notifications.vue |    3 +-
 packages/client/tsconfig.json                 |    6 +
 packages/client/vite.config.ts                |   72 +
 packages/client/vite.json5.ts                 |   38 +
 packages/client/webpack.config.js             |  192 --
 packages/client/yarn.lock                     | 2102 ++---------------
 66 files changed, 548 insertions(+), 2280 deletions(-)
 create mode 100644 packages/client/@types/theme.d.ts
 create mode 100644 packages/client/vite.config.ts
 create mode 100644 packages/client/vite.json5.ts
 delete mode 100644 packages/client/webpack.config.js

diff --git a/gulpfile.js b/gulpfile.js
index b7aa4e328e..90f8ebaabe 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -37,7 +37,6 @@ gulp.task('copy:client:locales', cb => {
 
 gulp.task('build:backend:script', () => {
 	return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
-		.pipe(replace('VERSION', JSON.stringify(meta.version)))
 		.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
 		.pipe(terser({
 			toplevel: true
diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts
index 7f765463e4..c2e6bea45e 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'].file.replace(/^_client_dist_\//, '');
 
 	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/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 751e8619bf..a9ee0df4f1 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -58,15 +58,11 @@
 		? `?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}${salt}`)
+		.catch(async () => {
+			await checkUpdate();
+			renderError('APP_FETCH_FAILED');
+		})
 	//#endregion
 
 	//#region Theme
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 061ea50609..9e31f2389e 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';
@@ -73,6 +74,9 @@ app.use(views(_dirname + '/views', {
 	extension: 'pug',
 	options: {
 		version: config.version,
+		clientEntry: () => process.env.NODE_ENV === 'production' ?
+			config.clientEntry :
+			JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'].file.replace(/^_client_dist_\//, ''),
 		config,
 	},
 }));
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 1513208310..d79354d118 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -50,6 +50,10 @@ html
 		style
 			include ../style.css
 
+		script.
+			var VERSION = "#{version}";
+			var CLIENT_ENTRY = "#{clientEntry()}";
+
 		script
 			include ../boot.js
 
diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts
new file mode 100644
index 0000000000..b8b906b82e
--- /dev/null
+++ b/packages/client/@types/theme.d.ts
@@ -0,0 +1,5 @@
+import { Theme } from '../src/scripts/theme';
+
+declare module '@/themes/*.json5' {
+	export = Theme;
+}
diff --git a/packages/client/package.json b/packages/client/package.json
index e533e1fb87..1d62d78d88 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,8 +1,8 @@
 {
 	"private": true,
 	"scripts": {
-		"watch": "webpack --watch",
-		"build": "webpack",
+		"watch": "vite build --watch --mode development",
+		"build": "vite build",
 		"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
 	},
 	"resolutions": {
@@ -12,8 +12,11 @@
 	"dependencies": {
 		"@discordapp/twemoji": "13.1.1",
 		"@fortawesome/fontawesome-free": "6.1.1",
+		"@rollup/plugin-alias": "3.1.9",
+		"@rollup/plugin-json": "4.1.0",
 		"@syuilo/aiscript": "0.11.1",
 		"@typescript-eslint/parser": "5.20.0",
+		"@vitejs/plugin-vue": "2.3.1",
 		"@vue/compiler-sfc": "3.2.33",
 		"abort-controller": "3.0.0",
 		"autobind-decorator": "2.4.0",
@@ -28,8 +31,6 @@
 		"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",
 		"date-fns": "2.28.0",
 		"escape-regexp": "0.0.1",
 		"eslint": "8.14.0",
@@ -40,7 +41,6 @@
 		"idb-keyval": "6.1.0",
 		"insert-text-at-cursor": "0.3.0",
 		"json5": "2.2.1",
-		"json5-loader": "4.0.1",
 		"katex": "0.15.3",
 		"matter-js": "0.18.0",
 		"mfm-js": "0.21.0",
@@ -51,8 +51,6 @@
 		"parse5": "6.0.1",
 		"photoswipe": "5.2.4",
 		"portscanner": "2.2.0",
-		"postcss": "8.4.12",
-		"postcss-loader": "6.2.1",
 		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
 		"promise-limit": "2.7.0",
@@ -63,19 +61,17 @@
 		"random-seed": "0.3.0",
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
+		"rollup": "2.70.2",
 		"s-age": "1.1.2",
 		"sass": "1.50.1",
-		"sass-loader": "12.6.0",
 		"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.1",
 		"tinycolor2": "1.4.2",
-		"ts-loader": "9.2.8",
 		"tsc-alias": "1.5.0",
 		"tsconfig-paths": "3.14.1",
 		"twemoji-parser": "14.0.0",
@@ -83,15 +79,11 @@
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
+		"vite": "2.9.6",
 		"vue": "3.2.33",
-		"vue-loader": "17.0.0",
 		"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",
 		"vuedraggable": "4.0.1",
-		"webpack": "5.72.0",
-		"webpack-cli": "4.9.2",
 		"websocket": "1.0.34",
 		"ws": "8.5.0"
 	},
@@ -113,8 +105,6 @@
 		"@types/throttle-debounce": "4.0.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/eslint-plugin": "5.20.0",
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts
index f4dcab319c..6f806ccc58 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';
@@ -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.vue b/packages/client/src/components/abuse-report.vue
index b67cef209b..46d45b690f 100644
--- a/packages/client/src/components/abuse-report.vue
+++ b/packages/client/src/components/abuse-report.vue
@@ -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/chart.vue b/packages/client/src/components/chart.vue
index cc1aa9c20a..a7d5206c71 100644
--- a/packages/client/src/components/chart.vue
+++ b/packages/client/src/components/chart.vue
@@ -29,7 +29,9 @@ 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';
@@ -50,7 +52,7 @@ Chart.register(
 	SubTitle,
 	Filler,
 	zoomPlugin,
-	gradient,
+	//gradient,
 );
 
 const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
@@ -221,7 +223,7 @@ export default defineComponent({
 						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 : {
+						/*gradient: props.bar ? undefined : {
 							backgroundColor: {
 								axis: 'y',
 								colors: {
@@ -229,7 +231,7 @@ export default defineComponent({
 									[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2),
 								},
 							},
-						},
+						},*/
 						barPercentage: 0.9,
 						categoryPercentage: 0.9,
 						fill: x.type === 'area',
@@ -340,7 +342,7 @@ export default defineComponent({
 								},
 							}
 						} : undefined,
-						gradient,
+						//gradient,
 					},
 				},
 				plugins: [{
diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue
index 262eae0de1..e2f78a84ec 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';
@@ -133,7 +133,7 @@ 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,
diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue
index 57621bf097..e7003a8074 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';
@@ -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/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue
index 8601ea121c..ae74f04c02 100644
--- a/packages/client/src/components/emoji-picker.vue
+++ b/packages/client/src/components/emoji-picker.vue
@@ -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">
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/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/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/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/mention.vue b/packages/client/src/components/mention.vue
index 479acfbc8f..39a333c5e8 100644
--- a/packages/client/src/components/mention.vue
+++ b/packages/client/src/components/mention.vue
@@ -16,7 +16,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 import { toUnicode } from 'punycode';
 import { host as localHost } from '@/config';
 import { $i } from '@/account';
diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue
index 9dd69a0ee5..47e2e6ce0f 100644
--- a/packages/client/src/components/post-form-attaches.vue
+++ b/packages/client/src/components/post-form-attaches.vue
@@ -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,
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 241c726c11..6d79736003 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';
@@ -384,7 +384,7 @@ function setVisibility() {
 		return;
 	}
 
-	os.popup(import('./visibility-picker.vue'), {
+	os.popup(defineAsyncComponent(() => import('./visibility-picker.vue')), {
 		currentVisibility: visibility,
 		currentLocalOnly: localOnly,
 		src: visibilityButton,
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index f640e948ad..bdf247a56f 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -46,7 +46,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { toUnicode } from 'punycode/';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
@@ -224,7 +224,7 @@ export default defineComponent({
 		},
 
 		resetPassword() {
-			os.popup(import('@/components/forgot-password.vue'), {}, {
+			os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
 			}, 'closed');
 		}
 	}
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index 38a9fd55f1..62f370ffa8 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -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';
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/widgets.vue b/packages/client/src/components/widgets.vue
index da9d935281..6e4122427b 100644
--- a/packages/client/src/components/widgets.vue
+++ b/packages/client/src/components/widgets.vue
@@ -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/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/init.ts b/packages/client/src/init.ts
index 49dfd8c06f..5dbbcb2a2b 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';
@@ -168,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;
@@ -203,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のやつを解除
@@ -230,7 +246,7 @@ 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) {
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index b8a3f94cc8..eada01bf20 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -110,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;
@@ -131,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;
@@ -163,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,
@@ -172,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,
@@ -180,7 +173,7 @@ export function modalPageWindow(path: string) {
 }
 
 export function toast(message: string) {
-	popup(import('@/components/toast.vue'), {
+	popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
 		message
 	}, {}, 'closed');
 }
@@ -191,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();
 			},
@@ -205,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,
 		}, {
@@ -226,7 +219,7 @@ 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: {
@@ -251,7 +244,7 @@ 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: {
@@ -276,7 +269,7 @@ 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: {
@@ -313,7 +306,7 @@ 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: {
@@ -335,7 +328,7 @@ 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
 		}, {
@@ -347,7 +340,7 @@ 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
 		}, {
@@ -358,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);
 			},
@@ -368,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);
 			},
@@ -378,7 +371,7 @@ 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
 		}, {
@@ -393,7 +386,7 @@ 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
 		}, {
@@ -408,7 +401,7 @@ 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
 		}, {
@@ -458,7 +451,7 @@ 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
 	}, {
@@ -480,7 +473,7 @@ 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,
@@ -501,7 +494,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,
 		}, {
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index a080ee9c23..43dd83fc02 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,7 +130,7 @@ 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 => {
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/api-console.vue b/packages/client/src/pages/api-console.vue
index 7f174a6318..88acbcd3a3 100644
--- a/packages/client/src/pages/api-console.vue
+++ b/packages/client/src/pages/api-console.vue
@@ -26,8 +26,8 @@
 </template>
 
 <script lang="ts" setup>
-import { defineExpose, ref } from 'vue';
-import * as JSON5 from 'json5';
+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';
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/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index a744a031d4..349c684f7c 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -21,7 +21,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
@@ -78,7 +78,7 @@ export default defineComponent({
 		},
 
 		addExistingAccount() {
-			os.popup(import('@/components/signin-dialog.vue'), {}, {
+			os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
 				done: res => {
 					addAccount(res.id, res.i);
 					os.success();
@@ -87,7 +87,7 @@ export default defineComponent({
 		},
 
 		createAccount() {
-			os.popup(import('@/components/signup-dialog.vue'), {}, {
+			os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
 				done: res => {
 					addAccount(res.id, res.i);
 					this.switchAccountWithToken(res.i);
diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue
index 20ff2a8d96..23e34e3343 100644
--- a/packages/client/src/pages/settings/api.vue
+++ b/packages/client/src/pages/settings/api.vue
@@ -7,7 +7,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
@@ -34,7 +34,7 @@ export default defineComponent({
 
 	methods: {
 		generateToken() {
-			os.popup(import('@/components/token-generate-window.vue'), {}, {
+			os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, {
 				done: async result => {
 					const { name, permissions } = result;
 					const { token } = await os.api('miauth/gen-token', {
diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue
index 9309eb5ec7..c3bdf4f6c6 100644
--- a/packages/client/src/pages/settings/drive.vue
+++ b/packages/client/src/pages/settings/drive.vue
@@ -35,7 +35,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
+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';
diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 12171530bb..334216ff33 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -10,7 +10,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import FormButton from '@/components/ui/button.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
@@ -52,7 +52,7 @@ export default defineComponent({
 
 		configure() {
 			const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x));
-			os.popup(import('@/components/notification-setting-window.vue'), {
+			os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
 				includingTypes,
 				showGlobalToggle: false,
 			}, {
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index d35d20d17a..298f6bc1f0 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -13,7 +13,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { AiScript, parse } from '@syuilo/aiscript';
 import { serialize } from '@syuilo/aiscript/built/serializer';
 import { v4 as uuid } from 'uuid';
@@ -94,7 +94,7 @@ export default defineComponent({
 			}
 
 			const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
-				os.popup(import('@/components/token-generate-window.vue'), {
+				os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {
 					title: this.$ts.tokenRequested,
 					information: this.$ts.pluginTokenRequestedDescription,
 					initialName: name,
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/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index 2d3514342e..0ef098f318 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';
diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index a1e849b540..2eb16bb701 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -27,7 +27,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
+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';
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index d134a092b6..a3ddc9a2ff 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -87,7 +87,7 @@
 
 <script lang="ts">
 import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue';
-import * as JSON5 from 'json5';
+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';
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index a53e23c1c5..5f9f1b9783 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -67,9 +67,9 @@
 <script lang="ts" setup>
 import { watch } from 'vue';
 import { toUnicode } from 'punycode/';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 import { v4 as uuid} from 'uuid';
-import * as JSON5 from 'json5';
+import JSON5 from 'json5';
 
 import FormButton from '@/components/ui/button.vue';
 import FormTextarea from '@/components/form/textarea.vue';
diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue
index 405494ec23..17e815892b 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';
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/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts
index f4a3a4c0fc..bf60e5805a 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';
 
@@ -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,
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/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts
index b19656d3cc..a50001b24e 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';
@@ -253,7 +253,7 @@ export function getNoteMenu(props: {
 				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');
diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts
index 192d14b83e..b9e4066e42 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');
 	}
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/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/theme.ts b/packages/client/src/scripts/theme.ts
index 2cb78fae5c..b61b1684a8 100644
--- a/packages/client/src/scripts/theme.ts
+++ b/packages/client/src/scripts/theme.ts
@@ -1,5 +1,5 @@
 import { globalEvents } from '@/events';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 
 export type Theme = {
 	id: string;
@@ -10,29 +10,29 @@ export type Theme = {
 	props: Record<string, string>;
 };
 
-export const lightTheme: Theme = require('@/themes/_light.json5');
-export const darkTheme: Theme = require('@/themes/_dark.json5');
+export const lightTheme: Theme = await import('@/themes/_light.json5');
+export const darkTheme: Theme = await import('@/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'),
+	await import('@/themes/l-light.json5'),
+	await import('@/themes/l-coffee.json5'),
+	await import('@/themes/l-apricot.json5'),
+	await import('@/themes/l-rainy.json5'),
+	await import('@/themes/l-vivid.json5'),
+	await import('@/themes/l-cherry.json5'),
+	await import('@/themes/l-sushi.json5'),
 
-	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'),
+	await import('@/themes/d-dark.json5'),
+	await import('@/themes/d-persimmon.json5'),
+	await import('@/themes/d-astro.json5'),
+	await import('@/themes/d-future.json5'),
+	await import('@/themes/d-botanical.json5'),
+	await import('@/themes/d-cherry.json5'),
+	await import('@/themes/d-ice.json5'),
+	await import('@/themes/d-pumpkin.json5'),
+	await import('@/themes/d-black.json5'),
 ] as Theme[];
 
 let timeout = null;
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index b9800ec607..6ab14f43fd 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/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue
index 62e97a11e1..9f7388db53 100644
--- a/packages/client/src/ui/_common_/common.vue
+++ b/packages/client/src/ui/_common_/common.vue
@@ -39,7 +39,7 @@ export default defineComponent({
 					id: notification.id
 				});
 
-				popup(import('@/components/notification-toast.vue'), {
+				popup(defineAsyncComponent(() => import('@/components/notification-toast.vue')), {
 					notification
 				}, {}, 'closed');
 			}
diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue
index afcc50725b..064a63bf29 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';
@@ -67,7 +67,7 @@ export default defineComponent({
 				}, 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/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..ad11c3ebd5 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,7 +121,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,
 			}, {}, 'closed');
 		},
diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue
index 50ee12a275..89d618382e 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';
@@ -24,7 +24,7 @@ const emit = defineEmits<{
 }>();
 
 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/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue
index 8cf29c9271..7f0055bb43 100644
--- a/packages/client/src/widgets/notifications.vue
+++ b/packages/client/src/widgets/notifications.vue
@@ -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';
 
@@ -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/tsconfig.json b/packages/client/tsconfig.json
index db91b99e7d..2c387286a4 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,6 +29,9 @@
 			"node_modules/@types",
 			"@types",
 		],
+		"types": [
+			"vite/client",
+		],
 		"lib": [
 			"esnext",
 			"dom"
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 adb5fb81b5..0000000000
--- a/packages/client/webpack.config.js
+++ /dev/null
@@ -1,192 +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',
-	},
-	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 59abe67862..97598e0d3e 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"
@@ -98,11 +82,6 @@
     twemoji-parser "13.1.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.2":
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
@@ -170,6 +149,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 +200,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 +210,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 +272,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"
@@ -371,11 +319,6 @@
   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"
@@ -386,11 +329,6 @@
   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,16 +356,6 @@
   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@4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ae62a652c914f46276c78730f53fab85b4825f09"
@@ -438,13 +366,6 @@
   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 +400,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"
@@ -623,6 +506,11 @@
   resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
   integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
 
+"@vitejs/plugin-vue@2.3.1":
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.1.tgz#5f286b8d3515381c6d5c8fa8eee5e6335f727e14"
+  integrity sha512-YNzBt8+jt6bSwpt7LP890U1UcTOIZZxfpE5WOJ638PNxSEKOqAi0+FSKS0nVeukfdZ0Ai/H7AFd6k3hayfGZqQ==
+
 "@vue/compiler-core@3.2.33":
   version "3.2.33"
   resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz#e915d59cce85898f5c5cfebe4c09e539278c3d59"
@@ -718,275 +606,6 @@
   resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e"
   integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==
 
-"@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==
-  dependencies:
-    "@webassemblyjs/helper-numbers" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-
-"@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==
-
 abort-controller@3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
@@ -994,11 +613,6 @@ 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"
@@ -1009,16 +623,6 @@ acorn@^7.1.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"
@@ -1037,12 +641,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 +668,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 +689,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"
@@ -1241,11 +826,6 @@ bcrypt-pbkdf@^1.0.0:
   dependencies:
     tweetnacl "^0.14.3"
 
-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"
@@ -1266,7 +846,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=
@@ -1308,27 +888,11 @@ browser-stdout@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"
@@ -1372,21 +936,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"
@@ -1400,15 +949,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"
@@ -1471,13 +1011,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"
@@ -1530,31 +1063,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"
@@ -1562,31 +1070,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"
@@ -1599,26 +1092,11 @@ 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:
   version "8.3.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
@@ -1659,17 +1137,6 @@ 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==
-  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"
-
 cross-env@7.0.3:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"
@@ -1686,153 +1153,11 @@ 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"
@@ -1961,7 +1286,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==
@@ -2019,69 +1344,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"
@@ -2095,21 +1357,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"
@@ -2122,30 +1374,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"
@@ -2153,40 +1381,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"
@@ -2213,16 +1407,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"
@@ -2258,6 +1442,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"
@@ -2325,7 +1635,7 @@ eslint-plugin-vue@8.7.1:
     semver "^7.3.5"
     vue-eslint-parser "^8.0.1"
 
-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==
@@ -2430,11 +1740,6 @@ espree@^9.3.1:
     acorn-jsx "^5.3.1"
     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 +1769,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 +1812,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 +1842,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"
@@ -2631,11 +1921,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"
@@ -2701,7 +1986,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==
@@ -2790,6 +2075,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"
@@ -2862,11 +2152,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"
@@ -2944,7 +2229,7 @@ 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:
+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,11 +2239,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"
@@ -2974,17 +2254,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,16 +2283,6 @@ 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"
@@ -3049,11 +2314,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"
@@ -3094,14 +2354,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 +2364,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"
@@ -3154,11 +2401,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-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"
@@ -3169,11 +2411,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"
@@ -3196,7 +2433,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==
@@ -3213,13 +2450,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"
@@ -3313,19 +2543,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==
@@ -3398,34 +2621,11 @@ isexe@^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"
@@ -3442,11 +2642,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"
@@ -3454,29 +2649,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@~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"
@@ -3497,15 +2674,6 @@ 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:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
@@ -3518,13 +2686,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"
@@ -3575,21 +2736,6 @@ katex@0.15.3:
   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"
@@ -3603,16 +2749,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"
@@ -3626,29 +2762,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"
@@ -3676,11 +2789,6 @@ lodash.isfinite@^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"
@@ -3691,11 +2799,6 @@ 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:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@@ -3743,21 +2846,6 @@ 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"
@@ -3780,7 +2868,7 @@ mfm-js@0.21.0:
   dependencies:
     twemoji-parser "13.1.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==
@@ -3801,7 +2889,7 @@ mime-db@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==
@@ -3848,13 +2936,6 @@ 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"
@@ -3915,11 +2996,6 @@ natural-compare@^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"
@@ -3950,21 +3026,11 @@ node-gyp-build@~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"
@@ -3972,20 +3038,6 @@ 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==
-  dependencies:
-    boolbase "^1.0.0"
-
 nth-check@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2"
@@ -4008,26 +3060,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"
@@ -4038,24 +3075,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"
@@ -4127,13 +3146,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"
@@ -4194,16 +3206,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"
@@ -4276,18 +3278,16 @@ 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:
+  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==
-  dependencies:
-    find-up "^4.0.0"
-
 pngjs@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
@@ -4301,242 +3301,6 @@ portscanner@2.2.0:
     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"
@@ -4545,40 +3309,6 @@ 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==
-  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"
@@ -4588,10 +3318,10 @@ postcss@^8.1.10:
     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==
+postcss@^8.4.12:
+  version "8.4.12"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
+  integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
   dependencies:
     nanoid "^3.3.1"
     picocolors "^1.0.0"
@@ -4767,11 +3497,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"
@@ -4818,13 +3543,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"
@@ -4862,13 +3580,6 @@ 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"
@@ -4882,11 +3593,6 @@ resolve-from@^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"
@@ -4904,14 +3610,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"
@@ -4940,6 +3638,13 @@ rndstr@1.0.0:
     rangestr "0.0.1"
     seedrandom "2.4.2"
 
+rollup@2.70.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"
@@ -4984,14 +3689,6 @@ 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.1:
   version "1.50.1"
   resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.1.tgz#e9b078a1748863013c4712d2466ce8ca4e4ed292"
@@ -5001,29 +3698,11 @@ sass@1.50.1:
     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"
@@ -5034,7 +3713,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==
@@ -5048,32 +3727,18 @@ semver@^7.3.5:
   dependencies:
     lru-cache "^6.0.0"
 
-serialize-javascript@6.0.0, serialize-javascript@^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"
@@ -5128,11 +3793,6 @@ 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"
@@ -5143,24 +3803,11 @@ source-map-js@^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"
@@ -5173,11 +3820,6 @@ split@0.3:
   dependencies:
     through "2"
 
-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"
@@ -5193,11 +3835,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"
@@ -5232,14 +3869,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"
@@ -5248,32 +3877,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"
@@ -5318,34 +3921,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==
@@ -5357,90 +3940,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"
@@ -5508,16 +4012,6 @@ 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==
-  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"
@@ -5628,11 +4122,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"
@@ -5651,11 +4140,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"
@@ -5680,16 +4164,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"
@@ -5724,6 +4198,18 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vite@2.9.6:
+  version "2.9.6"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.6.tgz#29f1b33193b0de9e155d67ba0dd097501c3c3281"
+  integrity sha512-3IffdrByHW95Yjv0a13TQOQfJs7L5dVlSPuTt432XLbRMriWbThqJN2k/IS6kXn5WY4xBLhK9XoaWay1B8VzUw==
+  dependencies:
+    esbuild "^0.14.27"
+    postcss "^8.4.12"
+    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"
@@ -5742,15 +4228,6 @@ vue-eslint-parser@^8.0.1:
     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"
-
 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"
@@ -5763,23 +4240,6 @@ vue-router@4.0.14:
   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==
-  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.33:
   version "3.2.33"
   resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.33.tgz#7867eb16a3293a28c4d190a837bc447878bd64c2"
@@ -5809,120 +4269,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"
@@ -5965,11 +4311,6 @@ which@^1.2.14:
   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"
@@ -6045,16 +4386,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"
@@ -6115,8 +4446,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==

From 38e6202bdc2d4c935da652e42a3844463bf1395a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 1 May 2022 22:52:11 +0900
Subject: [PATCH 058/258] chore(deps): bump postcss from 8.2.8 to 8.4.13 in
 /packages/client (#8588)

Bumps [postcss](https://github.com/postcss/postcss) from 8.2.8 to 8.4.13.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.2.8...8.4.13)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 packages/client/yarn.lock | 33 ++++++++++++---------------------
 1 file changed, 12 insertions(+), 21 deletions(-)

diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 97598e0d3e..23908a1b1e 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -2986,11 +2986,16 @@ mylas@^2.1.6:
   resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800"
   integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ==
 
-nanoid@3.3.1, nanoid@^3.1.20, nanoid@^3.3.1:
+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:
+  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"
@@ -3309,21 +3314,12 @@ postcss-selector-parser@^6.0.9:
     cssesc "^3.0.0"
     util-deprecate "^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==
+postcss@^8.1.10, postcss@^8.4.12:
+  version "8.4.13"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
+  integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
   dependencies:
-    colorette "^1.2.2"
-    nanoid "^3.1.20"
-    source-map "^0.6.1"
-
-postcss@^8.4.12:
-  version "8.4.12"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
-  integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
-  dependencies:
-    nanoid "^3.3.1"
+    nanoid "^3.3.3"
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
@@ -3793,12 +3789,7 @@ 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-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==

From 49b63154dfb819bef7ff05bf5bd3afd0c22335bb Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sun, 1 May 2022 17:55:17 +0200
Subject: [PATCH 059/258] fix(client): fix missing import of
 defineAsyncComponent in os.ts

---
 packages/client/src/os.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index eada01bf20..06a8ff99dc 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -1,6 +1,6 @@
 // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
 
-import { Component, markRaw, 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';

From 0e26fae3bb5beaa587101f7792b904398f620244 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Tue, 3 May 2022 13:33:40 +0200
Subject: [PATCH 060/258] refactor(client): refactor settings/accounts to use
 Composition API (#8604)

---
 .../client/src/pages/settings/accounts.vue    | 141 +++++++++---------
 1 file changed, 70 insertions(+), 71 deletions(-)

diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index 349c684f7c..ecb2d036f2 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 { defineAsyncComponent, 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);
+	});
+}
+
+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);
+}
+
+function addAccount(ev) {
+	os.popupMenu([{
+		text: i18n.ts.existingAccount,
+		action: () => { addExistingAccount(); },
+	}, {
+		text: i18n.ts.createAccount,
+		action: () => { createAccount(); },
+	}], ev.currentTarget ?? ev.target);
+}
+
+function addExistingAccount() {
+	os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
+		done: res => {
+			addAccounts(res.id, res.i);
+			os.success();
 		},
+	}, 'closed');
+}
 
-		addAccount(ev) {
-			os.popupMenu([{
-				text: this.$ts.existingAccount,
-				action: () => { this.addExistingAccount(); },
-			}, {
-				text: this.$ts.createAccount,
-				action: () => { this.createAccount(); },
-			}], ev.currentTarget ?? ev.target);
+function createAccount() {
+	os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
+		done: res => {
+			addAccounts(res.id, res.i);
+			switchAccountWithToken(res.i);
 		},
+	}, 'closed');
+}
 
-		addExistingAccount() {
-			os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
-				done: res => {
-					addAccount(res.id, res.i);
-					os.success();
-				},
-			}, 'closed');
-		},
+async function switchAccount(account: any) {
+	const fetchedAccounts: any[] = await getAccounts();
+	const token = fetchedAccounts.find(x => x.id === account.id).token;
+	switchAccountWithToken(token);
+}
 
-		createAccount() {
-			os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
-				done: res => {
-					addAccount(res.id, res.i);
-					this.switchAccountWithToken(res.i);
-				},
-			}, 'closed');
-		},
+function switchAccountWithToken(token: string) {
+	login(token);
+}
 
-		async switchAccount(account: any) {
-			const storedAccounts = await getAccounts();
-			const token = storedAccounts.find(x => x.id === account.id).token;
-			this.switchAccountWithToken(token);
-		},
-
-		switchAccountWithToken(token: string) {
-			login(token);
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.accounts,
+		icon: 'fas fa-users',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From 1f222e6cd19dcf2f4bb679e810090f11ce6fdc6a Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Tue, 3 May 2022 13:34:48 +0200
Subject: [PATCH 061/258] refactor(client): refactor settings/theme to use
 Composition API (#8595)

---
 packages/client/src/pages/settings/theme.vue | 151 ++++++++-----------
 1 file changed, 64 insertions(+), 87 deletions(-)

diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index a3ddc9a2ff..64b384bdcd 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -85,12 +85,11 @@
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue';
+<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';
@@ -101,100 +100,78 @@ 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 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;
 
-	emits: ['info'],
+watch(syncDeviceDarkMode, () => {
+	if (syncDeviceDarkMode.value) {
+		defaultStore.set('darkMode', isDeviceDarkmode());
+	}
+});
 
-	setup(props, { emit }) {
-		const INFO = {
-			title: i18n.ts.theme,
-			icon: 'fas fa-palette',
-				bg: 'var(--bg)',
-		};
+watch(wallpaper, () => {
+	if (wallpaper.value == null) {
+		localStorage.removeItem('wallpaper');
+	} else {
+		localStorage.setItem('wallpaper', wallpaper.value);
+	}
+	location.reload();
+});
 
-		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;
+onActivated(() => {
+	fetchThemes().then(() => {
+		installedThemes.value = getThemes();
+	});
+});
 
-		watch(syncDeviceDarkMode, () => {
-			if (syncDeviceDarkMode.value) {
-				defaultStore.set('darkMode', isDeviceDarkmode());
-			}
-		});
+fetchThemes().then(() => {
+	installedThemes.value = getThemes();
+});
 
-		watch(wallpaper, () => {
-			if (wallpaper.value == null) {
-				localStorage.removeItem('wallpaper');
-			} else {
-				localStorage.setItem('wallpaper', wallpaper.value);
-			}
-			location.reload();
-		});
+function setWallpaper(event) {
+	selectFile(event.currentTarget ?? event.target, null).then(file => {
+		wallpaper.value = file.url;
+	});
+}
 
-		onActivated(() => {
-			fetchThemes().then(() => {
-				installedThemes.value = getThemes();
-			});
-		});
-
-		fetchThemes().then(() => {
-			installedThemes.value = getThemes();
-		});
-
-		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>

From f8c66be130f3209e9c993fdde95ded6fecdfeeaf Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:10:34 +0200
Subject: [PATCH 062/258] refactor(client): refactor settings/security to use
 Composition API (#8592)

---
 .../client/src/pages/settings/security.vue    | 136 ++++++++----------
 1 file changed, 61 insertions(+), 75 deletions(-)

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,
-	},
+const pagination = {
+	endpoint: 'i/signin-history' as const,
+	limit: 5,
+};
+
+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: i18n.ts.newPassword,
+		type: 'password'
+	});
+	if (canceled2) 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: i18n.ts.retypedNotMatch
+		});
+		return;
+	}
 	
-	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,
-			},
-		}
-	},
+	os.apiWithDialog('i/change-password', {
+		currentPassword,
+		newPassword
+	});
+}
 
-	methods: {
-		async change() {
-			const { canceled: canceled1, result: currentPassword } = await os.inputText({
-				title: this.$ts.currentPassword,
-				type: 'password'
-			});
-			if (canceled1) return;
+function regenerateToken() {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.api('i/regenerate_token', {
+			password: password
+		});
+	});
+}
 
-			const { canceled: canceled2, result: newPassword } = await os.inputText({
-				title: this.$ts.newPassword,
-				type: 'password'
-			});
-			if (canceled2) return;
-
-			const { canceled: canceled3, result: newPassword2 } = await os.inputText({
-				title: this.$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
-			});
-		},
-
-		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>

From 4e1b5038fb9c9cfa8c7095c44b1e53e2ae5c19f0 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:10:52 +0200
Subject: [PATCH 063/258] refactor(client): refactor settings/plugin/install to
 use Composition API (#8591)

---
 .../src/pages/settings/plugin.install.vue     | 208 +++++++++---------
 1 file changed, 109 insertions(+), 99 deletions(-)

diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index 298f6bc1f0..5cf427d18f 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 { defineAsyncComponent, 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,121 @@ 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 (e) {
+		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;
+	}
+
+	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 :('
+		});
+		return;
+	}
+
+	const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
+		os.popup(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,
+				});
+
+				res(token);
+			}
+		}, 'closed');
+	});
+
+	installPlugin({
+		id: uuid(),
+		meta: {
+			name, version, author, description, permissions, config
 		},
+		token,
+		ast: serialize(ast)
+	});
 
-		async install() {
-			let ast;
-			try {
-				ast = parse(this.code);
-			} catch (e) {
-				os.alert({
-					type: 'error',
-					text: 'Syntax error :('
+	os.success();
+
+	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;
-			}
-			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 :('
-				});
-				return;
+				res(token);
 			}
+		}, 'closed');
+	});
 
-			const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
-				os.popup(defineAsyncComponent(() => 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,
-						});
+	nextTick(() => {
+		unisonReload();
+	});
+}
 
-						res(token);
-					}
-				}, 'closed');
-			});
-
-			this.installPlugin({
-				id: uuid(),
-				meta: {
-					name, version, author, description, permissions, config
-				},
-				token,
-				ast: serialize(ast)
-			});
-
-			os.success();
-
-			this.$nextTick(() => {
-				unisonReload();
-			});
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._plugin.install,
+		icon: 'fas fa-download',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From 81e5ff7dcea22d5309368d4f5361fa1a36df11cb Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:11:35 +0200
Subject: [PATCH 064/258] refactor(client): refactor settings/plugin to use
 Composition API (#8590)

---
 packages/client/src/pages/settings/plugin.vue | 111 ++++++++----------
 1 file changed, 49 insertions(+), 62 deletions(-)

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>
 

From 8489afa3d782904820830a79249a713e8c6027c3 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:12:08 +0200
Subject: [PATCH 065/258] Refactor settings/other to use Composition API
 (#8589)

* refactor(client): refactor settings/other to use Composition API

* fix(client): fix 'show featured notes' checkbox
---
 packages/client/src/pages/settings/other.vue | 66 +++++++-------------
 1 file changed, 22 insertions(+), 44 deletions(-)

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>

From 60010bdb0f4764af5bf3e5644f3a0ae674996c07 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:12:40 +0200
Subject: [PATCH 066/258] Refactor settings/menu to use Composition API (#8586)

* refactor(client): refactor settings/menu to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/pages/settings/menu.vue | 131 ++++++++------------
 1 file changed, 55 insertions(+), 76 deletions(-)

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();
-		},
+watch(items, async () => {
+	await save();
+});
 
-		reset() {
-			this.$store.reset('menu');
-			this.items = this.$store.state.menu.join('\n');
-		},
+watch(menuDisplay, async () => {
+	await reloadAsk();
+});
 
-		async reloadAsk() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-				showCancelButton: true
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.menu,
+		icon: 'fas fa-list-ul',
+		bg: 'var(--bg)',
+	}
 });
 </script>

From 247a9deb02becdc940bc876f51847d71b3e08d92 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:13:51 +0200
Subject: [PATCH 067/258] Refactor settings/general to use Composition API
 (#8578)

* refactor(client): refactor settings/general to use Composition API

* fix(client): turn watcher callback asynchronous
---
 .../client/src/pages/settings/general.vue     | 263 ++++++++----------
 1 file changed, 114 insertions(+), 149 deletions(-)

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();
-		},
+watch(lang, () => {
+	localStorage.setItem('lang', lang.value as string);
+	localStorage.removeItem('locale');
+});
 
-		fontSize() {
-			if (this.fontSize == null) {
-				localStorage.removeItem('fontSize');
-			} else {
-				localStorage.setItem('fontSize', this.fontSize);
-			}
-			this.reloadAsk();
-		},
+watch(fontSize, () => {
+	if (fontSize.value == null) {
+		localStorage.removeItem('fontSize');
+	} else {
+		localStorage.setItem('fontSize', fontSize.value);
+	}
+});
 
-		useSystemFont() {
-			if (this.useSystemFont) {
-				localStorage.setItem('useSystemFont', 't');
-			} else {
-				localStorage.removeItem('useSystemFont');
-			}
-			this.reloadAsk();
-		},
+watch(useSystemFont, () => {
+	if (useSystemFont.value) {
+		localStorage.setItem('useSystemFont', 't');
+	} else {
+		localStorage.removeItem('useSystemFont');
+	}
+});
 
-		enableInfiniteScroll() {
-			this.reloadAsk();
-		},
+watch([
+	lang,
+	fontSize,
+	useSystemFont,
+	enableInfiniteScroll,
+	squareAvatars,
+	aiChanMode,
+	showGapBetweenNotesInTimeline,
+	instanceTicker,
+	overridedDeviceKind
+], async () => {
+	await reloadAsk();
+});
 
-		squareAvatars() {
-			this.reloadAsk();
-		},
-
-		aiChanMode() {
-			this.reloadAsk();
-		},
-
-		showGapBetweenNotesInTimeline() {
-			this.reloadAsk();
-		},
-
-		instanceTicker() {
-			this.reloadAsk();
-		},
-
-		overridedDeviceKind() {
-			this.reloadAsk();
-		},
-	},
-
-	methods: {
-		async reloadAsk() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.general,
+		icon: 'fas fa-cogs',
+		bg: 'var(--bg)'
 	}
 });
 </script>

From 7a51f0ac9459391c351e9bb367fc4251228c04d0 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:14:26 +0200
Subject: [PATCH 068/258] Refactor settings/email to use Composition API
 (#8576)

* refactor(client): refactor settings/email to use Composition API

* fix(client): switch to non-null assertion for settings values
---
 packages/client/src/pages/settings/email.vue | 125 ++++++++-----------
 1 file changed, 54 insertions(+), 71 deletions(-)

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 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)
-			});
-		};
-
-		watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
-			saveNotificationSettings();
+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,
 		});
+	});
+};
 
-		onMounted(() => {
-			watch(emailAddress, () => {
-				saveEmailAddress();
-			});
-		});
+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'));
 
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			emailAddress,
-			onChangeReceiveAnnouncementEmail,
-			emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_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)
+	});
+};
+
+watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
+	saveNotificationSettings();
+});
+
+onMounted(() => {
+	watch(emailAddress, () => {
+		saveEmailAddress();
+	});
+});
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.email,
+		icon: 'fas fa-envelope',
+		bg: 'var(--bg)',
+	}
 });
 </script>

From fc02f8fc93635e83273e8687c03356c185c20348 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:14:48 +0200
Subject: [PATCH 069/258] refactor(client): refactor settings/drive to use
 Composition API (#8573)

---
 packages/client/src/pages/settings/drive.vue | 135 ++++++++-----------
 1 file changed, 57 insertions(+), 78 deletions(-)

diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue
index c3bdf4f6c6..f235ace7ca 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 tinycolor from 'tinycolor2';
+<script lang="ts" setup>
+import { computed, defineExpose, ref } from 'vue';
+import * as 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'));
+
+os.api('drive').then(info => {
+	capacity.value = info.capacity;
+	usage.value = info.usage;
+	fetching.value = false;
+});
+
+if (defaultStore.state.uploadFolder) {
+	os.api('drive/folders/show', {
+		folderId: defaultStore.state.uploadFolder
+	}).then(response => {
+		uploadFolder.value = response;
+	});
+}
+
+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;
 		}
-	},
+	});
+}
 
-	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'),
-	},
-
-	async created() {
-		os.api('drive').then(info => {
-			this.capacity = info.capacity;
-			this.usage = info.usage;
-			this.fetching = false;
-			this.$nextTick(() => {
-				this.renderChart();
-			});
-		});
-
-		if (this.$store.state.uploadFolder) {
-			this.uploadFolder = await os.api('drive/folders/show', {
-				folderId: this.$store.state.uploadFolder
-			});
-		}
-	},
-
-	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>

From 80355fb08ee115f7d458bcdb6bb265597032cc87 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:15:06 +0200
Subject: [PATCH 070/258] Refactor delete-account to use Composition API
 (#8572)

* refactor(client): refactor delete-account to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../src/pages/settings/delete-account.vue     | 78 ++++++++-----------
 1 file changed, 33 insertions(+), 45 deletions(-)

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,
-	},
+async function deleteAccount() {
+	{
+		const { canceled } = await os.confirm({
+			type: 'warning',
+			text: i18n.ts.deleteAccountConfirm,
+		});
+		if (canceled) return;
+	}
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts._accountDelete.accountDelete,
-				icon: 'fas fa-exclamation-triangle',
-				bg: 'var(--bg)',
-			},
-		}
-	},
+	const { canceled, result: password } = await os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	});
+	if (canceled) return;
 
-	methods: {
-		async deleteAccount() {
-			{
-				const { canceled } = await os.confirm({
-					type: 'warning',
-					text: this.$ts.deleteAccountConfirm,
-				});
-				if (canceled) return;
-			}
+	await os.apiWithDialog('i/delete-account', {
+		password: password
+	});
 
-			const { canceled, result: password } = await os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			});
-			if (canceled) return;
+	await os.alert({
+		title: i18n.ts._accountDelete.started,
+	});
 
-			await os.apiWithDialog('i/delete-account', {
-				password: password
-			});
+	await signout();
+}
 
-			await os.alert({
-				title: this.$ts._accountDelete.started,
-			});
-
-			signout();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._accountDelete.accountDelete,
+		icon: 'fas fa-exclamation-triangle',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From 6226e8d902b009e193c8d29df385022632799135 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:15:24 +0200
Subject: [PATCH 071/258] refactor(client): refactor settings/apps to use
 Composition API (#8570)

---
 packages/client/src/pages/settings/apps.vue | 58 +++++++++------------
 1 file changed, 25 insertions(+), 33 deletions(-)

diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue
index 9c0fa8a54d..f3b251d9b2 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>

From c72f5e27734e13fe5d14499a1d728f0b641a4d38 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:15:43 +0200
Subject: [PATCH 072/258] refactor(client): refactor settings/api to use
 Composition API (#8569)

---
 packages/client/src/pages/settings/api.vue | 67 +++++++++-------------
 1 file changed, 28 insertions(+), 39 deletions(-)

diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue
index 23e34e3343..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 { defineAsyncComponent, 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(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,
-					});
-
-					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>

From 7154ad5a73aa360c21add2979b4929b8545400c1 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 03:16:14 +0200
Subject: [PATCH 073/258] Refactor account-info to use Composition API (#8568)

* refactor(client): refactor account-info  to use Composition API

* fix(client): use mounted hook for initial data

* fix(client): switch to non-null assertion for account check
---
 .../src/pages/settings/account-info.vue       | 103 ++++++++----------
 1 file changed, 45 insertions(+), 58 deletions(-)

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
-		}
-	},
+onMounted(() => {
+	os.api('users/stats', {
+		userId: $i!.id
+	}).then(response => {
+		stats.value = response;
+	});
+});
 
-	mounted() {
-		os.api('users/stats', {
-			userId: this.$i.id
-		}).then(stats => {
-			this.stats = stats;
-		});
-	},
-
-	methods: {
-		number,
-		bytes,
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.accountInfo,
+		icon: 'fas fa-info-circle'
 	}
 });
 </script>

From 9230334a319c93d1604576778ed39c9de9d510ce Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Wed, 4 May 2022 05:25:19 +0200
Subject: [PATCH 074/258] Refactor settings/notifications to use Composition
 API (#8587)

* refactor(client): refactor settings/notifications to use Composition API

* fix(client): use async/await for API methods
---
 .../src/pages/settings/notifications.vue      | 88 ++++++++-----------
 1 file changed, 38 insertions(+), 50 deletions(-)

diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 334216ff33..6fe2ac55a4 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 { defineAsyncComponent, defineComponent } from 'vue';
+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)',
-			},
+async function readAllMessagingMessages() {
+	await os.api('i/read-all-messaging-messages');
+}
+
+async function readAllNotifications() {
+	await 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');
+}
 
-	methods: {
-		readAllUnreadNotes() {
-			os.api('i/read-all-unread-notes');
-		},
-
-		readAllMessagingMessages() {
-			os.api('i/read-all-messaging-messages');
-		},
-
-		readAllNotifications() {
-			os.api('notifications/mark-all-as-read');
-		},
-
-		configure() {
-			const includingTypes = notificationTypes.filter(x => !this.$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 => {
-						this.$i.mutingNotificationTypes = i.mutingNotificationTypes;
-					});
-				}
-			}, 'closed');
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.notifications,
+		icon: 'fas fa-bell',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From d075ead80a7bbec84de2129b6579b9410e0b1154 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 11:21:38 +0200
Subject: [PATCH 075/258] fix(client): fix duplicate token request dialog in
 plugin install (#8612)

---
 .../src/pages/settings/plugin.install.vue     | 42 +++++--------------
 1 file changed, 11 insertions(+), 31 deletions(-)

diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index 5cf427d18f..6ece531462 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -78,37 +78,6 @@ async function install() {
 		return;
 	}
 
-	const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
-		os.popup(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,
-				});
-
-				res(token);
-			}
-		}, 'closed');
-	});
-
-	installPlugin({
-		id: uuid(),
-		meta: {
-			name, version, author, description, permissions, config
-		},
-		token,
-		ast: serialize(ast)
-	});
-
-	os.success();
-
 	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,
@@ -128,6 +97,17 @@ async function install() {
 		}, 'closed');
 	});
 
+	installPlugin({
+		id: uuid(),
+		meta: {
+			name, version, author, description, permissions, config
+		},
+		token,
+		ast: serialize(ast)
+	});
+
+	os.success();
+
 	nextTick(() => {
 		unisonReload();
 	});

From a36f54dec2bd9033f7bc0992a44af88740f4a1dc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 5 May 2022 18:33:24 +0900
Subject: [PATCH 076/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a696bc5ceb..1d82a0cbb3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -62,6 +62,19 @@ Be willing to comment on the good points and not just the things you want fixed
 	- Are there any omissions or gaps?
 	- Does it check for anomalies?
 
+## Merge
+For now, basically only @syuilo has the authority to merge PRs into develop because he is most familiar with the codebase.
+However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
+
+## Release
+For now, basically only @syuilo has the authority to release Misskey.
+However, in case of emergency, a release can be made at the discretion of a contributor.
+
+### Release Instructions
+1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
+2. follow the `master` branch to the `develop` branch.
+3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+
 ## Localization (l10n)
 Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
 You can improve our translations with your Crowdin account.

From 56436b99bb9dbff1e8bef8045bdfe52129388b8c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 5 May 2022 18:36:30 +0900
Subject: [PATCH 077/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1d82a0cbb3..efdccc7848 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -74,6 +74,8 @@ However, in case of emergency, a release can be made at the discretion of a cont
 1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
 2. follow the `master` branch to the `develop` branch.
 3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+ - The target branch must be `master`
+ - The tag name must be the version
 
 ## Localization (l10n)
 Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.

From dd8cb7846fc7f8295a3780bad4b05f9d46dcf3a9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 5 May 2022 18:38:30 +0900
Subject: [PATCH 078/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index efdccc7848..cafca625d4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -74,8 +74,8 @@ However, in case of emergency, a release can be made at the discretion of a cont
 1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
 2. follow the `master` branch to the `develop` branch.
 3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
- - The target branch must be `master`
- - The tag name must be the version
+  - The target branch must be `master`
+  - The tag name must be the version
 
 ## Localization (l10n)
 Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.

From 7362c2da76ce8f03fb0a2eabf301db3966b52dd0 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 13:45:50 +0200
Subject: [PATCH 079/258] Fix lint issues in Drive components (#8613)

* fix(client): Fix lint issues in Drive components

* fix(client): only use !=/== for null comparisons

* Update drive.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 .../src/components/drive-file-thumbnail.vue   |  2 +-
 .../src/components/drive-select-dialog.vue    |  4 +-
 .../client/src/components/drive-window.vue    |  2 +-
 packages/client/src/components/drive.file.vue | 22 ++---
 .../client/src/components/drive.folder.vue    | 14 ++--
 .../src/components/drive.nav-folder.vue       | 42 +++++-----
 packages/client/src/components/drive.vue      | 82 +++++++++----------
 7 files changed, 84 insertions(+), 84 deletions(-)

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 e2f78a84ec..aaf7ca3ca3 100644
--- a/packages/client/src/components/drive.file.vue
+++ b/packages/client/src/components/drive.file.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;
 
@@ -137,7 +137,7 @@ function describe() {
 		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 e7003a8074..d530f8beff 100644
--- a/packages/client/src/components/drive.folder.vue
+++ b/packages/client/src/components/drive.folder.vue
@@ -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', {
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 2ec885b00c..42ec3a5995 100644
--- a/packages/client/src/components/drive.vue
+++ b/packages/client/src/components/drive.vue
@@ -110,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>>();
@@ -153,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);
@@ -170,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);
@@ -181,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;
@@ -211,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,
@@ -238,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,
@@ -357,16 +357,16 @@ function onChangeFileInput() {
 }
 
 function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) {
-	uploadFile(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).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);
 		}
@@ -382,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);
 		}
@@ -404,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;
 	}
 
@@ -430,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;
@@ -447,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;
@@ -464,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) {
@@ -512,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();
 		}
@@ -524,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();
 		}
@@ -551,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 {

From 1168e25721af9a2430e8ec8147487f247dc7ec99 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 5 May 2022 20:46:46 +0900
Subject: [PATCH 080/258] fix (client): fix mention icon height (#8615)

---
 packages/client/src/components/mention.vue | 40 ++++++++++++----------
 1 file changed, 21 insertions(+), 19 deletions(-)

diff --git a/packages/client/src/components/mention.vue b/packages/client/src/components/mention.vue
index 39a333c5e8..70c2f49afa 100644
--- a/packages/client/src/components/mention.vue
+++ b/packages/client/src/components/mention.vue
@@ -1,21 +1,21 @@
 <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 { defineComponent, useCssModule } from 'vue';
 import tinycolor from 'tinycolor2';
 import { toUnicode } from 'punycode';
 import { host as localHost } from '@/config';
@@ -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>

From bd620a8c7782d485d4385cc95af744edef5986c9 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 15:41:10 +0200
Subject: [PATCH 081/258] refactor(client): refactor 2FA settings to
 Composition API (#8599)

---
 packages/client/src/pages/settings/2fa.vue | 325 ++++++++++-----------
 1 file changed, 157 insertions(+), 168 deletions(-)

diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 10599d99ff..9ebf5101cd 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,19 @@
 					</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"></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 +72,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;
+		});
+	});
+}
+
+function submit() {
+	os.api('i/2fa/done', {
+		token: token.value
+	}).then(() => {
+		os.success();
+		$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();
+	})
+}
+
+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();
+		});
+	});
+}
+
+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;
+		});
+	});
+}
 
-		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;
-				});
-			});
-		},
-
-		submit() {
-			os.api('i/2fa/done', {
-				token: this.token
-			}).then(() => {
-				os.success();
-				this.$i.twoFactorEnabled = true;
-			}).catch(e => {
-				os.alert({
-					type: 'error',
-					text: e
-				});
-			});
-		},
-
-		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();
-			})
-		},
-
-		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();
-				});
-			});
-		},
-
-		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>

From 31c73fdfa2984c386b39f7e9ef70a52c1735efe3 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 5 May 2022 15:45:22 +0200
Subject: [PATCH 082/258] chore: synchronize code and database schema (#8577)

* chore: remove default null

null is always the default value if a table column is nullable, and typeorm's
@Column only accepts strings for default.

* chore: synchronize code with database schema

* chore: sync generated migrations with code
---
 .../migration/1651224615271-foreign-key.js    | 89 +++++++++++++++++++
 .../src/models/entities/access-token.ts       |  6 --
 .../src/models/entities/auth-session.ts       |  2 +-
 packages/backend/src/models/entities/clip.ts  |  2 +-
 .../backend/src/models/entities/drive-file.ts |  1 -
 packages/backend/src/models/entities/emoji.ts |  1 +
 .../backend/src/models/entities/instance.ts   | 20 ++---
 packages/backend/src/models/entities/meta.ts  |  4 +-
 .../backend/src/models/entities/muting.ts     |  1 -
 packages/backend/src/models/entities/note.ts  |  6 +-
 .../src/models/entities/user-profile.ts       |  1 +
 packages/backend/src/models/entities/user.ts  |  2 +-
 12 files changed, 108 insertions(+), 27 deletions(-)
 create mode 100644 packages/backend/migration/1651224615271-foreign-key.js

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/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;
 

From a6c138600f46aacdb8d9aecb17c6821ff1ce784a Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 15:51:05 +0200
Subject: [PATCH 083/258] Refactor settings/sounds to use Composition API
 (#8594)

* refactor(client): refactor settings/sounds to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* chore(client): remove old sound reference

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/pages/settings/sounds.vue | 166 ++++++++----------
 1 file changed, 76 insertions(+), 90 deletions(-)

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,
 		},
-		volumeIcon() {
-			return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up';
-		}
-	},
-
-	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');
-	},
-
-	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;
-
-			const v = {
-				type: result.type,
-				volume: result.volume,
-			};
-
-			ColdDeviceStorage.set('sound_' + type, v);
-			this.sounds[type] = v;
+		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
 		},
-
-		reset() {
-			for (const sound of Object.keys(this.sounds)) {
-				const v = ColdDeviceStorage.default['sound_' + sound];
-				ColdDeviceStorage.set('sound_' + sound, v);
-				this.sounds[sound] = v;
+		listen: {
+			type: 'button',
+			content: i18n.ts.listen,
+			action: (_, values) => {
+				playFile(values.type, values.volume);
 			}
 		}
+	});
+	if (canceled) return;
+
+	const v = {
+		type: result.type,
+		volume: result.volume,
+	};
+
+	ColdDeviceStorage.set('sound_' + type, v);
+	sounds.value[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;
+	}
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.sounds,
+		icon: 'fas fa-music',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From e5a8773bfeaf06a0085fbf8c44524637213acb94 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 15:51:29 +0200
Subject: [PATCH 084/258] refactor(client): refactor settings/deck to use
 Composition API (#8598)

---
 packages/client/src/pages/settings/deck.vue | 106 ++++++++------------
 1 file changed, 44 insertions(+), 62 deletions(-)

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)',
-			},
-		}
-	},
+	unisonReload();
+});
 
-	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'),
-	},
+async function setProfile() {
+	const { canceled, result: name } = await os.inputText({
+		title: i18n.ts._deck.profile,
+		allowEmpty: false
+	});
+	if (canceled) return;
+	
+	profile.value = name;
+	unisonReload();
+}
 
-	watch: {
-		async navWindow() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			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>

From f3628946af19ac84ce500dccdb04e58b36f85cea Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 15:51:58 +0200
Subject: [PATCH 085/258] refactor(client): refactor settings/word-mute to use
 Composition API (#8597)

---
 .../client/src/pages/settings/word-mute.vue   | 192 ++++++++----------
 1 file changed, 84 insertions(+), 108 deletions(-)

diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index c11707b6cf..97a15da5b5 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,
-	},
+const render = (mutedWords) => mutedWords.map(x => {
+	if (Array.isArray(x)) {
+		return x.join(' ');
+	} else {
+		return x;
+	}
+}).join('\n');
 
-	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 tab = ref('soft');
+const softMutedWords = ref(render(defaultStore.state.mutedWords));
+const hardMutedWords = ref(render($i!.mutedWords));
+const hardWordMutedNotesCount = ref(null);
+const changed = ref(false);
 
-	watch: {
-		softMutedWords: {
-			handler() {
-				this.changed = true;
-			},
-			deep: true
-		},
-		hardMutedWords: {
-			handler() {
-				this.changed = true;
-			},
-			deep: true
-		},
-	},
+os.api('i/get-word-muted-notes-count', {}).then(response => {
+	hardWordMutedNotesCount.value = response?.count;
+});
 
-	async created() {
-		const render = (mutedWords) => mutedWords.map(x => {
-			if (Array.isArray(x)) {
-				return x.join(' ');
-			} else {
-				return x;
-			}
-		}).join('\n');
+watch(softMutedWords, () => {
+	changed.value = true;
+});
 
-		this.softMutedWords = render(this.$store.state.mutedWords);
-		this.hardMutedWords = render(this.$i.mutedWords);
+watch(hardMutedWords, () => {
+	changed.value = true;
+});
 
-		this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
-	},
+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 != '');
 
-	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 != '');
-
-				// 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;
 				}
-
-				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;
+			} else {
+				lines[i] = line.split(' ');
 			}
+		}
 
-			this.$store.set('mutedWords', softMutes);
-			await os.api('i/update', {
-				mutedWords: hardMutes,
-			});
+		return lines;
+	};
 
-			this.changed = false;
-		},
+	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;
+	}
 
-		number
+	defaultStore.set('mutedWords', softMutes);
+	await os.api('i/update', {
+		mutedWords: hardMutes,
+	});
+
+	changed.value = false;
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.wordMute,
+		icon: 'fas fa-comment-slash',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From 3ea351d8a2f50f68dc695317062ba7f471ad7542 Mon Sep 17 00:00:00 2001
From: futchitwo <74236683+futchitwo@users.noreply.github.com>
Date: Thu, 5 May 2022 22:52:33 +0900
Subject: [PATCH 086/258] Enhance(MFM): Allow speed changes in all animated
 MFMs (#8551)

* MFM: Allow speed changes in all animated MFMs

* Feature(MFM): Add speed property to cheat sheet

* Use template literal

Oops!

* Remove unnecessary template string

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/components/mfm.ts         | 12 ++++++++----
 packages/client/src/pages/mfm-cheat-sheet.vue | 16 ++++++++--------
 2 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 37076652fd..6ac410762d 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': {
@@ -168,7 +171,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/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index 83ae5741c3..aa35ec2158 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -325,20 +325,20 @@ 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 🍮]`,
 		}

From ad860905c6043c4dfabed8b2c43029cb215a1741 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Thu, 5 May 2022 15:53:08 +0200
Subject: [PATCH 087/258] refactor(client): refactor settings/theme/manage to
 use Composition API (#8596)

---
 .../src/pages/settings/theme.manage.vue       | 95 ++++++++-----------
 1 file changed, 38 insertions(+), 57 deletions(-)

diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index 2eb16bb701..7da439f9c0 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -1,32 +1,32 @@
 <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>
+			<template #label>{{ i18n.ts.author }}</template>
 		</FormInput>
 		<FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock">
-			<template #label>{{ $ts._theme.description }}</template>
+			<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>
+			<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';
+<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';
@@ -35,61 +35,42 @@ import FormButton from '@/components/ui/button.vue';
 import { Theme, builtinThemes } 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 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(() => builtinThemes.concat(installedThemes.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);
+});
 
-		selectedThemeCode() {
-			if (this.selectedTheme == null) return null;
-			return JSON5.stringify(this.selectedTheme, null, '\t');
-		},
-	},
+const selectedThemeCode = computed(() => {
+	if (selectedTheme.value == null) return null;
+	return JSON5.stringify(selectedTheme.value, null, '\t');
+});
 
-	methods: {
-		copyThemeCode() {
-			copyToClipboard(this.selectedThemeCode);
-			os.success();
-		},
+function copyThemeCode() {
+	copyToClipboard(selectedThemeCode.value);
+	os.success();
+}
 
-		uninstall() {
-			removeTheme(this.selectedTheme);
-			this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId);
-			this.selectedThemeId = null;
-			os.success();
-		},
+function uninstall() {
+	removeTheme(selectedTheme.value as Theme);
+	installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value);
+	selectedThemeId.value = null;
+	os.success();
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._theme.manage,
+		icon: 'fas fa-folder-open',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From a975a0971cb0aa3b684204f910fba8b714c0f5fb Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sat, 7 May 2022 07:19:15 +0200
Subject: [PATCH 088/258] fix(client): fix lint issues in scripts (#8621)

---
 packages/client/src/scripts/2fa.ts            |  8 ++---
 packages/client/src/scripts/autocomplete.ts   | 22 ++++++------
 packages/client/src/scripts/contains.ts       |  2 +-
 .../extract-avg-color-from-blurhash.ts        |  2 +-
 .../client/src/scripts/get-account-from-id.ts |  2 +-
 packages/client/src/scripts/get-md5.ts        | 10 ------
 packages/client/src/scripts/get-note-menu.ts  | 14 ++++----
 .../client/src/scripts/get-note-summary.ts    |  2 +-
 packages/client/src/scripts/get-user-menu.ts  |  6 ++--
 packages/client/src/scripts/hotkey.ts         | 24 ++++++-------
 packages/client/src/scripts/hpml/evaluator.ts |  8 ++---
 packages/client/src/scripts/idb-proxy.ts      |  4 +--
 packages/client/src/scripts/lookup-user.ts    |  6 ++--
 packages/client/src/scripts/select-file.ts    | 10 +++---
 packages/client/src/scripts/upload.ts         | 34 +++++++++----------
 packages/client/src/scripts/url.ts            |  2 +-
 .../client/src/scripts/use-note-capture.ts    |  4 +--
 17 files changed, 75 insertions(+), 85 deletions(-)
 delete mode 100644 packages/client/src/scripts/get-md5.ts

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 bf60e5805a..8d9bdee8f5 100644
--- a/packages/client/src/scripts/autocomplete.ts
+++ b/packages/client/src/scripts/autocomplete.ts
@@ -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;
@@ -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/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/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 a50001b24e..aeb09ef97a 100644
--- a/packages/client/src/scripts/get-note-menu.ts
+++ b/packages/client/src/scripts/get-note-menu.ts
@@ -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,7 +246,7 @@ export function getNoteMenu(props: {
 			}]
 			: []
 		),*/
-		...(appearNote.userId != $i.id ? [
+		...(appearNote.userId !== $i.id ? [
 			null,
 			{
 				icon: 'fas fa-exclamation-circle',
@@ -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 b9e4066e42..1d2b761117 100644
--- a/packages/client/src/scripts/get-user-menu.ts
+++ b/packages/client/src/scripts/get-user-menu.ts
@@ -169,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,
@@ -178,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/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..0469a31cbb 100644
--- a/packages/client/src/scripts/hpml/evaluator.ts
+++ b/packages/client/src/scripts/hpml/evaluator.ts
@@ -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/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/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/select-file.ts b/packages/client/src/scripts/select-file.ts
index 49a46f0bb2..461d613b42 100644
--- a/packages/client/src/scripts/select-file.ts
+++ b/packages/client/src/scripts/select-file.ts
@@ -19,10 +19,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
 
 				Promise.all(promises).then(driveFiles => {
 					res(multiple ? driveFiles : driveFiles[0]);
-				}).catch(e => {
+				}).catch(err => {
 					os.alert({
 						type: 'error',
-						text: e
+						text: err
 					});
 				});
 
@@ -54,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/upload.ts b/packages/client/src/scripts/upload.ts
index 7e4f793b44..2f7b30b58d 100644
--- a/packages/client/src/scripts/upload.ts
+++ b/packages/client/src/scripts/upload.ts
@@ -33,13 +33,13 @@ export function uploadFile(
 	name?: string,
 	keepOriginal: boolean = defaultStore.state.keepOriginalUploading
 ): Promise<Misskey.entities.DriveFile> {
-	if (folder && typeof folder == 'object') folder = folder.id;
+	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 (e) => {
+		reader.onload = async (ev) => {
 			const ctx = reactive<Uploading>({
 				id: id,
 				name: name || file.name || 'untitled',
@@ -64,24 +64,24 @@ export function uploadFile(
 				try {
 					resizedImage = await readAndCompressImage(file, config);
 					ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].mimeType]}` : ctx.name;
-				} catch (e) {
-					console.error('Failed to resize image', e);
+				} catch (err) {
+					console.error('Failed to resize image', err);
 				}
 			}
 
-			const data = new FormData();
-			data.append('i', $i.token);
-			data.append('force', 'true');
-			data.append('file', resizedImage || file);
-			data.append('name', ctx.name);
-			if (folder) data.append('folderId', folder);
+			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);
+					uploads.value = uploads.value.filter(x => x.id !== id);
 
 					alert({
 						type: 'error',
@@ -97,17 +97,17 @@ export function uploadFile(
 
 				resolve(driveFile);
 
-				uploads.value = uploads.value.filter(x => x.id != id);
+				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.upload.onprogress = ev => {
+				if (ev.lengthComputable) {
+					ctx.progressMax = ev.total;
+					ctx.progressValue = ev.loaded;
 				}
 			};
 
-			xhr.send(data);
+			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;
 

From 7bd45e57290ad1fad2343041e03fde0c70a6b752 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sat, 7 May 2022 10:00:05 +0200
Subject: [PATCH 089/258] Fix lint issues in emoji picker components (#8620)

* fix(client): fix lint issues in emoji picker components

* fix(client): switch argument naming for emoji picker section event
---
 .../client/src/components/emoji-picker-window.vue    |  4 ++--
 .../client/src/components/emoji-picker.section.vue   |  2 +-
 packages/client/src/components/emoji-picker.vue      | 12 ++++++------
 3 files changed, 9 insertions(+), 9 deletions(-)

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 ae74f04c02..522f636474 100644
--- a/packages/client/src/components/emoji-picker.vue
+++ b/packages/client/src/components/emoji-picker.vue
@@ -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;

From a29ff7b1fa45e01bc7c097e8d770a4c4eedf2a8f Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <andreas.nedbal@in2code.de>
Date: Sat, 7 May 2022 10:01:01 +0200
Subject: [PATCH 090/258] Fix lint issues in post form component (#8619)

* fix(client): fix lint issues in post form

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/components/post-form.vue | 97 ++++++++++----------
 1 file changed, 48 insertions(+), 49 deletions(-)

diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 6d79736003..488c55231f 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -228,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) : ''} `;
 }
 
@@ -239,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} `;
 	}
@@ -303,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;
 			}
@@ -316,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);
 			});
@@ -357,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) {
@@ -427,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) : '';
@@ -453,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',
@@ -472,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,
@@ -527,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,
@@ -549,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)));
 		}
 	}
 
@@ -572,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))));
 			}
@@ -662,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;
 				}

From 9fc5e8688f99545086e9226cf3179486efd9255e Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Thu, 12 May 2022 19:31:26 +0200
Subject: [PATCH 091/258] fix(client): add setup attribute to notification page
 (#8648)

---
 packages/client/src/pages/settings/notifications.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 6fe2ac55a4..b8fff95a8d 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -9,7 +9,7 @@
 </div>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
 import { defineAsyncComponent, defineExpose } from 'vue';
 import FormButton from '@/components/ui/button.vue';
 import FormLink from '@/components/form/link.vue';

From c5699fae53824aee1d0ab66a0582efa1bc20c415 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 13 May 2022 10:14:52 +0900
Subject: [PATCH 092/258] New Crowdin updates (#8488)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Kabyle)

* New translations ja-JP.yml (Kannada)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Arabic)
---
 locales/ar-SA.yml   |   26 +-
 locales/bn-BD.yml   |    3 +
 locales/ca-ES.yml   |  158 +++++
 locales/cs-CZ.yml   |   30 +
 locales/de-DE.yml   |    9 +-
 locales/en-US.yml   |    7 +-
 locales/es-ES.yml   |   77 +++
 locales/fr-FR.yml   |    3 +
 locales/id-ID.yml   |    7 +
 locales/it-IT.yml   |    3 +
 locales/ja-KS.yml   |    3 +
 locales/kab-KAB.yml |    2 +
 locales/kn-IN.yml   |    2 +
 locales/ko-KR.yml   |   24 +-
 locales/nl-NL.yml   |    3 +
 locales/pl-PL.yml   |    3 +
 locales/pt-PT.yml   |   77 +++
 locales/ro-RO.yml   |   77 +++
 locales/ru-RU.yml   |    3 +
 locales/sk-SK.yml   |    4 +
 locales/uk-UA.yml   |   55 ++
 locales/vi-VN.yml   | 1610 +++++++++++++++++++++++++++++++++++++++++++
 locales/zh-CN.yml   |   41 +-
 locales/zh-TW.yml   |  171 ++++-
 24 files changed, 2358 insertions(+), 40 deletions(-)

diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index f3f8b45777..efad7c954b 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -141,7 +141,7 @@ flagAsBotDescription: "فعّل هذا الخيار إذا كان هذا الح
 flagAsCat: "علّم هذا الحساب كحساب قط"
 flagAsCatDescription: "فعّل هذا الخيار لوضع علامة على الحساب لتوضيح أنه حساب قط."
 flagShowTimelineReplies: "أظهر التعليقات في الخيط الزمني"
-flagShowTimelineRepliesDescription: "يظهر الردود في الخط الزمني"
+flagShowTimelineRepliesDescription: "يظهر الردود في الخيط الزمني"
 autoAcceptFollowed: "اقبل طلبات المتابعة تلقائيا من الحسابات المتابَعة"
 addAccount: "أضف حساباً"
 loginFailed: "فشل الولوج"
@@ -312,12 +312,12 @@ dayX: "{day}"
 monthX: "{month}"
 yearX: "{year}"
 pages: "الصفحات"
-integration: "دمج"
+integration: "التكامل"
 connectService: "اتصل"
 disconnectService: "اقطع الاتصال"
 enableLocalTimeline: "تفعيل الخيط المحلي"
 enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
-disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخطوط الزمنية حتى وإن لم تفعّل."
+disablingTimelinesInfo: "سيتمكن المديرون والمشرفون من الوصول إلى كل الخيوط الزمنية حتى وإن لم تفعّل."
 registration: "إنشاء حساب"
 enableRegistration: "تفعيل إنشاء الحسابات الجديدة"
 invite: "دعوة"
@@ -532,6 +532,7 @@ poll: "استطلاع رأي"
 useCw: "إخفاء المحتوى"
 enablePlayer: "افتح مشغل الفيديو"
 disablePlayer: "أغلق مشغل الفيديو"
+expandTweet: "وسّع التغريدة"
 themeEditor: "مصمم القوالب"
 description: "الوصف"
 describeFile: "أضف تعليقًا توضيحيًا"
@@ -635,6 +636,7 @@ yes: "نعم"
 no: "لا"
 driveFilesCount: "عدد الملفات في قرص التخزين"
 driveUsage: "المستغل من قرص التخزين"
+noCrawle: "ارفض فهرسة زاحف الويب"
 noCrawleDescription: "يطلب من محركات البحث ألّا يُفهرسوا ملفك الشخصي وملاحظات وصفحاتك وما شابه."
 alwaysMarkSensitive: "علّم افتراضيًا جميع ملاحظاتي كذات محتوى حساس"
 loadRawImages: "حمّل الصور الأصلية بدلًا من المصغرات"
@@ -878,9 +880,11 @@ _mfm:
   center: "وسط"
   centerDescription: "يمركز المحتوى في الوَسَط."
   quote: "اقتبس"
+  quoteDescription: "يعرض المحتوى كاقتباس"
   emoji: "إيموجي مخصص"
   emojiDescription: "إحاطة اسم الإيموجي بنقطتي تفسير سيستبدله بصورة الإيموجي."
   search: "البحث"
+  searchDescription: "يعرض نصًا في صندوق البحث"
   flip: "اقلب"
   flipDescription: "يقلب المحتوى عموديًا أو أفقيًا"
   jelly: "تأثير (هلام)"
@@ -1030,12 +1034,12 @@ _tutorial:
   step3_3: "املأ النموذج وانقر الزرّ الموجود في أعلى اليمين للإرسال."
   step3_4: "ليس لديك ما تقوله؟ إذا اكتب \"بدأتُ استخدم ميسكي\"."
   step4_1: "هل نشرت ملاحظتك الأولى؟"
-  step4_2: "مرحى! يمكنك الآن رؤية ملاحظتك في الخط الزمني."
-  step5_1: "والآن، لنجعل الخط الزمني أكثر حيوية وذلك بمتابعة بعض المستخدمين."
+  step4_2: "مرحى! يمكنك الآن رؤية ملاحظتك في الخيط الزمني."
+  step5_1: "والآن، لنجعل الخيط الزمني أكثر حيوية وذلك بمتابعة بعض المستخدمين."
   step5_2: "تعرض صفحة {features} الملاحظات المتداولة في هذا المثيل ويتيح لك {Explore} العثور على المستخدمين الرائدين. اعثر على الأشخاص الذين يثيرون إهتمامك وتابعهم!"
   step5_3: "لمتابعة مستخدمين ادخل ملفهم الشخصي بالنقر على صورتهم الشخصية ثم اضغط زر 'تابع'."
   step5_4: "إذا كان لدى المستخدم رمز قفل بجوار اسمه ، وجب عليك انتظاره ليقبل طلب المتابعة يدويًا."
-  step6_1: "الآن ستتمكن من رؤية ملاحظات المستخدمين المتابَعين في الخط الزمني."
+  step6_1: "الآن ستتمكن من رؤية ملاحظات المستخدمين المتابَعين في الخيط الزمني."
   step6_2: "يمكنك التفاعل بسرعة مع الملاحظات عن طريق إضافة \"تفاعل\"."
   step6_3: "لإضافة تفاعل لملاحظة ، انقر فوق علامة \"+\" أسفل للملاحظة واختر الإيموجي المطلوب."
   step7_1: "مبارك ! أنهيت الدورة التعليمية الأساسية لاستخدام ميسكي."
@@ -1201,8 +1205,13 @@ _charts:
 _instanceCharts:
   requests: "الطلبات"
   users: "تباين عدد المستخدمين"
+  usersTotal: "تباين عدد المستخدمين"
   notes: "تباين عدد الملاحظات"
+  notesTotal: "تباين عدد الملاحظات"
+  ff: "تباين عدد حسابات المتابَعة/المتابِعة"
+  ffTotal: "تباين عدد حسابات المتابَعة/المتابِعة"
   files: "تباين عدد الملفات"
+  filesTotal: "تباين عدد الملفات"
 _timelines:
   home: "الرئيسي"
   local: "المحلي"
@@ -1321,6 +1330,7 @@ _pages:
       random: "عشوائي"
       value: "القيم"
       fn: "دوال"
+      text: "إجراءات على النصوص"
       convert: "تحويل"
       list: "القوائم"
     blocks:
@@ -1501,6 +1511,10 @@ _notification:
     followRequestAccepted: "طلبات المتابعة المقبولة"
     groupInvited: "دعوات الفريق"
     app: "إشعارات التطبيقات المرتبطة"
+  _actions:
+    followBack: "تابعك بالمثل"
+    reply: "رد"
+    renote: "أعد النشر"
 _deck:
   alwaysShowMainColumn: "أظهر العمود الرئيسي دائمًا"
   columnAlign: "حاذِ الأعمدة"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index b2ba236fd5..bcecb06253 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -1621,6 +1621,9 @@ _notification:
     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
     groupInvited: "গ্রুপের আমন্ত্রনসমূহ"
     app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি"
+  _actions:
+    reply: "জবাব"
+    renote: "রিনোট"
 _deck:
   alwaysShowMainColumn: "সর্বদা মেইন কলাম দেখান"
   columnAlign: "কলাম সাজান"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 5f74cb6bef..577fbca2ae 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1,6 +1,8 @@
 ---
 _lang_: "Català"
 headlineMisskey: "Una xarxa connectada per notes"
+introMisskey: "Benvingut! Misskey és un servei de microblogging descentralitzat de codi obert.\nCrea \"notes\" per compartir els teus pensaments amb tots els que t'envolten. 📡\nAmb \"reaccions\", també pots expressar ràpidament els teus sentiments sobre les notes de tothom. 👍\nExplorem un món nou! 🚀"
+monthAndDay: "{day}/{month}"
 search: "Cercar"
 notifications: "Notificacions"
 username: "Nom d'usuari"
@@ -10,17 +12,173 @@ fetchingAsApObject: "Cercant en el Fediverse..."
 ok: "OK"
 gotIt: "Ho he entès!"
 cancel: "Cancel·lar"
+enterUsername: "Introdueix el teu nom d'usuari"
+renotedBy: "Resignat per {usuari}"
+noNotes: "Cap nota"
+noNotifications: "Cap notificació"
+instance: "Instàncies"
+settings: "Preferències"
+basicSettings: "Configuració bàsica"
+otherSettings: "Configuració avançada"
+openInWindow: "Obrir en una nova finestra"
+profile: "Perfil"
+timeline: "Línia de temps"
+noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
+login: "Iniciar sessió"
+loggingIn: "Identificant-se"
+logout: "Tancar la sessió"
+signup: "Registrar-se"
+uploading: "Pujant..."
+save: "Desar"
+users: "Usuaris"
+addUser: "Afegir un usuari"
+favorite: "Afegir a preferits"
+favorites: "Favorits"
+unfavorite: "Eliminar dels preferits"
+favorited: "Afegit als preferits."
+alreadyFavorited: "Ja s'ha afegit als preferits."
+cantFavorite: "No s'ha pogut afegir als preferits."
+pin: "Fixar al perfil"
+unpin: "Para de fixar del perfil"
+copyContent: "Copiar el contingut"
+copyLink: "Copiar l'enllaç"
+delete: "Eliminar"
+deleteAndEdit: "Esborrar i editar"
+deleteAndEditConfirm: "Estàs segur que vols suprimir aquesta nota i editar-la? Perdràs totes les reaccions, notes i respostes."
+addToList: "Afegir a una llista"
+sendMessage: "Enviar un missatge"
+copyUsername: "Copiar nom d'usuari"
+searchUser: "Cercar usuaris"
+reply: "Respondre"
+loadMore: "Carregar més"
+showMore: "Veure més"
+youGotNewFollower: "t'ha seguit"
+receiveFollowRequest: "Sol·licitud de seguiment rebuda"
+followRequestAccepted: "Sol·licitud de seguiment acceptada"
+mention: "Menció"
+mentions: "Mencions"
+directNotes: "Notes directes"
+importAndExport: "Importar / Exportar"
+import: "Importar"
+export: "Exportar"
+files: "Fitxers"
+download: "Baixar"
+driveFileDeleteConfirm: "Estàs segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer adjunt també se suprimiran."
+unfollowConfirm: "Estàs segur que vols deixar de seguir {name}?"
+exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà a la teva unitat un cop completat."
+importRequested: "Has sol·licitat una importació. Això pot trigar una estona."
+lists: "Llistes"
+noLists: "No tens cap llista"
+note: "Nota"
+notes: "Notes"
+following: "Seguint"
+followers: "Seguidors"
+followsYou: "Et segueix"
+createList: "Crear llista"
+manageLists: "Gestionar les llistes"
+error: "Error"
+somethingHappened: "S'ha produït un error"
+retry: "Torna-ho a intentar"
+pageLoadError: "S'ha produït un error en carregar la pàgina"
+pageLoadErrorDescription: "Això normalment es deu a errors de xarxa o a la memòria cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després d'esperar una estona."
+serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar."
+youShouldUpgradeClient: "Per veure aquesta pàgina, actualitzeu-la per actualitzar el vostre client."
+enterListName: "Introdueix un nom per a la llista"
+privacy: "Privadesa"
+makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació"
+defaultNoteVisibility: "Visibilitat per defecte"
+follow: "Seguint"
+followRequest: "Enviar la sol·licitud de seguiment"
+followRequests: "Sol·licituds de seguiment"
+unfollow: "Deixar de seguir"
+followRequestPending: "Sol·licituds de seguiment pendents"
+enterEmoji: "Introduir un emoji"
+renote: "Renotar"
+unrenote: "Anul·lar renota"
+renoted: "Renotat."
+cantRenote: "Aquesta publicació no pot ser renotada."
+cantReRenote: "Impossible renotar una renota."
+quote: "Citar"
+pinnedNote: "Nota fixada"
+pinned: "Fixar al perfil"
+you: "Tu"
+clickToShow: "Fes clic per mostrar"
+sensitive: "NSFW"
+add: "Afegir"
+reaction: "Reaccions"
+reactionSetting: "Reaccions a mostrar al selector de reaccions"
+reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
+rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
+attachCancel: "Eliminar el fitxer adjunt"
+markAsSensitive: "Marcar com a NSFW"
+instances: "Instàncies"
+remove: "Eliminar"
+nsfw: "NSFW"
+pinnedNotes: "Nota fixada"
+userList: "Llistes"
 smtpUser: "Nom d'usuari"
 smtpPass: "Contrasenya"
+user: "Usuaris"
 searchByGoogle: "Cercar"
+_email:
+  _follow:
+    title: "t'ha seguit"
 _mfm:
+  mention: "Menció"
+  quote: "Citar"
   search: "Cercar"
+_theme:
+  keys:
+    mention: "Menció"
+    renote: "Renotar"
 _sfx:
+  note: "Notes"
   notification: "Notificacions"
 _widgets:
   notifications: "Notificacions"
+  timeline: "Línia de temps"
+_cw:
+  show: "Carregar més"
+_visibility:
+  followers: "Seguidors"
 _profile:
   username: "Nom d'usuari"
+_exportOrImport:
+  followingList: "Seguint"
+  userLists: "Llistes"
+_pages:
+  script:
+    categories:
+      list: "Llistes"
+    blocks:
+      _join:
+        arg1: "Llistes"
+      _randomPick:
+        arg1: "Llistes"
+      _dailyRandomPick:
+        arg1: "Llistes"
+      _seedRandomPick:
+        arg2: "Llistes"
+      _pick:
+        arg1: "Llistes"
+      _listLen:
+        arg1: "Llistes"
+    types:
+      array: "Llistes"
+_notification:
+  youWereFollowed: "t'ha seguit"
+  _types:
+    follow: "Seguint"
+    mention: "Menció"
+    renote: "Renotar"
+    quote: "Citar"
+    reaction: "Reaccions"
+  _actions:
+    reply: "Respondre"
+    renote: "Renotar"
 _deck:
   _columns:
     notifications: "Notificacions"
+    tl: "Línia de temps"
+    list: "Llistes"
+    mentions: "Mencions"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 2f5e375372..4b20340df1 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -53,6 +53,8 @@ reply: "Odpovědět"
 loadMore: "Zobrazit více"
 showMore: "Zobrazit více"
 youGotNewFollower: "Máte nového následovníka"
+receiveFollowRequest: "Žádost o sledování přijata"
+followRequestAccepted: "Žádost o sledování přijata"
 mention: "Zmínění"
 mentions: "Zmínění"
 importAndExport: "Import a export"
@@ -60,7 +62,9 @@ import: "Importovat"
 export: "Exportovat"
 files: "Soubor(ů)"
 download: "Stáhnout"
+driveFileDeleteConfirm: "Opravdu chcete smazat soubor \"{name}\"? Poznámky, ke kterým je tento soubor připojen, budou také smazány."
 unfollowConfirm: "Jste si jisti že už nechcete sledovat {name}?"
+exportRequested: "Požádali jste o export. To může chvíli trvat. Přidáme ho na váš Disk až bude dokončen."
 importRequested: "Požádali jste o export. To může chvilku trvat."
 lists: "Seznamy"
 noLists: "Nemáte žádné seznamy"
@@ -75,13 +79,25 @@ error: "Chyba"
 somethingHappened: "Jejda. Něco se nepovedlo."
 retry: "Opakovat"
 pageLoadError: "Nepodařilo se načíst stránku"
+serverIsDead: "Server neodpovídá. Počkejte chvíli a zkuste to znovu."
+youShouldUpgradeClient: "Pro zobrazení této stránky obnovte stránku pro aktualizaci klienta."
 enterListName: "Jméno seznamu"
 privacy: "Soukromí"
+makeFollowManuallyApprove: "Žádosti o sledování vyžadují potvrzení"
+defaultNoteVisibility: "Výchozí viditelnost"
 follow: "Sledovaní"
+followRequest: "Odeslat žádost o sledování"
+followRequests: "Žádosti o sledování"
 unfollow: "Přestat sledovat"
+followRequestPending: "Čekající žádosti o sledování"
+enterEmoji: "Vložte emoji"
 renote: "Přeposlat"
+unrenote: "Zrušit přeposlání"
+renoted: "Přeposláno"
+cantRenote: "Tento příspěvek nelze přeposlat."
 cantReRenote: "Odpověď nemůže být odstraněna."
 quote: "Citovat"
+pinnedNote: "Připnutá poznámka"
 pinned: "Připnout"
 you: "Vy"
 clickToShow: "Klikněte pro zobrazení"
@@ -122,6 +138,8 @@ flagAsBot: "Tento účet je bot"
 flagAsBotDescription: "Pokud je tento účet kontrolován programem zaškrtněte tuto možnost. To označí tento účet jako bot pro ostatní vývojáře a zabrání tak nekonečným interakcím s ostatními boty a upraví Misskey systém aby se choval k tomuhle účtu jako bot."
 flagAsCat: "Tenhle účet je kočka"
 flagAsCatDescription: "Vyberte tuto možnost aby tento účet byl označen jako kočka."
+flagShowTimelineReplies: "Zobrazovat odpovědi na časové ose"
+flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazí odpovědi uživatelů na poznámky jiných uživatelů na vaší časové ose."
 autoAcceptFollowed: "Automaticky akceptovat následování od účtů které sledujete"
 addAccount: "Přidat účet"
 loginFailed: "Přihlášení se nezdařilo."
@@ -130,13 +148,16 @@ general: "Obecně"
 wallpaper: "Obrázek na pozadí"
 setWallpaper: "Nastavení obrázku na pozadí"
 removeWallpaper: "Odstranit pozadí"
+searchWith: "Hledat: {q}"
 youHaveNoLists: "Nemáte žádné seznamy"
+followConfirm: "Jste si jisti, že chcete sledovat {name}?"
 proxyAccount: "Proxy účet"
 proxyAccountDescription: "Proxy účet je účet, který za určitých podmínek sleduje uživatele na dálku vaším jménem. Například když uživatel zařadí vzdáleného uživatele do seznamu, pokud nikdo nesleduje uživatele na seznamu, aktivita nebude doručena instanci, takže místo toho bude uživatele sledovat účet proxy."
 host: "Hostitel"
 selectUser: "Vyberte uživatele"
 recipient: "Pro"
 annotation: "Komentáře"
+federation: "Federace"
 instances: "Instance"
 registeredAt: "Registrován"
 latestRequestSentAt: "Poslední požadavek poslán"
@@ -146,6 +167,7 @@ storageUsage: "Využití úložiště"
 charts: "Grafy"
 perHour: "za hodinu"
 perDay: "za den"
+stopActivityDelivery: "Přestat zasílat aktivitu"
 blockThisInstance: "Blokovat tuto instanci"
 operations: "Operace"
 software: "Software"
@@ -283,6 +305,8 @@ iconUrl: "Favicon URL"
 bannerUrl: "Baner URL"
 backgroundImageUrl: "Adresa URL obrázku pozadí"
 basicInfo: "Základní informace"
+pinnedUsers: "Připnutí uživatelé"
+pinnedNotes: "Připnutá poznámka"
 hcaptcha: "hCaptcha"
 enableHcaptcha: "Aktivovat hCaptchu"
 hcaptchaSecretKey: "Tajný Klíč (Secret Key)"
@@ -471,6 +495,7 @@ _widgets:
   notifications: "Oznámení"
   timeline: "Časová osa"
   activity: "Aktivita"
+  federation: "Federace"
   jobQueue: "Fronta úloh"
 _cw:
   show: "Zobrazit více"
@@ -485,6 +510,8 @@ _exportOrImport:
   muteList: "Ztlumit"
   blockingList: "Zablokovat"
   userLists: "Seznamy"
+_charts:
+  federation: "Federace"
 _timelines:
   home: "Domů"
 _pages:
@@ -517,6 +544,9 @@ _notification:
     renote: "Přeposlat"
     quote: "Citovat"
     reaction: "Reakce"
+  _actions:
+    reply: "Odpovědět"
+    renote: "Přeposlat"
 _deck:
   _columns:
     notifications: "Oznámení"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 1f558787ab..a3ff6c0a6c 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1006,7 +1006,7 @@ _instanceMute:
   heading: "Liste der stummzuschaltenden Instanzen"
 _theme:
   explore: "Farbschemata erforschen"
-  install: "Farbschmata installieren"
+  install: "Farbschemata installieren"
   manage: "Farbschemaverwaltung"
   code: "Farbschemencode"
   description: "Beschreibung"
@@ -1613,8 +1613,9 @@ _notification:
   youWereFollowed: "ist dir gefolgt"
   youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
   yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
-  youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen"
+  youWereInvitedToGroup: "{userName} hat dich in eine Gruppe eingeladen"
   pollEnded: "Umfrageergebnisse sind verfügbar"
+  emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
   _types:
     all: "Alle"
     follow: "Neue Follower"
@@ -1629,6 +1630,10 @@ _notification:
     followRequestAccepted: "Akzeptierte Follow-Anfragen"
     groupInvited: "Erhaltene Gruppeneinladungen"
     app: "Benachrichtigungen von Apps"
+  _actions:
+    followBack: "folgt dir nun auch"
+    reply: "Antworten"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Hauptspalte immer zeigen"
   columnAlign: "Spaltenausrichtung"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 99fe05375b..a7d69d6d4f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1613,8 +1613,9 @@ _notification:
   youWereFollowed: "followed you"
   youReceivedFollowRequest: "You've received a follow request"
   yourFollowRequestAccepted: "Your follow request was accepted"
-  youWereInvitedToGroup: "You've been invited to a group"
+  youWereInvitedToGroup: "{userName} invited you to a group"
   pollEnded: "Poll results have become available"
+  emptyPushNotificationMessage: "Push notifications have been updated"
   _types:
     all: "All"
     follow: "New followers"
@@ -1629,6 +1630,10 @@ _notification:
     followRequestAccepted: "Accepted follow requests"
     groupInvited: "Group invitations"
     app: "Notifications from linked apps"
+  _actions:
+    followBack: "followed you back"
+    reply: "Reply"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Always show main column"
   columnAlign: "Align columns"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index fd69f62ff5..8fff5ca4d9 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -141,6 +141,8 @@ flagAsBot: "Esta cuenta es un bot"
 flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opción. Al hacerlo, esta opción servirá para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustará los sistemas internos de Misskey para que trate a esta cuenta como un bot."
 flagAsCat: "Esta cuenta es un gato"
 flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opción."
+flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografía"
+flagShowTimelineRepliesDescription: "Cuando se marca, la línea de tiempo muestra respuestas a otras notas además de las notas del usuario"
 autoAcceptFollowed: "Aceptar automáticamente las solicitudes de seguimiento de los usuarios que sigues"
 addAccount: "Agregar Cuenta"
 loginFailed: "Error al iniciar sesión."
@@ -235,6 +237,8 @@ resetAreYouSure: "¿Desea reestablecer?"
 saved: "Guardado"
 messaging: "Chat"
 upload: "Subir"
+keepOriginalUploading: "Mantener la imagen original"
+keepOriginalUploadingDescription: "Mantener la versión original al cargar imágenes. Si está desactivado, el navegador generará imágenes para la publicación web en el momento de recargar la página"
 fromDrive: "Desde el drive"
 fromUrl: "Desde la URL"
 uploadFromUrl: "Subir desde una URL"
@@ -444,6 +448,7 @@ uiLanguage: "Idioma de visualización de la interfaz"
 groupInvited: "Invitado al grupo"
 aboutX: "Acerca de {x}"
 useOsNativeEmojis: "Usa los emojis nativos de la plataforma"
+disableDrawer: "No mostrar los menús en cajones"
 youHaveNoGroups: "Sin grupos"
 joinOrCreateGroup: "Obtenga una invitación para unirse al grupos o puede crear su propio grupo."
 noHistory: "No hay datos en el historial"
@@ -615,6 +620,10 @@ reportAbuse: "Reportar"
 reportAbuseOf: "Reportar a {name}"
 fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
 abuseReported: "Se ha enviado el reporte. Muchas gracias."
+reporteeOrigin: "Informar a"
+reporterOrigin: "Origen del informe"
+forwardReport: "Transferir un informe a una instancia remota"
+forwardReportIsAnonymous: "No puede ver su información de la instancia remota y aparecerá como una cuenta anónima del sistema"
 send: "Enviar"
 abuseMarkAsResolved: "Marcar reporte como resuelto"
 openInNewTab: "Abrir en una Nueva Pestaña"
@@ -676,6 +685,7 @@ center: "Centrar"
 wide: "Ancho"
 narrow: "Estrecho"
 reloadToApplySetting: "Esta configuración sólo se aplicará después de recargar la página. ¿Recargar ahora?"
+needReloadToApply: "Se requiere un reinicio para la aplicar los cambios"
 showTitlebar: "Mostrar la barra de título"
 clearCache: "Limpiar caché"
 onlineUsersCount: "{n} usuarios en línea"
@@ -706,19 +716,55 @@ capacity: "Capacidad"
 inUse: "Usado"
 editCode: "Editar código"
 apply: "Aplicar"
+receiveAnnouncementFromInstance: "Recibir notificaciones de la instancia"
+emailNotification: "Notificaciones por correo electrónico"
 publish: "Publicar"
 inChannelSearch: "Buscar en el canal"
+useReactionPickerForContextMenu: "Haga clic con el botón derecho para abrir el menu de reacciones"
+typingUsers: "{users} está escribiendo"
+jumpToSpecifiedDate: "Saltar a una fecha específica"
+showingPastTimeline: "Mostrar líneas de tiempo antiguas"
+clear: "Limpiar"
 markAllAsRead: "Marcar todo como leído"
 goBack: "Deseleccionar"
+fullView: "Vista completa"
+quitFullView: "quitar vista completa"
+addDescription: "Agregar descripción"
+userPagePinTip: "Puede mantener sus notas visibles aquí seleccionando Pin en el menú de notas individuales"
+notSpecifiedMentionWarning: "Algunas menciones no están incluidas en el destino"
 info: "Información"
+userInfo: "Información del usuario"
+unknown: "Desconocido"
+onlineStatus: "En línea"
+hideOnlineStatus: "mostrarse como desconectado"
+hideOnlineStatusDescription: "Ocultar su estado en línea puede reducir la eficacia de algunas funciones, como la búsqueda"
 online: "En línea"
+active: "Activo"
 offline: "Sin conexión"
+notRecommended: "obsoleto"
+botProtection: "Protección contra bots"
+instanceBlocking: "Instancias bloqueadas"
+selectAccount: "Elija una cuenta"
+switchAccount: "Cambiar de cuenta"
+enabled: "Activado"
+disabled: "Desactivado"
+quickAction: "Acciones rápidas"
 user: "Usuarios"
 administration: "Administrar"
+accounts: "Cuentas"
+switch: "Cambiar"
+noMaintainerInformationWarning: "No se ha establecido la información del administrador"
+noBotProtectionWarning: "La protección contra los bots no está configurada"
+configure: "Configurar"
+postToGallery: "Crear una nueva publicación en la galería"
 gallery: "Galería"
 recentPosts: "Posts recientes"
 popularPosts: "Más vistos"
+shareWithNote: "Compartir con una nota"
+ads: "Anuncios"
 expiration: "Termina el"
+memo: "Notas"
+priority: "Prioridad"
 high: "Alta"
 middle: "Mediano"
 low: "Baja"
@@ -770,22 +816,50 @@ _accountDelete:
   accountDelete: "Eliminar Cuenta"
 _ad:
   back: "Deseleccionar"
+_forgotPassword:
+  contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrónico, póngase en contacto con el administrador de la instancia para restablecer su contraseña"
 _gallery:
   my: "Mi galería"
+  liked: "Publicaciones que me gustan"
+  like: "¡Muy bien!"
   unlike: "Quitar me gusta"
 _email:
   _follow:
     title: "te ha seguido"
+  _receiveFollowRequest:
+    title: "Has recibido una solicitud de seguimiento"
+_plugin:
+  install: "Instalar plugins"
+  installWarn: "Por favor no instale plugins que no son de confianza"
+  manage: "Gestionar plugins"
 _registry:
+  scope: "Alcance"
   key: "Clave"
   keys: "Clave"
+  domain: "Dominio"
+  createKey: "Crear una llave"
+_aboutMisskey:
+  about: "Misskey es un software de código abierto, desarrollado por syuilo desde el 2014"
+  contributors: "Principales colaboradores"
+  allContributors: "Todos los colaboradores"
+  source: "Código fuente"
+  translation: "Traducir Misskey"
+  donate: "Donar a Misskey"
+  morePatrons: "Muchas más personas nos apoyan. Muchas gracias🥰"
+  patrons: "Patrocinadores"
+_nsfw:
+  respect: "Ocultar medios NSFW"
+  ignore: "No esconder medios NSFW "
+  force: "Ocultar todos los medios"
 _mfm:
   cheatSheet: "Hoja de referencia de MFM"
   intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquí puede ver una lista de sintaxis disponibles en MFM."
+  dummy: "Misskey expande el mundo de la Fediverso"
   mention: "Menciones"
   mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular."
   hashtag: "Hashtag"
   url: "URL"
+  urlDescription: "Se pueden mostrar las URL"
   link: "Vínculo"
   bold: "Negrita"
   center: "Centrar"
@@ -1432,6 +1506,9 @@ _notification:
     followRequestAccepted: "El seguimiento fue aceptado"
     groupInvited: "Invitado al grupo"
     app: "Notificaciones desde aplicaciones"
+  _actions:
+    reply: "Responder"
+    renote: "Renotar"
 _deck:
   alwaysShowMainColumn: "Siempre mostrar la columna principal"
   columnAlign: "Alinear columnas"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index 1fe74fa9ab..3b5dc1b06c 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -1615,6 +1615,9 @@ _notification:
     followRequestAccepted: "Demande d'abonnement acceptée"
     groupInvited: "Invitation à un groupe"
     app: "Notifications provenant des apps"
+  _actions:
+    reply: "Répondre"
+    renote: "Renoter"
 _deck:
   alwaysShowMainColumn: "Toujours afficher la colonne principale"
   columnAlign: "Aligner les colonnes"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 11dff184cd..a97ac819a9 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -593,6 +593,7 @@ smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
 testEmail: "Tes pengiriman surel"
 wordMute: "Bisukan kata"
 regexpError: "Kesalahan ekspresi reguler"
+regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
 instanceMute: "Bisuka instansi"
 userSaysSomething: "{name} mengatakan sesuatu"
 makeActive: "Aktifkan"
@@ -839,6 +840,7 @@ tenMinutes: "10 Menit"
 oneHour: "1 Jam"
 oneDay: "1 Hari"
 oneWeek: "1 Bulan"
+reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
 failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
 _emailUnavailable:
   used: "Alamat surel ini telah digunakan"
@@ -1613,6 +1615,7 @@ _notification:
   yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
   youWereInvitedToGroup: "Telah diundang ke grup"
   pollEnded: "Hasil Kuesioner telah keluar"
+  emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
   _types:
     all: "Semua"
     follow: "Ikuti"
@@ -1627,6 +1630,10 @@ _notification:
     followRequestAccepted: "Permintaan mengikuti disetujui"
     groupInvited: "Diundang ke grup"
     app: "Pemberitahuan dari aplikasi"
+  _actions:
+    followBack: "Ikuti Kembali"
+    reply: "Balas"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Selalu tampilkan kolom utama"
   columnAlign: "Luruskan kolom"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 1eaa78b646..4d10356698 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1433,6 +1433,9 @@ _notification:
     followRequestAccepted: "Richiesta di follow accettata"
     groupInvited: "Invito a un gruppo"
     app: "Notifiche da applicazioni"
+  _actions:
+    reply: "Rispondi"
+    renote: "Rinota"
 _deck:
   alwaysShowMainColumn: "Mostra sempre la colonna principale"
   columnAlign: "Allineare colonne"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 52ecd8c24e..fd6945160e 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1202,6 +1202,9 @@ _notification:
     reaction: "リアクション"
     receiveFollowRequest: "フォロー許可してほしいみたいやで"
     followRequestAccepted: "フォローが受理されたで"
+  _actions:
+    reply: "返事"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "いつもメインカラムを表示"
   columnAlign: "カラムの寄せ"
diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml
index 6a14cbe1ba..77ca824528 100644
--- a/locales/kab-KAB.yml
+++ b/locales/kab-KAB.yml
@@ -116,6 +116,8 @@ _notification:
   _types:
     follow: "Ig ṭṭafaṛ"
     mention: "Bder"
+  _actions:
+    reply: "Err"
 _deck:
   _columns:
     notifications: "Ilɣuyen"
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index 3111c90dd5..3682277175 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -76,6 +76,8 @@ _profile:
   username: "ಬಳಕೆಹೆಸರು"
 _notification:
   youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು"
+  _actions:
+    reply: "ಉತ್ತರಿಸು"
 _deck:
   _columns:
     notifications: "ಅಧಿಸೂಚನೆಗಳು"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index e1ad77cbc9..74d06185d9 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -592,6 +592,8 @@ smtpSecure: "SMTP 연결에 Implicit SSL/TTS 사용"
 smtpSecureInfo: "STARTTLS 사용 시에는 해제합니다."
 testEmail: "이메일 전송 테스트"
 wordMute: "단어 뮤트"
+regexpError: "정규 표현식 오류"
+regexpErrorDescription: "{tab}단어 뮤트 {line}행의 정규 표현식에 오류가 발생했습니다:"
 instanceMute: "인스턴스 뮤트"
 userSaysSomething: "{name}님이 무언가를 말했습니다"
 makeActive: "활성화"
@@ -825,8 +827,21 @@ overridedDeviceKind: "장치 유형"
 smartphone: "스마트폰"
 tablet: "태블릿"
 auto: "자동"
+themeColor: "테마 컬러"
+size: "크기"
+numberOfColumn: "한 줄에 보일 리액션의 수"
 searchByGoogle: "검색"
+instanceDefaultLightTheme: "인스턴스 기본 라이트 테마"
+instanceDefaultDarkTheme: "인스턴스 기본 다크 테마"
+instanceDefaultThemeDescription: "객체 형식의 테마 코드를 입력해 주세요."
+mutePeriod: "뮤트할 기간"
 indefinitely: "무기한"
+tenMinutes: "10분"
+oneHour: "1시간"
+oneDay: "1일"
+oneWeek: "일주일"
+reflectMayTakeTime: "반영되기까지 시간이 걸릴 수 있습니다."
+failedToFetchAccountInformation: "계정 정보를 가져오지 못했습니다"
 _emailUnavailable:
   used: "이 메일 주소는 사용중입니다"
   format: "형식이 올바르지 않습니다"
@@ -1249,7 +1264,7 @@ _profile:
   youCanIncludeHashtags: "해시 태그를 포함할 수 있습니다."
   metadata: "추가 정보"
   metadataEdit: "추가 정보 편집"
-  metadataDescription: "프로필에 최대 4개의 추가 정보를 표시할 수 있어요"
+  metadataDescription: "프로필에 추가 정보를 표시할 수 있어요"
   metadataLabel: "라벨"
   metadataContent: "내용"
   changeAvatar: "아바타 이미지 변경"
@@ -1599,6 +1614,8 @@ _notification:
   youReceivedFollowRequest: "새로운 팔로우 요청이 있습니다"
   yourFollowRequestAccepted: "팔로우 요청이 수락되었습니다"
   youWereInvitedToGroup: "그룹에 초대되었습니다"
+  pollEnded: "투표 결과가 발표되었습니다"
+  emptyPushNotificationMessage: "푸시 알림이 갱신되었습니다"
   _types:
     all: "전부"
     follow: "팔로잉"
@@ -1608,10 +1625,15 @@ _notification:
     quote: "인용"
     reaction: "리액션"
     pollVote: "투표 참여"
+    pollEnded: "투표가 종료됨"
     receiveFollowRequest: "팔로우 요청을 받았을 때"
     followRequestAccepted: "팔로우 요청이 승인되었을 때"
     groupInvited: "그룹에 초대되었을 때"
     app: "연동된 앱을 통한 알림"
+  _actions:
+    followBack: "팔로우"
+    reply: "답글"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "메인 칼럼 항상 표시"
   columnAlign: "칼럼 정렬"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index f4e4a62182..d0a83eb6a8 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -371,6 +371,9 @@ _notification:
     renote: "Herdelen"
     quote: "Quote"
     reaction: "Reacties"
+  _actions:
+    reply: "Antwoord"
+    renote: "Herdelen"
 _deck:
   _columns:
     notifications: "Meldingen"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 78d86dd7e3..7fabab3b4f 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -1401,6 +1401,9 @@ _notification:
     followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
     groupInvited: "Zaproszono do grup"
     app: "Powiadomienia z aplikacji"
+  _actions:
+    reply: "Odpowiedz"
+    renote: "Udostępnij"
 _deck:
   alwaysShowMainColumn: "Zawsze pokazuj główną kolumnę"
   columnAlign: "Wyrównaj kolumny"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 104e4ceb7c..c1afa5b56c 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -37,17 +37,58 @@ favorites: "Favoritar"
 unfavorite: "Remover dos favoritos"
 favorited: "Adicionado aos favoritos."
 alreadyFavorited: "Já adicionado aos favoritos."
+cantFavorite: "Não foi possível adicionar aos favoritos."
+pin: "Afixar no perfil"
+unpin: "Desafixar do perfil"
+copyContent: "Copiar conteúdos"
+copyLink: "Copiar hiperligação"
+delete: "Eliminar"
+deleteAndEdit: "Eliminar e editar"
+deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editá-la? Irás perder todas as suas reações, renotas e respostas."
+addToList: "Adicionar a lista"
+sendMessage: "Enviar uma mensagem"
+copyUsername: "Copiar nome de utilizador"
+searchUser: "Pesquisar utilizador"
+reply: "Responder"
+loadMore: "Carregar mais"
 showMore: "Ver mais"
 youGotNewFollower: "Você tem um novo seguidor"
+receiveFollowRequest: "Pedido de seguimento recebido"
 followRequestAccepted: "Pedido de seguir aceito"
+mention: "Menção"
+mentions: "Menções"
+directNotes: "Notas diretas"
+importAndExport: "Importar/Exportar"
+import: "Importar"
+export: "Exportar"
+files: "Ficheiros"
+download: "Descarregar"
+driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serão também apagadas."
+unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
+exportRequested: "Pediste uma exportação. Este processo pode demorar algum tempo. Será adicionado à tua Drive após a conclusão do processo."
+importRequested: "Pediste uma importação. Este processo pode demorar algum tempo."
+lists: "Listas"
+noLists: "Não tens nenhuma lista"
 note: "Post"
 notes: "Posts"
+following: "Seguindo"
+followers: "Seguidores"
+followsYou: "Segue-te"
+createList: "Criar lista"
+manageLists: "Gerir listas"
+error: "Erro"
+somethingHappened: "Ocorreu um erro"
+retry: "Tentar novamente"
+pageLoadError: "Ocorreu um erro ao carregar a página."
+pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
+follow: "Seguindo"
 enterEmoji: "Inserir emoji"
 renote: "Repostar"
 renoted: "Repostado"
 cantRenote: "Não pode repostar"
 cantReRenote: "Não pode repostar este repost"
 pinnedNote: "Post fixado"
+pinned: "Afixar no perfil"
 sensitive: "Conteúdo sensível"
 mute: "Silenciar"
 unmute: "Dessilenciar"
@@ -57,6 +98,7 @@ registeredAt: "Registrado em"
 perHour: "por hora"
 perDay: "por dia"
 noUsers: "Sem usuários"
+remove: "Eliminar"
 messageRead: "Lida"
 lightThemes: "Tema claro"
 darkThemes: "Tema escuro"
@@ -64,6 +106,7 @@ addFile: "Adicionar arquivo"
 nsfw: "Conteúdo sensível"
 monthX: "mês de {month}"
 pinnedNotes: "Post fixado"
+userList: "Listas"
 smtpUser: "Nome de usuário"
 smtpPass: "Senha"
 user: "Usuários"
@@ -72,9 +115,11 @@ _email:
   _follow:
     title: "Você tem um novo seguidor"
 _mfm:
+  mention: "Menção"
   search: "Pesquisar"
 _theme:
   keys:
+    mention: "Menção"
     renote: "Repostar"
 _sfx:
   note: "Posts"
@@ -82,15 +127,47 @@ _sfx:
 _widgets:
   notifications: "Notificações"
   timeline: "Timeline"
+_cw:
+  show: "Carregar mais"
+_visibility:
+  followers: "Seguidores"
 _profile:
   username: "Nome de usuário"
 _exportOrImport:
+  followingList: "Seguindo"
   muteList: "Silenciar"
+  userLists: "Listas"
+_pages:
+  script:
+    categories:
+      list: "Listas"
+    blocks:
+      _join:
+        arg1: "Listas"
+      _randomPick:
+        arg1: "Listas"
+      _dailyRandomPick:
+        arg1: "Listas"
+      _seedRandomPick:
+        arg2: "Listas"
+      _pick:
+        arg1: "Listas"
+      _listLen:
+        arg1: "Listas"
+    types:
+      array: "Listas"
 _notification:
   youWereFollowed: "Você tem um novo seguidor"
   _types:
+    follow: "Seguindo"
+    mention: "Menção"
+    renote: "Repostar"
+  _actions:
+    reply: "Responder"
     renote: "Repostar"
 _deck:
   _columns:
     notifications: "Notificações"
     tl: "Timeline"
+    list: "Listas"
+    mentions: "Menções"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 6b2ff19e8e..cc74756119 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -562,13 +562,87 @@ plugins: "Pluginuri"
 deck: "Deck"
 undeck: "Părăsește Deck"
 useBlurEffectForModal: "Folosește efect de blur pentru modale"
+width: "Lăţime"
+height: "Înălţime"
+large: "Mare"
+medium: "Mediu"
+small: "Mic"
+generateAccessToken: "Generează token de acces"
+permission: "Permisiuni"
+enableAll: "Actevează tot"
+disableAll: "Dezactivează tot"
+tokenRequested: "Acordă acces la cont"
+pluginTokenRequestedDescription: "Acest plugin va putea să folosească permisiunile setate aici."
+notificationType: "Tipul notificării"
+edit: "Editează"
+useStarForReactionFallback: "Folosește ★ ca fallback dacă emoji-ul este necunoscut"
+emailServer: "Server email"
+enableEmail: "Activează distribuția de emailuri"
+emailConfigInfo: "Folosit pentru a confirma emailul tău în timpul logări dacă îți uiți parola"
+email: "Email"
+emailAddress: "Adresă de email"
+smtpConfig: "Configurare Server SMTP"
 smtpHost: "Gazdă"
+smtpPort: "Port"
 smtpUser: "Nume de utilizator"
 smtpPass: "Parolă"
+emptyToDisableSmtpAuth: "Lasă username-ul și parola necompletate pentru a dezactiva verificarea SMTP"
+smtpSecure: "Folosește SSL/TLS implicit pentru conecțiunile SMTP"
+smtpSecureInfo: "Oprește opțiunea asta dacă STARTTLS este folosit"
+testEmail: "Testează livrarea emailurilor"
+wordMute: "Cuvinte pe mut"
+regexpError: "Eroare de Expresie Regulată"
+regexpErrorDescription: "A apărut o eroare în expresia regulată pe linia {line} al cuvintelor {tab} setate pe mut:"
+instanceMute: "Instanțe pe mut"
+userSaysSomething: "{name} a spus ceva"
+makeActive: "Activează"
+display: "Arată"
+copy: "Copiază"
+metrics: "Metrici"
+overview: "Privire de ansamblu"
+logs: "Log-uri"
+delayed: "Întârziate"
+database: "Baza de date"
+channel: "Canale"
+create: "Crează"
+notificationSetting: "Setări notificări"
+notificationSettingDesc: "Selectează tipurile de notificări care să fie arătate"
+useGlobalSetting: "Folosește setările globale"
+useGlobalSettingDesc: "Dacă opțiunea e pornită, notificările contului tău vor fi folosite. Dacă e oprită, configurația va fi individuală."
+other: "Altele"
+regenerateLoginToken: "Regenerează token de login"
+regenerateLoginTokenDescription: "Regenerează token-ul folosit intern în timpul logări. În mod normal asta nu este necesar. Odată regenerat, toate dispozitivele vor fi delogate."
+setMultipleBySeparatingWithSpace: "Separă mai multe intrări cu spații."
+fileIdOrUrl: "Introdu ID sau URL"
+behavior: "Comportament"
+sample: "exemplu"
+abuseReports: "Rapoarte"
+reportAbuse: "Raportează"
+reportAbuseOf: "Raportează {name}"
+fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacă este despre o notă specifică, te rog introdu URL-ul ei."
+abuseReported: "Raportul tău a fost trimis. Mulțumim."
+reporter: "Raportorul"
+reporteeOrigin: "Originea raportatului"
+reporterOrigin: "Originea raportorului"
+forwardReport: "Redirecționează raportul către instanța externă"
+forwardReportIsAnonymous: "În locul contului tău, va fi afișat un cont anonim, de sistem, ca raportor către instanța externă."
+send: "Trimite"
+abuseMarkAsResolved: "Marchează raportul ca rezolvat"
+openInNewTab: "Deschide în tab nou"
+openInSideView: "Deschide în vedere laterală"
+defaultNavigationBehaviour: "Comportament de navigare implicit"
+editTheseSettingsMayBreakAccount: "Editarea acestor setări îți pot defecta contul."
+waitingFor: "Așteptând pentru {x}"
+random: "Aleator"
+system: "Sistem"
+switchUi: "Schimbă UI"
+desktop: "Desktop"
 clearCache: "Golește cache-ul"
 info: "Despre"
 user: "Utilizatori"
 administration: "Gestionare"
+middle: "Mediu"
+sent: "Trimite"
 searchByGoogle: "Caută"
 _email:
   _follow:
@@ -641,6 +715,9 @@ _notification:
     renote: "Re-notează"
     quote: "Citează"
     reaction: "Reacție"
+  _actions:
+    reply: "Răspunde"
+    renote: "Re-notează"
 _deck:
   _columns:
     notifications: "Notificări"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 877e1e185d..7405c07e6c 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1599,6 +1599,9 @@ _notification:
     followRequestAccepted: "Запрос на подписку одобрен"
     groupInvited: "Приглашение в группы"
     app: "Уведомления из приложений"
+  _actions:
+    reply: "Ответить"
+    renote: "Репост"
 _deck:
   alwaysShowMainColumn: "Всегда показывать главную колонку"
   columnAlign: "Выравнивание колонок"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index c6f2f59bdf..6818a64d30 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -1628,6 +1628,10 @@ _notification:
     followRequestAccepted: "Schválené žiadosti o sledovanie"
     groupInvited: "Pozvánky do skupín"
     app: "Oznámenia z prepojených aplikácií"
+  _actions:
+    followBack: "Sledovať späť\n"
+    reply: "Odpovedať"
+    renote: "Preposlať"
 _deck:
   alwaysShowMainColumn: "Vždy zobraziť v hlavnom stĺpci"
   columnAlign: "Zarovnať stĺpce"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 073b2c310e..480526c134 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -7,6 +7,7 @@ search: "Пошук"
 notifications: "Сповіщення"
 username: "Ім'я користувача"
 password: "Пароль"
+forgotPassword: "Я забув пароль"
 fetchingAsApObject: "Отримуємо з федіверсу..."
 ok: "OK"
 gotIt: "Зрозуміло!"
@@ -80,6 +81,8 @@ somethingHappened: "Щось пішло не так"
 retry: "Спробувати знову"
 pageLoadError: "Помилка при завантаженні сторінки"
 pageLoadErrorDescription: "Зазвичай це пов’язано з помилками мережі або кешем браузера. Очистіть кеш або почекайте трохи й спробуйте ще раз."
+serverIsDead: "Відповіді від сервера немає. Зачекайте деякий час і повторіть спробу."
+youShouldUpgradeClient: "Перезавантажте та використовуйте нову версію клієнта, щоб переглянути цю сторінку."
 enterListName: "Введіть назву списку"
 privacy: "Конфіденційність"
 makeFollowManuallyApprove: "Підтверджувати підписників уручну"
@@ -103,6 +106,7 @@ clickToShow: "Натисніть для перегляду"
 sensitive: "NSFW"
 add: "Додати"
 reaction: "Реакції"
+reactionSetting: "Налаштування реакцій"
 reactionSettingDescription2: "Перемістити щоб змінити порядок, Клацнути мишою щоб видалити, Натиснути \"+\" щоб додати."
 rememberNoteVisibility: "Пам’ятати параметри видимісті"
 attachCancel: "Видалити вкладення"
@@ -137,7 +141,10 @@ flagAsBot: "Акаунт бота"
 flagAsBotDescription: "Ввімкніть якщо цей обліковий запис використовується ботом. Ця опція позначить обліковий запис як бота. Це потрібно щоб виключити безкінечну інтеракцію між ботами а також відповідного підлаштування Misskey."
 flagAsCat: "Акаунт кота"
 flagAsCatDescription: "Ввімкніть, щоб позначити, що обліковий запис є котиком."
+flagShowTimelineReplies: "Показувати відповіді на нотатки на часовій шкалі"
+flagShowTimelineRepliesDescription: "Показує відповіді користувачів на нотатки інших користувачів на часовій шкалі."
 autoAcceptFollowed: "Автоматично приймати запити на підписку від користувачів, на яких ви підписані"
+addAccount: "Додати акаунт"
 loginFailed: "Не вдалося увійти"
 showOnRemote: "Переглянути в оригіналі"
 general: "Загальне"
@@ -148,6 +155,7 @@ searchWith: "Пошук: {q}"
 youHaveNoLists: "У вас немає списків"
 followConfirm: "Підписатися на {name}?"
 proxyAccount: "Проксі-акаунт"
+proxyAccountDescription: "Обліковий запис проксі – це обліковий запис, який діє як віддалений підписник для користувачів за певних умов. Наприклад, коли користувач додає віддаленого користувача до списку, активність віддаленого користувача не буде доставлена на сервер, якщо жоден локальний користувач не стежить за цим користувачем, то замість нього буде використовуватися обліковий запис проксі-сервера."
 host: "Хост"
 selectUser: "Виберіть користувача"
 recipient: "Отримувач"
@@ -229,6 +237,8 @@ resetAreYouSure: "Справді скинути?"
 saved: "Збережено"
 messaging: "Чати"
 upload: "Завантажити"
+keepOriginalUploading: "Зберегти оригінальне зображення"
+keepOriginalUploadingDescription: "Зберігає початково завантажене зображення як є. Якщо вимкнено, версія для відображення в Інтернеті буде створена під час завантаження."
 fromDrive: "З диска"
 fromUrl: "З посилання"
 uploadFromUrl: "Завантажити з посилання"
@@ -275,6 +285,7 @@ emptyDrive: "Диск порожній"
 emptyFolder: "Тека порожня"
 unableToDelete: "Видалення неможливе"
 inputNewFileName: "Введіть ім'я нового файлу"
+inputNewDescription: "Введіть новий заголовок"
 inputNewFolderName: "Введіть ім'я нової теки"
 circularReferenceFolder: "Ви намагаєтесь перемістити папку в її підпапку."
 hasChildFilesOrFolders: "Ця тека не порожня і не може бути видалена"
@@ -306,6 +317,8 @@ monthX: "{month}"
 yearX: "{year}"
 pages: "Сторінки"
 integration: "Інтеграція"
+connectService: "Під’єднати"
+disconnectService: "Відключитися"
 enableLocalTimeline: "Увімкнути локальну стрічку"
 enableGlobalTimeline: "Увімкнути глобальну стрічку"
 disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті."
@@ -317,6 +330,7 @@ driveCapacityPerRemoteAccount: "Об'єм диска на одного відд
 inMb: "В мегабайтах"
 iconUrl: "URL аватара"
 bannerUrl: "URL банера"
+backgroundImageUrl: "URL-адреса фонового зображення"
 basicInfo: "Основна інформація"
 pinnedUsers: "Закріплені користувачі"
 pinnedUsersDescription: "Впишіть в список користувачів, яких хочете закріпити на сторінці \"Знайти\", ім'я в стовпчик."
@@ -332,6 +346,7 @@ recaptcha: "reCAPTCHA"
 enableRecaptcha: "Увімкнути reCAPTCHA"
 recaptchaSiteKey: "Ключ сайту"
 recaptchaSecretKey: "Секретний ключ"
+avoidMultiCaptchaConfirm: "Використання кількох систем Captcha може спричинити перешкоди між ними. Бажаєте вимкнути інші активні системи Captcha? Якщо ви хочете, щоб вони залишалися ввімкненими, натисніть «Скасувати»."
 antennas: "Антени"
 manageAntennas: "Налаштування антен"
 name: "Ім'я"
@@ -428,10 +443,12 @@ signinWith: "Увійти за допомогою {x}"
 signinFailed: "Не вдалося увійти. Введені ім’я користувача або пароль неправильнi."
 tapSecurityKey: "Торкніться ключа безпеки"
 or: "або"
+language: "Мова"
 uiLanguage: "Мова інтерфейсу"
 groupInvited: "Запрошення до групи"
 aboutX: "Про {x}"
 useOsNativeEmojis: "Використовувати емодзі ОС"
+disableDrawer: "Не використовувати висувні меню"
 youHaveNoGroups: "Немає груп"
 joinOrCreateGroup: "Отримуйте запрошення до груп або створюйте свої власні групи."
 noHistory: "Історія порожня"
@@ -442,6 +459,7 @@ category: "Категорія"
 tags: "Теги"
 docSource: "Джерело цього документа"
 createAccount: "Створити акаунт"
+existingAccount: "Існуючий обліковий запис"
 regenerate: "Оновити"
 fontSize: "Розмір шрифту"
 noFollowRequests: "Немає запитів на підписку"
@@ -463,6 +481,7 @@ showFeaturedNotesInTimeline: "Показувати популярні нотат
 objectStorage: "Object Storage"
 useObjectStorage: "Використовувати object storage"
 objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "Це початкова частина адреси, що використовується CDN або проксі, наприклад для S3: https://<bucket>.s3.amazonaws.com, або GCS: 'https://storage.googleapis.com/<bucket>'"
 objectStorageBucket: "Bucket"
 objectStorageBucketDesc: "Будь ласка вкажіть назву відра в налаштованому сервісі."
 objectStoragePrefix: "Prefix"
@@ -513,6 +532,9 @@ removeAllFollowing: "Скасувати всі підписки"
 removeAllFollowingDescription: "Скасувати підписку на всі акаунти з {host}. Будь ласка, робіть це, якщо інстанс більше не існує."
 userSuspended: "Обліковий запис заблокований."
 userSilenced: "Обліковий запис приглушений."
+yourAccountSuspendedTitle: "Цей обліковий запис заблоковано"
+yourAccountSuspendedDescription: "Цей обліковий запис було заблоковано через порушення умов надання послуг сервера. Зв'яжіться з адміністратором, якщо ви хочете дізнатися докладнішу причину. Будь ласка, не створюйте новий обліковий запис."
+menu: "Меню"
 divider: "Розділювач"
 addItem: "Додати елемент"
 relays: "Ретранслятори"
@@ -531,6 +553,8 @@ disablePlayer: "Закрити відеоплеєр"
 expandTweet: "Розгорнути твіт"
 themeEditor: "Редактор тем"
 description: "Опис"
+describeFile: "Додати підпис"
+enterFileDescription: "Введіть підпис"
 author: "Автор"
 leaveConfirm: "Зміни не збережені. Ви дійсно хочете скасувати зміни?"
 manage: "Управління"
@@ -553,6 +577,7 @@ pluginTokenRequestedDescription: "Цей плагін зможе викорис
 notificationType: "Тип сповіщення"
 edit: "Редагувати"
 useStarForReactionFallback: "Використовувати ★ як запасний варіант, якщо емодзі реакції невідомий"
+emailServer: "Сервер електронної пошти"
 enableEmail: "Увімкнути функцію доставки пошти"
 emailConfigInfo: "Використовується для підтвердження електронної пошти підчас реєстрації, а також для відновлення паролю."
 email: "E-mail"
@@ -567,6 +592,9 @@ smtpSecure: "Використовувати безумовне шифруван
 smtpSecureInfo: "Вимкніть при використанні STARTTLS  "
 testEmail: "Тестовий email"
 wordMute: "Блокування слів"
+regexpError: "Помилка регулярного виразу"
+regexpErrorDescription: "Сталася помилка в регулярному виразі в рядку {line} вашого слова {tab} слова що ігноруються:"
+instanceMute: "Приглушення інстансів"
 userSaysSomething: "{name} щось сказав(ла)"
 makeActive: "Активувати"
 display: "Відображення"
@@ -594,6 +622,11 @@ reportAbuse: "Поскаржитись"
 reportAbuseOf: "Поскаржитись на {name}"
 fillAbuseReportDescription: "Будь ласка вкажіть подробиці скарги. Якщо скарга стосується запису, вкажіть посилання на нього."
 abuseReported: "Дякуємо, вашу скаргу було відправлено. "
+reporter: "Репортер"
+reporteeOrigin: "Про кого повідомлено"
+reporterOrigin: "Хто повідомив"
+forwardReport: "Переслати звіт на віддалений інстанс"
+forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі"
 send: "Відправити"
 abuseMarkAsResolved: "Позначити скаргу як вирішену"
 openInNewTab: "Відкрити в новій вкладці"
@@ -655,6 +688,7 @@ center: "Центр"
 wide: "Широкий"
 narrow: "Вузький"
 reloadToApplySetting: "Налаштування ввійде в дію при перезавантаженні. Перезавантажити?"
+needReloadToApply: "Зміни набудуть чинності після перезавантаження сторінки."
 showTitlebar: "Показати титульний рядок"
 clearCache: "Очистити кеш"
 onlineUsersCount: "{n} користувачів онлайн"
@@ -669,12 +703,28 @@ textColor: "Текст"
 saveAs: "Зберегти як…"
 advanced: "Розширені"
 value: "Значення"
+createdAt: "Створено"
 updatedAt: "Останнє оновлення"
 saveConfirm: "Зберегти зміни?"
 deleteConfirm: "Ви дійсно бажаєте це видалити?"
 invalidValue: "Некоректне значення."
 registry: "Реєстр"
 closeAccount: "Закрити обліковий запис"
+currentVersion: "Версія, що використовується"
+latestVersion: "Сама свіжа версія"
+youAreRunningUpToDateClient: "У вас найсвіжіша версія клієнта."
+newVersionOfClientAvailable: "Доступніша свіжа версія клієнта."
+usageAmount: "Використане"
+capacity: "Ємність"
+inUse: "Зайнято"
+editCode: "Редагувати вихідний текст"
+apply: "Застосувати"
+receiveAnnouncementFromInstance: "Отримувати оповіщення з інстансу"
+emailNotification: "Сповіщення електронною поштою"
+publish: "Опублікувати"
+inChannelSearch: "Пошук за каналом"
+useReactionPickerForContextMenu: "Відкривати палітру реакцій правою кнопкою"
+typingUsers: "Стук клавіш. Це {users}…"
 goBack: "Назад"
 info: "Інформація"
 user: "Користувачі"
@@ -687,6 +737,8 @@ hashtags: "Хештеґ"
 hide: "Сховати"
 searchByGoogle: "Пошук"
 indefinitely: "Ніколи"
+_ffVisibility:
+  public: "Опублікувати"
 _ad:
   back: "Назад"
 _gallery:
@@ -1377,6 +1429,9 @@ _notification:
     followRequestAccepted: "Прийняті підписки"
     groupInvited: "Запрошення до груп"
     app: "Сповіщення від додатків"
+  _actions:
+    reply: "Відповісти"
+    renote: "Поширити"
 _deck:
   alwaysShowMainColumn: "Завжди показувати головну колонку"
   columnAlign: "Вирівняти стовпці"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 42f86b3359..ffe5ba1976 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1,33 +1,1091 @@
 ---
 _lang_: "Tiếng Việt"
 headlineMisskey: "Mạng xã hội liên hợp"
+introMisskey: "Xin chào! Misskey là một nền tảng tiểu blog phi tập trung mã nguồn mở.\nViết \"tút\" để chia sẻ những suy nghĩ của bạn 📡\nBằng \"biểu cảm\", bạn có thể bày tỏ nhanh chóng cảm xúc của bạn với các tút 👍\nHãy khám phá một thế giới mới! 🚀"
 monthAndDay: "{day} tháng {month}"
 search: "Tìm kiếm"
 notifications: "Thông báo"
 username: "Tên người dùng"
 password: "Mật khẩu"
 forgotPassword: "Quên mật khẩu"
+fetchingAsApObject: "Đang nạp dữ liệu từ Fediverse..."
 ok: "Đồng ý"
+gotIt: "Đã hiểu!"
+cancel: "Hủy"
+enterUsername: "Nhập tên người dùng"
 renotedBy: "Chia sẻ bởi {user}"
+noNotes: "Chưa có tút nào."
+noNotifications: "Không có thông báo"
+instance: "Máy chủ"
+settings: "Cài đặt"
+basicSettings: "Thiết lập chung"
+otherSettings: "Thiết lập khác"
+openInWindow: "Mở trong cửa sổ mới"
+profile: "Trang cá nhân"
+timeline: "Bảng tin"
+noAccountDescription: "Người này chưa viết mô tả."
+login: "Đăng nhập"
+loggingIn: "Đang đăng nhập..."
+logout: "Đăng xuất"
+signup: "Đăng ký"
+uploading: "Đang tải lên…"
+save: "Lưu"
+users: "Người dùng"
+addUser: "Thêm người dùng"
+favorite: "Thêm vào yêu thích"
+favorites: "Lượt thích"
+unfavorite: "Bỏ thích"
+favorited: "Đã thêm vào yêu thích."
+alreadyFavorited: "Đã thêm vào yêu thích rồi."
+cantFavorite: "Không thể thêm vào yêu thích."
+pin: "Ghim"
+unpin: "Bỏ ghim"
+copyContent: "Chép nội dung"
+copyLink: "Chép liên kết"
+delete: "Xóa"
+deleteAndEdit: "Sửa"
+deleteAndEditConfirm: "Bạn có chắc muốn sửa tút này? Những biểu cảm, lượt trả lời và đăng lại sẽ bị mất."
+addToList: "Thêm vào danh sách"
+sendMessage: "Gửi tin nhắn"
+copyUsername: "Chép tên người dùng"
+searchUser: "Tìm kiếm người dùng"
+reply: "Trả lời"
+loadMore: "Tải thêm"
+showMore: "Xem thêm"
+youGotNewFollower: "đã theo dõi bạn"
+receiveFollowRequest: "Đã yêu cầu theo dõi"
+followRequestAccepted: "Đã chấp nhận yêu cầu theo dõi"
+mention: "Nhắc đến"
+mentions: "Lượt nhắc"
+directNotes: "Nhắn riêng"
+importAndExport: "Nhập và xuất dữ liệu"
+import: "Nhập dữ liệu"
+export: "Xuất dữ liệu"
+files: "Tập tin"
+download: "Tải xuống"
+driveFileDeleteConfirm: "Bạn có chắc muốn xóa tập tin \"{name}\"? Tút liên quan cũng sẽ bị xóa theo."
+unfollowConfirm: "Bạn có chắc muốn ngưng theo dõi {name}?"
+exportRequested: "Đang chuẩn bị xuất tập tin. Quá trình này có thể mất ít phút. Nó sẽ được tự động thêm vào Drive sau khi hoàn thành."
+importRequested: "Bạn vừa yêu cầu nhập dữ liệu. Quá trình này có thể mất ít phút."
+lists: "Danh sách"
+noLists: "Bạn chưa có danh sách nào"
+note: "Tút"
+notes: "Tút"
+following: "Đang theo dõi"
+followers: "Người theo dõi"
+followsYou: "Theo dõi bạn"
+createList: "Tạo danh sách"
+manageLists: "Quản lý danh sách"
+error: "Lỗi"
+somethingHappened: "Xảy ra lỗi"
+retry: "Thử lại"
+pageLoadError: "Xảy ra lỗi khi tải trang."
+pageLoadErrorDescription: "Có thể là do bộ nhớ đệm của trình duyệt. Hãy thử xóa bộ nhớ đệm và thử lại sau ít phút."
+serverIsDead: "Máy chủ không phản hồi. Vui lòng thử lại sau giây lát."
+youShouldUpgradeClient: "Để xem trang này, hãy làm tươi để cập nhật ứng dụng."
+enterListName: "Đặt tên cho danh sách"
+privacy: "Bảo mật"
+makeFollowManuallyApprove: "Yêu cầu theo dõi cần được duyệt"
+defaultNoteVisibility: "Kiểu tút mặc định"
+follow: "Đang theo dõi"
+followRequest: "Gửi yêu cầu theo dõi"
+followRequests: "Yêu cầu theo dõi"
+unfollow: "Ngưng theo dõi"
+followRequestPending: "Yêu cầu theo dõi đang chờ"
+enterEmoji: "Chèn emoji"
+renote: "Đăng lại"
+unrenote: "Hủy đăng lại"
+renoted: "Đã đăng lại."
+cantRenote: "Không thể đăng lại tút này."
+cantReRenote: "Không thể đăng lại một tút đăng lại."
+quote: "Trích dẫn"
+pinnedNote: "Tút ghim"
+pinned: "Ghim"
+you: "Bạn"
+clickToShow: "Nhấn để xem"
+sensitive: "Nhạy cảm"
+add: "Thêm"
+reaction: "Biểu cảm"
+reactionSetting: "Chọn những biểu cảm hiển thị"
+reactionSettingDescription2: "Kéo để sắp xếp, nhấn để xóa, nhấn \"+\" để thêm."
+rememberNoteVisibility: "Lưu kiểu tút mặc định"
+attachCancel: "Gỡ tập tin đính kèm"
+markAsSensitive: "Đánh dấu là nhạy cảm"
+unmarkAsSensitive: "Bỏ đánh dấu nhạy cảm"
+enterFileName: "Nhập tên tập tin"
+mute: "Ẩn"
+unmute: "Bỏ ẩn"
+block: "Chặn"
+unblock: "Bỏ chặn"
+suspend: "Vô hiệu hóa"
+unsuspend: "Bỏ vô hiệu hóa"
+blockConfirm: "Bạn có chắc muốn chặn người này?"
+unblockConfirm: "Bạn có chắc muốn bỏ chặn người này?"
+suspendConfirm: "Bạn có chắc muốn vô hiệu hóa người này?"
+unsuspendConfirm: "Bạn có chắc muốn bỏ vô hiệu hóa người này?"
+selectList: "Chọn danh sách"
+selectAntenna: "Chọn một antenna"
+selectWidget: "Chọn tiện ích"
+editWidgets: "Sửa tiện ích"
+editWidgetsExit: "Xong"
+customEmojis: "Tùy chỉnh emoji"
+emoji: "Emoji"
+emojis: "Emoji"
+emojiName: "Tên emoji"
+emojiUrl: "URL Emoji"
+addEmoji: "Thêm emoji"
+settingGuide: "Cài đặt đề xuất"
+cacheRemoteFiles: "Tập tin cache từ xa"
+cacheRemoteFilesDescription: "Khi tùy chọn này bị tắt, các tập tin từ xa sẽ được tải trực tiếp từ máy chủ khác. Điều này sẽ giúp giảm dung lượng lưu trữ nhưng lại tăng lưu lượng truy cập, vì hình thu nhỏ sẽ không được tạo."
 flagAsBot: "Đánh dấu đây là tài khoản bot"
+flagAsBotDescription: "Bật tùy chọn này nếu tài khoản này được kiểm soát bởi một chương trình. Nếu được bật, nó sẽ được đánh dấu để các nhà phát triển khác ngăn chặn chuỗi tương tác vô tận với các bot khác và điều chỉnh hệ thống nội bộ của Misskey để coi tài khoản này như một bot."
+flagAsCat: "Tài khoản này là mèo"
+flagAsCatDescription: "Bật tùy chọn này để đánh dấu tài khoản là một con mèo."
+flagShowTimelineReplies: "Hiện lượt trả lời trong bảng tin"
+flagShowTimelineRepliesDescription: "Hiện lượt trả lời của người bạn theo dõi trên tút của những người khác."
+autoAcceptFollowed: "Tự động phê duyệt theo dõi từ những người mà bạn đang theo dõi"
+addAccount: "Thêm tài khoản"
+loginFailed: "Đăng nhập không thành công"
+showOnRemote: "Truy cập trang của người này"
+general: "Tổng quan"
+wallpaper: "Ảnh bìa"
+setWallpaper: "Đặt ảnh bìa"
+removeWallpaper: "Xóa ảnh bìa"
 searchWith: "Tìm kiếm: {q}"
+youHaveNoLists: "Bạn chưa có danh sách nào"
 followConfirm: "Bạn có chắc muốn theo dõi {name}?"
+proxyAccount: "Tài khoản proxy"
+proxyAccountDescription: "Tài khoản proxy là tài khoản hoạt động như một người theo dõi từ xa cho người dùng trong những điều kiện nhất định. Ví dụ: khi người dùng thêm người dùng từ xa vào danh sách, hoạt động của người dùng từ xa sẽ không được chuyển đến phiên bản nếu không có người dùng cục bộ nào theo dõi người dùng đó, vì vậy tài khoản proxy sẽ theo dõi."
+host: "Host"
+selectUser: "Chọn người dùng"
+recipient: "Người nhận"
+annotation: "Bình luận"
+federation: "Liên hợp"
+instances: "Máy chủ"
+registeredAt: "Đăng ký vào"
+latestRequestSentAt: "Yêu cầu cuối gửi lúc"
+latestRequestReceivedAt: "Yêu cầu cuối nhận lúc"
+latestStatus: "Trạng thái cuối cùng"
+storageUsage: "Dung lượng lưu trữ"
+charts: "Đồ thị"
+perHour: "Mỗi Giờ"
+perDay: "Mỗi Ngày"
+stopActivityDelivery: "Ngưng gửi hoạt động"
+blockThisInstance: "Chặn máy chủ này"
+operations: "Vận hành"
+software: "Phần mềm"
+version: "Phiên bản"
+metadata: "Metadata"
+withNFiles: "{n} tập tin"
+monitor: "Giám sát"
+jobQueue: "Công việc chờ xử lý"
 cpuAndMemory: "CPU và Dung lượng"
+network: "Mạng"
+disk: "Ổ đĩa"
+instanceInfo: "Thông tin máy chủ"
+statistics: "Thống kê"
+clearQueue: "Xóa hàng đợi"
+clearQueueConfirmTitle: "Bạn có chắc muốn xóa hàng đợi?"
+clearQueueConfirmText: "Mọi tút chưa được gửi còn lại trong hàng đợi sẽ không được liên hợp. Thông thường thao tác này không cần thiết."
+clearCachedFiles: "Xóa bộ nhớ đệm"
+clearCachedFilesConfirm: "Bạn có chắc muốn xóa sạch bộ nhớ đệm?"
+blockedInstances: "Máy chủ đã chặn"
+blockedInstancesDescription: "Danh sách những máy chủ bạn muốn chặn. Chúng sẽ không thể giao tiếp với máy chủy này nữa."
+muteAndBlock: "Ẩn và Chặn"
+mutedUsers: "Người đã ẩn"
+blockedUsers: "Người đã chặn"
+noUsers: "Chưa có ai"
+editProfile: "Sửa hồ sơ"
+noteDeleteConfirm: "Bạn có chắc muốn xóa tút này?"
+pinLimitExceeded: "Bạn đã đạt giới hạn số lượng tút có thể ghim"
+intro: "Đã cài đặt Misskey! Xin hãy tạo tài khoản admin."
+done: "Xong"
+processing: "Đang xử lý"
+preview: "Xem trước"
+default: "Mặc định"
+noCustomEmojis: "Không có emoji"
+noJobs: "Không có công việc"
+federating: "Đang liên hợp"
+blocked: "Đã chặn"
+suspended: "Đã vô hiệu hóa"
+all: "Tất cả"
+subscribing: "Đang đăng ký"
+publishing: "Đang đăng"
+notResponding: "Không có phản hồi"
+instanceFollowing: "Đang theo dõi máy chủ"
+instanceFollowers: "Người theo dõi của máy chủ"
+instanceUsers: "Người dùng trên máy chủ này"
+changePassword: "Đổi mật khẩu"
+security: "Bảo mật"
+retypedNotMatch: "Mật khẩu không trùng khớp."
+currentPassword: "Mật khẩu hiện tại"
+newPassword: "Mật khẩu mới"
+newPasswordRetype: "Nhập lại mật khẩu mới"
+attachFile: "Đính kèm tập tin"
+more: "Thêm nữa!"
+featured: "Nổi bật"
+usernameOrUserId: "Tên người dùng hoặc ID"
+noSuchUser: "Không tìm thấy người dùng"
+lookup: "Tìm kiếm"
+announcements: "Thông báo"
+imageUrl: "URL ảnh"
+remove: "Xóa"
+removed: "Đã xóa"
+removeAreYouSure: "Bạn có chắc muốn gỡ \"{x}\"?"
+deleteAreYouSure: "Bạn có chắc muốn xóa \"{x}\"?"
+resetAreYouSure: "Bạn có chắc muốn đặt lại?"
+saved: "Đã lưu"
+messaging: "Trò chuyện"
+upload: "Tải lên"
+keepOriginalUploading: "Giữ hình ảnh gốc"
+keepOriginalUploadingDescription: "Giữ nguyên như hình ảnh được tải lên ban đầu. Nếu tắt, một phiên bản để hiển thị trên web sẽ được tạo khi tải lên."
+fromDrive: "Từ ổ đĩa"
+fromUrl: "Từ URL"
+uploadFromUrl: "Tải lên bằng một URL"
+uploadFromUrlDescription: "URL của tập tin bạn muốn tải lên"
+uploadFromUrlRequested: "Đã yêu cầu tải lên"
+uploadFromUrlMayTakeTime: "Sẽ mất một khoảng thời gian để tải lên xong."
+explore: "Khám phá"
+messageRead: "Đã đọc"
+noMoreHistory: "Không còn gì để đọc"
+startMessaging: "Bắt đầu trò chuyện"
+nUsersRead: "đọc bởi {n}"
+agreeTo: "Tôi đồng ý {0}"
+tos: "Điều khoản dịch vụ"
+start: "Bắt đầu"
+home: "Trang chính"
+remoteUserCaution: "Vì người dùng này ở máy chủ khác, thông tin hiển thị có thể không đầy đủ."
+activity: "Hoạt động"
+images: "Hình ảnh"
+birthday: "Sinh nhật"
+yearsOld: "{age} tuổi"
+registeredDate: "Tham gia"
+location: "Đến từ"
+theme: "Chủ đề"
+themeForLightMode: "Chủ đề dùng trong trong chế độ Sáng"
+themeForDarkMode: "Chủ đề dùng trong chế độ Tối"
+light: "Sáng"
+dark: "Tối"
+lightThemes: "Những chủ đề sáng"
+darkThemes: "Những chủ đề tối"
+syncDeviceDarkMode: "Đồng bộ với thiết bị"
+drive: "Ổ đĩa"
+fileName: "Tên tập tin"
+selectFile: "Chọn tập tin"
+selectFiles: "Chọn nhiều tập tin"
+selectFolder: "Chọn thư mục"
+selectFolders: "Chọn nhiều thư mục"
+renameFile: "Đổi tên tập tin"
+folderName: "Tên thư mục"
+createFolder: "Tạo thư mục"
+renameFolder: "Đổi tên thư mục"
+deleteFolder: "Xóa thư mục"
+addFile: "Thêm tập tin"
+emptyDrive: "Ổ đĩa của bạn trống trơn"
+emptyFolder: "Thư mục trống"
+unableToDelete: "Không thể xóa"
+inputNewFileName: "Nhập tên mới cho tập tin"
+inputNewDescription: "Nhập mô tả mới"
+inputNewFolderName: "Nhập tên mới cho thư mục"
+circularReferenceFolder: "Thư mục đích là một thư mục con của thư mục bạn muốn di chuyển."
+hasChildFilesOrFolders: "Không thể xóa cho đến khi không còn gì trong thư mục."
+copyUrl: "Sao chép URL"
+rename: "Đổi tên"
+avatar: "Ảnh đại diện"
+banner: "Ảnh bìa"
+nsfw: "Nhạy cảm"
+whenServerDisconnected: "Khi mất kết nối tới máy chủ"
+disconnectedFromServer: "Mất kết nối tới máy chủ"
+reload: "Tải lại"
+doNothing: "Bỏ qua"
+reloadConfirm: "Bạn có muốn thử tải lại bảng tin?"
+watch: "Xem"
+unwatch: "Ngừng xem"
+accept: "Đồng ý"
+reject: "Từ chối"
+normal: "Bình thường"
+instanceName: "Tên máy chủ"
+instanceDescription: "Mô tả máy chủ"
+maintainerName: "Đội ngũ vận hành"
+maintainerEmail: "Email đội ngũ"
+tosUrl: "URL Điều khoản dịch vụ"
+thisYear: "Năm"
+thisMonth: "Tháng"
+today: "Hôm nay"
 dayX: "{day}"
+monthX: "{month}"
 yearX: "{year}"
+pages: "Trang"
+integration: "Tương tác"
+connectService: "Kết nối"
+disconnectService: "Ngắt kết nối"
+enableLocalTimeline: "Bật bảng tin máy chủ"
+enableGlobalTimeline: "Bật bảng tin liên hợp"
+disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyền truy cập mọi bảng tin, kể cả khi chúng không được bật."
+registration: "Đăng ký"
+enableRegistration: "Cho phép đăng ký mới"
+invite: "Mời"
+driveCapacityPerLocalAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng"
+driveCapacityPerRemoteAccount: "Dung lượng ổ đĩa tối đa cho mỗi người dùng từ xa"
+inMb: "Tính bằng MB"
+iconUrl: "URL Icon"
+bannerUrl: "URL Ảnh bìa"
+backgroundImageUrl: "URL Ảnh nền"
+basicInfo: "Thông tin cơ bản"
+pinnedUsers: "Những người thú vị"
+pinnedUsersDescription: "Liệt kê mỗi hàng một tên người dùng xuống dòng để ghim trên tab \"Khám phá\"."
+pinnedPages: "Trang đã ghim"
+pinnedPagesDescription: "Liệt kê các trang thú vị để ghim trên máy chủ."
+pinnedClipId: "ID của clip muốn ghim"
+pinnedNotes: "Tút ghim"
+hcaptcha: "hCaptcha"
+enableHcaptcha: "Bật hCaptcha"
+hcaptchaSiteKey: "Khóa của trang"
+hcaptchaSecretKey: "Khóa bí mật"
+recaptcha: "reCAPTCHA"
+enableRecaptcha: "Bật reCAPTCHA"
+recaptchaSiteKey: "Khóa của trang"
+recaptchaSecretKey: "Khóa bí mật"
+avoidMultiCaptchaConfirm: "Dùng nhiều hệ thống Captcha có thể gây nhiễu giữa chúng. Bạn có muốn tắt các hệ thống Captcha khác hiện đang hoạt động không? Nếu bạn muốn chúng tiếp tục được bật, hãy nhấn hủy."
+antennas: "Trạm phát sóng"
+manageAntennas: "Quản lý trạm phát sóng"
+name: "Tên"
+antennaSource: "Nguồn trạm phát sóng"
+antennaKeywords: "Từ khóa để nghe"
+antennaExcludeKeywords: "Từ khóa để lọc ra"
+antennaKeywordsDescription: "Phân cách bằng dấu cách cho điều kiện AND hoặc bằng xuống dòng cho điều kiện OR."
+notifyAntenna: "Thông báo có tút mới"
+withFileAntenna: "Chỉ những tút có media"
+enableServiceworker: "Bật ServiceWorker"
+antennaUsersDescription: "Liệt kê mỗi hàng một tên người dùng"
+caseSensitive: "Trường hợp nhạy cảm"
+withReplies: "Bao gồm lượt trả lời"
+connectedTo: "Những tài khoản sau đã kết nối"
+notesAndReplies: "Tút kèm trả lời"
+withFiles: "Media"
+silence: "Ẩn"
+silenceConfirm: "Bạn có chắc muốn ẩn người này?"
+unsilence: "Bỏ ẩn"
+unsilenceConfirm: "Bạn có chắc muốn bỏ ẩn người này?"
+popularUsers: "Những người nổi tiếng"
+recentlyUpdatedUsers: "Hoạt động gần đây"
+recentlyRegisteredUsers: "Mới tham gia"
+recentlyDiscoveredUsers: "Mới khám phá"
+exploreUsersCount: "Có {count} người"
+exploreFediverse: "Khám phá Fediverse"
+popularTags: "Hashtag thông dụng"
+userList: "Danh sách"
+about: "Giới thiệu"
 aboutMisskey: "Về Misskey"
+administrator: "Quản trị viên"
+token: "Token"
+twoStepAuthentication: "Xác minh 2 bước"
+moderator: "Kiểm duyệt viên"
+nUsersMentioned: "Dùng bởi {n} người"
+securityKey: "Khóa bảo mật"
+securityKeyName: "Tên khoá"
+registerSecurityKey: "Đăng ký khóa bảo mật"
+lastUsed: "Dùng lần cuối"
+unregister: "Hủy đăng ký"
+passwordLessLogin: "Đăng nhập không mật khẩu"
+resetPassword: "Đặt lại mật khẩu"
+newPasswordIs: "Mật khẩu mới là \"{password}\""
+reduceUiAnimation: "Giảm chuyển động UI"
+share: "Chia sẻ"
+notFound: "Không tìm thấy"
+notFoundDescription: "Không tìm thấy trang nào tương ứng với URL này."
+uploadFolder: "Thư mục tải lên mặc định"
+cacheClear: "Xóa bộ nhớ đệm"
+markAsReadAllNotifications: "Đánh dấu tất cả các thông báo là đã đọc"
+markAsReadAllUnreadNotes: "Đánh dấu tất cả các tút là đã đọc"
+markAsReadAllTalkMessages: "Đánh dấu tất cả các tin nhắn là đã đọc"
+help: "Trợ giúp"
+inputMessageHere: "Nhập nội dung tin nhắn"
+close: "Đóng"
+group: "Nhóm"
+groups: "Các nhóm"
+createGroup: "Tạo nhóm"
+ownedGroups: "Nhóm tôi quản lý"
+joinedGroups: "Nhóm tôi tham gia"
+invites: "Mời"
+groupName: "Tên nhóm"
+members: "Thành viên"
+transfer: "Chuyển giao"
+messagingWithUser: "Nhắn riêng"
+messagingWithGroup: "Chat nhóm"
+title: "Tựa đề"
+text: "Nội dung"
+enable: "Bật"
+next: "Kế tiếp"
+retype: "Nhập lại"
+noteOf: "Tút của {user}"
+inviteToGroup: "Mời vào nhóm"
+quoteAttached: "Trích dẫn"
+quoteQuestion: "Trích dẫn lại?"
+noMessagesYet: "Chưa có tin nhắn"
+newMessageExists: "Bạn có tin nhắn mới"
+onlyOneFileCanBeAttached: "Bạn chỉ có thể đính kèm một tập tin"
+signinRequired: "Vui lòng đăng nhập"
+invitations: "Mời"
+invitationCode: "Mã mời"
+checking: "Đang kiểm tra..."
+available: "Khả dụng"
+unavailable: "Không khả dụng"
+usernameInvalidFormat: "Bạn có thể dùng viết hoa/viết thường, chữ số, và dấu gạch dưới."
+tooShort: "Quá ngắn"
+tooLong: "Quá dài"
+weakPassword: "Mật khẩu yếu"
+normalPassword: "Mật khẩu tạm được"
+strongPassword: "Mật khẩu mạnh"
+passwordMatched: "Trùng khớp"
+passwordNotMatched: "Không trùng khớp"
+signinWith: "Đăng nhập bằng {x}"
+signinFailed: "Không thể đăng nhập. Vui lòng kiểm tra tên người dùng và mật khẩu của bạn."
+tapSecurityKey: "Nhấn mã bảo mật của bạn"
+or: "Hoặc"
+language: "Ngôn ngữ"
+uiLanguage: "Ngôn ngữ giao diện"
+groupInvited: "Bạn đã được mời tham gia nhóm"
+aboutX: "Giới thiệu {x}"
+useOsNativeEmojis: "Dùng emoji hệ thống"
+disableDrawer: "Không dùng menu thanh bên"
+youHaveNoGroups: "Không có nhóm nào"
+joinOrCreateGroup: "Tham gia hoặc tạo một nhóm mới."
+noHistory: "Không có dữ liệu"
+signinHistory: "Lịch sử đăng nhập"
+disableAnimatedMfm: "Tắt MFM với chuyển động"
+doing: "Đang xử lý..."
+category: "Phân loại"
+tags: "Thẻ"
+docSource: "Nguồn tài liệu"
+createAccount: "Tạo tài khoản"
+existingAccount: "Tài khoản hiện có"
+regenerate: "Tạo lại"
+fontSize: "Cỡ chữ"
+noFollowRequests: "Bạn không có yêu cầu theo dõi nào"
+openImageInNewTab: "Mở ảnh trong tab mới"
+dashboard: "Trang chính"
+local: "Máy chủ này"
+remote: "Máy chủ khác"
+total: "Tổng cộng"
+weekOverWeekChanges: "Thay đổi tuần rồi"
+dayOverDayChanges: "Thay đổi hôm qua"
+appearance: "Giao diện"
+clientSettings: "Cài đặt Client"
+accountSettings: "Cài đặt tài khoản"
+promotion: "Quảng cáo"
+promote: "Quảng cáo"
+numberOfDays: "Số ngày"
+hideThisNote: "Ẩn tút này"
+showFeaturedNotesInTimeline: "Hiện tút nổi bật trong bảng tin"
+objectStorage: "Đối tượng lưu trữ"
+useObjectStorage: "Dùng đối tượng lưu trữ"
+objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "URL được sử dụng làm tham khảo. Chỉ định URL của CDN hoặc Proxy của bạn nếu bạn đang sử dụng. Với S3 dùng 'https://<bucket>.s3.amazonaws.com', còn GCS hoặc dịch vụ tương tự dùng 'https://storage.googleapis.com/<bucket>', etc."
+objectStorageBucket: "Bucket"
+objectStorageBucketDesc: "Nhập tên bucket dùng ở nhà cung cấp của bạn."
+objectStoragePrefix: "Tiền tố"
+objectStoragePrefixDesc: "Các tập tin sẽ được lưu trữ trong các thư mục có tiền tố này."
+objectStorageEndpoint: "Đầu cuối"
+objectStorageEndpointDesc: "Để trống nếu bạn đang dùng AWS S3, nếu không thì chỉ định đầu cuối là '<host>' hoặc '<host>:<port>', tùy thuộc vào nhà cung cấp dịch vụ."
+objectStorageRegion: "Khu vực"
+objectStorageRegionDesc: "Nhập một khu vực cụ thể như 'xx-east-1'. Nếu nhà cung cấp dịch vụ của bạn không phân biệt giữa các khu vực, hãy để trống hoặc nhập 'us-east-1'."
+objectStorageUseSSL: "Dùng SSL"
+objectStorageUseSSLDesc: "Tắt nếu bạn không dùng HTTPS để kết nối API"
+objectStorageUseProxy: "Kết nối thông qua Proxy"
+objectStorageUseProxyDesc: "Tắt nếu bạn không dùng Proxy để kết nối API"
+objectStorageSetPublicRead: "Đặt \"public-read\" khi tải lên"
+serverLogs: "Nhật ký máy chủ"
+deleteAll: "Xóa tất cả"
+showFixedPostForm: "Hiện khung soạn tút ở phía trên bảng tin"
+newNoteRecived: "Đã nhận tút mới"
+sounds: "Âm thanh"
+listen: "Nghe"
+none: "Không"
+showInPage: "Hiện trong trang"
+popout: "Pop-out"
+volume: "Âm lượng"
+masterVolume: "Âm thanh chung"
+details: "Chi tiết"
+chooseEmoji: "Chọn emoji"
+unableToProcess: "Không thể hoàn tất hành động"
+recentUsed: "Sử dụng gần đây"
+install: "Cài đặt"
+uninstall: "Gỡ bỏ"
+installedApps: "Ứng dụng đã cài đặt"
+nothing: "Không có gì ở đây"
+installedDate: "Cho phép vào"
+lastUsedDate: "Dùng gần nhất"
+state: "Trạng thái"
+sort: "Sắp xếp"
+ascendingOrder: "Tăng dần"
+descendingOrder: "Giảm dần"
+scratchpad: "Scratchpad"
+scratchpadDescription: "Scratchpad cung cấp môi trường cho các thử nghiệm AiScript. Bạn có thể viết, thực thi và kiểm tra kết quả tương tác với Misskey trong đó."
+output: "Nguồn ra"
+script: "Kịch bản"
+disablePagesScript: "Tắt AiScript trên Trang"
+updateRemoteUser: "Cập nhật thông tin người dùng ở máy chủ khác"
+deleteAllFiles: "Xóa toàn bộ tập tin"
+deleteAllFilesConfirm: "Bạn có chắc xóa toàn bộ tập tin?"
+removeAllFollowing: "Ngưng theo dõi tất cả mọi người"
+removeAllFollowingDescription: "Thực hiện điều này sẽ ngưng theo dõi tất cả các tài khoản khỏi {host}. Chỉ thực hiện điều này nếu máy chủ không còn tồn tại."
+userSuspended: "Người này đã bị vô hiệu hóa."
+userSilenced: "Người này đã bị ẩn"
+yourAccountSuspendedTitle: "Tài khoản bị vô hiệu hóa"
+yourAccountSuspendedDescription: "Tài khoản này đã bị vô hiệu hóa do vi phạm quy tắc máy chủ hoặc điều tương tự. Liên hệ với quản trị viên nếu bạn muốn biết lý do chi tiết hơn. Vui lòng không tạo tài khoản mới."
+menu: "Menu"
+divider: "Phân chia"
+addItem: "Thêm mục"
+relays: "Chuyển tiếp"
+addRelay: "Thêm chuyển tiếp"
+inboxUrl: "URL Hộp thư đến"
+addedRelays: "Đã thêm các chuyển tiếp"
+serviceworkerInfo: "Phải được bật cho thông báo đẩy."
+deletedNote: "Tút đã bị xóa"
+invisibleNote: "Tút ẩn"
+enableInfiniteScroll: "Tự động tải tút mới"
+visibility: "Hiển thị"
+poll: "Bình chọn"
+useCw: "Ẩn nội dung"
+enablePlayer: "Mở trình phát video"
+disablePlayer: "Đóng trình phát video"
+expandTweet: "Mở rộng tweet"
+themeEditor: "Công cụ thiết kế theme"
+description: "Mô tả"
+describeFile: "Thêm mô tả"
+enterFileDescription: "Nhập mô tả"
+author: "Tác giả"
+leaveConfirm: "Có những thay đổi chưa được lưu. Bạn có muốn bỏ chúng không?"
+manage: "Quản lý"
+plugins: "Plugin"
+deck: "Deck"
+undeck: "Bỏ Deck"
+useBlurEffectForModal: "Sử dụng hiệu ứng mờ cho các hộp thoại"
+useFullReactionPicker: "Dùng bộ chọn biểu cảm cỡ lớn"
+width: "Chiều rộng"
+height: "Chiều cao"
+large: "Lớn"
+medium: "Vừa"
+small: "Nhỏ"
+generateAccessToken: "Tạo mã truy cập"
+permission: "Cho phép "
+enableAll: "Bật toàn bộ"
+disableAll: "Tắt toàn bộ"
+tokenRequested: "Cấp quyền truy cập vào tài khoản"
+pluginTokenRequestedDescription: "Plugin này sẽ có thể sử dụng các quyền được đặt ở đây."
+notificationType: "Loại thông báo"
+edit: "Sửa"
+useStarForReactionFallback: "Dùng ★ nếu emoji biểu cảm không có"
+emailServer: "Email máy chủ"
+enableEmail: "Bật phân phối email"
+emailConfigInfo: "Được dùng để xác minh email của bạn lúc đăng ký hoặc nếu bạn quên mật khẩu của mình"
+email: "Email"
+emailAddress: "Địa chỉ email"
+smtpConfig: "Cấu hình máy chủ SMTP"
+smtpHost: "Host"
+smtpPort: "Cổng"
 smtpUser: "Tên người dùng"
 smtpPass: "Mật khẩu"
+emptyToDisableSmtpAuth: "Để trống tên người dùng và mật khẩu để tắt xác thực SMTP"
+smtpSecure: "Dùng SSL/TLS ngầm định cho các kết nối SMTP"
+smtpSecureInfo: "Tắt cái này nếu dùng STARTTLS"
+testEmail: "Kiểm tra vận chuyển email"
+wordMute: "Ẩn chữ"
+regexpError: "Lỗi biểu thức"
+regexpErrorDescription: "Xảy ra lỗi biểu thức ở dòng {line} của {tab} chữ ẩn:"
+instanceMute: "Những máy chủ ẩn"
+userSaysSomething: "{name} nói gì đó"
+makeActive: "Kích hoạt"
+display: "Hiển thị"
+copy: "Sao chép"
+metrics: "Số liệu"
+overview: "Tổng quan"
+logs: "Nhật ký"
+delayed: "Độ trễ"
+database: "Cơ sở dữ liệu"
+channel: "Kênh"
+create: "Tạo"
+notificationSetting: "Cài đặt thông báo"
+notificationSettingDesc: "Chọn loại thông báo bạn muốn hiển thị."
+useGlobalSetting: "Dùng thiết lập chung"
+useGlobalSettingDesc: "Nếu được bật, cài đặt thông báo của bạn sẽ được áp dụng. Nếu bị tắt, có thể thực hiện các thiết lập riêng lẻ."
+other: "Khác"
+regenerateLoginToken: "Tạo lại mã đăng nhập"
+regenerateLoginTokenDescription: "Tạo lại mã nội bộ có thể dùng để đăng nhập. Thông thường hành động này là không cần thiết. Nếu được tạo lại, tất cả các thiết bị sẽ bị đăng xuất."
+setMultipleBySeparatingWithSpace: "Tách nhiều mục nhập bằng dấu cách."
+fileIdOrUrl: "ID tập tin hoặc URL"
+behavior: "Thao tác"
+sample: "Ví dụ"
+abuseReports: "Lượt báo cáo"
+reportAbuse: "Báo cáo"
 reportAbuseOf: "Báo cáo {name}"
+fillAbuseReportDescription: "Vui lòng điền thông tin chi tiết về báo cáo này. Nếu đó là về một tút cụ thể, hãy kèm theo URL của tút."
+abuseReported: "Báo cáo đã được gửi. Cảm ơn bạn nhiều."
+reporter: "Người báo cáo"
+reporteeOrigin: "Bị báo cáo"
+reporterOrigin: "Máy chủ người báo cáo"
+forwardReport: "Chuyển tiếp báo cáo cho máy chủ từ xa"
+forwardReportIsAnonymous: "Thay vì tài khoản của bạn, một tài khoản hệ thống ẩn danh sẽ được hiển thị dưới dạng người báo cáo ở máy chủ từ xa."
+send: "Gửi"
+abuseMarkAsResolved: "Đánh dấu đã xử lý"
+openInNewTab: "Mở trong tab mới"
+openInSideView: "Mở trong thanh bên"
+defaultNavigationBehaviour: "Thao tác điều hướng mặc định"
+editTheseSettingsMayBreakAccount: "Việc chỉnh sửa các cài đặt này có thể làm hỏng tài khoản của bạn."
+instanceTicker: "Thông tin máy chủ của tút"
+waitingFor: "Đang đợi {x}"
+random: "Ngẫu nhiên"
+system: "Hệ thống"
+switchUi: "Chuyển đổi giao diện người dùng"
+desktop: "Desktop"
+clip: "Ghim"
+createNew: "Tạo mới"
+optional: "Không bắt buộc"
+createNewClip: "Tạo một ghim mới"
+public: "Công khai"
+i18nInfo: "Misskey đang được các tình nguyện viên dịch sang nhiều thứ tiếng khác nhau. Bạn có thể hỗ trợ tại {link}."
+manageAccessTokens: "Tạo mã truy cập"
+accountInfo: "Thông tin tài khoản"
+notesCount: "Số lượng tút"
+repliesCount: "Số lượt trả lời đã gửi"
+renotesCount: "Số lượt đăng lại đã gửi"
+repliedCount: "Số lượt trả lời đã nhận"
 renotedCount: "Lượt chia sẻ"
+followingCount: "Số lượng người tôi theo dõi"
+followersCount: "Số lượng người theo dõi tôi"
+sentReactionsCount: "Số lượng biểu cảm đã gửi"
+receivedReactionsCount: "Số lượng biểu cảm đã nhận"
+pollVotesCount: "Số lượng bình chọn đã gửi"
+pollVotedCount: "Số lượng bình chọn đã nhận"
+yes: "Đồng ý"
+no: "Từ chối"
+driveFilesCount: "Số tập tin trong Ổ đĩa"
+driveUsage: "Dung lượng ổ đĩa"
+noCrawle: "Từ chối lập chỉ mục"
+noCrawleDescription: "Không cho công cụ tìm kiếm lập chỉ mục trang hồ sơ, tút, Trang, etc."
+lockedAccountInfo: "Ghi chú của bạn sẽ hiển thị với bất kỳ ai, trừ khi bạn đặt chế độ hiển thị tút của mình thành \"Chỉ người theo dõi\"."
+alwaysMarkSensitive: "Luôn đánh dấu NSFW"
+loadRawImages: "Tải ảnh gốc thay vì ảnh thu nhỏ"
+disableShowingAnimatedImages: "Không phát ảnh động"
+verificationEmailSent: "Một email xác minh đã được gửi. Vui lòng nhấn vào liên kết đính kèm để hoàn tất xác minh."
+notSet: "Chưa đặt"
+emailVerified: "Email đã được xác minh"
+noteFavoritesCount: "Số lượng tút yêu thích"
+pageLikesCount: "Số lượng trang đã thích"
+pageLikedCount: "Số lượng thích trang đã nhận"
+contact: "Liên hệ"
+useSystemFont: "Dùng phông chữ mặc định của hệ thống"
+clips: "Ghim"
+experimentalFeatures: "Tính năng thử nghiệm"
+developer: "Nhà phát triển"
+makeExplorable: "Không hiện tôi trong \"Khám phá\""
+makeExplorableDescription: "Nếu bạn tắt, tài khoản của bạn sẽ không hiện trong mục \"Khám phá\"."
+showGapBetweenNotesInTimeline: "Hiện dải phân cách giữa các tút trên bảng tin"
+duplicate: "Tạo bản sao"
+left: "Bên trái"
+center: "Giữa"
+wide: "Rộng"
+narrow: "Thu hẹp"
+reloadToApplySetting: "Cài đặt này sẽ chỉ áp dụng sau khi tải lại trang. Tải lại ngay bây giờ?"
+needReloadToApply: "Cần tải lại để điều này được áp dụng."
+showTitlebar: "Hiện thanh tựa đề"
+clearCache: "Xóa bộ nhớ đệm"
+onlineUsersCount: "{n} người đang online"
+nUsers: "{n} Người"
+nNotes: "{n} Tút"
+sendErrorReports: "Báo lỗi"
+sendErrorReportsDescription: "Khi được bật, thông tin chi tiết về lỗi sẽ được chia sẻ với Misskey khi xảy ra sự cố, giúp nâng cao chất lượng của Misskey.\nBao gồm thông tin như phiên bản hệ điều hành của bạn, trình duyệt bạn đang sử dụng, hoạt động của bạn trong Misskey, v.v."
+myTheme: "Theme của tôi"
+backgroundColor: "Màu nền"
+accentColor: "Màu phụ"
+textColor: "Màu chữ"
+saveAs: "Lưu thành"
+advanced: "Nâng cao"
+value: "Giá trị"
+createdAt: "Ngày tạo"
+updatedAt: "Cập nhật lúc"
+saveConfirm: "Lưu thay đổi?"
+deleteConfirm: "Bạn có muốn xóa không?"
+invalidValue: "Giá trị không hợp lệ."
+registry: "Registry"
+closeAccount: "Đóng tài khoản"
+currentVersion: "Phiên bản hiện tại"
+latestVersion: "Phiên bản mới nhất"
+youAreRunningUpToDateClient: "Bạn đang sử dụng phiên bản mới nhất."
+newVersionOfClientAvailable: "Có phiên bản mới cho bạn cập nhật."
+usageAmount: "Sử dụng"
+capacity: "Sức chứa"
+inUse: "Đã dùng"
+editCode: "Chỉnh sửa mã"
+apply: "Áp dụng"
+receiveAnnouncementFromInstance: "Nhận thông báo từ máy chủ này"
+emailNotification: "Thông báo email"
+publish: "Đăng"
+inChannelSearch: "Tìm trong kênh"
+useReactionPickerForContextMenu: "Nhấn chuột phải để mở bộ chọn biểu cảm"
+typingUsers: "{users} đang nhập…"
+jumpToSpecifiedDate: "Đến một ngày cụ thể"
+showingPastTimeline: "Hiện đang hiển thị dòng thời gian cũ"
+clear: "Hoàn lại"
+markAllAsRead: "Đánh dấu tất cả đã đọc"
+goBack: "Quay lại"
+unlikeConfirm: "Bạn có chắc muốn bỏ thích ?"
+fullView: "Kích thước đầy đủ"
+quitFullView: "Thoát toàn màn hình"
+addDescription: "Thêm mô tả"
+userPagePinTip: "Bạn có thể hiển thị các tút ở đây bằng cách chọn \"Ghim vào hồ sơ\" từ menu của mỗi tút."
+notSpecifiedMentionWarning: "Tút này có đề cập đến những người không mong muốn"
+info: "Giới thiệu"
+userInfo: "Thông tin người dùng"
+unknown: "Chưa biết"
+onlineStatus: "Trạng thái"
+hideOnlineStatus: "Ẩn trạng thái online"
+hideOnlineStatusDescription: "Ẩn trạng thái online của bạn làm giảm sự tiện lợi của một số tính năng như tìm kiếm."
+online: "Online"
+active: "Hoạt động"
+offline: "Offline"
+notRecommended: "Không đề xuất"
+botProtection: "Bảo vệ Bot"
+instanceBlocking: "Máy chủ đã chặn"
+selectAccount: "Chọn một tài khoản"
+switchAccount: "Chuyển tài khoản"
+enabled: "Đã bật"
+disabled: "Đã tắt"
+quickAction: "Thao tác nhanh"
+user: "Người dùng"
+administration: "Quản lý"
+accounts: "Tài khoản của bạn"
+switch: "Chuyển đổi"
+noMaintainerInformationWarning: "Chưa thiết lập thông tin vận hành."
+noBotProtectionWarning: "Bảo vệ Bot chưa thiết lập."
+configure: "Thiết lập"
+postToGallery: "Tạo tút có ảnh"
+gallery: "Thư viện ảnh"
+recentPosts: "Tút gần đây"
+popularPosts: "Tút được xem nhiều nhất"
+shareWithNote: "Chia sẻ kèm với tút"
+ads: "Quảng cáo"
+expiration: "Thời hạn"
+memo: "Lưu ý"
+priority: "Ưu tiên"
+high: "Cao"
+middle: "Vừa"
+low: "Thấp"
+emailNotConfiguredWarning: "Chưa đặt địa chỉ email."
+ratio: "Tỷ lệ"
+previewNoteText: "Hiện xem trước"
+customCss: "Tùy chỉnh CSS"
+customCssWarn: "Chỉ sử dụng những cài đặt này nếu bạn biết rõ về nó. Việc nhập các giá trị không đúng có thể khiến máy chủ hoạt động không bình thường."
+global: "Toàn cầu"
+squareAvatars: "Ảnh đại diện vuông"
+sent: "Gửi"
+received: "Đã nhận"
+searchResult: "Kết quả tìm kiếm"
+hashtags: "Hashtag"
+troubleshooting: "Khắc phục sự cố"
+useBlurEffect: "Dùng hiệu ứng làm mờ trong giao diện"
+learnMore: "Tìm hiểu thêm"
+misskeyUpdated: "Misskey vừa được cập nhật!"
+whatIsNew: "Hiện những thay đổi"
+translate: "Dịch"
 translatedFrom: "Dịch từ {x}"
+accountDeletionInProgress: "Đang xử lý việc xóa tài khoản"
+usernameInfo: "Bạn có thể sử dụng chữ cái (a ~ z, A ~ Z), chữ số (0 ~ 9) hoặc dấu gạch dưới (_). Tên người dùng không thể thay đổi sau này."
+aiChanMode: "Chế độ Ai"
+keepCw: "Giữ cảnh báo nội dung"
+pubSub: "Tài khoản Chính/Phụ"
+lastCommunication: "Lần giao tiếp cuối"
+resolved: "Đã xử lý"
+unresolved: "Chờ xử lý"
+breakFollow: "Xóa người theo dõi"
+itsOn: "Đã bật"
+itsOff: "Đã tắt"
+emailRequiredForSignup: "Yêu cầu địa chỉ email khi đăng ký"
+unread: "Chưa đọc"
+filter: "Bộ lọc"
+controlPanel: "Bảng điều khiển"
+manageAccounts: "Quản lý tài khoản"
+makeReactionsPublic: "Đặt lịch sử biểu cảm công khai"
+makeReactionsPublicDescription: "Điều này sẽ hiển thị công khai danh sách tất cả các biểu cảm trước đây của bạn."
+classic: "Cổ điển"
+muteThread: "Không quan tâm nữa"
+unmuteThread: "Quan tâm tút này"
+ffVisibility: "Hiển thị Theo dõi/Người theo dõi"
+ffVisibilityDescription: "Quyết định ai có thể xem những người bạn theo dõi và những người theo dõi bạn."
+continueThread: "Tiếp tục xem chuỗi tút"
+deleteAccountConfirm: "Điều này sẽ khiến tài khoản bị xóa vĩnh viễn. Vẫn tiếp tục?"
+incorrectPassword: "Sai mật khẩu."
+voteConfirm: "Xác nhận bình chọn \"{choice}\"?"
+hide: "Ẩn"
+leaveGroup: "Rời khỏi nhóm"
+leaveGroupConfirm: "Bạn có chắc muốn rời khỏi nhóm \"{name}\"?"
+useDrawerReactionPickerForMobile: "Hiện bộ chọn biểu cảm dạng xổ ra trên điện thoại"
+welcomeBackWithName: "Chào mừng trở lại, {name}"
+clickToFinishEmailVerification: "Vui lòng nhấn [{ok}] để hoàn tất việc đăng ký."
+overridedDeviceKind: "Loại thiết bị"
+smartphone: "Điện thoại"
+tablet: "Máy tính bảng"
+auto: "Tự động"
+themeColor: "Màu theme"
+size: "Kích thước"
+numberOfColumn: "Số lượng cột"
 searchByGoogle: "Google"
+instanceDefaultLightTheme: "Theme máy chủ Sáng-Rộng"
+instanceDefaultDarkTheme: "Theme máy chủ Tối-Rộng"
+instanceDefaultThemeDescription: "Nhập mã theme trong định dạng đối tượng."
+mutePeriod: "Thời hạn ẩn"
+indefinitely: "Vĩnh viễn"
+tenMinutes: "10 phút"
+oneHour: "1 giờ"
+oneDay: "1 ngày"
+oneWeek: "1 tuần"
+reflectMayTakeTime: "Có thể mất một thời gian để điều này được áp dụng."
+failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản"
+_emailUnavailable:
+  used: "Địa chỉ email đã được sử dụng"
+  format: "Địa chỉ email không hợp lệ"
+  disposable: "Cấm sử dụng địa chỉ email dùng một lần"
+  mx: "Máy chủ email không hợp lệ"
+  smtp: "Máy chủ email không phản hồi"
+_ffVisibility:
+  public: "Đăng"
+  followers: "Chỉ người theo dõi mới xem được"
+  private: "Riêng tư"
+_signup:
+  almostThere: "Gần xong rồi"
+  emailAddressInfo: "Hãy điền địa chỉ email của bạn. Nó sẽ không được công khai."
+  emailSent: "Một email xác minh đã được gửi đến địa chỉ email ({email}) của bạn. Vui lòng nhấn vào liên kết trong đó để hoàn tất việc tạo tài khoản."
+_accountDelete:
+  accountDelete: "Xóa tài khoản"
+  mayTakeTime: "Vì xóa tài khoản là một quá trình tốn nhiều tài nguyên nên có thể mất một khoảng thời gian để hoàn thành, tùy thuộc vào lượng nội dung bạn đã tạo và số lượng tập tin bạn đã tải lên."
+  sendEmail: "Sau khi hoàn tất việc xóa tài khoản, một email sẽ được gửi đến địa chỉ email đã đăng ký tài khoản này."
+  requestAccountDelete: "Yêu cầu xóa tài khoản"
+  started: "Đang bắt đầu xóa tài khoản."
+  inProgress: "Đang xóa dần tài khoản."
+_ad:
+  back: "Quay lại"
+  reduceFrequencyOfThisAd: "Hiện ít lại"
+_forgotPassword:
+  enterEmail: "Nhập địa chỉ email bạn đã sử dụng để đăng ký. Một liên kết mà bạn có thể đặt lại mật khẩu của mình sau đó sẽ được gửi đến nó."
+  ifNoEmail: "Nếu bạn không sử dụng email lúc đăng ký, vui lòng liên hệ với quản trị viên."
+  contactAdmin: "Máy chủ này không hỗ trợ sử dụng địa chỉ email, vui lòng liên hệ với quản trị viên để đặt lại mật khẩu của bạn."
+_gallery:
+  my: "Kho Ảnh"
+  liked: "Tút Đã Thích"
+  like: "Thích"
+  unlike: "Bỏ thích"
+_email:
+  _follow:
+    title: "đã theo dõi bạn"
+  _receiveFollowRequest:
+    title: "Chấp nhận yêu cầu theo dõi"
+_plugin:
+  install: "Cài đặt tiện ích"
+  installWarn: "Vui lòng không cài đặt những tiện ích đáng ngờ."
+  manage: "Quản lý plugin"
+_registry:
+  scope: "Phạm vi"
+  key: "Mã"
+  keys: "Các mã"
+  domain: "Tên miền"
+  createKey: "Tạo mã"
+_aboutMisskey:
+  about: "Misskey là phần mềm mã nguồn mở được phát triển bởi syuilo từ năm 2014."
+  contributors: "Những người đóng góp nổi bật"
+  allContributors: "Toàn bộ người đóng góp"
+  source: "Mã nguồn"
+  translation: "Dịch Misskey"
+  donate: "Ủng hộ Misskey"
+  morePatrons: "Chúng tôi cũng trân trọng sự hỗ trợ của nhiều người đóng góp khác không được liệt kê ở đây. Cảm ơn! 🥰"
+  patrons: "Người ủng hộ"
+_nsfw:
+  respect: "Ẩn nội dung NSFW"
+  ignore: "Hiện nội dung NSFW"
+  force: "Ẩn mọi media"
 _mfm:
+  cheatSheet: "MFM Cheatsheet"
+  intro: "MFM là ngôn ngữ phát triển độc quyền của Misskey có thể được sử dụng ở nhiều nơi. Tại đây bạn có thể xem danh sách tất cả các cú pháp MFM có sẵn."
+  dummy: "Misskey mở rộng thế giới Fediverse"
+  mention: "Nhắc đến"
+  mentionDescription: "Bạn có thể nhắc đến ai đó bằng cách sử dụng @tên người dùng."
+  hashtag: "Hashtag"
+  hashtagDescription: "Bạn có thể tạo một hashtag bằng #chữ hoặc #số."
+  url: "URL"
+  urlDescription: "Những URL có thể hiển thị."
+  link: "Đường dẫn"
+  linkDescription: "Các phần cụ thể của văn bản có thể được hiển thị dưới dạng URL."
+  bold: "In đậm"
+  boldDescription: "Nổi bật các chữ cái bằng cách làm chúng dày hơn."
+  small: "Nhỏ"
+  smallDescription: "Hiển thị nội dung nhỏ và mỏng."
+  center: "Giữa"
+  centerDescription: "Hiển thị nội dung căn giữa."
+  inlineCode: "Mã (Trong dòng)"
+  inlineCodeDescription: "Hiển thị tô sáng cú pháp trong dòng cho mã (chương trình)."
+  blockCode: "Mã (Khối)"
+  blockCodeDescription: "Hiển thị tô sáng cú pháp cho mã nhiều dòng (chương trình) trong một khối."
+  inlineMath: "Toán học (Trong dòng)"
+  inlineMathDescription: "Hiển thị công thức toán (KaTeX) trong dòng"
+  blockMath: "Toán học (Khối)"
+  blockMathDescription: "Hiển thị công thức toán học nhiều dòng (KaTeX) trong một khối"
+  quote: "Trích dẫn"
+  quoteDescription: "Hiển thị nội dung dạng lời trích dạng."
+  emoji: "Tùy chỉnh emoji"
+  emojiDescription: "Hiển thị emoji với cú pháp :tên emoji:"
   search: "Tìm kiếm"
+  searchDescription: "Hiển thị hộp tìm kiếm với văn bản được nhập trước."
+  flip: "Lật"
+  flipDescription: "Lật nội dung theo chiều ngang hoặc chiều dọc."
+  jelly: "Chuyển động (Thạch rau câu)"
+  jellyDescription: "Cho phép nội dung chuyển động giống như thạch rau câu."
+  tada: "Chuyển động (Tada)"
+  tadaDescription: "Cho phép nội dung chuyển động kiểu \"Tada!\"."
+  jump: "Chuyển động (Nhảy múa)"
+  jumpDescription: "Cho phép nội dung chuyển động nhảy nhót."
+  bounce: "Chuyển động (Cà tưng)"
+  bounceDescription: "Cho phép nội dung chuyển động cà tưng."
+  shake: "Chuyển động (Rung)"
+  shakeDescription: "Cho phép nội dung chuyển động rung lắc."
+  twitch: "Chuyển động (Co rút)"
+  twitchDescription: "Cho phép nội dung chuyển động co rút."
+  spin: "Chuyển động (Xoay tít)"
+  spinDescription: "Cho phép nội dung chuyển động xoay tít."
+  x2: "Lớn"
+  x2Description: "Hiển thị nội dung cỡ lớn hơn."
+  x3: "Rất lớn"
+  x3Description: "Hiển thị nội dung cỡ lớn hơn nữa."
+  x4: "Khổng lồ"
+  x4Description: "Hiển thị nội dung cỡ khổng lồ."
+  blur: "Làm mờ"
+  blurDescription: "Làm mờ nội dung. Nó sẽ được hiển thị rõ ràng khi di chuột qua."
+  font: "Phông chữ"
+  fontDescription: "Chọn phông chữ để hiển thị nội dung."
+  rainbow: "Cầu vồng"
+  rainbowDescription: "Làm cho nội dung hiển thị với màu sắc cầu vồng."
+  sparkle: "Lấp lánh"
+  sparkleDescription: "Làm cho nội dung hiệu ứng hạt lấp lánh."
+  rotate: "Xoay"
+  rotateDescription: "Xoay nội dung theo một góc cụ thể."
+_instanceTicker:
+  none: "Không hiển thị"
+  remote: "Hiện cho người dùng từ máy chủ khác"
+  always: "Luôn hiện"
+_serverDisconnectedBehavior:
+  reload: "Tự động tải lại"
+  dialog: "Hiện hộp thoại cảnh báo"
+  quiet: "Hiển thị cảnh báo không phô trương"
+_channel:
+  create: "Tạo kênh"
+  edit: "Chỉnh sửa kênh"
+  setBanner: "Đặt ảnh bìa"
+  removeBanner: "Xóa ảnh bìa"
+  featured: "Xu hướng"
+  owned: "Do tôi quản lý"
+  following: "Đang theo dõi"
+  usersCount: "{n} Thành viên"
+  notesCount: "{n} Tút"
+_menuDisplay:
+  sideFull: "Thanh bên"
+  sideIcon: "Thanh bên (Biểu tượng)"
+  top: "Trên cùng"
+  hide: "Ẩn"
+_wordMute:
+  muteWords: "Ẩn từ ngữ"
+  muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
+  muteWordsDescription2: "Bao quanh các từ khóa bằng dấu gạch chéo để sử dụng cụm từ thông dụng."
+  softDescription: "Ẩn các tút phù hợp điều kiện đã đặt khỏi bảng tin."
+  hardDescription: "Ngăn các tút đáp ứng các điều kiện đã đặt xuất hiện trên bảng tin. Lưu ý, những tút này sẽ không được thêm vào bảng tin ngay cả khi các điều kiện được thay đổi."
+  soft: "Yếu"
+  hard: "Mạnh"
+  mutedNotes: "Những tút đã ẩn"
+_instanceMute:
+  instanceMuteDescription: "Thao tác này sẽ ẩn mọi tút/lượt đăng lại từ các máy chủ được liệt kê, bao gồm cả những tút  dạng trả lời từ máy chủ bị ẩn."
+  instanceMuteDescription2: "Tách bằng cách xuống dòng"
+  title: "Ẩn tút từ những máy chủ đã liệt kê."
+  heading: "Danh sách những máy chủ bị ẩn"
 _theme:
+  explore: "Khám phá theme"
+  install: "Cài đặt theme"
+  manage: "Quản lý theme"
+  code: "Mã theme"
+  description: "Mô tả"
   installed: "{name} đã được cài đặt"
+  installedThemes: "Theme đã cài đặt"
+  builtinThemes: "Theme tích hợp sẵn"
+  alreadyInstalled: "Theme này đã được cài đặt"
+  invalid: "Định dạng của theme này không hợp lệ"
+  make: "Tạo theme"
+  base: "Dựa trên có sẵn"
+  addConstant: "Thêm hằng số"
+  constant: "Hằng số"
+  defaultValue: "Giá trị mặc định"
+  color: "Màu sắc"
+  refProp: "Tham chiếu một thuộc tính"
+  refConst: "Tham chiếu một hằng số"
+  key: "Khóa"
+  func: "Hàm"
+  funcKind: "Loại hàm"
+  argument: "Tham số"
+  basedProp: "Thuộc tính tham chiếu"
+  alpha: "Độ trong suốt"
+  darken: "Độ tối"
+  lighten: "Độ sáng"
+  inputConstantName: "Nhập tên cho hằng số này"
+  importInfo: "Nếu bạn nhập mã theme ở đây, bạn có thể nhập mã đó vào trình chỉnh sửa theme"
+  deleteConstantConfirm: "Bạn có chắc muốn xóa hằng số {const} không?"
+  keys:
+    accent: "Màu phụ"
+    bg: "Màu nền"
+    fg: "Màu chữ"
+    focus: "Trọng tâm"
+    indicator: "Chỉ báo"
+    panel: "Thanh bên"
+    shadow: "Bóng mờ"
+    header: "Ảnh bìa"
+    navBg: "Nền thanh bên"
+    navFg: "Chữ thanh bên"
+    navHoverFg: "Chữ thanh bên (Khi chạm)"
+    navActive: "Chữ thanh bên (Khi chọn)"
+    navIndicator: "Chỉ báo thanh bên"
+    link: "Đường dẫn"
+    hashtag: "Hashtag"
+    mention: "Nhắc đến"
+    mentionMe: "Lượt nhắc (Tôi)"
+    renote: "Đăng lại"
+    modalBg: "Nền phương thức"
+    divider: "Phân chia"
+    scrollbarHandle: "Thanh cuộn khi giữ"
+    scrollbarHandleHover: "Thanh cuộn khi chạm"
+    dateLabelFg: "Màu ngày tháng năm"
+    infoBg: "Nền thông tin"
+    infoFg: "Chữ thông tin"
+    infoWarnBg: "Nền cảnh báo"
+    infoWarnFg: "Chữ cảnh báo"
+    cwBg: "Nền nút nội dung ẩn"
+    cwFg: "Chữ nút nội dung ẩn"
+    cwHoverBg: "Nền nút nội dung ẩn (Chạm)"
+    toastBg: "Nền thông báo"
+    toastFg: "Chữ thông báo"
+    buttonBg: "Nền nút"
+    buttonHoverBg: "Nền nút (Chạm)"
+    inputBorder: "Đường viền khung soạn thảo"
+    listItemHoverBg: "Nền mục liệt kê (Chạm)"
+    driveFolderBg: "Nền thư mục Ổ đĩa"
+    wallpaperOverlay: "Lớp phủ hình nền"
+    badge: "Huy hiệu"
+    messageBg: "Nền chat"
+    accentDarken: "Màu phụ (Tối)"
+    accentLighten: "Màu phụ (Sáng)"
+    fgHighlighted: "Chữ nổi bật"
 _sfx:
+  note: "Tút"
+  noteMy: "Tút của tôi"
   notification: "Thông báo"
+  chat: "Trò chuyện"
+  chatBg: "Chat (Nền)"
+  antenna: "Trạm phát sóng"
+  channel: "Kênh"
 _ago:
   unknown: "Không rõ"
   future: "Tương lai"
@@ -39,10 +1097,562 @@ _ago:
   weeksAgo: "{n} tuần trước"
   monthsAgo: "{n} tháng trước"
   yearsAgo: "{n} năm trước"
+_time:
+  second: "s"
+  minute: "phút"
+  hour: "giờ"
+  day: "ngày"
+_tutorial:
+  title: "Cách dùng Misskey"
+  step1_1: "Xin chào!"
+  step1_2: "Trang này gọi là \"bảng tin\". Nó hiện \"tút\" từ những người mà bạn \"theo dõi\" theo thứ tự thời gian."
+  step1_3: "Bảng tin của bạn đang trống, bởi vì bạn chưa đăng tút nào hoặc chưa theo dõi ai."
+  step2_1: "Hãy hoàn thành việc thiết lập hồ sơ của bạn trước khi viết tút hoặc theo dõi bất kỳ ai."
+  step2_2: "Cung cấp một số thông tin giới thiệu bạn là ai sẽ giúp người khác dễ dàng biết được họ muốn đọc tút hay theo dõi bạn."
+  step3_1: "Hoàn thành thiết lập hồ sơ của bạn?"
+  step3_2: "Sau đó, hãy thử đăng một tút tiếp theo. Bạn có thể làm như vậy bằng cách nhấn vào nút có biểu tượng bút chì trên màn hình."
+  step3_3: "Nhập nội dung vào khung soạn thảo và nhấn nút đăng ở góc trên."
+  step3_4: "Chưa biết nói gì? Thử \"Tôi mới tham gia Misskey\"!"
+  step4_1: "Đăng xong tút đầu tiên của bạn?"
+  step4_2: "De! Tút đầu tiên của bạn đã hiện trên bảng tin."
+  step5_1: "Bây giờ, hãy thử làm cho bảng tin của bạn sinh động hơn bằng cách theo dõi những người khác."
+  step5_2: "{feature} sẽ hiển thị cho bạn các tút nổi bật trên máy chủ này. {explore} sẽ cho phép bạn tìm thấy những người dùng thú vị. Hãy thử tìm những người bạn muốn theo dõi ở đó!"
+  step5_3: "Để theo dõi những người dùng khác, hãy nhấn vào ảnh đại diện của họ và nhấn nút \"Theo dõi\" trên hồ sơ của họ."
+  step5_4: "Nếu người dùng khác có biểu tượng ổ khóa bên cạnh tên của họ, có thể mất một khoảng thời gian để người dùng đó phê duyệt yêu cầu theo dõi của bạn theo cách thủ công."
+  step6_1: "Bạn sẽ có thể xem tút của những người dùng khác trên bảng tin của mình ngay bây giờ."
+  step6_2: "Bạn cũng có thể đặt \"biểu cảm\" trên tút của người khác để phản hồi nhanh chúng."
+  step6_3: "Để đính kèm \"biểu cảm\", hãy nhấn vào dấu \"+\" trên tút của người dùng khác rồi chọn biểu tượng cảm xúc mà bạn muốn dùng."
+  step7_1: "Xin chúc mừng! Bây giờ bạn đã hoàn thành phần hướng dẫn cơ bản của Misskey."
+  step7_2: "Nếu bạn muốn tìm hiểu thêm về Misskey, hãy thử phần {help}."
+  step7_3: "Bây giờ, chúc may mắn và vui vẻ với Misskey! 🚀"
+_2fa:
+  alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước."
+  registerDevice: "Đăng ký một thiết bị"
+  registerKey: "Đăng ký một mã bảo vệ"
+  step1: "Trước tiên, hãy cài đặt một ứng dụng xác minh (chẳng hạn như {a} hoặc {b}) trên thiết bị của bạn."
+  step2: "Sau đó, quét mã QR hiển thị trên màn hình này."
+  step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập."
+  step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó."
+  securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình."
+_permissions:
+  "read:account": "Xem thông tin tài khoản của bạn"
+  "write:account": "Sửa thông tin tài khoản của bạn"
+  "read:blocks": "Xem danh sách người bạn chặn"
+  "write:blocks": "Sửa danh sách người bạn chặn"
+  "read:drive": "Truy cập tập tin, thư mục trong Ổ đĩa"
+  "write:drive": "Sửa và xóa tập tin, thư mục trong Ổ đĩa"
+  "read:favorites": "Xem lượt thích của tôi"
+  "write:favorites": "Sửa lượt thích của tôi"
+  "read:following": "Xem những người bạn theo dõi"
+  "write:following": "Theo dõi hoặc ngưng theo dõi ai đó"
+  "read:messaging": "Xem lịch sử chat"
+  "write:messaging": "Soạn hoặc xóa tin nhắn"
+  "read:mutes": "Xem những người bạn ẩn"
+  "write:mutes": "Sửa những người bạn ẩn"
+  "write:notes": "Soạn hoặc xóa tút"
+  "read:notifications": "Xem thông báo của tôi"
+  "write:notifications": "Quản lý thông báo của tôi"
+  "read:reactions": "Xem lượt biểu cảm của tôi"
+  "write:reactions": "Sửa lượt biểu cảm của tôi"
+  "write:votes": "Bình chọn"
+  "read:pages": "Xem trang của tôi"
+  "write:pages": "Sửa hoặc xóa trang của tôi"
+  "read:page-likes": "Xem lượt thích trên trang của tôi"
+  "write:page-likes": "Sửa lượt thích của tôi trên trang"
+  "read:user-groups": "Xem nhóm của tôi"
+  "write:user-groups": "Sửa hoặc xóa nhóm của tôi"
+  "read:channels": "Xem kênh của tôi"
+  "write:channels": "Sửa kênh của tôi"
+  "read:gallery": "Xem kho ảnh của tôi"
+  "write:gallery": "Sửa kho ảnh của tôi"
+  "read:gallery-likes": "Xem danh sách các tút đã thích trong thư viện của tôi"
+  "write:gallery-likes": "Sửa danh sách các tút đã thích trong thư viện của tôi"
+_auth:
+  shareAccess: "Bạn có muốn cho phép \"{name}\" truy cập vào tài khoản này không?"
+  shareAccessAsk: "Bạn có chắc muốn cho phép ứng dụng này truy cập vào tài khoản của mình không?"
+  permissionAsk: "Ứng dụng này yêu cầu các quyền sau"
+  pleaseGoBack: "Vui lòng quay lại ứng dụng"
+  callback: "Quay lại ứng dụng"
+  denied: "Truy cập bị từ chối"
+_antennaSources:
+  all: "Toàn bộ tút"
+  homeTimeline: "Tút từ những người đã theo dõi"
+  users: "Tút từ những người cụ thể"
+  userList: "Tút từ danh sách người dùng cụ thể"
+  userGroup: "Tút từ người dùng trong một nhóm cụ thể"
+_weekday:
+  sunday: "Chủ Nhật"
+  monday: "Thứ Hai"
+  tuesday: "Thứ Ba"
+  wednesday: "Thứ Tư"
+  thursday: "Thứ Năm"
+  friday: "Thứ Sáu"
+  saturday: "Thứ Bảy"
 _widgets:
+  memo: "Tút đã ghim"
   notifications: "Thông báo"
+  timeline: "Bảng tin"
+  calendar: "Lịch"
+  trends: "Xu hướng"
+  clock: "Đồng hồ"
+  rss: "Trình đọc RSS"
+  activity: "Hoạt động"
+  photos: "Kho ảnh"
+  digitalClock: "Đồng hồ số"
+  federation: "Liên hợp"
+  postForm: "Mẫu đăng"
+  slideshow: "Trình chiếu"
+  button: "Nút"
+  onlineUsers: "Ai đang online"
+  jobQueue: "Công việc chờ xử lý"
+  serverMetric: "Thống kê máy chủ"
+  aiscript: "AiScript console"
+  aichan: "Ai"
+_cw:
+  hide: "Ẩn"
+  show: "Tải thêm"
+  chars: "{count} ký tự"
+  files: "{count} tập tin"
+_poll:
+  noOnlyOneChoice: "Cần ít nhất hai lựa chọn."
+  choiceN: "Lựa chọn {n}"
+  noMore: "Bạn không thể thêm lựa chọn"
+  canMultipleVote: "Cho phép chọn nhiều lựa chọn"
+  expiration: "Thời hạn"
+  infinite: "Vĩnh viễn"
+  at: "Kết thúc vào..."
+  after: "Kết thúc sau..."
+  deadlineDate: "Ngày kết thúc"
+  deadlineTime: "giờ"
+  duration: "Thời hạn"
+  votesCount: "{n} bình chọn"
+  totalVotes: "{n} tổng bình chọn"
+  vote: "Bình chọn"
+  showResult: "Xem kết quả"
+  voted: "Đã bình chọn"
+  closed: "Đã kết thúc"
+  remainingDays: "{d} ngày {h} giờ còn lại"
+  remainingHours: "{h} giờ {m} phút còn lại"
+  remainingMinutes: "{m} phút {s}s còn lại"
+  remainingSeconds: "{s}s còn lại"
+_visibility:
+  public: "Công khai"
+  publicDescription: "Mọi người đều có thể đọc tút của bạn"
+  home: "Trang chính"
+  homeDescription: "Chỉ đăng lên bảng tin nhà"
+  followers: "Người theo dõi"
+  followersDescription: "Dành riêng cho người theo dõi"
+  specified: "Nhắn riêng"
+  specifiedDescription: "Chỉ người được nhắc đến mới thấy"
+  localOnly: "Chỉ trên máy chủ"
+  localOnlyDescription: "Không hiển thị với người ở máy chủ khác"
+_postForm:
+  replyPlaceholder: "Trả lời tút này"
+  quotePlaceholder: "Trích dẫn tút này"
+  channelPlaceholder: "Đăng lên một kênh"
+  _placeholders:
+    a: "Bạn đang định làm gì?"
+    b: "Hôm nay bạn có gì vui?"
+    c: "Bạn đang nghĩ gì?"
+    d: "Bạn muốn nói gì?"
+    e: "Bắt đầu viết..."
+    f: "Đang chờ bạn viết..."
 _profile:
+  name: "Tên"
   username: "Tên người dùng"
+  description: "Tiểu sử"
+  youCanIncludeHashtags: "Bạn có thể dùng hashtag trong tiểu sử."
+  metadata: "Thông tin bổ sung"
+  metadataEdit: "Sửa thông tin bổ sung"
+  metadataDescription: "Sử dụng phần này, bạn có thể hiển thị các mục thông tin bổ sung trong hồ sơ của mình."
+  metadataLabel: "Nhãn"
+  metadataContent: "Nội dung"
+  changeAvatar: "Đổi ảnh đại diện"
+  changeBanner: "Đổi ảnh bìa"
+_exportOrImport:
+  allNotes: "Toàn bộ tút"
+  followingList: "Đang theo dõi"
+  muteList: "Ẩn"
+  blockingList: "Chặn"
+  userLists: "Danh sách"
+  excludeMutingUsers: "Loại trừ những người dùng bị ẩn"
+  excludeInactiveUsers: "Loại trừ những người dùng không hoạt động"
+_charts:
+  federation: "Liên hợp"
+  apRequest: "Yêu cầu"
+  usersIncDec: "Sự khác biệt về số lượng người dùng"
+  usersTotal: "Tổng số người dùng"
+  activeUsers: "Số người đang hoạt động"
+  notesIncDec: "Sự khác biệt về số lượng tút"
+  localNotesIncDec: "Sự khác biệt về số lượng tút máy chủ này"
+  remoteNotesIncDec: "Sự khác biệt về số lượng tút từ máy chủ khác"
+  notesTotal: "Tổng số sút"
+  filesIncDec: "Sự khác biệt về số lượng tập tin"
+  filesTotal: "Tổng số tập tin"
+  storageUsageIncDec: "Sự khác biệt về dung lượng lưu trữ"
+  storageUsageTotal: "Tổng dung lượng lưu trữ"
+_instanceCharts:
+  requests: "Lượt yêu cầu"
+  users: "Sự khác biệt về số lượng người dùng"
+  usersTotal: "Số lượng người dùng tích lũy"
+  notes: "Sự khác biệt về số lượng tút"
+  notesTotal: "Số lượng tút tích lũy"
+  ff: "Sự khác biệt về số lượng người dùng được theo dõi/người theo dõi"
+  ffTotal: "Số lượng người dùng được theo dõi/người theo dõi tích lũy"
+  cacheSize: "Sự khác biệt về dung lượng bộ nhớ đệm"
+  cacheSizeTotal: "Dung lượng bộ nhớ đệm tích lũy"
+  files: "Sự khác biệt về số lượng tập tin"
+  filesTotal: "Số lượng tập tin tích lũy"
+_timelines:
+  home: "Trang chính"
+  local: "Máy chủ này"
+  social: "Xã hội"
+  global: "Liên hợp"
+_pages:
+  newPage: "Tạo Trang mới"
+  editPage: "Sửa Trang này"
+  readPage: "Xem mã nguồn Trang này"
+  created: "Trang đã được tạo thành công"
+  updated: "Trang đã được cập nhật thành công"
+  deleted: "Trang đã được xóa thành công"
+  pageSetting: "Cài đặt trang"
+  nameAlreadyExists: "URL Trang đã tồn tại"
+  invalidNameTitle: "URL Trang không hợp lệ"
+  invalidNameText: "Không được để trống tựa đề Trang"
+  editThisPage: "Sửa Trang này"
+  viewSource: "Xem mã nguồn"
+  viewPage: "Xem trang của tôi"
+  like: "Thích"
+  unlike: "Bỏ thích"
+  my: "Trang của tôi"
+  liked: "Trang đã thích"
+  featured: "Nổi tiếng"
+  inspector: "Thanh tra"
+  contents: "Nội dung"
+  content: "Chặn Trang"
+  variables: "Biến thể"
+  title: "Tựa đề"
+  url: "URL Trang"
+  summary: "Mô tả Trang"
+  alignCenter: "Căn giữa"
+  hideTitleWhenPinned: "Ẩn tựa đề Trang khi ghim lên hồ sơ"
+  font: "Phông chữ"
+  fontSerif: "Serif"
+  fontSansSerif: "Sans Serif"
+  eyeCatchingImageSet: "Đặt ảnh thu nhỏ"
+  eyeCatchingImageRemove: "Xóa ảnh thu nhỏ"
+  chooseBlock: "Thêm khối"
+  selectType: "Chọn kiểu"
+  enterVariableName: "Nhập tên một biến thể"
+  variableNameIsAlreadyUsed: "Tên biến thể này đã được sử dụng"
+  contentBlocks: "Nội dung"
+  inputBlocks: "Nhập"
+  specialBlocks: "Đặc biệt"
+  blocks:
+    text: "Văn bản"
+    textarea: "Khu vực văn bản"
+    section: "Mục "
+    image: "Hình ảnh"
+    button: "Nút"
+    if: "Nếu"
+    _if:
+      variable: "Biến thể"
+    post: "Mẫu đăng"
+    _post:
+      text: "Nội dung"
+      attachCanvasImage: "Đính kèm hình canva"
+      canvasId: "ID Canva"
+    textInput: "Văn bản đầu vào"
+    _textInput:
+      name: "Tên biến thể"
+      text: "Tựa đề"
+      default: "Giá trị mặc định"
+    textareaInput: "Văn bản nhiều dòng đầu vào"
+    _textareaInput:
+      name: "Tên biến thể"
+      text: "Tựa đề"
+      default: "Giá trị mặc định"
+    numberInput: "Đầu vào số"
+    _numberInput:
+      name: "Tên biến thể"
+      text: "Tựa đề"
+      default: "Giá trị mặc định"
+    canvas: "Canva"
+    _canvas:
+      id: "ID Canva"
+      width: "Chiều rộng"
+      height: "Chiều cao"
+    note: "Tút đã nhúng"
+    _note:
+      id: "ID tút"
+      idDescription: "Ngoài ra, bạn có thể dán URL tút vào đây."
+      detailed: "Xem chi tiết"
+    switch: "Chuyển đổi"
+    _switch:
+      name: "Tên biến thể"
+      text: "Tựa đề"
+      default: "Giá trị mặc định"
+    counter: "Bộ đếm"
+    _counter:
+      name: "Tên biến thể"
+      text: "Tựa đề"
+      inc: "Bước"
+    _button:
+      text: "Tựa đề"
+      colored: "Với màu"
+      action: "Thao tác khi nhấn nút"
+      _action:
+        dialog: "Hiện hộp thoại"
+        _dialog:
+          content: "Nội dung"
+        resetRandom: "Đặt lại seed ngẫu nhiên"
+        pushEvent: "Gửi một sự kiện"
+        _pushEvent:
+          event: "Tên sự kiện"
+          message: "Tin nhắn hiển thị khi kích hoạt"
+          variable: "Biển thể để gửi"
+          no-variable: "Không"
+        callAiScript: "Gọi AiScript"
+        _callAiScript:
+          functionName: "Tên tính năng"
+    radioButton: "Lựa chọn"
+    _radioButton:
+      name: "Tên biến thể"
+      title: "Tựa đề"
+      values: "Phân tách các mục bằng cách xuống dòng"
+      default: "Giá trị mặc định"
+  script:
+    categories:
+      flow: "Điều khiển"
+      logical: "Hoạt động logic"
+      operation: "Tính toán"
+      comparison: "So sánh"
+      random: "Ngẫu nhiên"
+      value: "Giá trị"
+      fn: "Tính năng"
+      text: "Tác vụ văn bản"
+      convert: "Chuyển đổi"
+      list: "Danh sách"
+    blocks:
+      text: "Văn bản"
+      multiLineText: "Văn bản (nhiều dòng)"
+      textList: "Văn bản liệt kê"
+      _textList:
+        info: "Phân tách mục bằng cách xuống dòng"
+      strLen: "Độ dài văn bản"
+      _strLen:
+        arg1: "Văn bản"
+      strPick: "Trích xuất chuỗi"
+      _strPick:
+        arg1: "Văn bản"
+        arg2: "Vị trí chuỗi"
+      strReplace: "Thay thế chuỗi"
+      _strReplace:
+        arg1: "Nội dung"
+        arg2: "Văn bản thay thế"
+        arg3: "Thay thế bằng"
+      strReverse: "Lật văn bản"
+      _strReverse:
+        arg1: "Văn bản"
+      join: "Nối văn bản"
+      _join:
+        arg1: "Danh sách"
+        arg2: "Phân cách"
+      add: "Cộng"
+      _add:
+        arg1: "A"
+        arg2: "B"
+      subtract: "Trừ"
+      _subtract:
+        arg1: "A"
+        arg2: "B"
+      multiply: "Nhân"
+      _multiply:
+        arg1: "A"
+        arg2: "B"
+      divide: "Chia"
+      _divide:
+        arg1: "A"
+        arg2: "B"
+      mod: "Phần còn lại"
+      _mod:
+        arg1: "A"
+        arg2: "B"
+      round: "Làm tròn thập phân"
+      _round:
+        arg1: "Số"
+      eq: "A và B bằng nhau"
+      _eq:
+        arg1: "A"
+        arg2: "B"
+      notEq: "A và B khác nhau"
+      _notEq:
+        arg1: "A"
+        arg2: "B"
+      and: "A VÀ B"
+      _and:
+        arg1: "A"
+        arg2: "B"
+      or: "A HOẶC B"
+      _or:
+        arg1: "A"
+        arg2: "B"
+      lt: "< A nhỏ hơn B"
+      _lt:
+        arg1: "A"
+        arg2: "B"
+      gt: "> A lớn hơn B"
+      _gt:
+        arg1: "A"
+        arg2: "B"
+      ltEq: "<= A nhỏ hơn hoặc bằng B"
+      _ltEq:
+        arg1: "A"
+        arg2: "B"
+      gtEq: ">= A lớn hơn hoặc bằng B"
+      _gtEq:
+        arg1: "A"
+        arg2: "B"
+      if: "Nhánh"
+      _if:
+        arg1: "Nếu"
+        arg2: "Sau đó"
+        arg3: "Khác"
+      not: "KHÔNG"
+      _not:
+        arg1: "KHÔNG"
+      random: "Ngẫu nhiên"
+      _random:
+        arg1: "Xác suất"
+      rannum: "Số ngẫu nhiên"
+      _rannum:
+        arg1: "Giá trị tối thiểu"
+        arg2: "Giá trị tối đa"
+      randomPick: "Chọn ngẫu nhiên từ danh sách"
+      _randomPick:
+        arg1: "Danh sách"
+      dailyRandom: "Ngẫu nhiên (Đổi mỗi người một lần mỗi ngày)"
+      _dailyRandom:
+        arg1: "Xác suất"
+      dailyRannum: "Số ngẫu nhiên (Đổi mỗi người một lần mỗi ngày)"
+      _dailyRannum:
+        arg1: "Giá trị tối thiểu"
+        arg2: "Giá trị tối đa"
+      dailyRandomPick: "Chọn ngẫu nhiên từ một danh sách (Đổi mỗi người một lần mỗi ngày)"
+      _dailyRandomPick:
+        arg1: "Danh sách"
+      seedRandom: "Ngẫu nhiên (với seed)"
+      _seedRandom:
+        arg1: "Seed"
+        arg2: "Xác suất"
+      seedRannum: "Số ngẫu nhiên (với seed)"
+      _seedRannum:
+        arg1: "Seed"
+        arg2: "Giá trị tối thiểu"
+        arg3: "Giá trị tối đa"
+      seedRandomPick: "Chọn ngẫu nhiên từ danh sách (với seed)"
+      _seedRandomPick:
+        arg1: "Seed"
+        arg2: "Danh sách"
+      DRPWPM: "Chọn ngẫu nhiên từ danh sách nặng (Đổi mỗi người một lần mỗi ngày)"
+      _DRPWPM:
+        arg1: "Văn bản liệt kê"
+      pick: "Chọn từ danh sách"
+      _pick:
+        arg1: "Danh sách"
+        arg2: "Vị trí"
+      listLen: "Lấy độ dài danh sách"
+      _listLen:
+        arg1: "Danh sách"
+      number: "Số"
+      stringToNumber: "Chữ thành số"
+      _stringToNumber:
+        arg1: "Văn bản"
+      numberToString: "Số thành chữ"
+      _numberToString:
+        arg1: "Số"
+      splitStrByLine: "Phân cách văn bản bằng cách xuống dòng"
+      _splitStrByLine:
+        arg1: "Văn bản"
+      ref: "Biến thể"
+      aiScriptVar: "Biển thể AiScript"
+      fn: "Tính năng"
+      _fn:
+        slots: "Chỗ"
+        slots-info: "Phân cách chỗ bằng cách xuống dòng"
+        arg1: "Đầu ra"
+      for: "để-Lặp lại"
+      _for:
+        arg1: "Số lần lặp lại"
+        arg2: "Hành động"
+    typeError: "Chỗ {slot} chấp nhận các giá trị thuộc loại \"{expect}\", nhưng giá trị được cung cấp thuộc loại \"{actual}\"!"
+    thereIsEmptySlot: "Chỗ {slot} đang trống!"
+    types:
+      string: "Văn bản"
+      number: "Số"
+      boolean: "Cờ"
+      array: "Danh sách"
+      stringArray: "Văn bản liệt kê"
+    emptySlot: "Chỗ trống"
+    enviromentVariables: "Biến môi trường"
+    pageVariables: "Biến trang"
+    argVariables: "Đầu vào chỗ"
+_relayStatus:
+  requesting: "Đang chờ"
+  accepted: "Đã duyệt"
+  rejected: "Đã từ chối"
+_notification:
+  fileUploaded: "Đã tải lên tập tin"
+  youGotMention: "{name} nhắc đến bạn"
+  youGotReply: "{name} trả lời bạn"
+  youGotQuote: "{name} trích dẫn tút của bạn"
+  youRenoted: "{name} đăng lại tút của bạn"
+  youGotPoll: "{name} bình chọn tút của bạn"
+  youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
+  youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
+  youWereFollowed: "đã theo dõi bạn"
+  youReceivedFollowRequest: "Bạn vừa có một yêu cầu theo dõi"
+  yourFollowRequestAccepted: "Yêu cầu theo dõi của bạn đã được chấp nhận"
+  youWereInvitedToGroup: "Bạn đã được mời tham gia nhóm"
+  pollEnded: "Cuộc bình chọn đã kết thúc"
+  emptyPushNotificationMessage: "Đã cập nhật thông báo đẩy"
+  _types:
+    all: "Toàn bộ"
+    follow: "Đang theo dõi"
+    mention: "Nhắc đến"
+    reply: "Lượt trả lời"
+    renote: "Đăng lại"
+    quote: "Trích dẫn"
+    reaction: "Biểu cảm"
+    pollVote: "Lượt bình chọn"
+    pollEnded: "Bình chọn kết thúc"
+    receiveFollowRequest: "Yêu cầu theo dõi"
+    followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
+    groupInvited: "Mời vào nhóm"
+    app: "Từ app liên kết"
+  _actions:
+    followBack: "đã theo dõi lại bạn"
+    reply: "Trả lời"
+    renote: "Đăng lại"
 _deck:
+  alwaysShowMainColumn: "Luôn hiện cột chính"
+  columnAlign: "Căn cột"
+  columnMargin: "Căn lề giữa các cột"
+  columnHeaderHeight: "Chiều rộng cột ảnh bìa"
+  addColumn: "Thêm cột"
+  swapLeft: "Hoán đổi với cột bên trái"
+  swapRight: "Hoán đổi với cột bên phải"
+  swapUp: "Hoán đổi với cột trên"
+  swapDown: "Hoán đổi với cột dưới"
+  stackLeft: "Xếp chồng với cột bên trái"
+  popRight: "Xếp chồng với cột bên trái"
+  profile: "Hồ sơ"
   _columns:
+    main: "Chính"
+    widgets: "Tiện ích"
     notifications: "Thông báo"
+    tl: "Bảng tin"
+    antenna: "Trạm phát sóng"
+    list: "Danh sách"
+    mentions: "Lượt nhắc"
+    direct: "Nhắn riêng"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index f644585835..c719dcb767 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -8,7 +8,7 @@ notifications: "通知"
 username: "用户名"
 password: "密码"
 forgotPassword: "忘记密码"
-fetchingAsApObject: "在联邦宇宙查询中..."
+fetchingAsApObject: "正在联邦宇宙查询中..."
 ok: "OK"
 gotIt: "我明白了"
 cancel: "取消"
@@ -69,7 +69,7 @@ exportRequested: "导出请求已提交,这可能需要花一些时间,导
 importRequested: "导入请求已提交,这可能需要花一点时间。"
 lists: "列表"
 noLists: "列表为空"
-note: "发帖"
+note: "帖子"
 notes: "帖子"
 following: "关注中"
 followers: "关注者"
@@ -96,7 +96,7 @@ enterEmoji: "输入表情符号"
 renote: "转发"
 unrenote: "取消转发"
 renoted: "已转发。"
-cantRenote: "该帖子无法转发。"
+cantRenote: "该帖无法转发。"
 cantReRenote: "转发无法被再次转发。"
 quote: "引用"
 pinnedNote: "已置顶的帖子"
@@ -155,7 +155,7 @@ searchWith: "搜索:{q}"
 youHaveNoLists: "列表为空"
 followConfirm: "你确定要关注{name}吗?"
 proxyAccount: "代理账户"
-proxyAccountDescription: "代理帐户是在某些情况下充当用户的远程关注者的帐户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该实例,因此将代之以代理帐户。"
+proxyAccountDescription: "代理账户是在某些情况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该实例,因此将代之以代理账户。"
 host: "主机名"
 selectUser: "选择用户"
 recipient: "收件人"
@@ -171,7 +171,7 @@ charts: "图表"
 perHour: "每小时"
 perDay: "每天"
 stopActivityDelivery: "停止发送活动"
-blockThisInstance: "阻止此实例"
+blockThisInstance: "阻止此实例向本实例推流"
 operations: "操作"
 software: "软件"
 version: "版本"
@@ -250,7 +250,7 @@ messageRead: "已读"
 noMoreHistory: "没有更多的历史记录"
 startMessaging: "添加聊天"
 nUsersRead: "{n}人已读"
-agreeTo: "{0}人同意"
+agreeTo: "{0}勾选则表示已阅读并同意"
 tos: "服务条款"
 start: "开始"
 home: "首页"
@@ -321,7 +321,7 @@ connectService: "连接"
 disconnectService: "断开连接"
 enableLocalTimeline: "启用本地时间线功能"
 enableGlobalTimeline: "启用全局时间线"
-disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原因,管理员和数据图表也可以继续使用。"
+disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和数据图表也可以继续使用。"
 registration: "注册"
 enableRegistration: "允许新用户注册"
 invite: "邀请"
@@ -440,7 +440,7 @@ strongPassword: "密码强度:强"
 passwordMatched: "密码一致"
 passwordNotMatched: "密码不一致"
 signinWith: "以{x}登录"
-signinFailed: "无法登录,请检查您的用户名和密码。"
+signinFailed: "无法登录,请检查您的用户名和密码是否正确。"
 tapSecurityKey: "轻触硬件安全密钥"
 or: "或者"
 language: "语言"
@@ -459,7 +459,7 @@ category: "类别"
 tags: "标签"
 docSource: "文件来源"
 createAccount: "注册账户"
-existingAccount: "现有的帐户"
+existingAccount: "现有的账户"
 regenerate: "重新生成"
 fontSize: "字体大小"
 noFollowRequests: "没有关注申请"
@@ -533,7 +533,7 @@ removeAllFollowingDescription: "取消{host}的所有关注者。当实例不存
 userSuspended: "该用户已被冻结。"
 userSilenced: "该用户已被禁言。"
 yourAccountSuspendedTitle: "账户已被冻结"
-yourAccountSuspendedDescription: "由于违反了服务器的服务条款或其他原因,该账户已被冻结。 您可以与管理员联系以了解更多信息。 请不要创建一个新的帐户。"
+yourAccountSuspendedDescription: "由于违反了服务器的服务条款或其他原因,该账户已被冻结。 您可以与管理员联系以了解更多信息。 请不要创建一个新的账户。"
 menu: "菜单"
 divider: "分割线"
 addItem: "添加项目"
@@ -609,7 +609,7 @@ create: "创建"
 notificationSetting: "通知设置"
 notificationSettingDesc: "选择要显示的通知类型。"
 useGlobalSetting: "使用全局设置"
-useGlobalSettingDesc: "启用时,将使用帐户通知设置。关闭时,则可以单独设置。"
+useGlobalSettingDesc: "启用时,将使用账户通知设置。关闭时,则可以单独设置。"
 other: "其他"
 regenerateLoginToken: "重新生成登录令牌"
 regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。"
@@ -621,12 +621,12 @@ abuseReports: "举报"
 reportAbuse: "举报"
 reportAbuseOf: "举报{name}"
 fillAbuseReportDescription: "请填写举报的详细原因。如果有对方发的帖子,请同时填写URL地址。"
-abuseReported: "内容已发送。感谢您的报告。"
-reporter: "报告者"
+abuseReported: "内容已发送。感谢您提交信息。"
+reporter: "举报者"
 reporteeOrigin: "举报来源"
 reporterOrigin: "举报者来源"
-forwardReport: "将报告转发给远程实例"
-forwardReportIsAnonymous: "在远程实例上显示的报告者是匿名的系统账号,而不是您的账号。"
+forwardReport: "将该举报信息转发给远程实例"
+forwardReportIsAnonymous: "勾选则在远程实例上显示的举报者是匿名的系统账号,而不是您的账号。"
 send: "发送"
 abuseMarkAsResolved: "处理完毕"
 openInNewTab: "在新标签页中打开"
@@ -644,9 +644,9 @@ createNew: "新建"
 optional: "可选"
 createNewClip: "新建书签"
 public: "公开"
-i18nInfo: "Misskey已经被志愿者们翻译到了各种语言。如果你也有兴趣,可以通过{link}帮助翻译。"
+i18nInfo: "Misskey已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过{link}帮助翻译。"
 manageAccessTokens: "管理 Access Tokens"
-accountInfo: "帐户信息"
+accountInfo: "账户信息"
 notesCount: "帖子数量"
 repliesCount: "回复数量"
 renotesCount: "转帖数量"
@@ -662,7 +662,7 @@ yes: "是"
 no: "否"
 driveFilesCount: "网盘的文件数"
 driveUsage: "网盘的空间用量"
-noCrawle: "拒绝搜索引擎的索引"
+noCrawle: "要求搜索引擎不索引该站点"
 noCrawleDescription: "要求搜索引擎不要收录(索引)您的用户页面,帖子,页面等。"
 lockedAccountInfo: "即使通过了关注请求,只要您不将帖子可见范围设置成“关注者”,任何人都可以看到您的帖子。"
 alwaysMarkSensitive: "默认将媒体文件标记为敏感内容"
@@ -1615,6 +1615,7 @@ _notification:
   yourFollowRequestAccepted: "您的关注请求已通过"
   youWereInvitedToGroup: "您有新的群组邀请"
   pollEnded: "问卷调查结果已生成。"
+  emptyPushNotificationMessage: "推送通知已更新"
   _types:
     all: "全部"
     follow: "关注中"
@@ -1629,6 +1630,10 @@ _notification:
     followRequestAccepted: "关注请求已通过"
     groupInvited: "加入群组邀请"
     app: "关联应用的通知"
+  _actions:
+    followBack: "回关"
+    reply: "回复"
+    renote: "转发"
 _deck:
   alwaysShowMainColumn: "总是显示主列"
   columnAlign: "列对齐"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 18c6f17154..e9b7ab6540 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -135,13 +135,14 @@ emojiName: "表情符號名稱"
 emojiUrl: "表情符號URL"
 addEmoji: "加入表情符號"
 settingGuide: "推薦設定"
-cacheRemoteFiles: "緩存非遠程檔案"
+cacheRemoteFiles: "快取遠端檔案"
 cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。"
 flagAsBot: "此使用者是機器人"
 flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人"
 flagAsCat: "此使用者是貓"
 flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
 flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
+flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
 autoAcceptFollowed: "自動追隨中使用者的追隨請求"
 addAccount: "添加帳戶"
 loginFailed: "登入失敗"
@@ -153,8 +154,8 @@ removeWallpaper: "移除桌布"
 searchWith: "搜尋: {q}"
 youHaveNoLists: "你沒有任何清單"
 followConfirm: "你真的要追隨{name}嗎?"
-proxyAccount: "代理帳號"
-proxyAccountDescription: "代理帳號是在某些情況下充當其他伺服器用戶的帳號。例如,當使用者將一個來自其他伺服器的帳號放在列表中時,由於沒有其他使用者關注該帳號,該指令不會傳送到該伺服器上,因此會由代理帳戶關注。"
+proxyAccount: "代理帳戶"
+proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者關注該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶關注。"
 host: "主機"
 selectUser: "選取使用者"
 recipient: "收件人"
@@ -197,7 +198,7 @@ noUsers: "沒有任何使用者"
 editProfile: "編輯個人檔案"
 noteDeleteConfirm: "確定刪除此貼文嗎?"
 pinLimitExceeded: "不能置頂更多貼文了"
-intro: "Misskey 部署完成!請建立管理員帳號!"
+intro: "Misskey 部署完成!請建立管理員帳戶。"
 done: "完成"
 processing: "處理中"
 preview: "預覽"
@@ -236,6 +237,8 @@ resetAreYouSure: "確定要重設嗎?"
 saved: "已儲存"
 messaging: "傳送訊息"
 upload: "上傳"
+keepOriginalUploading: "保留原圖"
+keepOriginalUploadingDescription: "上傳圖片時保留原始圖片。關閉時,瀏覽器會在上傳時生成一張用於web發布的圖片。"
 fromDrive: "從雲端空間"
 fromUrl: "從URL"
 uploadFromUrl: "從網址上傳"
@@ -357,7 +360,7 @@ enableServiceworker: "開啟 ServiceWorker"
 antennaUsersDescription: "指定用換行符分隔的用戶名"
 caseSensitive: "區分大小寫"
 withReplies: "包含回覆"
-connectedTo: "您的帳號已連接到以下社交帳號"
+connectedTo: "您的帳戶已連接到以下社交帳戶"
 notesAndReplies: "貼文與回覆"
 withFiles: "附件"
 silence: "禁言"
@@ -445,6 +448,7 @@ uiLanguage: "介面語言"
 groupInvited: "您有新的群組邀請"
 aboutX: "關於{x}"
 useOsNativeEmojis: "使用OS原生表情符號"
+disableDrawer: "不顯示下拉式選單"
 youHaveNoGroups: "找不到群組"
 joinOrCreateGroup: "請加入現有群組,或創建新群組。"
 noHistory: "沒有歷史紀錄"
@@ -468,7 +472,7 @@ weekOverWeekChanges: "與上週相比"
 dayOverDayChanges: "與前一日相比"
 appearance: "外觀"
 clientSettings: "用戶端設定"
-accountSettings: "帳號設定"
+accountSettings: "帳戶設定"
 promotion: "推廣"
 promote: "推廣"
 numberOfDays: "有效天數"
@@ -477,6 +481,7 @@ showFeaturedNotesInTimeline: "在時間軸上顯示熱門推薦"
 objectStorage: "Object Storage (物件儲存)"
 useObjectStorage: "使用Object Storage"
 objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "引用時的URL。如果您使用的是CDN或反向代理,请指定其URL,例如S3:“https://<bucket>.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/<bucket>”"
 objectStorageBucket: "儲存空間(Bucket)"
 objectStorageBucketDesc: "請指定您正在使用的服務的存儲桶名稱。 "
 objectStoragePrefix: "前綴"
@@ -484,8 +489,11 @@ objectStoragePrefixDesc: "它存儲在此前綴目錄下。"
 objectStorageEndpoint: "端點(Endpoint)"
 objectStorageEndpointDesc: "如要使用AWS S3,請留空。否則請依照你使用的服務商的說明書進行設定,以'<host>'或 '<host>:<port>'的形式設定端點(Endpoint)。"
 objectStorageRegion: "地域(Region)"
+objectStorageRegionDesc: "指定一個分區,例如“xx-east-1”。 如果您使用的服務沒有分區的概念,請留空或填寫“us-east-1”。"
 objectStorageUseSSL: "使用SSL"
+objectStorageUseSSLDesc: "如果不使用https進行API連接,請關閉"
 objectStorageUseProxy: "使用網路代理"
+objectStorageUseProxyDesc: "如果不使用代理進行API連接,請關閉"
 objectStorageSetPublicRead: "上傳時設定為\"public-read\""
 serverLogs: "伺服器日誌"
 deleteAll: "刪除所有記錄"
@@ -513,6 +521,7 @@ sort: "排序"
 ascendingOrder: "昇冪"
 descendingOrder: "降冪"
 scratchpad: "暫存記憶體"
+scratchpadDescription: "AiScript控制台為AiScript提供了實驗環境。您可以在此編寫、執行和確認代碼與Misskey互動的结果。"
 output: "輸出"
 script: "腳本"
 disablePagesScript: "停用頁面的AiScript腳本"
@@ -523,6 +532,9 @@ removeAllFollowing: "解除所有追蹤"
 removeAllFollowingDescription: "解除{host}所有的追蹤。在實例不再存在時執行。"
 userSuspended: "該使用者已被停用"
 userSilenced: "該用戶已被禁言。"
+yourAccountSuspendedTitle: "帳戶已被凍結"
+yourAccountSuspendedDescription: "由於違反了伺服器的服務條款或其他原因,該帳戶已被凍結。 您可以與管理員連繫以了解更多訊息。 請不要創建一個新的帳戶。"
+menu: "選單"
 divider: "分割線"
 addItem: "新增項目"
 relays: "中繼"
@@ -546,7 +558,7 @@ enterFileDescription: "輸入標題 "
 author: "作者"
 leaveConfirm: "有未保存的更改。要放棄嗎?"
 manage: "管理"
-plugins: "插件"
+plugins: "外掛"
 deck: "多欄模式"
 undeck: "取消多欄模式"
 useBlurEffectForModal: "在模態框使用模糊效果"
@@ -556,10 +568,12 @@ height: "高度"
 large: "大"
 medium: "中"
 small: "小"
+generateAccessToken: "發行存取權杖"
 permission: "權限"
 enableAll: "啟用全部"
 disableAll: "停用全部"
-tokenRequested: "允許存取帳號"
+tokenRequested: "允許存取帳戶"
+pluginTokenRequestedDescription: "此外掛將擁有在此設定的權限。"
 notificationType: "通知形式"
 edit: "編輯"
 useStarForReactionFallback: "以★代替未知的表情符號"
@@ -574,8 +588,13 @@ smtpPort: "埠"
 smtpUser: "使用者名稱"
 smtpPass: "密碼"
 emptyToDisableSmtpAuth: "留空使用者名稱和密碼以關閉SMTP驗證。"
+smtpSecure: "在 SMTP 連接中使用隱式 SSL/TLS"
+smtpSecureInfo: "使用STARTTLS時關閉。"
 testEmail: "測試郵件發送"
-wordMute: "靜音文字"
+wordMute: "被靜音的文字"
+regexpError: "正規表達式錯誤"
+regexpErrorDescription: "{tab} 靜音文字的第 {line} 行的正規表達式有錯誤:"
+instanceMute: "實例的靜音"
 userSaysSomething: "{name}說了什麼"
 makeActive: "啟用"
 display: "檢視"
@@ -606,6 +625,8 @@ abuseReported: "回報已送出。感謝您的報告。"
 reporter: "檢舉者"
 reporteeOrigin: "檢舉來源"
 reporterOrigin: "檢舉者來源"
+forwardReport: "將報告轉送給遠端實例"
+forwardReportIsAnonymous: "在遠端實例上看不到您的資訊,顯示的報告者是匿名的系统帳戶。"
 send: "發送"
 abuseMarkAsResolved: "處理完畢"
 openInNewTab: "在新分頁中開啟"
@@ -667,6 +688,7 @@ center: "置中"
 wide: "寬"
 narrow: "窄"
 reloadToApplySetting: "設定將會在頁面重新載入之後生效。要現在就重載頁面嗎?"
+needReloadToApply: "必須重新載入才會生效。"
 showTitlebar: "顯示標題列"
 clearCache: "清除快取資料"
 onlineUsersCount: "{n}人正在線上"
@@ -727,6 +749,7 @@ notRecommended: "不推薦"
 botProtection: "Bot防護"
 instanceBlocking: "已封鎖的實例"
 selectAccount: "選擇帳戶"
+switchAccount: "切換帳戶"
 enabled: "已啟用"
 disabled: "已停用"
 quickAction: "快捷操作"
@@ -753,32 +776,92 @@ emailNotConfiguredWarning: "沒有設定電子郵件地址"
 ratio: "%"
 previewNoteText: "預覽文本"
 customCss: "自定義 CSS"
+customCssWarn: "這個設定必須由具備相關知識的人員操作,不當的設定可能导致客戶端無法正常使用。"
 global: "公開"
+squareAvatars: "頭像以方形顯示"
 sent: "發送"
 received: "收取"
 searchResult: "搜尋結果"
 hashtags: "#tag"
 troubleshooting: "故障排除"
 useBlurEffect: "在 UI 上使用模糊效果"
+learnMore: "更多資訊"
 misskeyUpdated: "Misskey 更新完成!"
+whatIsNew: "顯示更新資訊"
 translate: "翻譯"
 translatedFrom: "從 {x} 翻譯"
 accountDeletionInProgress: "正在刪除帳戶"
+usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。"
+aiChanMode: "小藍模式"
+keepCw: "保持CW"
 pubSub: "Pub/Sub 帳戶"
+lastCommunication: "最近的通信"
 resolved: "已解決"
 unresolved: "未解決"
 breakFollow: "移除追蹤者"
+itsOn: "已開啟"
+itsOff: "已關閉"
+emailRequiredForSignup: "註冊帳戶需要電子郵件地址"
+unread: "未讀"
+filter: "篩選"
+controlPanel: "控制台"
+manageAccounts: "管理帳戶"
+makeReactionsPublic: "將回應設為公開"
+makeReactionsPublicDescription: "將您做過的回應設為公開可見。"
+classic: "經典"
+muteThread: "將貼文串設為靜音"
+unmuteThread: "將貼文串的靜音解除"
+ffVisibility: "連接的公開範圍"
+ffVisibilityDescription: "您可以設定您的關注/關注者資訊的公開範圍"
+continueThread: "查看更多貼文"
+deleteAccountConfirm: "將要刪除帳戶。是否確定?"
+incorrectPassword: "密碼錯誤。"
+voteConfirm: "確定投給「{choice}」?"
 hide: "隱藏"
+leaveGroup: "離開群組"
 leaveGroupConfirm: "確定離開「{name}」?"
+useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示"
+welcomeBackWithName: "歡迎回來,{name}"
+clickToFinishEmailVerification: "點擊 [{ok}] 完成電子郵件地址認證。"
+overridedDeviceKind: "裝置類型"
+smartphone: "智慧型手機"
+tablet: "平板"
 auto: "自動"
+themeColor: "主題顏色"
+size: "大小"
+numberOfColumn: "列數"
 searchByGoogle: "搜尋"
+instanceDefaultLightTheme: "實例預設的淺色主題"
+instanceDefaultDarkTheme: "實例預設的深色主題"
+instanceDefaultThemeDescription: "輸入物件形式的主题代碼"
+mutePeriod: "靜音的期限"
 indefinitely: "無期限"
+tenMinutes: "10分鐘"
+oneHour: "1小時"
+oneDay: "1天"
+oneWeek: "1週"
+reflectMayTakeTime: "可能需要一些時間才會出現效果。"
+failedToFetchAccountInformation: "取得帳戶資訊失敗"
+_emailUnavailable:
+  used: "已經在使用中"
+  format: "格式無效"
+  disposable: "不是永久可用的地址"
+  mx: "郵件伺服器不正確"
+  smtp: "郵件伺服器沒有應答"
 _ffVisibility:
   public: "發佈"
+  followers: "只有關注你的用戶能看到"
   private: "私密"
 _signup:
   almostThere: "即將完成"
+  emailAddressInfo: "請輸入您所使用的電子郵件地址。電子郵件地址不會被公開。"
+  emailSent: "已將確認郵件發送至您輸入的電子郵件地址 ({email})。請開啟電子郵件中的連結以完成帳戶創建。"
 _accountDelete:
+  accountDelete: "刪除帳戶"
+  mayTakeTime: "刪除帳戶的處理負荷較大,如果帳戶產生的內容數量上船的檔案數量較多的話,就需要花费一段時間才能完成。"
+  sendEmail: "帳戶删除完成後,將向註冊地電子郵件地址發送通知。"
+  requestAccountDelete: "刪除帳戶請求"
+  started: "已開始刪除作業。"
   inProgress: "正在刪除"
 _ad:
   back: "返回"
@@ -800,7 +883,7 @@ _email:
 _plugin:
   install: "安裝外掛組件"
   installWarn: "請不要安裝來源不明的外掛組件。"
-  manage: "管理插件"
+  manage: "管理外掛"
 _registry:
   scope: "範圍"
   key: "機碼"
@@ -833,14 +916,21 @@ _mfm:
   link: "鏈接"
   linkDescription: "您可以將特定範圍的文章與 URL 相關聯。 "
   bold: "粗體"
+  boldDescription: "可以將文字顯示为粗體来強調。"
   small: "縮小"
+  smallDescription: "可以使內容文字變小、變淡。"
   center: "置中"
+  centerDescription: "可以將內容置中顯示。"
   inlineCode: "程式碼(内嵌)"
+  inlineCodeDescription: "在行內用高亮度顯示,例如程式碼語法。"
   blockCode: "程式碼(區塊)"
+  blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。"
   inlineMath: "數學公式(內嵌)"
   inlineMathDescription: "顯示內嵌的KaTex數學公式。"
   blockMath: "數學公式(方塊)"
+  blockMathDescription: "以區塊顯示複數行的KaTex數學式。"
   quote: "引用"
+  quoteDescription: "可以用來表示引用的内容。"
   emoji: "自訂表情符號"
   emojiDescription: "您可以通過將自定義表情符號名稱括在冒號中來顯示自定義表情符號。 "
   search: "搜尋"
@@ -849,22 +939,34 @@ _mfm:
   flipDescription: "將內容上下或左右翻轉。"
   jelly: "動畫(果凍)"
   jellyDescription: "顯示果凍一樣的動畫效果。"
+  tada: "動畫(鏘~)"
+  tadaDescription: "顯示「鏘~!」這種感覺的動畫效果。"
   jump: "動畫(跳動)"
+  jumpDescription: "顯示跳動的動畫效果。"
   bounce: "動畫(反彈)"
+  bounceDescription: "顯示有彈性的動畫效果。"
   shake: "動畫(搖晃)"
+  shakeDescription: "顯示顫抖的動畫效果。"
   twitch: "動畫(顫抖)"
   twitchDescription: "顯示強烈顫抖的動畫效果。"
   spin: "動畫(旋轉)"
   spinDescription: "顯示旋轉的動畫效果。"
   x2: "大"
+  x2Description: "放大顯示內容。"
   x3: "較大"
   x3Description: "放大顯示內容。"
   x4: "最大"
   x4Description: "將顯示內容放至最大。"
   blur: "模糊"
+  blurDescription: "產生模糊效果。将游標放在上面即可將内容顯示出來。"
   font: "字型"
   fontDescription: "您可以設定顯示內容的字型"
+  rainbow: "彩虹"
+  rainbowDescription: "用彩虹色來顯示內容。"
+  sparkle: "閃閃發光"
+  sparkleDescription: "添加閃閃發光的粒子效果。"
   rotate: "旋轉"
+  rotateDescription: "以指定的角度旋轉。"
 _instanceTicker:
   none: "隱藏"
   remote: "向遠端使用者顯示"
@@ -884,11 +986,24 @@ _channel:
   usersCount: "有{n}人參與"
   notesCount: "有{n}個貼文"
 _menuDisplay:
+  sideFull: "側向"
+  sideIcon: "側向(圖示)"
+  top: "頂部"
   hide: "隱藏"
 _wordMute:
   muteWords: "加入靜音文字"
+  muteWordsDescription: "用空格分隔指定AND,用換行分隔指定OR。"
+  muteWordsDescription2: "將關鍵字用斜線括起來表示正規表達式。"
   softDescription: "隱藏時間軸中指定條件的貼文。"
+  hardDescription: "具有指定條件的貼文將不添加到時間軸。 即使您更改條件,未被添加的貼文也會被排除在外。"
+  soft: "軟性靜音"
+  hard: "硬性靜音"
   mutedNotes: "已靜音的貼文"
+_instanceMute:
+  instanceMuteDescription: "包括對被靜音實例上的用戶的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
+  instanceMuteDescription2: "設定時以換行進行分隔"
+  title: "被設定的實例,貼文將被隱藏。"
+  heading: "將實例靜音"
 _theme:
   explore: "取得佈景主題"
   install: "安裝佈景主題"
@@ -902,10 +1017,12 @@ _theme:
   invalid: "主題格式錯誤"
   make: "製作主題"
   base: "基於"
+  addConstant: "添加常數"
   constant: "常數"
   defaultValue: "預設值"
   color: "顏色"
   refProp: "查看屬性 "
+  refConst: "查看常數"
   key: "按鍵"
   func: "函数"
   funcKind: "功能類型"
@@ -914,6 +1031,9 @@ _theme:
   alpha: "透明度"
   darken: "暗度"
   lighten: "亮度"
+  inputConstantName: "請輸入常數的名稱"
+  importInfo: "您可以在此貼上主題代碼,將其匯入編輯器中"
+  deleteConstantConfirm: "確定要删除常數{const}嗎?"
   keys:
     accent: "重點色彩"
     bg: "背景"
@@ -933,6 +1053,7 @@ _theme:
     mention: "提到"
     mentionMe: "提到了我"
     renote: "轉發貼文"
+    modalBg: "對話框背景"
     divider: "分割線"
     scrollbarHandle: "捲動條"
     scrollbarHandleHover: "捲動條 (漂浮)"
@@ -1010,9 +1131,12 @@ _2fa:
   registerKey: "註冊鍵"
   step1: "首先,在您的設備上安裝二步驗證程式,例如{a}或{b}。"
   step2: "然後,掃描螢幕上的QR code。"
+  step3: "輸入您的App提供的權杖以完成設定。"
+  step4: "從現在開始,任何登入操作都將要求您提供權杖。"
+  securityKeyInfo: "您可以設定使用支援FIDO2的硬體安全鎖、終端設備的指纹認證或者PIN碼來登入。"
 _permissions:
-  "read:account": "查看帳戶信息"
-  "write:account": "更改帳戶信息"
+  "read:account": "查看我的帳戶資訊"
+  "write:account": "更改我的帳戶資訊"
   "read:blocks": "已封鎖用戶名單"
   "write:blocks": "編輯已封鎖用戶名單"
   "read:drive": "存取雲端硬碟"
@@ -1039,6 +1163,10 @@ _permissions:
   "write:user-groups": "編輯使用者群組"
   "read:channels": "已查看的頻道"
   "write:channels": "編輯頻道"
+  "read:gallery": "瀏覽圖庫"
+  "write:gallery": "操作圖庫"
+  "read:gallery-likes": "讀取喜歡的圖片"
+  "write:gallery-likes": "操作喜歡的圖片"
 _auth:
   shareAccess: "要授權「“{name}”」存取您的帳戶嗎?"
   shareAccessAsk: "您確定要授權這個應用程式使用您的帳戶嗎?"
@@ -1078,6 +1206,8 @@ _widgets:
   onlineUsers: "線上的用戶"
   jobQueue: "佇列"
   serverMetric: "服務器指標 "
+  aiscript: "AiScript控制台"
+  aichan: "小藍"
 _cw:
   hide: "隱藏"
   show: "瀏覽更多"
@@ -1103,12 +1233,15 @@ _poll:
   closed: "已結束"
   remainingDays: "{d}天{h}小時後結束"
   remainingHours: "{h}小時{m}分後結束"
+  remainingMinutes: "{m}分{s}秒後結束"
   remainingSeconds: "{s}秒後截止"
 _visibility:
   public: "公開"
   publicDescription: "發布給所有用戶 "
   home: "首頁"
+  homeDescription: "僅發送至首頁的時間軸"
   followers: "追隨者"
+  followersDescription: "僅發送至關注者"
   specified: "指定使用者"
   specifiedDescription: "僅發送至指定使用者"
   localOnly: "僅限本地"
@@ -1131,6 +1264,7 @@ _profile:
   youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag"
   metadata: "進階資訊"
   metadataEdit: "編輯進階資訊"
+  metadataDescription: "可以在個人資料中以表格形式顯示其他資訊。"
   metadataLabel: "標籤"
   metadataContent: "内容"
   changeAvatar: "更換大頭貼"
@@ -1141,6 +1275,8 @@ _exportOrImport:
   muteList: "靜音"
   blockingList: "封鎖"
   userLists: "清單"
+  excludeMutingUsers: "排除被靜音的用戶"
+  excludeInactiveUsers: "排除不活躍帳戶"
 _charts:
   federation: "站台聯邦"
   apRequest: "請求"
@@ -1418,6 +1554,7 @@ _pages:
       _seedRandomPick:
         arg1: "種子"
         arg2: "清單"
+      DRPWPM: "从機率列表中隨機選擇(每個用户每天)"
       _DRPWPM:
         arg1: "字串串列"
       pick: "從清單中選取"
@@ -1448,6 +1585,8 @@ _pages:
       _for:
         arg1: "重複次數"
         arg2: "處理"
+    typeError: "槽參數{slot}需要傳入“{expect}”,但是實際傳入為“{actual}”!"
+    thereIsEmptySlot: "參數{slot}是空的!"
     types:
       string: "字串"
       number: "数值"
@@ -1470,10 +1609,13 @@ _notification:
   youRenoted: "{name} 轉發了你的貼文"
   youGotPoll: "{name}已投票"
   youGotMessagingMessageFromUser: "{name}發送給您的訊息"
+  youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
   youWereFollowed: "您有新的追隨者"
   youReceivedFollowRequest: "您有新的追隨請求"
   yourFollowRequestAccepted: "您的追隨請求已通過"
   youWereInvitedToGroup: "您有新的群組邀請"
+  pollEnded: "問卷調查已產生結果"
+  emptyPushNotificationMessage: "推送通知已更新"
   _types:
     all: "全部 "
     follow: "追隨中"
@@ -1483,10 +1625,15 @@ _notification:
     quote: "引用"
     reaction: "反應"
     pollVote: "統計已投票數"
+    pollEnded: "問卷調查結束"
     receiveFollowRequest: "已收到追隨請求"
     followRequestAccepted: "追隨請求已接受"
     groupInvited: "加入社群邀請"
     app: "應用程式通知"
+  _actions:
+    followBack: "回關"
+    reply: "回覆"
+    renote: "轉發"
 _deck:
   alwaysShowMainColumn: "總是顯示主欄"
   columnAlign: "對齊欄位"

From 89c5fd0931e981e7d6aa0af5dd19f9492e345f78 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 14 May 2022 06:28:27 +0200
Subject: [PATCH 093/258] perf: fix caching (#8660)

The cache implementation did previously not store the results of the
computation and was thus not a cache at all. This can cause a significant
number of database queries each time someone with a large number of
followers does something that causes an activity to be federated.
---
 packages/backend/src/misc/cache.ts | 1 +
 1 file changed, 1 insertion(+)

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;
 	}
 

From e161b716510cccaf61b3724971fe85bb8e3e52c7 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 14 May 2022 14:57:51 +0900
Subject: [PATCH 094/258] update deps

---
 packages/backend/package.json |  39 ++---
 packages/backend/yarn.lock    | 269 +++++++++++++++++-----------------
 packages/client/package.json  |  38 ++---
 packages/client/yarn.lock     | 238 ++++++++++++++++--------------
 4 files changed, 301 insertions(+), 283 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 5950cd23ff..c06f48f546 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -20,20 +20,18 @@
 		"@koa/cors": "3.1.0",
 		"@koa/multer": "3.0.0",
 		"@koa/router": "9.0.1",
-		"@sinonjs/fake-timers": "9.1.1",
+		"@sinonjs/fake-timers": "9.1.2",
 		"@syuilo/aiscript": "0.11.1",
-		"@typescript-eslint/eslint-plugin": "5.20.0",
-		"@typescript-eslint/parser": "5.20.0",
 		"abort-controller": "3.0.0",
 		"ajv": "8.11.0",
 		"archiver": "5.3.1",
 		"autobind-decorator": "2.4.0",
 		"autwh": "0.1.0",
-		"aws-sdk": "2.1120.0",
+		"aws-sdk": "2.1135.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
 		"broadcast-channel": "4.11.0",
-		"bull": "4.8.2",
+		"bull": "4.8.3",
 		"cacheable-lookup": "6.0.4",
 		"cbor": "8.1.0",
 		"chalk": "5.0.1",
@@ -44,12 +42,10 @@
 		"date-fns": "2.28.0",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.14.0",
-		"eslint-plugin-import": "2.26.0",
 		"feed": "4.2.2",
 		"file-type": "17.1.1",
 		"fluent-ffmpeg": "2.1.2",
-		"got": "12.0.3",
+		"got": "12.0.4",
 		"hpagent": "0.1.2",
 		"http-signature": "1.3.6",
 		"ip-cidr": "3.0.7",
@@ -59,7 +55,7 @@
 		"json5": "2.2.1",
 		"json5-loader": "4.0.1",
 		"jsonld": "5.2.0",
-		"jsrsasign": "10.5.19",
+		"jsrsasign": "10.5.20",
 		"koa": "2.13.4",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
@@ -76,8 +72,8 @@
 		"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.4",
+		"nodemailer": "6.7.5",
 		"os-utils": "0.0.14",
 		"parse5": "6.0.1",
 		"pg": "8.7.3",
@@ -108,26 +104,25 @@
 		"style-loader": "3.3.1",
 		"summaly": "2.5.0",
 		"syslog-pro": "1.0.0",
-		"systeminformation": "5.11.14",
+		"systeminformation": "5.11.15",
 		"tinycolor2": "1.4.2",
 		"tmp": "0.2.1",
-		"ts-loader": "9.2.8",
+		"ts-loader": "9.3.0",
 		"ts-node": "10.7.0",
 		"tsc-alias": "1.4.1",
 		"tsconfig-paths": "3.14.1",
 		"twemoji-parser": "14.0.0",
 		"typeorm": "0.3.6",
-		"typescript": "4.6.3",
 		"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",
+		"ws": "8.6.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",
@@ -138,7 +133,7 @@
 		"@types/js-yaml": "4.0.5",
 		"@types/jsdom": "16.2.14",
 		"@types/jsonld": "1.5.6",
-		"@types/jsrsasign": "10.2.1",
+		"@types/jsrsasign": "10.5.1",
 		"@types/koa": "2.13.4",
 		"@types/koa-bodyparser": "4.3.7",
 		"@types/koa-cors": "0.0.2",
@@ -151,7 +146,7 @@
 		"@types/koa__multer": "2.0.4",
 		"@types/koa__router": "8.0.11",
 		"@types/mocha": "9.1.1",
-		"@types/node": "17.0.25",
+		"@types/node": "17.0.33",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.4",
 		"@types/oauth": "0.9.1",
@@ -174,6 +169,12 @@
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
+		"@typescript-eslint/eslint-plugin": "5.23.0",
+		"@typescript-eslint/parser": "5.23.0",
+		"typescript": "4.6.4",
+		"eslint": "8.15.0",
+		"eslint-plugin-import": "2.26.0",
+
 		"cross-env": "7.0.3",
 		"execa": "6.1.0"
 	}
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index fd91be84af..45aa8136b4 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -110,19 +110,19 @@
     pump "^3.0.0"
     secure-json-parse "^2.1.0"
 
-"@eslint/eslintrc@^1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
-  integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
+"@eslint/eslintrc@^1.2.3":
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.3.tgz#fcaa2bcef39e13d6e9e7f6271f4cc7cae1174886"
+  integrity sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
-    espree "^9.3.1"
+    espree "^9.3.2"
     globals "^13.9.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":
@@ -244,10 +244,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 +255,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 +277,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,10 +527,10 @@
   resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4"
   integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg==
 
-"@types/jsrsasign@10.2.1":
-  version "10.2.1"
-  resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.2.1.tgz#b82882523dfb5c476673dbef344ad838f96fb43d"
-  integrity sha512-piCIOMY0+d2wwRNcRw56VBqFYCYYeZ1c/NlUKVHTV3Y9j1RE2qpgCQvClI6yhH2sk8OoXiah43i9FmnC5tL2RQ==
+"@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"
@@ -671,10 +671,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
   integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA==
 
-"@types/node@17.0.25":
-  version "17.0.25"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.25.tgz#527051f3c2f77aa52e5dc74e45a3da5fb2301448"
-  integrity sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==
+"@types/node@17.0.33":
+  version "17.0.33"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506"
+  integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==
 
 "@types/node@^14.11.8":
   version "14.17.9"
@@ -855,14 +855,14 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
-  integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
+"@typescript-eslint/eslint-plugin@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz#bc4cbcf91fbbcc2e47e534774781b82ae25cc3d8"
+  integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/type-utils" "5.20.0"
-    "@typescript-eslint/utils" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/type-utils" "5.23.0"
+    "@typescript-eslint/utils" "5.23.0"
     debug "^4.3.2"
     functional-red-black-tree "^1.0.1"
     ignore "^5.1.8"
@@ -870,69 +870,69 @@
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
-  integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
+"@typescript-eslint/parser@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.23.0.tgz#443778e1afc9a8ff180f91b5e260ac3bec5e2de1"
+  integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/typescript-estree" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/typescript-estree" "5.23.0"
     debug "^4.3.2"
 
-"@typescript-eslint/scope-manager@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
-  integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
+"@typescript-eslint/scope-manager@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.23.0.tgz#4305e61c2c8e3cfa3787d30f54e79430cc17ce1b"
+  integrity sha512-EhjaFELQHCRb5wTwlGsNMvzK9b8Oco4aYNleeDlNuL6qXWDF47ch4EhVNPh8Rdhf9tmqbN4sWDk/8g+Z/J8JVw==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/visitor-keys" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/visitor-keys" "5.23.0"
 
-"@typescript-eslint/type-utils@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
-  integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
+"@typescript-eslint/type-utils@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
+  integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw==
   dependencies:
-    "@typescript-eslint/utils" "5.20.0"
+    "@typescript-eslint/utils" "5.23.0"
     debug "^4.3.2"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
-  integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
+"@typescript-eslint/types@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
+  integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
 
-"@typescript-eslint/typescript-estree@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
-  integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
+"@typescript-eslint/typescript-estree@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
+  integrity sha512-xE9e0lrHhI647SlGMl+m+3E3CKPF1wzvvOEWnuE3CCjjT7UiRnDGJxmAcVKJIlFgK6DY9RB98eLr1OPigPEOGg==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/visitor-keys" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/visitor-keys" "5.23.0"
     debug "^4.3.2"
     globby "^11.0.4"
     is-glob "^4.0.3"
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
-  integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
+"@typescript-eslint/utils@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
+  integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/typescript-estree" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/typescript-estree" "5.23.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
-  integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
+"@typescript-eslint/visitor-keys@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
+  integrity sha512-Vd4mFNchU62sJB8pX19ZSPog05B0Y0CE2UxAZPT5k4iqhRYjPnqyY3woMxCd0++t9OTqkgjST+1ydLBi7e2Fvg==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
     eslint-visitor-keys "^3.0.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -973,10 +973,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"
@@ -998,11 +998,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"
@@ -1278,10 +1283,10 @@ autwh@0.1.0:
   dependencies:
     oauth "0.9.15"
 
-aws-sdk@2.1120.0:
-  version "2.1120.0"
-  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1120.0.tgz#a299f595448019c4b4b69fa9aa57fd58658497a6"
-  integrity sha512-3cKXUFxC3CDBbJ/JlXEKmJZKFZhqGii7idGaLxvV5/OzqEDUstYkHGX3TCJdQRHrRwpFvRVOekXSwLxBltqXuQ==
+aws-sdk@2.1135.0:
+  version "2.1135.0"
+  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1135.0.tgz#8c14aa6894be529cb5fb7b6d19f3dc70e4f35816"
+  integrity sha512-bl9n4QgrEh52hmQ+Jo76BgJXM/p+PwfVZvImEQHFeel/33H/PDLcTJquEw5bzxM1HRNI24iH+FNPwyWLMrttTw==
   dependencies:
     buffer "4.9.2"
     events "1.1.1"
@@ -1503,10 +1508,10 @@ bufferutil@^4.0.1:
   dependencies:
     node-gyp-build "~3.7.0"
 
-bull@4.8.2:
-  version "4.8.2"
-  resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.2.tgz#0d02fe389777abe29d50fd46d123bc62e074cfcd"
-  integrity sha512-S7CNIL9+vsbLKwOGkUI6mawY5iABKQJLZn5a7KPnxAZrDhFXkrxsHHXLCKUR/+Oqys3Vk5ElWdj0SLtK84b1Nw==
+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"
@@ -2718,12 +2723,12 @@ eslint-visitor-keys@^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.14.0:
-  version "8.14.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
-  integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
+eslint@8.15.0:
+  version "8.15.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
+  integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
   dependencies:
-    "@eslint/eslintrc" "^1.2.2"
+    "@eslint/eslintrc" "^1.2.3"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -2734,7 +2739,7 @@ eslint@8.14.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"
@@ -2750,7 +2755,7 @@ eslint@8.14.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"
@@ -2764,13 +2769,13 @@ esm@^3.2.22:
   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:
@@ -3319,10 +3324,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.0.4:
+  version "12.0.4"
+  resolved "https://registry.yarnpkg.com/got/-/got-12.0.4.tgz#e3b6bf6992425f904076fd71aac7030da5122de8"
+  integrity sha512-2Eyz4iU/ktq7wtMFXxzK7g5p35uNYLLdiZarZ5/Yn3IJlNEpBd5+dCgcAyxN8/8guZLszffwe3wVyw+DEVrpBg==
   dependencies:
     "@sindresorhus/is" "^4.6.0"
     "@szmarczak/http-timer" "^5.0.1"
@@ -4180,10 +4185,10 @@ jsprim@^2.0.2:
     json-schema "0.4.0"
     verror "1.10.0"
 
-jsrsasign@10.5.19:
-  version "10.5.19"
-  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.19.tgz#61cd378190c3e65bd1a26a088696736e4437a806"
-  integrity sha512-GgOdly2Ee9nS+qxOjLkQKaoSTKqlk6lFKcKLPlNJOApoOUcqL2z+l4dAcBzYnZkA3tg+LwFOyQnqbuFn5IPdvw==
+jsrsasign@10.5.20:
+  version "10.5.20"
+  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.20.tgz#515a854a47c309cb350f32860dd37bfad1b81800"
+  integrity sha512-YHL6y8o6cnRoxwUY0eGpfvwj0pm9o0NToD4KXVp2UJC19oXSR2RgnSdSMplIRRKFovLAJGrAHqdb5MMznnhQDQ==
 
 jstransformer@1.0.0:
   version "1.0.0"
@@ -5007,10 +5012,10 @@ node-fetch@3.0.0-beta.9:
     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.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.4.tgz#3fbca2d8838111048232de54cb532bd3cf134947"
+  integrity sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==
   dependencies:
     data-uri-to-buffer "^4.0.0"
     fetch-blob "^3.1.4"
@@ -5054,10 +5059,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"
@@ -6651,10 +6656,10 @@ syslog-pro@1.0.0:
   dependencies:
     moment "^2.22.2"
 
-systeminformation@5.11.14:
-  version "5.11.14"
-  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.14.tgz#21fcb6f05d33e17d69c236b9c1b3d9c53d1d2b3a"
-  integrity sha512-m8CJx3fIhKohanB0ExTk5q53uI1J0g5B09p77kU+KxnxRVpADVqTAwCg1PFelqKsj4LHd+qmVnumb511Hg4xow==
+systeminformation@5.11.15:
+  version "5.11.15"
+  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.15.tgz#013038688e7ba375a5c8e88b8e7739bda7110e6b"
+  integrity sha512-zUbObRjQeZcu84z9NVSm9JTiCPyPQ3MefJ3+76yvp+TeCv9WsO3szijyQLv0fChRrm2/sl2De3y1ewUOYOtz2Q==
 
 tapable@^2.2.0:
   version "2.2.0"
@@ -6812,10 +6817,10 @@ 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"
@@ -6984,10 +6989,10 @@ typeorm@0.3.6:
     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.6.4:
+  version "4.6.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
+  integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
 
 ulid@2.3.0:
   version "2.3.0"
@@ -7142,10 +7147,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"
@@ -7296,10 +7301,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.6.0:
+  version "8.6.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
+  integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
 
 ws@^8.2.3:
   version "8.4.2"
diff --git a/packages/client/package.json b/packages/client/package.json
index 1d62d78d88..ef67d85218 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -12,12 +12,7 @@
 	"dependencies": {
 		"@discordapp/twemoji": "13.1.1",
 		"@fortawesome/fontawesome-free": "6.1.1",
-		"@rollup/plugin-alias": "3.1.9",
-		"@rollup/plugin-json": "4.1.0",
 		"@syuilo/aiscript": "0.11.1",
-		"@typescript-eslint/parser": "5.20.0",
-		"@vitejs/plugin-vue": "2.3.1",
-		"@vue/compiler-sfc": "3.2.33",
 		"abort-controller": "3.0.0",
 		"autobind-decorator": "2.4.0",
 		"autosize": "5.0.1",
@@ -33,11 +28,8 @@
 		"content-disposition": "0.5.4",
 		"date-fns": "2.28.0",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.14.0",
-		"eslint-plugin-vue": "8.7.1",
 		"eventemitter3": "4.0.7",
 		"feed": "4.2.2",
-		"glob": "7.2.0",
 		"idb-keyval": "6.1.0",
 		"insert-text-at-cursor": "0.3.0",
 		"json5": "2.2.1",
@@ -49,7 +41,7 @@
 		"ms": "2.1.3",
 		"nested-property": "4.0.0",
 		"parse5": "6.0.1",
-		"photoswipe": "5.2.4",
+		"photoswipe": "5.2.7",
 		"portscanner": "2.2.0",
 		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
@@ -61,31 +53,28 @@
 		"random-seed": "0.3.0",
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
-		"rollup": "2.70.2",
 		"s-age": "1.1.2",
-		"sass": "1.50.1",
+		"sass": "1.51.0",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
-		"three": "0.139.2",
+		"three": "0.140.2",
 		"throttle-debounce": "4.0.1",
 		"tinycolor2": "1.4.2",
 		"tsc-alias": "1.5.0",
 		"tsconfig-paths": "3.14.1",
 		"twemoji-parser": "14.0.0",
-		"typescript": "4.6.3",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vite": "2.9.6",
 		"vue": "3.2.33",
 		"vue-prism-editor": "2.0.0-alpha.2",
-		"vue-router": "4.0.14",
+		"vue-router": "4.0.15",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
-		"ws": "8.5.0"
+		"ws": "8.6.0"
 	},
 	"devDependencies": {
 		"@types/escape-regexp": "0.0.1",
@@ -102,14 +91,25 @@
 		"@types/qrcode": "1.4.2",
 		"@types/random-seed": "0.3.3",
 		"@types/seedrandom": "3.0.2",
-		"@types/throttle-debounce": "4.0.0",
+		"@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.20.0",
+		"@typescript-eslint/eslint-plugin": "5.23.0",
+		"@typescript-eslint/parser": "5.23.0",
+		"@vitejs/plugin-vue": "2.3.3",
+		"@vue/compiler-sfc": "3.2.33",
+		"@rollup/plugin-alias": "3.1.9",
+		"@rollup/plugin-json": "4.1.0",
+		"rollup": "2.73.0",
+		"typescript": "4.6.4",
+
+		"vite": "2.9.9",
+		"eslint": "8.15.0",
+		"eslint-plugin-vue": "8.7.1",
 		"cross-env": "7.0.3",
-		"cypress": "9.5.4",
+		"cypress": "9.6.1",
 		"eslint-plugin-import": "2.26.0",
 		"start-server-and-test": "1.14.0"
 	}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 23908a1b1e..fe4c17b52b 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -82,19 +82,19 @@
     twemoji-parser "13.1.0"
     universalify "^0.1.2"
 
-"@eslint/eslintrc@^1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.2.tgz#4989b9e8c0216747ee7cca314ae73791bb281aae"
-  integrity sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==
+"@eslint/eslintrc@^1.2.3":
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.3.tgz#fcaa2bcef39e13d6e9e7f6271f4cc7cae1174886"
+  integrity sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
-    espree "^9.3.1"
+    espree "^9.3.2"
     globals "^13.9.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":
@@ -356,10 +356,10 @@
   resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
   integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
 
-"@types/throttle-debounce@4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ae62a652c914f46276c78730f53fab85b4825f09"
-  integrity sha512-BZ1Y2nf1U3qj9MNWrlTG3+FWfFS8xfG2DA0ypiZ1pBUiO8WEvEjNTmQuecnYDtQvegRojeKh+iwNK2NjJ8a3YQ==
+"@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"
@@ -421,14 +421,14 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.20.0.tgz#022531a639640ff3faafaf251d1ce00a2ef000a1"
-  integrity sha512-fapGzoxilCn3sBtC6NtXZX6+P/Hef7VDbyfGqTTpzYydwhlkevB+0vE0EnmHPVTVSy68GUncyJ/2PcrFBeCo5Q==
+"@typescript-eslint/eslint-plugin@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz#bc4cbcf91fbbcc2e47e534774781b82ae25cc3d8"
+  integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/type-utils" "5.20.0"
-    "@typescript-eslint/utils" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/type-utils" "5.23.0"
+    "@typescript-eslint/utils" "5.23.0"
     debug "^4.3.2"
     functional-red-black-tree "^1.0.1"
     ignore "^5.1.8"
@@ -436,69 +436,69 @@
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.20.0.tgz#4991c4ee0344315c2afc2a62f156565f689c8d0b"
-  integrity sha512-UWKibrCZQCYvobmu3/N8TWbEeo/EPQbS41Ux1F9XqPzGuV7pfg6n50ZrFo6hryynD8qOTTfLHtHjjdQtxJ0h/w==
+"@typescript-eslint/parser@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.23.0.tgz#443778e1afc9a8ff180f91b5e260ac3bec5e2de1"
+  integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/typescript-estree" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/typescript-estree" "5.23.0"
     debug "^4.3.2"
 
-"@typescript-eslint/scope-manager@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.20.0.tgz#79c7fb8598d2942e45b3c881ced95319818c7980"
-  integrity sha512-h9KtuPZ4D/JuX7rpp1iKg3zOH0WNEa+ZIXwpW/KWmEFDxlA/HSfCMhiyF1HS/drTICjIbpA6OqkAhrP/zkCStg==
+"@typescript-eslint/scope-manager@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.23.0.tgz#4305e61c2c8e3cfa3787d30f54e79430cc17ce1b"
+  integrity sha512-EhjaFELQHCRb5wTwlGsNMvzK9b8Oco4aYNleeDlNuL6qXWDF47ch4EhVNPh8Rdhf9tmqbN4sWDk/8g+Z/J8JVw==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/visitor-keys" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/visitor-keys" "5.23.0"
 
-"@typescript-eslint/type-utils@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.20.0.tgz#151c21cbe9a378a34685735036e5ddfc00223be3"
-  integrity sha512-WxNrCwYB3N/m8ceyoGCgbLmuZwupvzN0rE8NBuwnl7APgjv24ZJIjkNzoFBXPRCGzLNkoU/WfanW0exvp/+3Iw==
+"@typescript-eslint/type-utils@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
+  integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw==
   dependencies:
-    "@typescript-eslint/utils" "5.20.0"
+    "@typescript-eslint/utils" "5.23.0"
     debug "^4.3.2"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.20.0.tgz#fa39c3c2aa786568302318f1cb51fcf64258c20c"
-  integrity sha512-+d8wprF9GyvPwtoB4CxBAR/s0rpP25XKgnOvMf/gMXYDvlUC3rPFHupdTQ/ow9vn7UDe5rX02ovGYQbv/IUCbg==
+"@typescript-eslint/types@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
+  integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
 
-"@typescript-eslint/typescript-estree@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.20.0.tgz#ab73686ab18c8781bbf249c9459a55dc9417d6b0"
-  integrity sha512-36xLjP/+bXusLMrT9fMMYy1KJAGgHhlER2TqpUVDYUQg4w0q/NW/sg4UGAgVwAqb8V4zYg43KMUpM8vV2lve6w==
+"@typescript-eslint/typescript-estree@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
+  integrity sha512-xE9e0lrHhI647SlGMl+m+3E3CKPF1wzvvOEWnuE3CCjjT7UiRnDGJxmAcVKJIlFgK6DY9RB98eLr1OPigPEOGg==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/visitor-keys" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/visitor-keys" "5.23.0"
     debug "^4.3.2"
     globby "^11.0.4"
     is-glob "^4.0.3"
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.20.0.tgz#b8e959ed11eca1b2d5414e12417fd94cae3517a5"
-  integrity sha512-lHONGJL1LIO12Ujyx8L8xKbwWSkoUKFSO+0wDAqGXiudWB2EO7WEUT+YZLtVbmOmSllAjLb9tpoIPwpRe5Tn6w==
+"@typescript-eslint/utils@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
+  integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.20.0"
-    "@typescript-eslint/types" "5.20.0"
-    "@typescript-eslint/typescript-estree" "5.20.0"
+    "@typescript-eslint/scope-manager" "5.23.0"
+    "@typescript-eslint/types" "5.23.0"
+    "@typescript-eslint/typescript-estree" "5.23.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.20.0":
-  version "5.20.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.20.0.tgz#70236b5c6b67fbaf8b2f58bf3414b76c1e826c2a"
-  integrity sha512-1flRpNF+0CAQkMNlTJ6L/Z5jiODG/e5+7mk6XwtPOUS3UrTz3UOiAg9jG2VtKsWI6rZQfy4C6a232QNRZTRGlg==
+"@typescript-eslint/visitor-keys@5.23.0":
+  version "5.23.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
+  integrity sha512-Vd4mFNchU62sJB8pX19ZSPog05B0Y0CE2UxAZPT5k4iqhRYjPnqyY3woMxCd0++t9OTqkgjST+1ydLBi7e2Fvg==
   dependencies:
-    "@typescript-eslint/types" "5.20.0"
+    "@typescript-eslint/types" "5.23.0"
     eslint-visitor-keys "^3.0.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -506,10 +506,10 @@
   resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
   integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
 
-"@vitejs/plugin-vue@2.3.1":
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.1.tgz#5f286b8d3515381c6d5c8fa8eee5e6335f727e14"
-  integrity sha512-YNzBt8+jt6bSwpt7LP890U1UcTOIZZxfpE5WOJ638PNxSEKOqAi0+FSKS0nVeukfdZ0Ai/H7AFd6k3hayfGZqQ==
+"@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.33":
   version "3.2.33"
@@ -618,6 +618,11 @@ acorn-jsx@^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"
@@ -628,10 +633,10 @@ acorn@^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"
@@ -1163,10 +1168,10 @@ csstype@^2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
 
-cypress@9.5.4:
-  version "9.5.4"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.4.tgz#49d9272f62eba12f2314faf29c2a865610e87550"
-  integrity sha512-6AyJAD8phe7IMvOL4oBsI9puRNOWxZjl8z1lgixJMcgJ85JJmyKeP6uqNA0dI1z14lmJ7Qklf2MOgP/xdAqJ/Q==
+cypress@9.6.1:
+  version "9.6.1"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.6.1.tgz#a7d6b5a53325b3dc4960181f5800a5ade0f085eb"
+  integrity sha512-ECzmV7pJSkk+NuAhEw6C3D+RIRATkSb2VAHXDY6qGZbca/F9mv5pPsj2LO6Ty6oIFVBTrwCyL9agl28MtJMe2g==
   dependencies:
     "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
@@ -1681,12 +1686,12 @@ eslint-visitor-keys@^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.14.0:
-  version "8.14.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.14.0.tgz#62741f159d9eb4a79695b28ec4989fcdec623239"
-  integrity sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==
+eslint@8.15.0:
+  version "8.15.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
+  integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
   dependencies:
-    "@eslint/eslintrc" "^1.2.2"
+    "@eslint/eslintrc" "^1.2.3"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -1697,7 +1702,7 @@ eslint@8.14.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"
@@ -1713,7 +1718,7 @@ eslint@8.14.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"
@@ -1731,13 +1736,13 @@ espree@^9.0.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==
+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"
 
 esquery@^1.4.0:
@@ -3268,10 +3273,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"
@@ -3314,7 +3319,7 @@ postcss-selector-parser@^6.0.9:
     cssesc "^3.0.0"
     util-deprecate "^1.0.2"
 
-postcss@^8.1.10, postcss@^8.4.12:
+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==
@@ -3634,7 +3639,14 @@ rndstr@1.0.0:
     rangestr "0.0.1"
     seedrandom "2.4.2"
 
-rollup@2.70.2, rollup@^2.59.0:
+rollup@2.73.0:
+  version "2.73.0"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.73.0.tgz#128fef4b333fd92d02d6929afbb6ee38d7feb32d"
+  integrity sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==
+  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==
@@ -3685,10 +3697,10 @@ 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@1.50.1:
-  version "1.50.1"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.1.tgz#e9b078a1748863013c4712d2466ce8ca4e4ed292"
-  integrity sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==
+sass@1.51.0:
+  version "1.51.0"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.51.0.tgz#25ea36cf819581fe1fe8329e8c3a4eaaf70d2845"
+  integrity sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
@@ -3946,10 +3958,10 @@ 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.140.2:
+  version "0.140.2"
+  resolved "https://registry.yarnpkg.com/three/-/three-0.140.2.tgz#ca0b7390d1ce4f7f2850e80afd00ef48fc244491"
+  integrity sha512-DdT/AHm/TbZXEhQKQpGt5/iSgBrmXpjU26FNtj1KhllVPTKj1eG4X/ShyD5W2fngE+I1s1wa4ttC4C3oCJt7Ag==
 
 throttle-debounce@4.0.1:
   version "4.0.1"
@@ -4098,10 +4110,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.6.4:
+  version "4.6.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
+  integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
 
 unbox-primitive@^1.0.1:
   version "1.0.1"
@@ -4189,13 +4201,13 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vite@2.9.6:
-  version "2.9.6"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.6.tgz#29f1b33193b0de9e155d67ba0dd097501c3c3281"
-  integrity sha512-3IffdrByHW95Yjv0a13TQOQfJs7L5dVlSPuTt432XLbRMriWbThqJN2k/IS6kXn5WY4xBLhK9XoaWay1B8VzUw==
+vite@2.9.9:
+  version "2.9.9"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
+  integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
   dependencies:
     esbuild "^0.14.27"
-    postcss "^8.4.12"
+    postcss "^8.4.13"
     resolve "^1.22.0"
     rollup "^2.59.0"
   optionalDependencies:
@@ -4224,10 +4236,10 @@ vue-prism-editor@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.15:
+  version "4.0.15"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.15.tgz#b4a0661efe197f8c724e0f233308f8776e2c3667"
+  integrity sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==
   dependencies:
     "@vue/devtools-api" "^6.0.0"
 
@@ -4345,10 +4357,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.6.0:
+  version "8.6.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
+  integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
 
 xml-js@^1.6.11:
   version "1.6.11"

From 98e42ec6ffd245f522bc1cbceda0d3f84d3330b5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 14 May 2022 15:00:15 +0900
Subject: [PATCH 095/258] enhance: Display TOTP Register URL

Close #7261

Co-Authored-By: tamaina <tamaina@hotmail.co.jp>
---
 locales/ja-JP.yml                                         | 1 +
 .../backend/src/server/api/endpoints/i/2fa/register.ts    | 8 +++++---
 packages/client/src/pages/settings/2fa.vue                | 5 +++--
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 4d04cd28c8..c52762bf38 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1157,6 +1157,7 @@ _2fa:
   registerKey: "キーを登録"
   step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
   step2: "次に、表示されているQRコードをアプリでスキャンします。"
+  step2Url: "デスクトップアプリでは次のURLを入力します:"
   step3: "アプリに表示されているトークンを入力して完了です。"
   step4: "これからログインするときも、同じようにトークンを入力します。"
   securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
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/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 9ebf5101cd..01dd9b74a2 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -52,8 +52,9 @@
 					</template>
 				</I18n>
 			</li>
-			<li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"></li>
-			<li>{{ i18n.ts._2fa.step3 }}<br>
+			<li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ data.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>

From 4b872856c2c79ea4e604af481cd2c78487993d88 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 14 May 2022 08:09:10 +0200
Subject: [PATCH 096/258] fix: keep file order (#8659)

---
 .../backend/src/server/api/endpoints/notes/create.ts | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 40a3ba73ca..ff62841a0c 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -172,10 +172,14 @@ 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")')
+			.setParameters({ fileIds })
+			.getMany();
 	}
 
 	let renote: Note | null = null;

From 22bb1a17930bdbcf9a2068790efd880fe8ff4158 Mon Sep 17 00:00:00 2001
From: iwata <ishowta@gmail.com>
Date: Sat, 14 May 2022 15:16:45 +0900
Subject: [PATCH 097/258] =?UTF-8?q?test:=20e2e=E3=83=86=E3=82=B9=E3=83=88?=
 =?UTF-8?q?=E3=81=8CCI=E3=81=A7=E5=A4=B1=E6=95=97=E3=81=97=E3=81=A6?=
 =?UTF-8?q?=E3=81=84=E3=81=9F=E5=95=8F=E9=A1=8C=E3=82=92=E3=81=84=E3=81=8F?=
 =?UTF-8?q?=E3=81=A4=E3=81=8B=E4=BF=AE=E6=AD=A3=20(#8642)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* test: indexeddbをテスト毎に初期化するように

* fix: metaが無いときにfetch-metaを同時に呼ぶと死ぬことがある問題を修正

* test: ログイン後のクライアント側処理を待たずにリロードされてログイン出来ないことがあったのを修正
---
 cypress/integration/basic.js            | 16 +++++++++++++---
 packages/backend/src/misc/fetch-meta.ts | 13 ++++++++++---
 2 files changed, 23 insertions(+), 6 deletions(-)

diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js
index 7d27b649f4..eb15cfe223 100644
--- a/cypress/integration/basic.js
+++ b/cypress/integration/basic.js
@@ -1,5 +1,8 @@
 describe('Before setup instance', () => {
 	beforeEach(() => {
+		cy.window(win => {
+			win.indexedDB.deleteDatabase('keyval-store');
+		});
 		cy.request('POST', '/api/reset-db').as('reset');
 		cy.get('@reset').its('status').should('equal', 204);
 		cy.reload(true);
@@ -32,6 +35,9 @@ describe('Before setup instance', () => {
 
 describe('After setup instance', () => {
 	beforeEach(() => {
+		cy.window(win => {
+			win.indexedDB.deleteDatabase('keyval-store');
+		});
 		cy.request('POST', '/api/reset-db').as('reset');
 		cy.get('@reset').its('status').should('equal', 204);
 		cy.reload(true);
@@ -70,6 +76,9 @@ describe('After setup instance', () => {
 
 describe('After user signup', () => {
 	beforeEach(() => {
+		cy.window(win => {
+			win.indexedDB.deleteDatabase('keyval-store');
+		});
 		cy.request('POST', '/api/reset-db').as('reset');
 		cy.get('@reset').its('status').should('equal', 204);
 		cy.reload(true);
@@ -129,6 +138,9 @@ describe('After user signup', () => {
 
 describe('After user singed in', () => {
 	beforeEach(() => {
+		cy.window(win => {
+			win.indexedDB.deleteDatabase('keyval-store');
+		});
 		cy.request('POST', '/api/reset-db').as('reset');
 		cy.get('@reset').its('status').should('equal', 204);
 		cy.reload(true);
@@ -163,12 +175,10 @@ describe('After user singed in', () => {
 	});
 
   it('successfully loads', () => {
-    cy.visit('/');
+		cy.get('[data-cy-open-post-form]').should('be.visible');
   });
 
 	it('note', () => {
-    cy.visit('/');
-
 		cy.get('[data-cy-open-post-form]').click();
 		cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
 		cy.get('[data-cy-open-post-form-submit]').click();
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;

From b2a5076d14e84fc427e9ab59ae373b04ddfbf40c Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 14 May 2022 15:24:44 +0900
Subject: [PATCH 098/258] =?UTF-8?q?fix:=20=E3=83=A6=E3=83=BC=E3=82=B6?=
 =?UTF-8?q?=E3=83=BC=E6=A4=9C=E7=B4=A2=E3=81=A7=E3=80=81=E3=82=AF=E3=82=A8?=
 =?UTF-8?q?=E3=83=AA=E3=81=8Cusername=E3=81=AE=E6=9D=A1=E4=BB=B6=E3=82=92?=
 =?UTF-8?q?=E6=BA=80=E3=81=9F=E3=81=99=E5=A0=B4=E5=90=88=E3=81=AFusername?=
 =?UTF-8?q?=E3=82=82LIKE=E6=A4=9C=E7=B4=A2=E3=81=99=E3=82=8B=E3=82=88?=
 =?UTF-8?q?=E3=81=86=E3=81=AB=20(#8644)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix #8643

* 部分一致にする
---
 .../backend/src/server/api/endpoints/users/search.ts     | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index a72a58a843..f93d4f718b 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -61,7 +61,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 });

From ebb4308a5c4bdf15ddc6e77cd1a3c4c04c8c8bc1 Mon Sep 17 00:00:00 2001
From: iwata <ishowta@gmail.com>
Date: Sat, 14 May 2022 16:09:47 +0900
Subject: [PATCH 099/258] =?UTF-8?q?test:=20`=5F=5Fdirname`=E3=81=AFESModul?=
 =?UTF-8?q?e=E3=81=A7=E3=81=AF=E4=BD=BF=E3=81=88=E3=81=AA=E3=81=84?=
 =?UTF-8?q?=E3=81=AE=E3=81=A7=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=E3=81=9F?=
 =?UTF-8?q?=20(#8626)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/backend/.eslintrc.cjs         | 13 ++++++++++++-
 packages/backend/test/.eslintrc        |  7 -------
 packages/backend/test/.eslintrc.cjs    | 11 +++++++++++
 packages/backend/test/get-file-info.ts | 25 +++++++++++++++----------
 packages/backend/test/user-notes.ts    |  9 +++++++--
 packages/backend/test/utils.ts         | 11 ++++++++---
 6 files changed, 53 insertions(+), 23 deletions(-)
 delete mode 100644 packages/backend/test/.eslintrc
 create mode 100644 packages/backend/test/.eslintrc.cjs

diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs
index dfc9d04950..5a06889dcd 100644
--- a/packages/backend/.eslintrc.cjs
+++ b/packages/backend/.eslintrc.cjs
@@ -16,6 +16,17 @@ module.exports = {
 					'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/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/get-file-info.ts b/packages/backend/test/get-file-info.ts
index 20061b8708..c0fa8ed143 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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.ts
index 25ffe04756..f99d7aeff2 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,8 +20,8 @@ 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]
 		});
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 09e812f437..0a495b3391 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -1,4 +1,6 @@
 import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
 import * as WebSocket from 'ws';
 import * as misskey from 'misskey-js';
 import fetch from 'node-fetch';
@@ -9,6 +11,9 @@ 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;
 
@@ -72,7 +77,7 @@ export const react = async (user: any, note: any, reaction: string): Promise<any
 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'));
+		formData.append('file', fs.createReadStream(path || _dirname + '/resources/Lenna.png'));
 
 		return fetch(`http://localhost:${port}/api/drive/files/create`, {
 			method: 'post',
@@ -139,7 +144,7 @@ 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 }
 		});
@@ -178,7 +183,7 @@ 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 }
 		});

From 67e1ee41c91558b9c3c8ac95ef52336b25f15b5e Mon Sep 17 00:00:00 2001
From: iwata <ishowta@gmail.com>
Date: Sat, 14 May 2022 16:10:20 +0900
Subject: [PATCH 100/258] =?UTF-8?q?test:=20Node=E3=81=AE=E3=82=AB=E3=82=B9?=
 =?UTF-8?q?=E3=82=BF=E3=83=A0=E3=83=AD=E3=83=BC=E3=83=80=E3=83=BC=E3=82=92?=
 =?UTF-8?q?=E7=9B=B4=E3=81=97=E3=81=A6=E3=83=86=E3=82=B9=E3=83=88=E3=81=8C?=
 =?UTF-8?q?=E5=8B=95=E3=81=8F=E3=82=88=E3=81=86=E3=81=AB=20(#8625)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* test: Nodeのカスタムローダーを直してテストが動くように

* dev: mochaを呼ぶコマンドにNODE_ENV=testを追加

* Update packages/backend/test/loader.js

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* chore: change export style in loader.js

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 package.json                    |  2 +-
 packages/backend/package.json   |  2 +-
 packages/backend/test/loader.js | 61 ++++++++++++++++-----------------
 3 files changed, 31 insertions(+), 34 deletions(-)

diff --git a/package.json b/package.json
index 9c3c39bf93..850a52ceb6 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
 		"cy:open": "cypress open",
 		"cy:run": "cypress run",
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
-		"mocha": "cd packages/backend && cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
+		"mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
 		"test": "npm run mocha",
 		"format": "gulp format",
 		"clean": "node ./scripts/clean.js",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c06f48f546..6467588956 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": {
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 };

From e0cce893bec31af050bf6df8c61512303569c5ac Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 14 May 2022 18:50:16 +0900
Subject: [PATCH 101/258] Update extensions.json

---
 .vscode/extensions.json | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 9adb0d0697..42264548ea 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,6 +3,7 @@
 		"editorconfig.editorconfig",
 		"eg2.vscode-npm-script",
 		"dbaeumer.vscode-eslint",
-		"johnsoncodehk.volar",
+		"Vue.volar",
+		"Vue.vscode-typescript-vue-plugin"
 	]
 }

From 88307327e6e9999f4e8e2be48749b5c91c1a10dd Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:20:41 +0200
Subject: [PATCH 102/258] Refactor Chart component (#8622)

* refactor(client): refactor Chart component

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* fix(client): don't expose values from Chart

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/components/chart.vue | 1582 +++++++++++-----------
 1 file changed, 789 insertions(+), 793 deletions(-)

diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue
index a7d5206c71..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,
@@ -36,6 +41,46 @@ 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,
@@ -80,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 = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
 
-			const maxes = data.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),
-								},
-							},
-						},*/
-						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,
+	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),
 						},
 					},
-					scales: {
+				},*/
+				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,
+				},
+			},
+			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,
+					},
+				},
+			},
+			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,
+						},
+						pinch: {
+							enabled: true,
+						},
+						drag: {
+							enabled: false,
+						},
+						mode: 'x',
+					},
+					limits: {
 						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(),
+							min: 'original',
+							max: 'original',
 						},
 						y: {
-							position: 'left',
-							stacked: props.stacked,
-							suggestedMax: 50,
-							grid: {
-								color: gridColor,
-								borderColor: 'rgb(0, 0, 0, 0)',
-							},
-							ticks: {
-								display: props.detailed,
-								//mirror: true,
-							},
+							min: 'original',
+							max: 'original',
 						},
-					},
-					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,
-								},
-								pinch: {
-									enabled: true,
-								},
-								drag: {
-									enabled: false,
-								},
-								mode: 'x',
-							},
-							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;
-
-							ctx.save();
-							ctx.beginPath();
-							ctx.moveTo(x, bottomY);
-							ctx.lineTo(x, topY);
-							ctx.lineWidth = 1;
-							ctx.strokeStyle = vLineColor;
-							ctx.stroke();
-							ctx.restore();
-						}
 					}
-				}]
-			});
-		};
+				} : 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
-		};
-
-		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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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);
-
-					case 'per-user-notes': return fetchPerUserNotesChart();
-					case 'per-user-following': return fetchPerUserFollowingChart();
-					case 'per-user-followers': return fetchPerUserFollowersChart();
-					case 'per-user-drive': return fetchPerUserDriveChart();
+					ctx.save();
+					ctx.beginPath();
+					ctx.moveTo(x, bottomY);
+					ctx.lineTo(x, topY);
+					ctx.lineWidth = 1;
+					ctx.strokeStyle = vLineColor;
+					ctx.stroke();
+					ctx.restore();
 				}
-			};
-			fetching.value = true;
-			data = await fetchData();
-			fetching.value = false;
-			render();
-		};
+			}
+		}]
+	});
+};
 
-		watch(() => [props.src, props.span], fetchAndRender);
+const exportData = () => {
+	// TODO
+};
 
-		onMounted(() => {
-			fetchAndRender();
-		});
+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,
+		}],
+	};
+};
 
-		onUnmounted(() => {
-			if (disposeTooltipComponent) disposeTooltipComponent();
-		});
+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)
+		}]
+	};
+};
 
-		return {
-			chartEl,
-			fetching,
-		};
-	},
+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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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),
+		}],
+	};
+};
+
+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);
+
+			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();
+};
+
+watch(() => [props.src, props.span], fetchAndRender);
+
+onMounted(() => {
+	fetchAndRender();
 });
+
+onUnmounted(() => {
+	if (disposeTooltipComponent) disposeTooltipComponent();
+});
+/* eslint-enable id-denylist */
 </script>
 
 <style lang="scss" scoped>

From cafd29888dc889eb0870634ec7fbf7a457f65295 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:33:41 +0200
Subject: [PATCH 103/258] refactor(client): refactor admin/ads to use
 Composition API (#8649)

---
 packages/client/src/pages/admin/ads.vue | 144 +++++++++++-------------
 1 file changed, 64 insertions(+), 80 deletions(-)

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,
-			});
-		},
+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()
+		});
+	}
+}
 
-		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
-				});
-			});
-		},
-
-		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>

From 9f07bd8f46ce325ac422e2562eb7be603f2910c5 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:34:07 +0200
Subject: [PATCH 104/258] refactor(client): refactor admin/announcements to use
 Composition API (#8650)

---
 .../client/src/pages/admin/announcements.vue  | 148 ++++++++----------
 1 file changed, 67 insertions(+), 81 deletions(-)

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);
+	});
+}
+
+function save(announcement) {
+	if (announcement.id == null) {
+		os.api('admin/announcements/create', announcement).then(() => {
+			os.alert({
+				type: 'success',
+				text: i18n.ts.saved
+			});
+		}).catch(err => {
+			os.alert({
+				type: 'error',
+				text: err
+			});
 		});
-	},
-
-	methods: {
-		add() {
-			this.announcements.unshift({
-				id: null,
-				title: '',
-				text: '',
-				imageUrl: null
+	} else {
+		os.api('admin/announcements/update', 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
 			});
-		},
+		});
+	}
+}
 
-		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>

From 5de77405ea8a4d325eb036134880ba1272be26de Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:34:50 +0200
Subject: [PATCH 105/258] Refactor admin/security to use Composition API
 (#8652)

* refactor(client): refactor admin/security to use Composition API

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/pages/admin/bot-protection.vue | 89 +++++++------------
 packages/client/src/pages/admin/security.vue  | 71 ++++++---------
 2 files changed, 59 insertions(+), 101 deletions(-)

diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue
index 5e0cdd96a5..4675b2bc08 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/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>

From 577f63c4f4d56b7d6fda6ca042821e7a0667d521 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:35:08 +0200
Subject: [PATCH 106/258] Refactor admin/database to use Composition API
 (#8654)

* refactor(client): refactor admin/database to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/pages/admin/database.vue | 31 ++++++--------------
 1 file changed, 9 insertions(+), 22 deletions(-)

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>

From 3f9b7e8b1d7a430abd3858b8e976c86eb9a6301b Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 14:36:12 +0200
Subject: [PATCH 107/258] Refactor admin/email-settings to use Composition API
 (#8656)

* refactor(client): refactor admin/email-settings to use Composition API

* Update packages/client/src/pages/admin/email-settings.vue

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/pages/admin/email-settings.vue | 161 ++++++++----------
 1 file changed, 73 insertions(+), 88 deletions(-)

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>

From 657dc1599596f39a254fe6b37a2c4c631282409b Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 14 May 2022 16:24:45 +0200
Subject: [PATCH 108/258] fix(client): remove unexpected token (#8672)

---
 packages/client/src/pages/admin/bot-protection.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue
index 4675b2bc08..30fee5015a 100644
--- a/packages/client/src/pages/admin/bot-protection.vue
+++ b/packages/client/src/pages/admin/bot-protection.vue
@@ -56,7 +56,7 @@ import { fetchInstance } from '@/instance';
 
 const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue'));
 
-let provider: = $ref(null);
+let provider = $ref(null);
 let hcaptchaSiteKey: string | null = $ref(null);
 let hcaptchaSecretKey: string | null = $ref(null);
 let recaptchaSiteKey: string | null = $ref(null);

From 6de40cf789de7cd8856e34d92d039d6d2affdbcc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 01:16:12 +0900
Subject: [PATCH 109/258] fix(server): prevent crash when processing certain
 PNGs

Fix #8605
---
 packages/backend/package.json |  2 +-
 packages/backend/yarn.lock    | 46 +++++++++++++++++------------------
 2 files changed, 24 insertions(+), 24 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 6467588956..88df79ab21 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -97,7 +97,7 @@
 		"s-age": "1.1.2",
 		"sanitize-html": "2.7.0",
 		"semver": "7.3.7",
-		"sharp": "0.30.4",
+		"sharp": "0.29.3",
 		"speakeasy": "2.0.0",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 45aa8136b4..936b362c25 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1872,7 +1872,7 @@ 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.3:
+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==
@@ -2279,16 +2279,16 @@ detect-file@^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"
@@ -4985,7 +4985,7 @@ node-addon-api@^1.2.0:
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
   integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
 
-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==
@@ -5580,10 +5580,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"
@@ -6227,7 +6227,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.7, 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==
@@ -6288,17 +6288,17 @@ sha.js@^2.4.11:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
-sharp@0.30.4:
-  version "0.30.4"
-  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.4.tgz#73d9daa63bbc20da189c9328d75d5d395fc8fb73"
-  integrity sha512-3Onig53Y6lji4NIZo69s14mERXXY/GV++6CzOYx/Rd8bnTwbhFbL09WZd7Ag/CCnA0WxFID8tkY0QReyfL6v0Q==
+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.3"
-    detect-libc "^2.0.1"
-    node-addon-api "^4.3.0"
-    prebuild-install "^7.0.1"
-    semver "^7.3.7"
-    simple-get "^4.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.0"
     tar-fs "^2.1.1"
     tunnel-agent "^0.6.0"
 
@@ -6343,7 +6343,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==

From b21b0580058c14532ff3f4033e2a9147643bfca6 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 12:18:46 +0900
Subject: [PATCH 110/258] feat: make captcha required when signin to improve
 security

---
 .../backend/src/server/api/private/signin.ts  | 25 ++++++++++++++++---
 packages/client/src/components/signin.vue     | 17 ++++++++++---
 packages/client/src/components/signup.vue     |  6 ++---
 3 files changed, 38 insertions(+), 10 deletions(-)

diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index 7b66657ad8..e8b222a4d5 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -1,20 +1,37 @@
+import { randomBytes } from 'node:crypto';
 import Koa from 'koa';
 import bcrypt from 'bcryptjs';
 import * as speakeasy from 'speakeasy';
-import signin from '../common/signin.js';
+import { IsNull } from 'typeorm';
 import config from '@/config/index.js';
 import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js';
 import { ILocalUser } from '@/models/entities/user.js';
 import { genId } from '@/misc/gen-id.js';
+import { fetchMeta } from '@/misc/fetch-meta.js';
+import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js';
 import { verifyLogin, hash } from '../2fa.js';
-import { randomBytes } from 'node:crypto';
-import { IsNull } from 'typeorm';
+import signin from '../common/signin.js';
 
 export default async (ctx: Koa.Context) => {
 	ctx.set('Access-Control-Allow-Origin', config.url);
 	ctx.set('Access-Control-Allow-Credentials', 'true');
 
 	const body = ctx.request.body as any;
+
+	const instance = await fetchMeta(true);
+
+	if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
+		await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => {
+			ctx.throw(400, e);
+		});
+	}
+
+	if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
+		await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => {
+			ctx.throw(400, e);
+		});
+	}
+
 	const username = body['username'];
 	const password = body['password'];
 	const token = body['token'];
@@ -155,7 +172,7 @@ export default async (ctx: Koa.Context) => {
 				body.credentialId
 					.replace(/-/g, '+')
 					.replace(/_/g, '/'),
-					'base64'
+				'base64',
 			).toString('hex'),
 		});
 
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index bdf247a56f..4f88e1829c 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -33,6 +33,8 @@
 					<template #label>{{ $ts.token }}</template>
 					<template #prefix><i class="fas fa-gavel"></i></template>
 				</MkInput>
+				<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 type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 			</div>
 		</div>
@@ -60,6 +62,7 @@ export default defineComponent({
 	components: {
 		MkButton,
 		MkInput,
+		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
 	},
 
 	props: {
@@ -90,6 +93,8 @@ export default defineComponent({
 			credential: null,
 			challengeData: null,
 			queryingKey: false,
+			hCaptchaResponse: null,
+			reCaptchaResponse: null,
 		};
 	},
 
@@ -139,11 +144,13 @@ export default defineComponent({
 				return os.api('signin', {
 					username: this.username,
 					password: this.password,
+					'hcaptcha-response': this.hCaptchaResponse,
+					'g-recaptcha-response': this.reCaptchaResponse,
 					signature: hexify(credential.response.signature),
 					authenticatorData: hexify(credential.response.authenticatorData),
 					clientDataJSON: hexify(credential.response.clientDataJSON),
 					credentialId: credential.id,
-					challengeId: this.challengeData.challengeId
+					challengeId: this.challengeData.challengeId,
 				});
 			}).then(res => {
 				this.$emit('login', res);
@@ -164,7 +171,9 @@ export default defineComponent({
 				if (window.PublicKeyCredential && this.user.securityKeys) {
 					os.api('signin', {
 						username: this.username,
-						password: this.password
+						password: this.password,
+						'hcaptcha-response': this.hCaptchaResponse,
+						'g-recaptcha-response': this.reCaptchaResponse,
 					}).then(res => {
 						this.totpLogin = true;
 						this.signing = false;
@@ -179,7 +188,9 @@ export default defineComponent({
 				os.api('signin', {
 					username: this.username,
 					password: this.password,
-					token: this.user && this.user.twoFactorEnabled ? this.token : undefined
+					'hcaptcha-response': this.hCaptchaResponse,
+					'g-recaptcha-response': this.reCaptchaResponse,
+					token: this.user && this.user.twoFactorEnabled ? this.token : undefined,
 				}).then(res => {
 					this.$emit('login', res);
 					this.onLogin(res);
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index 62f370ffa8..aeed0e53fa 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -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>
@@ -81,7 +81,7 @@ export default defineComponent({
 		MkButton,
 		MkInput,
 		MkSwitch,
-		captcha: defineAsyncComponent(() => import('./captcha.vue')),
+		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
 	},
 
 	props: {

From 9783f2de67ff7aaa6476c25e30c9eaacc8a93914 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 16:39:23 +0900
Subject: [PATCH 111/258] feat(dev): okteto integration

---
 chart/Chart.yaml               |   3 +
 chart/files/default.yml        | 166 +++++++++++++++++++++++++++++++++
 chart/templates/ConfigMap.yml  |   7 ++
 chart/templates/Deployment.yml |  44 +++++++++
 chart/templates/Service.yml    |  14 +++
 chart/templates/_helpers.tpl   |  62 ++++++++++++
 docker-compose.yml             |   2 +-
 okteto-pipeline.yml            |   3 +
 8 files changed, 300 insertions(+), 1 deletion(-)
 create mode 100644 chart/Chart.yaml
 create mode 100644 chart/files/default.yml
 create mode 100644 chart/templates/ConfigMap.yml
 create mode 100644 chart/templates/Deployment.yml
 create mode 100644 chart/templates/Service.yml
 create mode 100644 chart/templates/_helpers.tpl
 create mode 100644 okteto-pipeline.yml

diff --git a/chart/Chart.yaml b/chart/Chart.yaml
new file mode 100644
index 0000000000..8f31cf7fb4
--- /dev/null
+++ b/chart/Chart.yaml
@@ -0,0 +1,3 @@
+apiVersion: v2
+name: misskey
+version: 0.0.0
diff --git a/chart/files/default.yml b/chart/files/default.yml
new file mode 100644
index 0000000000..276e9f8915
--- /dev/null
+++ b/chart/files/default.yml
@@ -0,0 +1,166 @@
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# Misskey configuration
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+#   ┌─────┐
+#───┘ URL └─────────────────────────────────────────────────────
+
+# Final accessible URL seen by a user.
+url: https://example.tld/
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# URL SETTINGS AFTER THAT!
+
+#   ┌───────────────────────┐
+#───┘ Port and TLS settings └───────────────────────────────────
+
+#
+# Misskey supports two deployment options for public.
+#
+
+# Option 1: With Reverse Proxy
+#
+#                 +----- https://example.tld/ ------------+
+#   +------+      |+-------------+      +----------------+|
+#   | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+#   +------+      |+-------------+      +----------------+|
+#                 +---------------------------------------+
+#
+#   You need to setup reverse proxy. (eg. nginx)
+#   You do not define 'https' section.
+
+# Option 2: Standalone
+#
+#                 +- https://example.tld/ -+
+#   +------+      |   +---------------+    |
+#   | User | ---> |   | Misskey (443) |    |
+#   +------+      |   +---------------+    |
+#                 +------------------------+
+#
+#   You need to run Misskey as root.
+#   You need to set Certificate in 'https' section.
+
+# To use option 1, uncomment below line.
+port: 3000    # A port that your Misskey server should listen.
+
+# To use option 2, uncomment below lines.
+#port: 443
+
+#https:
+#  # path for certification
+#  key: /etc/letsencrypt/live/example.tld/privkey.pem
+#  cert: /etc/letsencrypt/live/example.tld/fullchain.pem
+
+#   ┌──────────────────────────┐
+#───┘ PostgreSQL configuration └────────────────────────────────
+
+db:
+  host: localhost
+  port: 5432
+
+  # Database name
+  db: misskey
+
+  # Auth
+  user: example-misskey-user
+  pass: example-misskey-pass
+
+  # Whether disable Caching queries
+  #disableCache: true
+
+  # Extra Connection options
+  #extra:
+  #  ssl: true
+
+#   ┌─────────────────────┐
+#───┘ Redis configuration └─────────────────────────────────────
+
+redis:
+  host: localhost
+  port: 6379
+  #pass: example-pass
+  #prefix: example-prefix
+  #db: 1
+
+#   ┌─────────────────────────────┐
+#───┘ Elasticsearch configuration └─────────────────────────────
+
+#elasticsearch:
+#  host: localhost
+#  port: 9200
+#  ssl: false
+#  user: 
+#  pass: 
+
+#   ┌───────────────┐
+#───┘ ID generation └───────────────────────────────────────────
+
+# You can select the ID generation method.
+# You don't usually need to change this setting, but you can
+# change it according to your preferences.
+
+# Available methods:
+# aid ... Short, Millisecond accuracy
+# meid ... Similar to ObjectID, Millisecond accuracy
+# ulid ... Millisecond accuracy
+# objectid ... This is left for backward compatibility
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# ID SETTINGS AFTER THAT!
+
+id: 'aid'
+
+#   ┌─────────────────────┐
+#───┘ Other configuration └─────────────────────────────────────
+
+# Whether disable HSTS
+#disableHsts: true
+
+# Number of worker processes
+#clusterLimit: 1
+
+# Job concurrency per worker
+# deliverJobConcurrency: 128
+# inboxJobConcurrency: 16
+
+# Job rate limiter
+# deliverJobPerSec: 128
+# inboxJobPerSec: 16
+
+# Job attempts
+# deliverJobMaxAttempts: 12
+# inboxJobMaxAttempts: 8
+
+# IP address family used for outgoing request (ipv4, ipv6 or dual)
+#outgoingAddressFamily: ipv4
+
+# Syslog option
+#syslog:
+#  host: localhost
+#  port: 514
+
+# Proxy for HTTP/HTTPS
+#proxy: http://127.0.0.1:3128
+
+#proxyBypassHosts: [
+#  'example.com',
+#  '192.0.2.8'
+#]
+
+# Proxy for SMTP/SMTPS
+#proxySmtp: http://127.0.0.1:3128   # use HTTP/1.1 CONNECT
+#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
+#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
+
+# Media Proxy
+#mediaProxy: https://example.com/proxy
+
+# Sign to ActivityPub GET request (default: false)
+#signToActivityPubGet: true
+
+#allowedPrivateNetworks: [
+#  '127.0.0.1/32'
+#]
+
+# Upload or download file size limits (bytes)
+#maxFileSize: 262144000
diff --git a/chart/templates/ConfigMap.yml b/chart/templates/ConfigMap.yml
new file mode 100644
index 0000000000..51a9c256ce
--- /dev/null
+++ b/chart/templates/ConfigMap.yml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "misskey.fullname" . }}-config-file
+data:
+  default.yml: |-
+{{ .Files.Get "files/default.yml"|indent 4 }}
diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml
new file mode 100644
index 0000000000..8ead4d7a9c
--- /dev/null
+++ b/chart/templates/Deployment.yml
@@ -0,0 +1,44 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "misskey.fullname" . }}
+  labels:
+		{{- include "misskey.labels" . | nindent 4 }}
+spec:
+  selector:
+    matchLabels:
+			{{- include "misskey.selectorLabels" . | nindent 6 }}
+  replicas: 1
+  template:
+    metadata:
+      labels:
+				{{- include "misskey.selectorLabels" . | nindent 8 }}
+    spec:
+      containers:
+        - name: misskey
+          image: okteto.dev/misskey:latest
+          volumeMounts:
+            - name: config-file
+              mountPath: /misskey/.config
+              readOnly: true
+          ports:
+            - containerPort: 3000
+        - name: postgres
+          image: postgres:14-alpine
+          env:
+            - name: POSTGRES_USER
+              value: "example-misskey-user"
+            - name: POSTGRES_PASSWORD
+              value: "example-misskey-pass"
+            - name: POSTGRES_DB
+              value: "misskey"
+          ports:
+            - containerPort: 5432
+        - name: redis
+          image: redis:alpine
+          ports:
+            - containerPort: 6379
+      volumes:
+        - name: config-file
+          configMap:
+            name: {{ include "misskey.fullname" . }}-config-file
diff --git a/chart/templates/Service.yml b/chart/templates/Service.yml
new file mode 100644
index 0000000000..3209581298
--- /dev/null
+++ b/chart/templates/Service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "misskey.fullname" . }}
+  annotations:
+    dev.okteto.com/auto-ingress: "true"
+spec:
+  type: ClusterIP
+  ports:
+    - port: 3000
+      protocol: TCP
+      name: http
+  selector:
+		{{- include "misskey.selectorLabels" . | nindent 4 }}
diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl
new file mode 100644
index 0000000000..a5a2499f3f
--- /dev/null
+++ b/chart/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "misskey.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "misskey.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "misskey.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "misskey.labels" -}}
+helm.sh/chart: {{ include "misskey.chart" . }}
+{{ include "misskey.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "misskey.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "misskey.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "misskey.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "misskey.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/docker-compose.yml b/docker-compose.yml
index e1d51668a7..0bf17a5557 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
       - redis
 #      - es
     ports:
-      - "127.0.0.1:3000:3000"
+      - "3000:3000"
     networks:
       - internal_network
       - external_network
diff --git a/okteto-pipeline.yml b/okteto-pipeline.yml
new file mode 100644
index 0000000000..0dc8059034
--- /dev/null
+++ b/okteto-pipeline.yml
@@ -0,0 +1,3 @@
+deploy:
+  - okteto build -t okteto.dev/misskey:latest
+  - helm upgrade --install misskey chart

From 02a43a310f6ad0cc9e9beccc26e51ab5b339e15f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 16:47:14 +0900
Subject: [PATCH 112/258] =?UTF-8?q?CAPTCHA=E6=B1=82=E3=82=81=E3=82=8B?=
 =?UTF-8?q?=E3=81=AE=E3=81=AF2fa=E8=AA=8D=E8=A8=BC=E3=81=8C=E7=84=A1?=
 =?UTF-8?q?=E5=8A=B9=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B?=
 =?UTF-8?q?=E3=81=A8=E3=81=8D=E3=81=A0=E3=81=91=E3=81=AB=E3=81=97=E3=81=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

2faのトークンは期限付きだから、CAPTCHA解いてる間に期限切れになる
---
 .../backend/src/server/api/private/signin.ts  | 24 +++++++++----------
 packages/client/src/components/signin.vue     |  4 ++--
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index e8b222a4d5..0024b8ce3e 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -20,18 +20,6 @@ export default async (ctx: Koa.Context) => {
 
 	const instance = await fetchMeta(true);
 
-	if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
-		await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => {
-			ctx.throw(400, e);
-		});
-	}
-
-	if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
-		await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => {
-			ctx.throw(400, e);
-		});
-	}
-
 	const username = body['username'];
 	const password = body['password'];
 	const token = body['token'];
@@ -96,6 +84,18 @@ export default async (ctx: Koa.Context) => {
 	}
 
 	if (!profile.twoFactorEnabled) {
+		if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
+			await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => {
+				ctx.throw(400, e);
+			});
+		}
+	
+		if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
+			await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => {
+				ctx.throw(400, e);
+			});
+		}
+	
 		if (same) {
 			signin(ctx, user);
 			return;
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index 4f88e1829c..d140e143d3 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -11,6 +11,8 @@
 				<template #prefix><i class="fas fa-lock"></i></template>
 				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
 			</MkInput>
+			<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" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 		</div>
 		<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
@@ -33,8 +35,6 @@
 					<template #label>{{ $ts.token }}</template>
 					<template #prefix><i class="fas fa-gavel"></i></template>
 				</MkInput>
-				<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 type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
 			</div>
 		</div>

From 504cf74b906f2a13aa415cbf2e38fc33ad270732 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 17:26:14 +0900
Subject: [PATCH 113/258] =?UTF-8?q?=E3=83=93=E3=83=AB=E3=83=89=E9=96=A2?=
 =?UTF-8?q?=E4=BF=82=E3=81=AE=E4=BE=9D=E5=AD=98=E9=96=A2=E4=BF=82=E3=82=92?=
 =?UTF-8?q?devDependencies=E3=81=AB=E5=85=A5=E3=82=8C=E3=82=8B=E3=81=AE?=
 =?UTF-8?q?=E3=82=92=E3=82=84=E3=82=81=E3=81=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

本番環境でビルドできないため
---
 packages/client/package.json | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/packages/client/package.json b/packages/client/package.json
index ef67d85218..6f9d52a421 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -74,6 +74,13 @@
 		"vue-router": "4.0.15",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
+		"@vitejs/plugin-vue": "2.3.3",
+		"@vue/compiler-sfc": "3.2.33",
+		"@rollup/plugin-alias": "3.1.9",
+		"@rollup/plugin-json": "4.1.0",
+		"rollup": "2.73.0",
+		"typescript": "4.6.4",
+		"vite": "2.9.9",
 		"ws": "8.6.0"
 	},
 	"devDependencies": {
@@ -98,14 +105,6 @@
 		"@types/ws": "8.5.3",
 		"@typescript-eslint/eslint-plugin": "5.23.0",
 		"@typescript-eslint/parser": "5.23.0",
-		"@vitejs/plugin-vue": "2.3.3",
-		"@vue/compiler-sfc": "3.2.33",
-		"@rollup/plugin-alias": "3.1.9",
-		"@rollup/plugin-json": "4.1.0",
-		"rollup": "2.73.0",
-		"typescript": "4.6.4",
-
-		"vite": "2.9.9",
 		"eslint": "8.15.0",
 		"eslint-plugin-vue": "8.7.1",
 		"cross-env": "7.0.3",

From 037ca92275bd8917eb7bf6f62f7613adc2bbaf36 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 15 May 2022 11:32:00 +0200
Subject: [PATCH 114/258] fix: postgres type error

Fix a bug introduced in #8659. Solution was already tested there.
---
 packages/backend/src/server/api/endpoints/notes/create.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index ff62841a0c..955f53bbc1 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -177,7 +177,7 @@ export default define(meta, paramDef, async (ps, user) => {
 				userId: user.id,
 				fileIds,
 			})
-			.orderBy('array_position(ARRAY[:...fileIds], "id")')
+			.orderBy('array_position(ARRAY[:...fileIds], "id"::text)')
 			.setParameters({ fileIds })
 			.getMany();
 	}

From 39bd71e0641249532f4dc26f2b3de002547acb47 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 15 May 2022 12:02:44 +0200
Subject: [PATCH 115/258] chore: update changelog

---
 CHANGELOG.md | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a13686b438..48d0c68fd4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,10 @@ You should also include the user name that made the change.
 - enhance: API: notifications/readは配列でも受け付けるように #7667 @tamaina
 - enhance: プッシュ通知を複数アカウント対応に #7667 @tamaina
 - enhance: プッシュ通知にクリックやactionを設定 #7667 @tamaina
+- replaced webpack with Vite @tamaina
+- update dependencies @syuilo
+- enhance: display URL of QR code for TOTP registration @syuilo
+- make CAPTCHA required for signin to improve security @syuilo
 
 ### Bugfixes
 - Client: fix settings page @tamaina
@@ -25,6 +29,13 @@ You should also include the user name that made the change.
 - Server: await promises when following or unfollowing users @Johann150
 - Client: fix abuse reports page to be able to show all reports @Johann150
 - Federation: Add rel attribute to host-meta @mei23
+- Client: fix profile picture height in mentions @tamaina
+- MFM: more animated functions support `speed` parameter @futchitwo
+- Federation: Fix quote renotes containing no text being federated correctly @Johann150
+- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
+- Server: fix internal in-memory caching @Johann150
+- Server: use correct order of attachments on notes @Johann150
+- Server: prevent crash when processing certain PNGs @syuilo
 
 ## 12.110.1 (2022/04/23)
 

From d62a55b46f7308ed04971cbfba5572b0d0feea97 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sun, 15 May 2022 15:20:01 +0200
Subject: [PATCH 116/258] Refactor emoji-edit-dialog to use Composition API
 (#8657)

* refactor(client): refactor emoji-edit-dialog to use Composition API

* fix(client): fix editing emoji not updating emoji list

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* fix(client): use cached category info instead of making a request

* fix(client): use updateItem in emoji pagination when editing

* fix(client): reimplement removeItem in MkPagination

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/components/ui/pagination.vue   |   6 +
 .../src/pages/admin/emoji-edit-dialog.vue     | 120 ++++++++----------
 packages/client/src/pages/admin/emojis.vue    |   8 +-
 3 files changed, 63 insertions(+), 71 deletions(-)

diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index ac6f59c332..9dd18785bc 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -244,6 +244,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]);
@@ -276,6 +281,7 @@ defineExpose({
 	fetchMoreAhead,
 	prepend,
 	append,
+	removeItem,
 	updateItem,
 });
 </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,
+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);
+
+const emit = defineEmits<{
+	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
+	(ev: 'closed'): void
+}>();
+
+function ok() {
+	update();
+}
+
+async function update() {
+	await os.apiWithDialog('admin/emoji/update', {
+		id: props.emoji.id,
+		name,
+		category,
+		aliases: aliases.split(' '),
+	});
+
+	emit('done', {
+		updated: {
+			id: props.emoji.id,
+			name,
+			category,
+			aliases: aliases.split(' '),
 		}
-	},
+	});
 
-	emits: ['done', 'closed'],
+	dialog.close();
+}
 
-	data() {
-		return {
-			name: this.emoji.name,
-			category: this.emoji.category,
-			aliases: this.emoji.aliases?.join(' '),
-			categories: [],
-		}
-	},
+async function del() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: name }),
+	});
+	if (canceled) return;
 
-	created() {
-		os.api('meta', { detail: false }).then(({ emojis }) => {
-			this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
+	os.api('admin/emoji/delete', {
+		id: props.emoji.id
+	}).then(() => {
+		emit('done', {
+			deleted: true
 		});
-	},
-
-	methods: {
-		ok() {
-			this.update();
-		},
-
-		async update() {
-			await os.apiWithDialog('admin/emoji/update', {
-				id: this.emoji.id,
-				name: this.name,
-				category: this.category,
-				aliases: this.aliases.split(' '),
-			});
-
-			this.$emit('done', {
-				updated: {
-					name: this.name,
-					category: this.category,
-					aliases: this.aliases.split(' '),
-				}
-			});
-			this.$refs.dialog.close();
-		},
-
-		async del() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.emoji.name }),
-			});
-			if (canceled) return;
-
-			os.api('admin/emoji/delete', {
-				id: this.emoji.id
-			}).then(() => {
-				this.$emit('done', {
-					deleted: true
-				});
-				this.$refs.dialog.close();
-			});
-		},
-	}
-});
+		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 43dd83fc02..ffb7fb34aa 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -135,12 +135,12 @@ const edit = (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');

From a8e779a68cc048d6d82b160e8e899cfab84db309 Mon Sep 17 00:00:00 2001
From: Takuya Yoshida <hawaiianphoto@geekhost.net>
Date: Sun, 15 May 2022 22:22:06 +0900
Subject: [PATCH 117/258] Power up (#8684)

---
 Dockerfile                      |  3 ++-
 chart/{Chart.yaml => Chart.yml} |  0
 chart/files/default.yml         | 11 +++++------
 chart/templates/ConfigMap.yml   |  5 +++--
 chart/templates/Deployment.yml  | 11 +++++++----
 chart/values.yml                |  3 +++
 okteto-pipeline.yml             |  3 ---
 okteto.yml                      |  6 ++++++
 8 files changed, 26 insertions(+), 16 deletions(-)
 rename chart/{Chart.yaml => Chart.yml} (100%)
 create mode 100644 chart/values.yml
 delete mode 100644 okteto-pipeline.yml
 create mode 100644 okteto.yml

diff --git a/Dockerfile b/Dockerfile
index 174e2e9bc7..33d5faad12 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
 FROM node:18.0.0-alpine3.15 AS base
 
-ENV NODE_ENV=production
+ARG NODE_ENV=production
 
 WORKDIR /misskey
 
@@ -31,5 +31,6 @@ COPY --from=builder /misskey/packages/backend/built ./packages/backend/built
 COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node_modules
 COPY . ./
 
+ENV NODE_ENV=production
 CMD ["npm", "run", "migrateandstart"]
 
diff --git a/chart/Chart.yaml b/chart/Chart.yml
similarity index 100%
rename from chart/Chart.yaml
rename to chart/Chart.yml
diff --git a/chart/files/default.yml b/chart/files/default.yml
index 276e9f8915..a9ef22f424 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -6,7 +6,7 @@
 #───┘ URL └─────────────────────────────────────────────────────
 
 # Final accessible URL seen by a user.
-url: https://example.tld/
+# url: https://example.tld/
 
 # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
 # URL SETTINGS AFTER THAT!
@@ -41,7 +41,7 @@ url: https://example.tld/
 #   You need to set Certificate in 'https' section.
 
 # To use option 1, uncomment below line.
-port: 3000    # A port that your Misskey server should listen.
+port: 3000 # A port that your Misskey server should listen.
 
 # To use option 2, uncomment below lines.
 #port: 443
@@ -89,8 +89,8 @@ redis:
 #  host: localhost
 #  port: 9200
 #  ssl: false
-#  user: 
-#  pass: 
+#  user:
+#  pass:
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
@@ -108,8 +108,7 @@ redis:
 # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
 # ID SETTINGS AFTER THAT!
 
-id: 'aid'
-
+id: "aid"
 #   ┌─────────────────────┐
 #───┘ Other configuration └─────────────────────────────────────
 
diff --git a/chart/templates/ConfigMap.yml b/chart/templates/ConfigMap.yml
index 51a9c256ce..37c25e0864 100644
--- a/chart/templates/ConfigMap.yml
+++ b/chart/templates/ConfigMap.yml
@@ -1,7 +1,8 @@
 apiVersion: v1
 kind: ConfigMap
 metadata:
-  name: {{ include "misskey.fullname" . }}-config-file
+  name: {{ include "misskey.fullname" . }}-configuration
 data:
   default.yml: |-
-{{ .Files.Get "files/default.yml"|indent 4 }}
+    {{ .Files.Get "files/default.yml"|nindent 4 }}
+    url: {{ .Values.url }}
diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml
index 8ead4d7a9c..d16aece915 100644
--- a/chart/templates/Deployment.yml
+++ b/chart/templates/Deployment.yml
@@ -16,9 +16,12 @@ spec:
     spec:
       containers:
         - name: misskey
-          image: okteto.dev/misskey:latest
+          image: {{ .Values.image }}
+          env:
+            - name: NODE_ENV
+              value: {{ .Values.environment }}
           volumeMounts:
-            - name: config-file
+            - name: {{ include "misskey.fullname" . }}-configuration
               mountPath: /misskey/.config
               readOnly: true
           ports:
@@ -39,6 +42,6 @@ spec:
           ports:
             - containerPort: 6379
       volumes:
-        - name: config-file
+        - name: {{ include "misskey.fullname" . }}-configuration
           configMap:
-            name: {{ include "misskey.fullname" . }}-config-file
+            name: {{ include "misskey.fullname" . }}-configuration
diff --git a/chart/values.yml b/chart/values.yml
new file mode 100644
index 0000000000..a7031538a9
--- /dev/null
+++ b/chart/values.yml
@@ -0,0 +1,3 @@
+url: https://example.tld/
+image: okteto.dev/misskey
+environment: production
diff --git a/okteto-pipeline.yml b/okteto-pipeline.yml
deleted file mode 100644
index 0dc8059034..0000000000
--- a/okteto-pipeline.yml
+++ /dev/null
@@ -1,3 +0,0 @@
-deploy:
-  - okteto build -t okteto.dev/misskey:latest
-  - helm upgrade --install misskey chart
diff --git a/okteto.yml b/okteto.yml
new file mode 100644
index 0000000000..e2996fbbc9
--- /dev/null
+++ b/okteto.yml
@@ -0,0 +1,6 @@
+build:
+  misskey:
+    args:
+      - NODE_ENV=development
+deploy:
+  - helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development

From fee2878b98253f2510ae2bc5c746c058a043ef82 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 15 May 2022 22:32:50 +0900
Subject: [PATCH 118/258] chore(dev): use .yaml for prevent okteto error

---
 chart/{Chart.yml => Chart.yaml} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename chart/{Chart.yml => Chart.yaml} (100%)

diff --git a/chart/Chart.yml b/chart/Chart.yaml
similarity index 100%
rename from chart/Chart.yml
rename to chart/Chart.yaml

From 95eea58d7cb0dce603afd7ce5e8a178dfd22aeb0 Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
Date: Tue, 17 May 2022 09:12:00 -0700
Subject: [PATCH 119/258] Improve README (#8645)

* Add files via upload

* Update title.svg

* Floating title!

* Update README.md

* Update title_float.svg

* Consolidate taglines

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* Update README.md

* Drive

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 README.md              | 56 +++++++++++++++++++----------------
 assets/title_float.svg | 67 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 97 insertions(+), 26 deletions(-)
 create mode 100644 assets/title_float.svg

diff --git a/README.md b/README.md
index 19e953aee8..9d7c59633a 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,32 @@
-[![Misskey](https://github.com/misskey-dev/assets/blob/main/banner.png?raw=true)](https://join.misskey.page/)
-
 <div align="center">
-
-**🌎 A forever evolving, interplanetary microblogging platform. 🚀**
-
-**Misskey** is a distributed microblogging platform with advanced features such as Reactions and a highly customizable UI.
-
-[Learn more](https://misskey-hub.net/)
-
+<a href="https://misskey-hub.net">
+	<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="400"/>
+</a>
+	
+**🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀**
+	
 ---
+	
+<a href="https://misskey-hub.net/instances.html">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>	
+	
+<a href="https://misskey-hub.net/docs/install.html">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>	
 
-[✨ Find an instance](https://misskey-hub.net/instances.html)
-•
-[📦 Create your own instance](https://misskey-hub.net/docs/install.html)
-•
-[🛠️ Contribute](./CONTRIBUTING.md)
-•
-[🚀 Join the community](https://discord.gg/Wp8gVStHW3)
+<a href="./CONTRIBUTING.md">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/>
+</a>
 
+<a href="https://discord.gg/Wp8gVStHW3">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/>
+</a>
+	
+<a href="https://www.patreon.com/syuilo">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/>
+</a>
+	
 ---
 
-<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
-
 </div>
 
 <div>
@@ -30,17 +35,16 @@
 
 ## ✨ Features
 - **ActivityPub support**\
-	It is possible to interact with other software.
+Not on Misskey? No problem! Not only can Misskey instances talk to each other, but you can make friends with people on other networks like Mastodon and Pixelfed!
 - **Reactions**\
-	You can add "reactions" to each post, making it easy for you to express your feelings.
+You can add emoji reactions to any post! No longer are you bound by a like button, show everyone exactly how you feel with the tap of a button.
 - **Drive**\
-	An interface to manage uploaded files such as images, videos, sounds, etc.
-	You can also organize your favorite content into folders, making it easy to share again.
+With Misskey's built in drive, you get cloud storage right in your social media, where you can upload any files, make folders, and find media from posts you've made!
 - **Rich Web UI**\
-	Misskey has a rich WebUI by default.
-	It is highly customizable by flexibly changing the layout and installing various widgets and themes.
-	Furthermore, plug-ins can be created using AiScript, a original programming language.
-- and more...
+	Misskey has a rich and easy to use Web UI!
+	It is highly customizable, from changing the layout and adding widgets to making custom themes.
+	Furthermore, plugins can be created using AiScript, an original programming language.
+- And much more...
 
 </div>
 
diff --git a/assets/title_float.svg b/assets/title_float.svg
new file mode 100644
index 0000000000..43205ac1c4
--- /dev/null
+++ b/assets/title_float.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   id="svg10"
+   version="1.1"
+   viewBox="0 0 162.642 54.261"
+   height="205.08"
+   width="614.71">
+  <metadata
+     id="metadata16">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+	<style>
+		#g8 { 
+    	animation-name: floating;
+    	animation-duration: 3s;
+    	animation-iteration-count: infinite;
+   	 	animation-timing-function: ease-in-out;
+		}
+ 
+		@keyframes floating {
+    	0% { transform: translate(0,  0px); }
+    	50% { transform: translate(0, -5px); }
+    	100% { transform: translate(0, 0px); }
+		}
+	</style>
+	<linearGradient id="myGradient" gradientTransform="rotate(90)">
+      <stop offset="5%"  stop-color="#A1CA03" />
+      <stop offset="95%" stop-color="#91BA03" />
+    </linearGradient>
+  <defs
+     id="defs14" />
+  <g
+     id="g8"
+     fill="url('#myGradient')"
+     word-spacing="0"
+     letter-spacing="0"
+     font-family="OTADESIGN Rounded"
+     font-weight="400">
+    <g
+       id="g4"
+       style="line-height:476.69509888px;-inkscape-font-specification:'OTADESIGN Rounded'">
+      <path
+         id="path2"
+         font-size="141.034"
+         aria-label="Mi"
+         d="m 27.595,34.59 c -1.676,0.006 -3.115,-1.004 -3.793,-2.179 -0.363,-0.513 -1.08,-0.696 -1.09,0 v 3.214 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.031,1.371 -3.35,1.371 -1.29,0 -2.407,-0.457 -3.349,-1.372 -0.914,-0.941 -1.372,-2.058 -1.372,-3.349 V 17.95 c 0,-0.995 0.283,-1.896 0.848,-2.703 0.591,-0.834 1.345,-1.413 2.26,-1.735 0.516591,-0.189385 1.062793,-0.285215 1.613,-0.283 1.453,0 2.664,0.565 3.632,1.695 l 4.832,5.608 c 0.108,0.08 0.424,0.697 1.18,0.697 0.758,0 1.115,-0.617 1.222,-0.698 l 4.791,-5.607 c 0.996,-1.13 2.22,-1.695 3.673,-1.695 0.538,0 1.076,0.094 1.614,0.283 0.914,0.322 1.654,0.9 2.22,1.735 0.591,0.807 0.887,1.708 0.887,2.703 v 17.675 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.032,1.371 -3.35,1.371 -1.291,0 -2.407,-0.457 -3.35,-1.372 -0.914,-0.941 -1.371,-2.058 -1.371,-3.349 v -3.214 c -0.08,-0.877 -0.855,-0.324 -1.13,0 -0.726,1.345 -2.118,2.173 -3.793,2.18 z M 47.806,21.38 c -1.13,0 -2.098333,-0.39 -2.905,-1.17 -0.78,-0.806667 -1.17,-1.775 -1.17,-2.905 0,-1.13 0.39,-2.085 1.17,-2.865 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 1.13,0 2.098667,0.403333 2.906,1.21 0.806667,0.78 1.21,1.735 1.21,2.865 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.807333,0.78 -1.776,1.17 -2.906,1.17 z m 0.04,0.808 c 1.13,0 2.085333,0.403333 2.866,1.21 0.806667,0.806667 1.21,1.775333 1.21,2.906 v 9.967 c 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.78,0.78 -1.735333,1.17 -2.866,1.17 -1.129333,0 -2.097667,-0.39 -2.905,-1.17 -0.806667,-0.806667 -1.21,-1.775 -1.21,-2.905 v -9.967 c 0,-1.13 0.403333,-2.098667 1.21,-2.906 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 z"
+         style="font-size:141.03399658px;-inkscape-font-specification:'OTADESIGN Rounded'" />
+    </g>
+    <path
+       id="path6"
+       d="M60.925 27.24q.968.243 2.42.525 2.42.403 3.792 1.29 2.582 1.695 2.582 5.083 0 2.743-1.815 4.478-2.098 2.017-5.85 2.017-2.742 0-6.13-.767-1.09-.242-1.776-1.089-.645-.847-.645-1.896 0-1.29.887-2.178.928-.928 2.179-.928.363 0 .685.081 1.17.242 4.478.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.372-.242-2.542-.524-1.33-.282-1.896-.484-1.129-.323-1.895-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.783.121 5.567.726 1.048.242 1.734 1.09.686.846.686 1.936 0 1.25-.928 2.178-.887.887-2.178.887-.323 0-.645-.08-1.17-.242-4.518-.565-.404-.04-.767 0-.323.04-.323.242.04.242.323.323zm17.555 0q.968.243 2.42.525 2.42.403 3.792 1.29 2.581 1.695 2.581 5.083 0 2.743-1.815 4.478-2.098 2.017-5.849 2.017-2.743 0-6.131-.767-1.09-.242-1.775-1.089-.646-.847-.646-1.896 0-1.29.888-2.178.927-.928 2.178-.928.363 0 .686.081 1.17.242 4.477.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.371-.242-2.541-.524-1.331-.282-1.896-.484-1.13-.323-1.896-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.784.121 5.567.726 1.049.242 1.735 1.09.685.846.685 1.936 0 1.25-.927 2.178-.888.887-2.179.887-.322 0-.645-.08-1.17-.242-4.518-.565-.403-.04-.767 0-.322.04-.322.242.04.242.322.323zm26.075 3.335q.12.08 2.864 2.783 1.25 1.21 1.25 2.945 0 1.613-1.17 2.864-1.17 1.21-2.904 1.21-1.654 0-2.864-1.17l-4.034-3.913q-.161-.12-.323-.12-.322 0-.322 1.21 0 1.694-1.21 2.904-1.21 1.17-2.905 1.17-1.694 0-2.904-1.17-1.17-1.21-1.17-2.905V17.586q0-1.694 1.17-2.864 1.21-1.21 2.904-1.21t2.904 1.21q1.21 1.17 1.21 2.864v6.293q0 .403.283.524.242.121.524-.08.162-.081 4.841-3.188 1.049-.645 2.259-.645 2.219 0 3.429 1.815.645 1.05.645 2.26 0 2.218-1.815 3.428l-2.541 1.614v.04l-.081.04q-.565.363-.04.888zm15.599 10.058q-4.195 0-7.18-2.945-2.945-2.985-2.945-7.18 0-4.155 2.945-7.1 2.985-2.985 7.18-2.985 4.155 0 6.979 2.784.928.927.928 2.259 0 1.33-.928 2.259l-4.68 4.639q-1.008 1.008-2.016 1.008-1.453 0-2.26-.807-.806-.807-.806-2.138 0-1.29.928-2.218l.806-.847q.162-.121.081-.243-.12-.08-.323-.04-.806.202-1.371.807-1.13 1.09-1.13 2.622 0 1.573 1.09 2.703 1.13 1.089 2.702 1.089 1.533 0 2.622-1.13.928-.927 2.26-.927 1.33 0 2.258.927.928.928.928 2.26 0 1.33-.928 2.258-2.985 2.945-7.14 2.945zm29.259-15.786v5.607q0 .564-.08 1.21v7.382q0 4.518-2.744 7.22-2.702 2.703-7.301 2.703-2.662 0-4.8-1.008-2.138-.968-2.138-3.348 0-.807.363-1.533.968-2.179 3.348-2.179.565 0 1.573.323 1.009.323 1.654.323 1.694 0 2.219-.726.201-.283.08-.444-.161-.242-.564-.161-.686.12-1.493.12-4.074 0-6.979-2.904-2.904-2.904-2.904-6.978v-5.607q0-1.695 1.17-2.864 1.21-1.21 2.904-1.21t2.905 1.21q1.21 1.17 1.21 2.864v5.607q0 .685.484 1.21.524.484 1.21.484.726 0 1.21-.484.484-.525.484-1.21v-5.607q0-1.695 1.21-2.864 1.21-1.21 2.905-1.21 1.694 0 2.864 1.21 1.21 1.17 1.21 2.864z"
+       style="line-height:136.34428406px;-inkscape-font-specification:'OTADESIGN Rounded'" />
+  </g>
+</svg>

From 7d08b936c6f3ef558283d26435e1332a9933c281 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:30:35 +0200
Subject: [PATCH 120/258] refactor(client): refactor my-antennas/index to use
 Composition API (#8679)

---
 .../client/src/pages/my-antennas/index.vue    | 41 +++++++------------
 1 file changed, 15 insertions(+), 26 deletions(-)

diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index 7138d269a9..9f1e01f11d 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,36 +14,25 @@
 </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>
 
 <style lang="scss" scoped>

From dfeafaf499af8b280dc89db6bb0c589308ea50fc Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:30:49 +0200
Subject: [PATCH 121/258] Refactor admin/relays to use Composition API (#8677)

* refactor(client): refactor admin/relays to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/pages/admin/relays.vue | 117 +++++++++------------
 1 file changed, 52 insertions(+), 65 deletions(-)

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'],
+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
+		});
+	});
+}
 
-	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: '',
-		}
-	},
+function remove(inbox: string) {
+	os.api('admin/relays/remove', {
+		inbox
+	}).then(() => {
+		refresh();
+	}).catch((err: any) => {
+		os.alert({
+			type: 'error',
+			text: err.message || err
+		});
+	});
+}
 
-	created() {
-		this.refresh();
-	},
+function refresh() {
+	os.api('admin/relays/list').then((relayList: any) => {
+		relays = relayList;
+	});
+}
 
-	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
-				});
-			});
-		},
+refresh();
 
-		remove(inbox: string) {
-			os.api('admin/relays/remove', {
-				inbox
-			}).then(() => {
-				this.refresh();
-			}).catch((e: any) => {
-				os.alert({
-					type: 'error',
-					text: e.message || e
-				});
-			});
-		},
-
-		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>

From 7c5c27cbe367168a23d4b10e5ce091085cf59401 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:31:04 +0200
Subject: [PATCH 122/258] Refactor admin/queue to use Composition API (#8676)

* refactor(client): refactor admin/queue to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/pages/admin/queue.chart.vue    | 72 ++++++---------
 packages/client/src/pages/admin/queue.vue     | 87 ++++++++-----------
 2 files changed, 63 insertions(+), 96 deletions(-)

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..e05098082a 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();
-	},
+onBeforeUnmount(() => {
+	connection.dispose();
+});
 
-	methods: {
-		clear() {
-			os.confirm({
-				type: 'warning',
-				title: this.$ts.clearQueueConfirmTitle,
-				text: this.$ts.clearQueueConfirmText,
-			}).then(({ canceled }) => {
-				if (canceled) return;
-
-				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>

From 13999d953bbade5d1ccdb0f1466d3eed025ac4be Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:31:16 +0200
Subject: [PATCH 123/258] refactor(client): refactor admin/proxy-account to use
 Composition API (#8675)

---
 .../client/src/pages/admin/proxy-account.vue  | 83 ++++++++-----------
 1 file changed, 35 insertions(+), 48 deletions(-)

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'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	proxyAccountId = meta.proxyAccountId;
+	if (proxyAccountId) {
+		proxyAccount = await os.api('users/show', { userId: proxyAccountId });
+	}
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.proxyAccount,
-				icon: 'fas fa-ghost',
-				bg: 'var(--bg)',
-			},
-			proxyAccount: null,
-			proxyAccountId: null,
-		}
-	},
+function chooseProxyAccount() {
+	os.selectUser().then(user => {
+		proxyAccount = user;
+		proxyAccountId = user.id;
+		save();
+	});
+}
 
-	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 save() {
+	os.apiWithDialog('admin/update-meta', {
+		proxyAccountId: proxyAccountId,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-		chooseProxyAccount() {
-			os.selectUser().then(user => {
-				this.proxyAccount = user;
-				this.proxyAccountId = user.id;
-				this.save();
-			});
-		},
-
-		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>

From 83ac6742f6aa7071db91aee96eb339232cc17d38 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:31:32 +0200
Subject: [PATCH 124/258] refactor(client): refactor admin/object-storage to
 use Composition API (#8666)

---
 .../client/src/pages/admin/object-storage.vue | 170 ++++++++----------
 1 file changed, 78 insertions(+), 92 deletions(-)

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>

From 18307c822c4bc6b80a82391293a722ac1e81a033 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:31:48 +0200
Subject: [PATCH 125/258] Refactor admin/integrations to use Composition API
 (#8664)

* refactor(client): refactor admin/integrations to use Composition API

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../src/pages/admin/integrations.discord.vue  | 65 +++++++------------
 .../src/pages/admin/integrations.github.vue   | 65 +++++++------------
 .../src/pages/admin/integrations.twitter.vue  | 63 ++++++------------
 .../client/src/pages/admin/integrations.vue   | 61 +++++++----------
 4 files changed, 87 insertions(+), 167 deletions(-)

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';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormFolder,
-		FormSecion,
-		FormSuspense,
-		XTwitter,
-		XGithub,
-		XDiscord,
-	},
+let enableTwitterIntegration: boolean = $ref(false);
+let enableGithubIntegration: boolean = $ref(false);
+let enableDiscordIntegration: boolean = $ref(false);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	enableTwitterIntegration = meta.enableTwitterIntegration;
+	enableGithubIntegration = meta.enableGithubIntegration;
+	enableDiscordIntegration = meta.enableDiscordIntegration;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.integration,
-				icon: 'fas fa-share-alt',
-				bg: 'var(--bg)',
-			},
-			enableTwitterIntegration: false,
-			enableGithubIntegration: false,
-			enableDiscordIntegration: false,
-		}
-	},
-
-	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>

From bf6cc34961a421d8bfd5f6e802d2acc506c314d6 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:31:59 +0200
Subject: [PATCH 126/258] refactor(client): refactor admin/instance-block to
 use Composition API (#8663)

---
 .../client/src/pages/admin/instance-block.vue | 58 ++++++++-----------
 1 file changed, 23 insertions(+), 35 deletions(-)

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>

From f03390f0b813e398b699a95ae89abb27dee3192c Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:32:21 +0200
Subject: [PATCH 127/258] Refactor admin/index to use Composition API (#8662)

* refactor(client): refactor admin/index to use Composition API

* fix(client): fix navigation to initial admin pages

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* fix(client): re-add abuses page to admin/index

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 packages/client/src/pages/admin/index.vue | 545 +++++++++++-----------
 1 file changed, 265 insertions(+), 280 deletions(-)

diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue
index 6b11650f48..210226f283 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,
+};
+
+const props = defineProps<{
+	initialPage?: string,
+}>();
+
+provide('shouldOmitHeaderTitle', 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;
+
+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: 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',
+	}],
+}]);
+
+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'));
+	}
+});
+
+watch(component, () => {
+	pageProps = {};
+
+	nextTick(() => {
+		scroll(el, { top: 0 });
+	});
+}, { immediate: true });
+
+watch(() => props.initialPage, () => {
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	} else {
+		if (props.initialPage == null) {
+			INFO = indexInfo;
 		}
-	},
+	}
+});
 
-	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({});
+watch(narrow, () => {
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	}
+});
 
-		const isEmpty = (x: any) => x == null || x == '';
+onMounted(() => {
+	ro.observe(el);
 
-		const noMaintainerInformation = ref(false);
-		const noBotProtection = ref(false);
+	narrow = el.offsetWidth < NARROW_THRESHOLD;
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	}
+});
 
-		os.api('meta', { detail: true }).then(meta => {
-			// TODO: 設定が完了しても残ったままになるので、ストリーミングでmeta更新イベントを受け取ってよしなに更新する
-			noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
-			noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
+onUnmounted(() => {
+	ro.disconnect();
+});
+
+const pageChanged = (page) => {
+	if (page == null) {
+		childInfo = null;
+	} else {
+		childInfo = page[symbols.PAGE_INFO];
+	}
+};
+
+const invite = () => {
+	os.api('admin/invite').then(x => {
+		os.alert({
+			type: 'info',
+			text: x.code
 		});
-
-		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'));
-			}
+	}).catch(e => {
+		os.alert({
+			type: 'error',
+			text: e
 		});
+	});
+};
 
-		watch(component, () => {
-			pageProps.value = {};
+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);
+};
 
-			nextTick(() => {
-				scroll(el.value, { top: 0 });
-			});
-		}, { immediate: true });
-
-		watch(() => props.initialPage, () => {
-			if (props.initialPage == null && !narrow.value) {
-				page.value = 'overview';
-			} else {
-				page.value = props.initialPage;
-				if (props.initialPage == null) {
-					INFO.value = indexInfo;
-				}
-			}
-		});
-
-		onMounted(() => {
-			narrow.value = el.value.offsetWidth < 800;
-			if (!narrow.value) {
-				page.value = 'overview';
-			}
-		});
-
-		const invite = () => {
-			os.api('admin/invite').then(x => {
-				os.alert({
-					type: 'info',
-					text: x.code
-				});
-			}).catch(e => {
-				os.alert({
-					type: 'error',
-					text: e
-				});
-			});
-		};
-
-		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);
-		};
-
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			menuDef,
-			header: {
-				title: i18n.ts.controlPanel,
-			},
-			noMaintainerInformation,
-			noBotProtection,
-			page,
-			narrow,
-			view,
-			el,
-			pageChanged,
-			childInfo,
-			pageProps,
-			component,
-			invite,
-			lookup,
-		};
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: INFO,
+	header: {
+		title: i18n.ts.controlPanel,
+	}
 });
 </script>
 

From a86e1221a085d7dee5ff2a9111eb6cf9c7a19ae9 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 17 May 2022 18:33:21 +0200
Subject: [PATCH 128/258] Refactor file-dialog to use Composition API (#8661)

* refactor(client): refactor file-dialog to use Composition API

* Apply review suggestion from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/pages/admin/file-dialog.vue    | 86 +++++++------------
 1 file changed, 32 insertions(+), 54 deletions(-)

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,
-		}
-	},
+const props = defineProps<{
+	fileId: string,
+}>();
 
-	emits: ['closed'],
+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;
+}
 
-	data() {
-		return {
-			file: null,
-			info: null,
-			isSensitive: false,
-		};
-	},
+fetch();
 
-	created() {
-		this.fetch();
-	},
+function showUser() {
+	os.pageWindow(`/user-info/${file.userId}`);
+}
 
-	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 del() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: file.name }),
+	});
+	if (canceled) return;
 
-		showUser() {
-			os.pageWindow(`/user-info/${this.file.userId}`);
-		},
+	os.apiWithDialog('drive/files/delete', {
+		fileId: file.id
+	});
+}
 
-		async del() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.file.name }),
-			});
-			if (canceled) return;
-
-			os.apiWithDialog('drive/files/delete', {
-				fileId: this.file.id
-			});
-		},
-
-		async toggleIsSensitive(v) {
-			await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
-			this.isSensitive = v;
-		},
-
-		bytes
-	}
-});
+async function toggleIsSensitive(v) {
+	await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
+	isSensitive = v;
+}
 </script>
 
 <style lang="scss" scoped>

From e3f2d469c0a42536bed59ff626cbbf05d7c0baf1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=82=E3=81=9A=E3=81=8D=E2=AA=A5=E2=84=A2?=
 <44765629+melt-adzuki@users.noreply.github.com>
Date: Wed, 18 May 2022 01:34:30 +0900
Subject: [PATCH 129/258] remove unneeded attrs (#8673)

---
 packages/client/src/ui/deck/widgets-column.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue
index a2edc38357..904cfb58d6 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>

From b6794b614b701ff1bfde98de5b4fa8735f5155b0 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 19 May 2022 11:49:07 +0900
Subject: [PATCH 130/258] enhance: Perform port diagnosis at startup only when
 Listen fails (#8698)

* Change port check

* Comment: disableClustering

* CHANGELOG

* Smart message
---
 CHANGELOG.md                         |  1 +
 packages/backend/package.json        |  2 --
 packages/backend/src/boot/master.ts  | 36 ++++------------------------
 packages/backend/src/server/index.ts | 22 +++++++++++++++++
 packages/backend/yarn.lock           | 34 +-------------------------
 packages/client/package.json         |  1 -
 packages/client/yarn.lock            | 29 +---------------------
 7 files changed, 29 insertions(+), 96 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48d0c68fd4..f692bdc46e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ You should also include the user name that made the change.
 - update dependencies @syuilo
 - enhance: display URL of QR code for TOTP registration @syuilo
 - make CAPTCHA required for signin to improve security @syuilo
+- Perform port diagnosis at startup only when Listen fails @mei23
 
 ### Bugfixes
 - Client: fix settings page @tamaina
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 88df79ab21..ab0e9fbc16 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -77,7 +77,6 @@
 		"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",
@@ -151,7 +150,6 @@
 		"@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",
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/server/index.ts b/packages/backend/src/server/index.ts
index c1a2a6dfff..cd061da725 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -2,6 +2,7 @@
  * Core Server
  */
 
+import cluster from 'node:cluster';
 import * as fs from 'node:fs';
 import * as http from 'node:http';
 import Koa from 'koa';
@@ -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/yarn.lock b/packages/backend/yarn.lock
index 936b362c25..550f31afb9 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -705,11 +705,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"
@@ -1254,13 +1249,6 @@ async@>=0.2.9:
   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.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
@@ -3861,13 +3849,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"
@@ -4498,11 +4479,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"
@@ -4548,7 +4524,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==
@@ -5541,14 +5517,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"
diff --git a/packages/client/package.json b/packages/client/package.json
index 6f9d52a421..00950ae1b5 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -42,7 +42,6 @@
 		"nested-property": "4.0.0",
 		"parse5": "6.0.1",
 		"photoswipe": "5.2.7",
-		"portscanner": "2.2.0",
 		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
 		"promise-limit": "2.7.0",
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index fe4c17b52b..abf3b24dee 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -751,13 +751,6 @@ 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"
@@ -2519,13 +2512,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"
@@ -2789,11 +2775,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.merge@^4.6.2:
   version "4.6.2"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -2804,7 +2785,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@^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==
@@ -3303,14 +3284,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-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"

From d9ac9e6d98624d796ed3de61fe134fe53eeede81 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 May 2022 11:49:54 +0900
Subject: [PATCH 131/258] chore(deps): bump path-parse from 1.0.6 to 1.0.7
 (#8705)

Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 2dc0d12ecf..4750fa7064 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2969,9 +2969,9 @@ path-key@^3.0.0, path-key@^3.1.0:
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
 path-parse@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
-  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
 path-root-regex@^0.1.0:
   version "0.1.2"

From 13b275773b2fe6f50a87aa91458eaffff88d10b7 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 May 2022 11:49:59 +0900
Subject: [PATCH 132/258] chore(deps): bump async from 3.2.0 to 3.2.3 in
 /packages/backend (#8706)

Bumps [async](https://github.com/caolan/async) from 3.2.0 to 3.2.3.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/master/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v3.2.0...v3.2.3)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 packages/backend/yarn.lock | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 550f31afb9..9c40715e1a 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -1244,12 +1244,7 @@ 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.2.9:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
-  integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
-
-async@^3.2.3:
+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==

From 7ed0763ad2357ff7edeb2bfd7428dd50f6c512a8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 19 May 2022 12:47:43 +0900
Subject: [PATCH 133/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index cafca625d4..62a7dd9ff2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,7 @@ Before creating an issue, please check the following:
 - Do not use Issues to ask questions or troubleshooting.
 	- Issues should only be used to feature requests, suggestions, and bug tracking.
 	- Please ask questions or troubleshooting in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3).
+- Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
 
 ## Before implementation
 When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.

From 4fc20587450bdba07c86bed2a5f68718ef5bf1f2 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 19 May 2022 15:24:35 +0900
Subject: [PATCH 134/258] chore(client): tweak loading spinner design

---
 packages/backend/src/server/web/style.css     | 34 +++++------
 .../backend/src/server/web/views/base.pug     | 10 ++++
 .../client/src/components/global/loading.vue  | 60 +++++++++++--------
 3 files changed, 60 insertions(+), 44 deletions(-)

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;
-	width: 28px;
-	height: 28px;
-	border-radius: 50%;
-	border: solid 4px;
-}
-
-#splashSpinner:before {
-	border-color: currentColor;
-	opacity: 0.3;
-}
-
-#splashSpinner:after {
+#splashSpinner > .spinner {
 	position: absolute;
 	top: 0;
-	border-color: currentColor transparent transparent transparent;
+	left: 0;
+	width: 28px;
+	height: 28px;
+	fill-rule: evenodd;
+	clip-rule: evenodd;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+	stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+	opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
 	animation: splashSpinner 0.5s linear infinite;
 }
 
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index d79354d118..a488e51171 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -65,4 +65,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/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue
index 43ea1395ed..933c5e5881 100644
--- a/packages/client/src/components/global/loading.vue
+++ b/packages/client/src/components/global/loading.vue
@@ -1,6 +1,17 @@
 <template>
 <div class="yxspomdl" :class="{ inline, colored, mini }">
-	<div class="ring"></div>
+	<div class="container">
+		<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>
+	</div>
 </div>
 </template>
 
@@ -19,7 +30,7 @@ const props = withDefaults(defineProps<{
 </script>
 
 <style lang="scss" scoped>
-@keyframes ring {
+@keyframes spinner {
 	0% {
 		transform: rotate(0deg);
 	}
@@ -33,7 +44,7 @@ const props = withDefaults(defineProps<{
 	text-align: center;
 	cursor: wait;
 
-	--size: 48px;
+	--size: 40px;
 
 	&.colored {
 		color: var(--accent);
@@ -50,32 +61,31 @@ const props = withDefaults(defineProps<{
 		--size: 32px;
 	}
 
-	> .ring {
+	> .container {
 		position: relative;
-		display: inline-block;
-		vertical-align: middle;
+		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;
-		}
-
-		&:before {
-			border-color: currentColor;
-			opacity: 0.3;
-		}
-
-		&:after {
+		> .spinner {
 			position: absolute;
 			top: 0;
-			border-color: currentColor transparent transparent transparent;
-			animation: ring 0.5s linear infinite;
+			left: 0;
+			width: var(--size);
+			height: var(--size);
+			fill-rule: evenodd;
+			clip-rule: evenodd;
+			stroke-linecap: round;
+			stroke-linejoin: round;
+			stroke-miterlimit: 1.5;
+		}
+
+		> .bg {
+			opacity: 0.275;
+		}
+
+		> .fg {
+			animation: spinner 0.5s linear infinite;
 		}
 	}
 }

From 02f9e5d6f0124e1a2c5bd9d471695ea95039285f Mon Sep 17 00:00:00 2001
From: xianon <xianon@hotmail.co.jp>
Date: Thu, 19 May 2022 16:17:00 +0900
Subject: [PATCH 135/258] =?UTF-8?q?fix:=20=E3=83=8E=E3=83=BC=E3=83=88?=
 =?UTF-8?q?=E3=81=AE=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9?=
 =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=81=AE=E6=96=87=E5=AD=97=E3=81=AB=E7=B8=81?=
 =?UTF-8?q?=E3=82=92=E4=BB=98=E3=81=91=E3=81=A6=E8=A6=8B=E3=82=84=E3=81=99?=
 =?UTF-8?q?=E3=81=8F=E3=81=99=E3=82=8B=20(#8697)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* ノートのインスタンス情報の背景色が反映されないことがあるのを修正する

* ノートのインスタンス情報の文字に縁を付けて見やすくする

* Revert "ノートのインスタンス情報の背景色が反映されないことがあるのを修正する"

This reverts commit de920dfc537d1f2c68804d0d6930520f2b3cbce7.

* ノートのインスタンス情報の文字の影の数を増やしてさらに見やすくする
---
 packages/client/src/components/instance-ticker.vue | 13 +++++++++++++
 1 file changed, 13 insertions(+)

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%;

From 55a578a8df67a9028001104d3a4c2584f3b51a7d Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Thu, 19 May 2022 16:19:23 +0900
Subject: [PATCH 136/258] fix: Unable to generate video thumbnails (#8696)

* fix: Unable to generate video thumbnails

* CHANGELOG
---
 CHANGELOG.md                                                    | 1 +
 packages/backend/src/services/drive/generate-video-thumbnail.ts | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f692bdc46e..fb8b8fdee6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ You should also include the user name that made the change.
 - Server: fix internal in-memory caching @Johann150
 - Server: use correct order of attachments on notes @Johann150
 - Server: prevent crash when processing certain PNGs @syuilo
+- Server: Fix unable to generate video thumbnails @mei23
 
 ## 12.110.1 (2022/04/23)
 
diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts
index da93bc97c7..ef75a9f585 100644
--- a/packages/backend/src/services/drive/generate-video-thumbnail.ts
+++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts
@@ -1,7 +1,7 @@
 import * as fs from 'node:fs';
 import * as tmp from 'tmp';
 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) => {

From aaf5bb62abd6c1daefc675a7aa7eebfac561fb3a Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 19 May 2022 09:54:45 +0200
Subject: [PATCH 137/258] enhance: uniform theme color (#8702)

* enhance: make theme color format uniform

All newly fetched instance theme colors will be uniformely formatted
as hashtag followed by 6 hexadecimal digits.

Colors are checked for validity and invalid colors are not handled.

* better input validation for own theme color

* migration to unify theme color formats

Fixes theme colors of other instances as well as the local instance.

* add changelog entry

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                                  |  3 ++
 .../1652859567549-uniform-themecolor.js       | 38 +++++++++++++++++++
 .../server/api/endpoints/admin/update-meta.ts |  2 +-
 .../src/services/fetch-instance-metadata.ts   | 14 +++----
 4 files changed, 47 insertions(+), 10 deletions(-)
 create mode 100644 packages/backend/migration/1652859567549-uniform-themecolor.js

diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb8b8fdee6..21ae948d0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,9 @@ You should also include the user name that made the change.
 - update dependencies @syuilo
 - enhance: display URL of QR code for TOTP registration @syuilo
 - make CAPTCHA required for signin to improve security @syuilo
+- The theme color is now better validated. @Johann150
+  Your own theme color may be unset if it was in an invalid format.
+  Admins should check their instance settings if in doubt.
 - Perform port diagnosis at startup only when Listen fails @mei23
 
 ### Bugfixes
diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js
new file mode 100644
index 0000000000..bc47143e54
--- /dev/null
+++ b/packages/backend/migration/1652859567549-uniform-themecolor.js
@@ -0,0 +1,38 @@
+import tinycolor from 'tinycolor2';
+
+export class uniformThemecolor1652859567549 {
+	name = 'uniformThemecolor1652859567549'
+
+	async up(queryRunner) {
+		const formatColor = (color) => {
+			let tc = new tinycolor(color);
+			if (color.isValid()) {
+				return color.toHexString();
+			} else {
+				return null;
+			}
+		};
+
+		await Promise.all(queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL')
+		.then(instances => instances.map(instance => {
+			// update theme color to uniform format, e.g. #00ff00
+			// invalid theme colors get set to null
+			instance.color = formatColor(instance.color);
+
+			return queryRunner.query('UPDATE "instance" SET "themeColor" = :themeColor WHERE "id" = :id', instance);
+		})));
+
+		// 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" = :color', { color: formatColor(metas[0].color) });
+			}
+		});
+	}
+
+	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/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/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;

From 65b048bb70dc986870a7fc72446ff52fd3f5a32f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 19 May 2022 17:21:08 +0900
Subject: [PATCH 138/258] chore(client): tweak loading spinner design

---
 packages/client/src/components/global/loading.vue | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue
index 933c5e5881..fa2ce1800c 100644
--- a/packages/client/src/components/global/loading.vue
+++ b/packages/client/src/components/global/loading.vue
@@ -1,14 +1,14 @@
 <template>
 <div class="yxspomdl" :class="{ inline, colored, mini }">
 	<div class="container">
-		<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;"/>
+		<svg class="spinner 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="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;"/>
+		<svg class="spinner 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>

From 3d46da64a8b93738013f761f5d389438aa5953de Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 May 2022 17:35:11 +0900
Subject: [PATCH 139/258] chore(deps): bump async from 3.2.1 to 3.2.3 in
 /packages/client (#8707)

Bumps [async](https://github.com/caolan/async) from 3.2.1 to 3.2.3.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/master/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v3.2.1...v3.2.3)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 packages/client/yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index abf3b24dee..cd85ce4cea 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -752,9 +752,9 @@ astral-regex@^2.0.0:
   integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
 
 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"

From a273940348c662bd2968a0a221e9313fc80e5707 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Thu, 19 May 2022 10:35:43 +0200
Subject: [PATCH 140/258] fix(client): fix lint issues in Deck UI components
 (#8681)

---
 .../client/src/ui/deck/antenna-column.vue     |  4 +--
 packages/client/src/ui/deck/column-core.vue   |  2 +-
 packages/client/src/ui/deck/column.vue        | 26 +++++++++----------
 packages/client/src/ui/deck/deck-store.ts     | 18 ++++++-------
 packages/client/src/ui/deck/direct-column.vue |  2 +-
 packages/client/src/ui/deck/list-column.vue   |  4 +--
 packages/client/src/ui/deck/main-column.vue   |  2 +-
 .../client/src/ui/deck/mentions-column.vue    |  2 +-
 .../src/ui/deck/notifications-column.vue      |  2 +-
 packages/client/src/ui/deck/tl-column.vue     |  4 +--
 .../client/src/ui/deck/widgets-column.vue     |  2 +-
 11 files changed, 34 insertions(+), 34 deletions(-)

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..fbaea64f56 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>();
@@ -193,9 +193,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,21 +204,21 @@ function onDragstart(e) {
 	}, 10);
 }
 
-function onDragend(e) {
+function onDragend(ev) {
 	dragging = false;
 }
 
-function onDragover(e) {
+function onDragover(ev) {
 	// 自分自身がドラッグされている場合
 	if (dragging) {
 		// 自分自身にはドロップさせない
-		e.dataTransfer.dropEffect = 'none';
+		ev.dataTransfer.dropEffect = 'none';
 		return;
 	}
 
-	const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_;
+	const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
 
-	e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
+	ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
 
 	if (!dragging && isDeckColumn) draghover = true;
 }
@@ -227,12 +227,12 @@ 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..c2c9ae540b 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();
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 89d618382e..6dd040cb8d 100644
--- a/packages/client/src/ui/deck/notifications-column.vue
+++ b/packages/client/src/ui/deck/notifications-column.vue
@@ -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;
 }>();
 
 function func() {
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 904cfb58d6..10c6f5adf6 100644
--- a/packages/client/src/ui/deck/widgets-column.vue
+++ b/packages/client/src/ui/deck/widgets-column.vue
@@ -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);

From 67126500197a8d6b5948920f1bee3c0684878cf8 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 May 2022 18:39:13 +0900
Subject: [PATCH 141/258] chore(deps): bump async from 3.2.1 to 3.2.3 (#8501)

Bumps [async](https://github.com/caolan/async) from 3.2.1 to 3.2.3.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/master/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v3.2.1...v3.2.3)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 4750fa7064..7423df30cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -468,9 +468,9 @@ async-settle@^1.0.0:
     async-done "^1.2.2"
 
 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"

From 992fd76067901204e1a217a82c9cc61820ddf68b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 19 May 2022 18:40:13 +0900
Subject: [PATCH 142/258] chore(deps): bump copy-props from 2.0.4 to 2.0.5
 (#8709)

Bumps [copy-props](https://github.com/gulpjs/copy-props) from 2.0.4 to 2.0.5.
- [Release notes](https://github.com/gulpjs/copy-props/releases)
- [Changelog](https://github.com/gulpjs/copy-props/blob/master/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/copy-props/compare/2.0.4...2.0.5)

---
updated-dependencies:
- dependency-name: copy-props
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 7423df30cd..edcc023fec 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -382,7 +382,7 @@ arr-union@^3.1.0:
 array-each@^1.0.0, array-each@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f"
-  integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8=
+  integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==
 
 array-initial@^1.0.0:
   version "1.1.0"
@@ -1005,12 +1005,12 @@ copy-descriptor@^0.1.0:
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
 copy-props@^2.0.1:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe"
-  integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2"
+  integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==
   dependencies:
-    each-props "^1.3.0"
-    is-plain-object "^2.0.1"
+    each-props "^1.3.2"
+    is-plain-object "^5.0.0"
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -1261,7 +1261,7 @@ duplexify@^3.6.0:
     readable-stream "^2.0.0"
     stream-shift "^1.0.0"
 
-each-props@^1.3.0:
+each-props@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333"
   integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==
@@ -2276,6 +2276,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   dependencies:
     isobject "^3.0.1"
 
+is-plain-object@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
 is-relative@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"

From 1d9a4f68f472def57c259290cfacb7b7d238e490 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Thu, 19 May 2022 13:28:08 +0200
Subject: [PATCH 143/258] Refactor pleaseLogin to show a sign-in dialog (#8630)

* refactor(client): refactor pleaseLogin to show a sign-in dialog

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 locales/ja-JP.yml                             |   2 +-
 .../client/src/components/signin-dialog.vue   |  16 +-
 packages/client/src/components/signin.vue     | 382 +++++++++---------
 packages/client/src/scripts/please-login.ts   |  19 +-
 4 files changed, 215 insertions(+), 204 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c52762bf38..f64246d155 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -425,7 +425,7 @@ quoteQuestion: "引用として添付しますか?"
 noMessagesYet: "まだチャットはありません"
 newMessageExists: "新しいメッセージがあります"
 onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
-signinRequired: "ログインしてください"
+signinRequired: "続行する前に、サインアップまたはサインインが必要です"
 invitations: "招待"
 invitationCode: "招待コード"
 checking: "確認しています"
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 d140e143d3..e3d92dc431 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -1,41 +1,44 @@
 <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>
 			<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" 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>
@@ -47,199 +50,192 @@
 </form>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, 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,
-		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
+const MkCaptcha = defineAsyncComponent(() => import('./captcha.vue'));
+
+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);
+
+const meta = $computed(() => instance);
+
+const emit = defineEmits<{
+	(ev: 'login', v: any): void;
+}>();
+
+const props = defineProps({
+	withAvatar: {
+		type: Boolean,
+		required: false,
+		default: true
 	},
-
-	props: {
-		withAvatar: {
-			type: Boolean,
-			required: false,
-			default: true
-		},
-		autoSet: {
-			type: Boolean,
-			required: false,
-			default: false,
-		}
+	autoSet: {
+		type: Boolean,
+		required: false,
+		default: false,
 	},
-
-	emits: ['login'],
-
-	data() {
-		return {
-			signing: false,
-			user: null,
-			username: '',
-			password: '',
-			token: '',
-			apiUrl,
-			host: toUnicode(host),
-			totpLogin: false,
-			credential: null,
-			challengeData: null,
-			queryingKey: false,
-			hCaptchaResponse: null,
-			reCaptchaResponse: null,
-		};
-	},
-
-	computed: {
-		meta() {
-			return this.$instance;
-		},
-	},
-
-	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,
-					'hcaptcha-response': this.hCaptchaResponse,
-					'g-recaptcha-response': this.reCaptchaResponse,
-					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;
-			});
-		},
-
-		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,
-						'hcaptcha-response': this.hCaptchaResponse,
-						'g-recaptcha-response': this.reCaptchaResponse,
-					}).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,
-					'hcaptcha-response': this.hCaptchaResponse,
-					'g-recaptcha-response': this.reCaptchaResponse,
-					token: this.user && this.user.twoFactorEnabled ? this.token : undefined,
-				}).then(res => {
-					this.$emit('login', res);
-					this.onLogin(res);
-				}).catch(this.loginFailed);
-			}
-		},
-
-		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)
-					});
-				}
-			}
-
-			this.challengeData = null;
-			this.totpLogin = false;
-			this.signing = false;
-		},
-
-		resetPassword() {
-			os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
-			}, 'closed');
-		}
+	message: {
+		type: String,
+		required: false,
+		default: ''
 	}
 });
+
+function onUsernameChange() {
+	os.api('users/show', {
+		username: username
+	}).then(user => {
+		user = user;
+	}, () => {
+		user = null;
+	});
+}
+
+function onLogin(res) {
+	if (props.autoSet) {
+		return login(res.i);
+	}
+}
+
+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;
+	});
+}
+
+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);
+	}
+}
+
+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;
+		}
+		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/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');
 }

From 3abb0d589d201f7b9c5ad8cde249b9de64faa550 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Thu, 19 May 2022 13:30:23 +0200
Subject: [PATCH 144/258] refactor(client): refactor admin/settings to use
 Composition API (#8678)

---
 packages/client/src/pages/admin/settings.vue | 266 +++++++++----------
 1 file changed, 125 insertions(+), 141 deletions(-)

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>

From 0f33864df1497f43bcd931e6e2403191a90bb017 Mon Sep 17 00:00:00 2001
From: xianon <xianon@hotmail.co.jp>
Date: Thu, 19 May 2022 20:32:55 +0900
Subject: [PATCH 145/258] =?UTF-8?q?fix:=20=E3=83=8E=E3=83=BC=E3=83=88?=
 =?UTF-8?q?=E8=A9=B3=E7=B4=B0=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=AE=E6=96=B0?=
 =?UTF-8?q?=E3=81=97=E3=81=84=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92=E8=A1=A8?=
 =?UTF-8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD=E3=81=AE=E5=8B=95?=
 =?UTF-8?q?=E4=BD=9C=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8F=E3=81=AA=E3=82=8B?=
 =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3=E3=81=99=E3=82=8B?=
 =?UTF-8?q?=20(#8607)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* ノート詳細で新しいノートの表示が正しくないのを修正する

* ノート詳細から別のノート詳細を表示した時に前後の表示をリセットする
---
 packages/client/src/components/ui/pagination.vue | 9 +++++++--
 packages/client/src/pages/note.vue               | 4 ++++
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 9dd18785bc..428a9d0225 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>
@@ -278,7 +284,6 @@ defineExpose({
 	queue,
 	backed,
 	reload,
-	fetchMoreAhead,
 	prepend,
 	append,
 	removeItem,
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index 29261ec484..9e174ef495 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

From 60fd793bc325303668028150930cbcde8fd9fb1a Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 19 May 2022 13:38:14 +0200
Subject: [PATCH 146/258] enhance(MFM): limit large MFM (#8540)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* add CSS classes for zoom MFM

* limit nesting of x2, x3, x4 MFM

* simplify CSS calculation

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>

Co-authored-by: Acid Chicken (硫酸鶏) <root@acid-chicken.com>
---
 .../global/misskey-flavored-markdown.vue      | 26 +++++++++++++++++++
 packages/client/src/components/mfm.ts         | 15 ++++++-----
 2 files changed, 35 insertions(+), 6 deletions(-)

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/mfm.ts b/packages/client/src/components/mfm.ts
index 6ac410762d..4556a82d55 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -142,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 =

From edfded7fb7e55a83b21256469fd3a58dec1bfe20 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 19 May 2022 13:40:16 +0200
Subject: [PATCH 147/258] fix(activitypub): add authorization checks (#8534)

* fix spelling

* fix(activitypub): add authorization checks
---
 .../activitypub/kernel/announce/note.ts       |  3 +++
 .../remote/activitypub/kernel/delete/index.ts | 22 +++++++++----------
 .../activitypub/kernel/undo/announce.ts       |  1 +
 .../src/services/note/reaction/create.ts      |  5 +++++
 4 files changed, 20 insertions(+), 11 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
index 680749f4d8..052751c654 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)) 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/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/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts
index 5a0948bca9..5cb7ebdcd1 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)) {
+		throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
+	}
+
 	// TODO: cache
 	reaction = await toDbReaction(reaction, user.host);
 

From be1d02a7f8acc751c88894e0c9d4ed0bc4080b85 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 19 May 2022 13:41:47 +0200
Subject: [PATCH 148/258] enhance: page image component with alt text (#8634)

* refactor to composition API

* use existing image component

This improves user experience because alt text is displayed correctly.

* fix: correct image src

* fix: defineProps

* fix
---
 .../client/src/components/page/page.image.vue | 28 ++++++-------------
 1 file changed, 8 insertions(+), 20 deletions(-)

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>

From 68f9341e955380cccd9f3df4a169f5fc19c0c09f Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 19 May 2022 15:40:48 +0200
Subject: [PATCH 149/258] hotfix: uniform color migration fix

---
 .../migration/1652859567549-uniform-themecolor.js  | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js
index bc47143e54..8da1fd7fbb 100644
--- a/packages/backend/migration/1652859567549-uniform-themecolor.js
+++ b/packages/backend/migration/1652859567549-uniform-themecolor.js
@@ -6,27 +6,25 @@ export class uniformThemecolor1652859567549 {
 	async up(queryRunner) {
 		const formatColor = (color) => {
 			let tc = new tinycolor(color);
-			if (color.isValid()) {
-				return color.toHexString();
+			if (tc.isValid()) {
+				return tc.toHexString();
 			} else {
 				return null;
 			}
 		};
 
-		await Promise.all(queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL')
-		.then(instances => instances.map(instance => {
+		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
-			instance.color = formatColor(instance.color);
-
-			return queryRunner.query('UPDATE "instance" SET "themeColor" = :themeColor WHERE "id" = :id', instance);
+			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" = :color', { color: formatColor(metas[0].color) });
+				return queryRunner.query('UPDATE "meta" SET "themeColor" = $1', [formatColor(metas[0].themeColor)]);
 			}
 		});
 	}

From b811de53b6c6841ec0816f58ea3c759be6f94a1e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 19 May 2022 23:23:12 +0900
Subject: [PATCH 150/258] fix(client): make emoji stand out more on reaction
 button

Fix #8520
Close #8521

Co-Authored-By: Johann150 <20990607+Johann150@users.noreply.github.com>
---
 .../src/components/reactions-viewer.reaction.vue     | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

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;

From 4bb04a2c247846e7216d244dd0fad9afad82af9d Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 21 May 2022 02:48:40 +0200
Subject: [PATCH 151/258] chore(meta): add pixeldesu to patron list (#8714)

---
 packages/client/src/pages/about-misskey.vue | 1 +
 1 file changed, 1 insertion(+)

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;

From 5e55b192474d07206efff17fcba5dbe19f119c5e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 11:41:41 +0900
Subject: [PATCH 152/258] feat(dev): introduce Pull Request Labeler

---
 .github/labeler.yml           |  5 +++++
 .github/workflows/labeler.yml | 14 ++++++++++++++
 2 files changed, 19 insertions(+)
 create mode 100644 .github/labeler.yml
 create mode 100644 .github/workflows/labeler.yml

diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000000..5d4564c683
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,5 @@
+'⚙️Server':
+- packages/backend/**/*
+
+'🖥️Client':
+- packages/client/**/*
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 0000000000..057208eda3
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,14 @@
+name: "Pull Request Labeler"
+on:
+- pull_request_target
+
+jobs:
+  triage:
+    permissions:
+      contents: read
+      pull-requests: write
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/labeler@v4
+      with:
+        repo-token: "${{ secrets.GITHUB_TOKEN }}"

From 02ec5b1dbe88ca0bbf387bb6d8585277a43a09f9 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sat, 21 May 2022 16:45:42 +0900
Subject: [PATCH 153/258] chore(deps): bump hosted-git-info from 2.8.8 to 2.8.9
 (#8708)

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

---
updated-dependencies:
- dependency-name: hosted-git-info
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
 yarn.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index edcc023fec..1e04e27526 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2001,9 +2001,9 @@ homedir-polyfill@^1.0.1:
     parse-passwd "^1.0.0"
 
 hosted-git-info@^2.1.4:
-  version "2.8.8"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
-  integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+  version "2.8.9"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
 
 html-comment-regex@^1.1.0:
   version "1.1.2"

From 2205c61edf5dacb75deabda7b2772d4acb9b7303 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 17:40:43 +0900
Subject: [PATCH 154/258] Update utils.ts

---
 packages/backend/test/utils.ts | 70 ++++++++++++++++------------------
 1 file changed, 33 insertions(+), 37 deletions(-)

diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 0a495b3391..e900746be9 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -1,14 +1,15 @@
 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 'node: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);
@@ -27,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);
@@ -59,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);
@@ -70,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> {
@@ -112,8 +113,8 @@ export function connectStream(user: any, channel: string, listener: (message: Re
 					channel: channel,
 					id: 'a',
 					pong: true,
-					params: params
-				}
+					params: params,
+				},
 			}));
 		});
 	});
@@ -124,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);
@@ -146,7 +147,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce
 	return (done: (err?: Error) => any) => {
 		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 => {
@@ -158,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({
+	return new DataSource({
 		type: 'postgres',
 		host: config.db.host,
 		port: config.db.port,
@@ -172,7 +168,7 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
 		database: config.db.db,
 		synchronize: true && !justBorrow,
 		dropSchema: true && !justBorrow,
-		entities: initEntities || entities
+		entities: initEntities || entities,
 	});
 }
 
@@ -185,7 +181,7 @@ export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProc
 
 		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));

From 425084b596edd4aae5736031039f2fc2f4cb05dd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 22:07:01 +0900
Subject: [PATCH 155/258] Update utils.ts

---
 packages/backend/test/utils.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index e900746be9..4e98f13328 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -159,7 +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';
 
-	return new DataSource({
+	const db = new DataSource({
 		type: 'postgres',
 		host: config.db.host,
 		port: config.db.port,
@@ -170,6 +170,10 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
 		dropSchema: true && !justBorrow,
 		entities: initEntities || entities,
 	});
+
+	await db.initialize();
+
+	return db;
 }
 
 export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProcess> {

From 05c4d6b11ef935c462de7e9b69767b3087ebb3f1 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 22:07:11 +0900
Subject: [PATCH 156/258] refactor

---
 packages/backend/src/db/postgre.ts | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index eb5fc2e186..e09e93f04e 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,8 @@ 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';
 
 const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
 
@@ -212,7 +211,7 @@ export async function initDb() {
 	if (db.isInitialized) {
 		// nop
 	} else {
-		await db.connect();
+		await db.initialize();
 	}
 }
 

From b8544814ec5743e242940d82b02bdf57d11b6047 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 22:21:41 +0900
Subject: [PATCH 157/258] lint

---
 packages/backend/test/activitypub.ts        |   6 +-
 packages/backend/test/ap-request.ts         |   8 +-
 packages/backend/test/api-visibility.ts     |  28 ++--
 packages/backend/test/block.ts              |   2 +-
 packages/backend/test/chart.ts              |  64 ++++-----
 packages/backend/test/extract-mentions.ts   |  14 +-
 packages/backend/test/fetch-resource.ts     |   6 +-
 packages/backend/test/get-file-info.ts      |  18 +--
 packages/backend/test/misc/mock-resolver.ts |   6 +-
 packages/backend/test/mute.ts               |   4 +-
 packages/backend/test/note.ts               |  92 ++++++-------
 packages/backend/test/prelude/url.ts        |   2 +-
 packages/backend/test/streaming.ts          | 142 ++++++++++----------
 packages/backend/test/user-notes.ts         |  10 +-
 14 files changed, 201 insertions(+), 201 deletions(-)

diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts
index 70f35cafd8..5d8b28ec7a 100644
--- a/packages/backend/test/activitypub.ts
+++ b/packages/backend/test/activitypub.ts
@@ -1,7 +1,7 @@
 process.env.NODE_ENV = 'test';
 
-import rndstr from 'rndstr';
 import * as assert from 'assert';
+import rndstr from 'rndstr';
 import { initTestDb } from './utils.js';
 
 describe('ActivityPub', () => {
@@ -57,8 +57,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..823e388a82 100644
--- a/packages/backend/test/chart.ts
+++ b/packages/backend/test/chart.ts
@@ -2,7 +2,6 @@ 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';
@@ -11,6 +10,7 @@ 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 { async, initTestDb } from './utils.js';
 
 describe('Chart', () => {
 	let testChart: TestChart;
@@ -33,7 +33,7 @@ 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)),
 		});
 	}));
 
@@ -52,7 +52,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -60,7 +60,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 	}));
@@ -76,7 +76,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [1, 0, 0],
 				inc: [0, 0, 0],
-				total: [-1, 0, 0]
+				total: [-1, 0, 0],
 			},
 		});
 
@@ -84,7 +84,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [1, 0, 0],
 				inc: [0, 0, 0],
-				total: [-1, 0, 0]
+				total: [-1, 0, 0],
 			},
 		});
 	}));
@@ -97,7 +97,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [0, 0, 0]
+				total: [0, 0, 0],
 			},
 		});
 
@@ -105,7 +105,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [0, 0, 0]
+				total: [0, 0, 0],
 			},
 		});
 	}));
@@ -123,7 +123,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [3, 0, 0],
-				total: [3, 0, 0]
+				total: [3, 0, 0],
 			},
 		});
 
@@ -131,7 +131,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [3, 0, 0],
-				total: [3, 0, 0]
+				total: [3, 0, 0],
 			},
 		});
 	}));
@@ -149,7 +149,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -157,7 +157,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 	}));
@@ -178,7 +178,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 1, 0],
-				total: [2, 1, 0]
+				total: [2, 1, 0],
 			},
 		});
 
@@ -186,7 +186,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
 	}));
@@ -238,7 +238,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 1],
-				total: [2, 1, 1]
+				total: [2, 1, 1],
 			},
 		});
 
@@ -246,7 +246,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
 	}));
@@ -265,7 +265,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [1, 1, 1]
+				total: [1, 1, 1],
 			},
 		});
 
@@ -273,7 +273,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 	}));
@@ -296,7 +296,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [2, 1, 1]
+				total: [2, 1, 1],
 			},
 		});
 
@@ -304,7 +304,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
 	}));
@@ -325,7 +325,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -333,7 +333,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
 	}));
@@ -356,7 +356,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -364,7 +364,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
 	}));
@@ -383,7 +383,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [1, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -391,7 +391,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [1, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -399,7 +399,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [0, 0, 0]
+					total: [0, 0, 0],
 				},
 			});
 
@@ -407,7 +407,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [0, 0, 0]
+					total: [0, 0, 0],
 				},
 			});
 		}));
@@ -493,7 +493,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -501,7 +501,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 		}));
@@ -523,7 +523,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 1, 0],
-					total: [100, 1, 0]
+					total: [100, 1, 0],
 				},
 			});
 
@@ -531,7 +531,7 @@ 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 c0fa8ed143..7ce98db50f 100644
--- a/packages/backend/test/get-file-info.ts
+++ b/packages/backend/test/get-file-info.ts
@@ -18,7 +18,7 @@ describe('Get file info', () => {
 			md5: 'd41d8cd98f00b204e9800998ecf8427e',
 			type: {
 				mime: 'application/octet-stream',
-				ext: null
+				ext: null,
 			},
 			width: undefined,
 			height: undefined,
@@ -36,7 +36,7 @@ describe('Get file info', () => {
 			md5: '091b3f259662aa31e2ffef4519951168',
 			type: {
 				mime: 'image/jpeg',
-				ext: 'jpg'
+				ext: 'jpg',
 			},
 			width: 512,
 			height: 512,
@@ -54,7 +54,7 @@ describe('Get file info', () => {
 			md5: '08189c607bea3b952704676bb3c979e0',
 			type: {
 				mime: 'image/apng',
-				ext: 'apng'
+				ext: 'apng',
 			},
 			width: 256,
 			height: 256,
@@ -72,7 +72,7 @@ describe('Get file info', () => {
 			md5: '32c47a11555675d9267aee1a86571e7e',
 			type: {
 				mime: 'image/gif',
-				ext: 'gif'
+				ext: 'gif',
 			},
 			width: 256,
 			height: 256,
@@ -90,7 +90,7 @@ describe('Get file info', () => {
 			md5: 'f73535c3e1e27508885b69b10cf6e991',
 			type: {
 				mime: 'image/png',
-				ext: 'png'
+				ext: 'png',
 			},
 			width: 256,
 			height: 256,
@@ -108,7 +108,7 @@ describe('Get file info', () => {
 			md5: 'b6f52b4b021e7b92cdd04509c7267965',
 			type: {
 				mime: 'image/svg+xml',
-				ext: 'svg'
+				ext: 'svg',
 			},
 			width: 256,
 			height: 256,
@@ -127,7 +127,7 @@ describe('Get file info', () => {
 			md5: '4b7a346cde9ccbeb267e812567e33397',
 			type: {
 				mime: 'image/svg+xml',
-				ext: 'svg'
+				ext: 'svg',
 			},
 			width: 256,
 			height: 256,
@@ -145,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,
@@ -163,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/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 f99d7aeff2..5b7933da67 100644
--- a/packages/backend/test/user-notes.ts
+++ b/packages/backend/test/user-notes.ts
@@ -23,13 +23,13 @@ describe('users/notes', () => {
 		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],
 		});
 	});
 
@@ -40,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);
@@ -53,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);

From 53fc1235d72d87b53956c449fd308abfd9d01ca5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 21 May 2022 22:24:57 +0900
Subject: [PATCH 158/258] Update .mocharc.json

---
 packages/backend/.mocharc.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json
index 26628066eb..589522216d 100644
--- a/packages/backend/.mocharc.json
+++ b/packages/backend/.mocharc.json
@@ -5,6 +5,6 @@
 		"loader=./test/loader.js"
 	],
 	"slow": 1000,
-	"timeout": 35000,
+	"timeout": 3000,
 	"exit": true
 }

From 12359697214d6124a540bad9230e947401165c45 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 22 May 2022 14:49:53 +0900
Subject: [PATCH 159/258] chore(dev): tweak text

---
 CONTRIBUTING.md | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 62a7dd9ff2..135a3e140c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,11 @@
 # Contribution guide
 We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
 
-**ℹ️ Important:** This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
-Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
-The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
-It will also allow the reader to use the translation tool of their preference if necessary.
+> **Note**
+> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
+> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
+> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
+> It will also allow the reader to use the translation tool of their preference if necessary.
 
 ## Roadmap
 See [ROADMAP.md](./ROADMAP.md)
@@ -15,7 +16,9 @@ Before creating an issue, please check the following:
 - Do not use Issues to ask questions or troubleshooting.
 	- Issues should only be used to feature requests, suggestions, and bug tracking.
 	- Please ask questions or troubleshooting in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3).
-- Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
+
+> **Warning**
+> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
 
 ## Before implementation
 When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.

From 563cb36a8fa574d352437d07ac2beeac41f6fa72 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Mon, 23 May 2022 21:55:06 +0200
Subject: [PATCH 160/258] fix(client): fix undefined data value on 2FA settings
 (#8725)

---
 packages/client/src/pages/settings/2fa.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 01dd9b74a2..a19d6378d7 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -52,7 +52,7 @@
 					</template>
 				</I18n>
 			</li>
-			<li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ data.url }}</p></li>
+			<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>

From f90c947036dc6d9c0b7e4ea7cfa1dc53a0b98c15 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Mon, 23 May 2022 22:43:13 +0200
Subject: [PATCH 161/258] fix(client): wrong scoping breaks 2FA

---
 packages/client/src/components/signin.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index e3d92dc431..d283a758a6 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -106,8 +106,8 @@ const props = defineProps({
 function onUsernameChange() {
 	os.api('users/show', {
 		username: username
-	}).then(user => {
-		user = user;
+	}).then(userResponse => {
+		user = userResponse;
 	}, () => {
 		user = null;
 	});

From 0a050eac56534267a8ed521a7b64338954b7e0da Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
Date: Mon, 23 May 2022 23:10:00 -0700
Subject: [PATCH 162/258] Update README.md (#8729)

---
 README.md | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/README.md b/README.md
index 9d7c59633a..4b7b3273d9 100644
--- a/README.md
+++ b/README.md
@@ -6,24 +6,21 @@
 **🌎 **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! 🚀**
 	
 ---
-	
+
 <a href="https://misskey-hub.net/instances.html">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>	
-	
+		<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
+
 <a href="https://misskey-hub.net/docs/install.html">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>	
+		<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
 
 <a href="./CONTRIBUTING.md">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/>
-</a>
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
 
 <a href="https://discord.gg/Wp8gVStHW3">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/>
-</a>
-	
+		<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
+
 <a href="https://www.patreon.com/syuilo">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/>
-</a>
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
 	
 ---
 

From 6b109c7b0fc701ab626df0401c46de1629573f84 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 24 May 2022 10:12:42 +0200
Subject: [PATCH 163/258] fix: wrong type for isVisibleForMe

---
 packages/backend/src/remote/activitypub/kernel/announce/note.ts | 2 +-
 packages/backend/src/services/note/reaction/create.ts           | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
index 052751c654..759cb4ae83 100644
--- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts
+++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
@@ -53,7 +53,7 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac
 			throw e;
 		}
 
-		if (!await Notes.isVisibleForMe(renote, actor)) return 'skip: invalid actor for this activity';
+		if (!await Notes.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity';
 
 		logger.info(`Creating the (Re)Note: ${uri}`);
 
diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts
index 5cb7ebdcd1..83d302826a 100644
--- a/packages/backend/src/services/note/reaction/create.ts
+++ b/packages/backend/src/services/note/reaction/create.ts
@@ -28,7 +28,7 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
 	}
 
 	// check visibility
-	if (!await Notes.isVisibleForMe(note, user)) {
+	if (!await Notes.isVisibleForMe(note, user.id)) {
 		throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
 	}
 

From 33debe73d56ded12ff9623791e9654a0c2c54a4e Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 24 May 2022 11:14:00 +0200
Subject: [PATCH 164/258] feat(dev): highlight editing of wrong locales

Highlight PRs that edit locales other than the ja-JP one so the author may see and fix it themselves.
---
 .github/labeler.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/labeler.yml b/.github/labeler.yml
index 5d4564c683..dff3935571 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -3,3 +3,6 @@
 
 '🖥️Client':
 - packages/client/**/*
+
+'‼️ wrong locales':
+- any: ['locales/*.yml', '!locales/ja-JP.yml']

From 6b44fe165b5119278ab4f852e2ac5028d841af34 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Wed, 25 May 2022 16:35:30 +0900
Subject: [PATCH 165/258] Supports Unicode Emoji 14.0 (#8699)

* Unicode 14.0 Emoji

* mfm-js@0.22.0

* CHANGELOG

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 CHANGELOG.md                       |  1 +
 packages/backend/package.json      |  4 +--
 packages/backend/yarn.lock         | 27 ++++++++-----------
 packages/client/package.json       |  2 +-
 packages/client/src/emojilist.json | 42 +++++++++++++++++++++++++++---
 packages/client/yarn.lock          | 14 +++++-----
 6 files changed, 61 insertions(+), 29 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21ae948d0a..6aadc0b2ae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ You should also include the user name that made the change.
 - update dependencies @syuilo
 - enhance: display URL of QR code for TOTP registration @syuilo
 - make CAPTCHA required for signin to improve security @syuilo
+- enhance: Supports Unicode Emoji 14.0 @mei23
 - The theme color is now better validated. @Johann150
   Your own theme color may be unset if it was in an invalid format.
   Admins should check their instance settings if in doubt.
diff --git a/packages/backend/package.json b/packages/backend/package.json
index ab0e9fbc16..88f824ea7c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -15,7 +15,7 @@
 	},
 	"dependencies": {
 		"@bull-board/koa": "3.10.4",
-		"@discordapp/twemoji": "13.1.1",
+		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.11.0",
 		"@koa/cors": "3.1.0",
 		"@koa/multer": "3.0.0",
@@ -65,7 +65,7 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"mfm-js": "0.21.0",
+		"mfm-js": "0.22.0",
 		"mime-types": "2.1.35",
 		"misskey-js": "0.0.14",
 		"mocha": "9.2.2",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 9c40715e1a..82a6e4f501 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -89,14 +89,14 @@
     ky "^0.25.1"
     ky-universal "^0.8.2"
 
-"@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":
@@ -4619,12 +4619,12 @@ methods@^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.0:
+  version "0.22.0"
+  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.0.tgz#f619e6358e865dde948b72c1688615b616f5571f"
+  integrity sha512-81Asd97Sjs66mRiCZ8qpFQvkHt6kDaxdRCUy3OAW8vJJuBADiVs10iHc9SFpqa8g+DJmFG0NduBRYT0/2LtxQQ==
   dependencies:
-    twemoji-parser "13.1.x"
+    twemoji-parser "14.0.x"
 
 micromatch@^4.0.0, micromatch@^4.0.2:
   version "4.0.2"
@@ -6865,12 +6865,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==
diff --git a/packages/client/package.json b/packages/client/package.json
index 00950ae1b5..0ea37e388c 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -35,7 +35,7 @@
 		"json5": "2.2.1",
 		"katex": "0.15.3",
 		"matter-js": "0.18.0",
-		"mfm-js": "0.21.0",
+		"mfm-js": "0.22.0",
 		"misskey-js": "0.0.14",
 		"mocha": "9.2.2",
 		"ms": "2.1.3",
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/yarn.lock b/packages/client/yarn.lock
index cd85ce4cea..efc6d9ddb3 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -2847,12 +2847,12 @@ merge@^2.1.0:
   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.0:
+  version "0.22.0"
+  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.0.tgz#f619e6358e865dde948b72c1688615b616f5571f"
+  integrity sha512-81Asd97Sjs66mRiCZ8qpFQvkHt6kDaxdRCUy3OAW8vJJuBADiVs10iHc9SFpqa8g+DJmFG0NduBRYT0/2LtxQQ==
   dependencies:
-    twemoji-parser "13.1.x"
+    twemoji-parser "14.0.x"
 
 micromatch@^4.0.2:
   version "4.0.2"
@@ -4039,12 +4039,12 @@ 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:
+twemoji-parser@13.1.0:
   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==

From 83b831d9759cd3a3e10647a524cfdda130dc3db5 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Wed, 25 May 2022 09:37:15 +0200
Subject: [PATCH 166/258] Refactor my-antennas/edit to use Composition API
 (#8680)

* refactor(client): refactor my-antennas/edit to use Composition API

* fix(client): apply review suggestions
---
 .../client/src/pages/my-antennas/edit.vue     |  55 ++---
 .../client/src/pages/my-antennas/editor.vue   | 195 +++++++-----------
 2 files changed, 100 insertions(+), 150 deletions(-)

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];
-			}
-		}
-	},
-
-	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;
-	},
-
-	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');
-			}
-		},
-
-		async deleteAntenna() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.antenna.name }),
-			});
-			if (canceled) return;
-
-			await os.api('antennas/delete', {
-				antennaId: this.antenna.id,
-			});
-
-			os.success();
-			this.$emit('deleted');
-		},
-
-		addUser() {
-			os.selectUser().then(user => {
-				this.users = this.users.trim();
-				this.users += '\n@' + Acct.toString(user);
-				this.users = this.users.trim();
-			});
-		}
+		userGroups = [...groups1, ...groups2];
 	}
 });
+
+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(' ')),
+	};
+
+	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 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: props.antenna.id,
+	});
+
+	os.success();
+	emit('deleted');
+}
+
+function addUser() {
+	os.selectUser().then(user => {
+		users = users.trim();
+		users += '\n@' + Acct.toString(user as any);
+		users = users.trim();
+	});
+}
 </script>
 
 <style lang="scss" scoped>

From 67f3515dc179ecf789932eea59a80e5a57c73a66 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Wed, 25 May 2022 09:37:35 +0200
Subject: [PATCH 167/258] Refactor admin/overview to use Composition API
 (#8674)

* refactor(client): refactor admin/overview to use Composition API

* fix(client): apply review suggestions
---
 packages/client/src/pages/admin/overview.vue | 118 ++++++-------------
 1 file changed, 38 insertions(+), 80 deletions(-)

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('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/users', { limit: 2, span: 'day' }).then(chart => {
+			usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
 		});
 
-		os.api('admin/server-info', {}).then(serverInfo => {
-			this.serverInfo = serverInfo;
+		os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
+			notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
 		});
+	});
 
-		this.$nextTick(() => {
-			this.queueStatsConnection.send('requestLog', {
-				id: Math.random().toString().substr(2, 8),
-				length: 200
-			});
+	os.api('admin/server-info').then(serverInfoResponse => {
+		serverInfo = serverInfoResponse;
+	});
+
+	nextTick(() => {
+		queueStatsConnection.send('requestLog', {
+			id: Math.random().toString().substr(2, 8),
+			length: 200
 		});
-	},
+	});
+});
 
-	beforeUnmount() {
-		this.queueStatsConnection.dispose();
-	},
+onBeforeUnmount(() => {
+	queueStatsConnection.dispose();
+});
 
-	methods: {
-		async showInstanceInfo(q) {
-			let instance = q;
-			if (typeof q === 'string') {
-				instance = await os.api('federation/show-instance', {
-					host: q
-				});
-			}
-			// TODO
-		},
-
-		bytes,
-
-		number,
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.dashboard,
+		icon: 'fas fa-tachometer-alt',
+		bg: 'var(--bg)',
 	}
 });
 </script>

From 81fccb56565dfa90a619b94ff8a69a460cdabc24 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Wed, 25 May 2022 09:38:18 +0200
Subject: [PATCH 168/258] refactor(client): refactor admin/other-settings to
 use Composition API (#8667)

---
 .../client/src/pages/admin/other-settings.vue | 61 +++++++------------
 1 file changed, 22 insertions(+), 39 deletions(-)

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>

From b049633db7bc39b3fc75f30990d0e8d17f077ec5 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Wed, 25 May 2022 09:43:12 +0200
Subject: [PATCH 169/258] Refactor widgets and fix lint issues (#8719)

* fix(client): refactor widgets and fix lint issues

* Apply review suggestions from @Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../client/src/widgets/activity.calendar.vue  |  62 ++++-----
 .../client/src/widgets/activity.chart.vue     |  89 +++++-------
 packages/client/src/widgets/activity.vue      |   6 +-
 packages/client/src/widgets/aichan.vue        |   2 +-
 packages/client/src/widgets/aiscript.vue      |   8 +-
 packages/client/src/widgets/button.vue        |   8 +-
 packages/client/src/widgets/calendar.vue      |   2 +-
 packages/client/src/widgets/clock.vue         |   2 +-
 packages/client/src/widgets/digital-clock.vue |   2 +-
 packages/client/src/widgets/federation.vue    |   2 +-
 packages/client/src/widgets/job-queue.vue     |   2 +-
 packages/client/src/widgets/memo.vue          |   2 +-
 packages/client/src/widgets/notifications.vue |   2 +-
 packages/client/src/widgets/online-users.vue  |   2 +-
 packages/client/src/widgets/photos.vue        |   2 +-
 packages/client/src/widgets/post-form.vue     |   2 +-
 packages/client/src/widgets/rss.vue           |   2 +-
 .../src/widgets/server-metric/cpu-mem.vue     | 129 +++++++++---------
 .../client/src/widgets/server-metric/cpu.vue  |  49 +++----
 .../src/widgets/server-metric/index.vue       |   4 +-
 .../client/src/widgets/server-metric/mem.vue  |  62 ++++-----
 .../client/src/widgets/server-metric/net.vue  | 128 ++++++++---------
 packages/client/src/widgets/slideshow.vue     |   4 +-
 packages/client/src/widgets/timeline.vue      |   2 +-
 packages/client/src/widgets/trends.vue        |   2 +-
 packages/client/src/widgets/widget.ts         |   2 +-
 26 files changed, 261 insertions(+), 318 deletions(-)

diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue
index b833bd65ca..2388edefc6 100644
--- a/packages/client/src/widgets/activity.calendar.vue
+++ b/packages/client/src/widgets/activity.calendar.vue
@@ -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..631beceb72 100644
--- a/packages/client/src/widgets/activity.vue
+++ b/packages/client/src/widgets/activity.vue
@@ -6,8 +6,8 @@
 	<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..70e47f2af1 100644
--- a/packages/client/src/widgets/aichan.vue
+++ b/packages/client/src/widgets/aichan.vue
@@ -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..b74e2258a9 100644
--- a/packages/client/src/widgets/aiscript.vue
+++ b/packages/client/src/widgets/aiscript.vue
@@ -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..0a35c4c5ab 100644
--- a/packages/client/src/widgets/clock.vue
+++ b/packages/client/src/widgets/clock.vue
@@ -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..1bfb068a2f 100644
--- a/packages/client/src/widgets/federation.vue
+++ b/packages/client/src/widgets/federation.vue
@@ -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..f2d1bbc047 100644
--- a/packages/client/src/widgets/memo.vue
+++ b/packages/client/src/widgets/memo.vue
@@ -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 7f0055bb43..f51e983a0e 100644
--- a/packages/client/src/widgets/notifications.vue
+++ b/packages/client/src/widgets/notifications.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, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
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..8e30765290 100644
--- a/packages/client/src/widgets/photos.vue
+++ b/packages/client/src/widgets/photos.vue
@@ -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..5b74602c85 100644
--- a/packages/client/src/widgets/post-form.vue
+++ b/packages/client/src/widgets/post-form.vue
@@ -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..6b057cdd06 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -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..7cbc7fa15f 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}`;
-
-			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];
-
-			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);
-			}
-		}
-	}
+onMounted(() => {
+	props.connection.on('stats', onStats);
+	props.connection.on('statsLog', onStatsLog);
+	props.connection.send('requestLog', {
+		id: Math.random().toString().substr(2, 8)
+	});
 });
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
+	props.connection.off('statsLog', onStatsLog);
+});
+
+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 = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
+	cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
+	memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
+	memHeadY = memPolylinePoints[memPolylinePoints.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..82b3a67d76 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
+}>();
 
-			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)));
+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);
 
-			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(' ');
-
-			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}`;
-
-			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];
-
-			this.inRecent = stats.net.rx;
-			this.outRecent = stats.net.tx;
-		},
-		onStatsLog(statsLog) {
-			for (const stats of [...statsLog].reverse()) {
-				this.onStats(stats);
-			}
-		},
-		bytes
-	}
+onMounted(() => {
+	props.connection.on('stats', onStats);
+	props.connection.on('statsLog', onStatsLog);
+	props.connection.send('requestLog', {
+		id: Math.random().toString().substr(2, 8)
+	});
 });
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
+	props.connection.off('statsLog', onStatsLog);
+});
+
+function onStats(connStats) {
+	stats.push(connStats);
+	if (stats.length > 50) stats.shift();
+
+	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)));
+
+	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(' ');
+
+	inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
+	outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
+
+	inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0];
+	inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1];
+	outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0];
+	outHeadY = outPolylinePoints[outPolylinePoints.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..1b6c2d766d 100644
--- a/packages/client/src/widgets/slideshow.vue
+++ b/packages/client/src/widgets/slideshow.vue
@@ -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..c9a9e68bb9 100644
--- a/packages/client/src/widgets/timeline.vue
+++ b/packages/client/src/widgets/timeline.vue
@@ -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,
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
index a34710eae7..34bbc16a8b 100644
--- a/packages/client/src/widgets/trends.vue
+++ b/packages/client/src/widgets/trends.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,
diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts
index 81239bfb3b..db164c2bc3 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 = {

From e27c6abaeaf0e0e0be9fba7ffc6fd165474d8592 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 25 May 2022 09:50:22 +0200
Subject: [PATCH 170/258] refactor: temporary files (#8713)

* simplify temporary files for thumbnails

Because only a single file will be written to the directory, creating a
separate directory seems unnecessary. If only a temporary file is created,
the code from `createTemp` can be reused here as well.

* refactor: deduplicate code for temporary files/directories

To follow the DRY principle, the same code should not be duplicated
across different files. Instead an already existing function is used.

Because temporary directories are also create in multiple locations,
a function for this is also newly added to reduce duplication.

* fix: clean up identicon temp files

The temporary files for identicons are not reused and can be deleted
after they are fully read. This condition is met when the stream is closed
and so the file can be cleaned up using the events API of the stream.

* fix: ensure cleanup is called when download fails

* fix: ensure cleanup is called in error conditions

This covers import/export queue jobs and is mostly just wrapping all
code in a try...finally statement where the finally runs the cleanup.

* fix: use correct type instead of `any`
---
 packages/backend/src/misc/create-temp.ts      |  13 +-
 .../queue/processors/db/export-blocking.ts    | 107 ++++++++-------
 .../processors/db/export-custom-emojis.ts     |  17 +--
 .../queue/processors/db/export-following.ts   | 109 ++++++++--------
 .../src/queue/processors/db/export-mute.ts    | 109 ++++++++--------
 .../src/queue/processors/db/export-notes.ts   | 123 +++++++++---------
 .../queue/processors/db/export-user-lists.ts  |  67 +++++-----
 .../processors/db/import-custom-emojis.ts     |  10 +-
 .../src/server/file/send-drive-file.ts        |   9 +-
 packages/backend/src/server/index.ts          |   4 +-
 .../drive/generate-video-thumbnail.ts         |  55 ++++----
 .../src/services/drive/upload-from-url.ts     |  25 ++--
 12 files changed, 307 insertions(+), 341 deletions(-)

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/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;
-		}
-
-		cursor = blockings[blockings.length - 1].id;
-
-		for (const block of blockings) {
-			const u = await Users.findOneBy({ id: block.blockeeId });
-			if (u == null) {
-				exportedCount++; continue;
+			if (blockings.length === 0) {
+				job.progress(100);
+				break;
 			}
 
-			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();
-					}
+			cursor = blockings[blockings.length - 1].id;
+
+			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();
+						}
+					});
 				});
+				exportedCount++;
+			}
+
+			const total = await Blockings.countBy({
+				blockerId: user.id,
 			});
-			exportedCount++;
+
+			job.progress(exportedCount / total);
 		}
 
-		const total = await Blockings.countBy({
-			blockerId: user.id,
-		});
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
 
-		job.progress(exportedCount / total);
+		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();
 	}
 
-	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 });
-
-	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..97ba62dcf6 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 { 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;
-		}
-
-		cursor = followings[followings.length - 1].id;
-
-		for (const following of followings) {
-			const u = await Users.findOneBy({ id: following.followeeId });
-			if (u == null) {
-				continue;
+			if (followings.length === 0) {
+				break;
 			}
 
-			if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) {
-				continue;
-			}
+			cursor = followings[followings.length - 1].id;
 
-			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();
-					}
+			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;
+				}
+
+				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}`);
+
+		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();
 	}
 
-	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 });
-
-	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;
-		}
-
-		cursor = mutes[mutes.length - 1].id;
-
-		for (const mute of mutes) {
-			const u = await Users.findOneBy({ id: mute.muteeId });
-			if (u == null) {
-				exportedCount++; continue;
+			if (mutes.length === 0) {
+				job.progress(100);
+				break;
 			}
 
-			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();
-					}
+			cursor = mutes[mutes.length - 1].id;
+
+			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();
+						}
+					});
 				});
+				exportedCount++;
+			}
+
+			const total = await Mutings.countBy({
+				muterId: user.id,
 			});
-			exportedCount++;
+
+			job.progress(exportedCount / total);
 		}
 
-		const total = await Mutings.countBy({
-			muterId: user.id,
-		});
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
 
-		job.progress(exportedCount / total);
+		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();
 	}
 
-	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 });
-
-	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;
-		}
-
-		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 });
+			if (notes.length === 0) {
+				job.progress(100);
+				break;
 			}
-			const content = JSON.stringify(serialize(note, poll));
-			const isFirst = exportedNotesCount === 0;
-			await write(isFirst ? content : ',\n' + content);
-			exportedNotesCount++;
+
+			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 });
+				}
+				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,
+			});
+
+			job.progress(exportedNotesCount / total);
 		}
 
-		const total = await Notes.countBy({
-			userId: user.id,
-		});
+		await write(']');
 
-		job.progress(exportedNotesCount / total);
+		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 });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	await write(']');
-
-	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 });
-
-	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 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 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();
+						}
+					});
+				});
+			}
 		}
+
+		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 });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	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 });
-
-	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/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 027d078ce1..c34e043145 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -4,11 +4,11 @@ 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 { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.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);
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index cd061da725..f31de2b7f4 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -89,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 => {
diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts
index ef75a9f585..ca12ab8d3d 100644
--- a/packages/backend/src/services/drive/generate-video-thumbnail.ts
+++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts
@@ -1,38 +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 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);
+
+	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%'],
+			});
 		});
-	});
 
-	await new Promise((res, rej) => {
-		FFmpeg({
-			source: path,
-		})
-		.on('end', res)
-		.on('error', rej)
-		.screenshot({
-			folder: outDir,
-			filename: 'output.png',
-			count: 1,
-			timestamps: ['5%'],
-		});
-	});
-
-	const outPath = `${outDir}/output.png`;
-
-	// JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる)
-	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/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();
 	}
 }

From a7be9be43d0c4ab387e65c77b09e58e5eef33f5e Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 25 May 2022 10:13:46 +0200
Subject: [PATCH 171/258] fix: server metrics widget

---
 packages/client/src/widgets/server-metric/cpu-mem.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue
index 7cbc7fa15f..00c3a10c9b 100644
--- a/packages/client/src/widgets/server-metric/cpu-mem.vue
+++ b/packages/client/src/widgets/server-metric/cpu-mem.vue
@@ -121,10 +121,10 @@ function onStats(connStats) {
 	cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
 	memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
 
-	cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
-	cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
-	memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
-	memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1];
+	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);

From 429f1ad061467a4f83fd4bb4a363f7c8e23cd2ae Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 25 May 2022 10:44:04 +0200
Subject: [PATCH 172/258] fix: activity widget used wrong variable name

---
 packages/client/src/widgets/activity.calendar.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue
index 2388edefc6..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"

From 8d5c9e96e46f54ba09ba35fc9758bce82a7a16f5 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 25 May 2022 16:17:00 +0200
Subject: [PATCH 173/258] fix: assume remote users are following each other
 (#8734)

Misskey does not know if two remote users are following each other.
Because ActivityPub actions would otherwise fail on followers only
notes, we have to assume that two remote users are following each other
when an interaction about a remote note occurs.
---
 .../backend/src/models/repositories/note.ts   | 24 ++++++++++++-------
 1 file changed, 15 insertions(+), 9 deletions(-)

diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index cf5fcb1787..638d78f626 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -168,16 +168,22 @@ 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.findOneBy({
+						followeeId: note.userId,
+						followerId: meId,
+					}),
+					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 != null || (note.userHost != null && user.host != null);
 			}
 		}
 

From 3c3140a1006cd4d916d8a086a8fca0faf90dc3cd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 25 May 2022 23:19:39 +0900
Subject: [PATCH 174/258] refactor: use ===

---
 packages/backend/test/utils.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 4e98f13328..5eb4ed3b01 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -100,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);
 				}
 			});

From b3ad04fcb02830f553c7d146b664475c114519cb Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 25 May 2022 23:28:56 +0900
Subject: [PATCH 175/258] update deps

---
 packages/backend/package.json |  26 +-
 packages/backend/yarn.lock    | 538 ++++++++++++-------------
 packages/client/package.json  |  42 +-
 packages/client/yarn.lock     | 734 +++++++++++++++-------------------
 4 files changed, 608 insertions(+), 732 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 88f824ea7c..86265df2ca 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -30,7 +30,7 @@
 		"aws-sdk": "2.1135.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.11.0",
+		"broadcast-channel": "4.12.0",
 		"bull": "4.8.3",
 		"cacheable-lookup": "6.0.4",
 		"cbor": "8.1.0",
@@ -48,14 +48,14 @@
 		"got": "12.0.4",
 		"hpagent": "0.1.2",
 		"http-signature": "1.3.6",
-		"ip-cidr": "3.0.7",
+		"ip-cidr": "3.0.8",
 		"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": "10.5.20",
+		"jsrsasign": "10.5.22",
 		"koa": "2.13.4",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
@@ -65,10 +65,10 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"mfm-js": "0.22.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",
@@ -107,9 +107,9 @@
 		"tinycolor2": "1.4.2",
 		"tmp": "0.2.1",
 		"ts-loader": "9.3.0",
-		"ts-node": "10.7.0",
-		"tsc-alias": "1.4.1",
-		"tsconfig-paths": "3.14.1",
+		"ts-node": "10.8.0",
+		"tsc-alias": "1.6.7",
+		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
 		"typeorm": "0.3.6",
 		"ulid": "2.3.0",
@@ -145,7 +145,7 @@
 		"@types/koa__multer": "2.0.4",
 		"@types/koa__router": "8.0.11",
 		"@types/mocha": "9.1.1",
-		"@types/node": "17.0.33",
+		"@types/node": "17.0.35",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.4",
 		"@types/oauth": "0.9.1",
@@ -167,10 +167,10 @@
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
-		"@typescript-eslint/eslint-plugin": "5.23.0",
-		"@typescript-eslint/parser": "5.23.0",
-		"typescript": "4.6.4",
-		"eslint": "8.15.0",
+		"@typescript-eslint/eslint-plugin": "5.26.0",
+		"@typescript-eslint/parser": "5.26.0",
+		"typescript": "4.7.2",
+		"eslint": "8.16.0",
 		"eslint-plugin-import": "2.26.0",
 
 		"cross-env": "7.0.3",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 82a6e4f501..8db18eb854 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -63,17 +63,12 @@
   dependencies:
     "@bull-board/api" "3.10.4"
 
-"@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"
@@ -110,15 +105,15 @@
     pump "^3.0.0"
     secure-json-parse "^2.1.0"
 
-"@eslint/eslintrc@^1.2.3":
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.3.tgz#fcaa2bcef39e13d6e9e7f6271f4cc7cae1174886"
-  integrity sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==
+"@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.2"
-    globals "^13.9.0"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.2.1"
     js-yaml "^4.1.0"
@@ -144,6 +139,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"
@@ -671,10 +684,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
   integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA==
 
-"@types/node@17.0.33":
-  version "17.0.33"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.33.tgz#3c1879b276dc63e73030bb91165e62a4509cd506"
-  integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==
+"@types/node@17.0.35":
+  version "17.0.35"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a"
+  integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
 
 "@types/node@^14.11.8":
   version "14.17.9"
@@ -850,85 +863,85 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz#bc4cbcf91fbbcc2e47e534774781b82ae25cc3d8"
-  integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==
+"@typescript-eslint/eslint-plugin@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2"
+  integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/type-utils" "5.23.0"
-    "@typescript-eslint/utils" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/type-utils" "5.26.0"
+    "@typescript-eslint/utils" "5.26.0"
+    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.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.23.0.tgz#443778e1afc9a8ff180f91b5e260ac3bec5e2de1"
-  integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw==
+"@typescript-eslint/parser@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2"
+  integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/typescript-estree" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/typescript-estree" "5.26.0"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.23.0.tgz#4305e61c2c8e3cfa3787d30f54e79430cc17ce1b"
-  integrity sha512-EhjaFELQHCRb5wTwlGsNMvzK9b8Oco4aYNleeDlNuL6qXWDF47ch4EhVNPh8Rdhf9tmqbN4sWDk/8g+Z/J8JVw==
+"@typescript-eslint/scope-manager@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339"
+  integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/visitor-keys" "5.23.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/visitor-keys" "5.26.0"
 
-"@typescript-eslint/type-utils@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
-  integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw==
+"@typescript-eslint/type-utils@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013"
+  integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A==
   dependencies:
-    "@typescript-eslint/utils" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/utils" "5.26.0"
+    debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
-  integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
+"@typescript-eslint/types@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3"
+  integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==
 
-"@typescript-eslint/typescript-estree@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
-  integrity sha512-xE9e0lrHhI647SlGMl+m+3E3CKPF1wzvvOEWnuE3CCjjT7UiRnDGJxmAcVKJIlFgK6DY9RB98eLr1OPigPEOGg==
+"@typescript-eslint/typescript-estree@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3"
+  integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/visitor-keys" "5.23.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/visitor-keys" "5.26.0"
+    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.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
-  integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
+"@typescript-eslint/utils@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4"
+  integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/typescript-estree" "5.23.0"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/typescript-estree" "5.26.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
-  integrity sha512-Vd4mFNchU62sJB8pX19ZSPog05B0Y0CE2UxAZPT5k4iqhRYjPnqyY3woMxCd0++t9OTqkgjST+1ydLBi7e2Fvg==
+"@typescript-eslint/visitor-keys@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57"
+  integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    eslint-visitor-keys "^3.0.0"
+    "@typescript-eslint/types" "5.26.0"
+    eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
   version "1.1.2"
@@ -1399,22 +1412,22 @@ brace-expansion@^2.0.1:
   dependencies:
     balanced-match "^1.0.0"
 
-braces@^3.0.1, braces@~3.0.2:
+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.11.0:
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e"
-  integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q==
+broadcast-channel@4.12.0:
+  version "4.12.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.12.0.tgz#891876c5262376ab714b33a0d9e9d87a894b5bcb"
+  integrity sha512-hfb0L2P2CEsdM5nSqlRiZ2gQFHPdJNs1VU4rTLnFPYtq5vQAnyOdjIx+04KCWfFfRhfP3OEbxxQmnouSi8WCbQ==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
     microtime "3.0.0"
-    oblivious-set "1.0.0"
+    oblivious-set "1.1.1"
     p-queue "6.6.2"
     rimraf "3.0.2"
     unload "2.3.1"
@@ -1716,7 +1729,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==
@@ -1880,10 +1893,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"
@@ -2120,6 +2133,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"
@@ -2141,13 +2161,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"
@@ -2257,11 +2270,6 @@ 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"
@@ -2696,22 +2704,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.15.0:
-  version "8.15.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
-  integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
+eslint@8.16.0:
+  version "8.16.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae"
+  integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==
   dependencies:
-    "@eslint/eslintrc" "^1.2.3"
+    "@eslint/eslintrc" "^1.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -2729,7 +2732,7 @@ eslint@8.15.0:
     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"
@@ -2840,13 +2843,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"
@@ -2893,6 +2889,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"
@@ -2965,14 +2972,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"
@@ -2996,16 +2995,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"
@@ -3206,7 +3195,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==
@@ -3244,37 +3233,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"
 
@@ -3290,6 +3252,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"
@@ -3341,11 +3315,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"
@@ -3400,13 +3369,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"
@@ -3596,11 +3558,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"
@@ -3696,10 +3653,10 @@ ip-address@^7.1.0:
     jsbn "1.1.0"
     sprintf-js "1.1.2"
 
-ip-cidr@3.0.7:
-  version "3.0.7"
-  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.7.tgz#22708dd4f2d3f6397c0fb7d647b44e3c565937e9"
-  integrity sha512-0cBBICDnmmpAdULMbMVdi4f0mSG+VWY/QBPL/OIIjuom14x7Y63VhpS/uSAOycasXOeGXah5y0eu//PDU51aNw==
+ip-cidr@3.0.8:
+  version "3.0.8"
+  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.8.tgz#51876484109e4aa200b86b3dc32f24a1d8986429"
+  integrity sha512-DLrHwoFNuLVNulwoQuHLdkIED1Hyo9iB0MB8XzZdfie23b5bonJXVB5aCyVbbvXYOmQrw3nZDcjnSO7MMYgjJg==
   dependencies:
     ip-address "^7.1.0"
     jsbn "^1.1.0"
@@ -3951,11 +3908,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"
@@ -4106,7 +4058,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==
@@ -4161,10 +4113,10 @@ jsprim@^2.0.2:
     json-schema "0.4.0"
     verror "1.10.0"
 
-jsrsasign@10.5.20:
-  version "10.5.20"
-  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.20.tgz#515a854a47c309cb350f32860dd37bfad1b81800"
-  integrity sha512-YHL6y8o6cnRoxwUY0eGpfvwj0pm9o0NToD4KXVp2UJC19oXSR2RgnSdSMplIRRKFovLAJGrAHqdb5MMznnhQDQ==
+jsrsasign@10.5.22:
+  version "10.5.22"
+  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.22.tgz#a702fb591e634767ca3296ce7a212f92974df17c"
+  integrity sha512-exqUDmWKOCUK4fT79z/Fi2qGV4c+WCPjHrtJ/lVUUrrbBwJ5T5HppCcalGf3tuOlmyNdyMZ074r1bqPOUNl4Uw==
 
 jstransformer@1.0.0:
   version "1.0.0"
@@ -4604,25 +4556,20 @@ 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.22.0:
-  version "0.22.0"
-  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.0.tgz#f619e6358e865dde948b72c1688615b616f5571f"
-  integrity sha512-81Asd97Sjs66mRiCZ8qpFQvkHt6kDaxdRCUy3OAW8vJJuBADiVs10iHc9SFpqa8g+DJmFG0NduBRYT0/2LtxQQ==
+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 "14.0.x"
 
@@ -4634,6 +4581,14 @@ micromatch@^4.0.0, micromatch@^4.0.2:
     braces "^3.0.1"
     picomatch "^2.0.5"
 
+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"
@@ -4686,12 +4641,12 @@ 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==
+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.0.4, minimatch@^3.1.2:
   version "3.1.2"
@@ -4700,13 +4655,6 @@ minimatch@^3.0.4, minimatch@^3.1.2:
   dependencies:
     brace-expansion "^1.1.7"
 
-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 "^2.0.1"
-
 minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
   version "1.2.6"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
@@ -4797,32 +4745,30 @@ 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"
@@ -4881,10 +4827,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"
@@ -4900,7 +4846,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==
 
-nanoid@3.3.1, nanoid@^3.1.30:
+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.1.30:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
   integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
@@ -5160,10 +5111,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==
 
 on-finished@^2.3.0:
   version "2.3.0"
@@ -5349,11 +5300,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"
@@ -5492,11 +5438,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"
@@ -5805,6 +5763,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"
@@ -6030,14 +5993,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"
@@ -6190,7 +6145,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.7:
+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==
@@ -6790,12 +6745,12 @@ ts-loader@9.3.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.0:
+  version "10.8.0"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.0.tgz#3ceb5ac3e67ae8025c1950626aafbdecb55d82ce"
+  integrity sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==
   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"
@@ -6806,22 +6761,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.7:
+  version "1.6.7"
+  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.7.tgz#354840d6444db79dd13fcc4f9ec37574ff9d5120"
+  integrity sha512-GRbZx/zTee01JtrHB7hkddgxn+aQqYDmRaFv/MTYIqBbk/L8Zf0nA/T60wXOr/Q7002YXppUFXsqsu5ViWB4vQ==
   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@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==
@@ -6947,10 +6911,10 @@ typeorm@0.3.6:
     xml2js "^0.4.23"
     yargs "^17.3.1"
 
-typescript@4.6.4:
-  version "4.6.4"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
-  integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
+typescript@4.7.2:
+  version "4.7.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
+  integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
 
 ulid@2.3.0:
   version "2.3.0"
@@ -7062,10 +7026,10 @@ uuid@8.3.2, uuid@^8.3.0, uuid@^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"
@@ -7188,20 +7152,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"
@@ -7231,10 +7195,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"
diff --git a/packages/client/package.json b/packages/client/package.json
index 0ea37e388c..a4c6638565 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -10,7 +10,7 @@
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
-		"@discordapp/twemoji": "13.1.1",
+		"@discordapp/twemoji": "14.0.2",
 		"@fortawesome/fontawesome-free": "6.1.1",
 		"@syuilo/aiscript": "0.11.1",
 		"abort-controller": "3.0.0",
@@ -18,11 +18,11 @@
 		"autosize": "5.0.1",
 		"autwh": "0.1.0",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.11.0",
+		"broadcast-channel": "4.12.0",
 		"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
-		"chart.js": "3.7.1",
+		"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",
@@ -33,14 +33,13 @@
 		"idb-keyval": "6.1.0",
 		"insert-text-at-cursor": "0.3.0",
 		"json5": "2.2.1",
-		"katex": "0.15.3",
+		"katex": "0.15.6",
 		"matter-js": "0.18.0",
-		"mfm-js": "0.22.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.7",
 		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
@@ -53,32 +52,32 @@
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
-		"sass": "1.51.0",
+		"sass": "1.52.1",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
 		"three": "0.140.2",
-		"throttle-debounce": "4.0.1",
+		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.4.2",
-		"tsc-alias": "1.5.0",
-		"tsconfig-paths": "3.14.1",
+		"tsc-alias": "1.6.7",
+		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vue": "3.2.33",
+		"vue": "3.2.36",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vue-router": "4.0.15",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
 		"@vitejs/plugin-vue": "2.3.3",
-		"@vue/compiler-sfc": "3.2.33",
+		"@vue/compiler-sfc": "3.2.36",
 		"@rollup/plugin-alias": "3.1.9",
 		"@rollup/plugin-json": "4.1.0",
-		"rollup": "2.73.0",
-		"typescript": "4.6.4",
+		"rollup": "2.74.1",
+		"typescript": "4.7.2",
 		"vite": "2.9.9",
 		"ws": "8.6.0"
 	},
@@ -92,7 +91,6 @@
 		"@types/matter-js": "0.17.7",
 		"@types/mocha": "9.1.1",
 		"@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",
@@ -102,12 +100,12 @@
 		"@types/uuid": "8.3.4",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
-		"@typescript-eslint/eslint-plugin": "5.23.0",
-		"@typescript-eslint/parser": "5.23.0",
-		"eslint": "8.15.0",
-		"eslint-plugin-vue": "8.7.1",
+		"@typescript-eslint/eslint-plugin": "5.26.0",
+		"@typescript-eslint/parser": "5.26.0",
+		"eslint": "8.16.0",
+		"eslint-plugin-vue": "9.0.1",
 		"cross-env": "7.0.3",
-		"cypress": "9.6.1",
+		"cypress": "9.7.0",
 		"eslint-plugin-import": "2.26.0",
 		"start-server-and-test": "1.14.0"
 	}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index efc6d9ddb3..63ab2edc96 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -72,25 +72,25 @@
     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"
 
-"@eslint/eslintrc@^1.2.3":
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.3.tgz#fcaa2bcef39e13d6e9e7f6271f4cc7cae1174886"
-  integrity sha512-uGo44hIwoLGNyduRpjdEpovcbMdd+Nv7amtmJxnKmI8xj6yd5LncmSwDa5NgX/41lIFJtkjD6YdVfgEzPfJ5UA==
+"@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.2"
-    globals "^13.9.0"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.2.1"
     js-yaml "^4.1.0"
@@ -319,11 +319,6 @@
   dependencies:
     "@types/node" "*"
 
-"@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"
@@ -421,85 +416,85 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz#bc4cbcf91fbbcc2e47e534774781b82ae25cc3d8"
-  integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==
+"@typescript-eslint/eslint-plugin@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2"
+  integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/type-utils" "5.23.0"
-    "@typescript-eslint/utils" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/type-utils" "5.26.0"
+    "@typescript-eslint/utils" "5.26.0"
+    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.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.23.0.tgz#443778e1afc9a8ff180f91b5e260ac3bec5e2de1"
-  integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw==
+"@typescript-eslint/parser@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2"
+  integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/typescript-estree" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/typescript-estree" "5.26.0"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.23.0.tgz#4305e61c2c8e3cfa3787d30f54e79430cc17ce1b"
-  integrity sha512-EhjaFELQHCRb5wTwlGsNMvzK9b8Oco4aYNleeDlNuL6qXWDF47ch4EhVNPh8Rdhf9tmqbN4sWDk/8g+Z/J8JVw==
+"@typescript-eslint/scope-manager@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339"
+  integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/visitor-keys" "5.23.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/visitor-keys" "5.26.0"
 
-"@typescript-eslint/type-utils@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
-  integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw==
+"@typescript-eslint/type-utils@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013"
+  integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A==
   dependencies:
-    "@typescript-eslint/utils" "5.23.0"
-    debug "^4.3.2"
+    "@typescript-eslint/utils" "5.26.0"
+    debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
-  integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
+"@typescript-eslint/types@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3"
+  integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==
 
-"@typescript-eslint/typescript-estree@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
-  integrity sha512-xE9e0lrHhI647SlGMl+m+3E3CKPF1wzvvOEWnuE3CCjjT7UiRnDGJxmAcVKJIlFgK6DY9RB98eLr1OPigPEOGg==
+"@typescript-eslint/typescript-estree@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3"
+  integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/visitor-keys" "5.23.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/visitor-keys" "5.26.0"
+    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.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
-  integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
+"@typescript-eslint/utils@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4"
+  integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.23.0"
-    "@typescript-eslint/types" "5.23.0"
-    "@typescript-eslint/typescript-estree" "5.23.0"
+    "@typescript-eslint/scope-manager" "5.26.0"
+    "@typescript-eslint/types" "5.26.0"
+    "@typescript-eslint/typescript-estree" "5.26.0"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.23.0":
-  version "5.23.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
-  integrity sha512-Vd4mFNchU62sJB8pX19ZSPog05B0Y0CE2UxAZPT5k4iqhRYjPnqyY3woMxCd0++t9OTqkgjST+1ydLBi7e2Fvg==
+"@typescript-eslint/visitor-keys@5.26.0":
+  version "5.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57"
+  integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==
   dependencies:
-    "@typescript-eslint/types" "5.23.0"
-    eslint-visitor-keys "^3.0.0"
+    "@typescript-eslint/types" "5.26.0"
+    eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
   version "1.1.2"
@@ -511,100 +506,100 @@
   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.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.33.tgz#e915d59cce85898f5c5cfebe4c09e539278c3d59"
-  integrity sha512-AAmr52ji3Zhk7IKIuigX2osWWsb2nQE5xsdFYjdnmtQ4gymmqXbjLvkSE174+fF3A3kstYrTgGkqgOEbsdLDpw==
+"@vue/compiler-core@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.36.tgz#2fa44595308c95610602df54dcb69063ba2c8383"
+  integrity sha512-bbyZM5hvBicv0PW3KUfVi+x3ylHnfKG7DOn5wM+f2OztTzTjLEyBb/5yrarIYpmnGitVGbjZqDbODyW4iK8hqw==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.33"
+    "@vue/shared" "3.2.36"
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
-"@vue/compiler-dom@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.33.tgz#6db84296f949f18e5d3e7fd5e80f943dbed7d5ec"
-  integrity sha512-GhiG1C8X98Xz9QUX/RlA6/kgPBWJkjq0Rq6//5XTAGSYrTMBgcLpP9+CnlUg1TFxnnCVughAG+KZl28XJqw8uQ==
+"@vue/compiler-dom@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.36.tgz#16d911ff163ed5fc8087a01645bf14bb7f325401"
+  integrity sha512-tcOTAOiW4s24QLnq+ON6J+GRONXJ+A/mqKCORi0LSlIh8XQlNnlm24y8xIL8la+ZDgkdbjarQ9ZqYSvEja6gVA==
   dependencies:
-    "@vue/compiler-core" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-core" "3.2.36"
+    "@vue/shared" "3.2.36"
 
-"@vue/compiler-sfc@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.33.tgz#7ce01dc947a8b76c099811dc6ca58494d4dc773d"
-  integrity sha512-H8D0WqagCr295pQjUYyO8P3IejM3vEzeCO1apzByAEaAR/WimhMYczHfZVvlCE/9yBaEu/eu9RdiWr0kF8b71Q==
+"@vue/compiler-sfc@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.36.tgz#e5065e7c0e5170ffa750e3c3dd93a29db109d0f2"
+  integrity sha512-AvGb4bTj4W8uQ4BqaSxo7UwTEqX5utdRSMyHy58OragWlt8nEACQ9mIeQh3K4di4/SX+41+pJrLIY01lHAOFOA==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.33"
-    "@vue/compiler-dom" "3.2.33"
-    "@vue/compiler-ssr" "3.2.33"
-    "@vue/reactivity-transform" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-core" "3.2.36"
+    "@vue/compiler-dom" "3.2.36"
+    "@vue/compiler-ssr" "3.2.36"
+    "@vue/reactivity-transform" "3.2.36"
+    "@vue/shared" "3.2.36"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
     postcss "^8.1.10"
     source-map "^0.6.1"
 
-"@vue/compiler-ssr@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.33.tgz#3e820267e4eea48fde9519f006dedca3f5e42e71"
-  integrity sha512-XQh1Xdk3VquDpXsnoCd7JnMoWec9CfAzQDQsaMcSU79OrrO2PNR0ErlIjm/mGq3GmBfkQjzZACV+7GhfRB8xMQ==
+"@vue/compiler-ssr@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.36.tgz#314f3a9424db58142c3608f48cbda7aa05fc66cb"
+  integrity sha512-+KugInUFRvOxEdLkZwE+W43BqHyhBh0jpYXhmqw1xGq2dmE6J9eZ8UUSOKNhdHtQ/iNLWWeK/wPZkVLUf3YGaw==
   dependencies:
-    "@vue/compiler-dom" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-dom" "3.2.36"
+    "@vue/shared" "3.2.36"
 
 "@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.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.33.tgz#286063f44ca56150ae9b52f8346a26e5913fa699"
-  integrity sha512-4UL5KOIvSQb254aqenW4q34qMXbfZcmEsV/yVidLUgvwYQQ/D21bGX3DlgPUGI3c4C+iOnNmDCkIxkILoX/Pyw==
+"@vue/reactivity-transform@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.36.tgz#8426a941b0b09d1b94fc162d4642758183b5d133"
+  integrity sha512-Jk5o2BhpODC9XTA7o4EL8hSJ4JyrFWErLtClG3NH8wDS7ri9jBDWxI7/549T7JY9uilKsaNM+4pJASLj5dtRwA==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-core" "3.2.36"
+    "@vue/shared" "3.2.36"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
-"@vue/reactivity@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.33.tgz#c84eedb5225138dbfc2472864c151d3efbb4b673"
-  integrity sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ==
+"@vue/reactivity@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.36.tgz#026b14e716febffe80cd284fd8a2b33378968646"
+  integrity sha512-c2qvopo0crh9A4GXi2/2kfGYMxsJW4tVILrqRPydVGZHhq0fnzy6qmclWOhBFckEhmyxmpHpdJtIRYGeKcuhnA==
   dependencies:
-    "@vue/shared" "3.2.33"
+    "@vue/shared" "3.2.36"
 
-"@vue/runtime-core@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2"
-  integrity sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw==
+"@vue/runtime-core@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.36.tgz#be5115e665679c26bf3807d2326675dc1d847134"
+  integrity sha512-PTWBD+Lub+1U3/KhbCExrfxyS14hstLX+cBboxVHaz+kXoiDLNDEYAovPtxeTutbqtClIXtft+wcGdC+FUQ9qQ==
   dependencies:
-    "@vue/reactivity" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/reactivity" "3.2.36"
+    "@vue/shared" "3.2.36"
 
-"@vue/runtime-dom@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848"
-  integrity sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw==
+"@vue/runtime-dom@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.36.tgz#cd5d403ea23c18ee7c17767103a1b2f8263c54bb"
+  integrity sha512-gYPYblm7QXHVuBohqNRRT7Wez0f2Mx2D40rb4fleehrJU9CnkjG0phhcGEZFfGwCmHZRqBCRgbFWE98bPULqkg==
   dependencies:
-    "@vue/runtime-core" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/runtime-core" "3.2.36"
+    "@vue/shared" "3.2.36"
     csstype "^2.6.8"
 
-"@vue/server-renderer@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.33.tgz#4b45d6d2ae10ea4e3d2cf8e676804cf60f331979"
-  integrity sha512-4jpJHRD4ORv8PlbYi+/MfP8ec1okz6rybe36MdpkDrGIdEItHEUyaHSKvz+ptNEyQpALmmVfRteHkU9F8vxOew==
+"@vue/server-renderer@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.36.tgz#1e7c1cf63bd17df7828d04e8c780ee6ca7a9ed7c"
+  integrity sha512-uZE0+jfye6yYXWvAQYeHZv+f50sRryvy16uiqzk3jn8hEY8zTjI+rzlmZSGoE915k+W/Ol9XSw6vxOUD8dGkUg==
   dependencies:
-    "@vue/compiler-ssr" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-ssr" "3.2.36"
+    "@vue/shared" "3.2.36"
 
-"@vue/shared@3.2.33":
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e"
-  integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg==
+"@vue/shared@3.2.36":
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.36.tgz#35e11200542cf29068ba787dad57da9bdb82f644"
+  integrity sha512-JtB41wXl7Au3+Nl3gD16Cfpj7k/6aCroZ6BbOiCMFCMvrOpkg/qQUXTso2XowaNqBbnkuGHurLAqkLBxNGc1hQ==
 
 abort-controller@3.0.0:
   version "3.0.0"
@@ -613,11 +608,6 @@ abort-controller@3.0.0:
   dependencies:
     event-target-shim "^5.0.0"
 
-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"
@@ -628,11 +618,6 @@ acorn@^7.1.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-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.1:
   version "8.7.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
@@ -857,22 +842,29 @@ 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.11.0:
-  version "4.11.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e"
-  integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q==
+broadcast-channel@4.12.0:
+  version "4.12.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.12.0.tgz#891876c5262376ab714b33a0d9e9d87a894b5bcb"
+  integrity sha512-hfb0L2P2CEsdM5nSqlRiZ2gQFHPdJNs1VU4rTLnFPYtq5vQAnyOdjIx+04KCWfFfRhfP3OEbxxQmnouSi8WCbQ==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
     microtime "3.0.0"
-    oblivious-set "1.0.0"
+    oblivious-set "1.1.1"
     p-queue "6.6.2"
     rimraf "3.0.2"
     unload "2.3.1"
@@ -967,20 +959,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"
@@ -994,7 +986,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==
@@ -1095,11 +1087,16 @@ commander@^5.1.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
   integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
 
-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"
@@ -1161,10 +1158,10 @@ csstype@^2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
 
-cypress@9.6.1:
-  version "9.6.1"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.6.1.tgz#a7d6b5a53325b3dc4960181f5800a5ade0f085eb"
-  integrity sha512-ECzmV7pJSkk+NuAhEw6C3D+RIRATkSb2VAHXDY6qGZbca/F9mv5pPsj2LO6Ty6oIFVBTrwCyL9agl28MtJMe2g==
+cypress@9.7.0:
+  version "9.7.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744"
+  integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==
   dependencies:
     "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
@@ -1241,10 +1238,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"
 
@@ -1296,11 +1293,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"
@@ -1621,17 +1613,18 @@ eslint-plugin-import@2.26.0:
     resolve "^1.22.0"
     tsconfig-paths "^3.14.1"
 
-eslint-plugin-vue@8.7.1:
-  version "8.7.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz#f13c53547a0c9d64588a675cc5ecc6ccaf63703f"
-  integrity sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg==
+eslint-plugin-vue@9.0.1:
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.0.1.tgz#66ba4a6e4085a26a724adcde06eaf72b178285c9"
+  integrity sha512-/w/9/vzz+4bSYtp5UqXgJ0CfycXTMtpp6lkz7/fMp0CcJxPWyRP6Pr88ihhrsNEcVt2ZweMupWRNYa+5Md41LQ==
   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:
   version "5.1.1"
@@ -1641,14 +1634,6 @@ 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"
@@ -1669,22 +1654,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.15.0:
-  version "8.15.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.15.0.tgz#fea1d55a7062da48d82600d2e0974c55612a11e9"
-  integrity sha512-GG5USZ1jhCu8HJkzGgeK8/+RGnHaNYZGrGDzUtigK3BsGESW/rs2az23XqE0WVwDxy1VRvvjSSGu5nB0Bu+6SA==
+eslint@8.16.0:
+  version "8.16.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae"
+  integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==
   dependencies:
-    "@eslint/eslintrc" "^1.2.3"
+    "@eslint/eslintrc" "^1.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -1702,7 +1682,7 @@ eslint@8.15.0:
     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"
@@ -1720,16 +1700,7 @@ eslint@8.15.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==
-  dependencies:
-    acorn "^8.5.0"
-    acorn-jsx "^5.3.1"
-    eslint-visitor-keys "^3.0.0"
-
-espree@^9.3.2:
+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==
@@ -1847,13 +1818,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"
@@ -1909,6 +1873,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"
@@ -1961,14 +1936,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"
@@ -1992,16 +1959,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"
@@ -2136,7 +2093,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==
@@ -2181,37 +2138,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"
 
@@ -2227,6 +2157,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"
+
 graceful-fs@^4.1.6:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
@@ -2237,11 +2179,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==
 
-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"
@@ -2286,13 +2223,6 @@ he@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"
@@ -2329,11 +2259,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"
@@ -2380,11 +2305,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"
@@ -2602,11 +2522,6 @@ 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"
@@ -2665,7 +2580,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@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==
@@ -2720,10 +2635,10 @@ 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"
 
@@ -2837,20 +2752,15 @@ 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==
-
-mfm-js@0.22.0:
-  version "0.22.0"
-  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.0.tgz#f619e6358e865dde948b72c1688615b616f5571f"
-  integrity sha512-81Asd97Sjs66mRiCZ8qpFQvkHt6kDaxdRCUy3OAW8vJJuBADiVs10iHc9SFpqa8g+DJmFG0NduBRYT0/2LtxQQ==
+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 "14.0.x"
 
@@ -2862,6 +2772,14 @@ micromatch@^4.0.2:
     braces "^3.0.1"
     picomatch "^2.0.5"
 
+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"
@@ -2887,12 +2805,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"
@@ -2922,32 +2840,30 @@ misskey-js@0.0.14:
     eventemitter3 "^4.0.7"
     reconnecting-websocket "^4.4.0"
 
-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"
@@ -2967,17 +2883,12 @@ 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==
 
-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, 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==
@@ -3075,10 +2986,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"
@@ -3197,16 +3108,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=
-
-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"
@@ -3269,7 +3170,7 @@ 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.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==
@@ -3279,6 +3180,13 @@ pify@^2.2.0:
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
 
+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"
+
 pngjs@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
@@ -3491,6 +3399,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"
@@ -3554,14 +3467,6 @@ 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-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"
@@ -3612,10 +3517,10 @@ rndstr@1.0.0:
     rangestr "0.0.1"
     seedrandom "2.4.2"
 
-rollup@2.73.0:
-  version "2.73.0"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.73.0.tgz#128fef4b333fd92d02d6929afbb6ee38d7feb32d"
-  integrity sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==
+rollup@2.74.1:
+  version "2.74.1"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.74.1.tgz#4fba0ff1c312cc4ee82691b154eee69a0d01929f"
+  integrity sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==
   optionalDependencies:
     fsevents "~2.3.2"
 
@@ -3670,10 +3575,10 @@ 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@1.51.0:
-  version "1.51.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.51.0.tgz#25ea36cf819581fe1fe8329e8c3a4eaaf70d2845"
-  integrity sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==
+sass@1.52.1:
+  version "1.52.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.1.tgz#554693da808543031f9423911d62c60a1acf7889"
+  integrity sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
@@ -3708,6 +3613,13 @@ semver@^7.3.5:
   dependencies:
     lru-cache "^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"
@@ -3936,10 +3848,10 @@ three@0.140.2:
   resolved "https://registry.yarnpkg.com/three/-/three-0.140.2.tgz#ca0b7390d1ce4f7f2850e80afd00ef48fc244491"
   integrity sha512-DdT/AHm/TbZXEhQKQpGt5/iSgBrmXpjU26FNtj1KhllVPTKj1eG4X/ShyD5W2fngE+I1s1wa4ttC4C3oCJt7Ag==
 
-throttle-debounce@4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.1.tgz#f86656fe9c8a6b8218952ef36c3bf225089b1baf"
-  integrity sha512-s3PedbXdZtr8v3J5Sxd5T/GmWG80BcK5GVpwDdvgEaUXsaMqQe4zxgmC4TA7B8luSDCPxo3CeSBS3F9rF1CZwg==
+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"
@@ -3988,19 +3900,28 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
-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==
+tsc-alias@1.6.7:
+  version "1.6.7"
+  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.7.tgz#354840d6444db79dd13fcc4f9ec37574ff9d5120"
+  integrity sha512-GRbZx/zTee01JtrHB7hkddgxn+aQqYDmRaFv/MTYIqBbk/L8Zf0nA/T60wXOr/Q7002YXppUFXsqsu5ViWB4vQ==
   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==
@@ -4039,11 +3960,6 @@ 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:
-  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.x:
   version "14.0.0"
   resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62"
@@ -4083,10 +3999,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@4.6.4:
-  version "4.6.4"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
-  integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
+typescript@4.7.2:
+  version "4.7.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
+  integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
 
 unbox-primitive@^1.0.1:
   version "1.0.1"
@@ -4191,18 +4107,18 @@ void-elements@^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"
+    semver "^7.3.6"
 
 vue-prism-editor@2.0.0-alpha.2:
   version "2.0.0-alpha.2"
@@ -4216,16 +4132,16 @@ vue-router@4.0.15:
   dependencies:
     "@vue/devtools-api" "^6.0.0"
 
-vue@3.2.33:
-  version "3.2.33"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.33.tgz#7867eb16a3293a28c4d190a837bc447878bd64c2"
-  integrity sha512-si1ExAlDUrLSIg/V7D/GgA4twJwfsfgG+t9w10z38HhL/HA07132pUQ2KuwAo8qbCyMJ9e6OqrmWrOCr+jW7ZQ==
+vue@3.2.36:
+  version "3.2.36"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.36.tgz#8daa996e2ced521708de97d066c7c998e8bc3378"
+  integrity sha512-5yTXmrE6gW8IQgttzHW5bfBiFA6mx35ZXHjGLDmKYzW6MMmYvCwuKybANRepwkMYeXw2v1buGg3/lPICY5YlZw==
   dependencies:
-    "@vue/compiler-dom" "3.2.33"
-    "@vue/compiler-sfc" "3.2.33"
-    "@vue/runtime-dom" "3.2.33"
-    "@vue/server-renderer" "3.2.33"
-    "@vue/shared" "3.2.33"
+    "@vue/compiler-dom" "3.2.36"
+    "@vue/compiler-sfc" "3.2.36"
+    "@vue/runtime-dom" "3.2.36"
+    "@vue/server-renderer" "3.2.36"
+    "@vue/shared" "3.2.36"
 
 vuedraggable@4.0.1:
   version "4.0.1"
@@ -4273,20 +4189,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"
-
 with@^7.0.0:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"
@@ -4302,10 +4211,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"
@@ -4342,6 +4251,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"

From 9c80403072d2f49103f77af54dffbd21325806db Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 26 May 2022 02:12:17 +0200
Subject: [PATCH 176/258] use http-signature module that supports hs2019
 (#8635)

---
 packages/backend/package.json                 |  2 +-
 .../backend/src/@types/http-signature.d.ts    |  2 +-
 packages/backend/src/queue/index.ts           |  2 +-
 .../backend/src/queue/processors/inbox.ts     |  2 +-
 packages/backend/src/queue/types.ts           |  2 +-
 packages/backend/src/server/activitypub.ts    |  2 +-
 packages/backend/yarn.lock                    | 26 +++++++++----------
 7 files changed, 19 insertions(+), 19 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 86265df2ca..4e0d60b74e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -20,6 +20,7 @@
 		"@koa/cors": "3.1.0",
 		"@koa/multer": "3.0.0",
 		"@koa/router": "9.0.1",
+		"@peertube/http-signature": "1.6.0",
 		"@sinonjs/fake-timers": "9.1.2",
 		"@syuilo/aiscript": "0.11.1",
 		"abort-controller": "3.0.0",
@@ -47,7 +48,6 @@
 		"fluent-ffmpeg": "2.1.2",
 		"got": "12.0.4",
 		"hpagent": "0.1.2",
-		"http-signature": "1.3.6",
 		"ip-cidr": "3.0.8",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts
index 0426cb8bcd..d1f9cd9552 100644
--- a/packages/backend/src/@types/http-signature.d.ts
+++ b/packages/backend/src/@types/http-signature.d.ts
@@ -1,4 +1,4 @@
-declare module 'http-signature' {
+declare module '@peertube/http-signature' {
 	import { IncomingMessage, ClientRequest } from 'node:http';
 
 	interface ISignature {
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index 2d40290e4c..67d5f5d248 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';
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/server/activitypub.ts b/packages/backend/src/server/activitypub.ts
index 133dd36066..a48c2d4122 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';
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 8db18eb854..d131f70e38 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -247,6 +247,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"
@@ -3465,15 +3474,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"
@@ -4103,10 +4103,10 @@ jsonld@5.2.0:
     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"

From 3dae18b93cc2a219087c4e1a8acc763ff064b71d Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 26 May 2022 15:53:09 +0200
Subject: [PATCH 177/258] fix lints (#8737)

* fix: emits use ev instead of e

* fix: errors use err instead of e

* fix: replace use of data where possible

* fix: events use evt instead of e

* fix: use strict equals

* fix: use emoji instead of e

* fix: vue lints
---
 packages/client/src/account.ts                | 10 +--
 .../src/components/abuse-report-window.vue    |  2 +-
 .../client/src/components/abuse-report.vue    |  2 +-
 .../src/components/channel-follow-button.vue  |  4 +-
 packages/client/src/components/cw-button.vue  |  2 +-
 packages/client/src/components/dialog.vue     | 16 ++---
 .../client/src/components/follow-button.vue   |  6 +-
 .../client/src/components/forgot-password.vue |  4 +-
 .../client/src/components/form-dialog.vue     |  2 +-
 .../client/src/components/form/switch.vue     |  2 +-
 .../client/src/components/global/avatar.vue   |  2 +-
 .../client/src/components/global/emoji.vue    |  2 +-
 .../client/src/components/global/time.vue     |  2 +-
 .../client/src/components/image-viewer.vue    |  2 +-
 .../client/src/components/media-caption.vue   | 16 ++---
 .../client/src/components/note-detailed.vue   |  4 +-
 packages/client/src/components/note.vue       |  2 +-
 .../client/src/components/page/page.post.vue  | 10 +--
 packages/client/src/components/page/page.vue  | 10 +--
 .../client/src/components/poll-editor.vue     |  2 +-
 .../src/components/post-form-attaches.vue     |  2 +-
 packages/client/src/components/post-form.vue  |  2 +-
 .../client/src/components/signup-dialog.vue   |  4 +-
 packages/client/src/components/signup.vue     | 20 +++---
 packages/client/src/components/timeline.vue   |  6 +-
 packages/client/src/components/toast.vue      |  2 +-
 packages/client/src/components/ui/button.vue  | 14 ++---
 .../client/src/components/ui/context-menu.vue |  6 +-
 packages/client/src/components/ui/menu.vue    |  2 +-
 .../client/src/components/ui/modal-window.vue |  8 +--
 .../client/src/components/ui/pagination.vue   |  8 +--
 .../client/src/components/ui/popup-menu.vue   |  2 +-
 packages/client/src/components/ui/window.vue  | 54 ++++++++--------
 .../client/src/components/user-preview.vue    |  2 +-
 .../src/components/user-select-dialog.vue     |  6 +-
 .../src/components/visibility-picker.vue      |  6 +-
 .../client/src/components/waiting-dialog.vue  |  4 +-
 packages/client/src/components/widgets.vue    |  2 +-
 packages/client/src/filters/bytes.ts          |  2 +-
 packages/client/src/init.ts                   |  6 +-
 packages/client/src/instance.ts               |  4 +-
 packages/client/src/os.ts                     | 12 ++--
 packages/client/src/pages/admin/emojis.vue    |  8 +--
 packages/client/src/pages/admin/index.vue     |  4 +-
 packages/client/src/pages/channel-editor.vue  |  4 +-
 packages/client/src/pages/emojis.category.vue |  4 +-
 packages/client/src/pages/emojis.vue          |  4 +-
 packages/client/src/pages/follow.vue          |  2 +-
 packages/client/src/pages/gallery/edit.vue    |  4 +-
 packages/client/src/pages/gallery/post.vue    |  4 +-
 packages/client/src/pages/messaging/index.vue |  8 +--
 .../pages/messaging/messaging-room.form.vue   | 61 +++++++++----------
 .../src/pages/messaging/messaging-room.vue    | 32 +++++-----
 packages/client/src/pages/note.vue            |  4 +-
 packages/client/src/pages/page.vue            |  4 +-
 packages/client/src/pages/settings/2fa.vue    |  4 +-
 .../src/pages/settings/plugin.install.vue     |  8 +--
 .../src/pages/settings/theme.install.vue      |  2 +-
 .../src/pages/settings/webhook.edit.vue       | 16 ++---
 .../client/src/pages/settings/word-mute.vue   |  2 +-
 packages/client/src/pages/share.vue           |  6 +-
 packages/client/src/pages/theme-editor.vue    |  2 +-
 packages/client/src/pages/user-info.vue       |  4 +-
 packages/client/src/pages/user/index.vue      |  4 +-
 packages/client/src/theme-store.ts            |  8 +--
 packages/client/src/ui/_common_/sw-inject.ts  | 23 ++++---
 packages/client/src/ui/classic.widgets.vue    |  4 +-
 packages/client/src/ui/deck/deck-store.ts     |  4 +-
 packages/client/src/ui/universal.widgets.vue  |  6 +-
 69 files changed, 255 insertions(+), 257 deletions(-)

diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts
index 6f806ccc58..ce4af61f18 100644
--- a/packages/client/src/account.ts
+++ b/packages/client/src/account.ts
@@ -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));
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 46d45b690f..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>
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/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/follow-button.vue b/packages/client/src/components/follow-button.vue
index 93c9e891c1..b3540bc316 100644
--- a/packages/client/src/components/follow-button.vue
+++ b/packages/client/src/components/follow-button.vue
@@ -58,7 +58,7 @@ if (props.user.isFollowing == null) {
 }
 
 function onFollowChange(user: Misskey.entities.UserDetailed) {
-	if (user.id == props.user.id) {
+	if (user.id === props.user.id) {
 		isFollowing.value = user.isFollowing;
 		hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
 	}
@@ -96,8 +96,8 @@ async function onClick() {
 				hasPendingFollowRequestFromYou.value = true;
 			}
 		}
-	} catch (e) {
-		console.error(e);
+	} catch (err) {
+		console.error(err);
 	} finally {
 		wait.value = 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/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/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/time.vue b/packages/client/src/components/global/time.vue
index 5748d9de61..02351deb5f 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());
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/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/note-detailed.vue b/packages/client/src/components/note-detailed.vue
index d30284ca5f..14bbbd4f3c 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({
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 3cd7a819d4..bc8a0dd19d 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({
diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue
index 847da37c51..8ac8c46692 100644
--- a/packages/client/src/components/page/page.post.vue
+++ b/packages/client/src/components/page/page.post.vue
@@ -52,16 +52,16 @@ 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 => {
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 47e2e6ce0f..3807769118 100644
--- a/packages/client/src/components/post-form-attaches.vue
+++ b/packages/client/src/components/post-form-attaches.vue
@@ -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,
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 488c55231f..64ee873fd7 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -107,7 +107,7 @@ const props = withDefaults(defineProps<{
 	fixed?: boolean;
 	autofocus?: boolean;
 }>(), {
-	initialVisibleUsers: [],
+	initialVisibleUsers: () => [],
 	autofocus: true,
 });
 
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 aeed0e53fa..58c15d81b1 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -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..fe8f1c7cca 100644
--- a/packages/client/src/components/ui/button.vue
+++ b/packages/client/src/components/ui/button.vue
@@ -90,7 +90,7 @@ 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);
 			}
@@ -104,18 +104,18 @@ export default defineComponent({
 				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/menu.vue b/packages/client/src/components/ui/menu.vue
index a93cc8cda8..ca56048262 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -60,7 +60,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'close'): void;
+	(ev: 'close'): void;
 }>();
 
 let itemsEl = $ref<HTMLDivElement>();
diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue
index b4b8c2b965..6de29c83fa 100644
--- a/packages/client/src/components/ui/modal-window.vue
+++ b/packages/client/src/components/ui/modal-window.vue
@@ -79,10 +79,10 @@ export default defineComponent({
 			this.$refs.modal.close();
 		},
 
-		onKeydown(e) {
-			if (e.which === 27) { // Esc
-				e.preventDefault();
-				e.stopPropagation();
+		onKeydown(evt) {
+			if (evt.which === 27) { // Esc
+				evt.preventDefault();
+				evt.stopPropagation();
 				this.close();
 			}
 		},
diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 428a9d0225..c081e06acd 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -68,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; };
@@ -112,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;
 	});
@@ -155,7 +155,7 @@ const fetchMore = async (): Promise<void> => {
 		}
 		offset.value += res.length;
 		moreFetching.value = false;
-	}, e => {
+	}, err => {
 		moreFetching.value = false;
 	});
 };
@@ -183,7 +183,7 @@ const fetchMoreAhead = async (): Promise<void> => {
 		}
 		offset.value += res.length;
 		moreFetching.value = false;
-	}, e => {
+	}, err => {
 		moreFetching.value = false;
 	});
 };
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/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/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 6e4122427b..b6835795cb 100644
--- a/packages/client/src/components/widgets.vue
+++ b/packages/client/src/components/widgets.vue
@@ -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>
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 5dbbcb2a2b..bb6176e409 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -146,7 +146,7 @@ if ($i && $i.token) {
 		try {
 			document.body.innerHTML = '<div>Please wait...</div>';
 			await login(i);
-		} catch (e) {
+		} catch (err) {
 			// Render the error screen
 			// TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな)
 			document.body.innerHTML = '<div id="err">Oops!</div>';
@@ -249,7 +249,7 @@ if (lastVersion !== version) {
 				popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed');
 			}
 		}
-	} catch (e) {
+	} catch (err) {
 	}
 }
 
@@ -334,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 06a8ff99dc..6baf538917 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -59,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,
 		});
 	});
 
@@ -72,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);
@@ -88,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,
 			});
 		}
 	});
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index ffb7fb34aa..38bcc41ea0 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -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/index.vue b/packages/client/src/pages/admin/index.vue
index 210226f283..9b7fa5678e 100644
--- a/packages/client/src/pages/admin/index.vue
+++ b/packages/client/src/pages/admin/index.vue
@@ -265,10 +265,10 @@ const invite = () => {
 			type: 'info',
 			text: x.code
 		});
-	}).catch(e => {
+	}).catch(err => {
 		os.alert({
 			type: 'error',
-			text: e
+			text: err,
 		});
 	});
 };
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..1be004cf51 100644
--- a/packages/client/src/pages/emojis.category.vue
+++ b/packages/client/src/pages/emojis.category.vue
@@ -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/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..a0c2d1a596 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -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..61c8bb0ce3 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;
diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
index 35cb75743f..ad8aaae6b7 100644
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ b/packages/client/src/pages/messaging/messaging-room.form.vue
@@ -59,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;
@@ -88,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) : '';
@@ -101,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
@@ -110,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
@@ -135,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();
 			}
 		},
@@ -154,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;
 			});
 		},
@@ -193,9 +192,9 @@ 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,
@@ -203,15 +202,15 @@ export default defineComponent({
 				}
 			}
 
-			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/note.vue b/packages/client/src/pages/note.vue
index 9e174ef495..f0a18ecc36 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -136,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.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/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index a19d6378d7..be464f040d 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -121,10 +121,10 @@ function submit() {
 	}).then(() => {
 		os.success();
 		$i!.twoFactorEnabled = true;
-	}).catch(e => {
+	}).catch(err => {
 		os.alert({
 			type: 'error',
-			text: e
+			text: err,
 		});
 	});
 }
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index 6ece531462..96c0abfd99 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -43,7 +43,7 @@ async function install() {
 	let ast;
 	try {
 		ast = parse(code.value);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
 			text: 'Syntax error :('
@@ -60,8 +60,8 @@ async function install() {
 		return;
 	}
 
-	const data = meta.get(null);
-	if (data == null) {
+	const metadata = meta.get(null);
+	if (metadata == null) {
 		os.alert({
 			type: 'error',
 			text: 'No metadata found :('
@@ -69,7 +69,7 @@ async function install() {
 		return;
 	}
 
-	const { name, version, author, description, permissions, config } = data;
+	const { name, version, author, description, permissions, config } = metadata;
 	if (name == null || version == null || author == null) {
 		os.alert({
 			type: 'error',
diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index 0ef098f318..25fa6c012b 100644
--- a/packages/client/src/pages/settings/theme.install.vue
+++ b/packages/client/src/pages/settings/theme.install.vue
@@ -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/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 97a15da5b5..48fcb362b9 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -71,7 +71,7 @@ watch(hardMutedWords, () => {
 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 != '');
+		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++) {
diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue
index 4d77de5819..b08ac2b237 100644
--- a/packages/client/src/pages/share.vue
+++ b/packages/client/src/pages/share.vue
@@ -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 5f9f1b9783..4250673d91 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -128,7 +128,7 @@ function showPreview() {
 }
 
 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/user-info.vue b/packages/client/src/pages/user-info.vue
index 516ab4d440..1b2682ed29 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -232,10 +232,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 17e815892b..a024dd28bc 100644
--- a/packages/client/src/pages/user/index.vue
+++ b/packages/client/src/pages/user/index.vue
@@ -260,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/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_/sw-inject.ts b/packages/client/src/ui/_common_/sw-inject.ts
index e3e2ddd7e6..371f80ca15 100644
--- a/packages/client/src/ui/_common_/sw-inject.ts
+++ b/packages/client/src/ui/_common_/sw-inject.ts
@@ -14,30 +14,29 @@ export function swInject() {
 			console.log('sw msg', ev.data);
 		}
 
-		const data = ev.data; // as SwMessage
-		if (data.type !== 'order') return;
+		if (ev.data.type !== 'order') return;
 
-		if (data.loginId !== $i?.id) {
-			return getAccountFromId(data.loginId).then(account => {
+		if (ev.data.loginId !== $i?.id) {
+			return getAccountFromId(ev.data.loginId).then(account => {
 				if (!account) return;
-				return login(account.token, data.url);
+				return login(account.token, ev.data.url);
 			});
 		}
 
-		switch (data.order) {
+		switch (ev.data.order) {
 			case 'post':
-				return post(data.options);
+				return post(ev.data.options);
 			case 'push':
-				if (router.currentRoute.value.path === data.url) {
+				if (router.currentRoute.value.path === ev.data.url) {
 					return window.scroll({ top: 0, behavior: 'smooth' });
 				}
 				if (navHook) {
-					return navHook(data.url);
+					return navHook(ev.data.url);
 				}
-				if (sideViewHook && defaultStore.state.defaultSideView && data.url !== '/') {
-					return sideViewHook(data.url);
+				if (sideViewHook && defaultStore.state.defaultSideView && ev.data.url !== '/') {
+					return sideViewHook(ev.data.url);
 				}
-				return router.push(data.url);
+				return router.push(ev.data.url);
 			default:
 				return;
 		}
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/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts
index c2c9ae540b..c847bf2b43 100644
--- a/packages/client/src/ui/deck/deck-store.ts
+++ b/packages/client/src/ui/deck/deck-store.ts
@@ -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/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue
index 2660e80368..a42c085690 100644
--- a/packages/client/src/ui/universal.widgets.vue
+++ b/packages/client/src/ui/universal.widgets.vue
@@ -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));
 }
 

From 63a814c70e96a5d39738e4157b4e94fb29c93f2e Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 27 May 2022 15:03:25 +0200
Subject: [PATCH 178/258] fix(docs): correct information for drive upload
 (#8736)

---
 .../backend/src/server/api/openapi/gen-spec.ts   | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

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,
 					},
 				},
 			},

From cec3dcec8afee97222e7ec9a8b8d3ff0d163e31a Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 27 May 2022 23:21:12 +0200
Subject: [PATCH 179/258] enhance: clearly link documentation

fix #8744
---
 README.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/README.md b/README.md
index 4b7b3273d9..c273270644 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,10 @@ With Misskey's built in drive, you get cloud storage right in your social media,
 
 <div style="clear: both;"></div>
 
+## Documentation
+
+Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it.
+
 ## Sponsors
 <div align="center">
 	<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>

From 161659de5cd7633161b0788799b641ff6b7e55f9 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 28 May 2022 05:06:47 +0200
Subject: [PATCH 180/258] enhance: replace signin CAPTCHA with rate limit
 (#8740)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* enhance: rate limit works without signed in user

* fix: make limit key required for limiter

As before the fallback limiter key will be set from the endpoint name.

* enhance: use limiter for signin

* Revert "CAPTCHA求めるのは2fa認証が無効になっているときだけにした"

This reverts commit 02a43a310f6ad0cc9e9beccc26e51ab5b339e15f.

* Revert "feat: make captcha required when signin to improve security"

This reverts commit b21b0580058c14532ff3f4033e2a9147643bfca6.

* fix undefined reference

* fix: better error message

* enhance: only handle prefix of IPv6
---
 CHANGELOG.md                                  |  2 +
 locales/ja-JP.yml                             |  1 +
 packages/backend/src/server/api/call.ts       | 49 +++++++++++++------
 packages/backend/src/server/api/endpoints.ts  |  1 -
 packages/backend/src/server/api/limiter.ts    | 26 ++++------
 .../backend/src/server/api/private/signin.ts  | 41 ++++++++--------
 packages/client/src/components/signin.vue     | 12 +++--
 7 files changed, 75 insertions(+), 57 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6aadc0b2ae..2a463c3a0b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,8 @@ You should also include the user name that made the change.
   Your own theme color may be unset if it was in an invalid format.
   Admins should check their instance settings if in doubt.
 - Perform port diagnosis at startup only when Listen fails @mei23
+- Rate limiting is now also usable for non-authenticated users. @Johann150
+  Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
 
 ### Bugfixes
 - Client: fix settings page @tamaina
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index f64246d155..6354fcfda1 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -842,6 +842,7 @@ oneDay: "1日"
 oneWeek: "1週間"
 reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
 failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
+rateLimitExceeded: "レート制限を超えました"
 
 _emailUnavailable:
   used: "既に使用されています"
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 9a85e4565b..fbe25e1732 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 IPCIDR from 'ip-cidr';
 
 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,37 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		throw new ApiError(accessDenied);
 	}
 
+	if (ep.meta.requireCredential && 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 {
+			// 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 ip = IPCIDR.createAddress(ctx.ip).mask(64);
+
+			limitActor = 'ip-' + parseInt(ip, 2).toString(36);
+		}
+
+		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 +86,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 +98,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/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/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/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index 0024b8ce3e..b304550e29 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -1,25 +1,21 @@
-import { randomBytes } from 'node:crypto';
 import Koa from 'koa';
 import bcrypt from 'bcryptjs';
 import * as speakeasy from 'speakeasy';
-import { IsNull } from 'typeorm';
+import signin from '../common/signin.js';
 import config from '@/config/index.js';
 import { Users, Signins, UserProfiles, UserSecurityKeys, AttestationChallenges } from '@/models/index.js';
 import { ILocalUser } from '@/models/entities/user.js';
 import { genId } from '@/misc/gen-id.js';
-import { fetchMeta } from '@/misc/fetch-meta.js';
-import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha.js';
 import { verifyLogin, hash } from '../2fa.js';
-import signin from '../common/signin.js';
+import { randomBytes } from 'node:crypto';
+import { IsNull } from 'typeorm';
+import { limiter } from '../limiter.js';
 
 export default async (ctx: Koa.Context) => {
 	ctx.set('Access-Control-Allow-Origin', config.url);
 	ctx.set('Access-Control-Allow-Credentials', 'true');
 
 	const body = ctx.request.body as any;
-
-	const instance = await fetchMeta(true);
-
 	const username = body['username'];
 	const password = body['password'];
 	const token = body['token'];
@@ -29,6 +25,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 }, 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;
@@ -84,18 +95,6 @@ export default async (ctx: Koa.Context) => {
 	}
 
 	if (!profile.twoFactorEnabled) {
-		if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
-			await verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(e => {
-				ctx.throw(400, e);
-			});
-		}
-	
-		if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
-			await verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(e => {
-				ctx.throw(400, e);
-			});
-		}
-	
 		if (same) {
 			signin(ctx, user);
 			return;
@@ -172,7 +171,7 @@ export default async (ctx: Koa.Context) => {
 				body.credentialId
 					.replace(/-/g, '+')
 					.replace(/_/g, '/'),
-				'base64',
+					'base64'
 			).toString('hex'),
 		});
 
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index d283a758a6..be87274020 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -14,8 +14,6 @@
 				<template #prefix><i class="fas fa-lock"></i></template>
 				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
 			</MkInput>
-			<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" 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 }">
@@ -64,8 +62,6 @@ import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 
-const MkCaptcha = defineAsyncComponent(() => import('./captcha.vue'));
-
 let signing = $ref(false);
 let user = $ref(null);
 let username = $ref('');
@@ -217,6 +213,14 @@ function loginFailed(err) {
 			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({

From 1c057818c6f6d8ffb0b6ce506577cd890a1a2d5c Mon Sep 17 00:00:00 2001
From: Kainoa Kanter <44733677+ThatOneCalculator@users.noreply.github.com>
Date: Fri, 27 May 2022 20:49:34 -0700
Subject: [PATCH 181/258] Remove require captcha from signin from CHANGELOG
 (#8748)

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2a463c3a0b..9b086ddba9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,7 +21,6 @@ You should also include the user name that made the change.
 - replaced webpack with Vite @tamaina
 - update dependencies @syuilo
 - enhance: display URL of QR code for TOTP registration @syuilo
-- make CAPTCHA required for signin to improve security @syuilo
 - enhance: Supports Unicode Emoji 14.0 @mei23
 - The theme color is now better validated. @Johann150
   Your own theme color may be unset if it was in an invalid format.

From 708fba989adab95b3a4c381f6b351979c5973591 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Sat, 28 May 2022 07:28:12 +0200
Subject: [PATCH 182/258] feat(tests): add e2e tests for widgets (#8735)

* test(e2e): add baseline for widget tests

* chore(repo): enable test running in branch

* fix(e2e): set viewport for widget tests

* fix(client): add widget identifier classes to widgets

* test(e2e): add memo widget test

* fix(tests): force select value

* fix(tests): force button press for widget addition

* fix(tests): invoke select value differently

* fix(tests): adjust widget submit

* fix(tests): don't explicitly navigate for widget test

* fix(tests): click label to hide select popup

* fix(tests): just click modal background

* fix(tests): adjust modal background selector

* fix(tests): click all modal backgrounds

* feat(e2e): add test for adding timeline widget

* fix(client): add more widget identifier classes

* feat(tests): add method abstraction for test cases

* fix(tests): force-click overlays

* fix(tests): force widget button press

* fix(tests): remove timeout from final widget check

* feat(tests): add widget removal test case

* fix(client): use mk instead of msky as class prefix

* fix(tests): check widgets for existence rather than visibility

* chore(meta): don't run tests for specific feature branch
---
 cypress/integration/widgets.js                | 84 +++++++++++++++++++
 packages/client/src/components/widgets.vue    |  4 +-
 packages/client/src/ui/universal.widgets.vue  |  2 +-
 packages/client/src/widgets/activity.vue      |  2 +-
 packages/client/src/widgets/aichan.vue        |  2 +-
 packages/client/src/widgets/aiscript.vue      |  2 +-
 packages/client/src/widgets/clock.vue         |  2 +-
 packages/client/src/widgets/federation.vue    |  2 +-
 packages/client/src/widgets/memo.vue          |  2 +-
 packages/client/src/widgets/notifications.vue |  2 +-
 packages/client/src/widgets/photos.vue        |  2 +-
 packages/client/src/widgets/post-form.vue     |  2 +-
 packages/client/src/widgets/rss.vue           |  2 +-
 packages/client/src/widgets/slideshow.vue     |  2 +-
 packages/client/src/widgets/timeline.vue      |  2 +-
 packages/client/src/widgets/trends.vue        |  2 +-
 16 files changed, 100 insertions(+), 16 deletions(-)
 create mode 100644 cypress/integration/widgets.js

diff --git a/cypress/integration/widgets.js b/cypress/integration/widgets.js
new file mode 100644
index 0000000000..d63ff274bd
--- /dev/null
+++ b/cypress/integration/widgets.js
@@ -0,0 +1,84 @@
+describe('After user signed in', () => {
+	beforeEach(() => {
+		cy.window(win => {
+			win.indexedDB.deleteDatabase('keyval-store');
+		});
+		cy.viewport('macbook-16');
+		cy.request('POST', '/api/reset-db').as('reset');
+		cy.get('@reset').its('status').should('equal', 204);
+		cy.reload(true);
+
+		// インスタンス初期セットアップ
+		cy.request('POST', '/api/admin/accounts/create', {
+			username: 'admin',
+			password: 'pass',
+		}).its('body').as('admin');
+
+		// ユーザー作成
+		cy.request('POST', '/api/signup', {
+			username: 'alice',
+			password: 'alice1234',
+		}).its('body').as('alice');
+
+		cy.visit('/');
+
+		cy.intercept('POST', '/api/signin').as('signin');
+
+		cy.get('[data-cy-signin]').click();
+		cy.get('[data-cy-signin-username] input').type('alice');
+		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
+
+		cy.wait('@signin').as('signedIn');
+	});
+
+	afterEach(() => {
+		// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
+		// waitを入れることでそれを防止できる
+		cy.wait(1000);
+	});
+
+  it('widget edit toggle is visible', () => {
+		cy.get('.mk-widget-edit').should('be.visible');
+  });
+
+	it('widget select should be visible in edit mode', () => {
+		cy.get('.mk-widget-edit').click();
+		cy.get('.mk-widget-select').should('be.visible');
+  });
+
+	it('first widget should be removed', () => {
+		cy.get('.mk-widget-edit').click();
+		cy.get('.customize-container:first-child .remove._button').click();
+		cy.get('.customize-container').should('have.length', 2);
+	});
+
+	function buildWidgetTest(widgetName) {
+		it(`${widgetName} widget should get added`, () => {
+			cy.get('.mk-widget-edit').click();
+			cy.get('.mk-widget-select select').select(widgetName, { force: true });
+			cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true });
+			cy.get('.mk-widget-add').click({ force: true });
+			cy.get(`.mkw-${widgetName}`).should('exist');
+		});
+	}
+
+	buildWidgetTest('memo');
+	buildWidgetTest('notifications');
+	buildWidgetTest('timeline');
+	buildWidgetTest('calendar');
+	buildWidgetTest('rss');
+	buildWidgetTest('trends');
+	buildWidgetTest('clock');
+	buildWidgetTest('activity');
+	buildWidgetTest('photos');
+	buildWidgetTest('digitalClock');
+	buildWidgetTest('federation');
+	buildWidgetTest('postForm');
+	buildWidgetTest('slideshow');
+	buildWidgetTest('serverMetric');
+	buildWidgetTest('onlineUsers');
+	buildWidgetTest('jobQueue');
+	buildWidgetTest('button');
+	buildWidgetTest('aiscript');
+	buildWidgetTest('aichan');
+});
diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue
index b6835795cb..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
diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue
index a42c085690..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>
 
diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue
index 631beceb72..7fb9f5894c 100644
--- a/packages/client/src/widgets/activity.vue
+++ b/packages/client/src/widgets/activity.vue
@@ -1,5 +1,5 @@
 <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>
 
diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue
index 70e47f2af1..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>
diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue
index b74e2258a9..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">
diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue
index 0a35c4c5ab..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>
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
index 1bfb068a2f..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">
diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue
index f2d1bbc047..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">
diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue
index f51e983a0e..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>
 
diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue
index 8e30765290..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="">
diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue
index 5b74602c85..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>
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index 6b057cdd06..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>
 
diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue
index 1b6c2d766d..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 }}
diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue
index c9a9e68bb9..408cf2cbea 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>
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
index 34bbc16a8b..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">

From 4a50c49211654758d391b39d78fe4d171afc1f19 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sat, 28 May 2022 21:59:23 +0900
Subject: [PATCH 183/258] Fix theme import (#8749)

---
 packages/client/@types/theme.d.ts             |  8 +--
 .../src/pages/settings/theme.manage.vue       | 11 +++--
 packages/client/src/pages/settings/theme.vue  |  5 +-
 packages/client/src/pages/theme-editor.vue    |  4 +-
 packages/client/src/scripts/theme.ts          | 49 +++++++++++--------
 5 files changed, 46 insertions(+), 31 deletions(-)

diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts
index b8b906b82e..67f724a9aa 100644
--- a/packages/client/@types/theme.d.ts
+++ b/packages/client/@types/theme.d.ts
@@ -1,5 +1,7 @@
-import { Theme } from '../src/scripts/theme';
-
 declare module '@/themes/*.json5' {
-	export = Theme;
+	import { Theme } from "@/scripts/theme";
+
+	const theme: Theme;
+
+	export default theme;
 }
diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index 7da439f9c0..94b2d24455 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -10,13 +10,13 @@
 		</optgroup>
 	</FormSelect>
 	<template v-if="selectedTheme">
-		<FormInput readonly :modelValue="selectedTheme.author" class="_formBlock">
+		<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">
+		<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">
+		<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>
@@ -32,7 +32,7 @@ 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 { getThemes, removeTheme } from '@/theme-store';
@@ -40,9 +40,10 @@ import * as symbols from '@/symbols';
 import { i18n } from '@/i18n';
 
 const installedThemes = ref(getThemes());
+const builtinThemes = getBuiltinThemesRef();
 const selectedThemeId = ref(null);
 
-const themes = computed(() => builtinThemes.concat(installedThemes.value));
+const themes = computed(() => [ ...installedThemes.value, ...builtinThemes.value ]);
 
 const selectedTheme = computed(() => {
 	if (selectedThemeId.value == null) return null;
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index 64b384bdcd..b32aa237fe 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -93,7 +93,7 @@ import FormSelect from '@/components/form/select.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';
@@ -105,12 +105,13 @@ import { fetchThemes, getThemes } from '@/theme-store';
 import * as symbols from '@/symbols';
 
 const installedThemes = ref(getThemes());
+const builtinThemes = getBuiltinThemesRef();
 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 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');
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index 4250673d91..2ee530597c 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -75,7 +75,9 @@ 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';
diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts
index b61b1684a8..e2b272405a 100644
--- a/packages/client/src/scripts/theme.ts
+++ b/packages/client/src/scripts/theme.ts
@@ -1,3 +1,4 @@
+import { ref } from 'vue';
 import { globalEvents } from '@/events';
 import tinycolor from 'tinycolor2';
 
@@ -10,30 +11,38 @@ export type Theme = {
 	props: Record<string, string>;
 };
 
-export const lightTheme: Theme = await import('@/themes/_light.json5');
-export const darkTheme: Theme = await import('@/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 = [
-	await import('@/themes/l-light.json5'),
-	await import('@/themes/l-coffee.json5'),
-	await import('@/themes/l-apricot.json5'),
-	await import('@/themes/l-rainy.json5'),
-	await import('@/themes/l-vivid.json5'),
-	await import('@/themes/l-cherry.json5'),
-	await import('@/themes/l-sushi.json5'),
+export const getBuiltinThemes = () => Promise.all(
+	[
+		'l-light',
+		'l-coffee',
+		'l-apricot',
+		'l-rainy',
+		'l-vivid',
+		'l-cherry',
+		'l-sushi',
 
-	await import('@/themes/d-dark.json5'),
-	await import('@/themes/d-persimmon.json5'),
-	await import('@/themes/d-astro.json5'),
-	await import('@/themes/d-future.json5'),
-	await import('@/themes/d-botanical.json5'),
-	await import('@/themes/d-cherry.json5'),
-	await import('@/themes/d-ice.json5'),
-	await import('@/themes/d-pumpkin.json5'),
-	await import('@/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;
 

From abc8998b485ebe6f9a1be03cd93950f668a974cc Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 29 May 2022 00:15:32 +0900
Subject: [PATCH 184/258] refactor: use css module at
 components/global/loading.vue (#8750)

* refactor: use css module at components/global/loading.vue

* rename class name to "root"
---
 .../client/src/components/global/loading.vue  | 64 ++++++++++---------
 1 file changed, 33 insertions(+), 31 deletions(-)

diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue
index fa2ce1800c..5a7e362fcf 100644
--- a/packages/client/src/components/global/loading.vue
+++ b/packages/client/src/components/global/loading.vue
@@ -1,12 +1,12 @@
 <template>
-<div class="yxspomdl" :class="{ inline, colored, mini }">
-	<div class="container">
-		<svg class="spinner bg" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
+<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="spinner fg" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/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>
@@ -16,7 +16,9 @@
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { useCssModule } from 'vue';
+
+useCssModule();
 
 const props = withDefaults(defineProps<{
 	inline?: boolean;
@@ -29,7 +31,7 @@ const props = withDefaults(defineProps<{
 });
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
 @keyframes spinner {
 	0% {
 		transform: rotate(0deg);
@@ -39,7 +41,7 @@ const props = withDefaults(defineProps<{
 	}
 }
 
-.yxspomdl {
+.root {
 	padding: 32px;
 	text-align: center;
 	cursor: wait;
@@ -60,33 +62,33 @@ const props = withDefaults(defineProps<{
 		padding: 16px;
 		--size: 32px;
 	}
+}
 
-	> .container {
-		position: relative;
-		width: var(--size);
-		height: var(--size);
-		margin: 0 auto;
+.container {
+	position: relative;
+	width: var(--size);
+	height: var(--size);
+	margin: 0 auto;
+}
 
-		> .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;
-		}
+.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;
+}
 
-		> .bg {
-			opacity: 0.275;
-		}
+.bg {
+	opacity: 0.275;
+}
 
-		> .fg {
-			animation: spinner 0.5s linear infinite;
-		}
-	}
+.fg {
+	animation: spinner 0.5s linear infinite;
 }
 </style>

From 21d54f2758903c31714c49f278141c00e33294c9 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 28 May 2022 17:26:17 +0200
Subject: [PATCH 185/258] fix: validate text is not empty

fix #8747
---
 packages/backend/src/server/api/endpoints/notes/create.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 955f53bbc1..a133294169 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -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'],
 		},

From e54aa56ee13eb146cc6244ff1eaf401a0765a899 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 20 May 2022 11:11:27 +0200
Subject: [PATCH 186/258] chore: remove unused imports

---
 packages/backend/src/remote/activitypub/models/note.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index 097a716614..ad24bbcd65 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';

From 4917961736cb4d01da31183a90a0f33623684c72 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 29 May 2022 10:57:06 +0900
Subject: [PATCH 187/258] preload app css (#8752)

---
 packages/backend/src/config/load.ts           |  2 +-
 packages/backend/src/server/web/index.ts      |  4 +--
 .../backend/src/server/web/views/base.pug     | 32 ++++++++++++-------
 3 files changed, 23 insertions(+), 15 deletions(-)

diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts
index c2e6bea45e..9654a4f3b0 100644
--- a/packages/backend/src/config/load.ts
+++ b/packages/backend/src/config/load.ts
@@ -46,7 +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'].file.replace(/^_client_dist_\//, '');
+	mixin.clientEntry = clientManifest['src/init.ts'];
 
 	if (!config.redis.prefix) config.redis.prefix = mixin.host;
 
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 9e31f2389e..0b2c62c044 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -74,9 +74,9 @@ app.use(views(_dirname + '/views', {
 	extension: 'pug',
 	options: {
 		version: config.version,
-		clientEntry: () => process.env.NODE_ENV === 'production' ?
+		getClientEntry: () => process.env.NODE_ENV === 'production' ?
 			config.clientEntry :
-			JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'].file.replace(/^_client_dist_\//, ''),
+			JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'],
 		config,
 	},
 }));
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index a488e51171..69ddb73c0a 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -1,17 +1,23 @@
 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,9 +36,11 @@ 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')
 
+		each href in clientEntry.css
+			link(rel='preload' href=`/assets/${href}` as='style')
+
 		title
 			block title
 				= title || 'Misskey'
@@ -52,7 +60,7 @@ html
 
 		script.
 			var VERSION = "#{version}";
-			var CLIENT_ENTRY = "#{clientEntry()}";
+			var CLIENT_ENTRY = "#{clientEntry.file}";
 
 		script
 			include ../boot.js

From f1d2398eacbc69a14d09ebe5e7040be891f9e143 Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 29 May 2022 10:58:54 +0900
Subject: [PATCH 188/258] fix(client): Vite related boot mechanism revision
 (#8753)

* preload app css

* remove salt

* APP_FETCH_FAILED error

* set max-age to 15s
---
 packages/backend/src/server/web/boot.js        | 13 +++----------
 packages/backend/src/server/web/index.ts       | 14 +++++++-------
 packages/backend/src/server/web/views/base.pug |  4 ++++
 3 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index a9ee0df4f1..94329e11c9 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -54,14 +54,10 @@
 	//#endregion
 
 	//#region Script
-	const salt = localStorage.getItem('salt')
-		? `?salt=${localStorage.getItem('salt')}`
-		: '';
-
-	import(`/assets/${CLIENT_ENTRY}${salt}`)
-		.catch(async () => {
+	import(`/assets/${CLIENT_ENTRY}`)
+		.catch(async e => {
 			await checkUpdate();
-			renderError('APP_FETCH_FAILED');
+			renderError('APP_FETCH_FAILED', JSON.stringify(e));
 		})
 	//#endregion
 
@@ -142,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 0b2c62c044..2feee72be7 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -247,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にはしない
@@ -292,7 +292,7 @@ router.get('/notes/:note', async (ctx, next) => {
 			themeColor: meta.themeColor,
 		});
 
-		ctx.set('Cache-Control', 'public, max-age=180');
+		ctx.set('Cache-Control', 'public, max-age=15');
 
 		return;
 	}
@@ -329,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');
 		}
@@ -360,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;
 	}
@@ -385,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;
 	}
@@ -409,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;
 	}
@@ -468,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/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 69ddb73c0a..230ed1578a 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -37,6 +37,10 @@ html
 		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='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')

From 804fa33535baa9e5cdf49070a50a555cf2c3b1ea Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 29 May 2022 08:15:52 +0200
Subject: [PATCH 189/258] refactor: improve code quality (#8751)

* remove unnecessary if

`Array.prototype.some` already returns a boolean so an if to return
true or false is completely unnecessary in this case.

* perf: use count instead of find

When using `count` instead of `findOneBy`, the data is not
unnecessarily loaded.

* remove duplicate null check

The variable is checked for null in the lines above and the function
returns if so. Therefore, it can not be null at this point.

* simplify `getJsonSchema`

Because the assigned value is `null` and the used keys are only
shallow, use of `nestedProperty.set` seems inappropriate. Because the
value is not read, the initial for loop can be replaced by a `for..in`
loop.

Since all keys will be assigned `null`, the condition of the ternary
expression in the nested function will always be true. Therefore the
recursion case will never happen. With this the nested function can be
eliminated.

* remove duplicate condition

The code above already checks `dragging` and returns if it is truthy.
Checking it again later is therefore unnecessary.

To make this more obvious the `return` is removed in favour of using
an if...else construct.

* remove impossible "unknown" time

The `ago` variable will always be a number and all non-negative numbers
are already covered by other cases, the negative case is handled with
`future` so there is no case when `unkown` could be achieved.
---
 locales/ja-JP.yml                             |  1 -
 .../backend/src/models/repositories/note.ts   | 19 ++--
 .../backend/src/models/repositories/user.ts   | 91 +++++++++++--------
 .../src/remote/activitypub/renderer/index.ts  |  2 +-
 packages/backend/src/services/chart/core.ts   | 27 ++----
 .../client/src/components/global/time.vue     |  3 +-
 packages/client/src/ui/deck/column.vue        | 13 ++-
 7 files changed, 77 insertions(+), 79 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6354fcfda1..9cd1d1eedb 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1111,7 +1111,6 @@ _sfx:
   channel: "チャンネル通知"
 
 _ago:
-  unknown: "謎"
   future: "未来"
   justNow: "たった今"
   secondsAgo: "{n}秒前"
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index 638d78f626..c0abbb4f93 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -144,13 +144,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);
 			}
 		}
 
@@ -169,9 +163,12 @@ export const NoteRepository = db.getRepository(Note).extend({
 			} else {
 				// フォロワーかどうか
 				const [following, user] = await Promise.all([
-					Followings.findOneBy({
-						followeeId: note.userId,
-						followerId: meId,
+					Followings.count({
+						where: {
+							followeeId: note.userId,
+							followerId: meId,
+						},
+						take: 1,
 					}),
 					Users.findOneByOrFail({ id: meId }),
 				]);
@@ -183,7 +180,7 @@ export const NoteRepository = db.getRepository(Note).extend({
 				in which case we can never know the following. Instead we have
 				to assume that the users are following each other.
 				*/
-				return following != null || (note.userHost != null && user.host != null);
+				return following > 0 || (note.userHost != null && user.host != null);
 			}
 		}
 
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/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/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/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue
index 02351deb5f..a7f142f961 100644
--- a/packages/client/src/components/global/time.vue
+++ b/packages/client/src/components/global/time.vue
@@ -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/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue
index fbaea64f56..31063a753d 100644
--- a/packages/client/src/ui/deck/column.vue
+++ b/packages/client/src/ui/deck/column.vue
@@ -213,14 +213,13 @@ function onDragover(ev) {
 	if (dragging) {
 		// 自分自身にはドロップさせない
 		ev.dataTransfer.dropEffect = 'none';
-		return;
+	} else {
+		const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
+
+		ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
+
+		if (isDeckColumn) draghover = true;
 	}
-
-	const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
-
-	ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
-
-	if (!dragging && isDeckColumn) draghover = true;
 }
 
 function onDragleave() {

From d2784030ec898e0b4e391f2eb840646f72cd7d6b Mon Sep 17 00:00:00 2001
From: futchitwo <74236683+futchitwo@users.noreply.github.com>
Date: Sun, 29 May 2022 19:21:36 +0900
Subject: [PATCH 190/258] fix(client): fix popout url (#8494)

---
 packages/client/src/pages/theme-editor.vue | 2 +-
 packages/client/src/scripts/popout.ts      | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index 2ee530597c..b7b537cead 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -126,7 +126,7 @@ let changed = $ref(false);
 useLeaveGuard($$(changed));
 
 function showPreview() {
-	os.pageWindow('preview');
+	os.pageWindow('/preview');
 }
 
 function setBgColor(color: typeof bgColors[number]) {
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);

From feba678e03d3c7635d6f9f130ea4355af245e70a Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 29 May 2022 14:26:29 +0200
Subject: [PATCH 191/258] enhance(dev): ask for log snippets

---
 .github/ISSUE_TEMPLATE/01_bug-report.md | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md
index 8734fc0c36..0fecce2ee8 100644
--- a/.github/ISSUE_TEMPLATE/01_bug-report.md
+++ b/.github/ISSUE_TEMPLATE/01_bug-report.md
@@ -22,7 +22,10 @@ First, in order to avoid duplicate Issues, please search to see if the problem y
 
 ## 🤬 Actual Behavior
 
-<!--- Tell us what happens instead of the expected behavior -->
+<!--
+Tell us what happens instead of the expected behavior.
+Please include errors from the developer console and/or server log files if you have access to them.
+-->
 
 ## 📝 Steps to Reproduce
 

From ebc2566130c785924380d504c76b270198bdc2c2 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sun, 29 May 2022 14:32:52 +0200
Subject: [PATCH 192/258] fix: add missing import

fix #8756
---
 .../backend/src/queue/processors/db/export-custom-emojis.ts     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 97ba62dcf6..8ce1d05272 100644
--- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts
@@ -9,7 +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 { createTempDir } from '@/misc/create-temp.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';

From 39051854635617d1b95d85b759e3c1aa6f827b4b Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Mon, 30 May 2022 04:37:34 +0200
Subject: [PATCH 193/258] fix(client): import shared ESLint config in client
 package (#8761)

---
 packages/client/.eslintrc.js  | 105 +++++++++++++++++++---------------
 packages/client/tsconfig.json |   1 +
 2 files changed, 59 insertions(+), 47 deletions(-)

diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js
index a6e23e5171..1c2ab0a427 100644
--- a/packages/client/.eslintrc.js
+++ b/packages/client/.eslintrc.js
@@ -1,68 +1,79 @@
 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: {
 		// 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/tsconfig.json b/packages/client/tsconfig.json
index 2c387286a4..f7320a7251 100644
--- a/packages/client/tsconfig.json
+++ b/packages/client/tsconfig.json
@@ -39,6 +39,7 @@
 	},
 	"compileOnSave": false,
 	"include": [
+		".eslintrc.js",
 		"./**/*.ts",
 		"./**/*.vue"
 	]

From 9759ca7d24d4a7b7154043160a8d8ba34b2505dc Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Mon, 30 May 2022 05:53:36 +0000
Subject: [PATCH 194/258] chore: remove packages/sw/webpack.config.js

---
 packages/sw/webpack.config.js | 71 -----------------------------------
 1 file changed, 71 deletions(-)
 delete mode 100644 packages/sw/webpack.config.js

diff --git a/packages/sw/webpack.config.js b/packages/sw/webpack.config.js
deleted file mode 100644
index a4bcf96ddc..0000000000
--- a/packages/sw/webpack.config.js
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * webpack configuration
- */
-
-const fs = require('fs');
-const webpack = require('webpack');
-
-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');
-
-module.exports = {
-	target: 'webworker',
-	entry: {
-		['sw-lib']: './src/lib.ts'
-	},
-	module: {
-		rules: [{
-			test: /\.ts$/,
-			exclude: /node_modules/,
-			use: [{
-				loader: 'ts-loader',
-				options: {
-					happyPackMode: true,
-					transpileOnly: true,
-					configFile: __dirname + '/tsconfig.json',
-				}
-			}]
-		}]
-	},
-	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:'),
-		}),
-	],
-	output: {
-		path: __dirname + '/../../built/_sw_dist_',
-		filename: `[name].js`,
-		publicPath: `/`,
-		pathinfo: false,
-	},
-	resolve: {
-		extensions: [
-			'.js', '.ts', '.json'
-		],
-		alias: {
-			'@': __dirname + '/src/',
-		}
-	},
-	resolveLoader: {
-		modules: ['node_modules']
-	},
-	devtool: false, //'source-map',
-	mode: isProduction ? 'production' : 'development'
-};

From 857055a9dd50bbc3331d2454da3897d855fdebfe Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Mon, 30 May 2022 12:09:22 +0000
Subject: [PATCH 195/258] chore: fix import tinycolor

---
 packages/client/src/pages/settings/drive.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue
index f235ace7ca..09a2537ed5 100644
--- a/packages/client/src/pages/settings/drive.vue
+++ b/packages/client/src/pages/settings/drive.vue
@@ -35,7 +35,7 @@
 
 <script lang="ts" setup>
 import { computed, defineExpose, ref } from 'vue';
-import * as tinycolor from 'tinycolor2';
+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';

From a98194bf1b10b7905f3eaf9bda18248dbad826ea Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 31 May 2022 04:38:52 +0200
Subject: [PATCH 196/258] chore(meta): label Pull Requests containing tests
 (#8768)

---
 .github/labeler.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.github/labeler.yml b/.github/labeler.yml
index dff3935571..98f1d2e383 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -4,5 +4,9 @@
 '🖥️Client':
 - packages/client/**/*
 
+'🧪Test':
+- cypress/**/*
+- packages/backend/test/**/*
+
 '‼️ wrong locales':
 - any: ['locales/*.yml', '!locales/ja-JP.yml']

From c05723ca6ad4f17b823662e83ed8b442fe10626a Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Tue, 31 May 2022 17:44:22 +0900
Subject: [PATCH 197/258] Fix IP address rate limit (#8758)

* Fix IP address rate limit

* CHANGELOG

* Tune getIpHash
---
 CHANGELOG.md                                      |  2 +-
 packages/backend/src/misc/get-ip-hash.ts          |  9 +++++++++
 packages/backend/src/server/api/call.ts           | 11 +++--------
 packages/backend/src/server/api/private/signin.ts |  3 ++-
 4 files changed, 15 insertions(+), 10 deletions(-)
 create mode 100644 packages/backend/src/misc/get-ip-hash.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9b086ddba9..cfcf52ce92 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,7 +26,7 @@ You should also include the user name that made the change.
   Your own theme color may be unset if it was in an invalid format.
   Admins should check their instance settings if in doubt.
 - Perform port diagnosis at startup only when Listen fails @mei23
-- Rate limiting is now also usable for non-authenticated users. @Johann150
+- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
   Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
 
 ### Bugfixes
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/server/api/call.ts b/packages/backend/src/server/api/call.ts
index fbe25e1732..cd3e0abc06 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -6,7 +6,7 @@ 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 IPCIDR from 'ip-cidr';
+import { getIpHash } from '@/misc/get-ip-hash.js';
 
 const accessDenied = {
 	message: 'Access denied.',
@@ -33,18 +33,13 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		throw new ApiError(accessDenied);
 	}
 
-	if (ep.meta.requireCredential && ep.meta.limit && !isModerator) {
+	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 {
-			// 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 ip = IPCIDR.createAddress(ctx.ip).mask(64);
-
-			limitActor = 'ip-' + parseInt(ip, 2).toString(36);
+			limitActor = getIpHash(ctx!.ip);
 		}
 
 		const limit = Object.assign({}, ep.meta.limit);
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index b304550e29..79b31764fd 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -10,6 +10,7 @@ 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);
@@ -27,7 +28,7 @@ export default async (ctx: Koa.Context) => {
 
 	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 }, ctx.ip);
+		await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
 	} catch (err) {
 		ctx.status = 429;
 		ctx.body = {

From c56e45ecef571d8eece5fba9054906ab8077e417 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 31 May 2022 10:54:02 +0200
Subject: [PATCH 198/258] fix: always remove completed tasks (#8771)

---
 packages/backend/src/queue/index.ts          | 2 ++
 packages/backend/src/services/note/create.ts | 3 ++-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index 67d5f5d248..c5fd7de1cb 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -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/services/note/create.ts b/packages/backend/src/services/note/create.ts
index ceb5e8cc71..e2bf9d5b59 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -312,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
 		endedPollNotificationQueue.add({
 			noteId: note.id,
 		}, {
-			delay
+			delay,
+			removeOnComplete: true,
 		});
 	}
 

From 95a3565d1c412983c0380a6cb3e4588c313d081a Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Tue, 31 May 2022 17:55:07 +0900
Subject: [PATCH 199/258] Fix `Cannot find module` issue (#8770)

* Add --force to yarn in the installation script

* CHAGELOG
---
 CHANGELOG.md                | 1 +
 scripts/install-packages.js | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfcf52ce92..9687595010 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -43,6 +43,7 @@ You should also include the user name that made the change.
 - Server: use correct order of attachments on notes @Johann150
 - Server: prevent crash when processing certain PNGs @syuilo
 - Server: Fix unable to generate video thumbnails @mei23
+- Server: Fix `Cannot find module` issue @mei23
 
 ## 12.110.1 (2022/04/23)
 
diff --git a/scripts/install-packages.js b/scripts/install-packages.js
index bc8e016a3c..d1dea3ebe5 100644
--- a/scripts/install-packages.js
+++ b/scripts/install-packages.js
@@ -3,7 +3,7 @@ const execa = require('execa');
 (async () => {
 	console.log('installing dependencies of packages/backend ...');
 
-	await execa('yarn', ['install'], {
+	await execa('yarn', ['--force', 'install'], {
 		cwd: __dirname + '/../packages/backend',
 		stdout: process.stdout,
 		stderr: process.stderr,

From d3e242a7f25e72bd65f27feebd878f5a45e7ae3b Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Tue, 31 May 2022 10:57:01 +0200
Subject: [PATCH 200/258] Extract commonly used test logic to commands (#8767)

* meta(tests): enable workflows to run in branch

* feat(tests): move commonly used logic to Cypress commands

* chore(tests): replace more code with commands

* meta(tests): disable workflows to run in branch
---
 cypress/integration/basic.js   | 63 ++++++----------------------------
 cypress/integration/widgets.js | 27 +++------------
 cypress/support/commands.js    | 30 ++++++++++++++++
 3 files changed, 44 insertions(+), 76 deletions(-)

diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js
index eb15cfe223..eb5195c4b2 100644
--- a/cypress/integration/basic.js
+++ b/cypress/integration/basic.js
@@ -1,11 +1,6 @@
 describe('Before setup instance', () => {
 	beforeEach(() => {
-		cy.window(win => {
-			win.indexedDB.deleteDatabase('keyval-store');
-		});
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 	});
 
 	afterEach(() => {
@@ -35,18 +30,10 @@ describe('Before setup instance', () => {
 
 describe('After setup instance', () => {
 	beforeEach(() => {
-		cy.window(win => {
-			win.indexedDB.deleteDatabase('keyval-store');
-		});
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// インスタンス初期セットアップ
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 	});
 
 	afterEach(() => {
@@ -76,24 +63,13 @@ describe('After setup instance', () => {
 
 describe('After user signup', () => {
 	beforeEach(() => {
-		cy.window(win => {
-			win.indexedDB.deleteDatabase('keyval-store');
-		});
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// インスタンス初期セットアップ
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 
 		// ユーザー作成
-		cy.request('POST', '/api/signup', {
-			username: 'alice',
-			password: 'alice1234',
-		}).its('body').as('alice');
+		cy.registerUser('alice', 'alice1234');
 	});
 
 	afterEach(() => {
@@ -138,34 +114,15 @@ describe('After user signup', () => {
 
 describe('After user singed in', () => {
 	beforeEach(() => {
-		cy.window(win => {
-			win.indexedDB.deleteDatabase('keyval-store');
-		});
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// インスタンス初期セットアップ
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 
 		// ユーザー作成
-		cy.request('POST', '/api/signup', {
-			username: 'alice',
-			password: 'alice1234',
-		}).its('body').as('alice');
+		cy.registerUser('alice', 'alice1234');
 
-		cy.visit('/');
-
-		cy.intercept('POST', '/api/signin').as('signin');
-
-		cy.get('[data-cy-signin]').click();
-		cy.get('[data-cy-signin-username] input').type('alice');
-		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
-
-		cy.wait('@signin').as('signedIn');
+		cy.login('alice', 'alice1234');
 	});
 
 	afterEach(() => {
diff --git a/cypress/integration/widgets.js b/cypress/integration/widgets.js
index d63ff274bd..56ad95ee94 100644
--- a/cypress/integration/widgets.js
+++ b/cypress/integration/widgets.js
@@ -1,34 +1,15 @@
 describe('After user signed in', () => {
 	beforeEach(() => {
-		cy.window(win => {
-			win.indexedDB.deleteDatabase('keyval-store');
-		});
+		cy.resetState();
 		cy.viewport('macbook-16');
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
 
 		// インスタンス初期セットアップ
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 
 		// ユーザー作成
-		cy.request('POST', '/api/signup', {
-			username: 'alice',
-			password: 'alice1234',
-		}).its('body').as('alice');
+		cy.registerUser('alice', 'alice1234');
 
-		cy.visit('/');
-
-		cy.intercept('POST', '/api/signin').as('signin');
-
-		cy.get('[data-cy-signin]').click();
-		cy.get('[data-cy-signin-username] input').type('alice');
-		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
-
-		cy.wait('@signin').as('signedIn');
+		cy.login('alice', 'alice1234');
 	});
 
 	afterEach(() => {
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 119ab03f7c..95bfcf6855 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -23,3 +23,33 @@
 //
 // -- This will overwrite an existing command --
 // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+
+Cypress.Commands.add('resetState', () => {
+	cy.window(win => {
+		win.indexedDB.deleteDatabase('keyval-store');
+	});
+	cy.request('POST', '/api/reset-db').as('reset');
+	cy.get('@reset').its('status').should('equal', 204);
+	cy.reload(true);
+});
+
+Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
+	const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
+
+	cy.request('POST', route, {
+		username: username,
+		password: password,
+	}).its('body').as(username);
+});
+
+Cypress.Commands.add('login', (username, password) => {
+	cy.visit('/');
+
+	cy.intercept('POST', '/api/signin').as('signin');
+
+	cy.get('[data-cy-signin]').click();
+	cy.get('[data-cy-signin-username] input').type(username);
+	cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
+
+	cy.wait('@signin').as('signedIn');
+});

From 025bf4a5e77f78928b21e77ceb33bc05a8ce642e Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 31 May 2022 11:57:55 +0200
Subject: [PATCH 201/258] fix(mfm): remove duplicate br tag/newline (#8616)

---
 packages/backend/src/mfm/from-html.ts | 3 +++
 1 file changed, 3 insertions(+)

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 = '';

From 121a1784a2be9264740e3cc229ad0a282e6a6e20 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Tue, 31 May 2022 16:22:00 +0200
Subject: [PATCH 202/258] fix(lint): indentation

---
 packages/client/src/ui/deck.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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)"
 		/>

From 118f3546616ab71e37e02eaf2330ccb195b57f04 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 1 Jun 2022 08:51:00 +0200
Subject: [PATCH 203/258] fix: server metrics widget

---
 packages/client/src/widgets/server-metric/net.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue
index 82b3a67d76..b698953f97 100644
--- a/packages/client/src/widgets/server-metric/net.vue
+++ b/packages/client/src/widgets/server-metric/net.vue
@@ -94,10 +94,10 @@ function onStats(connStats) {
 	inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
 	outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
 
-	inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0];
-	inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1];
-	outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0];
-	outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1];
+	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;

From 0263a783a6ef7cb8ebab8aa5e745ba2ed48528a1 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 1 Jun 2022 09:34:40 +0200
Subject: [PATCH 204/258] fix(dev): no labels for l10n_develop

---
 .github/workflows/labeler.yml | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 057208eda3..fa4a58c3a9 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -1,6 +1,8 @@
 name: "Pull Request Labeler"
 on:
-- pull_request_target
+  pull_request_target:
+    branches-ignore:
+      - 'l10n_develop'
 
 jobs:
   triage:

From df3bbfb4161ac53bdff8fbc5c78c4e1c8f5173f8 Mon Sep 17 00:00:00 2001
From: sn0w <92278018+realsn0w@users.noreply.github.com>
Date: Fri, 3 Jun 2022 14:22:03 +0200
Subject: [PATCH 205/258] fix(client): correctly handle MiAuth URLs with query
 string (#8772)

---
 packages/client/src/pages/miauth.vue | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

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() {

From a3fed7d0fbb551e7cf42b648a841f7d78f7d5659 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 3 Jun 2022 23:08:15 +0900
Subject: [PATCH 206/258] fix(test): reset redis in e2e test

#7986
---
 packages/backend/src/db/postgre.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index e09e93f04e..50a85d6267 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -73,6 +73,7 @@ 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);
 
@@ -217,6 +218,7 @@ export async function initDb() {
 
 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')

From 6061937996ec90c4d59cab5d199760bc20ffae6e Mon Sep 17 00:00:00 2001
From: PikaDude <mail@pikadude.me>
Date: Sat, 4 Jun 2022 00:14:50 +1000
Subject: [PATCH 207/258] User moderation details (#8762)

* add more user details for admins to see

* fix some issues

* small style fix

as suggested by Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* fix

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
---
 .../server/api/endpoints/admin/show-user.ts   | 42 ++++++++++++++++---
 packages/client/src/pages/user-info.vue       |  3 ++
 2 files changed, 40 insertions(+), 5 deletions(-)

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/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 1b2682ed29..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>

From 81109b14b585b2ca6ba85ebedcb41f9b8cca5382 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 3 Jun 2022 16:18:44 +0200
Subject: [PATCH 208/258] fix: correctly render empty note text (#8746)

Ensure that the _misskey_content attribute will always exist. Because
the API endpoint does not require the existence of the `text` field,
that field may be `undefined`. By using `?? null` it can be ensured
that the value is at least `null`.

Furthermore, the rendered HTML of a note with empty text will also be
the empty string. From git blame it seems that this behaviour was added
because of a Mastodon bug that might have previously existed. Hoever,
this seems to be no longer the case as I can find mastodon posts that
have empty content.

The code could be made a bit more succinct by using the null coercion
operator.
---
 .../backend/src/remote/activitypub/misc/get-note-html.ts    | 6 ++----
 packages/backend/src/remote/activitypub/renderer/note.ts    | 6 +++---
 2 files changed, 5 insertions(+), 7 deletions(-)

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/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index e8d429e5de..b7df0e9a39 100644
--- a/packages/backend/src/remote/activitypub/renderer/note.ts
+++ b/packages/backend/src/remote/activitypub/renderer/note.ts
@@ -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;
+	// 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}`;

From 9954c054a7e9fa8148345c7ff3d0bfaaee05fcb1 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 4 Jun 2022 04:29:20 +0200
Subject: [PATCH 209/258] fix: ensure resolver does not fetch local resources
 via HTTP(S) (#8733)

* refactor: parseUri types and checks

The type has been refined to better represent what it actually is. Uses of
parseUri are now also checking the parsed object type before resolving.

* cannot resolve URLs with fragments

* also take remaining part of URL into account

Needed for parsing the follows URIs.

* Resolver uses DbResolver for local

* remove unnecessary use of DbResolver

Using DbResolver would mean that the URL is parsed and handled again.
This duplicated processing can be avoided by querying the database directly.

* fix missing property name
---
 .../src/remote/activitypub/db-resolver.ts     | 101 +++++++++---------
 .../src/remote/activitypub/resolver.ts        |  64 ++++++++++-
 2 files changed, 115 insertions(+), 50 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index ef07966e42..a9ed1a4a8d 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -13,6 +13,44 @@ import { uriPersonCache, userByIdCache } from '@/services/user-cache.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(url: string) : 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/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts
index 334eae9843..2f9af43c0c 100644
--- a/packages/backend/src/remote/activitypub/resolver.ts
+++ b/packages/backend/src/remote/activitypub/resolver.ts
@@ -3,9 +3,18 @@ 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 } from '@/misc/convert-host.js';
+import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
 import { signedGet } from './request.js';
 import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.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');
 		}
@@ -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`);
+		}
+	}
 }

From 32dff2846003bb079891593b660869511fca5f01 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 4 Jun 2022 06:52:42 +0200
Subject: [PATCH 210/258] fix: add id for activitypub follows (#8689)

* add id for activitypub follows

* fix lint

* fix: follower must be local, followee must be remote

Misskey will only use ActivityPub follow requests for users that are local
and are requesting to follow a remote user. This check is to ensure that
this endpoint can not be used by other services or instances.

* fix: missing import

* render block with id

* fix comment
---
 .../src/remote/activitypub/renderer/block.ts  | 24 +++++++++++----
 .../src/remote/activitypub/renderer/follow.ts |  3 +-
 packages/backend/src/server/activitypub.ts    | 29 ++++++++++++++++++-
 .../backend/src/services/blocking/create.ts   | 13 ++++++---
 .../backend/src/services/blocking/delete.ts   |  9 ++++--
 5 files changed, 63 insertions(+), 15 deletions(-)

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/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/server/activitypub.ts b/packages/backend/src/server/activitypub.ts
index a48c2d4122..cd5f917c40 100644
--- a/packages/backend/src/server/activitypub.ts
+++ b/packages/backend/src/server/activitypub.ts
@@ -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/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index b2be78b220..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);
 	}
 }
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);
 	}
 }

From e675ffcf38b07f5c70d00b49c171c7ab3460e810 Mon Sep 17 00:00:00 2001
From: Balazs Nadasdi <balazs@weave.works>
Date: Sat, 4 Jun 2022 06:57:09 +0200
Subject: [PATCH 211/258] feat: option to collapse long notes (#8561)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* feat: option to collapse long notes

Closes #8559

* do not collapse if cw exists

* use '閉じる' to close / show less.

* make it sticky

* Change style of the Show less button
---
 locales/ja-JP.yml                       |  1 +
 packages/client/src/components/note.vue | 27 ++++++++++++++++++++++---
 2 files changed, 25 insertions(+), 3 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 9cd1d1eedb..57be9bfcbb 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -52,6 +52,7 @@ searchUser: "ユーザーを検索"
 reply: "返信"
 loadMore: "もっと見る"
 showMore: "もっと見る"
+showLess: "閉じる"
 youGotNewFollower: "フォローされました"
 receiveFollowRequest: "フォローリクエストされました"
 followRequestAccepted: "フォローが承認されました"
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index bc8a0dd19d..4840b0dc2a 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -46,7 +46,7 @@
 					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
 					<XCwButton v-model="showContent" :note="appearNote"/>
 				</p>
-				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }">
+				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
 					<div class="text">
 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
@@ -66,9 +66,12 @@
 					<XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
 					<div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div>
-					<button v-if="collapsed" class="fade _button" @click="collapsed = false">
+					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
 						<span>{{ i18n.ts.showMore }}</span>
 					</button>
+					<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
+						<span>{{ i18n.ts.showLess }}</span>
+					</button>
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
@@ -166,7 +169,8 @@ const reactButton = ref<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
-const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
+const collapsed = ref(appearNote.cw == null);
+const isLong = ref(appearNote.cw == null && appearNote.text != null && (
 	(appearNote.text.split('\n').length > 9) ||
 	(appearNote.text.length > 500)
 ));
@@ -452,6 +456,23 @@ function readPromo() {
 				}
 
 				> .content {
+					&.isLong {
+						> .showLess {
+							width: 100%;
+							margin-top: 1em;
+							position: sticky;
+							bottom: 1em;
+
+							> span {
+								display: inline-block;
+								background: var(--panel);
+								padding: 6px 10px;
+								font-size: 0.8em;
+								border-radius: 999px;
+								box-shadow: 0 0 7px 7px var(--bg);
+							}
+						}
+					}
 					&.collapsed {
 						position: relative;
 						max-height: 9em;

From 702edfd3d3424a666091dd29dd5b4a9beaa822db Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 14:25:30 +0900
Subject: [PATCH 212/258] fix test

---
 packages/backend/test/activitypub.ts | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts
index 5d8b28ec7a..f4ae27e5ec 100644
--- a/packages/backend/test/activitypub.ts
+++ b/packages/backend/test/activitypub.ts
@@ -2,11 +2,13 @@ process.env.NODE_ENV = 'test';
 
 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', () => {

From 11afdf7e24549b66c000f4e1e598d82d92bc9f7e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 15:15:44 +0900
Subject: [PATCH 213/258] fix bug

---
 packages/backend/src/remote/activitypub/db-resolver.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index a9ed1a4a8d..1a02f675ca 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -5,10 +5,10 @@ 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);
@@ -29,7 +29,7 @@ export type UriParseResult = {
 	uri: string;
 };
 
-export function parseUri(url: string) : UriParseResult {
+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.

From 71150f21cd91df7bdd78a8f708db092418e85baa Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 15:23:53 +0900
Subject: [PATCH 214/258] Revert "feat: option to collapse long notes (#8561)"

This reverts commit e675ffcf38b07f5c70d00b49c171c7ab3460e810.
---
 locales/ja-JP.yml                       |  1 -
 packages/client/src/components/note.vue | 27 +++----------------------
 2 files changed, 3 insertions(+), 25 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 57be9bfcbb..9cd1d1eedb 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -52,7 +52,6 @@ searchUser: "ユーザーを検索"
 reply: "返信"
 loadMore: "もっと見る"
 showMore: "もっと見る"
-showLess: "閉じる"
 youGotNewFollower: "フォローされました"
 receiveFollowRequest: "フォローリクエストされました"
 followRequestAccepted: "フォローが承認されました"
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 4840b0dc2a..bc8a0dd19d 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -46,7 +46,7 @@
 					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
 					<XCwButton v-model="showContent" :note="appearNote"/>
 				</p>
-				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
+				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed }">
 					<div class="text">
 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="fas fa-reply"></i></MkA>
@@ -66,12 +66,9 @@
 					<XPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
 					<div v-if="appearNote.renote" class="renote"><XNoteSimple :note="appearNote.renote"/></div>
-					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
+					<button v-if="collapsed" class="fade _button" @click="collapsed = false">
 						<span>{{ i18n.ts.showMore }}</span>
 					</button>
-					<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
-						<span>{{ i18n.ts.showLess }}</span>
-					</button>
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="fas fa-satellite-dish"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
@@ -169,8 +166,7 @@ const reactButton = ref<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
-const collapsed = ref(appearNote.cw == null);
-const isLong = ref(appearNote.cw == null && appearNote.text != null && (
+const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
 	(appearNote.text.split('\n').length > 9) ||
 	(appearNote.text.length > 500)
 ));
@@ -456,23 +452,6 @@ function readPromo() {
 				}
 
 				> .content {
-					&.isLong {
-						> .showLess {
-							width: 100%;
-							margin-top: 1em;
-							position: sticky;
-							bottom: 1em;
-
-							> span {
-								display: inline-block;
-								background: var(--panel);
-								padding: 6px 10px;
-								font-size: 0.8em;
-								border-radius: 999px;
-								box-shadow: 0 0 7px 7px var(--bg);
-							}
-						}
-					}
 					&.collapsed {
 						position: relative;
 						max-height: 9em;

From bb3c6785c93084da5f56372ff6e5235877210620 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 15:47:10 +0900
Subject: [PATCH 215/258] Update CHANGELOG.md

---
 CHANGELOG.md | 44 +++++++++++++++++++++++++++++---------------
 1 file changed, 29 insertions(+), 15 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9687595010..299cbd0919 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,14 +14,17 @@ You should also include the user name that made the change.
 - From this version, Node 18.0.0 or later is required.
 
 ### Improvements
-- enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
-- enhance: API: notifications/readは配列でも受け付けるように #7667 @tamaina
-- enhance: プッシュ通知を複数アカウント対応に #7667 @tamaina
-- enhance: プッシュ通知にクリックやactionを設定 #7667 @tamaina
-- replaced webpack with Vite @tamaina
-- update dependencies @syuilo
-- enhance: display URL of QR code for TOTP registration @syuilo
-- enhance: Supports Unicode Emoji 14.0 @mei23
+- Supports Unicode Emoji 14.0 @mei23
+- プッシュ通知を複数アカウント対応に #7667 @tamaina
+- プッシュ通知にクリックやactionを設定 #7667 @tamaina
+- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
+- Server: always remove completed tasks of job queue @Johann150
+- Server: アンテナ、クリップ、リストの表示を速くする @xianonn
+- Client: make emoji stand out more on reaction button @Johann150
+- Client: display URL of QR code for TOTP registration @tamaina
+- API: notifications/readは配列でも受け付けるように #7667 @tamaina
+- API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
+- MFM: Allow speed changes in all animated MFMs @Johann150
 - The theme color is now better validated. @Johann150
   Your own theme color may be unset if it was in an invalid format.
   Admins should check their instance settings if in doubt.
@@ -30,20 +33,31 @@ You should also include the user name that made the change.
   Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
 
 ### Bugfixes
-- Client: fix settings page @tamaina
-- Client: fix profile tabs @futchitwo
+- Server: keep file order of note attachement @Johann150
+- Server: fix caching @Johann150
 - Server: await promises when following or unfollowing users @Johann150
-- Client: fix abuse reports page to be able to show all reports @Johann150
-- Federation: Add rel attribute to host-meta @mei23
-- Client: fix profile picture height in mentions @tamaina
-- MFM: more animated functions support `speed` parameter @futchitwo
-- Federation: Fix quote renotes containing no text being federated correctly @Johann150
 - Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
 - Server: fix internal in-memory caching @Johann150
 - Server: use correct order of attachments on notes @Johann150
 - Server: prevent crash when processing certain PNGs @syuilo
 - Server: Fix unable to generate video thumbnails @mei23
 - Server: Fix `Cannot find module` issue @mei23
+- Federation: Add rel attribute to host-meta @mei23
+- Federation: add id for activitypub follows @Johann150
+- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
+- Federation: correctly render empty note text @Johann150
+- Federation: Fix quote renotes containing no text being federated correctly @Johann150
+- Federation: remove duplicate br tag/newline @Johann150
+- Federation: add missing authorization checks @Johann150
+- Client: fix profile picture height in mentions @tamaina
+- Client: fix abuse reports page to be able to show all reports @Johann150
+- Client: fix settings page @tamaina
+- Client: fix profile tabs @futchitwo
+- Client: fix popout URL @futchitwo
+- Client: correctly handle MiAuth URLs with query string @sn0w
+- Client: ノート詳細ページの新しいノートを表示する機能の動作が正しくなるように修正する @xianonn
+- MFM: more animated functions support `speed` parameter @futchitwo
+- MFM: limit large MFM @Johann150
 
 ## 12.110.1 (2022/04/23)
 

From 0946d5091329815f49da3973f6925adad137777b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 16:01:11 +0900
Subject: [PATCH 216/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 135a3e140c..adbdb1cab0 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -71,13 +71,15 @@ For now, basically only @syuilo has the authority to merge PRs into develop beca
 However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
 
 ## Release
-For now, basically only @syuilo has the authority to release Misskey.
-However, in case of emergency, a release can be made at the discretion of a contributor.
-
 ### Release Instructions
-1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
-2. follow the `master` branch to the `develop` branch.
-3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
+2. Create a release PR.
+	- Into `master` from `develop` branch.
+	- The title must be in the format `Release: x.y.z`.
+		- `x.y.z` is the new version you are trying to release.
+	- Assign about 2~3 reviewers.
+3. The release PR is approved, merge it.
+4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
   - The target branch must be `master`
   - The tag name must be the version
 

From b62a050b2ccbb886ee75aac4bf9f6cb0af5c46be Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 16:01:27 +0900
Subject: [PATCH 217/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adbdb1cab0..f70e2df001 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,8 +80,8 @@ However, minor fixes, refactoring, and urgent changes may be merged at the discr
 	- Assign about 2~3 reviewers.
 3. The release PR is approved, merge it.
 4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
-  - The target branch must be `master`
-  - The tag name must be the version
+	- The target branch must be `master`
+	- The tag name must be the version
 
 ## Localization (l10n)
 Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.

From 7aae9987d5043f037a099c7b0e67c1152767ebfb Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 16:04:59 +0900
Subject: [PATCH 218/258] Update CHANGELOG.md

---
 CHANGELOG.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 299cbd0919..05158278d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,7 +19,6 @@ You should also include the user name that made the change.
 - プッシュ通知にクリックやactionを設定 #7667 @tamaina
 - ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
 - Server: always remove completed tasks of job queue @Johann150
-- Server: アンテナ、クリップ、リストの表示を速くする @xianonn
 - Client: make emoji stand out more on reaction button @Johann150
 - Client: display URL of QR code for TOTP registration @tamaina
 - API: notifications/readは配列でも受け付けるように #7667 @tamaina

From abcd5bc9515b47a579d1cc88003108418febb33b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 17:24:41 +0900
Subject: [PATCH 219/258] update summaly

---
 packages/backend/package.json | 2 +-
 packages/backend/yarn.lock    | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 4e0d60b74e..32e4cf201b 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -101,7 +101,7 @@
 		"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.15",
 		"tinycolor2": "1.4.2",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index d131f70e38..303843c346 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -6520,16 +6520,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"

From 89419c05b27d5419af75b3759bf62e2c4c3a29c3 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 4 Jun 2022 17:26:56 +0900
Subject: [PATCH 220/258] use node 16

---
 .node-version                                          | 2 +-
 CHANGELOG.md                                           | 3 ---
 packages/backend/src/models/repositories/drive-file.ts | 4 +++-
 packages/backend/src/server/web/manifest.ts            | 4 +++-
 packages/backend/src/services/relay.ts                 | 8 +++++---
 5 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/.node-version b/.node-version
index 658984787f..c9b6b29e00 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v18.0.0
+v16.0.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05158278d0..c58714fd25 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,6 @@ You should also include the user name that made the change.
 -->
 
 ## 12.x.x (unreleased)
-### NOTE
-- From this version, Node 18.0.0 or later is required.
-
 ### Improvements
 - Supports Unicode Emoji 14.0 @mei23
 - プッシュ通知を複数アカウント対応に #7667 @tamaina
diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index b626359d98..0d589d4f11 100644
--- a/packages/backend/src/models/repositories/drive-file.ts
+++ b/packages/backend/src/models/repositories/drive-file.ts
@@ -29,7 +29,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
 
 	getPublicProperties(file: DriveFile): DriveFile['properties'] {
 		if (file.properties.orientation != null) {
-			const properties = structuredClone(file.properties);
+			// 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];
 			}
diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts
index 61d7660066..ee568b8077 100644
--- a/packages/backend/src/server/web/manifest.ts
+++ b/packages/backend/src/server/web/manifest.ts
@@ -3,7 +3,9 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
 import manifest from './manifest.json' assert { type: 'json' };
 
 export const manifestHandler = async (ctx: Koa.Context) => {
-	const res = structuredClone(manifest);
+	// TODO
+	//const res = structuredClone(manifest);
+	const res = JSON.parse(JSON.stringify(manifest));
 
 	const instance = await fetchMeta(true);
 
diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts
index 08bf72cc26..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,7 +88,9 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
 	}));
 	if (relays.length === 0) return;
 
-	const copy = structuredClone(activity);
+	// TODO
+	//const copy = structuredClone(activity);
+	const copy = JSON.parse(JSON.stringify(activity));
 	if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
 
 	const signed = await attachLdSignature(copy, user);

From adf3190859191775d7056f000a3508aac9712dfa Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 5 Jun 2022 12:23:57 +0900
Subject: [PATCH 221/258] chore(client): fix menu item style

---
 packages/client/src/components/ui/menu.vue | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index ca56048262..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' : '' }"
@@ -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;
 		}

From 5fb3f8a1164e88d4cbff458e0aefb8754e5bb76f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 5 Jun 2022 12:26:36 +0900
Subject: [PATCH 222/258] chore: lint fixes

---
 .../client/src/components/modal-page-window.vue  | 16 ++++++++--------
 packages/client/src/components/note-detailed.vue | 16 ++++++++--------
 packages/client/src/components/note.vue          | 14 +++++++-------
 .../components/notification-setting-window.vue   | 15 ++++++++-------
 packages/client/src/components/notification.vue  |  9 +++++----
 packages/client/src/components/notifications.vue |  7 +++----
 packages/client/src/components/number-diff.vue   |  4 ++--
 7 files changed, 41 insertions(+), 40 deletions(-)

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 14bbbd4f3c..6234b710d2 100644
--- a/packages/client/src/components/note-detailed.vue
+++ b/packages/client/src/components/note-detailed.vue
@@ -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.vue b/packages/client/src/components/note.vue
index bc8a0dd19d..e5744d1ce9 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -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 3791c576ee..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"
@@ -74,10 +75,10 @@
 <script lang="ts">
 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();
 				});
diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue
index dc900a670d..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',
 		});
 	}
 };
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>
 

From 09b749eb97cfec32b06e0a31f51064f32584ebc8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 5 Jun 2022 19:46:52 +0900
Subject: [PATCH 223/258] Update .mocharc.json

---
 packages/backend/.mocharc.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json
index 589522216d..87c571cfd6 100644
--- a/packages/backend/.mocharc.json
+++ b/packages/backend/.mocharc.json
@@ -5,6 +5,6 @@
 		"loader=./test/loader.js"
 	],
 	"slow": 1000,
-	"timeout": 3000,
+	"timeout": 10000,
 	"exit": true
 }

From d17298d3b5b5fb7377ad34fe7c2b8613bbf478de Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 5 Jun 2022 20:37:24 +0900
Subject: [PATCH 224/258] fix(test): make chart tests working

---
 packages/backend/src/db/postgre.ts            | 10 +-
 .../backend/src/services/chart/entities.ts    | 12 +++
 packages/backend/test/chart.ts                | 96 +++++++++----------
 3 files changed, 65 insertions(+), 53 deletions(-)

diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index 50a85d6267..298f6713ea 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -208,7 +208,15 @@ 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 {
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/test/chart.ts b/packages/backend/test/chart.ts
index 823e388a82..ac0844679f 100644
--- a/packages/backend/test/chart.ts
+++ b/packages/backend/test/chart.ts
@@ -6,26 +6,17 @@ 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 { async, initTestDb } from './utils.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();
@@ -34,14 +25,15 @@ describe('Chart', () => {
 
 		clock = lolex.install({
 			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();
 
@@ -63,9 +55,9 @@ describe('Chart', () => {
 				total: [1, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can updates (dec)', async(async () => {
+	it('Can updates (dec)', async () => {
 		await testChart.decrement();
 		await testChart.save();
 
@@ -87,9 +79,9 @@ describe('Chart', () => {
 				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);
 
@@ -108,9 +100,9 @@ describe('Chart', () => {
 				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();
@@ -134,9 +126,9 @@ describe('Chart', () => {
 				total: [3, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('複数回saveされてもデータの更新は一度だけ', async(async () => {
+	it('複数回saveされてもデータの更新は一度だけ', async () => {
 		await testChart.increment();
 		await testChart.save();
 		await testChart.save();
@@ -160,9 +152,9 @@ describe('Chart', () => {
 				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();
 
@@ -189,11 +181,11 @@ describe('Chart', () => {
 				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();
 
@@ -249,10 +241,10 @@ describe('Chart', () => {
 				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();
 
@@ -276,11 +268,11 @@ describe('Chart', () => {
 				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();
 
@@ -307,9 +299,9 @@ describe('Chart', () => {
 				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can specify offset', async(async () => {
+	it('Can specify offset', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -336,9 +328,9 @@ describe('Chart', () => {
 				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();
@@ -367,10 +359,10 @@ describe('Chart', () => {
 				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
 	describe('Grouped', () => {
-		it('Can updates', async(async () => {
+		it('Can updates', async () => {
 			await testGroupedChart.increment('alice');
 			await testGroupedChart.save();
 
@@ -410,11 +402,11 @@ describe('Chart', () => {
 					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();
@@ -504,9 +496,9 @@ describe('Chart', () => {
 					total: [1, 0, 0],
 				},
 			});
-		}));
+		});
 
-		it('Can resync (2)', async(async () => {
+		it('Can resync (2)', async () => {
 			await testChart.increment();
 			await testChart.save();
 
@@ -534,6 +526,6 @@ describe('Chart', () => {
 					total: [100, 0, 0],
 				},
 			});
-		}));
+		});
 	});
 });

From aea2f01ef7909885cf20df9bc2d27bb77863b35b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 6 Jun 2022 21:01:00 +0900
Subject: [PATCH 225/258] Update .node-version

---
 .node-version | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.node-version b/.node-version
index c9b6b29e00..7fd023741b 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v16.0.0
+v16.15.0

From 0fa2a52facaeab21ea51e4fbb8d441a3f68f0239 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 8 Jun 2022 10:59:48 +0200
Subject: [PATCH 226/258] refactor: use awaitAll to reduce duplication (#8791)

* refactor: use awaitAll to reduce duplication

* fix lint

* fix typo
---
 .../src/server/api/endpoints/users/stats.ts   | 86 ++++++-------------
 1 file changed, 25 insertions(+), 61 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts
index d138019a72..59283e4f2a 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -1,6 +1,7 @@
 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'],
@@ -31,109 +32,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),
+	});
 
-	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,
-	};
+	result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
+	result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+
+	return result;
 });

From 4800dd06e5576e29c28c683ce20b001b78f12da4 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Wed, 8 Jun 2022 13:20:37 +0200
Subject: [PATCH 227/258] fix: try to prevent autocomplete for emoji search
 (#8798)

---
 packages/client/src/components/emoji-picker.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue
index 522f636474..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">

From 3dba63afbbff8278bb53147e0900b9c324feba01 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Wed, 8 Jun 2022 22:23:43 +0900
Subject: [PATCH 228/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f70e2df001..3c17e61207 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -77,9 +77,9 @@ However, minor fixes, refactoring, and urgent changes may be merged at the discr
 	- Into `master` from `develop` branch.
 	- The title must be in the format `Release: x.y.z`.
 		- `x.y.z` is the new version you are trying to release.
-	- Assign about 2~3 reviewers.
-3. The release PR is approved, merge it.
-4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+3. ~~Deploy and perform a simple QA check. Also verify that the tests passed.~~ (TODO)
+4. Merge it.
+5. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
 	- The target branch must be `master`
 	- The tag name must be the version
 

From 327c62337e3bc63616153bbe5512ff235be9fc7e Mon Sep 17 00:00:00 2001
From: Takuya Yoshida <hawaiianphoto@geekhost.net>
Date: Thu, 9 Jun 2022 00:50:23 +0900
Subject: [PATCH 229/258] ok-to-test with okteto (#8799)

---
 .github/workflows/ok-to-test.yml          | 36 ++++++++++++
 .github/workflows/pr-preview-deploy.yml   | 70 +++++++++++++++++++++++
 .github/workflows/pr-preview-destroy.yml  | 21 +++++++
 okteto.yml => .okteto/okteto-pipeline.yml |  0
 4 files changed, 127 insertions(+)
 create mode 100644 .github/workflows/ok-to-test.yml
 create mode 100644 .github/workflows/pr-preview-deploy.yml
 create mode 100644 .github/workflows/pr-preview-destroy.yml
 rename okteto.yml => .okteto/okteto-pipeline.yml (100%)

diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml
new file mode 100644
index 0000000000..63ae05cbda
--- /dev/null
+++ b/.github/workflows/ok-to-test.yml
@@ -0,0 +1,36 @@
+# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
+name: Ok To Test
+
+on:
+  issue_comment:
+    types: [created]
+
+jobs:
+  ok-to-test:
+    runs-on: ubuntu-latest
+    # Only run for PRs, not issue comments
+    if: ${{ github.event.issue.pull_request }}
+    steps:
+    # Generate a GitHub App installation access token from an App ID and private key
+    # To create a new GitHub App:
+    #   https://developer.github.com/apps/building-github-apps/creating-a-github-app/
+    # See app.yml for an example app manifest
+    - name: Generate token
+      id: generate_token
+      uses: tibdex/github-app-token@v1
+      with:
+        app_id: ${{ secrets.APP_ID }}
+        private_key: ${{ secrets.PRIVATE_KEY }}
+
+    - name: Slash Command Dispatch
+      uses: peter-evans/slash-command-dispatch@v1
+      env:
+        TOKEN: ${{ steps.generate_token.outputs.token }}
+      with:
+        token: ${{ env.TOKEN }} # GitHub App installation access token
+        # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
+        reaction-token: ${{ secrets.GITHUB_TOKEN }}
+        issue-type: pull-request
+        commands: ok-to-test
+        named-args: true
+        permission: write
diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
new file mode 100644
index 0000000000..5890065764
--- /dev/null
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -0,0 +1,70 @@
+# Run secret-dependent integration tests only after /ok-to-test approval
+on:
+  repository_dispatch:
+    types: [ok-to-test-command]
+
+name: Deploy preview environment
+
+jobs:
+  # Repo owner has commented /ok-to-test on a (fork-based) pull request
+  deploy-preview-environment:
+    runs-on: ubuntu-latest
+    if:
+      github.event_name == 'repository_dispatch' &&
+      github.event.client_payload.slash_command.sha != '' &&
+      contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
+    steps:
+
+    # Check out merge commit
+    - name: Fork based /ok-to-test checkout
+      uses: actions/checkout@v2
+      with:
+        ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
+
+    # <insert integration tests needing secrets>
+    - name: Context
+      uses: okteto/context@latest
+      with:
+        token: ${{ secrets.OKTETO_TOKEN }}
+
+    - name: Deploy preview environment
+      uses: ikuradon/deploy-preview@latest
+      env:
+       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        name: pr-${{ github.event.client_payload.pull_request.number }}-misskey-dev
+        timeout: 15m
+
+    # Update check run called "integration-fork"
+    - uses: actions/github-script@v5
+      id: update-check-run
+      if: ${{ always() }}
+      env:
+        number: ${{ github.event.client_payload.pull_request.number }}
+        job: ${{ github.job }}
+        # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
+        conclusion: ${{ job.status }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          const { data: pull } = await github.rest.pulls.get({
+            ...context.repo,
+            pull_number: process.env.number
+          });
+          const ref = pull.head.sha;
+
+          const { data: checks } = await github.rest.checks.listForRef({
+            ...context.repo,
+            ref
+          });
+
+          const check = checks.check_runs.filter(c => c.name === process.env.job);
+
+          const { data: result } = await github.rest.checks.update({
+            ...context.repo,
+            check_run_id: check[0].id,
+            status: 'completed',
+            conclusion: process.env.conclusion
+          });
+
+          return result;
diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml
new file mode 100644
index 0000000000..fa872715da
--- /dev/null
+++ b/.github/workflows/pr-preview-destroy.yml
@@ -0,0 +1,21 @@
+# file: .github/workflows/preview-closed.yaml
+on:
+  pull_request:
+    types:
+      - closed
+
+name: Destroy preview environment
+
+jobs:
+  destroy-preview-environment:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Context
+        uses: okteto/context@latest
+        with:
+          token: ${{ secrets.OKTETO_TOKEN }}
+
+      - name: Destroy preview environment
+        uses: okteto/destroy-preview@latest
+        with:
+          name: pr-${{ github.event.number }}-misskey-dev
diff --git a/okteto.yml b/.okteto/okteto-pipeline.yml
similarity index 100%
rename from okteto.yml
rename to .okteto/okteto-pipeline.yml

From 2d6de2299c74b872c5c36b0e34b936c865689260 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 9 Jun 2022 01:35:57 +0900
Subject: [PATCH 230/258] chore(dev): update okteto workflow

---
 .github/workflows/ok-to-test.yml        | 6 +++---
 .github/workflows/pr-preview-deploy.yml | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml
index 63ae05cbda..87af3a6ba6 100644
--- a/.github/workflows/ok-to-test.yml
+++ b/.github/workflows/ok-to-test.yml
@@ -19,8 +19,8 @@ jobs:
       id: generate_token
       uses: tibdex/github-app-token@v1
       with:
-        app_id: ${{ secrets.APP_ID }}
-        private_key: ${{ secrets.PRIVATE_KEY }}
+        app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
+        private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
 
     - name: Slash Command Dispatch
       uses: peter-evans/slash-command-dispatch@v1
@@ -31,6 +31,6 @@ jobs:
         # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
         reaction-token: ${{ secrets.GITHUB_TOKEN }}
         issue-type: pull-request
-        commands: ok-to-test
+        commands: deploy
         named-args: true
         permission: write
diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
index 5890065764..53f670652d 100644
--- a/.github/workflows/pr-preview-deploy.yml
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -30,7 +30,7 @@ jobs:
     - name: Deploy preview environment
       uses: ikuradon/deploy-preview@latest
       env:
-       GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       with:
         name: pr-${{ github.event.client_payload.pull_request.number }}-misskey-dev
         timeout: 15m

From d8eb610aaba10174b6e1d6204e775b6032086bd8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 9 Jun 2022 01:43:35 +0900
Subject: [PATCH 231/258] Update pr-preview-deploy.yml

---
 .github/workflows/pr-preview-deploy.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
index 53f670652d..126665850f 100644
--- a/.github/workflows/pr-preview-deploy.yml
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -1,12 +1,12 @@
-# Run secret-dependent integration tests only after /ok-to-test approval
+# Run secret-dependent integration tests only after /deploy approval
 on:
   repository_dispatch:
-    types: [ok-to-test-command]
+    types: [deploy-command]
 
 name: Deploy preview environment
 
 jobs:
-  # Repo owner has commented /ok-to-test on a (fork-based) pull request
+  # Repo owner has commented /deploy on a (fork-based) pull request
   deploy-preview-environment:
     runs-on: ubuntu-latest
     if:
@@ -16,7 +16,7 @@ jobs:
     steps:
 
     # Check out merge commit
-    - name: Fork based /ok-to-test checkout
+    - name: Fork based /deploy checkout
       uses: actions/checkout@v2
       with:
         ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'

From 79de4d77f6a403b0ca6b32d82bb213a7f627416f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 9 Jun 2022 01:48:00 +0900
Subject: [PATCH 232/258] chore(dev): update okteto workflow

---
 .github/workflows/pr-preview-deploy.yml  | 2 +-
 .github/workflows/pr-preview-destroy.yml | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
index 126665850f..1b399264db 100644
--- a/.github/workflows/pr-preview-deploy.yml
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -32,7 +32,7 @@ jobs:
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       with:
-        name: pr-${{ github.event.client_payload.pull_request.number }}-misskey-dev
+        name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo
         timeout: 15m
 
     # Update check run called "integration-fork"
diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml
index fa872715da..c14c3db5c5 100644
--- a/.github/workflows/pr-preview-destroy.yml
+++ b/.github/workflows/pr-preview-destroy.yml
@@ -18,4 +18,4 @@ jobs:
       - name: Destroy preview environment
         uses: okteto/destroy-preview@latest
         with:
-          name: pr-${{ github.event.number }}-misskey-dev
+          name: pr-${{ github.event.number }}-syuilo

From 065aa0f9b6f9a12c67dd2c0cfe338a78575b244b Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Thu, 9 Jun 2022 05:49:00 +0900
Subject: [PATCH 233/258] Display the deploy status on checks (#8803)

* Display deploy status on check suite

* Display deploy status on check suite

* fix

* fix
---
 .github/workflows/pr-preview-deploy.yml | 57 ++++++++++++++++++-------
 1 file changed, 41 insertions(+), 16 deletions(-)

diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
index 1b399264db..fd43bce9e6 100644
--- a/.github/workflows/pr-preview-deploy.yml
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -1,5 +1,7 @@
 # Run secret-dependent integration tests only after /deploy approval
 on:
+  pull_request:
+    types: [opened, reopened, synchronize]
   repository_dispatch:
     types: [deploy-command]
 
@@ -14,6 +16,43 @@ jobs:
       github.event.client_payload.slash_command.sha != '' &&
       contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
     steps:
+    - uses: actions/github-script@v5
+      id: check-id
+      env:
+        number: ${{ github.event.client_payload.pull_request.number }}
+        job: ${{ github.job }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        result-encoding: string
+        script: |
+          const { data: pull } = await github.rest.pulls.get({
+            ...context.repo,
+            pull_number: process.env.number
+          });
+          const ref = pull.head.sha;
+
+          const { data: checks } = await github.rest.checks.listForRef({
+            ...context.repo,
+            ref
+          });
+
+          const check = checks.check_runs.filter(c => c.name === process.env.job);
+
+          return check[0].id;
+
+    - uses: actions/github-script@v5
+      env:
+        check_id: ${{ steps.check-id.outputs.result }}
+        details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          await github.rest.checks.update({
+            ...context.repo,
+            check_run_id: process.env.check_id,
+            status: 'in_progress',
+            details_url: process.env.details_url
+          });
 
     # Check out merge commit
     - name: Fork based /deploy checkout
@@ -40,29 +79,15 @@ jobs:
       id: update-check-run
       if: ${{ always() }}
       env:
-        number: ${{ github.event.client_payload.pull_request.number }}
-        job: ${{ github.job }}
         # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
         conclusion: ${{ job.status }}
+        check_id: ${{ steps.check-id.outputs.result }}
       with:
         github-token: ${{ secrets.GITHUB_TOKEN }}
         script: |
-          const { data: pull } = await github.rest.pulls.get({
-            ...context.repo,
-            pull_number: process.env.number
-          });
-          const ref = pull.head.sha;
-
-          const { data: checks } = await github.rest.checks.listForRef({
-            ...context.repo,
-            ref
-          });
-
-          const check = checks.check_runs.filter(c => c.name === process.env.job);
-
           const { data: result } = await github.rest.checks.update({
             ...context.repo,
-            check_run_id: check[0].id,
+            check_run_id: process.env.check_id,
             status: 'completed',
             conclusion: process.env.conclusion
           });

From c6e0430aa7e05bf302af0c558aaeb26a6635a76b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 9 Jun 2022 05:55:58 +0900
Subject: [PATCH 234/258] Update CONTRIBUTING.md

---
 CONTRIBUTING.md | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3c17e61207..a37df3bdee 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -66,6 +66,13 @@ Be willing to comment on the good points and not just the things you want fixed
 	- Are there any omissions or gaps?
 	- Does it check for anomalies?
 
+## Deploy
+The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment.
+```
+/deploy sha=<commit hash>
+```
+An actual domain will be assigned so you can test the federation.
+
 ## Merge
 For now, basically only @syuilo has the authority to merge PRs into develop because he is most familiar with the codebase.
 However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
@@ -77,7 +84,7 @@ However, minor fixes, refactoring, and urgent changes may be merged at the discr
 	- Into `master` from `develop` branch.
 	- The title must be in the format `Release: x.y.z`.
 		- `x.y.z` is the new version you are trying to release.
-3. ~~Deploy and perform a simple QA check. Also verify that the tests passed.~~ (TODO)
+3. Deploy and perform a simple QA check. Also verify that the tests passed.
 4. Merge it.
 5. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
 	- The target branch must be `master`

From eec7a0a1f6d31c2d669fcdb29596ab69d70d3635 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 9 Jun 2022 16:35:58 +0200
Subject: [PATCH 235/258] properly display alt text for videos (#8802)

The alt text is already properly federated and given by the API,
it is just not displayed properly by the client.
---
 packages/client/src/components/media-video.vue | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

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

From b66e73aed51365a0a09c22249269ee7d815c7822 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Thu, 9 Jun 2022 16:42:00 +0200
Subject: [PATCH 236/258] fix: use autocomplete=new-password (#8797)

---
 packages/client/src/components/signup.vue | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index 58c15d81b1..ec5be60a2a 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>

From 7dde0b136235d8ea8474c5045611626bd769afa1 Mon Sep 17 00:00:00 2001
From: Andreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>
Date: Thu, 9 Jun 2022 16:45:16 +0200
Subject: [PATCH 237/258] fix(client): render quote renote CWs as MFM (#8792)

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
---
 packages/client/src/components/note-simple.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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">

From 527f0440626586e9a601375a45620e2549fcff7f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Thu, 9 Jun 2022 23:47:26 +0900
Subject: [PATCH 238/258] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c58714fd25..713251ff40 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,7 @@ You should also include the user name that made the change.
 - Server: always remove completed tasks of job queue @Johann150
 - Client: make emoji stand out more on reaction button @Johann150
 - Client: display URL of QR code for TOTP registration @tamaina
+- Client: render quote renote CWs as MFM @pixeldesu
 - API: notifications/readは配列でも受け付けるように #7667 @tamaina
 - API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
 - MFM: Allow speed changes in all animated MFMs @Johann150

From 78df3dc484f5dd1601ee35d5735d4074d5b298e6 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 10 Jun 2022 07:25:20 +0200
Subject: [PATCH 239/258] enhance: improve documentation for `/users/`
 endpoints (#8790)

* docs: category & description for reset password

* docs: category & description for testing

* docs: descriptions for groups endpoints

* docs: descriptions for drive file endpoints

* docs: descriptions for sw endpoints

* docs: descriptions for user list endpoints

* docs: descriptions & result type for gallery posts

* docs: descriptions & result type for user endpoints

* docs: add return type for stats
---
 .../endpoints/drive/files/attached-notes.ts   |  2 +
 .../endpoints/drive/files/check-existence.ts  |  2 +
 .../api/endpoints/drive/files/create.ts       |  2 +
 .../api/endpoints/drive/files/delete.ts       |  2 +
 .../api/endpoints/drive/files/find-by-hash.ts |  2 +
 .../server/api/endpoints/drive/files/find.ts  |  2 +
 .../server/api/endpoints/drive/files/show.ts  |  2 +
 .../api/endpoints/drive/files/update.ts       |  2 +
 .../endpoints/drive/files/upload-from-url.ts  |  2 +
 .../api/endpoints/request-reset-password.ts   |  4 +
 .../src/server/api/endpoints/reset-db.ts      |  4 +
 .../server/api/endpoints/reset-password.ts    |  4 +
 .../src/server/api/endpoints/sw/register.ts   |  2 +
 .../src/server/api/endpoints/sw/unregister.ts |  2 +
 .../backend/src/server/api/endpoints/test.ts  |  4 +
 .../src/server/api/endpoints/users/clips.ts   | 12 +++
 .../server/api/endpoints/users/followers.ts   |  2 +
 .../server/api/endpoints/users/following.ts   |  2 +
 .../api/endpoints/users/gallery/posts.ts      | 12 +++
 .../users/get-frequently-replied-users.ts     |  2 +
 .../api/endpoints/users/groups/create.ts      |  2 +
 .../api/endpoints/users/groups/delete.ts      |  2 +
 .../users/groups/invitations/accept.ts        |  2 +
 .../users/groups/invitations/reject.ts        |  2 +
 .../api/endpoints/users/groups/invite.ts      |  2 +
 .../api/endpoints/users/groups/joined.ts      |  2 +
 .../api/endpoints/users/groups/leave.ts       |  2 +
 .../api/endpoints/users/groups/owned.ts       |  2 +
 .../server/api/endpoints/users/groups/pull.ts |  2 +
 .../server/api/endpoints/users/groups/show.ts |  2 +
 .../api/endpoints/users/groups/transfer.ts    |  2 +
 .../api/endpoints/users/groups/update.ts      |  2 +
 .../api/endpoints/users/lists/create.ts       |  2 +
 .../api/endpoints/users/lists/delete.ts       |  2 +
 .../server/api/endpoints/users/lists/list.ts  |  2 +
 .../server/api/endpoints/users/lists/pull.ts  |  2 +
 .../server/api/endpoints/users/lists/push.ts  |  2 +
 .../server/api/endpoints/users/lists/show.ts  |  2 +
 .../api/endpoints/users/lists/update.ts       |  2 +
 .../src/server/api/endpoints/users/notes.ts   |  2 +
 .../src/server/api/endpoints/users/pages.ts   | 12 +++
 .../server/api/endpoints/users/reactions.ts   |  2 +
 .../api/endpoints/users/recommendation.ts     |  2 +
 .../server/api/endpoints/users/relation.ts    |  2 +
 .../api/endpoints/users/report-abuse.ts       |  2 +
 .../users/search-by-username-and-host.ts      |  2 +
 .../src/server/api/endpoints/users/search.ts  |  2 +
 .../src/server/api/endpoints/users/show.ts    |  2 +
 .../src/server/api/endpoints/users/stats.ts   | 90 +++++++++++++++++++
 49 files changed, 224 insertions(+)

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 0b74cb9f01..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
@@ -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 fb19345fee..2c604c54c8 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -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,
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/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 f93d4f718b..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,
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 59283e4f2a..d17e8b64b5 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Show statistics about a user.',
+
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -15,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 = {

From 42f48ffea23247339df076657d84bd7997e6047a Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 10 Jun 2022 07:29:46 +0200
Subject: [PATCH 240/258] refactor: follow button (#8789)

* fix: display cancelling follow request

* remove unnecessary branch

The executed code is the same as in the else branch so this special
condition is unnecessary.

* remove code duplication

Use the same callback as later for updating these variables.

* use $ref sugar

* remove unused import

Co-authored-by: blackskye-sx <saul.newman@gmail.com>
---
 .../client/src/components/follow-button.vue   | 34 ++++++++-----------
 1 file changed, 14 insertions(+), 20 deletions(-)

diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue
index b3540bc316..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;
+		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 (err) {
 		console.error(err);
 	} finally {
-		wait.value = false;
+		wait = false;
 	}
 }
 

From a683a7092dfe932ff7645ae08360124e5f0a232d Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 10 Jun 2022 07:31:58 +0200
Subject: [PATCH 241/258] enhance(federation): use ActivityPub defined property
 in favour of proprietary property. (#8787)

* add activitypub `source` property

* parse MFM from new `source` attribute
---
 packages/backend/src/remote/activitypub/models/note.ts |  9 ++++++++-
 .../backend/src/remote/activitypub/renderer/note.ts    |  4 ++++
 packages/backend/src/remote/activitypub/type.ts        | 10 ++++++++--
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index ad24bbcd65..56c1a483ad 100644
--- a/packages/backend/src/remote/activitypub/models/note.ts
+++ b/packages/backend/src/remote/activitypub/models/note.ts
@@ -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/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index b7df0e9a39..df2ae65205 100644
--- a/packages/backend/src/remote/activitypub/renderer/note.ts
+++ b/packages/backend/src/remote/activitypub/renderer/note.ts
@@ -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(),
diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts
index ef5b98b59e..5d00481b75 100644
--- a/packages/backend/src/remote/activitypub/type.ts
+++ b/packages/backend/src/remote/activitypub/type.ts
@@ -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[];

From 5e29528ad4d75174b4a8ebd462f0ff830c5b1c43 Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Fri, 10 Jun 2022 07:36:55 +0200
Subject: [PATCH 242/258] chore: fix some lints automatically (#8788)

* chore: fix some lints automatically

Fixed lints that were automatically fixable with `eslint --fix`.

* fix type

* workaround for empty interface lint
---
 packages/client/.eslintrc.js                           |  6 ++++++
 packages/client/src/components/captcha.vue             |  3 +--
 packages/client/src/components/drive.folder.vue        |  4 ++--
 packages/client/src/components/drive.vue               |  8 ++++----
 packages/client/src/components/form/folder.vue         |  2 +-
 packages/client/src/components/form/radios.vue         |  2 +-
 packages/client/src/components/page/page.post.vue      |  2 +-
 packages/client/src/components/post-form-attaches.vue  | 10 +++++-----
 packages/client/src/components/post-form.vue           |  2 +-
 packages/client/src/components/queue-chart.vue         |  2 +-
 packages/client/src/components/sample.vue              |  2 +-
 packages/client/src/components/signin.vue              |  4 ++--
 packages/client/src/components/signup.vue              |  2 +-
 packages/client/src/components/ui/button.vue           | 10 +++++-----
 packages/client/src/components/ui/modal.vue            |  2 +-
 packages/client/src/components/ui/tooltip.vue          | 10 +++++-----
 packages/client/src/components/url-preview.vue         |  2 +-
 packages/client/src/directives/adaptive-border.ts      |  2 +-
 packages/client/src/directives/get-size.ts             |  6 +++---
 packages/client/src/directives/panel.ts                |  2 +-
 packages/client/src/directives/size.ts                 |  4 ++--
 packages/client/src/os.ts                              |  2 +-
 packages/client/src/pages/admin/emojis.vue             |  2 +-
 packages/client/src/pages/admin/metrics.vue            |  2 +-
 packages/client/src/pages/admin/queue.vue              |  4 ++--
 packages/client/src/pages/auth.form.vue                |  2 +-
 packages/client/src/pages/emojis.category.vue          |  2 +-
 packages/client/src/pages/federation.vue               |  2 +-
 packages/client/src/pages/gallery/edit.vue             |  2 +-
 packages/client/src/pages/messaging/index.vue          |  4 ++--
 .../client/src/pages/messaging/messaging-room.form.vue |  2 +-
 packages/client/src/pages/mfm-cheat-sheet.vue          |  2 +-
 packages/client/src/pages/my-antennas/index.vue        |  2 +-
 packages/client/src/pages/page-editor/page-editor.vue  |  2 +-
 packages/client/src/pages/scratchpad.vue               |  2 +-
 packages/client/src/pages/settings/2fa.vue             |  2 +-
 packages/client/src/pages/settings/accounts.vue        |  2 +-
 packages/client/src/pages/settings/apps.vue            |  2 +-
 packages/client/src/pages/settings/theme.vue           |  4 ++--
 packages/client/src/pages/settings/word-mute.vue       |  2 +-
 packages/client/src/pages/share.vue                    |  2 +-
 packages/client/src/pages/theme-editor.vue             |  2 +-
 packages/client/src/pages/timeline.vue                 |  2 +-
 packages/client/src/pages/welcome.setup.vue            |  2 +-
 packages/client/src/pages/welcome.timeline.vue         |  2 +-
 packages/client/src/scripts/format-time-string.ts      |  6 +++---
 packages/client/src/scripts/get-note-menu.ts           |  2 +-
 packages/client/src/scripts/get-user-menu.ts           |  2 +-
 packages/client/src/scripts/hpml/evaluator.ts          |  2 +-
 packages/client/src/scripts/physics.ts                 |  6 +++---
 packages/client/src/scripts/theme-editor.ts            |  2 +-
 packages/client/src/scripts/theme.ts                   |  2 +-
 packages/client/src/store.ts                           |  2 +-
 packages/client/src/ui/_common_/sidebar-for-mobile.vue |  2 +-
 packages/client/src/ui/classic.sidebar.vue             |  2 +-
 packages/client/src/ui/deck/column.vue                 |  1 -
 packages/client/src/widgets/timeline.vue               |  8 ++++----
 packages/client/src/widgets/widget.ts                  |  2 +-
 58 files changed, 92 insertions(+), 88 deletions(-)

diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js
index 1c2ab0a427..10f0e5a9cb 100644
--- a/packages/client/.eslintrc.js
+++ b/packages/client/.eslintrc.js
@@ -15,6 +15,12 @@ module.exports = {
 		'plugin:vue/vue3-recommended',
 	],
 	rules: {
+		'@typescript-eslint/no-empty-interface': [
+			'error',
+			{
+				'allowSingleExtends': true,
+			},
+		],
 		// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
 		// data の禁止理由: 抽象的すぎるため
 		// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
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/drive.folder.vue b/packages/client/src/components/drive.folder.vue
index d530f8beff..3ccb5d6219 100644
--- a/packages/client/src/components/drive.folder.vue
+++ b/packages/client/src/components/drive.folder.vue
@@ -71,7 +71,7 @@ function onMouseover() {
 }
 
 function onMouseout() {
-	hover.value = false
+	hover.value = false;
 }
 
 function onDragover(ev: DragEvent) {
@@ -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',
diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue
index 42ec3a5995..6c2c8acad0 100644
--- a/packages/client/src/components/drive.vue
+++ b/packages/client/src/components/drive.vue
@@ -143,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));
 
@@ -332,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',
@@ -607,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);
 		});
 	}
 
@@ -628,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/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/page/page.post.vue b/packages/client/src/components/page/page.post.vue
index 8ac8c46692..3401f945bd 100644
--- a/packages/client/src/components/page/page.post.vue
+++ b/packages/client/src/components/page/page.post.vue
@@ -66,7 +66,7 @@ export default defineComponent({
 					.then(response => response.json())
 					.then(f => {
 						ok(f);
-					})
+					});
 				});
 			});
 			os.promiseDialog(promise);
diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue
index 3807769118..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({
@@ -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 64ee873fd7..0197313e0e 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -442,7 +442,7 @@ function onCompositionEnd(ev: CompositionEvent) {
 }
 
 async function onPaste(ev: ClipboardEvent) {
-	for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({item, i}))) {
+	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('.');
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/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.vue b/packages/client/src/components/signin.vue
index be87274020..b772d1479b 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -159,7 +159,7 @@ function queryKey() {
 
 function onSubmit() {
 	signing = true;
-	console.log('submit')
+	console.log('submit');
 	if (!totpLogin && user && user.twoFactorEnabled) {
 		if (window.PublicKeyCredential && user.securityKeys) {
 			os.api('signin', {
@@ -222,7 +222,7 @@ function loginFailed(err) {
 			break;
 		}
 		default: {
-			console.log(err)
+			console.log(err);
 			os.alert({
 				type: 'error',
 				title: i18n.ts.loginFailed,
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index ec5be60a2a..3f2af306e5 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -111,7 +111,7 @@ export default defineComponent({
 			ToSAgreement: false,
 			hCaptchaResponse: null,
 			reCaptchaResponse: null,
-		}
+		};
 	},
 
 	computed: {
diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue
index fe8f1c7cca..e6b20d9881 100644
--- a/packages/client/src/components/ui/button.vue
+++ b/packages/client/src/components/ui/button.vue
@@ -96,11 +96,11 @@ export default defineComponent({
 			}
 
 			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;
 			}
 
diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue
index 1e4159055e..010262da2f 100644
--- a/packages/client/src/components/ui/modal.vue
+++ b/packages/client/src/components/ui/modal.vue
@@ -234,7 +234,7 @@ onMounted(() => {
 		}
 		fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
 
-		await nextTick()
+		await nextTick();
 		
 		align();
 	}, { immediate: true, });
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/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/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/os.ts b/packages/client/src/os.ts
index 6baf538917..4f19fadf19 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -285,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;
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index 38bcc41ea0..8ca5b3d65c 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -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);
 };
 
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/queue.vue b/packages/client/src/pages/admin/queue.vue
index e05098082a..656b18199f 100644
--- a/packages/client/src/pages/admin/queue.vue
+++ b/packages/client/src/pages/admin/queue.vue
@@ -20,7 +20,7 @@ import * as symbols from '@/symbols';
 import * as config from '@/config';
 import { i18n } from '@/i18n';
 
-const connection = markRaw(stream.useChannel('queueStats'))
+const connection = markRaw(stream.useChannel('queueStats'));
 
 function clear() {
 	os.confirm({
@@ -41,7 +41,7 @@ onMounted(() => {
 			length: 200
 		});
 	});
-})
+});
 
 onBeforeUnmount(() => {
 	connection.dispose();
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/emojis.category.vue b/packages/client/src/pages/emojis.category.vue
index 1be004cf51..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: {
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/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index a0c2d1a596..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: {
diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue
index 61c8bb0ce3..7c1d3e3cbe 100644
--- a/packages/client/src/pages/messaging/index.vue
+++ b/packages/client/src/pages/messaging/index.vue
@@ -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 ad8aaae6b7..8e779c4f39 100644
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ b/packages/client/src/pages/messaging/messaging-room.form.vue
@@ -200,7 +200,7 @@ export default defineComponent({
 					text: this.text,
 					file: this.file
 				}
-			}
+			};
 
 			localStorage.setItem('message_drafts', JSON.stringify(drafts));
 		},
diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index aa35ec2158..2c10494ede 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -341,7 +341,7 @@ export default defineComponent({
 			preview_rainbow: `$[rainbow 🍮] $[rainbow.speed=5s 🍮]`,
 			preview_sparkle: `$[sparkle 🍮]`,
 			preview_rotate: `$[rotate 🍮]`,
-		}
+		};
 	},
 });
 </script>
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index 9f1e01f11d..a568f64c52 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -32,7 +32,7 @@ defineExpose({
 		icon: 'fas fa-satellite',
 		bg: 'var(--bg)'
 	}
-})
+});
 </script>
 
 <style lang="scss" scoped>
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/scratchpad.vue b/packages/client/src/pages/scratchpad.vue
index eb91938db2..34a41b81a5 100644
--- a/packages/client/src/pages/scratchpad.vue
+++ b/packages/client/src/pages/scratchpad.vue
@@ -100,7 +100,7 @@ async function run() {
 			text: error.message
 		});
 	}
-};
+}
 
 function highlighter(code) {
 	return highlight(code, languages.js, 'javascript');
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index be464f040d..fb3a7a17f3 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -142,7 +142,7 @@ function registerKey() {
 		registration.value = null;
 		key.lastUsed = new Date();
 		os.success();
-	})
+	});
 }
 
 function unregisterKey(key) {
diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index ecb2d036f2..5e75639c55 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -45,7 +45,7 @@ const init = async () => {
 		accounts.value = response;
 		console.log(accounts.value);
 	});
-}
+};
 
 function menu(account, ev) {
 	os.popupMenu([{
diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue
index f3b251d9b2..7b0b5548d5 100644
--- a/packages/client/src/pages/settings/apps.vue
+++ b/packages/client/src/pages/settings/apps.vue
@@ -52,7 +52,7 @@ const pagination = {
 	params: {
 		sort: '+lastUsedAt'
 	}
-}
+};
 
 function revoke(token) {
 	os.api('i/revoke-token', { tokenId: token.id }).then(() => {
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index b32aa237fe..5e7ffcff4b 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -120,7 +120,7 @@ const darkThemeId = computed({
 		return darkTheme.value.id;
 	},
 	set(id) {
-		ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id))
+		ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id));
 	}
 });
 const lightTheme = ColdDeviceStorage.ref('lightTheme');
@@ -129,7 +129,7 @@ const lightThemeId = computed({
 		return lightTheme.value.id;
 	},
 	set(id) {
-		ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id))
+		ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id));
 	}
 });
 const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index 48fcb362b9..6e1a4b2ccb 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -75,7 +75,7 @@ async function save() {
 
 		// check each line if it is a RegExp or not
 		for (let i = 0; i < lines.length; i++) {
-			const line = lines[i]
+			const line = lines[i];
 			const regexp = line.match(/^\/(.+)\/(.*)$/);
 			if (regexp) {
 				// check that the RegExp is valid
diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue
index b08ac2b237..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() {
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index b7b537cead..2a11c07fd2 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -68,7 +68,7 @@
 import { watch } from 'vue';
 import { toUnicode } from 'punycode/';
 import tinycolor from 'tinycolor2';
-import { v4 as uuid} from 'uuid';
+import { v4 as uuid } from 'uuid';
 import JSON5 from 'json5';
 
 import FormButton from '@/components/ui/button.vue';
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/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/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-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts
index aeb09ef97a..78749ad6bb 100644
--- a/packages/client/src/scripts/get-note-menu.ts
+++ b/packages/client/src/scripts/get-note-menu.ts
@@ -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({
diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts
index 1d2b761117..091338efd6 100644
--- a/packages/client/src/scripts/get-user-menu.ts
+++ b/packages/client/src/scripts/get-user-menu.ts
@@ -148,7 +148,7 @@ export function getUserMenu(user) {
 			userId: user.id
 		}).then(() => {
 			user.isFollowed = !user.isFollowed;
-		})
+		});
 	}
 
 	let menu = [{
diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts
index 0469a31cbb..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({
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/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 e2b272405a..dec9fb355c 100644
--- a/packages/client/src/scripts/theme.ts
+++ b/packages/client/src/scripts/theme.ts
@@ -42,7 +42,7 @@ export const getBuiltinThemesRef = () => {
 	const builtinThemes = ref<Theme[]>([]);
 	getBuiltinThemes().then(themes => builtinThemes.value = themes);
 	return builtinThemes;
-}
+};
 
 let timeout = null;
 
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index 6ab14f43fd..deee23951e 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -256,7 +256,7 @@ type Plugin = {
  * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
  */
 import lightTheme from '@/themes/l-light.json5';
-import darkTheme from '@/themes/d-dark.json5'
+import darkTheme from '@/themes/d-dark.json5';
 
 export class ColdDeviceStorage {
 	public static default = {
diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue
index 064a63bf29..41d0837233 100644
--- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue
+++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue
@@ -61,7 +61,7 @@ export default defineComponent({
 			otherMenuItemIndicated,
 			post: os.post,
 			search,
-			openAccountMenu:(ev) => {
+			openAccountMenu: (ev) => {
 				openAccountMenu({
 					withExtraOperation: true,
 				}, ev);
diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue
index ad11c3ebd5..6c0ce023e4 100644
--- a/packages/client/src/ui/classic.sidebar.vue
+++ b/packages/client/src/ui/classic.sidebar.vue
@@ -126,7 +126,7 @@ export default defineComponent({
 			}, {}, 'closed');
 		},
 
-		openAccountMenu:(ev) => {
+		openAccountMenu: (ev) => {
 			openAccountMenu({
 				withExtraOperation: true,
 			}, ev);
diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue
index 31063a753d..6db3549fbb 100644
--- a/packages/client/src/ui/deck/column.vue
+++ b/packages/client/src/ui/deck/column.vue
@@ -94,7 +94,6 @@ onBeforeUnmount(() => {
 	os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd);
 });
 
-
 function onOtherDragStart() {
 	dropready = true;
 }
diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue
index 408cf2cbea..3bcad1ae29 100644
--- a/packages/client/src/widgets/timeline.vue
+++ b/packages/client/src/widgets/timeline.vue
@@ -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/widget.ts b/packages/client/src/widgets/widget.ts
index db164c2bc3..9626d01619 100644
--- a/packages/client/src/widgets/widget.ts
+++ b/packages/client/src/widgets/widget.ts
@@ -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 () => {

From f5ba73e7c85ff03894aafa9d86a2ef513711dcf9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 10 Jun 2022 14:56:05 +0900
Subject: [PATCH 243/258] chore: tweak logo

---
 packages/backend/src/server/web/views/base.pug | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 230ed1578a..5bb156f0f4 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -7,17 +7,15 @@ doctype html
 
 //
 	-
-
 	  _____ _         _           
 	 |     |_|___ ___| |_ ___ _ _ 
-	 | | | | |_ -|_ -| \'_| -_| | |
+	 | | | | |_ -|_ -| '_| -_| | |
 	 |_|_|_|_|___|___|_,_|___|_  |
 	                         |___|
 	 Thank you for using Misskey!
 	 If you are reading this message... how about joining the development?
 	 https://github.com/misskey-dev/misskey
 	 
-	 
 
 html
 

From ec6b418a2323797076ba1320efbf11cc3b1df8d3 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Fri, 10 Jun 2022 15:06:42 +0900
Subject: [PATCH 244/258] update deps

---
 package.json                  |   6 +-
 packages/backend/package.json |  36 ++-
 packages/backend/yarn.lock    | 400 +++++++++++++---------------------
 packages/client/package.json  |  32 +--
 packages/client/yarn.lock     | 326 +++++++++++++--------------
 yarn.lock                     | 138 ++++++------
 6 files changed, 428 insertions(+), 510 deletions(-)

diff --git a/package.json b/package.json
index 850a52ceb6..a938465777 100644
--- a/package.json
+++ b/package.json
@@ -41,10 +41,10 @@
 	"devDependencies": {
 		"@types/gulp": "4.0.9",
 		"@types/gulp-rename": "2.0.1",
-		"@typescript-eslint/parser": "5.18.0",
+		"@typescript-eslint/parser": "5.27.1",
 		"cross-env": "7.0.3",
-		"cypress": "9.5.3",
+		"cypress": "10.0.3",
 		"start-server-and-test": "1.14.0",
-		"typescript": "4.6.3"
+		"typescript": "4.7.3"
 	}
 }
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 32e4cf201b..2186dcc6a9 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -14,7 +14,7 @@
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
-		"@bull-board/koa": "3.10.4",
+		"@bull-board/koa": "3.11.1",
 		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.11.0",
 		"@koa/cors": "3.1.0",
@@ -28,10 +28,9 @@
 		"archiver": "5.3.1",
 		"autobind-decorator": "2.4.0",
 		"autwh": "0.1.0",
-		"aws-sdk": "2.1135.0",
+		"aws-sdk": "2.1152.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.12.0",
 		"bull": "4.8.3",
 		"cacheable-lookup": "6.0.4",
 		"cbor": "8.1.0",
@@ -44,18 +43,18 @@
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
 		"feed": "4.2.2",
-		"file-type": "17.1.1",
+		"file-type": "17.1.2",
 		"fluent-ffmpeg": "2.1.2",
-		"got": "12.0.4",
+		"got": "12.1.0",
 		"hpagent": "0.1.2",
-		"ip-cidr": "3.0.8",
+		"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": "10.5.22",
+		"jsonld": "6.0.0",
+		"jsrsasign": "10.5.24",
 		"koa": "2.13.4",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
@@ -72,7 +71,7 @@
 		"ms": "3.0.0-canary.1",
 		"multer": "1.4.4",
 		"nested-property": "4.0.0",
-		"node-fetch": "3.2.4",
+		"node-fetch": "3.2.6",
 		"nodemailer": "6.7.5",
 		"os-utils": "0.0.14",
 		"parse5": "6.0.1",
@@ -103,12 +102,12 @@
 		"style-loader": "3.3.1",
 		"summaly": "2.5.1",
 		"syslog-pro": "1.0.0",
-		"systeminformation": "5.11.15",
+		"systeminformation": "5.11.16",
 		"tinycolor2": "1.4.2",
 		"tmp": "0.2.1",
 		"ts-loader": "9.3.0",
-		"ts-node": "10.8.0",
-		"tsc-alias": "1.6.7",
+		"ts-node": "10.8.1",
+		"tsc-alias": "1.6.9",
 		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
 		"typeorm": "0.3.6",
@@ -117,7 +116,7 @@
 		"uuid": "8.3.2",
 		"web-push": "3.5.0",
 		"websocket": "1.0.34",
-		"ws": "8.6.0",
+		"ws": "8.8.0",
 		"xev": "3.0.2"
 	},
 	"devDependencies": {
@@ -145,7 +144,7 @@
 		"@types/koa__multer": "2.0.4",
 		"@types/koa__router": "8.0.11",
 		"@types/mocha": "9.1.1",
-		"@types/node": "17.0.35",
+		"@types/node": "17.0.41",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.4",
 		"@types/oauth": "0.9.1",
@@ -167,12 +166,11 @@
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
-		"@typescript-eslint/eslint-plugin": "5.26.0",
-		"@typescript-eslint/parser": "5.26.0",
-		"typescript": "4.7.2",
-		"eslint": "8.16.0",
+		"@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/yarn.lock b/packages/backend/yarn.lock
index 303843c346..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,33 +21,33 @@
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
-"@bull-board/api@3.10.4":
-  version "3.10.4"
-  resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.4.tgz#f29d95a9624224ceec0f3ff26ef2c2bba8106921"
-  integrity sha512-JJjMg8O/ELeaqkuL1Wsdn6rdQfH+/2+BfnFD0B7j4ZCtLVAPfsOUZYpLqSKUgaNizwp1nTw0e3L/EI0yvX5aiw==
+"@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.4":
-  version "3.10.4"
-  resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.4.tgz#8e54600bfd8e003a8d5838ae6e65f9ec8c9979f7"
-  integrity sha512-NO0kzgVrl5lGNnX6maBAuP6aecGvROGka3RJSALubDfsrQ3aWNuY2BjUMUvm4ZDVfAeYT3wPaak8rdRCwxYE2g==
+"@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.4"
-    "@bull-board/ui" "3.10.4"
-    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.4":
-  version "3.10.4"
-  resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.4.tgz#6455b4e75fdbec1bc2ee84fde2a6a283b3c77bc9"
-  integrity sha512-nqnE3wqqpso7ORPcmcGVesYeFkHwv3AsBdRV2W0VLtfBPGzMdqZ1sJeSTAmlanFZnvTprU4Eg/G0DcEeMUTGhA==
+"@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.4"
+    "@bull-board/api" "3.11.1"
 
 "@cspotcode/source-map-support@^0.8.0":
   version "0.8.1"
@@ -75,14 +61,14 @@
   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@14.0.2":
   version "14.0.2"
@@ -693,10 +679,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
   integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA==
 
-"@types/node@17.0.35":
-  version "17.0.35"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a"
-  integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
+"@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"
@@ -872,14 +858,14 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2"
-  integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA==
+"@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.26.0"
-    "@typescript-eslint/type-utils" "5.26.0"
-    "@typescript-eslint/utils" "5.26.0"
+    "@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.2.0"
@@ -887,69 +873,69 @@
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2"
-  integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q==
+"@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.26.0"
-    "@typescript-eslint/types" "5.26.0"
-    "@typescript-eslint/typescript-estree" "5.26.0"
+    "@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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339"
-  integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==
+"@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.26.0"
-    "@typescript-eslint/visitor-keys" "5.26.0"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
 
-"@typescript-eslint/type-utils@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013"
-  integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A==
+"@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.26.0"
+    "@typescript-eslint/utils" "5.27.1"
     debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3"
-  integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==
+"@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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3"
-  integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==
+"@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.26.0"
-    "@typescript-eslint/visitor-keys" "5.26.0"
+    "@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.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4"
-  integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==
+"@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.26.0"
-    "@typescript-eslint/types" "5.26.0"
-    "@typescript-eslint/typescript-estree" "5.26.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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57"
-  integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==
+"@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.26.0"
+    "@typescript-eslint/types" "5.27.1"
     eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -1288,10 +1274,10 @@ autwh@0.1.0:
   dependencies:
     oauth "0.9.15"
 
-aws-sdk@2.1135.0:
-  version "2.1135.0"
-  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1135.0.tgz#8c14aa6894be529cb5fb7b6d19f3dc70e4f35816"
-  integrity sha512-bl9n4QgrEh52hmQ+Jo76BgJXM/p+PwfVZvImEQHFeel/33H/PDLcTJquEw5bzxM1HRNI24iH+FNPwyWLMrttTw==
+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"
@@ -1300,7 +1286,7 @@ aws-sdk@2.1135.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:
@@ -1428,19 +1414,6 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-broadcast-channel@4.12.0:
-  version "4.12.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.12.0.tgz#891876c5262376ab714b33a0d9e9d87a894b5bcb"
-  integrity sha512-hfb0L2P2CEsdM5nSqlRiZ2gQFHPdJNs1VU4rTLnFPYtq5vQAnyOdjIx+04KCWfFfRhfP3OEbxxQmnouSi8WCbQ==
-  dependencies:
-    "@babel/runtime" "^7.16.0"
-    detect-node "^2.1.0"
-    microtime "3.0.0"
-    oblivious-set "1.1.1"
-    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"
@@ -2097,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"
@@ -2289,11 +2257,6 @@ detect-libc@^2.0.0:
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204"
   integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw==
 
-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"
@@ -2483,10 +2446,10 @@ 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.7"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.7.tgz#c544d9c7f715783dd92f0bddcf73a59e6962d006"
-  integrity sha512-BIar7R6abbUxDA3bfXrO4DSgwo8I+fB5/1zgujl3HLLjwd6+9iOnrT+t3grn2qbk9vOgBubXOFwX2m9axoFaGw==
+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.8.5"
 
@@ -2718,10 +2681,10 @@ eslint-visitor-keys@^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.16.0:
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae"
-  integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==
+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.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
@@ -2759,11 +2722,6 @@ eslint@8.16.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.2:
   version "9.3.2"
   resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
@@ -2817,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==
@@ -2938,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"
@@ -2958,10 +2911,10 @@ 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"
@@ -3290,10 +3243,10 @@ got@11.5.1:
     p-cancelable "^2.0.0"
     responselike "^2.0.0"
 
-got@12.0.4:
-  version "12.0.4"
-  resolved "https://registry.yarnpkg.com/got/-/got-12.0.4.tgz#e3b6bf6992425f904076fd71aac7030da5122de8"
-  integrity sha512-2Eyz4iU/ktq7wtMFXxzK7g5p35uNYLLdiZarZ5/Yn3IJlNEpBd5+dCgcAyxN8/8guZLszffwe3wVyw+DEVrpBg==
+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"
@@ -3653,10 +3606,10 @@ ip-address@^7.1.0:
     jsbn "1.1.0"
     sprintf-js "1.1.2"
 
-ip-cidr@3.0.8:
-  version "3.0.8"
-  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.8.tgz#51876484109e4aa200b86b3dc32f24a1d8986429"
-  integrity sha512-DLrHwoFNuLVNulwoQuHLdkIED1Hyo9iB0MB8XzZdfie23b5bonJXVB5aCyVbbvXYOmQrw3nZDcjnSO7MMYgjJg==
+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"
@@ -4093,12 +4046,12 @@ 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"
@@ -4113,10 +4066,10 @@ jsprim@^1.2.2:
     json-schema "0.4.0"
     verror "1.10.0"
 
-jsrsasign@10.5.22:
-  version "10.5.22"
-  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.22.tgz#a702fb591e634767ca3296ce7a212f92974df17c"
-  integrity sha512-exqUDmWKOCUK4fT79z/Fi2qGV4c+WCPjHrtJ/lVUUrrbBwJ5T5HppCcalGf3tuOlmyNdyMZ074r1bqPOUNl4Uw==
+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"
@@ -4309,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"
@@ -4589,14 +4542,6 @@ micromatch@^4.0.4:
     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"
@@ -4902,11 +4847,6 @@ node-abi@^3.3.0:
   dependencies:
     semver "^7.3.5"
 
-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-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"
@@ -4926,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.4:
-  version "3.2.4"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.4.tgz#3fbca2d8838111048232de54cb532bd3cf134947"
-  integrity sha512-WvYJRN7mMyOLurFR2YpysQGuwYrJN+qrrpHjJDuKMcSPdfFccRUla/kng2mz6HWSBxJcqPbvatS6Gb4RhOzCJw==
+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"
@@ -4950,11 +4882,6 @@ node-fetch@^2.6.1:
   dependencies:
     whatwg-url "^5.0.0"
 
-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@^4.2.3:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
@@ -5111,11 +5038,6 @@ object.values@^1.1.5:
     define-properties "^1.1.3"
     es-abstract "^1.19.1"
 
-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==
-
 on-finished@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -5263,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"
@@ -5946,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"
@@ -6047,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==
@@ -6575,10 +6484,10 @@ syslog-pro@1.0.0:
   dependencies:
     moment "^2.22.2"
 
-systeminformation@5.11.15:
-  version "5.11.15"
-  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.15.tgz#013038688e7ba375a5c8e88b8e7739bda7110e6b"
-  integrity sha512-zUbObRjQeZcu84z9NVSm9JTiCPyPQ3MefJ3+76yvp+TeCv9WsO3szijyQLv0fChRrm2/sl2De3y1ewUOYOtz2Q==
+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"
@@ -6746,10 +6655,10 @@ ts-loader@9.3.0:
     micromatch "^4.0.0"
     semver "^7.3.4"
 
-ts-node@10.8.0:
-  version "10.8.0"
-  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.0.tgz#3ceb5ac3e67ae8025c1950626aafbdecb55d82ce"
-  integrity sha512-/fNd5Qh+zTt8Vt1KbYZjRHCE9sI5i7nqfD/dzBBRDeVXZXS6kToW6R7tTU6Nd4XavFs0mAVCg29Q//ML7WsZYA==
+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.8.0"
     "@tsconfig/node10" "^1.0.7"
@@ -6765,10 +6674,10 @@ ts-node@10.8.0:
     v8-compile-cache-lib "^3.0.1"
     yn "3.1.1"
 
-tsc-alias@1.6.7:
-  version "1.6.7"
-  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.7.tgz#354840d6444db79dd13fcc4f9ec37574ff9d5120"
-  integrity sha512-GRbZx/zTee01JtrHB7hkddgxn+aQqYDmRaFv/MTYIqBbk/L8Zf0nA/T60wXOr/Q7002YXppUFXsqsu5ViWB4vQ==
+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.3"
     commander "^9.0.0"
@@ -6912,10 +6821,10 @@ typeorm@0.3.6:
     xml2js "^0.4.23"
     yargs "^17.3.1"
 
-typescript@4.7.2:
-  version "4.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
-  integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
+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"
@@ -6932,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"
@@ -6951,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"
@@ -7012,16 +6918,16 @@ 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"
@@ -7224,10 +7130,10 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-ws@8.6.0:
-  version "8.6.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
-  integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
+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"
diff --git a/packages/client/package.json b/packages/client/package.json
index a4c6638565..689cd81b21 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -18,7 +18,7 @@
 		"autosize": "5.0.1",
 		"autwh": "0.1.0",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.12.0",
+		"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",
@@ -52,34 +52,34 @@
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
-		"sass": "1.52.1",
+		"sass": "1.52.3",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
-		"three": "0.140.2",
+		"three": "0.141.0",
 		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.4.2",
-		"tsc-alias": "1.6.7",
+		"tsc-alias": "1.6.9",
 		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vue": "3.2.36",
+		"vue": "3.2.37",
 		"vue-prism-editor": "2.0.0-alpha.2",
-		"vue-router": "4.0.15",
+		"vue-router": "4.0.16",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
 		"@vitejs/plugin-vue": "2.3.3",
-		"@vue/compiler-sfc": "3.2.36",
+		"@vue/compiler-sfc": "3.2.37",
 		"@rollup/plugin-alias": "3.1.9",
 		"@rollup/plugin-json": "4.1.0",
-		"rollup": "2.74.1",
-		"typescript": "4.7.2",
-		"vite": "2.9.9",
-		"ws": "8.6.0"
+		"rollup": "2.75.6",
+		"typescript": "4.7.3",
+		"vite": "2.9.10",
+		"ws": "8.8.0"
 	},
 	"devDependencies": {
 		"@types/escape-regexp": "0.0.1",
@@ -100,12 +100,12 @@
 		"@types/uuid": "8.3.4",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.3",
-		"@typescript-eslint/eslint-plugin": "5.26.0",
-		"@typescript-eslint/parser": "5.26.0",
-		"eslint": "8.16.0",
-		"eslint-plugin-vue": "9.0.1",
+		"@typescript-eslint/eslint-plugin": "5.27.1",
+		"@typescript-eslint/parser": "5.27.1",
+		"eslint": "8.17.0",
+		"eslint-plugin-vue": "9.1.0",
 		"cross-env": "7.0.3",
-		"cypress": "9.7.0",
+		"cypress": "10.0.3",
 		"eslint-plugin-import": "2.26.0",
 		"start-server-and-test": "1.14.0"
 	}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 63ab2edc96..31e7053a0f 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -416,14 +416,14 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.26.0.tgz#c1f98ccba9d345e38992975d3ca56ed6260643c2"
-  integrity sha512-oGCmo0PqnRZZndr+KwvvAUvD3kNE4AfyoGCwOZpoCncSh4MVD06JTE8XQa2u9u+NX5CsyZMBTEc2C72zx38eYA==
+"@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.26.0"
-    "@typescript-eslint/type-utils" "5.26.0"
-    "@typescript-eslint/utils" "5.26.0"
+    "@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.2.0"
@@ -431,69 +431,69 @@
     semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.26.0.tgz#a61b14205fe2ab7533deb4d35e604add9a4ceee2"
-  integrity sha512-n/IzU87ttzIdnAH5vQ4BBDnLPly7rC5VnjN3m0xBG82HK6rhRxnCb3w/GyWbNDghPd+NktJqB/wl6+YkzZ5T5Q==
+"@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.26.0"
-    "@typescript-eslint/types" "5.26.0"
-    "@typescript-eslint/typescript-estree" "5.26.0"
+    "@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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.26.0.tgz#44209c7f649d1a120f0717e0e82da856e9871339"
-  integrity sha512-gVzTJUESuTwiju/7NiTb4c5oqod8xt5GhMbExKsCTp6adU3mya6AGJ4Pl9xC7x2DX9UYFsjImC0mA62BCY22Iw==
+"@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.26.0"
-    "@typescript-eslint/visitor-keys" "5.26.0"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
 
-"@typescript-eslint/type-utils@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.26.0.tgz#937dee97702361744a3815c58991acf078230013"
-  integrity sha512-7ccbUVWGLmcRDSA1+ADkDBl5fP87EJt0fnijsMFTVHXKGduYMgienC/i3QwoVhDADUAPoytgjbZbCOMj4TY55A==
+"@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.26.0"
+    "@typescript-eslint/utils" "5.27.1"
     debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.26.0.tgz#cb204bb154d3c103d9cc4d225f311b08219469f3"
-  integrity sha512-8794JZFE1RN4XaExLWLI2oSXsVImNkl79PzTOOWt9h0UHROwJedNOD2IJyfL0NbddFllcktGIO2aOu10avQQyA==
+"@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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.26.0.tgz#16cbceedb0011c2ed4f607255f3ee1e6e43b88c3"
-  integrity sha512-EyGpw6eQDsfD6jIqmXP3rU5oHScZ51tL/cZgFbFBvWuCwrIptl+oueUZzSmLtxFuSOQ9vDcJIs+279gnJkfd1w==
+"@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.26.0"
-    "@typescript-eslint/visitor-keys" "5.26.0"
+    "@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.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.26.0.tgz#896b8480eb124096e99c8b240460bb4298afcfb4"
-  integrity sha512-PJFwcTq2Pt4AMOKfe3zQOdez6InIDOjUJJD3v3LyEtxHGVVRK3Vo7Dd923t/4M9hSH2q2CLvcTdxlLPjcIk3eg==
+"@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.26.0"
-    "@typescript-eslint/types" "5.26.0"
-    "@typescript-eslint/typescript-estree" "5.26.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.26.0":
-  version "5.26.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.26.0.tgz#7195f756e367f789c0e83035297c45b417b57f57"
-  integrity sha512-wei+ffqHanYDOQgg/fS6Hcar6wAWv0CUPQ3TZzOWd2BLfgP539rb49bwua8WRAs7R6kOSLn82rfEu2ro6Llt8Q==
+"@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.26.0"
+    "@typescript-eslint/types" "5.27.1"
     eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -506,100 +506,100 @@
   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.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.36.tgz#2fa44595308c95610602df54dcb69063ba2c8383"
-  integrity sha512-bbyZM5hvBicv0PW3KUfVi+x3ylHnfKG7DOn5wM+f2OztTzTjLEyBb/5yrarIYpmnGitVGbjZqDbODyW4iK8hqw==
+"@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.36"
+    "@vue/shared" "3.2.37"
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
-"@vue/compiler-dom@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.36.tgz#16d911ff163ed5fc8087a01645bf14bb7f325401"
-  integrity sha512-tcOTAOiW4s24QLnq+ON6J+GRONXJ+A/mqKCORi0LSlIh8XQlNnlm24y8xIL8la+ZDgkdbjarQ9ZqYSvEja6gVA==
+"@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.36"
-    "@vue/shared" "3.2.36"
+    "@vue/compiler-core" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/compiler-sfc@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.36.tgz#e5065e7c0e5170ffa750e3c3dd93a29db109d0f2"
-  integrity sha512-AvGb4bTj4W8uQ4BqaSxo7UwTEqX5utdRSMyHy58OragWlt8nEACQ9mIeQh3K4di4/SX+41+pJrLIY01lHAOFOA==
+"@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.36"
-    "@vue/compiler-dom" "3.2.36"
-    "@vue/compiler-ssr" "3.2.36"
-    "@vue/reactivity-transform" "3.2.36"
-    "@vue/shared" "3.2.36"
+    "@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.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.36.tgz#314f3a9424db58142c3608f48cbda7aa05fc66cb"
-  integrity sha512-+KugInUFRvOxEdLkZwE+W43BqHyhBh0jpYXhmqw1xGq2dmE6J9eZ8UUSOKNhdHtQ/iNLWWeK/wPZkVLUf3YGaw==
+"@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.36"
-    "@vue/shared" "3.2.36"
+    "@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.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.36.tgz#8426a941b0b09d1b94fc162d4642758183b5d133"
-  integrity sha512-Jk5o2BhpODC9XTA7o4EL8hSJ4JyrFWErLtClG3NH8wDS7ri9jBDWxI7/549T7JY9uilKsaNM+4pJASLj5dtRwA==
+"@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.36"
-    "@vue/shared" "3.2.36"
+    "@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.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.36.tgz#026b14e716febffe80cd284fd8a2b33378968646"
-  integrity sha512-c2qvopo0crh9A4GXi2/2kfGYMxsJW4tVILrqRPydVGZHhq0fnzy6qmclWOhBFckEhmyxmpHpdJtIRYGeKcuhnA==
+"@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.36"
+    "@vue/shared" "3.2.37"
 
-"@vue/runtime-core@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.36.tgz#be5115e665679c26bf3807d2326675dc1d847134"
-  integrity sha512-PTWBD+Lub+1U3/KhbCExrfxyS14hstLX+cBboxVHaz+kXoiDLNDEYAovPtxeTutbqtClIXtft+wcGdC+FUQ9qQ==
+"@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.36"
-    "@vue/shared" "3.2.36"
+    "@vue/reactivity" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/runtime-dom@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.36.tgz#cd5d403ea23c18ee7c17767103a1b2f8263c54bb"
-  integrity sha512-gYPYblm7QXHVuBohqNRRT7Wez0f2Mx2D40rb4fleehrJU9CnkjG0phhcGEZFfGwCmHZRqBCRgbFWE98bPULqkg==
+"@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.36"
-    "@vue/shared" "3.2.36"
+    "@vue/runtime-core" "3.2.37"
+    "@vue/shared" "3.2.37"
     csstype "^2.6.8"
 
-"@vue/server-renderer@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.36.tgz#1e7c1cf63bd17df7828d04e8c780ee6ca7a9ed7c"
-  integrity sha512-uZE0+jfye6yYXWvAQYeHZv+f50sRryvy16uiqzk3jn8hEY8zTjI+rzlmZSGoE915k+W/Ol9XSw6vxOUD8dGkUg==
+"@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:
-    "@vue/compiler-ssr" "3.2.36"
-    "@vue/shared" "3.2.36"
+    "@vue/compiler-ssr" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/shared@3.2.36":
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.36.tgz#35e11200542cf29068ba787dad57da9bdb82f644"
-  integrity sha512-JtB41wXl7Au3+Nl3gD16Cfpj7k/6aCroZ6BbOiCMFCMvrOpkg/qQUXTso2XowaNqBbnkuGHurLAqkLBxNGc1hQ==
+"@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"
@@ -856,10 +856,10 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-broadcast-channel@4.12.0:
-  version "4.12.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.12.0.tgz#891876c5262376ab714b33a0d9e9d87a894b5bcb"
-  integrity sha512-hfb0L2P2CEsdM5nSqlRiZ2gQFHPdJNs1VU4rTLnFPYtq5vQAnyOdjIx+04KCWfFfRhfP3OEbxxQmnouSi8WCbQ==
+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"
@@ -1158,10 +1158,10 @@ csstype@^2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
 
-cypress@9.7.0:
-  version "9.7.0"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.7.0.tgz#bf55b2afd481f7a113ef5604aa8b693564b5e744"
-  integrity sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==
+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"
@@ -1613,10 +1613,10 @@ eslint-plugin-import@2.26.0:
     resolve "^1.22.0"
     tsconfig-paths "^3.14.1"
 
-eslint-plugin-vue@9.0.1:
-  version "9.0.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.0.1.tgz#66ba4a6e4085a26a724adcde06eaf72b178285c9"
-  integrity sha512-/w/9/vzz+4bSYtp5UqXgJ0CfycXTMtpp6lkz7/fMp0CcJxPWyRP6Pr88ihhrsNEcVt2ZweMupWRNYa+5Md41LQ==
+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"
@@ -1659,10 +1659,10 @@ eslint-visitor-keys@^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.16.0:
-  version "8.16.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.16.0.tgz#6d936e2d524599f2a86c708483b4c372c5d3bbae"
-  integrity sha512-MBndsoXY/PeVTDJeWsYj7kLZ5hQpJOfMYLsF6LicLHQWbRDG19lK5jOix4DPl8yY4SUFcE3txy86OzFLWT+yoA==
+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.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
@@ -3517,10 +3517,10 @@ rndstr@1.0.0:
     rangestr "0.0.1"
     seedrandom "2.4.2"
 
-rollup@2.74.1:
-  version "2.74.1"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.74.1.tgz#4fba0ff1c312cc4ee82691b154eee69a0d01929f"
-  integrity sha512-K2zW7kV8Voua5eGkbnBtWYfMIhYhT9Pel2uhBk2WO5eMee161nPze/XRfvEQPFYz7KgrCCnmh2Wy0AMFLGGmMA==
+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"
 
@@ -3575,10 +3575,10 @@ 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@1.52.1:
-  version "1.52.1"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.1.tgz#554693da808543031f9423911d62c60a1acf7889"
-  integrity sha512-fSzYTbr7z8oQnVJ3Acp9hV80dM1fkMN7mSD/25mpcct9F7FPBMOI8krEYALgU1aZoqGhQNhTPsuSmxjnIvAm4Q==
+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"
@@ -3843,10 +3843,10 @@ 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.140.2:
-  version "0.140.2"
-  resolved "https://registry.yarnpkg.com/three/-/three-0.140.2.tgz#ca0b7390d1ce4f7f2850e80afd00ef48fc244491"
-  integrity sha512-DdT/AHm/TbZXEhQKQpGt5/iSgBrmXpjU26FNtj1KhllVPTKj1eG4X/ShyD5W2fngE+I1s1wa4ttC4C3oCJt7Ag==
+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@5.0.0:
   version "5.0.0"
@@ -3900,10 +3900,10 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
-tsc-alias@1.6.7:
-  version "1.6.7"
-  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.7.tgz#354840d6444db79dd13fcc4f9ec37574ff9d5120"
-  integrity sha512-GRbZx/zTee01JtrHB7hkddgxn+aQqYDmRaFv/MTYIqBbk/L8Zf0nA/T60wXOr/Q7002YXppUFXsqsu5ViWB4vQ==
+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.3"
     commander "^9.0.0"
@@ -3999,10 +3999,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@4.7.2:
-  version "4.7.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
-  integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
+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"
@@ -4090,10 +4090,10 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
-vite@2.9.9:
-  version "2.9.9"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.9.tgz#8b558987db5e60fedec2f4b003b73164cb081c5e"
-  integrity sha512-ffaam+NgHfbEmfw/Vuh6BHKKlI/XIAhxE5QSS7gFLIngxg171mg1P3a4LSRME0z2ZU1ScxoKzphkipcYwSD5Ew==
+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"
@@ -4125,23 +4125,23 @@ vue-prism-editor@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.15:
-  version "4.0.15"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.15.tgz#b4a0661efe197f8c724e0f233308f8776e2c3667"
-  integrity sha512-xa+pIN9ZqORdIW1MkN2+d9Ui2pCM1b/UMgwYUCZOiFYHAvz/slKKBDha8DLrh5aCG/RibtrpyhKjKOZ85tYyWg==
+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@3.2.36:
-  version "3.2.36"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.36.tgz#8daa996e2ced521708de97d066c7c998e8bc3378"
-  integrity sha512-5yTXmrE6gW8IQgttzHW5bfBiFA6mx35ZXHjGLDmKYzW6MMmYvCwuKybANRepwkMYeXw2v1buGg3/lPICY5YlZw==
+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:
-    "@vue/compiler-dom" "3.2.36"
-    "@vue/compiler-sfc" "3.2.36"
-    "@vue/runtime-dom" "3.2.36"
-    "@vue/server-renderer" "3.2.36"
-    "@vue/shared" "3.2.36"
+    "@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"
@@ -4239,10 +4239,10 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-ws@8.6.0:
-  version "8.6.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.6.0.tgz#e5e9f1d9e7ff88083d0c0dd8281ea662a42c9c23"
-  integrity sha512-AzmM3aH3gk0aX7/rZLYvjdvZooofDu3fFOzGqcSnQ1tOcTWwhM/o+q++E8mAyVVIyUdajrkzWUGftaVSDLn1bw==
+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"
diff --git a/yarn.lock b/yarn.lock
index 1e04e27526..327eac0172 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -194,49 +194,49 @@
   dependencies:
     "@types/node" "*"
 
-"@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/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/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"
 
 aggregate-error@^3.0.0:
   version "3.1.0"
@@ -1084,10 +1084,10 @@ csso@~2.3.1:
     clap "^1.0.9"
     source-map "^0.5.3"
 
-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"
@@ -1173,6 +1173,13 @@ debug@^3.1.0:
   dependencies:
     ms "^2.1.1"
 
+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"
+
 decamelize@^1.1.1, decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1349,10 +1356,10 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-eslint-visitor-keys@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2"
-  integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==
+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==
 
 esprima@^2.6.0:
   version "2.7.3"
@@ -1506,10 +1513,10 @@ fancy-log@^1.3.2:
     parse-node-version "^1.0.0"
     time-stamp "^1.0.0"
 
-fast-glob@^3.1.1:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
-  integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
+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"
@@ -1826,16 +1833,16 @@ global-prefix@^1.0.1:
     is-windows "^1.0.1"
     which "^1.2.14"
 
-globby@^11.0.4:
-  version "11.0.4"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
-  integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
+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.1.1"
-    ignore "^5.1.4"
-    merge2 "^1.3.0"
+    fast-glob "^3.2.9"
+    ignore "^5.2.0"
+    merge2 "^1.4.1"
     slash "^3.0.0"
 
 glogg@^1.0.0:
@@ -2034,10 +2041,10 @@ ieee754@^1.1.13:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore@^5.1.4:
-  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"
+  integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
 
 indent-string@^4.0.0:
   version "4.0.0"
@@ -2627,7 +2634,7 @@ 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==
@@ -3663,13 +3670,20 @@ semver-greatest-satisfied-range@^1.1.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@^7.3.2, semver@^7.3.5:
+semver@^7.3.2:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
   dependencies:
     lru-cache "^6.0.0"
 
+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"
+
 set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -4187,10 +4201,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-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==
 
 unc-path-regex@^0.1.2:
   version "0.1.2"

From ff9d4b2f74064817ac64982c7c4d5a4f79a51b64 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 12:07:28 +0900
Subject: [PATCH 245/258] update cypress

---
 cypress.config.ts | 7 +++++++
 cypress.json      | 3 ---
 2 files changed, 7 insertions(+), 3 deletions(-)
 create mode 100644 cypress.config.ts
 delete mode 100644 cypress.json

diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 0000000000..3c8a8c1387
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+	e2e: {
+		baseUrl: 'http://localhost:61812',
+	},
+});
diff --git a/cypress.json b/cypress.json
deleted file mode 100644
index e858e480b0..0000000000
--- a/cypress.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"baseUrl": "http://localhost:61812"
-}

From 759261b912b7bf53bb9043002ec6b7a3820c2866 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 12:23:47 +0900
Subject: [PATCH 246/258] update cypress

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index a938465777..97216a205e 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
 		"watch": "npm run dev",
 		"dev": "node ./scripts/dev.js",
 		"lint": "node ./scripts/lint.js",
-		"cy:open": "cypress open",
+		"cy:open": "cypress open --config-file=cypress.config.ts",
 		"cy:run": "cypress run",
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
 		"mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",

From 18385117669115b2e1700670c3d51e24249bb032 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 15:08:31 +0900
Subject: [PATCH 247/258] update cypress

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 97216a205e..fa0ebfe944 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
 		"watch": "npm run dev",
 		"dev": "node ./scripts/dev.js",
 		"lint": "node ./scripts/lint.js",
-		"cy:open": "cypress open --config-file=cypress.config.ts",
+		"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
 		"cy:run": "cypress run",
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
 		"mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",

From ecb3c43520e1f47447a86f4cac8b25aef039f229 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 15:45:44 +0900
Subject: [PATCH 248/258] feat: image cropping (#8808)

* wip

* wip

* wip
---
 CHANGELOG.md                                  |   1 +
 locales/ja-JP.yml                             |   2 +
 packages/client/package.json                  |  19 +-
 .../client/src/components/cropper-dialog.vue  | 171 ++++++++++++++++++
 .../client/src/components/ui/modal-window.vue | 128 ++++++-------
 packages/client/src/components/ui/modal.vue   |   9 +-
 packages/client/src/os.ts                     |  45 +++--
 .../client/src/pages/settings/profile.vue     |  32 +++-
 packages/client/yarn.lock                     | 107 +++++++++++
 9 files changed, 420 insertions(+), 94 deletions(-)
 create mode 100644 packages/client/src/components/cropper-dialog.vue

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 713251ff40..f8597f05d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ You should also include the user name that made the change.
 - プッシュ通知にクリックやactionを設定 #7667 @tamaina
 - ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
 - Server: always remove completed tasks of job queue @Johann150
+- Client: アバターの設定で画像をクロップできるように @syuilo
 - Client: make emoji stand out more on reaction button @Johann150
 - Client: display URL of QR code for TOTP registration @tamaina
 - Client: render quote renote CWs as MFM @pixeldesu
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 9cd1d1eedb..43ab7f2d69 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -843,6 +843,8 @@ oneWeek: "1週間"
 reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
 failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
 rateLimitExceeded: "レート制限を超えました"
+cropImage: "画像のクロップ"
+cropImageAsk: "画像をクロップしますか?"
 
 _emailUnavailable:
   used: "既に使用されています"
diff --git a/packages/client/package.json b/packages/client/package.json
index 689cd81b21..83c8086e23 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -12,7 +12,11 @@
 	"dependencies": {
 		"@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",
+		"@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",
@@ -26,6 +30,7 @@
 		"chartjs-plugin-zoom": "1.2.1",
 		"compare-versions": "4.1.3",
 		"content-disposition": "0.5.4",
+		"cropperjs": "2.0.0-beta",
 		"date-fns": "2.28.0",
 		"escape-regexp": "0.0.1",
 		"eventemitter3": "4.0.7",
@@ -51,6 +56,7 @@
 		"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.52.3",
 		"seedrandom": "3.0.5",
@@ -64,21 +70,16 @@
 		"tsc-alias": "1.6.9",
 		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
+		"typescript": "4.7.3",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
+		"vite": "2.9.10",
 		"vue": "3.2.37",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vue-router": "4.0.16",
 		"vuedraggable": "4.0.1",
 		"websocket": "1.0.34",
-		"@vitejs/plugin-vue": "2.3.3",
-		"@vue/compiler-sfc": "3.2.37",
-		"@rollup/plugin-alias": "3.1.9",
-		"@rollup/plugin-json": "4.1.0",
-		"rollup": "2.75.6",
-		"typescript": "4.7.3",
-		"vite": "2.9.10",
 		"ws": "8.8.0"
 	},
 	"devDependencies": {
@@ -102,11 +103,11 @@
 		"@types/ws": "8.5.3",
 		"@typescript-eslint/eslint-plugin": "5.27.1",
 		"@typescript-eslint/parser": "5.27.1",
-		"eslint": "8.17.0",
-		"eslint-plugin-vue": "9.1.0",
 		"cross-env": "7.0.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/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
new file mode 100644
index 0000000000..24ae4e87ae
--- /dev/null
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -0,0 +1,171 @@
+<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="file.url" 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 } from '@/config';
+
+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;
+}>();
+
+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/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue
index 6de29c83fa..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(evt) {
-			if (evt.which === 27) { // Esc
-				evt.preventDefault();
-				evt.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 010262da2f..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 => {
@@ -237,7 +240,7 @@ onMounted(() => {
 		await nextTick();
 		
 		align();
-	}, { immediate: true, });
+	}, { immediate: true });
 
 	nextTick(() => {
 		const popover = content.value;
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 4f19fadf19..14860465fa 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -34,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();
 
@@ -142,7 +142,7 @@ export async function popup(component: Component, props: Record<string, any>, ev
 		props,
 		events: disposeEvent ? {
 			...events,
-			[disposeEvent]: dispose
+			[disposeEvent]: dispose,
 		} : events,
 		id,
 	};
@@ -174,7 +174,7 @@ export function modalPageWindow(path: string) {
 
 export function toast(message: string) {
 	popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
-		message
+		message,
 	}, {}, 'closed');
 }
 
@@ -226,7 +226,7 @@ export function inputText(props: {
 				type: props.type,
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -251,7 +251,7 @@ export function inputNumber(props: {
 				type: 'number',
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -276,7 +276,7 @@ export function inputDate(props: {
 				type: 'date',
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
@@ -313,7 +313,7 @@ export function select<C = any>(props: {
 				items: props.items,
 				groupedItems: props.groupedItems,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -330,7 +330,7 @@ export function success() {
 		}, 1000);
 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
 			success: true,
-			showing: showing
+			showing: showing,
 		}, {
 			done: () => resolve(),
 		}, 'closed');
@@ -342,7 +342,7 @@ export function waiting() {
 		const showing = ref(true);
 		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
 			success: false,
-			showing: showing
+			showing: showing,
 		}, {
 			done: () => resolve(),
 		}, 'closed');
@@ -373,7 +373,7 @@ export async function selectDriveFile(multiple: boolean) {
 	return new Promise((resolve, reject) => {
 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
 			type: 'file',
-			multiple
+			multiple,
 		}, {
 			done: files => {
 				if (files) {
@@ -388,7 +388,7 @@ export async function selectDriveFolder(multiple: boolean) {
 	return new Promise((resolve, reject) => {
 		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
 			type: 'folder',
-			multiple
+			multiple,
 		}, {
 			done: folders => {
 				if (folders) {
@@ -403,7 +403,7 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
 	return new Promise((resolve, reject) => {
 		popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
 			src,
-			...opts
+			...opts,
 		}, {
 			done: emoji => {
 				resolve(emoji);
@@ -412,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 :
@@ -453,7 +468,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
 
 	openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), {
 		src,
-		...opts
+		...opts,
 	}, {
 		chosen: emoji => {
 			insertTextAtCursor(activeTextarea, emoji);
@@ -462,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
 			openingEmojiPicker!.dispose();
 			openingEmojiPicker = null;
 			observer.disconnect();
-		}
+		},
 	});
 }
 
@@ -478,7 +493,7 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
 			src,
 			width: options?.width,
 			align: options?.align,
-			viaKeyboard: options?.viaKeyboard
+			viaKeyboard: options?.viaKeyboard,
 		}, {
 			closed: () => {
 				resolve();
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/yarn.lock b/packages/client/yarn.lock
index 31e7053a0f..796c72304a 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -40,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"
@@ -1132,6 +1231,14 @@ 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=
 
+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:
+    "@cropper/elements" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
 cross-env@7.0.3:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf"

From eaf6ddd47496e884066006f1fb35e3a90aac5176 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 15:53:45 +0900
Subject: [PATCH 249/258] update cypress

---
 cypress.config.ts                                 | 15 ++++++++++-----
 cypress/{integration/basic.js => e2e/basic.cy.js} |  0
 .../{integration/widgets.js => e2e/widgets.cy.js} |  0
 cypress/support/{index.js => e2e.js}              |  0
 4 files changed, 10 insertions(+), 5 deletions(-)
 rename cypress/{integration/basic.js => e2e/basic.cy.js} (100%)
 rename cypress/{integration/widgets.js => e2e/widgets.cy.js} (100%)
 rename cypress/support/{index.js => e2e.js} (100%)

diff --git a/cypress.config.ts b/cypress.config.ts
index 3c8a8c1387..e390c41a54 100644
--- a/cypress.config.ts
+++ b/cypress.config.ts
@@ -1,7 +1,12 @@
-import { defineConfig } from 'cypress';
+import { defineConfig } from 'cypress'
 
 export default defineConfig({
-	e2e: {
-		baseUrl: 'http://localhost:61812',
-	},
-});
+  e2e: {
+    // We've imported your old cypress plugins here.
+    // You may want to clean this up later by importing these.
+    setupNodeEvents(on, config) {
+      return require('./cypress/plugins/index.js')(on, config)
+    },
+    baseUrl: 'http://localhost:61812',
+  },
+})
diff --git a/cypress/integration/basic.js b/cypress/e2e/basic.cy.js
similarity index 100%
rename from cypress/integration/basic.js
rename to cypress/e2e/basic.cy.js
diff --git a/cypress/integration/widgets.js b/cypress/e2e/widgets.cy.js
similarity index 100%
rename from cypress/integration/widgets.js
rename to cypress/e2e/widgets.cy.js
diff --git a/cypress/support/index.js b/cypress/support/e2e.js
similarity index 100%
rename from cypress/support/index.js
rename to cypress/support/e2e.js

From 97fe50a376d1baee587649f82df809ce4c4ce013 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 16:00:39 +0900
Subject: [PATCH 250/258] fix(test): use cypress-io/github-action@v4

---
 .github/workflows/test.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 2d858daa7c..c32c82e2a1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -103,7 +103,7 @@ jobs:
     - name: ALSA Env
       run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
     - name: Cypress run
-      uses: cypress-io/github-action@v2
+      uses: cypress-io/github-action@v4
       with:
         install: false
         start: npm run start:test

From 2a4eddc80fd37d7b2a0da887a2a2ff31dbd1f6a6 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 16:07:33 +0900
Subject: [PATCH 251/258] New Crowdin updates (#8669)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Catalan)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Romanian)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Portuguese)
---
 locales/ar-SA.yml |   1 -
 locales/bn-BD.yml |  13 +-
 locales/ca-ES.yml |   2 +
 locales/de-DE.yml |   3 +-
 locales/en-US.yml |   3 +-
 locales/es-ES.yml |   1 -
 locales/fr-FR.yml |   3 +-
 locales/id-ID.yml |   3 +-
 locales/it-IT.yml |   8 +-
 locales/ja-KS.yml |   1 -
 locales/ko-KR.yml |   2 +-
 locales/pl-PL.yml |   1 -
 locales/pt-PT.yml | 244 +++++++++++++++++++++++++++++++++++
 locales/ru-RU.yml |  16 ++-
 locales/sk-SK.yml |   3 +-
 locales/sv-SE.yml | 319 ++++++++++++++++++++++++++++++++++++++++++++++
 locales/uk-UA.yml |   1 -
 locales/vi-VN.yml |   3 +-
 locales/zh-CN.yml |   2 +-
 locales/zh-TW.yml |   2 +-
 20 files changed, 612 insertions(+), 19 deletions(-)
 create mode 100644 locales/sv-SE.yml

diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index efad7c954b..3bd8f1e506 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -1007,7 +1007,6 @@ _sfx:
   antenna: "الهوائيات"
   channel: "إشعارات القنات"
 _ago:
-  unknown: "مجهول"
   future: "المستقبَل"
   justNow: "اللحظة"
   secondsAgo: "منذ {n} ثوانٍ"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index bcecb06253..d7753b6dcf 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -831,11 +831,18 @@ themeColor: "থিমের রং"
 size: "আকার"
 numberOfColumn: "কলামের সংখ্যা"
 searchByGoogle: "গুগল"
+instanceDefaultLightTheme: "ইন্সট্যান্সের ডিফল্ট লাইট থিম"
+instanceDefaultDarkTheme: "ইন্সট্যান্সের ডিফল্ট ডার্ক থিম"
+instanceDefaultThemeDescription: "অবজেক্ট ফরম্যাটে থিম কোড লিখুন"
+mutePeriod: "মিউটের সময়কাল"
 indefinitely: "অনির্দিষ্ট"
 tenMinutes: "১০ মিনিট"
 oneHour: "১ ঘণ্টা"
 oneDay: "একদিন"
 oneWeek: "এক সপ্তাহ"
+reflectMayTakeTime: "এটির কাজ দেখা যেতে কিছুটা সময় লাগতে পারে।"
+failedToFetchAccountInformation: "অ্যাকাউন্টের তথ্য উদ্ধার করা যায়নি"
+rateLimitExceeded: "রেট লিমিট ছাড়িয়ে গেছে "
 _emailUnavailable:
   used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে"
   format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি"
@@ -1081,7 +1088,6 @@ _sfx:
   antenna: "অ্যান্টেনাগুলি"
   channel: "চ্যানেলের বিজ্ঞপ্তি"
 _ago:
-  unknown: "অজানা"
   future: "ভবিষ্যৎ"
   justNow: "এইমাত্র"
   secondsAgo: "{n} সেকেন্ড আগে"
@@ -1125,6 +1131,7 @@ _2fa:
   registerKey: "সিকিউরিটি কী নিবন্ধন করুন"
   step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷"
   step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।"
+  step2Url: "ডেস্কটপ অ্যাপে, নিম্নলিখিত URL লিখুন:"
   step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।"
   step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।"
   securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷"
@@ -1608,6 +1615,8 @@ _notification:
   youReceivedFollowRequest: "অনুসরণ করার জন্য অনুরোধ পাওয়া গেছে"
   yourFollowRequestAccepted: "আপনার অনুসরণ করার অনুরোধ গৃহীত হয়েছে"
   youWereInvitedToGroup: "আপনি একটি গ্রুপে আমন্ত্রিত হয়েছেন"
+  pollEnded: "পোলের ফলাফল দেখা যাবে"
+  emptyPushNotificationMessage: "আপডেট করা পুশ বিজ্ঞপ্তি"
   _types:
     all: "সকল"
     follow: "অনুসরণ করা হচ্ছে"
@@ -1617,11 +1626,13 @@ _notification:
     quote: "উদ্ধৃতি"
     reaction: "প্রতিক্রিয়া"
     pollVote: "পোলে ভোট আছে"
+    pollEnded: "পোল শেষ"
     receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
     groupInvited: "গ্রুপের আমন্ত্রনসমূহ"
     app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি"
   _actions:
+    followBack: "ফলো ব্যাক করেছে"
     reply: "জবাব"
     renote: "রিনোট"
 _deck:
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 577fbca2ae..74eab3603b 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -134,6 +134,8 @@ _theme:
 _sfx:
   note: "Notes"
   notification: "Notificacions"
+_2fa:
+  step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:"
 _widgets:
   notifications: "Notificacions"
   timeline: "Línia de temps"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index a3ff6c0a6c..b554ad81fc 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -842,6 +842,7 @@ oneDay: "Einen Tag"
 oneWeek: "Eine Woche"
 reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt."
 failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden"
+rateLimitExceeded: "Versuchsanzahl überschritten"
 _emailUnavailable:
   used: "Diese Email-Adresse wird bereits verwendet"
   format: "Das Format dieser Email-Adresse ist ungültig"
@@ -1087,7 +1088,6 @@ _sfx:
   antenna: "Antennen"
   channel: "Kanalbenachrichtigung"
 _ago:
-  unknown: "Unbekannt"
   future: "Zukunft"
   justNow: "Gerade eben"
   secondsAgo: "vor {n} Sekunde(n)"
@@ -1131,6 +1131,7 @@ _2fa:
   registerKey: "Neuen Sicherheitsschlüssel registrieren"
   step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerät."
   step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerät."
+  step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:"
   step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
   step4: "Alle folgenden Anmeldungsversuche werden ab sofort die Eingabe eines solchen Tokens benötigen."
   securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten."
diff --git a/locales/en-US.yml b/locales/en-US.yml
index a7d69d6d4f..e818dd6f7d 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -842,6 +842,7 @@ oneDay: "One day"
 oneWeek: "One week"
 reflectMayTakeTime: "It may take some time for this to be reflected."
 failedToFetchAccountInformation: "Could not fetch account information"
+rateLimitExceeded: "Rate limit exceeded"
 _emailUnavailable:
   used: "This email address is already being used"
   format: "The format of this email address is invalid"
@@ -1087,7 +1088,6 @@ _sfx:
   antenna: "Antennas"
   channel: "Channel notifications"
 _ago:
-  unknown: "Unknown"
   future: "Future"
   justNow: "Just now"
   secondsAgo: "{n} second(s) ago"
@@ -1131,6 +1131,7 @@ _2fa:
   registerKey: "Register a security key"
   step1: "First, install an authentication app (such as {a} or {b}) on your device."
   step2: "Then, scan the QR code displayed on this screen."
+  step2Url: "You can also enter this URL if you're using a desktop program:"
   step3: "Enter the token provided by your app to finish setup."
   step4: "From now on, any future login attempts will ask for such a login token."
   securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 8fff5ca4d9..6c10942b48 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -989,7 +989,6 @@ _sfx:
   antenna: "Antena receptora"
   channel: "Notificaciones del canal"
 _ago:
-  unknown: "Desconocido"
   future: "Futuro"
   justNow: "Recién ahora"
   secondsAgo: "Hace {n} segundos"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index 3b5dc1b06c..7e225c2992 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -804,7 +804,7 @@ manageAccounts: "Gérer les comptes"
 makeReactionsPublic: "Rendre les réactions publiques"
 makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données publique."
 classic: "Classique"
-muteThread: "Mettre ce thread en sourdine"
+muteThread: "Masquer cette discussion"
 unmuteThread: "Ne plus masquer le fil"
 ffVisibility: "Visibilité des abonnés/abonnements"
 ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu suis et les personnes qui te suivent."
@@ -1075,7 +1075,6 @@ _sfx:
   antenna: "Réception de l’antenne"
   channel: "Notifications de canal"
 _ago:
-  unknown: "Inconnu"
   future: "Futur"
   justNow: "à l’instant"
   secondsAgo: "Il y a {n}s"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index a97ac819a9..4ee77f4ad2 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -842,6 +842,7 @@ oneDay: "1 Hari"
 oneWeek: "1 Bulan"
 reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
 failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
+rateLimitExceeded: "Batas sudah terlampaui"
 _emailUnavailable:
   used: "Alamat surel ini telah digunakan"
   format: "Format tidak valid."
@@ -1087,7 +1088,6 @@ _sfx:
   antenna: "Penerimaan Antenna"
   channel: "Pemberitahuan saluran"
 _ago:
-  unknown: "Tidak diketahui"
   future: "Masa depan"
   justNow: "Baru saja"
   secondsAgo: "{n} detik lalu"
@@ -1131,6 +1131,7 @@ _2fa:
   registerKey: "Daftarkan kunci keamanan baru"
   step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu."
   step2: "Lalu, pindai kode QR yang ada di layar."
+  step2Url: "Di aplikasi desktop, masukkan URL berikut:"
   step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan."
   step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu."
   securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 4d10356698..8584ed6a8e 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -10,7 +10,7 @@ password: "Password"
 forgotPassword: "Hai dimenticato la tua password?"
 fetchingAsApObject: "Recuperando dal Fediverso..."
 ok: "OK"
-gotIt: "Capito!"
+gotIt: "Ho capito"
 cancel: "Annulla"
 enterUsername: "Inserisci un nome utente"
 renotedBy: "Rinotato da {user}"
@@ -767,6 +767,7 @@ customCss: "CSS personalizzato"
 global: "Federata"
 squareAvatars: "Mostra l'immagine del profilo come quadrato"
 sent: "Inviare"
+received: "Ricevuto"
 searchResult: "Risultati della Ricerca"
 hashtags: "Hashtag"
 troubleshooting: "Risoluzione problemi"
@@ -804,6 +805,10 @@ welcomeBackWithName: "Bentornato/a, {name}"
 clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email."
 searchByGoogle: "Cerca"
 indefinitely: "Non scade"
+tenMinutes: "10 minuti"
+oneHour: "1 ora"
+oneDay: "1 giorno"
+oneWeek: "1 settimana"
 _emailUnavailable:
   used: "Email già in uso"
   format: "Formato email non valido"
@@ -999,7 +1004,6 @@ _sfx:
   antenna: "Ricezione dell'antenna"
   channel: "Notifiche di canale"
 _ago:
-  unknown: "Sconosciuto"
   future: "Futuro"
   justNow: "Ora"
   secondsAgo: "{n}s fa"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index fd6945160e..5458152dda 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -799,7 +799,6 @@ _sfx:
   notification: "通知"
   chat: "チャット"
 _ago:
-  unknown: "わからん"
   future: "未来"
   justNow: "たった今"
   secondsAgo: "{n}秒前"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 74d06185d9..4e7369a5ef 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1087,7 +1087,6 @@ _sfx:
   antenna: "안테나 수신"
   channel: "채널 알림"
 _ago:
-  unknown: "알 수 없음"
   future: "미래"
   justNow: "방금 전"
   secondsAgo: "{n}초 전"
@@ -1131,6 +1130,7 @@ _2fa:
   registerKey: "키를 등록"
   step1: "먼저, {a}나 {b}등의 인증 앱을 사용 중인 디바이스에 설치합니다."
   step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다."
+  step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:"
   step3: "앱에 표시된 토큰을 입력하시면 완료됩니다."
   step4: "다음 로그인부터는 토큰을 입력해야 합니다."
   securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다."
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 7fabab3b4f..fa1dad2173 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -946,7 +946,6 @@ _sfx:
   chatBg: "Rozmowy (tło)"
   channel: "Powiadomienia kanału"
 _ago:
-  unknown: "Nieznane"
   future: "W przyszłości"
   justNow: "Przed chwilą"
   secondsAgo: "{n} sek. temu"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index c1afa5b56c..0dc15a27bb 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -81,18 +81,67 @@ somethingHappened: "Ocorreu um erro"
 retry: "Tentar novamente"
 pageLoadError: "Ocorreu um erro ao carregar a página."
 pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo."
+serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente novamente."
+youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para atualizar seu cliente."
+enterListName: "Insira um nome para a lista"
+privacy: "Privacidade"
+makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
+defaultNoteVisibility: "Visibilidade padrão"
 follow: "Seguindo"
+followRequest: "Mandar pedido de seguimento"
+followRequests: "Pedidos de seguimento"
+unfollow: "Deixar de seguir"
+followRequestPending: "Pedido de seguimento pendente"
 enterEmoji: "Inserir emoji"
 renote: "Repostar"
 renoted: "Repostado"
 cantRenote: "Não pode repostar"
 cantReRenote: "Não pode repostar este repost"
+quote: "Citar"
 pinnedNote: "Post fixado"
 pinned: "Afixar no perfil"
+you: "Você"
+clickToShow: "Clique para ver"
 sensitive: "Conteúdo sensível"
+add: "Adicionar"
+reaction: "Reações"
+reactionSetting: "Quais reações a mostrar no selecionador de reações"
+rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas"
+attachCancel: "Remover anexo"
+markAsSensitive: "Marcar como sensível"
+unmarkAsSensitive: "Desmarcar como sensível"
+enterFileName: "Digite o nome do ficheiro"
 mute: "Silenciar"
 unmute: "Dessilenciar"
+block: "Bloquear"
+unblock: "Desbloquear"
+suspend: "Suspender"
+unsuspend: "Cancelar suspensão"
+blockConfirm: "Tem certeza que gostaria de bloquear essa conta?"
+unblockConfirm: "Tem certeza que gostaria de desbloquear essa conta?"
+suspendConfirm: "Tem certeza que gostaria de suspender essa conta?"
+unsuspendConfirm: "Tem certeza que gostaria de cancelar a suspensão dessa conta?"
+selectList: "Escolhe uma lista"
+selectAntenna: "Escolhe uma antena"
+selectWidget: "Escolhe um widget"
+editWidgets: "Editar widgets"
+editWidgetsExit: "Pronto"
+customEmojis: "Emoji personalizado"
+emoji: "Emoji"
+emojis: "Emojis"
+emojiName: "Nome do Emoji"
+emojiUrl: "URL do Emoji"
+addEmoji: "Adicionar um Emoji"
 settingGuide: "Guia de configuração"
+flagAsBot: "Marcar conta como robô"
+flagAsCat: "Marcar conta como gato"
+flagAsCatDescription: "Ative essa opção para marcar essa conta como gato."
+flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
+general: "Geral"
+wallpaper: "Papel de parede"
+searchWith: "Buscar: {q}"
+youHaveNoLists: "Não tem nenhuma lista"
+followConfirm: "Tem certeza que quer deixar de seguir {name}?"
 instances: "Instância"
 registeredAt: "Registrado em"
 perHour: "por hora"
@@ -107,6 +156,8 @@ nsfw: "Conteúdo sensível"
 monthX: "mês de {month}"
 pinnedNotes: "Post fixado"
 userList: "Listas"
+none: "Nenhum"
+output: "Resultado"
 smtpUser: "Nome de usuário"
 smtpPass: "Senha"
 user: "Usuários"
@@ -116,6 +167,8 @@ _email:
     title: "Você tem um novo seguidor"
 _mfm:
   mention: "Menção"
+  quote: "Citar"
+  emoji: "Emoji personalizado"
   search: "Pesquisar"
 _theme:
   keys:
@@ -136,38 +189,229 @@ _profile:
 _exportOrImport:
   followingList: "Seguindo"
   muteList: "Silenciar"
+  blockingList: "Bloquear"
   userLists: "Listas"
 _pages:
+  blocks:
+    _button:
+      _action:
+        _pushEvent:
+          event: "Nome do evento"
+          message: "Mostrar mensagem quando ativado"
+          variable: "Variável a mandar"
+          no-variable: "Nenhum"
+        callAiScript: "Invocar AiScript"
+        _callAiScript:
+          functionName: "Nome da função"
+    radioButton: "Escolha"
+    _radioButton:
+      values: "Lista de escolhas separadas por quebras de texto"
   script:
     categories:
+      logical: "Operação lógica"
+      operation: "Cálculos"
+      comparison: "Comparação"
       list: "Listas"
     blocks:
+      _strReplace:
+        arg2: "Texto que irá ser substituído"
+        arg3: "Substituir com"
+      strReverse: "Virar texto"
+      join: "Sequência de texto"
       _join:
         arg1: "Listas"
+        arg2: "Separador"
+      add: "Somar"
+      _add:
+        arg1: "A"
+        arg2: "B"
+      subtract: "Subtrair"
+      _subtract:
+        arg1: "A"
+        arg2: "B"
+      multiply: "Multiplicar"
+      _multiply:
+        arg1: "A"
+        arg2: "B"
+      divide: "Dividir"
+      _divide:
+        arg1: "A"
+        arg2: "B"
+      mod: "O resto de"
+      _mod:
+        arg1: "A"
+        arg2: "B"
+      round: "Arredondar decimal"
+      _round:
+        arg1: "Numérico"
+      eq: "A e B são iguais"
+      _eq:
+        arg1: "A"
+        arg2: "B"
+      notEq: "A e B são diferentes"
+      _notEq:
+        arg1: "A"
+        arg2: "B"
+      and: "A e B"
+      _and:
+        arg1: "A"
+        arg2: "B"
+      or: "A OU B"
+      _or:
+        arg1: "A"
+        arg2: "B"
+      lt: "< A é menor do que B"
+      _lt:
+        arg1: "A"
+        arg2: "B"
+      gt: "> A é maior do que B"
+      _gt:
+        arg1: "A"
+        arg2: "B"
+      ltEq: "<= A é maior ou igual a B"
+      _ltEq:
+        arg1: "A"
+        arg2: "B"
+      gtEq: ">= A é maior ou igual a B"
+      _gtEq:
+        arg1: "A"
+        arg2: "B"
+      if: "Galho"
+      _if:
+        arg1: "Se"
+        arg2: "Então"
+        arg3: "Se não"
+      not: "NÃO"
+      _not:
+        arg1: "NÃO"
+      random: "Aleatório"
+      _random:
+        arg1: "Probabilidade"
+      rannum: "Numeral aleatório"
+      _rannum:
+        arg1: "Valor mínimo"
+        arg2: "Valor máximo"
+      randomPick: "Escolher aleatoriamente de uma lista"
       _randomPick:
         arg1: "Listas"
+      dailyRandom: "Aleatório (Muda uma vez por dia para cada usuário)"
+      _dailyRandom:
+        arg1: "Probabilidade"
+      dailyRannum: "Numeral aleatório (Muda uma vez por dia para cada usuário)"
+      _dailyRannum:
+        arg1: "Valor mínimo"
+        arg2: "Valor máximo"
+      dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)"
       _dailyRandomPick:
         arg1: "Listas"
+      seedRandom: "Aleatório (com semente)"
+      _seedRandom:
+        arg1: "Semente"
+        arg2: "Probabilidade"
+      seedRannum: "Número aleatório (com semente)"
+      _seedRannum:
+        arg1: "Semente"
+        arg2: "Valor mínimo"
+        arg3: "Valor máximo"
+      seedRandomPick: "Escolher aleatoriamente de uma lista (com uma semente)"
       _seedRandomPick:
+        arg1: "Semente"
         arg2: "Listas"
+      DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)"
+      _DRPWPM:
+        arg1: "Lista de texto"
+      pick: "Escolhe a partir da lista"
       _pick:
         arg1: "Listas"
+        arg2: "Posição"
+      listLen: "Pegar comprimento da lista"
       _listLen:
         arg1: "Listas"
+      number: "Numérico"
+      stringToNumber: "Texto para numérico"
+      _stringToNumber:
+        arg1: "Texto"
+      numberToString: "Numérico para texto"
+      _numberToString:
+        arg1: "Numérico"
+      splitStrByLine: "Dividir texto por quebras"
+      _splitStrByLine:
+        arg1: "Texto"
+      ref: "Variável"
+      aiScriptVar: "Variável AiScript"
+      fn: "Função"
+      _fn:
+        slots: "Espaços"
+        slots-info: "Separar cada espaço com uma quebra de texto"
+        arg1: "Resultado"
+      for: "Repetição 'for'"
+      _for:
+        arg1: "Número de repetições"
+        arg2: "Ação"
+    typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!"
+    thereIsEmptySlot: "O espaço {slot} está vazio!"
     types:
+      string: "Texto"
+      number: "Numérico"
       array: "Listas"
+      stringArray: "Lista de texto"
+    emptySlot: "Espaço vazio"
+    enviromentVariables: "Variáveis de ambiente"
+    pageVariables: "Variáveis de página"
+_relayStatus:
+  requesting: "Pendente"
+  accepted: "Aprovado"
+  rejected: "Recusado"
 _notification:
+  fileUploaded: "Carregamento de arquivo efetuado com sucesso"
+  youGotMention: "{name} te mencionou"
+  youGotReply: "{name} te respondeu"
+  youGotQuote: "{name} te citou"
+  youGotPoll: "{name} votou em sua enquete"
+  youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
+  youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
   youWereFollowed: "Você tem um novo seguidor"
+  youReceivedFollowRequest: "Você recebeu um pedido de seguimento"
+  yourFollowRequestAccepted: "Seu pedido de seguimento foi aceito"
+  youWereInvitedToGroup: "{userName} te convidou para um grupo"
+  pollEnded: "Os resultados da enquete agora estão disponíveis"
+  emptyPushNotificationMessage: "As notificações de alerta foram atualizadas"
   _types:
+    all: "Todos"
     follow: "Seguindo"
     mention: "Menção"
+    reply: "Respostas"
     renote: "Repostar"
+    quote: "Citar"
+    reaction: "Reações"
+    pollVote: "Votações em enquetes"
+    pollEnded: "Enquetes terminando"
+    receiveFollowRequest: "Recebeu pedidos de seguimento"
+    followRequestAccepted: "Aceitou pedidos de seguimento"
+    groupInvited: "Convites de grupo"
+    app: "Notificações de aplicativos conectados"
   _actions:
+    followBack: "te seguiu de volta"
     reply: "Responder"
     renote: "Repostar"
 _deck:
+  alwaysShowMainColumn: "Sempre mostrar a coluna principal"
+  columnAlign: "Alinhar colunas"
+  columnMargin: "Margem entre colunas"
+  columnHeaderHeight: "Altura do cabeçalho de coluna"
+  addColumn: "Adicionar coluna"
+  swapLeft: "Trocar de posição com a coluna à esquerda"
+  swapRight: "Trocar de posição com a coluna à direita"
+  swapUp: "Trocar de posição com a coluna acima"
+  swapDown: "Trocar de posição com a coluna abaixo"
+  popRight: "Acoplar coluna à direita"
+  profile: "Perfil"
   _columns:
+    main: "Principal"
+    widgets: "Widgets"
     notifications: "Notificações"
     tl: "Timeline"
+    antenna: "Antenas"
     list: "Listas"
     mentions: "Menções"
+    direct: "Notas diretas"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 7405c07e6c..c44589a7e5 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -141,6 +141,8 @@ flagAsBot: "Аккаунт бота"
 flagAsBotDescription: "Включите, если этот аккаунт управляется программой. Это позволит системе Misskey учитывать это, а также поможет разработчикам других ботов предотвратить бесконечные циклы взаимодействия."
 flagAsCat: "Аккаунт кота"
 flagAsCatDescription: "Включите, и этот аккаунт будет помечен как кошачий."
+flagShowTimelineReplies: "Показывать ответы на заметки в ленте"
+flagShowTimelineRepliesDescription: "Если этот параметр включен, то в ленте, в дополнение к заметкам пользователя, отображаются ответы на другие заметки пользователя."
 autoAcceptFollowed: "Принимать подписчиков автоматически"
 addAccount: "Добавить учётную запись"
 loginFailed: "Неудачная попытка входа"
@@ -236,6 +238,7 @@ saved: "Сохранено"
 messaging: "Сообщения"
 upload: "Загрузить"
 keepOriginalUploading: "Сохранить исходное изображение"
+keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации."
 fromDrive: "С «диска»"
 fromUrl: "По ссылке"
 uploadFromUrl: "Загрузить по ссылке"
@@ -589,6 +592,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений"
 smtpSecureInfo: "Выключите при использовании STARTTLS."
 testEmail: "Проверка доставки электронной почты"
 wordMute: "Скрытие слов"
+regexpError: "Ошибка в регулярном выражении"
 instanceMute: "Глушение инстансов"
 userSaysSomething: "{name} что-то сообщает"
 makeActive: "Активировать"
@@ -619,6 +623,8 @@ fillAbuseReportDescription: "Опишите, пожалуйста, причин
 abuseReported: "Жалоба отправлена. Большое спасибо за информацию."
 reporteeOrigin: "О ком сообщено"
 reporterOrigin: "Кто сообщил"
+forwardReport: "Перенаправление отчета на инстант."
+forwardReportIsAnonymous: "Удаленный инстант не сможет увидеть вашу информацию и будет отображаться как анонимная системная учетная запись."
 send: "Отправить"
 abuseMarkAsResolved: "Отметить жалобу как решённую"
 openInNewTab: "Открыть в новой вкладке"
@@ -815,7 +821,16 @@ leaveGroupConfirm: "Покинуть группу «{name}»?"
 useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве"
 welcomeBackWithName: "С возвращением, {name}!"
 clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты."
+overridedDeviceKind: "Тип устройства"
+smartphone: "Смартфон"
+tablet: "Планшет"
+auto: "Автоматически"
+themeColor: "Цвет темы"
+size: "Размер"
+numberOfColumn: "Количество столбцов"
 searchByGoogle: "Поиск"
+instanceDefaultLightTheme: "Светлая тема по умолчанию"
+instanceDefaultDarkTheme: "Темная тема по умолчанию"
 indefinitely: "вечно"
 _emailUnavailable:
   used: "Уже используется"
@@ -1059,7 +1074,6 @@ _sfx:
   antenna: "Антенна"
   channel: "Канал"
 _ago:
-  unknown: "Когда-то"
   future: "Из будущего"
   justNow: "Только что"
   secondsAgo: "{n} с назад"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 6818a64d30..dc1151522e 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -841,6 +841,7 @@ oneDay: "1 deň"
 oneWeek: "1 týždeň"
 reflectMayTakeTime: "Zmeny môžu chvíľu trvať kým sa prejavia."
 failedToFetchAccountInformation: "Nepodarilo sa načítať informácie o účte."
+rateLimitExceeded: "Prekročený limit rýchlosti"
 _emailUnavailable:
   used: "Táto emailová adresa sa už používa"
   format: "Formát emailovej adresy je nesprávny"
@@ -1086,7 +1087,6 @@ _sfx:
   antenna: "Antény"
   channel: "Upozornenia kanála"
 _ago:
-  unknown: "Neznáme"
   future: "Budúcnosť"
   justNow: "Teraz"
   secondsAgo: "pred {n} sekundami"
@@ -1130,6 +1130,7 @@ _2fa:
   registerKey: "Registrovať bezpečnostný kľúč"
   step1: "Najprv si nainštalujte autentifikačnú aplikáciu (napríklad {a} alebo {b}) na svoje zariadenie."
   step2: "Potom, naskenujte QR kód zobrazený na obrazovke."
+  step2Url: "Do aplikácie zadajte nasledujúcu URL adresu:"
   step3: "Nastavenie dokončíte zadaním tokenu z vašej aplikácie."
   step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token."
   securityKeyInfo: "Okrem odtlačku prsta alebo PIN autentifikácie si môžete nastaviť autentifikáciu cez hardvérový bezpečnostný kľúč podporujúci FIDO2 a tak ešte viac zabezpečiť svoj účet."
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
new file mode 100644
index 0000000000..42bfa45f25
--- /dev/null
+++ b/locales/sv-SE.yml
@@ -0,0 +1,319 @@
+---
+_lang_: "Svenska"
+headlineMisskey: "Ett nätverk kopplat av noter"
+introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀"
+monthAndDay: "{day}/{month}"
+search: "Sök"
+notifications: "Notifikationer"
+username: "Användarnamn"
+password: "Lösenord"
+forgotPassword: "Glömt lösenord"
+fetchingAsApObject: "Hämtar från Fediversum..."
+ok: "OK"
+gotIt: "Uppfattat!"
+cancel: "Avbryt"
+enterUsername: "Ange användarnamn"
+renotedBy: "Omnoterad av {user}"
+noNotes: "Inga noteringar"
+noNotifications: "Inga aviseringar"
+instance: "Instanser"
+settings: "Inställningar"
+basicSettings: "Basinställningar"
+otherSettings: "Andra inställningar"
+openInWindow: "Öppna i ett fönster"
+profile: "Profil"
+timeline: "Tidslinje"
+noAccountDescription: "Användaren har inte skrivit en biografi än."
+login: "Logga in"
+loggingIn: "Loggar in"
+logout: "Logga ut"
+signup: "Registrera"
+uploading: "Uppladdning sker..."
+save: "Spara"
+users: "Användare"
+addUser: "Lägg till användare"
+favorite: "Lägg till i favoriter"
+favorites: "Favoriter"
+unfavorite: "Avfavorisera"
+favorited: "Tillagd i favoriter."
+alreadyFavorited: "Redan tillagd i favoriter."
+cantFavorite: "Gick inte att lägga till i favoriter."
+pin: "Fäst till profil"
+unpin: "Lossa från profil"
+copyContent: "Kopiera innehåll"
+copyLink: "Kopiera länk"
+delete: "Radera"
+deleteAndEdit: "Radera och ändra"
+deleteAndEditConfirm: "Är du säker att du vill radera denna not och ändra den? Du kommer förlora alla reaktioner, omnoteringar och svar till den."
+addToList: "Lägg till i lista"
+sendMessage: "Skicka ett meddelande"
+copyUsername: "Kopiera användarnamn"
+searchUser: "Sök användare"
+reply: "Svara"
+loadMore: "Ladda mer"
+showMore: "Visa mer"
+youGotNewFollower: "följde dig"
+receiveFollowRequest: "Följarförfrågan mottagen"
+followRequestAccepted: "Följarförfrågan accepterad"
+mention: "Nämn"
+mentions: "Omnämningar"
+directNotes: "Direktnoter"
+importAndExport: "Importera / Exportera"
+import: "Importera"
+export: "Exportera"
+files: "Filer"
+download: "Nedladdning"
+driveFileDeleteConfirm: "Är du säker att du vill radera filen \"{name}\"? Noter med denna fil bifogad kommer också raderas."
+unfollowConfirm: "Är du säker att du vill avfölja {name}?"
+exportRequested: "Du har begärt en export. Detta kan ta lite tid. Den kommer läggas till i din Drive när den blir klar."
+importRequested: "Du har begärt en import. Detta kan ta lite tid."
+lists: "Listor"
+noLists: "Du har inga listor"
+note: "Not"
+notes: "Noter"
+following: "Följer"
+followers: "Följare"
+followsYou: "Följer dig"
+createList: "Skapa lista"
+manageLists: "Hantera lista"
+error: "Fel!"
+somethingHappened: "Ett fel har uppstått"
+retry: "Försök igen"
+pageLoadError: "Det gick inte att ladda sidan."
+pageLoadErrorDescription: "Detta händer oftast p.g.a. nätverksfel eller din webbläsarcache. Försök tömma din cache och testa sedan igen efter en liten stund."
+serverIsDead: "Servern svarar inte. Vänta ett litet tag och försök igen."
+youShouldUpgradeClient: "För att kunna se denna sida, vänligen ladda om sidan för att uppdatera din klient."
+enterListName: "Skriv ett namn till listan"
+privacy: "Integritet"
+makeFollowManuallyApprove: "Följarförfrågningar kräver manuellt godkännande"
+defaultNoteVisibility: "Standardsynlighet"
+follow: "Följ"
+followRequest: "Skicka följarförfrågan"
+followRequests: "Följarförfrågningar"
+unfollow: "Avfölj"
+followRequestPending: "Följarförfrågning avvaktar för svar"
+enterEmoji: "Skriv en emoji"
+renote: "Omnotera"
+unrenote: "Ta tillbaka omnotering"
+renoted: "Omnoterad."
+cantRenote: "Inlägget kunde inte bli omnoterat."
+cantReRenote: "En omnotering kan inte bli omnoterad."
+quote: "Citat"
+pinnedNote: "Fästad not"
+pinned: "Fäst till profil"
+you: "Du"
+clickToShow: "Klicka för att visa"
+sensitive: "Känsligt innehåll"
+add: "Lägg till"
+reaction: "Reaktioner"
+reactionSetting: "Reaktioner som ska visas i reaktionsväljaren"
+reactionSettingDescription2: "Dra för att omordna, klicka för att radera, tryck \"+\" för att lägga till."
+rememberNoteVisibility: "Komihåg notvisningsinställningar"
+attachCancel: "Ta bort bilaga"
+markAsSensitive: "Markera som känsligt innehåll"
+unmarkAsSensitive: "Avmarkera som känsligt innehåll"
+enterFileName: "Ange filnamn"
+mute: "Tysta"
+unmute: "Avtysta"
+block: "Blockera"
+unblock: "Avblockera"
+suspend: "Suspendera"
+unsuspend: "Ta bort suspenderingen"
+blockConfirm: "Är du säker att du vill blockera kontot?"
+unblockConfirm: "Är du säkert att du vill avblockera kontot?"
+suspendConfirm: "Är du säker att du vill suspendera detta konto?"
+unsuspendConfirm: "Är du säker att du vill avsuspendera detta konto?"
+selectList: "Välj lista"
+selectAntenna: "Välj en antenn"
+selectWidget: "Välj en widget"
+editWidgets: "Redigera widgets"
+editWidgetsExit: "Avsluta redigering"
+customEmojis: "Anpassa emoji"
+emoji: "Emoji"
+emojis: "Emoji"
+emojiName: "Emoji namn"
+emojiUrl: "Emoji länk"
+addEmoji: "Lägg till emoji"
+settingGuide: "Rekommenderade inställningar"
+cacheRemoteFiles: "Spara externa filer till cachen"
+cacheRemoteFilesDescription: "När denna inställning är avstängd kommer externa filer laddas direkt från den externa instansen. Genom att stänga av detta kommer lagringsutrymme minska i användning men kommer öka datatrafiken eftersom miniatyrer inte kommer genereras."
+flagAsBot: "Markera konto som bot"
+flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat av ett program. Om aktiverat kommer den fungera som en flagga för andra utvecklare för att hindra ändlösa kedjor med andra bottar. Det kommer också få Misskeys interna system att hantera kontot som en bot."
+flagAsCat: "Markera konto som katt"
+flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
+flagShowTimelineReplies: "Visa svar i tidslinje"
+flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen."
+autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt"
+addAccount: "Lägg till konto"
+loginFailed: "Inloggningen misslyckades"
+showOnRemote: "Se på extern instans"
+general: "Allmänt"
+wallpaper: "Bakgrundsbild"
+setWallpaper: "Välj bakgrund"
+removeWallpaper: "Ta bort bakgrund"
+searchWith: "Sök: {q}"
+youHaveNoLists: "Du har inga listor"
+followConfirm: "Är du säker att du vill följa {name}?"
+proxyAccount: "Proxykonto"
+proxyAccountDescription: "Ett proxykonto är ett konto som agerar som en extern följare för användare under vissa villkor. Till exempel, när en användare lägger till en extern användare till en lista så kommer den externa användarens aktivitet inte levireras till instansen om ingen lokal användare följer det kontot, så proxykontot används istället."
+host: "Värd"
+selectUser: "Välj användare"
+recipient: "Mottagare"
+annotation: "Kommentarer"
+federation: "Federation"
+instances: "Instanser"
+registeredAt: "Registrerad på"
+latestRequestSentAt: "Senaste förfrågan skickad"
+latestRequestReceivedAt: "Senaste begäran mottagen"
+latestStatus: "Senaste status"
+storageUsage: "Använt lagringsutrymme"
+charts: "Diagram"
+perHour: "Per timme"
+perDay: "Per dag"
+stopActivityDelivery: "Sluta skicka aktiviteter"
+blockThisInstance: "Blockera instans"
+operations: "Operationer"
+software: "Mjukvara"
+version: "Version"
+metadata: "Metadata"
+withNFiles: "{n} fil(er)"
+monitor: "Övervakning"
+jobQueue: "Jobbkö"
+cpuAndMemory: "CPU och minne"
+network: "Nätverk"
+disk: "Disk"
+instanceInfo: "Instansinformation"
+statistics: "Statistik"
+clearQueue: "Rensa kö"
+clearQueueConfirmTitle: "Är du säker att du vill rensa kön?"
+clearQueueConfirmText: "Om någon not är olevererad i kön kommer den inte federeras. Vanligtvis behövs inte denna handling."
+clearCachedFiles: "Rensa cache"
+clearCachedFilesConfirm: "Är du säker att du vill radera alla cachade externa filer?"
+blockedInstances: "Blockerade instanser"
+blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera. Listade instanser kommer inte längre kommunicera med denna instans."
+muteAndBlock: "Tystningar och blockeringar"
+mutedUsers: "Tystade användare"
+blockedUsers: "Blockerade användare"
+noUsers: "Det finns inga användare"
+editProfile: "Redigera profil"
+noteDeleteConfirm: "Är du säker på att du vill ta bort denna not?"
+pinLimitExceeded: "Du kan inte fästa fler noter"
+intro: "Misskey har installerats! Vänligen skapa en adminanvändare."
+done: "Klar"
+processing: "Bearbetar..."
+preview: "Förhandsvisning"
+default: "Standard"
+noCustomEmojis: "Det finns ingen emoji"
+noJobs: "Det finns inga jobb"
+federating: "Federerar"
+blocked: "Blockerad"
+suspended: "Suspenderad"
+all: "Allt"
+subscribing: "Prenumererar"
+publishing: "Publiceras"
+notResponding: "Svarar inte"
+instanceFollowing: "Följer på instans"
+instanceFollowers: "Följare av instans"
+instanceUsers: "Användare av denna instans"
+changePassword: "Ändra lösenord"
+security: "Säkerhet"
+retypedNotMatch: "Inmatningen matchar inte"
+currentPassword: "Nuvarande lösenord"
+newPassword: "Nytt lösenord"
+newPasswordRetype: "Bekräfta lösenord"
+attachFile: "Bifoga filer"
+more: "Mer!"
+featured: "Utvalda"
+usernameOrUserId: "Användarnamn eller användar-id"
+noSuchUser: "Kan inte hitta användaren"
+lookup: "Sökning"
+announcements: "Nyheter"
+imageUrl: "Bild-URL"
+remove: "Radera"
+removed: "Borttaget"
+removeAreYouSure: "Är du säker att du vill radera \"{x}\"?"
+deleteAreYouSure: "Är du säker att du vill radera \"{x}\"?"
+resetAreYouSure: "Vill du återställa?"
+saved: "Sparad"
+messaging: "Chatt"
+upload: "Ladda upp"
+keepOriginalUploading: "Behåll originalbild"
+nsfw: "Känsligt innehåll"
+pinnedNotes: "Fästad not"
+userList: "Listor"
+smtpHost: "Värd"
+smtpUser: "Användarnamn"
+smtpPass: "Lösenord"
+clearCache: "Rensa cache"
+user: "Användare"
+searchByGoogle: "Sök"
+_email:
+  _follow:
+    title: "följde dig"
+_mfm:
+  mention: "Nämn"
+  quote: "Citat"
+  emoji: "Anpassa emoji"
+  search: "Sök"
+_theme:
+  keys:
+    mention: "Nämn"
+    renote: "Omnotera"
+_sfx:
+  note: "Noter"
+  notification: "Notifikationer"
+  chat: "Chatt"
+_widgets:
+  notifications: "Notifikationer"
+  timeline: "Tidslinje"
+  federation: "Federation"
+  jobQueue: "Jobbkö"
+_cw:
+  show: "Ladda mer"
+_visibility:
+  followers: "Följare"
+_profile:
+  username: "Användarnamn"
+_exportOrImport:
+  followingList: "Följer"
+  muteList: "Tysta"
+  blockingList: "Blockera"
+  userLists: "Listor"
+_charts:
+  federation: "Federation"
+_pages:
+  script:
+    categories:
+      list: "Listor"
+    blocks:
+      _join:
+        arg1: "Listor"
+      _randomPick:
+        arg1: "Listor"
+      _dailyRandomPick:
+        arg1: "Listor"
+      _seedRandomPick:
+        arg2: "Listor"
+      _pick:
+        arg1: "Listor"
+      _listLen:
+        arg1: "Listor"
+    types:
+      array: "Listor"
+_notification:
+  youWereFollowed: "följde dig"
+  _types:
+    follow: "Följer"
+    mention: "Nämn"
+    renote: "Omnotera"
+    quote: "Citat"
+    reaction: "Reaktioner"
+  _actions:
+    reply: "Svara"
+    renote: "Omnotera"
+_deck:
+  _columns:
+    notifications: "Notifikationer"
+    tl: "Tidslinje"
+    list: "Listor"
+    mentions: "Omnämningar"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 480526c134..7e7ef8685f 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -919,7 +919,6 @@ _sfx:
   antenna: "Прийом антени"
   channel: "Повідомлення каналу"
 _ago:
-  unknown: "Невідомо"
   future: "Майбутнє"
   justNow: "Щойно"
   secondsAgo: "{n}с тому"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index ffe5ba1976..9919e0a0a4 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -842,6 +842,7 @@ oneDay: "1 ngày"
 oneWeek: "1 tuần"
 reflectMayTakeTime: "Có thể mất một thời gian để điều này được áp dụng."
 failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản"
+rateLimitExceeded: "Giới hạn quá mức"
 _emailUnavailable:
   used: "Địa chỉ email đã được sử dụng"
   format: "Địa chỉ email không hợp lệ"
@@ -1087,7 +1088,6 @@ _sfx:
   antenna: "Trạm phát sóng"
   channel: "Kênh"
 _ago:
-  unknown: "Không rõ"
   future: "Tương lai"
   justNow: "Vừa xong"
   secondsAgo: "{n}s trước"
@@ -1131,6 +1131,7 @@ _2fa:
   registerKey: "Đăng ký một mã bảo vệ"
   step1: "Trước tiên, hãy cài đặt một ứng dụng xác minh (chẳng hạn như {a} hoặc {b}) trên thiết bị của bạn."
   step2: "Sau đó, quét mã QR hiển thị trên màn hình này."
+  step2Url: "Bạn cũng có thể nhập URL này nếu sử dụng một chương trình máy tính:"
   step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập."
   step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó."
   securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình."
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index c719dcb767..4953f55280 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -1087,7 +1087,6 @@ _sfx:
   antenna: "天线接收"
   channel: "频道通知"
 _ago:
-  unknown: "未知"
   future: "未来"
   justNow: "最近"
   secondsAgo: "{n}秒前"
@@ -1131,6 +1130,7 @@ _2fa:
   registerKey: "注册密钥"
   step1: "首先,在您的设备上安装验证应用,例如{a}或{b}。"
   step2: "然后,扫描屏幕上显示的二维码。"
+  step2Url: "在桌面应用程序中输入以下URL:"
   step3: "输入您的应用提供的动态口令以完成设置。"
   step4: "从现在开始,任何登录操作都将要求您提供动态口令。"
   securityKeyInfo: "您可以设置使用支持FIDO2的硬件安全密钥、设备上的指纹或PIN来保护您的登录过程。"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index e9b7ab6540..f088fdc0e9 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -1087,7 +1087,6 @@ _sfx:
   antenna: "天線接收"
   channel: "頻道通知"
 _ago:
-  unknown: "未知"
   future: "未來"
   justNow: "剛剛"
   secondsAgo: "{n}秒前"
@@ -1131,6 +1130,7 @@ _2fa:
   registerKey: "註冊鍵"
   step1: "首先,在您的設備上安裝二步驗證程式,例如{a}或{b}。"
   step2: "然後,掃描螢幕上的QR code。"
+  step2Url: "在桌面版應用中,請輸入以下的URL:"
   step3: "輸入您的App提供的權杖以完成設定。"
   step4: "從現在開始,任何登入操作都將要求您提供權杖。"
   securityKeyInfo: "您可以設定使用支援FIDO2的硬體安全鎖、終端設備的指纹認證或者PIN碼來登入。"

From 7db09103e76218f6c867f4a9f1cbc2dd25512e3d Mon Sep 17 00:00:00 2001
From: Johann150 <johann.galle@protonmail.com>
Date: Sat, 11 Jun 2022 09:14:44 +0200
Subject: [PATCH 252/258] chore: synchronize visibility checks (#8687)

* reuse single meId parameter

* unify code style

Use template string to avoid having to use escaped quote marks.

* fix: follower only notes are visible to mentioned users

This synchronizes the visibility rules with the Notes.isVisibleForMe
method from packages/backend/src/models/repositories/note.ts

* add comment
---
 packages/backend/src/models/repositories/note.ts   |  1 +
 .../server/api/common/generate-visibility-query.ts | 14 ++++++++------
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index c0abbb4f93..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) {
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 });
 	}
 }

From 410210cf6fbcafc896f5f1c05e23a55223c970cc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 16:46:21 +0900
Subject: [PATCH 253/258] =?UTF-8?q?fix(client):=20=E3=82=AA=E3=83=96?=
 =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=82=B9=E3=83=88=E3=83=AC?=
 =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=81=A6?=
 =?UTF-8?q?=E3=81=84=E3=82=8B=E3=81=A8=E7=94=BB=E5=83=8F=E3=81=AE=E3=82=AF?=
 =?UTF-8?q?=E3=83=AD=E3=83=83=E3=83=97=E3=81=8C=E3=81=A7=E3=81=8D=E3=81=AA?=
 =?UTF-8?q?=E3=81=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/client/src/components/cropper-dialog.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
index 24ae4e87ae..6f4a6f122c 100644
--- a/packages/client/src/components/cropper-dialog.vue
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -18,7 +18,7 @@
 				</div>
 			</Transition>
 			<div class="container">
-				<img ref="imgEl" :src="file.url" style="display: none;" @load="onImageLoad">
+				<img ref="imgEl" crossorigin="anonymous" :src="file.url" style="display: none;" @load="onImageLoad">
 			</div>
 		</div>
 	</template>

From 7894804eedb0d980cd8720e22b16ca47a8302f76 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 18:47:58 +0900
Subject: [PATCH 254/258] Update cropper-dialog.vue

---
 packages/client/src/components/cropper-dialog.vue | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
index 6f4a6f122c..12d9414d22 100644
--- a/packages/client/src/components/cropper-dialog.vue
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -18,7 +18,7 @@
 				</div>
 			</Transition>
 			<div class="container">
-				<img ref="imgEl" crossorigin="anonymous" :src="file.url" style="display: none;" @load="onImageLoad">
+				<img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad">
 			</div>
 		</div>
 	</template>
@@ -34,7 +34,8 @@ import XModalWindow from '@/components/ui/modal-window.vue';
 import * as os from '@/os';
 import { $i } from '@/account';
 import { defaultStore } from '@/store';
-import { apiUrl } from '@/config';
+import { apiUrl, url } from '@/config';
+import { query } from '@/scripts/url';
 
 const emit = defineEmits<{
 	(ev: 'ok', cropped: misskey.entities.DriveFile): void;
@@ -47,6 +48,9 @@ const props = defineProps<{
 	aspectRatio: number;
 }>();
 
+const imgUrl = `${url}/proxy/image.webp?${query({
+	url,
+})}`;
 let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
 let imgEl = $ref<HTMLImageElement>();
 let cropper: Cropper | null = null;

From ca9753f2e7cd3240279d6d8f17e95afde796e38f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 18:48:59 +0900
Subject: [PATCH 255/258] Update cropper-dialog.vue

---
 packages/client/src/components/cropper-dialog.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
index 12d9414d22..a8bde6ea05 100644
--- a/packages/client/src/components/cropper-dialog.vue
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -49,7 +49,7 @@ const props = defineProps<{
 }>();
 
 const imgUrl = `${url}/proxy/image.webp?${query({
-	url,
+	url: props.file.url,
 })}`;
 let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
 let imgEl = $ref<HTMLImageElement>();

From 884b3e5cd58f9c26fe149d8f7c5370935d40cb34 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 19:21:50 +0900
Subject: [PATCH 256/258] New Crowdin updates (#8809)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Indonesian)
---
 locales/de-DE.yml | 2 ++
 locales/en-US.yml | 2 ++
 locales/id-ID.yml | 2 ++
 locales/nl-NL.yml | 2 ++
 4 files changed, 8 insertions(+)

diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index b554ad81fc..5dfce28002 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -843,6 +843,8 @@ oneWeek: "Eine Woche"
 reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt."
 failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden"
 rateLimitExceeded: "Versuchsanzahl überschritten"
+cropImage: "Bild zuschneiden"
+cropImageAsk: "Möchtest du das Bild zuschneiden?"
 _emailUnavailable:
   used: "Diese Email-Adresse wird bereits verwendet"
   format: "Das Format dieser Email-Adresse ist ungültig"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index e818dd6f7d..8bfea26b0f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -843,6 +843,8 @@ oneWeek: "One week"
 reflectMayTakeTime: "It may take some time for this to be reflected."
 failedToFetchAccountInformation: "Could not fetch account information"
 rateLimitExceeded: "Rate limit exceeded"
+cropImage: "Crop image"
+cropImageAsk: "Do you want to crop this image?"
 _emailUnavailable:
   used: "This email address is already being used"
   format: "The format of this email address is invalid"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 4ee77f4ad2..39e2c1f661 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -843,6 +843,8 @@ oneWeek: "1 Bulan"
 reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
 failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
 rateLimitExceeded: "Batas sudah terlampaui"
+cropImage: "potong gambar"
+cropImageAsk: "Ingin memotong gambar?"
 _emailUnavailable:
   used: "Alamat surel ini telah digunakan"
   format: "Format tidak valid."
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index d0a83eb6a8..0ded573948 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -303,6 +303,8 @@ muteThread: "Discussies dempen "
 unmuteThread: "Dempen van discussie ongedaan maken"
 hide: "Verbergen"
 searchByGoogle: "Zoeken"
+cropImage: "Afbeelding bijsnijden"
+cropImageAsk: "Bijsnijdengevraagd"
 _email:
   _follow:
     title: "volgde jou"

From 43967daea5fb0800316d678c9dd0c031e8b890aa Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 19:28:12 +0900
Subject: [PATCH 257/258] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8597f05d3..d8ff45d6d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,7 +33,6 @@ You should also include the user name that made the change.
 ### Bugfixes
 - Server: keep file order of note attachement @Johann150
 - Server: fix caching @Johann150
-- Server: await promises when following or unfollowing users @Johann150
 - Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
 - Server: fix internal in-memory caching @Johann150
 - Server: use correct order of attachments on notes @Johann150
@@ -42,6 +41,7 @@ You should also include the user name that made the change.
 - Server: Fix `Cannot find module` issue @mei23
 - Federation: Add rel attribute to host-meta @mei23
 - Federation: add id for activitypub follows @Johann150
+- Federation: use `source` instead of `_misskey_content` @Johann150
 - Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
 - Federation: correctly render empty note text @Johann150
 - Federation: Fix quote renotes containing no text being federated correctly @Johann150

From 3a987b00063e69805ab7f3b75e86ac06ff0abdb1 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 11 Jun 2022 19:30:07 +0900
Subject: [PATCH 258/258] 12.111.0

---
 CHANGELOG.md | 2 +-
 package.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d8ff45d6d6..eb16a1675d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,7 @@
 You should also include the user name that made the change.
 -->
 
-## 12.x.x (unreleased)
+## 12.111.0 (2022/06/11)
 ### Improvements
 - Supports Unicode Emoji 14.0 @mei23
 - プッシュ通知を複数アカウント対応に #7667 @tamaina
diff --git a/package.json b/package.json
index fa0ebfe944..e3340005ad 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "12.110.1",
+	"version": "12.111.0",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",