Skip to content

Commit 45d730c

Browse files
enhance: support unixtime MFM (#394)
Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
1 parent ee6b2f7 commit 45d730c

3 files changed

Lines changed: 138 additions & 0 deletions

File tree

components/mk/Mfm.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import type { VNode } from 'vue';
88
import * as mfm from 'mfm-js';
99
import MkGoogle from '@/components/mk/Google.vue';
1010
import MkSparkle from '@/components/mk/Sparkle.vue';
11+
import MkTime from '@/components/mk/Time.vue';
1112
import MkCustomEmoji from '@/components/mk/CustomEmoji.vue';
1213
import MkMention from '@/components/mk/Mention.vue';
1314
import NuxtLink from '@/components/g/NuxtLink.vue';
1415
import ProseAVue from '@/components/content/ProseA.vue';
16+
import ClockIco from 'bi/clock.svg';
1517

1618
const QUOTE_STYLE = `
1719
display: block;
@@ -248,6 +250,22 @@ export default function(props: {
248250
return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
249251
}
250252
}
253+
case 'unixtime': {
254+
const child = token.children[0];
255+
const unixtime = parseInt(child.type === 'text' ? child.props.text : '');
256+
return h('span', {
257+
style: 'display: inline-block; font-size: 90%; border: solid 1px light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1)); border-radius: 999px; padding: 4px 10px 4px 6px;',
258+
}, [
259+
h(ClockIco, {
260+
style: 'margin-right: 0.25em;',
261+
}),
262+
h(MkTime, {
263+
key: Math.random(),
264+
time: unixtime * 1000,
265+
mode: 'detail',
266+
}),
267+
]);
268+
}
251269
}
252270
if (style == null) {
253271
return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);

components/mk/Time.vue

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<!--
2+
SPDX-FileCopyrightText: syuilo and misskey-project
3+
SPDX-License-Identifier: AGPL-3.0-only
4+
-->
5+
6+
<template>
7+
<time :title="absolute">
8+
<template v-if="invalid">{{ $t('_mk._ago.invalid') }}</template>
9+
<template v-else-if="mode === 'relative'">{{ relative }}</template>
10+
<template v-else-if="mode === 'absolute'">{{ absolute }}</template>
11+
<template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template>
12+
</time>
13+
</template>
14+
15+
<script lang="ts" setup>
16+
import { computed } from 'vue';
17+
const { locale } = useI18n();
18+
19+
const props = withDefaults(defineProps<{
20+
time: Date | string | number | null;
21+
mode?: 'relative' | 'absolute' | 'detail';
22+
}>(), {
23+
mode: 'relative',
24+
});
25+
26+
function getDateSafe(n: Date | string | number) {
27+
try {
28+
if (n instanceof Date) {
29+
return n;
30+
}
31+
return new Date(n);
32+
} catch (err) {
33+
return {
34+
getTime: () => NaN,
35+
};
36+
}
37+
}
38+
39+
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
40+
const _time = props.time == null ? NaN : getDateSafe(props.time).getTime();
41+
const invalid = Number.isNaN(_time);
42+
const dateTimeFormat = new Intl.DateTimeFormat(locale.value, {
43+
year: 'numeric',
44+
month: 'numeric',
45+
day: 'numeric',
46+
hour: 'numeric',
47+
minute: 'numeric',
48+
second: 'numeric',
49+
});
50+
const absolute = !invalid ? dateTimeFormat.format(_time) : $t('_mk._ago.invalid');
51+
const now = ref(Date.now());
52+
53+
// eslint-disable-next-line vue/no-setup-props-reactivity-loss
54+
const ago = computed(() => (now.value - _time) / 1000/*ms*/);
55+
56+
const relative = computed<string>(() => {
57+
if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない
58+
if (invalid) return $t('_mk._ago.invalid');
59+
60+
return (
61+
ago.value >= 31536000 ? $t('_mk._ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) :
62+
ago.value >= 2592000 ? $t('_mk._ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) :
63+
ago.value >= 604800 ? $t('_mk._ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) :
64+
ago.value >= 86400 ? $t('_mk._ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) :
65+
ago.value >= 3600 ? $t('_mk._ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) :
66+
ago.value >= 60 ? $t('_mk._ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) :
67+
ago.value >= 10 ? $t('_mk._ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) :
68+
ago.value >= -3 ? $t('_mk._ago.justNow') :
69+
ago.value < -31536000 ? $t('_mk._timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) :
70+
ago.value < -2592000 ? $t('_mk._timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) :
71+
ago.value < -604800 ? $t('_mk._timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) :
72+
ago.value < -86400 ? $t('_mk._timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) :
73+
ago.value < -3600 ? $t('_mk._timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) :
74+
ago.value < -60 ? $t('_mk._timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) :
75+
$t('_mk._timeIn.seconds', { n: (~~(-ago.value % 60)).toString() })
76+
);
77+
});
78+
79+
let tickId: number;
80+
let currentInterval: number;
81+
82+
function tick() {
83+
now.value = Date.now();
84+
const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
85+
86+
if (currentInterval !== nextInterval) {
87+
if (tickId) window.clearInterval(tickId);
88+
currentInterval = nextInterval;
89+
tickId = window.setInterval(tick, nextInterval);
90+
}
91+
}
92+
93+
if (!invalid && (props.mode === 'relative' || props.mode === 'detail')) {
94+
onMounted(() => {
95+
tick();
96+
});
97+
onUnmounted(() => {
98+
if (tickId) window.clearInterval(tickId);
99+
});
100+
}
101+
</script>

locales/ja-JP.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,25 @@ _content:
350350

351351
_mk:
352352
searchByGoogle: "検索"
353+
_ago:
354+
future: "未来"
355+
justNow: "たった今"
356+
secondsAgo: "{n}秒前"
357+
minutesAgo: "{n}分前"
358+
hoursAgo: "{n}時間前"
359+
daysAgo: "{n}日前"
360+
weeksAgo: "{n}週間前"
361+
monthsAgo: "{n}ヶ月前"
362+
yearsAgo: "{n}年前"
363+
invalid: "日時の解析に失敗"
364+
_timeIn:
365+
seconds: "{n}秒後"
366+
minutes: "{n}分後"
367+
hours: "{n}時間後"
368+
days: "{n}日後"
369+
weeks: "{n}週間後"
370+
months: "{n}ヶ月後"
371+
years: "{n}年後"
353372

354373
_other:
355374
title: "もっと!"

0 commit comments

Comments
 (0)