Skip to content

Commit 209a302

Browse files
committed
feat: add useFormatRelativeTime composable using @nextcloud/l10n
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent 89e43e5 commit 209a302

File tree

6 files changed

+297
-188
lines changed

6 files changed

+297
-188
lines changed

src/components/NcDateTime/NcDateTime.vue

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ h4 {
9292
<template>
9393
<span class="nc-datetime"
9494
:data-timestamp="timestamp"
95-
:title="formattedFullTime"
95+
:title
9696
v-text="formattedTime" />
9797
</template>
9898

9999
<script setup lang="ts">
100-
import { toRef } from 'vue'
101-
import { useFormatDateTime } from '../../composables/useFormatDateTime.ts'
100+
import { computed, toRef } from 'vue'
101+
import { useFormatRelativeTime, useFormatTime } from '../../composables/useFormatDateTime/index.ts'
102102
103103
const props = withDefaults(defineProps<{
104104
/**
@@ -130,8 +130,15 @@ const props = withDefaults(defineProps<{
130130
relativeTime: 'long',
131131
})
132132
133-
const {
134-
formattedTime,
135-
formattedFullTime,
136-
} = useFormatDateTime(toRef(() => props.timestamp), props)
133+
const timeOptions = computed(() => ({ format: props.format }))
134+
const relativeTimeOptions = computed(() => ({
135+
ignoreSeconds: props.ignoreSeconds,
136+
relativeTime: props.relativeTime || 'long',
137+
update: props.relativeTime !== false,
138+
}))
139+
140+
const title = useFormatTime(toRef(() => props.timestamp), timeOptions)
141+
const relativeTime = useFormatRelativeTime(toRef(() => props.timestamp), relativeTimeOptions)
142+
143+
const formattedTime = computed(() => props.relativeTime ? relativeTime.value : title.value)
137144
</script>

src/composables/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
export * from './useFormatDateTime.ts'
6+
export {
7+
useFormatDateTime,
8+
useFormatRelativeTime,
9+
useFormatTime,
10+
} from './useFormatDateTime/index.ts'
11+
712
export * from './useHotKey/index.ts'
813
export * from './useIsDarkTheme/index.ts'
914
export * from './useIsFullscreen/index.ts'

src/composables/useFormatDateTime.ts

Lines changed: 0 additions & 127 deletions
This file was deleted.
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { FormatDateOptions } from '@nextcloud/l10n'
7+
import type { MaybeRefOrGetter, Ref } from 'vue'
8+
9+
import { formatRelativeTime, getCanonicalLocale } from '@nextcloud/l10n'
10+
import { computed, onUnmounted, readonly, ref, toValue, watchEffect } from 'vue'
11+
import { t } from '../../l10n.js'
12+
13+
interface FormatRelativeTimeOptions extends Partial<Omit<FormatDateOptions, 'ignoreSeconds'>> {
14+
ignoreSeconds?: boolean
15+
16+
/**
17+
* If set to false the relative time will not be updated anymore.
18+
* @default true - Meaning the relative time will be updated if needed
19+
*/
20+
update?: boolean
21+
}
22+
23+
interface FormatTimeOptions {
24+
/**
25+
* Locale to use for formatting.
26+
* @default current locale
27+
*/
28+
locale?: string
29+
30+
/**
31+
* The format used for displaying.
32+
*
33+
* @default { timeStyle: 'medium', dateStyle: 'short' }
34+
*/
35+
format?: Intl.DateTimeFormatOptions
36+
}
37+
38+
/**
39+
* @deprecated
40+
*/
41+
interface LegacyFormatDateTimeOptions {
42+
/**
43+
* The format used for displaying, or if relative time is used the format used for the title
44+
*/
45+
format?: Intl.DateTimeFormatOptions
46+
/**
47+
* Ignore seconds when displaying the relative time and just show `a few seconds ago`
48+
*/
49+
ignoreSeconds?: boolean
50+
/**
51+
* Wether to display the timestamp as time from now
52+
*/
53+
relativeTime?: false | 'long' | 'short' | 'narrow'
54+
}
55+
56+
const FEW_SECONDS_AGO = {
57+
long: t('a few seconds ago'),
58+
short: t('seconds ago'), // FOR TRANSLATORS: Shorter version of 'a few seconds ago'
59+
narrow: t('sec. ago'), // FOR TRANSLATORS: If possible in your language an even shorter version of 'a few seconds ago'
60+
}
61+
62+
/**
63+
* Format a timestamp or date object as relative time.
64+
*
65+
* This is a composable wrapper around `formatRelativeTime` from `@nextcloud/l10n`.
66+
*
67+
* @param timestamp - The timestamp to format
68+
* @param opts - Formatting options
69+
*/
70+
export function useFormatRelativeTime(
71+
timestamp: MaybeRefOrGetter<Date | number> = Date.now(),
72+
opts: MaybeRefOrGetter<FormatRelativeTimeOptions> = {},
73+
): Readonly<Ref<string>> {
74+
let timeoutId: number
75+
76+
/**
77+
* ECMA Date object of the timestamp
78+
*/
79+
const date = computed(() => new Date(toValue(timestamp)))
80+
81+
/**
82+
* Reactive options for `formatRelativeTime` method
83+
*/
84+
const options = computed<FormatDateOptions>(() => {
85+
const { language, relativeTime, ignoreSeconds } = toValue(opts)
86+
return {
87+
...language && { language },
88+
...relativeTime && { relativeTime },
89+
ignoreSeconds: ignoreSeconds
90+
? FEW_SECONDS_AGO[relativeTime || 'long']
91+
: false,
92+
}
93+
})
94+
95+
/**
96+
* The formatted relative time
97+
*/
98+
const relativeTime = ref('')
99+
watchEffect(() => updateRelativeTime())
100+
101+
/**
102+
* Update the relative time string.
103+
* This is the callback for the interval.
104+
*/
105+
function updateRelativeTime() {
106+
relativeTime.value = formatRelativeTime(date.value, options.value)
107+
108+
if (toValue(opts).update !== false) {
109+
const diff = Math.abs(Date.now() - new Date(toValue(timestamp)).getTime())
110+
const interval = diff > 120000 || options.value.ignoreSeconds
111+
? Math.min(diff / 60, 1800000)
112+
: 1000
113+
timeoutId = window.setTimeout(updateRelativeTime, interval)
114+
}
115+
}
116+
117+
// when the component is unmounted we also clear the timeout
118+
onUnmounted(() => timeoutId && window.clearTimeout(timeoutId))
119+
120+
return readonly(relativeTime)
121+
}
122+
123+
/**
124+
* Format a given timestamp or date object as a human readable string.
125+
*
126+
* @param timestamp - Timestamp or date object to format
127+
* @param opts - Formatting options
128+
*/
129+
export function useFormatTime(
130+
timestamp: MaybeRefOrGetter<number | Date>,
131+
opts: MaybeRefOrGetter<FormatTimeOptions>,
132+
): Readonly<Ref<string>> {
133+
const options = computed<Required<FormatTimeOptions>>(() => ({
134+
locale: getCanonicalLocale(),
135+
format: { dateStyle: 'short', timeStyle: 'medium' },
136+
...toValue(opts),
137+
}))
138+
139+
const formatter = computed(() => new Intl.DateTimeFormat(options.value.locale, options.value.format))
140+
141+
return computed(() => formatter.value.format(toValue(timestamp)))
142+
}
143+
144+
/**
145+
* Composable for formatting time stamps using current users locale and language
146+
*
147+
* @param {import('vue').MaybeRefOrGetter<Date | number>} timestamp Current timestamp
148+
* @param {object} opts Optional options
149+
* @param {Intl.DateTimeFormatOptions} opts.format The format used for displaying, or if relative time is used the format used for the title (optional)
150+
* @param {boolean} opts.ignoreSeconds Ignore seconds when displaying the relative time and just show `a few seconds ago`
151+
* @param {false | 'long' | 'short' | 'narrow'} opts.relativeTime Wether to display the timestamp as time from now (optional)
152+
*
153+
* @deprecated use `useFormatRelativeTime` or `useFormatTime` instead.
154+
*/
155+
export function useFormatDateTime(
156+
timestamp: MaybeRefOrGetter<Date|number> = Date.now(),
157+
opts: MaybeRefOrGetter<LegacyFormatDateTimeOptions> = {},
158+
) {
159+
const formattedFullTime = useFormatTime(timestamp, opts)
160+
const relativeTime = useFormatRelativeTime(timestamp, computed(() => {
161+
const options = toValue(opts)
162+
return {
163+
...options,
164+
relativeTime: typeof options.relativeTime === 'string'
165+
? options.relativeTime
166+
: 'long',
167+
}
168+
}))
169+
170+
const formattedTime = computed(() => toValue(opts).relativeTime !== false
171+
? relativeTime.value
172+
: formattedFullTime.value,
173+
)
174+
175+
return {
176+
formattedTime,
177+
formattedFullTime,
178+
}
179+
}

0 commit comments

Comments
 (0)