From 41592eafb363e3c62ab2d3e5f41b38d7d083d3fb Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Fri, 9 Jan 2026 22:06:40 +0900
Subject: refactor: make noImplicitAny true (#17083)

* wip

* Update emojis.emoji.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update manager.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update analytics.ts
---
 packages/frontend/src/components/MkInput.vue | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

(limited to 'packages/frontend/src/components/MkInput.vue')

diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 7f052dff94..4f6ca083a3 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -88,7 +88,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'change', _ev: InputEvent): void;
 	(ev: 'keydown', _ev: KeyboardEvent): void;
 	(ev: 'enter', _ev: KeyboardEvent): void;
 	(ev: 'update:modelValue', value: ModelValueType<T>): void;
@@ -111,10 +111,9 @@ const height =
 let autocompleteWorker: Autocomplete | null = null;
 
 const focus = () => inputEl.value?.focus();
-const onInput = (event: Event) => {
-	const ev = event as KeyboardEvent;
+const onInput = (event: InputEvent) => {
 	changed.value = true;
-	emit('change', ev);
+	emit('change', event);
 };
 const onKeydown = (ev: KeyboardEvent) => {
 	if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
-- 
cgit v1.2.3-freya


From f3aa5081ed994af857a97798528e1788d7762d36 Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Tue, 13 Jan 2026 15:02:50 +0900
Subject: fix(frontend):
 MkFormで入力に不備がある場合は完了ボタンを押して続行できないように (#17096)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* fix(frontend): MkFormで入力に不備がある場合は完了ボタンを押して続行できないように

* fix lint
---
 packages/frontend/src/components/MkForm.vue        | 41 ++++++++++++++++++++--
 packages/frontend/src/components/MkFormDialog.vue  | 12 +++++--
 packages/frontend/src/components/MkInput.vue       |  5 +++
 packages/frontend/src/components/MkTextarea.vue    |  5 +++
 .../src/components/MkWidgetSettingsDialog.vue      | 12 +++++--
 5 files changed, 68 insertions(+), 7 deletions(-)

(limited to 'packages/frontend/src/components/MkInput.vue')

diff --git a/packages/frontend/src/components/MkForm.vue b/packages/frontend/src/components/MkForm.vue
index 711aa611c3..3d4724e6b7 100644
--- a/packages/frontend/src/components/MkForm.vue
+++ b/packages/frontend/src/components/MkForm.vue
@@ -7,15 +7,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
 	<template v-for="v, k in form">
 		<template v-if="typeof v.hidden == 'function' ? v.hidden(values) : v.hidden"></template>
-		<MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1" :manualSave="v.manualSave">
+		<MkInput v-else-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1" :manualSave="v.manualSave" @savingStateChange="(changed, invalid) => onSavingStateChange(k, changed, invalid)">
 			<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
 			<template v-if="v.description" #caption>{{ v.description }}</template>
 		</MkInput>
-		<MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm" :manualSave="v.manualSave">
+		<MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm" :manualSave="v.manualSave" @savingStateChange="(changed, invalid) => onSavingStateChange(k, changed, invalid)">
 			<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
 			<template v-if="v.description" #caption>{{ v.description }}</template>
 		</MkInput>
-		<MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm" :manualSave="v.manualSave">
+		<MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm" :manualSave="v.manualSave" @savingStateChange="(changed, invalid) => onSavingStateChange(k, changed, invalid)">
 			<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
 			<template v-if="v.description" #caption>{{ v.description }}</template>
 		</MkTextarea>
@@ -49,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
 import XFile from '@/components/MkForm.file.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -65,9 +66,43 @@ const props = defineProps<{
 	form: Form;
 }>();
 
+const emit = defineEmits<{
+	(ev: 'canSaveStateChange', canSave: boolean): void;
+}>();
+
 // TODO: ジェネリックにしたい
 const values = defineModel<Record<string, any>>({ required: true });
 
+// 保存可能状態の管理
+const inputSavingStates = ref<Record<string, { changed: boolean; invalid: boolean }>>({});
+
+function onSavingStateChange(key: string, changed: boolean, invalid: boolean) {
+	inputSavingStates.value[key] = { changed, invalid };
+}
+
+const canSave = computed(() => {
+	for (const key in inputSavingStates.value) {
+		const state = inputSavingStates.value[key];
+		if (
+			('manualSave' in props.form[key] && props.form[key].manualSave && state.changed) ||
+			state.invalid
+	 	) {
+			return false;
+		}
+		if ('required' in props.form[key] && props.form[key].required) {
+			const val = values.value[key];
+			if (val === null || val === undefined || val === '') {
+				return false;
+			}
+		}
+	}
+	return true;
+});
+
+watch(canSave, (newCanSave) => {
+	emit('canSaveStateChange', newCanSave);
+}, { immediate: true });
+
 function getMkSelectDef(def: EnumFormItem): MkSelectItem[] {
 	return def.enum.map((v) => {
 		if (typeof v === 'string') {
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index c314de7c89..091721b40b 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:width="450"
 	:canClose="false"
 	:withOkButton="true"
-	:okButtonDisabled="false"
+	:okButtonDisabled="!canSave"
 	@click="cancel()"
 	@ok="ok()"
 	@close="cancel()"
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</template>
 
 	<div class="_spacer" style="--MI_SPACER-min: 20px; --MI_SPACER-max: 32px;">
-		<MkForm v-model="values" :form="form"/>
+		<MkForm v-model="values" :form="form" @canSaveStateChange="onCanSaveStateChanged"/>
 	</div>
 </MkModalWindow>
 </template>
@@ -59,7 +59,15 @@ const values = ref((() => {
 	return obj;
 })());
 
+const canSave = ref(true);
+
+function onCanSaveStateChanged(newCanSave: boolean) {
+	canSave.value = newCanSave;
+}
+
 function ok() {
+	if (!canSave.value) return;
+
 	emit('done', {
 		result: values.value,
 	});
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 4f6ca083a3..8a49fd231d 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -92,6 +92,7 @@ const emit = defineEmits<{
 	(ev: 'keydown', _ev: KeyboardEvent): void;
 	(ev: 'enter', _ev: KeyboardEvent): void;
 	(ev: 'update:modelValue', value: ModelValueType<T>): void;
+	(ev: 'savingStateChange', saved: boolean, invalid: boolean): void;
 }>();
 
 const { modelValue } = toRefs(props);
@@ -152,6 +153,10 @@ watch(v, () => {
 	invalid.value = inputEl.value?.validity.badInput ?? true;
 });
 
+watch([changed, invalid], ([newChanged, newInvalid]) => {
+	emit('savingStateChange', newChanged, newInvalid);
+}, { immediate: true });
+
 // このコンポーネントが作成された時、非表示状態である場合がある
 // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
 useInterval(() => {
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index d53d4ec018..fe4f7b7aaf 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -67,6 +67,7 @@ const emit = defineEmits<{
 	(ev: 'keydown', _ev: KeyboardEvent): void;
 	(ev: 'enter'): void;
 	(ev: 'update:modelValue', value: string): void;
+	(ev: 'savingStateChange', saved: boolean, invalid: boolean): void;
 }>();
 
 const { modelValue, autofocus } = toRefs(props);
@@ -131,6 +132,10 @@ watch(v, () => {
 	invalid.value = inputEl.value?.validity.badInput ?? true;
 });
 
+watch([changed, invalid], ([newChanged, newInvalid]) => {
+	emit('savingStateChange', newChanged, newInvalid);
+}, { immediate: true });
+
 onMounted(() => {
 	nextTick(() => {
 		if (autofocus.value) {
diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
index 63f294770c..41fec2a9e0 100644
--- a/packages/frontend/src/components/MkWidgetSettingsDialog.vue
+++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:height="600"
 	:scroll="false"
 	:withOkButton="true"
+	:okButtonDisabled="!canSave"
 	@close="cancel()"
 	@ok="save()"
 	@closed="emit('closed')"
@@ -38,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<template #controls>
 			<div class="_spacer">
-				<MkForm v-model="settings" :form="form"/>
+				<MkForm v-model="settings" :form="form" @canSaveStateChange="onCanSaveStateChanged"/>
 			</div>
 		</template>
 	</MkPreviewWithControls>
@@ -46,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { reactive, useTemplateRef, ref, computed, watch, onBeforeUnmount, onMounted } from 'vue';
+import { useTemplateRef, ref, computed, onBeforeUnmount, onMounted } from 'vue';
 import MkPreviewWithControls from './MkPreviewWithControls.vue';
 import type { Form } from '@/utility/form.js';
 import { deepClone } from '@/utility/clone.js';
@@ -70,7 +71,14 @@ const dialog = useTemplateRef('dialog');
 
 const settings = ref<Record<string, any>>(deepClone(props.currentSettings));
 
+const canSave = ref(true);
+
+function onCanSaveStateChanged(newCanSave: boolean) {
+	canSave.value = newCanSave;
+}
+
 function save() {
+	if (!canSave.value) return;
 	emit('saved', deepClone(settings.value));
 	dialog.value?.close();
 }
-- 
cgit v1.2.3-freya


From 389861f1da42e66fe52fe5d33ba4ade35aa16f23 Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Fri, 16 Jan 2026 22:42:08 +0900
Subject: refactor(frontend): fix invalid self-closing tags (#17103)

* fix(frontend): fix invalid self-closing tags

* apply eslint rules

* fix

* fix
---
 packages/frontend-embed/eslint.config.js                     | 10 +++++++++-
 packages/frontend-embed/src/components/EmAvatar.vue          | 12 ++++++------
 packages/frontend-embed/src/components/EmImgWithBlurhash.vue |  2 +-
 packages/frontend/eslint.config.js                           | 10 +++++++++-
 packages/frontend/src/components/MkDialog.vue                |  4 ++--
 packages/frontend/src/components/MkDivider.vue               |  2 +-
 packages/frontend/src/components/MkImgWithBlurhash.vue       |  2 +-
 packages/frontend/src/components/MkInput.vue                 |  2 +-
 packages/frontend/src/components/MkNotification.vue          |  4 ++--
 packages/frontend/src/components/MkPagingButtons.vue         |  4 ++--
 packages/frontend/src/components/MkPolkadots.vue             |  2 +-
 packages/frontend/src/components/MkPostForm.vue              |  4 ++--
 packages/frontend/src/components/MkPullToRefresh.vue         |  2 +-
 packages/frontend/src/components/MkReactionsViewer.vue       |  2 +-
 packages/frontend/src/components/global/MkAvatar.vue         | 12 ++++++------
 .../admin/abuse-report/notification-recipient.editor.vue     |  4 ++--
 .../pages/admin/abuse-report/notification-recipient.item.vue |  6 +++---
 .../src/pages/admin/abuse-report/notification-recipient.vue  |  2 +-
 packages/frontend/src/pages/admin/server-rules.vue           |  4 ++--
 packages/frontend/src/pages/admin/system-webhook.item.vue    |  8 ++++----
 packages/frontend/src/pages/drop-and-fusion.game.vue         |  4 ++--
 packages/frontend/src/pages/follow-requests.vue              |  6 +++---
 packages/frontend/src/pages/settings/2fa.vue                 |  2 +-
 packages/frontend/src/pages/user/home.vue                    |  6 +++---
 packages/frontend/src/ui/deck/tl-column.vue                  |  2 +-
 25 files changed, 67 insertions(+), 51 deletions(-)

(limited to 'packages/frontend/src/components/MkInput.vue')

diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js
index 63767cfb3c..d1ca70617b 100644
--- a/packages/frontend-embed/eslint.config.js
+++ b/packages/frontend-embed/eslint.config.js
@@ -144,7 +144,15 @@ export default [
 			'vue/return-in-computed-property': 'warn',
 			'vue/no-setup-props-reactivity-loss': 'warn',
 			'vue/max-attributes-per-line': 'off',
-			'vue/html-self-closing': 'off',
+			'vue/html-self-closing': ['error', {
+				html: {
+					void: 'any',
+					normal: 'never',
+					component: 'any',
+				},
+				svg: 'any',
+				math: 'any',
+			}],
 			'vue/singleline-html-element-content-newline': 'off',
 			'vue/v-on-event-hyphenation': ['error', 'never', {
 				autofix: true,
diff --git a/packages/frontend-embed/src/components/EmAvatar.vue b/packages/frontend-embed/src/components/EmAvatar.vue
index 58c35c8ef0..3f91e14403 100644
--- a/packages/frontend-embed/src/components/EmAvatar.vue
+++ b/packages/frontend-embed/src/components/EmAvatar.vue
@@ -9,16 +9,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div v-if="user.isCat" :class="[$style.ears]">
 		<div :class="$style.earLeft">
 			<div v-if="false" :class="$style.layer">
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
 			</div>
 		</div>
 		<div :class="$style.earRight">
 			<div v-if="false" :class="$style.layer">
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
 			</div>
 		</div>
 	</div>
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
index 71f0ee9294..be18ce79d5 100644
--- a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
+++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
-	<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
+	<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"></canvas>
 	<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
 </div>
 </template>
diff --git a/packages/frontend/eslint.config.js b/packages/frontend/eslint.config.js
index 15baf449fe..adae471c0a 100644
--- a/packages/frontend/eslint.config.js
+++ b/packages/frontend/eslint.config.js
@@ -147,7 +147,15 @@ export default [
 			'vue/return-in-computed-property': 'warn',
 			'vue/no-setup-props-reactivity-loss': 'warn',
 			'vue/max-attributes-per-line': 'off',
-			'vue/html-self-closing': 'off',
+			'vue/html-self-closing': ['error', {
+				html: {
+					void: 'any',
+					normal: 'never',
+					component: 'any',
+				},
+				svg: 'any',
+				math: 'any',
+			}],
 			'vue/singleline-html-element-content-newline': 'off',
 			'vue/v-on-event-hyphenation': ['error', 'never', {
 				autofix: true,
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 4801b412f8..fb8b38de6d 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -25,8 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
 			<template #caption>
-				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string)?.length ?? 0, max: input.maxLength ?? 'NaN' })"/>
-				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string)?.length ?? 0, min: input.minLength ?? 'NaN' })"/>
+				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string)?.length ?? 0, max: input.maxLength ?? 'NaN' })"></span>
+				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string)?.length ?? 0, min: input.minLength ?? 'NaN' })"></span>
 			</template>
 		</MkInput>
 		<MkSelect v-if="select" v-model="selectedValue" :items="selectDef" autofocus></MkSelect>
diff --git a/packages/frontend/src/components/MkDivider.vue b/packages/frontend/src/components/MkDivider.vue
index f72f091383..808a9ae2f8 100644
--- a/packages/frontend/src/components/MkDivider.vue
+++ b/packages/frontend/src/components/MkDivider.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		borderWidth ? { borderWidth: borderWidth } : {},
 		borderColor ? { borderColor: borderColor } : {},
 	]"
-/>
+></div>
 </template>
 
 <script setup lang="ts">
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 983a0932c3..a61836e101 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			draggable="false"
 			tabindex="-1"
 			style="-webkit-user-drag: none;"
-		/>
+		></canvas>
 		<img
 			v-show="!hide"
 			key="img"
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 8a49fd231d..aebeefe165 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			@input="onInput"
 		>
 		<datalist v-if="datalist" :id="id">
-			<option v-for="data in datalist" :key="data" :value="data"/>
+			<option v-for="data in datalist" :key="data" :value="data"></option>
 		</datalist>
 		<div ref="suffixEl" :class="$style.suffix"><slot name="suffix"></slot></div>
 	</div>
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index a22fb7db01..30494d5362 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -143,8 +143,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<template v-else-if="notification.type === 'receiveFollowRequest'">
 				<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>
 				<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
-					<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
-					<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
+					<MkButton :class="$style.followRequestCommandButton" rounded primary @click="acceptFollowRequest()"><i class="ti ti-check"></i> {{ i18n.ts.accept }}</MkButton>
+					<MkButton :class="$style.followRequestCommandButton" rounded danger @click="rejectFollowRequest()"><i class="ti ti-x"></i> {{ i18n.ts.reject }}</MkButton>
 				</div>
 			</template>
 			<span v-else-if="notification.type === 'test'" :class="$style.text">{{ i18n.ts._notification.notificationWillBeDisplayedLikeThis }}</span>
diff --git a/packages/frontend/src/components/MkPagingButtons.vue b/packages/frontend/src/components/MkPagingButtons.vue
index fe59efd83a..10f432e50d 100644
--- a/packages/frontend/src/components/MkPagingButtons.vue
+++ b/packages/frontend/src/components/MkPagingButtons.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div :class="$style.buttons">
 		<div v-if="prevDotVisible" :class="$style.headTailButtons">
 			<MkButton @click="onToHeadButtonClicked">{{ min }}</MkButton>
-			<span class="ti ti-dots"/>
+			<span class="ti ti-dots"></span>
 		</div>
 
 		<MkButton
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</MkButton>
 
 		<div v-if="nextDotVisible" :class="$style.headTailButtons">
-			<span class="ti ti-dots"/>
+			<span class="ti ti-dots"></span>
 			<MkButton @click="onToTailButtonClicked">{{ max }}</MkButton>
 		</div>
 	</div>
diff --git a/packages/frontend/src/components/MkPolkadots.vue b/packages/frontend/src/components/MkPolkadots.vue
index 4f1346b685..ca7bfd3a93 100644
--- a/packages/frontend/src/components/MkPolkadots.vue
+++ b/packages/frontend/src/components/MkPolkadots.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"/>
+<div :class="[$style.root, accented ? $style.accented : null, revered ? $style.revered : null]"></div>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 9d5d2392d0..72a7f4a01c 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -77,7 +77,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
 		<div v-if="targetChannel" :class="$style.colorBar" :style="{ background: targetChannel.color }"></div>
-		<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
+		<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"></textarea>
 		<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
 	</div>
 	<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
@@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</footer>
 	<datalist id="hashtags">
-		<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"/>
+		<option v-for="hashtag in recentHashtags" :key="hashtag" :value="hashtag"></option>
 	</datalist>
 </div>
 </template>
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index 89aca5d29b..4d936758c5 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</div>
 
-	<slot/>
+	<slot></slot>
 </div>
 </template>
 
diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue
index ab7f86842c..67fd570b41 100644
--- a/packages/frontend/src/components/MkReactionsViewer.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:myReaction="props.myReaction"
 		@reactionToggled="onMockToggleReaction"
 	/>
-	<slot v-if="hasMoreReactions" name="more"/>
+	<slot v-if="hasMoreReactions" name="more"></slot>
 </component>
 </template>
 
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index fd1d0c2b63..b413fef3b8 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -11,16 +11,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div v-if="user.isCat" :class="[$style.ears]">
 		<div :class="$style.earLeft">
 			<div v-if="false" :class="$style.layer">
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
 			</div>
 		</div>
 		<div :class="$style.earRight">
 			<div v-if="false" :class="$style.layer">
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
-				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"></div>
 			</div>
 		</div>
 	</div>
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index 7c3f736506..591d8fa736 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -37,8 +37,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<template #label>{{ i18n.ts._abuseReport._notificationRecipient.notifiedWebhook }}</template>
 						</MkSelect>
 						<MkButton rounded :class="$style.systemWebhookEditButton" @click="onEditSystemWebhookClicked">
-							<span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"/>
-							<span v-else class="ti ti-settings" style="line-height: normal"/>
+							<span v-if="systemWebhookId === null" class="ti ti-plus" style="line-height: normal"></span>
+							<span v-else class="ti ti-settings" style="line-height: normal"></span>
 						</MkButton>
 					</div>
 				</div>
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
index 36d586bd23..ba5830c2e8 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div :class="$style.root" class="_panel _gaps_s">
-	<div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"/> {{ methodName }}</div>
+	<div :class="$style.rightDivider" style="width: 80px;"><span :class="`ti ${methodIcon}`"></span> {{ methodName }}</div>
 	<div :class="$style.rightDivider" style="flex: 0.5">{{ entity.name }}</div>
 	<div :class="$style.rightDivider" style="flex: 1">
 		<div v-if="method === 'email' && user">
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="$style.recipientButtons" style="margin-left: auto">
 		<button :class="$style.recipientButton" @click="onEditButtonClicked()">
-			<span class="ti ti-settings"/>
+			<span class="ti ti-settings"></span>
 		</button>
 		<button :class="$style.recipientButton" @click="onDeleteButtonClicked()">
-			<span class="ti ti-trash"/>
+			<span class="ti ti-trash"></span>
 		</button>
 	</div>
 </div>
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
index 893bd8d6d3..a9cf372c0e 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.root" class="_gaps_m">
 			<div :class="$style.addButton">
 				<MkButton primary @click="onAddButtonClicked">
-					<span class="ti ti-plus"/> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }}
+					<span class="ti ti-plus"></span> {{ i18n.ts._abuseReport._notificationRecipient.createRecipient }}
 				</MkButton>
 			</div>
 			<div :class="$style.subMenus" class="_gaps_s">
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index b192129548..02aad732f6 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -21,8 +21,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #default="{ item, index, dragStart }">
 					<div :class="$style.item">
 						<div :class="$style.itemHeader">
-							<div :class="$style.itemNumber" v-text="String(index + 1)"/>
-							<span :class="$style.itemHandle" :draggable="true" @dragstart.stop="dragStart"><i class="ti ti-menu"/></span>
+							<div :class="$style.itemNumber">{{ index + 1 }}</div>
+							<span :class="$style.itemHandle" :draggable="true" @dragstart.stop="dragStart"><i class="ti ti-menu"></i></span>
 							<button class="_button" :class="$style.itemRemove" @click="remove(item.id)"><i class="ti ti-x"></i></button>
 						</div>
 						<MkInput :modelValue="item.text" @update:modelValue="serverRules[index].text = $event"/>
diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue
index b53667e98c..9807cbb313 100644
--- a/packages/frontend/src/pages/admin/system-webhook.item.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.item.vue
@@ -8,14 +8,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #label>{{ entity.name || entity.url }}</template>
 	<template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template>
 	<template #icon>
-		<i v-if="!entity.isActive" class="ti ti-player-pause"/>
-		<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
+		<i v-if="!entity.isActive" class="ti ti-player-pause"></i>
+		<i v-else-if="entity.latestStatus === null" class="ti ti-circle"></i>
 		<i
 			v-else-if="[200, 201, 204].includes(entity.latestStatus)"
 			class="ti ti-check"
 			:style="{ color: 'var(--MI_THEME-success)' }"
-		/>
-		<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"/>
+		></i>
+		<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i>
 	</template>
 	<template #suffix>
 		<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 4c677a8d09..21e4657b2c 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
 				<img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
 				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
-				<canvas ref="canvasEl" :class="$style.canvas"/>
+				<canvas ref="canvasEl" :class="$style.canvas"></canvas>
 				<Transition
 					:enterActiveClass="$style.transition_combo_enterActive"
 					:leaveActiveClass="$style.transition_combo_leaveActive"
@@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</Transition>
 					<template v-if="dropReady && currentPick">
 						<img src="/client-assets/drop-and-fusion/drop-arrow.svg" :class="$style.currentMonoArrow"/>
-						<div :class="$style.dropGuide"/>
+						<div :class="$style.dropGuide"></div>
 					</template>
 				</div>
 				<div v-if="isGameOver && !replaying" :class="$style.gameOverLabel">
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 1b729837a7..2404fd9744 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -18,11 +18,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<p class="acct">@{{ acct(displayUser(req)) }}</p>
 							</div>
 							<div v-if="tab === 'list'" class="commands">
-								<MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
-								<MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
+								<MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"></i> {{ i18n.ts.accept }}</MkButton>
+								<MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"></i> {{ i18n.ts.reject }}</MkButton>
 							</div>
 							<div v-else class="commands">
-								<MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.cancel }}</MkButton>
+								<MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
 							</div>
 						</div>
 					</div>
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index fcd598fe76..bf71845a38 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
 
 					<div v-if="$i.twoFactorEnabled" class="_gaps_s">
-						<div v-text="i18n.ts._2fa.alreadyRegistered"/>
+						<div>{{ i18n.ts._2fa.alreadyRegistered }}</div>
 						<template v-if="$i.securityKeysList!.length > 0">
 							<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
 							<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index c472c4da7b..64b03bc4bc 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 									<span v-if="user.isLocked"><i class="ti ti-lock"></i></span>
 									<span v-if="user.isBot"><i class="ti ti-robot"></i></span>
 									<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
-										<i class="ti ti-edit"/> {{ i18n.ts.addMemo }}
+										<i class="ti ti-edit"></i> {{ i18n.ts.addMemo }}
 									</button>
 								</div>
 							</div>
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</div>
 						</div>
 						<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
-							<div class="heading" v-text="i18n.ts.memo"/>
+							<div class="heading">{{ i18n.ts.memo }}</div>
 							<textarea
 								ref="memoTextareaEl"
 								v-model="memoDraft"
@@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 								@focus="isEditingMemo = true"
 								@blur="updateMemo"
 								@input="adjustMemoTextarea"
-							/>
+							></textarea>
 						</div>
 						<div class="description">
 							<MkOmit>
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 0e59913c4c..aab0cde1c8 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <XColumn :menu="menu" :column="column" :isStacked="isStacked" :refresher="async () => { await timeline?.reloadTimeline() }">
 	<template #header>
-		<i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"/>
+		<i v-if="column.tl != null" :class="basicTimelineIconClass(column.tl)"></i>
 		<span style="margin-left: 8px;">{{ column.name || (column.tl ? i18n.ts._timelines[column.tl] : null) || i18n.ts._deck._columns.tl }}</span>
 	</template>
 
-- 
cgit v1.2.3-freya

