From 3d4a90b08a284f416564fc950a9216936c1a8e99 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 14 May 2023 11:43:56 +0900
Subject: [PATCH] refactor(frontend): use composition api

---
 packages/frontend/src/components/MkSample.vue | 118 -----------
 .../frontend/src/components/MkSuperMenu.vue   |  21 +-
 .../frontend/src/components/MkTextarea.vue    | 199 ++++++------------
 .../pages/page-editor/page-editor.blocks.vue  |  60 +++---
 .../page-editor/page-editor.container.vue     |  84 +++-----
 packages/frontend/src/pages/preview.vue       |  21 --
 packages/frontend/src/router.ts               |   3 -
 7 files changed, 125 insertions(+), 381 deletions(-)
 delete mode 100644 packages/frontend/src/components/MkSample.vue
 delete mode 100644 packages/frontend/src/pages/preview.vue

diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue
deleted file mode 100644
index 922b862b47..0000000000
--- a/packages/frontend/src/components/MkSample.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<template>
-<div class="">
-	<div class="">
-		<MkInput v-model="text">
-			<template #label>Text</template>
-		</MkInput>
-		<MkSwitch v-model="flag">
-			<span>Switch is now {{ flag ? 'on' : 'off' }}</span>
-		</MkSwitch>
-		<div style="margin: 32px 0;">
-			<MkRadio v-model="radio" value="misskey">Misskey</MkRadio>
-			<MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio>
-			<MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio>
-		</div>
-		<MkButton inline>This is</MkButton>
-		<MkButton inline primary>the button</MkButton>
-	</div>
-	<div class="" style="pointer-events: none;">
-		<Mfm :text="mfm"/>
-	</div>
-	<div class="">
-		<MkButton inline primary @click="openMenu">Open menu</MkButton>
-		<MkButton inline primary @click="openDialog">Open dialog</MkButton>
-		<MkButton inline primary @click="openForm">Open form</MkButton>
-		<MkButton inline primary @click="openDrive">Open drive</MkButton>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@/components/MkButton.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
-import MkTextarea from '@/components/MkTextarea.vue';
-import MkRadio from '@/components/MkRadio.vue';
-import * as os from '@/os';
-import * as config from '@/config';
-import { $i } from '@/account';
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
-		MkSwitch,
-		MkTextarea,
-		MkRadio,
-	},
-
-	data() {
-		return {
-			text: '',
-			flag: true,
-			radio: 'misskey',
-			$i,
-			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: {
-		async openDialog() {
-			os.alert({
-				type: 'warning',
-				title: 'Oh my Aichan',
-				text: 'Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
-			});
-		},
-
-		async openForm() {
-			os.form('Example form', {
-				foo: {
-					type: 'boolean',
-					default: true,
-					label: 'This is a boolean property',
-				},
-				bar: {
-					type: 'number',
-					default: 300,
-					label: 'This is a number property',
-				},
-				baz: {
-					type: 'string',
-					default: 'Misskey makes you happy.',
-					label: 'This is a string property',
-				},
-			});
-		},
-
-		async openDrive() {
-			os.selectDriveFile(false);
-		},
-
-		async selectUser() {
-			os.selectUser();
-		},
-
-		async openMenu(ev) {
-			os.popupMenu([{
-				type: 'label',
-				text: 'Fruits',
-			}, {
-				text: 'Create some apples',
-				action: () => {},
-			}, {
-				text: 'Read some oranges',
-				action: () => {},
-			}, {
-				text: 'Update some melons',
-				action: () => {},
-			}, null, {
-				text: 'Delete some bananas',
-				danger: true,
-				action: () => {},
-			}], ev.currentTarget ?? ev.target);
-		},
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 2a8e43c570..72b70416d9 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -23,22 +23,13 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 
-export default defineComponent({
-	props: {
-		def: {
-			type: Array,
-			required: true,
-		},
-		grid: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-});
+defineProps<{
+	def: any[];
+	grid?: boolean;
+}>();
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index 82b631edda..e8f10ab048 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -26,153 +26,88 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, nextTick, ref, watch, computed, toRefs } from 'vue';
+<script lang="ts" setup>
+import { onMounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-	},
+const props = defineProps<{
+	modelValue: string | null;
+	required?: boolean;
+	readonly?: boolean;
+	disabled?: boolean;
+	pattern?: string;
+	placeholder?: string;
+	autofocus?: boolean;
+	autocomplete?: string;
+	spellcheck?: boolean;
+	debounce?: boolean;
+	manualSave?: boolean;
+	code?: boolean;
+	tall?: boolean;
+	pre?: boolean;
+}>();
 
-	props: {
-		modelValue: {
-			required: true,
-		},
-		type: {
-			type: String,
-			required: false,
-		},
-		required: {
-			type: Boolean,
-			required: false,
-		},
-		readonly: {
-			type: Boolean,
-			required: false,
-		},
-		disabled: {
-			type: Boolean,
-			required: false,
-		},
-		pattern: {
-			type: String,
-			required: false,
-		},
-		placeholder: {
-			type: String,
-			required: false,
-		},
-		autofocus: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		autocomplete: {
-			required: false,
-		},
-		spellcheck: {
-			required: false,
-		},
-		code: {
-			type: Boolean,
-			required: false,
-		},
-		tall: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		pre: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		debounce: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		manualSave: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
+const emit = defineEmits<{
+	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'keydown', _ev: KeyboardEvent): void;
+	(ev: 'enter'): void;
+	(ev: 'update:modelValue', value: string): void;
+}>();
 
-	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
+const { modelValue, autofocus } = toRefs(props);
+const v = ref<string>(modelValue.value ?? '');
+const focused = ref(false);
+const changed = ref(false);
+const invalid = ref(false);
+const filled = computed(() => v.value !== '' && v.value != null);
+const inputEl = shallowRef<HTMLTextAreaElement>();
 
-	setup(props, context) {
-		const { modelValue, autofocus } = toRefs(props);
-		const v = ref(modelValue.value);
-		const focused = ref(false);
-		const changed = ref(false);
-		const invalid = ref(false);
-		const filled = computed(() => v.value !== '' && v.value != null);
-		const inputEl = ref(null);
+const focus = () => inputEl.value.focus();
+const onInput = (ev) => {
+	changed.value = true;
+	emit('change', ev);
+};
+const onKeydown = (ev: KeyboardEvent) => {
+	if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
 
-		const focus = () => inputEl.value.focus();
-		const onInput = (ev) => {
-			changed.value = true;
-			context.emit('change', ev);
-		};
-		const onKeydown = (ev: KeyboardEvent) => {
-			if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
+	emit('keydown', ev);
 
-			context.emit('keydown', ev);
+	if (ev.code === 'Enter') {
+		emit('enter');
+	}
+};
 
-			if (ev.code === 'Enter') {
-				context.emit('enter');
-			}
-		};
+const updated = () => {
+	changed.value = false;
+	emit('update:modelValue', v.value ?? '');
+};
 
-		const updated = () => {
-			changed.value = false;
-			context.emit('update:modelValue', v.value);
-		};
+const debouncedUpdated = debounce(1000, updated);
 
-		const debouncedUpdated = debounce(1000, updated);
+watch(modelValue, newValue => {
+	v.value = newValue;
+});
 
-		watch(modelValue, newValue => {
-			v.value = newValue;
-		});
+watch(v, newValue => {
+	if (!props.manualSave) {
+		if (props.debounce) {
+			debouncedUpdated();
+		} else {
+			updated();
+		}
+	}
 
-		watch(v, newValue => {
-			if (!props.manualSave) {
-				if (props.debounce) {
-					debouncedUpdated();
-				} else {
-					updated();
-				}
-			}
+	invalid.value = inputEl.value.validity.badInput;
+});
 
-			invalid.value = inputEl.value.validity.badInput;
-		});
-
-		onMounted(() => {
-			nextTick(() => {
-				if (autofocus.value) {
-					focus();
-				}
-			});
-		});
-
-		return {
-			v,
-			focused,
-			invalid,
-			changed,
-			filled,
-			inputEl,
-			focus,
-			onInput,
-			onKeydown,
-			updated,
-			i18n,
-		};
-	},
+onMounted(() => {
+	nextTick(() => {
+		if (autofocus.value) {
+			focus();
+		}
+	});
 });
 </script>
 
diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
index 97bdcfe80f..2c3d59256c 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
@@ -9,49 +9,41 @@
 </Sortable>
 </template>
 
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
 import XSection from './els/page-editor.el.section.vue';
 import XText from './els/page-editor.el.text.vue';
 import XImage from './els/page-editor.el.image.vue';
 import XNote from './els/page-editor.el.note.vue';
 
-export default defineComponent({
-	components: {
-		Sortable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
-		XSection, XText, XImage, XNote,
-	},
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
-	props: {
-		modelValue: {
-			type: Array,
-			required: true,
-		},
-	},
+const props = defineProps<{
+	modelValue: any[];
+}>();
 
-	emits: ['update:modelValue'],
+const emit = defineEmits<{
+	(ev: 'update:modelValue', value: any[]): void;
+}>();
 
-	methods: {
-		updateItem(v) {
-			const i = this.modelValue.findIndex(x => x.id === v.id);
-			const newValue = [
-				...this.modelValue.slice(0, i),
-				v,
-				...this.modelValue.slice(i + 1),
-			];
-			this.$emit('update:modelValue', newValue);
-		},
+function updateItem(v) {
+	const i = props.modelValue.findIndex(x => x.id === v.id);
+	const newValue = [
+		...props.modelValue.slice(0, i),
+		v,
+		...props.modelValue.slice(i + 1),
+	];
+	emit('update:modelValue', newValue);
+}
 
-		removeItem(el) {
-			const i = this.modelValue.findIndex(x => x.id === el.id);
-			const newValue = [
-				...this.modelValue.slice(0, i),
-				...this.modelValue.slice(i + 1),
-			];
-			this.$emit('update:modelValue', newValue);
-		},
-	},
-});
+function removeItem(el) {
+	const i = props.modelValue.findIndex(x => x.id === el.id);
+	const newValue = [
+		...props.modelValue.slice(0, i),
+		...props.modelValue.slice(i + 1),
+	];
+	emit('update:modelValue', newValue);
+}
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue
index dd733403af..0842b4fd26 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
+<div class="cpjygsrt">
 	<header>
 		<div class="title"><slot name="header"></slot></div>
 		<div class="buttons">
@@ -16,58 +16,40 @@
 			</button>
 		</div>
 	</header>
-	<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
-	<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
 	<div v-show="showBody" class="body">
 		<slot></slot>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	props: {
-		expanded: {
-			type: Boolean,
-			default: true,
-		},
-		removable: {
-			type: Boolean,
-			default: true,
-		},
-		draggable: {
-			type: Boolean,
-			default: false,
-		},
-		error: {
-			required: false,
-			default: null,
-		},
-		warn: {
-			required: false,
-			default: null,
-		},
-	},
-	emits: ['toggle', 'remove'],
-	data() {
-		return {
-			showBody: this.expanded,
-			i18n,
-		};
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			this.showBody = show;
-			this.$emit('toggle', show);
-		},
-		remove() {
-			this.$emit('remove');
-		},
-	},
+const props = withDefaults(defineProps<{
+	expanded?: boolean;
+	removable?: boolean;
+	draggable?: boolean;
+}>(), {
+	expanded: true,
+	removable: true,
 });
+
+const emit = defineEmits<{
+	(ev: 'toggle', show: boolean): void;
+	(ev: 'remove'): void;
+}>();
+
+const showBody = ref(props.expanded);
+
+function toggleContent(show: boolean) {
+	showBody.value = show;
+	emit('toggle', show);
+}
+
+function remove() {
+	emit('remove');
+}
 </script>
 
 <style lang="scss" scoped>
@@ -128,20 +110,6 @@ export default defineComponent({
 		}
 	}
 
-	> .warn {
-		color: #b19e49;
-		margin: 0;
-		padding: 16px 16px 0 16px;
-		font-size: 14px;
-	}
-
-	> .error {
-		color: #f00;
-		margin: 0;
-		padding: 16px 16px 0 16px;
-		font-size: 14px;
-	}
-
 	> .body {
 		::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
 			&:not(.inline):first-child {
diff --git a/packages/frontend/src/pages/preview.vue b/packages/frontend/src/pages/preview.vue
deleted file mode 100644
index 952af23a53..0000000000
--- a/packages/frontend/src/pages/preview.vue
+++ /dev/null
@@ -1,21 +0,0 @@
-<template>
-<div>
-	<MkSample/>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed } from 'vue';
-import MkSample from '@/components/MkSample.vue';
-import { i18n } from '@/i18n';
-import { definePageMetadata } from '@/scripts/page-metadata';
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata(computed(() => ({
-	title: i18n.ts.preview,
-	icon: 'ti ti-eye',
-})));
-</script>
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index e46c1eeb77..add4bd9217 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -242,9 +242,6 @@ export const routes = [{
 }, {
 	path: '/scratchpad',
 	component: page(() => import('./pages/scratchpad.vue')),
-}, {
-	path: '/preview',
-	component: page(() => import('./pages/preview.vue')),
 }, {
 	path: '/auth/:token',
 	component: page(() => import('./pages/auth.vue')),