wip: refactor(client): migrate components to composition api
This commit is contained in:
		
							parent
							
								
									9693dfb09d
								
							
						
					
					
						commit
						7cbeef21e1
					
				
					 6 changed files with 214 additions and 274 deletions
				
			
		|  | @ -4,130 +4,113 @@ | |||
| </a> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <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 } from '@/scripts/popout'; | ||||
| import { ColdDeviceStorage } from '@/store'; | ||||
| import { popout as popout_ } from '@/scripts/popout'; | ||||
| import { i18n } from '@/i18n'; | ||||
| import { defaultStore } from '@/store'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	inject: { | ||||
| 		navHook: { | ||||
| 			default: null | ||||
| 		}, | ||||
| 		sideViewHook: { | ||||
| 			default: null | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	to: string; | ||||
| 	activeClass?: null | string; | ||||
| 	behavior?: null | 'window' | 'browser' | 'modalWindow'; | ||||
| }>(), { | ||||
| 	activeClass: null, | ||||
| 	behavior: null, | ||||
| }); | ||||
| 
 | ||||
| const navHook = inject('navHook', null); | ||||
| const sideViewHook = inject('sideViewHook', null); | ||||
| 
 | ||||
| const active = $computed(() => { | ||||
| 	if (props.activeClass == null) return false; | ||||
| 	const resolved = router.resolve(props.to); | ||||
| 	if (resolved.path === router.currentRoute.value.path) return true; | ||||
| 	if (resolved.name == null) return false; | ||||
| 	if (router.currentRoute.value.name == null) return false; | ||||
| 	return resolved.name === router.currentRoute.value.name; | ||||
| }); | ||||
| 
 | ||||
| function onContextmenu(ev) { | ||||
| 	if (window.getSelection().toString() !== '') return; | ||||
| 	os.contextMenu([{ | ||||
| 		type: 'label', | ||||
| 		text: props.to, | ||||
| 	}, { | ||||
| 		icon: 'fas fa-window-maximize', | ||||
| 		text: i18n.locale.openInWindow, | ||||
| 		action: () => { | ||||
| 			os.pageWindow(props.to); | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		to: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		activeClass: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 		behavior: { | ||||
| 			type: String, | ||||
| 			required: false, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		active() { | ||||
| 			if (this.activeClass == null) return false; | ||||
| 			const resolved = router.resolve(this.to); | ||||
| 			if (resolved.path == this.$route.path) return true; | ||||
| 			if (resolved.name == null) return false; | ||||
| 			if (this.$route.name == null) return false; | ||||
| 			return resolved.name == this.$route.name; | ||||
| 	}, sideViewHook ? { | ||||
| 		icon: 'fas fa-columns', | ||||
| 		text: i18n.locale.openInSideView, | ||||
| 		action: () => { | ||||
| 			sideViewHook(props.to); | ||||
| 		} | ||||
| 	}, | ||||
| 	} : undefined, { | ||||
| 		icon: 'fas fa-expand-alt', | ||||
| 		text: i18n.locale.showInPage, | ||||
| 		action: () => { | ||||
| 			router.push(props.to); | ||||
| 		} | ||||
| 	}, null, { | ||||
| 		icon: 'fas fa-external-link-alt', | ||||
| 		text: i18n.locale.openInNewTab, | ||||
| 		action: () => { | ||||
| 			window.open(props.to, '_blank'); | ||||
| 		} | ||||
| 	}, { | ||||
| 		icon: 'fas fa-link', | ||||
| 		text: i18n.locale.copyLink, | ||||
| 		action: () => { | ||||
| 			copyToClipboard(`${url}${props.to}`); | ||||
| 		} | ||||
| 	}], ev); | ||||
| } | ||||
| 
 | ||||
| 	methods: { | ||||
| 		onContextmenu(e) { | ||||
| 			if (window.getSelection().toString() !== '') return; | ||||
| 			os.contextMenu([{ | ||||
| 				type: 'label', | ||||
| 				text: this.to, | ||||
| 			}, { | ||||
| 				icon: 'fas fa-window-maximize', | ||||
| 				text: this.$ts.openInWindow, | ||||
| 				action: () => { | ||||
| 					os.pageWindow(this.to); | ||||
| 				} | ||||
| 			}, this.sideViewHook ? { | ||||
| 				icon: 'fas fa-columns', | ||||
| 				text: this.$ts.openInSideView, | ||||
| 				action: () => { | ||||
| 					this.sideViewHook(this.to); | ||||
| 				} | ||||
| 			} : undefined, { | ||||
| 				icon: 'fas fa-expand-alt', | ||||
| 				text: this.$ts.showInPage, | ||||
| 				action: () => { | ||||
| 					this.$router.push(this.to); | ||||
| 				} | ||||
| 			}, null, { | ||||
| 				icon: 'fas fa-external-link-alt', | ||||
| 				text: this.$ts.openInNewTab, | ||||
| 				action: () => { | ||||
| 					window.open(this.to, '_blank'); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: 'fas fa-link', | ||||
| 				text: this.$ts.copyLink, | ||||
| 				action: () => { | ||||
| 					copyToClipboard(`${url}${this.to}`); | ||||
| 				} | ||||
| 			}], e); | ||||
| 		}, | ||||
| function window() { | ||||
| 	os.pageWindow(props.to); | ||||
| } | ||||
| 
 | ||||
| 		window() { | ||||
| 			os.pageWindow(this.to); | ||||
| 		}, | ||||
| function modalWindow() { | ||||
| 	os.modalPageWindow(props.to); | ||||
| } | ||||
| 
 | ||||
| 		modalWindow() { | ||||
| 			os.modalPageWindow(this.to); | ||||
| 		}, | ||||
| function popout() { | ||||
| 	popout_(props.to); | ||||
| } | ||||
| 
 | ||||
| 		popout() { | ||||
| 			popout(this.to); | ||||
| 		}, | ||||
| function nav() { | ||||
| 	if (props.behavior === 'browser') { | ||||
| 		location.href = props.to; | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 		nav() { | ||||
| 			if (this.behavior === 'browser') { | ||||
| 				location.href = this.to; | ||||
| 				return; | ||||
| 			} | ||||
| 
 | ||||
| 			if (this.behavior) { | ||||
| 				if (this.behavior === 'window') { | ||||
| 					return this.window(); | ||||
| 				} else if (this.behavior === 'modalWindow') { | ||||
| 					return this.modalWindow(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if (this.navHook) { | ||||
| 				this.navHook(this.to); | ||||
| 			} else { | ||||
| 				if (this.$store.state.defaultSideView && this.sideViewHook && this.to !== '/') { | ||||
| 					return this.sideViewHook(this.to); | ||||
| 				} | ||||
| 
 | ||||
| 				if (this.$router.currentRoute.value.path === this.to) { | ||||
| 					window.scroll({ top: 0, behavior: 'smooth' }); | ||||
| 				} else { | ||||
| 					this.$router.push(this.to); | ||||
| 				} | ||||
| 			} | ||||
| 	if (props.behavior) { | ||||
| 		if (props.behavior === 'window') { | ||||
| 			return window(); | ||||
| 		} else if (props.behavior === 'modalWindow') { | ||||
| 			return modalWindow(); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| 	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); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -1,74 +1,54 @@ | |||
| <template> | ||||
| <span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :title="acct(user)" @click="onClick"> | ||||
| <span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick"> | ||||
| 	<img class="inner" :src="url" decoding="async"/> | ||||
| 	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> | ||||
| </span> | ||||
| <MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat, square: $store.state.squareAvatars }" :to="userPage(user)" :title="acct(user)" :target="target"> | ||||
| <MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target"> | ||||
| 	<img class="inner" :src="url" decoding="async"/> | ||||
| 	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/> | ||||
| </MkA> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { onMounted, watch } from 'vue'; | ||||
| import * as misskey from 'misskey-js'; | ||||
| import { getStaticImageUrl } from '@/scripts/get-static-image-url'; | ||||
| import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; | ||||
| import { acct, userPage } from '@/filters/user'; | ||||
| import MkUserOnlineIndicator from '@/components/user-online-indicator.vue'; | ||||
| import { defaultStore } from '@/store'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkUserOnlineIndicator | ||||
| 	}, | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		target: { | ||||
| 			required: false, | ||||
| 			default: null | ||||
| 		}, | ||||
| 		disableLink: { | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		disablePreview: { | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		showIndicator: { | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		} | ||||
| 	}, | ||||
| 	emits: ['click'], | ||||
| 	computed: { | ||||
| 		cat(): boolean { | ||||
| 			return this.user.isCat; | ||||
| 		}, | ||||
| 		url(): string { | ||||
| 			return this.$store.state.disableShowingAnimatedImages | ||||
| 				? getStaticImageUrl(this.user.avatarUrl) | ||||
| 				: this.user.avatarUrl; | ||||
| 		}, | ||||
| 	}, | ||||
| 	watch: { | ||||
| 		'user.avatarBlurhash'() { | ||||
| 			if (this.$el == null) return; | ||||
| 			this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); | ||||
| 		} | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.$el.style.color = extractAvgColorFromBlurhash(this.user.avatarBlurhash); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onClick(e) { | ||||
| 			this.$emit('click', e); | ||||
| 		}, | ||||
| 		acct, | ||||
| 		userPage | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	user: misskey.entities.User; | ||||
| 	target?: string | null; | ||||
| 	disableLink?: boolean; | ||||
| 	disablePreview?: boolean; | ||||
| 	showIndicator?: boolean; | ||||
| }>(), { | ||||
| 	target: null, | ||||
| 	disableLink: false, | ||||
| 	disablePreview: false, | ||||
| 	showIndicator: false, | ||||
| }); | ||||
| 
 | ||||
| const emit = defineEmits<{ | ||||
| 	(e: 'click', ev: MouseEvent): void; | ||||
| }>(); | ||||
| 
 | ||||
| const url = defaultStore.state.disableShowingAnimatedImages | ||||
| 	? getStaticImageUrl(props.user.avatarUrl) | ||||
| 	: props.user.avatarUrl; | ||||
| 
 | ||||
| function onClick(ev: MouseEvent) { | ||||
| 	emit('click', ev); | ||||
| } | ||||
| 
 | ||||
| let color = $ref(); | ||||
| 
 | ||||
| watch(() => props.user.avatarBlurhash, () => { | ||||
| 	color = extractAvgColorFromBlurhash(props.user.avatarBlurhash); | ||||
| }, { | ||||
| 	immediate: true, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,27 +4,17 @@ | |||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		inline: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 		colored: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: true | ||||
| 		}, | ||||
| 		mini: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: false | ||||
| 		}, | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	inline?: boolean; | ||||
| 	colored?: boolean; | ||||
| 	mini?: boolean; | ||||
| }>(), { | ||||
| 	inline: false, | ||||
| 	colored: true, | ||||
| 	mini: false, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,15 +1,23 @@ | |||
| <template> | ||||
| <mfm-core v-bind="$attrs" class="havbbuyv" :class="{ nowrap: $attrs['nowrap'] }"/> | ||||
| <MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :customEmojis="customEmojis" :isNote="isNote" class="havbbuyv" :class="{ nowrap }"/> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import MfmCore from '@/components/mfm'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MfmCore | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	text: string; | ||||
| 	plain?: boolean; | ||||
| 	nowrap?: boolean; | ||||
| 	author?: any; | ||||
| 	customEmojis?: any; | ||||
| 	isNote?: boolean; | ||||
| }>(), { | ||||
| 	plain: false, | ||||
| 	nowrap: false, | ||||
| 	author: null, | ||||
| 	isNote: true, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,73 +1,57 @@ | |||
| <template> | ||||
| <time :title="absolute"> | ||||
| 	<template v-if="mode == 'relative'">{{ relative }}</template> | ||||
| 	<template v-else-if="mode == 'absolute'">{{ absolute }}</template> | ||||
| 	<template v-else-if="mode == 'detail'">{{ absolute }} ({{ relative }})</template> | ||||
| 	<template v-if="mode === 'relative'">{{ relative }}</template> | ||||
| 	<template v-else-if="mode === 'absolute'">{{ absolute }}</template> | ||||
| 	<template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template> | ||||
| </time> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { onUnmounted } from 'vue'; | ||||
| import { i18n } from '@/i18n'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		time: { | ||||
| 			type: [Date, String], | ||||
| 			required: true | ||||
| 		}, | ||||
| 		mode: { | ||||
| 			type: String, | ||||
| 			default: 'relative' | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			tickId: null, | ||||
| 			now: new Date() | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		_time(): Date { | ||||
| 			return typeof this.time == 'string' ? new Date(this.time) : this.time; | ||||
| 		}, | ||||
| 		absolute(): string { | ||||
| 			return this._time.toLocaleString(); | ||||
| 		}, | ||||
| 		relative(): string { | ||||
| 			const time = this._time; | ||||
| 			const ago = (this.now.getTime() - time.getTime()) / 1000/*ms*/; | ||||
| 			return ( | ||||
| 				ago >= 31536000 ? this.$t('_ago.yearsAgo',   { n: (~~(ago / 31536000)).toString() }) : | ||||
| 				ago >= 2592000  ? this.$t('_ago.monthsAgo',  { n: (~~(ago / 2592000)).toString() }) : | ||||
| 				ago >= 604800   ? this.$t('_ago.weeksAgo',   { n: (~~(ago / 604800)).toString() }) : | ||||
| 				ago >= 86400    ? this.$t('_ago.daysAgo',    { n: (~~(ago / 86400)).toString() }) : | ||||
| 				ago >= 3600     ? this.$t('_ago.hoursAgo',   { n: (~~(ago / 3600)).toString() }) : | ||||
| 				ago >= 60       ? this.$t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : | ||||
| 				ago >= 10       ? this.$t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : | ||||
| 				ago >= -1       ? this.$ts._ago.justNow : | ||||
| 				ago <  -1       ? this.$ts._ago.future : | ||||
| 				this.$ts._ago.unknown); | ||||
| 		} | ||||
| 	}, | ||||
| 	created() { | ||||
| 		if (this.mode == 'relative' || this.mode == 'detail') { | ||||
| 			this.tickId = window.requestAnimationFrame(this.tick); | ||||
| 		} | ||||
| 	}, | ||||
| 	unmounted() { | ||||
| 		if (this.mode === 'relative' || this.mode === 'detail') { | ||||
| 			window.clearTimeout(this.tickId); | ||||
| 		} | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		tick() { | ||||
| 			// TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する | ||||
| 			this.now = new Date(); | ||||
| 
 | ||||
| 			this.tickId = setTimeout(() => { | ||||
| 				window.requestAnimationFrame(this.tick); | ||||
| 			}, 10000); | ||||
| 		} | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	time: Date | string; | ||||
| 	mode?: 'relative' | 'absolute' | 'detail'; | ||||
| }>(), { | ||||
| 	mode: 'relative', | ||||
| }); | ||||
| 
 | ||||
| const _time = typeof props.time == 'string' ? new Date(props.time) : props.time; | ||||
| const absolute = _time.toLocaleString(); | ||||
| 
 | ||||
| let now = $ref(new Date()); | ||||
| const relative = $computed(() => { | ||||
| 	const ago = (now.getTime() - _time.getTime()) / 1000/*ms*/; | ||||
| 	return ( | ||||
| 		ago >= 31536000 ? i18n.t('_ago.yearsAgo',   { n: (~~(ago / 31536000)).toString() }) : | ||||
| 		ago >= 2592000  ? i18n.t('_ago.monthsAgo',  { n: (~~(ago / 2592000)).toString() }) : | ||||
| 		ago >= 604800   ? i18n.t('_ago.weeksAgo',   { n: (~~(ago / 604800)).toString() }) : | ||||
| 		ago >= 86400    ? i18n.t('_ago.daysAgo',    { n: (~~(ago / 86400)).toString() }) : | ||||
| 		ago >= 3600     ? i18n.t('_ago.hoursAgo',   { n: (~~(ago / 3600)).toString() }) : | ||||
| 		ago >= 60       ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) : | ||||
| 		ago >= 10       ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) : | ||||
| 		ago >= -1       ? i18n.locale._ago.justNow : | ||||
| 		ago <  -1       ? i18n.locale._ago.future : | ||||
| 		i18n.locale._ago.unknown); | ||||
| }); | ||||
| 
 | ||||
| function tick() { | ||||
| 	// TODO: パフォーマンス向上のため、このコンポーネントが画面内に表示されている場合のみ更新する | ||||
| 	now = new Date(); | ||||
| 
 | ||||
| 	tickId = window.setTimeout(() => { | ||||
| 		window.requestAnimationFrame(tick); | ||||
| 	}, 10000); | ||||
| } | ||||
| 
 | ||||
| let tickId: number; | ||||
| 
 | ||||
| if (props.mode === 'relative' || props.mode === 'detail') { | ||||
| 	tickId = window.requestAnimationFrame(tick); | ||||
| 
 | ||||
| 	onUnmounted(() => { | ||||
| 		window.clearTimeout(tickId); | ||||
| 	}); | ||||
| } | ||||
| </script> | ||||
|  |  | |||
|  | @ -2,19 +2,14 @@ | |||
| <Mfm :text="user.name || user.username" :plain="true" :nowrap="nowrap" :custom-emojis="user.emojis"/> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| <script lang="ts" setup> | ||||
| import { } from 'vue'; | ||||
| import * as misskey from 'misskey-js'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		user: { | ||||
| 			type: Object, | ||||
| 			required: true | ||||
| 		}, | ||||
| 		nowrap: { | ||||
| 			type: Boolean, | ||||
| 			default: true | ||||
| 		}, | ||||
| 	} | ||||
| const props = withDefaults(defineProps<{ | ||||
| 	user: misskey.entities.User; | ||||
| 	nowrap?: boolean; | ||||
| }>(), { | ||||
| 	nowrap: true, | ||||
| }); | ||||
| </script> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue