<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 > <slot></slot> </time> </template> <script lang="ts" setup> import { onUnmounted } from "vue"; import { i18n } from "@/i18n"; const props = withDefaults( defineProps<{ time: Date | string; mode?: "relative" | "absolute" | "detail" | "none"; }>(), { mode: "relative", } ); const _time = typeof props.time === "string" ? new Date(props.time) : props.time; const absolute = _time.toLocaleString(); let now = $shallowRef(new Date()); const relative = $computed(() => { const ago = (now.getTime() - _time.getTime()) / 1000; /*ms*/ return ago >= 31536000 ? i18n.t("_ago.yearsAgo", { n: Math.round(ago / 31536000).toString() }) : ago >= 2592000 ? i18n.t("_ago.monthsAgo", { n: Math.round(ago / 2592000).toString() }) : ago >= 604800 ? i18n.t("_ago.weeksAgo", { n: Math.round(ago / 604800).toString() }) : ago >= 86400 ? i18n.t("_ago.daysAgo", { n: Math.round(ago / 86400).toString() }) : ago >= 3600 ? i18n.t("_ago.hoursAgo", { n: Math.round(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.ts._ago.justNow : i18n.ts._ago.future; }); 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.cancelAnimationFrame(tickId); }); } </script>