You've already forked AstralRinth
forked from didirus/AstralRinth
fix: standardize relative timestamping (#3612)
* fix(frontend): relative timestamps are incorrectly rounded. Closes: #1371 * fix(all): remove legacy fromNow for proper relative timestamp creation Closes: #1395
This commit is contained in:
@@ -80,6 +80,8 @@ import EnvironmentIndicator from './EnvironmentIndicator.vue'
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { useRelativeTime } from '../../composables'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -191,6 +193,10 @@ export default defineComponent({
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(_) {
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
return { formatRelativeTime }
|
||||
},
|
||||
computed: {
|
||||
toColor() {
|
||||
let color = this.color
|
||||
@@ -205,13 +211,13 @@ export default defineComponent({
|
||||
return dayjs(this.createdAt).format('MMMM D, YYYY [at] h:mm:ss A')
|
||||
},
|
||||
sinceCreation() {
|
||||
return dayjs(this.createdAt).fromNow()
|
||||
return this.formatRelativeTime(this.createdAt)
|
||||
},
|
||||
updatedDate() {
|
||||
return dayjs(this.updatedAt).format('MMMM D, YYYY [at] h:mm:ss A')
|
||||
},
|
||||
sinceUpdated() {
|
||||
return dayjs(this.updatedAt).fromNow()
|
||||
return this.formatRelativeTime(this.updatedAt)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -48,8 +48,10 @@ import dayjs from 'dayjs'
|
||||
import { useVIntl, defineMessages } from '@vintl/vintl'
|
||||
import { computed, ref } from 'vue'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
import { useRelativeTime } from '../../composables'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -70,7 +72,7 @@ const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtrac
|
||||
const future = computed(() => props.entry.date.isAfter(currentDate.value))
|
||||
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
|
||||
|
||||
const relativeDate = computed(() => props.entry.date.fromNow())
|
||||
const relativeDate = computed(() => formatRelativeTime(props.entry.date.toISOString()))
|
||||
const longDate = computed(() => props.entry.date.format('MMMM D, YYYY'))
|
||||
const versionName = computed(() => props.entry.version ?? longDate.value)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<HistoryIcon class="shrink-0" />
|
||||
<span>
|
||||
<span class="text-secondary">Updated</span>
|
||||
{{ dayjs(project.date_modified ?? project.updated).fromNow() }}
|
||||
{{ formatRelativeTime(project.date_modified ?? project.updated) }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
@@ -67,10 +67,9 @@
|
||||
import { TagsIcon, DownloadIcon, HeartIcon, HistoryIcon } from '@modrinth/assets'
|
||||
import Avatar from '../base/Avatar.vue'
|
||||
import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRelativeTime } from '../../composables'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
defineProps({
|
||||
project: {
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
class="z-[1] flex cursor-help items-center gap-1 text-nowrap font-medium xl:self-center"
|
||||
>
|
||||
<CalendarIcon class="xl:hidden" />
|
||||
{{ dayjs(version.date_published).fromNow() }}
|
||||
{{ formatRelativeTime(version.date_published) }}
|
||||
</div>
|
||||
<div
|
||||
class="pointer-events-none z-[1] flex items-center gap-1 font-medium xl:self-center"
|
||||
@@ -185,11 +185,12 @@ import { Pagination, VersionChannelIndicator, VersionFilterControl } from '../in
|
||||
import { useVIntl } from '@vintl/vintl'
|
||||
import { type Ref, ref, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import dayjs from 'dayjs'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
import TagItem from '../base/TagItem.vue'
|
||||
import { useRelativeTime } from '../../composables'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
type VersionWithDisplayUrlEnding = Version & {
|
||||
displayUrlEnding: string
|
||||
|
||||
@@ -68,8 +68,10 @@ import { BookTextIcon, CalendarIcon, ScaleIcon, VersionIcon, ExternalIcon } from
|
||||
import { useVIntl, defineMessages } from '@vintl/vintl'
|
||||
import { computed } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useRelativeTime } from '../../composables'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
const formatRelativeTime = useRelativeTime()
|
||||
|
||||
const props = defineProps<{
|
||||
project: {
|
||||
@@ -89,16 +91,16 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const createdDate = computed(() =>
|
||||
props.project.published ? dayjs(props.project.published).fromNow() : 'unknown',
|
||||
props.project.published ? formatRelativeTime(props.project.published) : 'unknown',
|
||||
)
|
||||
const submittedDate = computed(() =>
|
||||
props.project.queued ? dayjs(props.project.queued).fromNow() : 'unknown',
|
||||
props.project.queued ? formatRelativeTime(props.project.queued) : 'unknown',
|
||||
)
|
||||
const publishedDate = computed(() =>
|
||||
props.project.approved ? dayjs(props.project.approved).fromNow() : 'unknown',
|
||||
props.project.approved ? formatRelativeTime(props.project.approved) : 'unknown',
|
||||
)
|
||||
const updatedDate = computed(() =>
|
||||
props.project.updated ? dayjs(props.project.updated).fromNow() : 'unknown',
|
||||
props.project.updated ? formatRelativeTime(props.project.updated) : 'unknown',
|
||||
)
|
||||
|
||||
const licenseIdDisplay = computed(() => {
|
||||
|
||||
23
packages/ui/src/composables/how-ago.ts
Normal file
23
packages/ui/src/composables/how-ago.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createFormatter, type FormatOptions, type Formatter } from '@vintl/how-ago'
|
||||
import type { IntlController } from '@vintl/vintl/controller'
|
||||
import { useVIntl } from '@vintl/vintl'
|
||||
import { computed } from 'vue'
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const formatters = new WeakMap<IntlController<any>, Formatter>()
|
||||
|
||||
export function useRelativeTime(): Formatter {
|
||||
const vintl = useVIntl()
|
||||
|
||||
let formatter = formatters.get(vintl)
|
||||
|
||||
if (formatter == null) {
|
||||
const formatterRef = computed(() => createFormatter(vintl.intl))
|
||||
const defaultOptions: FormatOptions = { roundingMode: 'halfExpand' as const }
|
||||
|
||||
formatter = (value, options) => formatterRef.value(value, { ...options, ...defaultOptions })
|
||||
formatters.set(vintl, formatter)
|
||||
}
|
||||
|
||||
return formatter
|
||||
}
|
||||
1
packages/ui/src/composables/index.ts
Normal file
1
packages/ui/src/composables/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './how-ago'
|
||||
4
packages/ui/src/utils/index.ts
Normal file
4
packages/ui/src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './common-messages'
|
||||
export * from './game-modes'
|
||||
export * from './notices'
|
||||
export * from './search'
|
||||
Reference in New Issue
Block a user