Impove Intl formatting (#5372)
* Improve Intl formatting * Additional fixes * Fixed formatters were not updated on locale change * Fixed formatNumber was not updated on locale change * Additional formatting and fixes after merge * Run prepr:frontend * Remove `'` in icon map * Run `pnpm install` * fix: lint + import * Additional fixes --------- Co-authored-by: Calum H. <calum@modrinth.com> Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
|
||||||
import { Avatar, FormattedTag, TagItem } from '@modrinth/ui'
|
import { Avatar, FormattedTag, TagItem, useCompactNumber } from '@modrinth/ui'
|
||||||
import { formatNumber } from '@modrinth/utils'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -11,6 +10,8 @@ dayjs.extend(relativeTime)
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
project: {
|
project: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -96,13 +97,13 @@ const toTransparent = computed(() => {
|
|||||||
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
|
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
{{ formatNumber(project.downloads) }}
|
{{ formatCompactNumber(project.downloads) }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
|
class="flex items-center gap-1 pr-2 border-0 border-r-[1px] border-solid border-button-border"
|
||||||
>
|
>
|
||||||
<HeartIcon />
|
<HeartIcon />
|
||||||
{{ formatNumber(project.follows) }}
|
{{ formatCompactNumber(project.follows) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 pr-2">
|
<div class="flex items-center gap-1 pr-2">
|
||||||
<TagIcon />
|
<TagIcon />
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<div v-if="diffs.length" class="flex flex-col gap-2">
|
<div v-if="diffs.length" class="flex flex-col gap-2">
|
||||||
<span v-if="publishedDate" class="text-contrast font-semibold">{{
|
<span v-if="publishedDate" class="text-contrast font-semibold">{{
|
||||||
formatMessage(messages.publishedDate, { date: publishedDate })
|
formatDate(publishedDate)
|
||||||
}}</span>
|
}}</span>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<div v-if="removedCount" class="flex gap-1 items-center">
|
<div v-if="removedCount" class="flex gap-1 items-center">
|
||||||
@@ -136,6 +136,7 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
NewModal,
|
NewModal,
|
||||||
|
useFormatDateTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
@@ -186,6 +187,7 @@ type ProjectInfo = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
const installStore = useInstall()
|
const installStore = useInstall()
|
||||||
type UpdateCompleteCallback = () => void | Promise<void>
|
type UpdateCompleteCallback = () => void | Promise<void>
|
||||||
|
|
||||||
@@ -409,10 +411,6 @@ const messages = defineMessages({
|
|||||||
defaultMessage:
|
defaultMessage:
|
||||||
'An update is required to play {name}. Please update to the latest version to launch the game.',
|
'An update is required to play {name}. Please update to the latest version to launch the game.',
|
||||||
},
|
},
|
||||||
publishedDate: {
|
|
||||||
id: 'app.modal.update-to-play.published-date',
|
|
||||||
defaultMessage: '{date, date, long}',
|
|
||||||
},
|
|
||||||
removedCount: {
|
removedCount: {
|
||||||
id: 'app.modal.update-to-play.removed-count',
|
id: 'app.modal.update-to-play.removed-count',
|
||||||
defaultMessage: '{count} removed',
|
defaultMessage: '{count} removed',
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
SmartClickable,
|
SmartClickable,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import type { Dayjs } from 'dayjs'
|
import type { Dayjs } from 'dayjs'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
@@ -36,6 +36,10 @@ import { handleSevereError } from '@/store/error'
|
|||||||
const { handleError } = injectNotificationManager()
|
const { handleError } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -145,11 +149,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 text-sm text-secondary">
|
<div class="flex items-center gap-2 text-sm text-secondary">
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="instance.last_played ? formatDateTime(instance.last_played) : null"
|
||||||
instance.last_played
|
|
||||||
? dayjs(instance.last_played).format('MMMM D, YYYY [at] h:mm A')
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
class="w-fit shrink-0"
|
class="w-fit shrink-0"
|
||||||
:class="{ 'cursor-help smart-clickable:allow-pointer-events': last_played }"
|
:class="{ 'cursor-help smart-clickable:allow-pointer-events': last_played }"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -26,10 +26,12 @@ import {
|
|||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
SmartClickable,
|
SmartClickable,
|
||||||
TagItem,
|
TagItem,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatNumber,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatNumber, getPingLevel } from '@modrinth/utils'
|
import { getPingLevel } from '@modrinth/utils'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
@@ -51,6 +53,11 @@ import { LockIcon } from '../../../../../../packages/assets/generated-icons'
|
|||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -258,7 +265,7 @@ const messages = defineMessages({
|
|||||||
/>
|
/>
|
||||||
<Tooltip :disabled="!hasPlayersTooltip">
|
<Tooltip :disabled="!hasPlayersTooltip">
|
||||||
<span :class="{ 'cursor-help': hasPlayersTooltip }">
|
<span :class="{ 'cursor-help': hasPlayersTooltip }">
|
||||||
{{ formatNumber(serverStatus.players?.online, false) }}
|
{{ formatNumber(serverStatus.players?.online) }}
|
||||||
online
|
online
|
||||||
</span>
|
</span>
|
||||||
<template #popper>
|
<template #popper>
|
||||||
@@ -279,9 +286,7 @@ const messages = defineMessages({
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 text-sm text-secondary">
|
<div class="flex items-center gap-2 text-sm text-secondary">
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="world.last_played ? formatDateTime(world.last_played) : null"
|
||||||
world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null
|
|
||||||
"
|
|
||||||
class="w-fit shrink-0"
|
class="w-fit shrink-0"
|
||||||
:class="{
|
:class="{
|
||||||
'cursor-help smart-clickable:allow-pointer-events': world.last_played,
|
'cursor-help smart-clickable:allow-pointer-events': world.last_played,
|
||||||
|
|||||||
@@ -47,9 +47,6 @@
|
|||||||
"app.modal.update-to-play.header": {
|
"app.modal.update-to-play.header": {
|
||||||
"message": "Update to play"
|
"message": "Update to play"
|
||||||
},
|
},
|
||||||
"app.modal.update-to-play.published-date": {
|
|
||||||
"message": "{date, date, long}"
|
|
||||||
},
|
|
||||||
"app.modal.update-to-play.removed-count": {
|
"app.modal.update-to-play.removed-count": {
|
||||||
"message": "{count} removed"
|
"message": "{count} removed"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,13 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="gallery-time">
|
<span class="gallery-time">
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
{{
|
{{ formatDate(new Date(image.created)) }}
|
||||||
new Date(image.created).toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +85,7 @@ import {
|
|||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, Card } from '@modrinth/ui'
|
import { Button, Card, useFormatDateTime } from '@modrinth/ui'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||||
@@ -99,6 +93,12 @@ import { trackEvent } from '@/helpers/analytics'
|
|||||||
|
|
||||||
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
const MC_SERVER_BANNER_NAME = '__mc_server_banner__'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
project: {
|
project: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -136,22 +136,7 @@
|
|||||||
<div class="metadata-item">
|
<div class="metadata-item">
|
||||||
<span class="metadata-label">Publication Date</span>
|
<span class="metadata-label">Publication Date</span>
|
||||||
<span class="metadata-value">
|
<span class="metadata-value">
|
||||||
{{
|
{{ formatDateTime(version.date_published) }}
|
||||||
new Date(version.date_published).toLocaleString('en-US', {
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
at
|
|
||||||
{{
|
|
||||||
new Date(version.date_published).toLocaleString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
second: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="author" class="metadata-item">
|
<div v-if="author" class="metadata-item">
|
||||||
@@ -183,7 +168,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
|
import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
|
||||||
import { Avatar, Badge, Breadcrumbs, Button, Card, CopyCode } from '@modrinth/ui'
|
import { Avatar, Badge, Breadcrumbs, Button, Card, CopyCode, useFormatDateTime } from '@modrinth/ui'
|
||||||
import { formatBytes, renderString } from '@modrinth/utils'
|
import { formatBytes, renderString } from '@modrinth/utils'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
@@ -193,6 +178,11 @@ import { get_project_many, get_version_many } from '@/helpers/cache.js'
|
|||||||
import { releaseColor } from '@/helpers/utils'
|
import { releaseColor } from '@/helpers/utils'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -178,12 +178,7 @@
|
|||||||
class="categories"
|
class="categories"
|
||||||
/>
|
/>
|
||||||
{{ $formatVersion(notif.extra_data.version.game_versions) }}
|
{{ $formatVersion(notif.extra_data.version.game_versions) }}
|
||||||
<span
|
<span v-tooltip="formatDateTime(notif.extra_data.version.date_published)" class="date">
|
||||||
v-tooltip="
|
|
||||||
$dayjs(notif.extra_data.version.date_published).format('MMMM D, YYYY [at] h:mm A')
|
|
||||||
"
|
|
||||||
class="date"
|
|
||||||
>
|
|
||||||
{{ formatRelativeTime(notif.extra_data.version.date_published) }}
|
{{ formatRelativeTime(notif.extra_data.version.date_published) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -197,10 +192,7 @@
|
|||||||
<span v-if="notification.read" class="read-badge inline-flex">
|
<span v-if="notification.read" class="read-badge inline-flex">
|
||||||
<CheckCircleIcon /> Read
|
<CheckCircleIcon /> Read
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span v-tooltip="formatDateTime(notification.created)" class="inline-flex">
|
||||||
v-tooltip="$dayjs(notification.created).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
class="inline-flex"
|
|
||||||
>
|
|
||||||
<CalendarIcon class="mr-1" /> Received
|
<CalendarIcon class="mr-1" /> Received
|
||||||
{{ formatRelativeTime(notification.created) }}
|
{{ formatRelativeTime(notification.created) }}
|
||||||
</span>
|
</span>
|
||||||
@@ -338,6 +330,7 @@ import {
|
|||||||
DoubleIcon,
|
DoubleIcon,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
ProjectStatusBadge,
|
ProjectStatusBadge,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { getUserLink, renderString } from '@modrinth/utils'
|
import { getUserLink, renderString } from '@modrinth/utils'
|
||||||
@@ -351,6 +344,10 @@ import ThreadSummary from './thread/ThreadSummary.vue'
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const emit = defineEmits(['update:notifications'])
|
const emit = defineEmits(['update:notifications'])
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
notification: {
|
notification: {
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FileTextIcon } from '@modrinth/assets'
|
import { FileTextIcon } from '@modrinth/assets'
|
||||||
import { ButtonStyled, defineMessages, PagewideBanner, useVIntl } from '@modrinth/ui'
|
import {
|
||||||
import { formatMoney } from '@modrinth/utils'
|
ButtonStyled,
|
||||||
|
defineMessages,
|
||||||
|
PagewideBanner,
|
||||||
|
useFormatMoney,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { getTaxThreshold } from '@/providers/creator-withdraw.ts'
|
import { getTaxThreshold } from '@/providers/creator-withdraw.ts'
|
||||||
@@ -9,6 +14,7 @@ import CreatorTaxFormModal from '~/components/ui/dashboard/CreatorTaxFormModal.v
|
|||||||
import { useGeneratedState } from '~/composables/generated'
|
import { useGeneratedState } from '~/composables/generated'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
|
||||||
const generatedState = useGeneratedState()
|
const generatedState = useGeneratedState()
|
||||||
const taxThreshold = computed(() => getTaxThreshold(generatedState.value?.taxComplianceThresholds))
|
const taxThreshold = computed(() => getTaxThreshold(generatedState.value?.taxComplianceThresholds))
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { formatMoney, formatNumber } from '@modrinth/utils'
|
import { useFormatDateTime, useFormatMoney, useFormatNumber } from '@modrinth/ui'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import VueApexCharts from 'vue3-apexcharts'
|
import VueApexCharts from 'vue3-apexcharts'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -18,7 +17,6 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
formatLabels: {
|
formatLabels: {
|
||||||
type: Function,
|
type: Function,
|
||||||
default: (label) => dayjs(label).format('MMM D'),
|
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -78,8 +76,15 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
function formatTooltipValue(value, props) {
|
function formatTooltipValue(value, props) {
|
||||||
return props.isMoney ? formatMoney(value, false) : formatNumber(value, false)
|
return props.isMoney ? formatMoney(value) : formatNumber(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateListEntry(value, index, _, w, props) {
|
function generateListEntry(value, index, _, w, props) {
|
||||||
@@ -99,7 +104,7 @@ function generateListEntry(value, index, _, w, props) {
|
|||||||
function generateTooltip({ series, seriesIndex, dataPointIndex, w }, props) {
|
function generateTooltip({ series, seriesIndex, dataPointIndex, w }, props) {
|
||||||
const label = w.globals.lastXAxis.categories?.[dataPointIndex]
|
const label = w.globals.lastXAxis.categories?.[dataPointIndex]
|
||||||
|
|
||||||
const formattedLabel = props.formatLabels(label)
|
const formattedLabel = props.formatLabels ? props.formatLabels(label) : formatDate(label)
|
||||||
|
|
||||||
let tooltip = `<div class="bar-tooltip">
|
let tooltip = `<div class="bar-tooltip">
|
||||||
<div class="seperated-entry title">
|
<div class="seperated-entry title">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
ref="tinyDownloadChart"
|
ref="tinyDownloadChart"
|
||||||
:title="`Downloads`"
|
:title="`Downloads`"
|
||||||
color="var(--color-brand)"
|
color="var(--color-brand)"
|
||||||
:value="formatNumber(analytics.formattedData.value.downloads.sum, false)"
|
:value="formatNumber(analytics.formattedData.value.downloads.sum)"
|
||||||
:data="analytics.formattedData.value.downloads.chart.sumData"
|
:data="analytics.formattedData.value.downloads.chart.sumData"
|
||||||
:labels="analytics.formattedData.value.downloads.chart.labels"
|
:labels="analytics.formattedData.value.downloads.chart.labels"
|
||||||
suffix="<svg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' /></svg>"
|
suffix="<svg xmlns='http://www.w3.org/2000/svg' class='h-6 w-6' fill='none' viewBox='0 0 24 24' stroke='currentColor' stroke-width='2'><path stroke-linecap='round' stroke-linejoin='round' d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4' /></svg>"
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
ref="tinyViewChart"
|
ref="tinyViewChart"
|
||||||
:title="`Views`"
|
:title="`Views`"
|
||||||
color="var(--color-blue)"
|
color="var(--color-blue)"
|
||||||
:value="formatNumber(analytics.formattedData.value.views.sum, false)"
|
:value="formatNumber(analytics.formattedData.value.views.sum)"
|
||||||
:data="analytics.formattedData.value.views.chart.sumData"
|
:data="analytics.formattedData.value.views.chart.sumData"
|
||||||
:labels="analytics.formattedData.value.views.chart.labels"
|
:labels="analytics.formattedData.value.views.chart.labels"
|
||||||
suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/><circle cx='12' cy='12' r='3'/></svg>"
|
suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z'/><circle cx='12' cy='12' r='3'/></svg>"
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
ref="tinyRevenueChart"
|
ref="tinyRevenueChart"
|
||||||
:title="`Revenue`"
|
:title="`Revenue`"
|
||||||
color="var(--color-purple)"
|
color="var(--color-purple)"
|
||||||
:value="formatMoney(analytics.formattedData.value.revenue.sum, false)"
|
:value="formatMoney(analytics.formattedData.value.revenue.sum)"
|
||||||
:data="analytics.formattedData.value.revenue.chart.sumData"
|
:data="analytics.formattedData.value.revenue.chart.sumData"
|
||||||
:labels="analytics.formattedData.value.revenue.chart.labels"
|
:labels="analytics.formattedData.value.revenue.chart.labels"
|
||||||
is-money
|
is-money
|
||||||
@@ -221,7 +221,7 @@
|
|||||||
><template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
|
><template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
|
||||||
<template v-else>{{ countryCodeToName(name) }}</template>
|
<template v-else>{{ countryCodeToName(name) }}</template>
|
||||||
</strong>
|
</strong>
|
||||||
<span class="data-point">{{ formatNumber(count) }}</span>
|
<span class="data-point">{{ formatCompactNumber(count) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
<template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
|
<template v-if="name.toLowerCase() === 'xx' || !name">Other</template>
|
||||||
<template v-else>{{ countryCodeToName(name) }}</template>
|
<template v-else>{{ countryCodeToName(name) }}</template>
|
||||||
</strong>
|
</strong>
|
||||||
<span class="data-point">{{ formatNumber(count) }}</span>
|
<span class="data-point">{{ formatCompactNumber(count) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
@@ -310,8 +310,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets'
|
import { DownloadIcon, PaletteIcon, UpdatedIcon } from '@modrinth/assets'
|
||||||
import { Button, Card, DropdownSelect } from '@modrinth/ui'
|
import {
|
||||||
import { capitalizeString, formatMoney, formatNumber } from '@modrinth/utils'
|
Button,
|
||||||
|
Card,
|
||||||
|
DropdownSelect,
|
||||||
|
useCompactNumber,
|
||||||
|
useFormatMoney,
|
||||||
|
useFormatNumber,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
@@ -325,6 +332,10 @@ import {
|
|||||||
intToRgba,
|
intToRgba,
|
||||||
} from '~/utils/analytics.js'
|
} from '~/utils/analytics.js'
|
||||||
|
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
|
||||||
const router = useNativeRouter()
|
const router = useNativeRouter()
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ import {
|
|||||||
commonMessages,
|
commonMessages,
|
||||||
formFieldPlaceholders,
|
formFieldPlaceholders,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatMoney,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -82,6 +82,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
const amountInput = ref<InstanceType<typeof StyledInput> | null>(null)
|
const amountInput = ref<InstanceType<typeof StyledInput> | null>(null)
|
||||||
|
|
||||||
const safeMaxAmount = computed(() => {
|
const safeMaxAmount = computed(() => {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
>{{ formatTransactionStatus(transaction.status) }} <BulletDivider
|
>{{ formatTransactionStatus(transaction.status) }} <BulletDivider
|
||||||
/></span>
|
/></span>
|
||||||
</template>
|
</template>
|
||||||
{{ dayjs(transaction.created).format('MMM DD YYYY') }}
|
{{ formatDate(transaction.created) }}
|
||||||
<template v-if="transaction.type === 'withdrawal' && transaction.fee">
|
<template v-if="transaction.type === 'withdrawal' && transaction.fee">
|
||||||
<BulletDivider /> Fee {{ formatMoney(transaction.fee) }}
|
<BulletDivider /> Fee {{ formatMoney(transaction.fee) }}
|
||||||
</template>
|
</template>
|
||||||
@@ -79,10 +79,11 @@ import {
|
|||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
getCurrencyIcon,
|
getCurrencyIcon,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatMoney,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { capitalizeString, formatMoney } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
|
|
||||||
import { useGeneratedState } from '~/composables/generated'
|
import { useGeneratedState } from '~/composables/generated'
|
||||||
@@ -188,6 +189,8 @@ function formatTransactionStatus(status: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'medium' })
|
||||||
|
|
||||||
function formatMethodName(method: string | undefined, method_id: string | undefined): string {
|
function formatMethodName(method: string | undefined, method_id: string | undefined): string {
|
||||||
if (!method) return 'Unknown'
|
if (!method) return 'Unknown'
|
||||||
|
|||||||
@@ -57,8 +57,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { LoaderCircleIcon } from '@modrinth/assets'
|
import { LoaderCircleIcon } from '@modrinth/assets'
|
||||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
import { defineMessages, useFormatMoney, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
@@ -78,6 +77,7 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
|
||||||
const amountInUsd = computed(() => {
|
const amountInUsd = computed(() => {
|
||||||
if (props.isGiftCard && shouldShowExchangeRate.value) {
|
if (props.isGiftCard && shouldShowExchangeRate.value) {
|
||||||
@@ -119,31 +119,13 @@ const formattedLocalCurrency = computed(() => {
|
|||||||
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !props.localCurrency)
|
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !props.localCurrency)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
try {
|
return formatMoney(netAmountInLocalCurrency.value, props.localCurrency)
|
||||||
return new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: props.localCurrency,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
}).format(netAmountInLocalCurrency.value)
|
|
||||||
} catch {
|
|
||||||
return `${props.localCurrency} ${netAmountInLocalCurrency.value.toFixed(2)}`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const formattedLocalCurrencyAmount = computed(() => {
|
const formattedLocalCurrencyAmount = computed(() => {
|
||||||
if (!shouldShowExchangeRate.value || !localCurrencyAmount.value || !props.localCurrency) return ''
|
if (!shouldShowExchangeRate.value || !localCurrencyAmount.value || !props.localCurrency) return ''
|
||||||
|
|
||||||
try {
|
return formatMoney(localCurrencyAmount.value, props.localCurrency)
|
||||||
return new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: props.localCurrency,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
}).format(localCurrencyAmount.value)
|
|
||||||
} catch {
|
|
||||||
return `${props.localCurrency} ${localCurrencyAmount.value.toFixed(2)}`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
|||||||
@@ -124,9 +124,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineMessages, IntlFormatted, normalizeChildren, useVIntl } from '@modrinth/ui'
|
import {
|
||||||
import { formatMoney } from '@modrinth/utils'
|
defineMessages,
|
||||||
import dayjs from 'dayjs'
|
IntlFormatted,
|
||||||
|
normalizeChildren,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatMoney,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import ConfettiExplosion from 'vue-confetti-explosion'
|
import ConfettiExplosion from 'vue-confetti-explosion'
|
||||||
|
|
||||||
@@ -135,6 +140,8 @@ import { getRailConfig } from '@/utils/muralpay-rails'
|
|||||||
|
|
||||||
const { withdrawData } = useWithdrawContext()
|
const { withdrawData } = useWithdrawContext()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
const result = computed(() => withdrawData.value.result)
|
const result = computed(() => withdrawData.value.result)
|
||||||
|
|
||||||
@@ -149,7 +156,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
const formattedDate = computed(() => {
|
const formattedDate = computed(() => {
|
||||||
if (!result.value?.created) return 'N/A'
|
if (!result.value?.created) return 'N/A'
|
||||||
return dayjs(result.value.created).format('MMMM D, YYYY')
|
return formatDate(result.value.created)
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRail = computed(() => {
|
const selectedRail = computed(() => {
|
||||||
@@ -185,16 +192,7 @@ const formattedLocalCurrency = computed(() => {
|
|||||||
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !localCurrency.value)
|
if (!shouldShowExchangeRate.value || !netAmountInLocalCurrency.value || !localCurrency.value)
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
try {
|
return formatMoney(netAmountInLocalCurrency.value, localCurrency.value)
|
||||||
return new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: localCurrency.value,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
}).format(netAmountInLocalCurrency.value)
|
|
||||||
} catch {
|
|
||||||
return `${localCurrency.value} ${netAmountInLocalCurrency.value.toFixed(2)}`
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isMuralPayWithdrawal = computed(() => {
|
const isMuralPayWithdrawal = computed(() => {
|
||||||
|
|||||||
@@ -88,9 +88,9 @@ import {
|
|||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useFormatMoney,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import { useGeolocation } from '@vueuse/core'
|
import { useGeolocation } from '@vueuse/core'
|
||||||
|
|
||||||
import { useCountries, useFormattedCountries, useUserCountry } from '@/composables/country.ts'
|
import { useCountries, useFormattedCountries, useUserCountry } from '@/composables/country.ts'
|
||||||
@@ -115,6 +115,7 @@ const userCountry = useUserCountry()
|
|||||||
const allCountries = useCountries()
|
const allCountries = useCountries()
|
||||||
const { coords } = useGeolocation()
|
const { coords } = useGeolocation()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|
||||||
|
|||||||
@@ -80,9 +80,9 @@ import {
|
|||||||
defineMessages,
|
defineMessages,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
|
useFormatMoney,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { getTaxThreshold, getTaxThresholdActual } from '@/providers/creator-withdraw.ts'
|
import { getTaxThreshold, getTaxThresholdActual } from '@/providers/creator-withdraw.ts'
|
||||||
@@ -94,6 +94,7 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
|
||||||
const generatedState = useGeneratedState()
|
const generatedState = useGeneratedState()
|
||||||
const taxThreshold = computed(() => getTaxThreshold(generatedState.value.taxComplianceThresholds))
|
const taxThreshold = computed(() => getTaxThreshold(generatedState.value.taxComplianceThresholds))
|
||||||
|
|||||||
@@ -350,9 +350,9 @@ import {
|
|||||||
paymentMethodMessages,
|
paymentMethodMessages,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useFormatMoney,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
import { useDebounceFn } from '@vueuse/core'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
@@ -365,6 +365,7 @@ const debug = useDebugLogger('TremendousDetailsStage')
|
|||||||
const { withdrawData, maxWithdrawAmount, availableMethods, paymentOptions, calculateFees } =
|
const { withdrawData, maxWithdrawAmount, availableMethods, paymentOptions, calculateFees } =
|
||||||
useWithdrawContext()
|
useWithdrawContext()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|
||||||
const userEmail = computed(() => {
|
const userEmail = computed(() => {
|
||||||
@@ -587,16 +588,7 @@ function formatAmountForDisplay(
|
|||||||
if (!currencyCode || currencyCode === 'USD' || !rate) {
|
if (!currencyCode || currencyCode === 'USD' || !rate) {
|
||||||
return formatMoney(localAmount)
|
return formatMoney(localAmount)
|
||||||
}
|
}
|
||||||
try {
|
return formatMoney(localAmount, currencyCode)
|
||||||
return new Intl.NumberFormat('en-US', {
|
|
||||||
style: 'currency',
|
|
||||||
currency: currencyCode,
|
|
||||||
minimumFractionDigits: 2,
|
|
||||||
maximumFractionDigits: 2,
|
|
||||||
}).format(localAmount)
|
|
||||||
} catch {
|
|
||||||
return `${currencyCode} ${localAmount.toFixed(2)}`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useFixedDenominations = computed(() => {
|
const useFixedDenominations = computed(() => {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span
|
<span
|
||||||
v-tooltip="`Since ${queuedDate.toLocaleString()}`"
|
v-tooltip="`Since ${formatDateTimeFull(queuedDate.toDate())}`"
|
||||||
class="text-base text-secondary"
|
class="text-base text-secondary"
|
||||||
:class="{
|
:class="{
|
||||||
'text-red': daysInQueue > 4,
|
'text-red': daysInQueue > 4,
|
||||||
@@ -120,6 +120,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
type OverflowMenuOption,
|
type OverflowMenuOption,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
@@ -130,6 +131,17 @@ import type { ModerationProject } from '~/helpers/moderation'
|
|||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTimeFull = useFormatDateTime({
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
timeZoneName: 'short',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
})
|
||||||
|
|
||||||
const baseId = useId()
|
const baseId = useId()
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
<div class="flex flex-row items-center gap-2 self-end sm:self-auto">
|
<div class="flex flex-row items-center gap-2 self-end sm:self-auto">
|
||||||
<span
|
<span
|
||||||
v-tooltip="formatExactDate(report.created)"
|
v-tooltip="formatDateTime(report.created)"
|
||||||
class="cursor-help whitespace-nowrap text-sm text-secondary"
|
class="cursor-help whitespace-nowrap text-sm text-secondary"
|
||||||
>
|
>
|
||||||
{{ formatRelativeTime(report.created) }}
|
{{ formatRelativeTime(report.created) }}
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="report.user?.created"
|
v-if="report.user?.created"
|
||||||
v-tooltip="formatExactDate(report.user.created)"
|
v-tooltip="formatDateTime(report.user.created)"
|
||||||
class="cursor-help text-sm text-secondary"
|
class="cursor-help text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Joined {{ formatRelativeTime(report.user.created) }}
|
Joined {{ formatRelativeTime(report.user.created) }}
|
||||||
@@ -190,7 +190,7 @@ import {
|
|||||||
LinkIcon,
|
LinkIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
import { type ExtendedReport, reportQuickReplies } from '@modrinth/moderation'
|
||||||
import type { OverflowMenuOption } from '@modrinth/ui'
|
import { type OverflowMenuOption, useFormatDateTime } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
ButtonStyled,
|
ButtonStyled,
|
||||||
@@ -201,7 +201,6 @@ import {
|
|||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatProjectType } from '@modrinth/utils'
|
import { formatProjectType } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { isStaff } from '~/helpers/users.js'
|
import { isStaff } from '~/helpers/users.js'
|
||||||
@@ -305,10 +304,10 @@ async function reopenReport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
function formatExactDate(date: string): string {
|
timeStyle: 'short',
|
||||||
return dayjs(date).format('MMMM D, YYYY [at] h:mm A')
|
dateStyle: 'long',
|
||||||
}
|
})
|
||||||
|
|
||||||
function updateThread(newThread: any) {
|
function updateThread(newThread: any) {
|
||||||
if (props.report.thread) {
|
if (props.report.thread) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
type OverflowMenuOption,
|
type OverflowMenuOption,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
capitalizeString,
|
capitalizeString,
|
||||||
@@ -46,6 +47,16 @@ import ThreadView from '~/components/ui/thread/ThreadView.vue'
|
|||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
const featureFlags = useFeatureFlags()
|
const featureFlags = useFeatureFlags()
|
||||||
|
|
||||||
|
const formatDateTimeUtc = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'short',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
})
|
||||||
|
|
||||||
type FlattenedFileReport = Labrinth.TechReview.Internal.FileReport & {
|
type FlattenedFileReport = Labrinth.TechReview.Internal.FileReport & {
|
||||||
id: string
|
id: string
|
||||||
version_id: string
|
version_id: string
|
||||||
@@ -763,7 +774,7 @@ const reviewSummaryPreview = computed(() => {
|
|||||||
const totalDecisions = totalSafe + totalUnsafe
|
const totalDecisions = totalSafe + totalUnsafe
|
||||||
if (totalDecisions === 0) return ''
|
if (totalDecisions === 0) return ''
|
||||||
|
|
||||||
const timestamp = dayjs().utc().format('MMMM D, YYYY [at] h:mm A [UTC]')
|
const timestamp = formatDateTimeUtc(dayjs().toDate())
|
||||||
let markdown = `## Tech Review Summary\n*${timestamp}*\n\n`
|
let markdown = `## Tech Review Summary\n*${timestamp}*\n\n`
|
||||||
markdown += `<details>\n<summary>File Details (${totalSafe} safe, ${totalUnsafe} unsafe)</summary>\n\n`
|
markdown += `<details>\n<summary>File Details (${totalSafe} safe, ${totalUnsafe} unsafe)</summary>\n\n`
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@
|
|||||||
<span>{{ report.reporterUser.username }}</span>
|
<span>{{ report.reporterUser.username }}</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
<span> </span>
|
<span> </span>
|
||||||
<span v-tooltip="$dayjs(report.created).format('MMMM D, YYYY [at] h:mm A')">{{
|
<span v-tooltip="formatDateTime(report.created)">{{
|
||||||
formatRelativeTime(report.created)
|
formatRelativeTime(report.created)
|
||||||
}}</span>
|
}}</span>
|
||||||
<CopyCode v-if="flags.developerMode" :text="report.id" class="report-id" />
|
<CopyCode v-if="flags.developerMode" :text="report.id" class="report-id" />
|
||||||
@@ -104,13 +104,17 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ReportIcon, UnknownIcon, VersionIcon } from '@modrinth/assets'
|
import { ReportIcon, UnknownIcon, VersionIcon } from '@modrinth/assets'
|
||||||
import { Avatar, Badge, CopyCode, useRelativeTime } from '@modrinth/ui'
|
import { Avatar, Badge, CopyCode, useFormatDateTime, useRelativeTime } from '@modrinth/ui'
|
||||||
import { formatProjectType, renderHighlightedString } from '@modrinth/utils'
|
import { formatProjectType, renderHighlightedString } from '@modrinth/utils'
|
||||||
|
|
||||||
import ThreadSummary from '~/components/ui/thread/ThreadSummary.vue'
|
import ThreadSummary from '~/components/ui/thread/ThreadSummary.vue'
|
||||||
import { getProjectTypeForUrl } from '~/helpers/projects.js'
|
import { getProjectTypeForUrl } from '~/helpers/projects.js'
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
report: {
|
report: {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ import {
|
|||||||
getFileExtensionIcon,
|
getFileExtensionIcon,
|
||||||
isEditableFile as isEditableFileExt,
|
isEditableFile as isEditableFileExt,
|
||||||
isImageFile,
|
isImageFile,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { computed, h, ref, shallowRef } from 'vue'
|
import { computed, h, ref, shallowRef } from 'vue'
|
||||||
import { renderToString } from 'vue/server-renderer'
|
import { renderToString } from 'vue/server-renderer'
|
||||||
@@ -121,6 +122,14 @@ const units = Object.freeze(['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'])
|
|||||||
const route = shallowRef(useRoute())
|
const route = shallowRef(useRoute())
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
year: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const containerClasses = computed(() => [
|
const containerClasses = computed(() => [
|
||||||
'group m-0 flex w-full select-none items-center justify-between overflow-hidden border-0 border-t border-solid border-surface-3 px-4 py-3 focus:!outline-none',
|
'group m-0 flex w-full select-none items-center justify-between overflow-hidden border-0 border-t border-solid border-surface-3 px-4 py-3 focus:!outline-none',
|
||||||
props.index % 2 === 0 ? 'bg-surface-2' : 'bg-surface-3',
|
props.index % 2 === 0 ? 'bg-surface-2' : 'bg-surface-3',
|
||||||
@@ -177,28 +186,12 @@ const iconComponent = computed(() => {
|
|||||||
|
|
||||||
const formattedModifiedDate = computed(() => {
|
const formattedModifiedDate = computed(() => {
|
||||||
const date = new Date(props.modified * 1000)
|
const date = new Date(props.modified * 1000)
|
||||||
return `${date.toLocaleDateString('en-US', {
|
return formatDateTime(date)
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}, ${date.toLocaleTimeString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
})}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const formattedCreationDate = computed(() => {
|
const formattedCreationDate = computed(() => {
|
||||||
const date = new Date(props.created * 1000)
|
const date = new Date(props.created * 1000)
|
||||||
return `${date.toLocaleDateString('en-US', {
|
return formatDateTime(date)
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}, ${date.toLocaleTimeString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
})}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isEditableFile = computed(() => {
|
const isEditableFile = computed(() => {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MessageDescriptor } from '@modrinth/ui'
|
import { type MessageDescriptor, useFormatPrice } from '@modrinth/ui'
|
||||||
import { ButtonStyled, defineMessage, defineMessages, ServersSpecs, useVIntl } from '@modrinth/ui'
|
import { ButtonStyled, defineMessage, defineMessages, ServersSpecs, useVIntl } from '@modrinth/ui'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'select' | 'scroll-to-faq'): void
|
(e: 'select' | 'scroll-to-faq'): void
|
||||||
@@ -132,7 +132,7 @@ const billingMonths = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="m-0 text-2xl font-bold text-contrast">
|
<span class="m-0 text-2xl font-bold text-contrast">
|
||||||
{{ formatPrice(locale, price / billingMonths, currency, true) }}
|
{{ formatPrice(price / billingMonths, currency, true) }}
|
||||||
<span class="text-lg font-semibold text-secondary">
|
<span class="text-lg font-semibold text-secondary">
|
||||||
/ month<template v-if="interval !== 'monthly'">, billed {{ interval }}</template>
|
/ month<template v-if="interval !== 'monthly'">, billed {{ interval }}</template>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -6,14 +6,22 @@ import {
|
|||||||
NOTICE_LEVELS,
|
NOTICE_LEVELS,
|
||||||
ServerNotice,
|
ServerNotice,
|
||||||
TagItem,
|
TagItem,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
import type { ServerNotice as ServerNoticeType } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const formatDateTimeShortMonth = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'medium',
|
||||||
|
})
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
notice: ServerNoticeType
|
notice: ServerNoticeType
|
||||||
@@ -27,17 +35,14 @@ defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span v-if="notice.announce_at">
|
<span v-if="notice.announce_at">
|
||||||
{{ dayjs(notice.announce_at).format('MMM D, YYYY [at] h:mm A') }} ({{
|
{{ formatDateTimeShortMonth(notice.announce_at) }} ({{
|
||||||
formatRelativeTime(notice.announce_at)
|
formatRelativeTime(notice.announce_at)
|
||||||
}})
|
}})
|
||||||
</span>
|
</span>
|
||||||
<template v-else> Never begins </template>
|
<template v-else> Never begins </template>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span
|
<span v-if="notice.expires" v-tooltip="formatDateTime(notice.expires)">
|
||||||
v-if="notice.expires"
|
|
||||||
v-tooltip="dayjs(notice.expires).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
>
|
|
||||||
{{ formatRelativeTime(notice.expires) }}
|
{{ formatRelativeTime(notice.expires) }}
|
||||||
</span>
|
</span>
|
||||||
<template v-else> Never expires </template>
|
<template v-else> Never expires </template>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="message__date">
|
<span class="message__date">
|
||||||
<span v-tooltip="$dayjs(message.created).format('MMMM D, YYYY [at] h:mm A')">
|
<span v-tooltip="formatDateTime(message.created)">
|
||||||
{{ timeSincePosted }}
|
{{ timeSincePosted }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -131,7 +131,14 @@ import {
|
|||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { AutoLink, Avatar, Badge, OverflowMenu, useRelativeTime } from '@modrinth/ui'
|
import {
|
||||||
|
AutoLink,
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
OverflowMenu,
|
||||||
|
useFormatDateTime,
|
||||||
|
useRelativeTime,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { renderString } from '@modrinth/utils'
|
import { renderString } from '@modrinth/utils'
|
||||||
|
|
||||||
import { isStaff } from '~/helpers/users.js'
|
import { isStaff } from '~/helpers/users.js'
|
||||||
@@ -186,6 +193,11 @@ const formattedMessage = computed(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const timeSincePosted = ref(formatRelativeTime(props.message.created))
|
const timeSincePosted = ref(formatRelativeTime(props.message.created))
|
||||||
|
|
||||||
const isPrivateMessage = computed(() => {
|
const isPrivateMessage = computed(() => {
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
const formatters = new WeakMap<object, Intl.NumberFormat>()
|
|
||||||
|
|
||||||
export function useCompactNumber(truncate = false, fractionDigits = 2, locale?: string) {
|
|
||||||
const context = {}
|
|
||||||
|
|
||||||
let formatter = formatters.get(context)
|
|
||||||
|
|
||||||
if (!formatter) {
|
|
||||||
formatter = new Intl.NumberFormat(locale, {
|
|
||||||
notation: 'compact',
|
|
||||||
maximumFractionDigits: fractionDigits,
|
|
||||||
})
|
|
||||||
formatters.set(context, formatter)
|
|
||||||
}
|
|
||||||
|
|
||||||
function format(value: number): string {
|
|
||||||
let formattedValue = value
|
|
||||||
if (truncate) {
|
|
||||||
const scale = Math.pow(10, fractionDigits)
|
|
||||||
formattedValue = Math.floor(value * scale) / scale
|
|
||||||
}
|
|
||||||
return formatter!.format(formattedValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
return format
|
|
||||||
}
|
|
||||||
@@ -393,7 +393,7 @@
|
|||||||
"message": "No projects in collection yet"
|
"message": "No projects in collection yet"
|
||||||
},
|
},
|
||||||
"collection.label.projects-count": {
|
"collection.label.projects-count": {
|
||||||
"message": "{count, plural, =0 {No projects yet} one {<stat>{count}</stat> project} other {<stat>{count}</stat> {type}}}"
|
"message": "{count, plural, =0 {No projects yet} other {<stat>{count}</stat> {type}}}"
|
||||||
},
|
},
|
||||||
"collection.label.updated-at": {
|
"collection.label.updated-at": {
|
||||||
"message": "Updated {ago}"
|
"message": "Updated {ago}"
|
||||||
@@ -591,7 +591,7 @@
|
|||||||
"message": "Try adjusting your filters or search terms."
|
"message": "Try adjusting your filters or search terms."
|
||||||
},
|
},
|
||||||
"dashboard.collections.label.projects-count": {
|
"dashboard.collections.label.projects-count": {
|
||||||
"message": "{count, plural, one {{count} project} other {{count} projects}}"
|
"message": "{count} {countPlural, plural, one {project} other {projects}}"
|
||||||
},
|
},
|
||||||
"dashboard.collections.label.search-input": {
|
"dashboard.collections.label.search-input": {
|
||||||
"message": "Search your collections"
|
"message": "Search your collections"
|
||||||
@@ -1437,7 +1437,7 @@
|
|||||||
"message": "For Players"
|
"message": "For Players"
|
||||||
},
|
},
|
||||||
"landing.section.for-players.tagline": {
|
"landing.section.for-players.tagline": {
|
||||||
"message": "Discover over {count} creations"
|
"message": "Discover over {count, number} creations"
|
||||||
},
|
},
|
||||||
"landing.subheading": {
|
"landing.subheading": {
|
||||||
"message": "Discover, play, and share Minecraft content through our open-source platform built for the community."
|
"message": "Discover, play, and share Minecraft content through our open-source platform built for the community."
|
||||||
@@ -2040,7 +2040,7 @@
|
|||||||
"message": "Collection"
|
"message": "Collection"
|
||||||
},
|
},
|
||||||
"profile.label.downloads": {
|
"profile.label.downloads": {
|
||||||
"message": "{count} {count, plural, one {download} other {downloads}}"
|
"message": "{count} {countPlural, plural, one {download} other {downloads}}"
|
||||||
},
|
},
|
||||||
"profile.label.joined": {
|
"profile.label.joined": {
|
||||||
"message": "Joined"
|
"message": "Joined"
|
||||||
@@ -2061,7 +2061,7 @@
|
|||||||
"message": "Organizations"
|
"message": "Organizations"
|
||||||
},
|
},
|
||||||
"profile.label.projects": {
|
"profile.label.projects": {
|
||||||
"message": "{count} {count, plural, one {project} other {projects}}"
|
"message": "{count} {countPlural, plural, one {project} other {projects}}"
|
||||||
},
|
},
|
||||||
"profile.label.saving": {
|
"profile.label.saving": {
|
||||||
"message": "Saving..."
|
"message": "Saving..."
|
||||||
|
|||||||
@@ -613,7 +613,7 @@
|
|||||||
<IntlFormatted
|
<IntlFormatted
|
||||||
:message-id="messages.serversPromoPricing"
|
:message-id="messages.serversPromoPricing"
|
||||||
:values="{
|
:values="{
|
||||||
price: formatPrice(locale, 500, 'USD', true),
|
price: formatPrice(500, 'USD', true),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template #small="{ children }">
|
<template #small="{ children }">
|
||||||
@@ -945,7 +945,7 @@
|
|||||||
{{
|
{{
|
||||||
capitalizeString(
|
capitalizeString(
|
||||||
formatMessage(commonMessages.projectFollowers, {
|
formatMessage(commonMessages.projectFollowers, {
|
||||||
count: formatNumber(project.followers, false),
|
count: project.followers,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -953,7 +953,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="project.approved"
|
v-if="project.approved"
|
||||||
v-tooltip="$dayjs(project.approved).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(project.approved)"
|
||||||
class="details-list__item"
|
class="details-list__item"
|
||||||
>
|
>
|
||||||
<CalendarIcon aria-hidden="true" />
|
<CalendarIcon aria-hidden="true" />
|
||||||
@@ -968,11 +968,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div v-else v-tooltip="formatDateTime(project.published)" class="details-list__item">
|
||||||
v-else
|
|
||||||
v-tooltip="$dayjs(project.published).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
class="details-list__item"
|
|
||||||
>
|
|
||||||
<CalendarIcon aria-hidden="true" />
|
<CalendarIcon aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
@@ -983,7 +979,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="project.status === 'processing' && project.queued"
|
v-if="project.status === 'processing' && project.queued"
|
||||||
v-tooltip="$dayjs(project.queued).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(project.queued)"
|
||||||
class="details-list__item"
|
class="details-list__item"
|
||||||
>
|
>
|
||||||
<ScaleIcon aria-hidden="true" />
|
<ScaleIcon aria-hidden="true" />
|
||||||
@@ -1000,7 +996,7 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="versions.length > 0 && project.updated"
|
v-if="versions.length > 0 && project.updated"
|
||||||
v-tooltip="$dayjs(project.updated).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(project.updated)"
|
||||||
class="details-list__item"
|
class="details-list__item"
|
||||||
>
|
>
|
||||||
<VersionIcon aria-hidden="true" />
|
<VersionIcon aria-hidden="true" />
|
||||||
@@ -1099,17 +1095,13 @@ import {
|
|||||||
StyledInput,
|
StyledInput,
|
||||||
TagItem,
|
TagItem,
|
||||||
useDebugLogger,
|
useDebugLogger,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatPrice,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
import VersionSummary from '@modrinth/ui/src/components/version/VersionSummary.vue'
|
||||||
import {
|
import { capitalizeString, formatProjectType, renderString } from '@modrinth/utils'
|
||||||
capitalizeString,
|
|
||||||
formatNumber,
|
|
||||||
formatPrice,
|
|
||||||
formatProjectType,
|
|
||||||
renderString,
|
|
||||||
} from '@modrinth/utils'
|
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||||
import { useLocalStorage } from '@vueuse/core'
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -1150,7 +1142,12 @@ const tags = useGeneratedState()
|
|||||||
const flags = useFeatureFlags()
|
const flags = useFeatureFlags()
|
||||||
const cosmetics = useCosmetics()
|
const cosmetics = useCosmetics()
|
||||||
|
|
||||||
const { locale, formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const debug = useDebugLogger('DownloadModal')
|
const debug = useDebugLogger('DownloadModal')
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
on
|
on
|
||||||
{{ $dayjs(version.date_published).format('MMM D, YYYY') }}</span
|
{{ formatDate(version.date_published) }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
@@ -86,12 +86,23 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
|
import { DownloadIcon, SpinnerIcon } from '@modrinth/assets'
|
||||||
import { injectModrinthClient, injectProjectPageContext, Pagination } from '@modrinth/ui'
|
import {
|
||||||
|
injectModrinthClient,
|
||||||
|
injectProjectPageContext,
|
||||||
|
Pagination,
|
||||||
|
useFormatDateTime,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import VersionFilterControl from '@modrinth/ui/src/components/version/VersionFilterControl.vue'
|
import VersionFilterControl from '@modrinth/ui/src/components/version/VersionFilterControl.vue'
|
||||||
import { renderHighlightedString } from '@modrinth/utils'
|
import { renderHighlightedString } from '@modrinth/utils'
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const { projectV2, versions, versionsLoading, loadVersions } = injectProjectPageContext()
|
const { projectV2, versions, versionsLoading, loadVersions } = injectProjectPageContext()
|
||||||
|
|
||||||
// Load versions on mount (client-side)
|
// Load versions on mount (client-side)
|
||||||
|
|||||||
@@ -264,7 +264,7 @@
|
|||||||
<div class="gallery-bottom">
|
<div class="gallery-bottom">
|
||||||
<div class="gallery-created">
|
<div class="gallery-created">
|
||||||
<CalendarIcon aria-hidden="true" aria-label="Date created" />
|
<CalendarIcon aria-hidden="true" aria-label="Date created" />
|
||||||
{{ $dayjs(item.created).format('MMMM D, YYYY') }}
|
{{ formatDate(item.created) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentMember" class="gallery-buttons input-group">
|
<div v-if="currentMember" class="gallery-buttons input-group">
|
||||||
<button
|
<button
|
||||||
@@ -341,11 +341,18 @@ import {
|
|||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
NewModal as Modal,
|
NewModal as Modal,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { useEventListener, useLocalStorage } from '@vueuse/core'
|
import { useEventListener, useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
import { isPermission } from '~/utils/permissions.ts'
|
import { isPermission } from '~/utils/permissions.ts'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
// Router
|
// Router
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@
|
|||||||
<div class="gallery-bottom">
|
<div class="gallery-bottom">
|
||||||
<div class="gallery-created">
|
<div class="gallery-created">
|
||||||
<CalendarIcon aria-hidden="true" aria-label="Date created" />
|
<CalendarIcon aria-hidden="true" aria-label="Date created" />
|
||||||
{{ $dayjs(item.created).format('MMMM D, YYYY') }}
|
{{ formatDate(item.created) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="currentMember" class="gallery-buttons input-group">
|
<div v-if="currentMember" class="gallery-buttons input-group">
|
||||||
<button
|
<button
|
||||||
@@ -300,10 +300,17 @@ import {
|
|||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
NewModal as Modal,
|
NewModal as Modal,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import { isPermission } from '~/utils/permissions.ts'
|
import { isPermission } from '~/utils/permissions.ts'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
projectV2: project,
|
projectV2: project,
|
||||||
currentMember,
|
currentMember,
|
||||||
|
|||||||
@@ -368,7 +368,7 @@
|
|||||||
<div v-if="!isEditing">
|
<div v-if="!isEditing">
|
||||||
<h4>Publication date</h4>
|
<h4>Publication date</h4>
|
||||||
<span>
|
<span>
|
||||||
{{ $dayjs(version.date_published).format('MMMM D, YYYY [at] h:mm A') }}
|
{{ formatDateTime(version.date_published) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!isEditing && version.author">
|
<div v-if="!isEditing && version.author">
|
||||||
@@ -437,6 +437,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
injectProjectPageContext,
|
injectProjectPageContext,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { formatBytes, renderHighlightedString } from '@modrinth/utils'
|
import { formatBytes, renderHighlightedString } from '@modrinth/utils'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
@@ -460,6 +461,11 @@ const auth = await useAuth()
|
|||||||
const tags = useGeneratedState()
|
const tags = useGeneratedState()
|
||||||
const flags = useFeatureFlags()
|
const flags = useFeatureFlags()
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'medium' })
|
||||||
|
|
||||||
// Helper for accessing nuxt app $formatVersion
|
// Helper for accessing nuxt app $formatVersion
|
||||||
const formatVersionDisplay = (versions: string[]) => (data as any).$formatVersion(versions)
|
const formatVersionDisplay = (versions: string[]) => (data as any).$formatVersion(versions)
|
||||||
@@ -697,9 +703,9 @@ const description = computed(
|
|||||||
version.value.loaders ?? []
|
version.value.loaders ?? []
|
||||||
)
|
)
|
||||||
.map((x: string) => x.charAt(0).toUpperCase() + x.slice(1))
|
.map((x: string) => x.charAt(0).toUpperCase() + x.slice(1))
|
||||||
.join(' & ')}. Published on ${data
|
.join(
|
||||||
.$dayjs(version.value.date_published)
|
' & ',
|
||||||
.format('MMM D, YYYY')}. ${version.value.downloads} downloads.`,
|
)}. Published on ${formatDate(version.value.date_published)}. ${version.value.downloads} downloads.`,
|
||||||
)
|
)
|
||||||
|
|
||||||
const usesFeaturedVersions = computed(() =>
|
const usesFeaturedVersions = computed(() =>
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="mb-4 mt-2 flex w-full items-center gap-1 text-sm text-secondary">
|
<div class="mb-4 mt-2 flex w-full items-center gap-1 text-sm text-secondary">
|
||||||
{{ capitalizeString(subscription.interval) }} ⋅ {{ subscription.status }} ⋅
|
{{ capitalizeString(subscription.interval) }} ⋅ {{ subscription.status }} ⋅
|
||||||
{{ dayjs(subscription.created).format('MMMM D, YYYY [at] h:mma') }} ({{
|
{{ formatDateTime(subscription.created) }} ({{
|
||||||
formatRelativeTime(subscription.created)
|
formatRelativeTime(subscription.created)
|
||||||
}})
|
}})
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<template v-if="charge.status !== 'cancelled'">
|
<template v-if="charge.status !== 'cancelled'">
|
||||||
⋅
|
⋅
|
||||||
{{ formatPrice(vintl.locale, charge.amount, charge.currency_code) }}
|
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
@@ -252,13 +252,13 @@
|
|||||||
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
|
<span v-else-if="charge.status === 'cancelled'" class="font-bold">Ends:</span>
|
||||||
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
|
<span v-else-if="charge.type === 'refund'" class="font-bold">Issued:</span>
|
||||||
<span v-else class="font-bold">Due:</span>
|
<span v-else class="font-bold">Due:</span>
|
||||||
{{ dayjs(charge.due).format('MMMM D, YYYY [at] h:mma') }}
|
{{ formatDateTime(charge.due) }}
|
||||||
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
|
<span class="text-secondary">({{ formatRelativeTime(charge.due) }}) </span>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
|
<span v-if="charge.last_attempt != null" class="text-sm text-secondary">
|
||||||
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
|
<span v-if="charge.status === 'failed'" class="font-bold">Last attempt:</span>
|
||||||
<span v-else class="font-bold">Charged:</span>
|
<span v-else class="font-bold">Charged:</span>
|
||||||
{{ dayjs(charge.last_attempt).format('MMMM D, YYYY [at] h:mma') }}
|
{{ formatDateTime(charge.last_attempt) }}
|
||||||
<span class="text-secondary"
|
<span class="text-secondary"
|
||||||
>({{ formatRelativeTime(charge.last_attempt) }})
|
>({{ formatRelativeTime(charge.last_attempt) }})
|
||||||
</span>
|
</span>
|
||||||
@@ -268,9 +268,9 @@
|
|||||||
⋅
|
⋅
|
||||||
{{ charge.type }}
|
{{ charge.type }}
|
||||||
⋅
|
⋅
|
||||||
{{ formatPrice(vintl.locale, charge.amount, charge.currency_code) }}
|
{{ formatPrice(charge.amount, charge.currency_code) }}
|
||||||
⋅
|
⋅
|
||||||
{{ dayjs(charge.due).format('YYYY-MM-DD h:mma') }}
|
{{ formatDateTimeShort(charge.due) }}
|
||||||
<template v-if="charge.subscription_interval">
|
<template v-if="charge.subscription_interval">
|
||||||
⋅ {{ charge.subscription_interval }}
|
⋅ {{ charge.subscription_interval }}
|
||||||
</template>
|
</template>
|
||||||
@@ -332,16 +332,30 @@ import {
|
|||||||
NewModal,
|
NewModal,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatPrice,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { capitalizeString, formatPrice } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
|
import { DEFAULT_CREDIT_EMAIL_MESSAGE } from '@modrinth/utils/utils.ts'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
|
import ModrinthServersIcon from '~/components/ui/servers/ModrinthServersIcon.vue'
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const formatDateTimeShort = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const vintl = useVIntl()
|
const vintl = useVIntl()
|
||||||
|
|||||||
@@ -159,16 +159,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span v-if="notice.announce_at">
|
<span v-if="notice.announce_at">
|
||||||
{{ dayjs(notice.announce_at).format('MMM D, YYYY [at] h:mm A') }}
|
{{ formatDateTimeShortMonth(notice.announce_at) }}
|
||||||
({{ formatRelativeTime(notice.announce_at) }})
|
({{ formatRelativeTime(notice.announce_at) }})
|
||||||
</span>
|
</span>
|
||||||
<template v-else> Never begins </template>
|
<template v-else> Never begins </template>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm">
|
<div class="text-sm">
|
||||||
<span
|
<span v-if="notice.expires" v-tooltip="formatDateTime(notice.expires)">
|
||||||
v-if="notice.expires"
|
|
||||||
v-tooltip="dayjs(notice.expires).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
>
|
|
||||||
{{ formatRelativeTime(notice.expires) }}
|
{{ formatRelativeTime(notice.expires) }}
|
||||||
</span>
|
</span>
|
||||||
<template v-else> Never expires </template>
|
<template v-else> Never expires </template>
|
||||||
@@ -276,6 +273,7 @@ import {
|
|||||||
StyledInput,
|
StyledInput,
|
||||||
TagItem,
|
TagItem,
|
||||||
Toggle,
|
Toggle,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -290,6 +288,14 @@ import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const formatDateTimeShortMonth = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'medium',
|
||||||
|
})
|
||||||
|
|
||||||
const notices = ref<ServerNoticeType[]>([])
|
const notices = ref<ServerNoticeType[]>([])
|
||||||
const createNoticeModal = ref<InstanceType<typeof NewModal>>()
|
const createNoticeModal = ref<InstanceType<typeof NewModal>>()
|
||||||
|
|||||||
@@ -76,11 +76,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-secondary">
|
<div class="flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-secondary">
|
||||||
<span v-tooltip="dayjs(batch.created_at).format('MMMM D, YYYY [at] h:mm A')">
|
<span v-tooltip="formatDateTime(batch.created_at)">
|
||||||
Created {{ formatRelativeTime(batch.created_at) }}
|
Created {{ formatRelativeTime(batch.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span v-tooltip="dayjs(batch.scheduled_at).format('MMMM D, YYYY [at] h:mm A')">
|
<span v-tooltip="formatDateTime(batch.scheduled_at)">
|
||||||
Scheduled {{ formatRelativeTime(batch.scheduled_at) }}
|
Scheduled {{ formatRelativeTime(batch.scheduled_at) }}
|
||||||
</span>
|
</span>
|
||||||
<template v-if="batch.provision_options?.region">
|
<template v-if="batch.provision_options?.region">
|
||||||
@@ -116,6 +116,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
Pagination,
|
Pagination,
|
||||||
TagItem,
|
TagItem,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { User } from '@modrinth/utils'
|
import type { User } from '@modrinth/utils'
|
||||||
@@ -127,6 +128,10 @@ import { useServersFetch } from '~/composables/servers/servers-fetch.ts'
|
|||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
interface ProvisionOptions {
|
interface ProvisionOptions {
|
||||||
|
|||||||
@@ -180,9 +180,11 @@
|
|||||||
count: formatCompactNumber(projects?.length || 0),
|
count: formatCompactNumber(projects?.length || 0),
|
||||||
type: formatMessage(
|
type: formatMessage(
|
||||||
commonProjectTypeSentenceMessages[
|
commonProjectTypeSentenceMessages[
|
||||||
projectTypes.length === 1 ? projectTypes[0] : 'project'
|
projectTypes.length === 1 && projects?.length > 1
|
||||||
|
? projectTypes[0]
|
||||||
|
: 'project'
|
||||||
],
|
],
|
||||||
{ count: projects?.length || 0 },
|
{ count: formatCompactNumberPlural(projects?.length || 0) },
|
||||||
),
|
),
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
@@ -247,7 +249,7 @@
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span
|
<span
|
||||||
v-tooltip="dayjs(collection.created).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(collection.created)"
|
||||||
class="flex w-fit items-center gap-2"
|
class="flex w-fit items-center gap-2"
|
||||||
>
|
>
|
||||||
<CalendarIcon aria-hidden="true" />
|
<CalendarIcon aria-hidden="true" />
|
||||||
@@ -259,7 +261,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="showUpdatedDate"
|
v-if="showUpdatedDate"
|
||||||
v-tooltip="dayjs(collection.updated).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(collection.updated)"
|
||||||
class="flex w-fit items-center gap-2"
|
class="flex w-fit items-center gap-2"
|
||||||
>
|
>
|
||||||
<UpdatedIcon aria-hidden="true" />
|
<UpdatedIcon aria-hidden="true" />
|
||||||
@@ -410,6 +412,8 @@ import {
|
|||||||
RadioButtons,
|
RadioButtons,
|
||||||
SidebarCard,
|
SidebarCard,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useCompactNumber,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useSavable,
|
useSavable,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
@@ -425,7 +429,11 @@ const { handleError } = injectNotificationManager()
|
|||||||
const api = injectModrinthClient()
|
const api = injectModrinthClient()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
const formatCompactNumber = useCompactNumber()
|
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const route = useNativeRoute()
|
const route = useNativeRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -491,8 +499,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
projectsCountLabel: {
|
projectsCountLabel: {
|
||||||
id: 'collection.label.projects-count',
|
id: 'collection.label.projects-count',
|
||||||
defaultMessage:
|
defaultMessage: '{count, plural, =0 {No projects yet} other {<stat>{count}</stat> {type}}}',
|
||||||
'{count, plural, =0 {No projects yet} one {<stat>{count}</stat> project} other {<stat>{count}</stat> {type}}}',
|
|
||||||
},
|
},
|
||||||
removeProjectButton: {
|
removeProjectButton: {
|
||||||
id: 'collection.button.remove-project',
|
id: 'collection.button.remove-project',
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
{{
|
{{
|
||||||
formatMessage(messages.projectsCountLabel, {
|
formatMessage(messages.projectsCountLabel, {
|
||||||
count: formatCompactNumber(user ? user.follows.length : 0),
|
count: formatCompactNumber(user ? user.follows.length : 0),
|
||||||
|
countPlural: formatCompactNumberPlural(user ? user.follows.length : 0),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
{{
|
{{
|
||||||
formatMessage(messages.projectsCountLabel, {
|
formatMessage(messages.projectsCountLabel, {
|
||||||
count: formatCompactNumber(collection.projects?.length || 0),
|
count: formatCompactNumber(collection.projects?.length || 0),
|
||||||
|
countPlural: formatCompactNumberPlural(collection.projects?.length || 0),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
@@ -154,13 +156,14 @@ import {
|
|||||||
defineMessages,
|
defineMessages,
|
||||||
DropdownSelect,
|
DropdownSelect,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useCompactNumber,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
import CollectionCreateModal from '~/components/ui/create/CollectionCreateModal.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatCompactNumber = useCompactNumber()
|
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
createNewButton: {
|
createNewButton: {
|
||||||
@@ -177,7 +180,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
projectsCountLabel: {
|
projectsCountLabel: {
|
||||||
id: 'dashboard.collections.label.projects-count',
|
id: 'dashboard.collections.label.projects-count',
|
||||||
defaultMessage: '{count, plural, one {{count} project} other {{count} projects}}',
|
defaultMessage: '{count} {countPlural, plural, one {project} other {projects}}',
|
||||||
},
|
},
|
||||||
searchInputLabel: {
|
searchInputLabel: {
|
||||||
id: 'dashboard.collections.label.search-input',
|
id: 'dashboard.collections.label.search-input',
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
></span>
|
></span>
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.estimatedWithDate, {
|
formatMessage(messages.estimatedWithDate, {
|
||||||
date: date.date ? dayjs(date.date).format('MMM D, YYYY') : '',
|
date: date.date ? formatDate(date.date) : '',
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
<Tooltip theme="dismissable-prompt" :triggers="['hover', 'focus']" no-auto-focus>
|
<Tooltip theme="dismissable-prompt" :triggers="['hover', 'focus']" no-auto-focus>
|
||||||
@@ -260,8 +260,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
|
import { ArrowUpRightIcon, InProgressIcon, UnknownIcon } from '@modrinth/assets'
|
||||||
import { defineMessages, useVIntl } from '@modrinth/ui'
|
import { defineMessages, useFormatDateTime, useFormatMoney, useVIntl } from '@modrinth/ui'
|
||||||
import { formatMoney } from '@modrinth/utils'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { Tooltip } from 'floating-vue'
|
import { Tooltip } from 'floating-vue'
|
||||||
|
|
||||||
@@ -271,6 +270,8 @@ import CreatorWithdrawModal from '~/components/ui/dashboard/CreatorWithdrawModal
|
|||||||
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'medium' })
|
||||||
|
|
||||||
await useAuth()
|
await useAuth()
|
||||||
|
|
||||||
|
|||||||
@@ -90,8 +90,15 @@ import {
|
|||||||
GenericListIcon,
|
GenericListIcon,
|
||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { ButtonStyled, Combobox, defineMessages, useVIntl } from '@modrinth/ui'
|
import {
|
||||||
import { formatMoney } from '@modrinth/utils'
|
ButtonStyled,
|
||||||
|
Combobox,
|
||||||
|
defineMessages,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatMoney,
|
||||||
|
useVIntl,
|
||||||
|
} from '@modrinth/ui'
|
||||||
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
import RevenueTransaction from '~/components/ui/dashboard/RevenueTransaction.vue'
|
||||||
@@ -99,6 +106,12 @@ import { useGeneratedState } from '~/composables/generated'
|
|||||||
import { findRail } from '~/utils/muralpay-rails'
|
import { findRail } from '~/utils/muralpay-rails'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatMonth = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const generatedState = useGeneratedState()
|
const generatedState = useGeneratedState()
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
@@ -152,7 +165,7 @@ function getPeriodLabel(date) {
|
|||||||
} else if (txnDate.isSame(now.subtract(1, 'month'), 'month')) {
|
} else if (txnDate.isSame(now.subtract(1, 'month'), 'month')) {
|
||||||
return 'Last month'
|
return 'Last month'
|
||||||
} else {
|
} else {
|
||||||
return txnDate.format('MMMM YYYY')
|
return capitalizeString(formatMonth(txnDate.toDate()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -616,7 +616,7 @@
|
|||||||
<p v-if="lowestPrice" class="m-0 text-sm">
|
<p v-if="lowestPrice" class="m-0 text-sm">
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.startingAtPrice, {
|
formatMessage(messages.startingAtPrice, {
|
||||||
price: formatPrice(locale, lowestPrice, selectedCurrency, true),
|
price: formatPrice(lowestPrice, selectedCurrency, true),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</p>
|
</p>
|
||||||
@@ -644,10 +644,10 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
ModrinthServersPurchaseModal,
|
ModrinthServersPurchaseModal,
|
||||||
|
useFormatPrice,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { monthsInInterval } from '@modrinth/ui/src/utils/billing.ts'
|
import { monthsInInterval } from '@modrinth/ui/src/utils/billing.ts'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useBaseFetch } from '@/composables/fetch.js'
|
import { useBaseFetch } from '@/composables/fetch.js'
|
||||||
@@ -678,7 +678,8 @@ if (affiliateCode.value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { locale, formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
const flags = useFeatureFlags()
|
const flags = useFeatureFlags()
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<div class="section-label green">{{ formatMessage(messages.forPlayersLabel) }}</div>
|
<div class="section-label green">{{ formatMessage(messages.forPlayersLabel) }}</div>
|
||||||
<h2 class="section-tagline">
|
<h2 class="section-tagline">
|
||||||
{{ formatMessage(messages.discoverCreationsTagline, { count: formattedProjectCount }) }}
|
{{ formatMessage(messages.discoverCreationsTagline, { count: PROJECT_COUNT }) }}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="section-description">
|
<p class="section-description">
|
||||||
{{ formatMessage(messages.playersDescription) }}
|
{{ formatMessage(messages.playersDescription) }}
|
||||||
@@ -466,8 +466,6 @@ const searchQuery = ref('leave')
|
|||||||
const sortType = ref('relevance')
|
const sortType = ref('relevance')
|
||||||
|
|
||||||
const PROJECT_COUNT = 100000
|
const PROJECT_COUNT = 100000
|
||||||
const formatNumber = new Intl.NumberFormat().format
|
|
||||||
const formattedProjectCount = computed(() => formatNumber(PROJECT_COUNT))
|
|
||||||
|
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|
||||||
@@ -526,7 +524,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
discoverCreationsTagline: {
|
discoverCreationsTagline: {
|
||||||
id: 'landing.section.for-players.tagline',
|
id: 'landing.section.for-players.tagline',
|
||||||
defaultMessage: 'Discover over {count} creations',
|
defaultMessage: 'Discover over {count, number} creations',
|
||||||
},
|
},
|
||||||
shareContentTagline: {
|
shareContentTagline: {
|
||||||
id: 'landing.section.for-creators.tagline',
|
id: 'landing.section.for-creators.tagline',
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>End of the month</td>
|
<td>End of the month</td>
|
||||||
<td>{{ formatDate(endOfMonthDate) }}</td>
|
<td>{{ formatDate(endOfMonthDate.toDate()) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>NET 60 policy applied</td>
|
<td>NET 60 policy applied</td>
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="final-result">
|
<tr class="final-result">
|
||||||
<td>Available for withdrawal</td>
|
<td>Available for withdrawal</td>
|
||||||
<td>{{ formatDate(withdrawalDate) }}</td>
|
<td>{{ formatDate(withdrawalDate.toDate()) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -162,11 +162,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { StyledInput } from '@modrinth/ui'
|
import { StyledInput, useFormatDateTime, useFormatMoney } from '@modrinth/ui'
|
||||||
import { formatDate, formatMoney } from '@modrinth/utils'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
const formatMoney = useFormatMoney()
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const description =
|
const description =
|
||||||
'Information about the Rewards Program of Modrinth, an open source modding platform focused on Minecraft.'
|
'Information about the Rewards Program of Modrinth, an open source modding platform focused on Minecraft.'
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { GitGraphIcon, RssIcon } from '@modrinth/assets'
|
import { GitGraphIcon, RssIcon } from '@modrinth/assets'
|
||||||
import { articles as rawArticles } from '@modrinth/blog'
|
import { articles as rawArticles } from '@modrinth/blog'
|
||||||
import { Avatar, ButtonStyled } from '@modrinth/ui'
|
import { Avatar, ButtonStyled, useFormatDateTime } from '@modrinth/ui'
|
||||||
import type { User } from '@modrinth/utils'
|
import type { User } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, onMounted } from 'vue'
|
import { computed, onMounted } from 'vue'
|
||||||
@@ -12,6 +12,8 @@ import ShareArticleButtons from '~/components/ui/ShareArticleButtons.vue'
|
|||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
const rawArticle = rawArticles.find((article) => article.slug === route.params.slug)
|
const rawArticle = rawArticles.find((article) => article.slug === route.params.slug)
|
||||||
|
|
||||||
if (!rawArticle) {
|
if (!rawArticle) {
|
||||||
@@ -157,10 +159,10 @@ onMounted(() => {
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
<span class="hidden md:block">•</span>
|
<span class="hidden md:block">•</span>
|
||||||
<span class="hidden md:block"> {{ dayjsDate.format('MMMM D, YYYY') }}</span>
|
<span class="hidden md:block"> {{ formatDate(dayjsDate.toDate()) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-sm text-secondary sm:text-base md:hidden">
|
<span class="text-sm text-secondary sm:text-base md:hidden">
|
||||||
Posted on {{ dayjsDate.format('MMMM D, YYYY') }}</span
|
Posted on {{ formatDate(dayjsDate.toDate()) }}</span
|
||||||
>
|
>
|
||||||
<ShareArticleButtons :title="article.title" :url="articleUrl" />
|
<ShareArticleButtons :title="article.title" :url="articleUrl" />
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ChevronRightIcon, GitGraphIcon, RssIcon } from '@modrinth/assets'
|
import { ChevronRightIcon, GitGraphIcon, RssIcon } from '@modrinth/assets'
|
||||||
import { articles as rawArticles } from '@modrinth/blog'
|
import { articles as rawArticles } from '@modrinth/blog'
|
||||||
import { ButtonStyled, NewsArticleCard } from '@modrinth/ui'
|
import { ButtonStyled, NewsArticleCard, useFormatDateTime } from '@modrinth/ui'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import NewsletterButton from '~/components/ui/NewsletterButton.vue'
|
import NewsletterButton from '~/components/ui/NewsletterButton.vue'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
const articles = ref(
|
const articles = ref(
|
||||||
rawArticles
|
rawArticles
|
||||||
.map((article) => ({
|
.map((article) => ({
|
||||||
@@ -83,7 +84,7 @@ useSeoMeta({
|
|||||||
</h3>
|
</h3>
|
||||||
<p class="m-0 text-lg leading-tight">{{ featuredArticle?.summary }}</p>
|
<p class="m-0 text-lg leading-tight">{{ featuredArticle?.summary }}</p>
|
||||||
<div class="mt-auto text-secondary">
|
<div class="mt-auto text-secondary">
|
||||||
{{ dayjs(featuredArticle?.date).format('MMMM D, YYYY') }}
|
{{ formatDate(featuredArticle?.date) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</h2>
|
</h2>
|
||||||
<span>
|
<span>
|
||||||
{{ formatNumber(acceptedMembers?.length || 0) }}
|
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
||||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
projects
|
projects
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="sumDownloads.toLocaleString()"
|
v-tooltip="formatNumber(sumDownloads)"
|
||||||
class="flex items-center gap-2 font-semibold"
|
class="flex items-center gap-2 font-semibold"
|
||||||
>
|
>
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||||
@@ -293,10 +293,11 @@ import {
|
|||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
ProjectCard,
|
ProjectCard,
|
||||||
ProjectCardList,
|
ProjectCardList,
|
||||||
|
useCompactNumber,
|
||||||
|
useFormatNumber,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import type { Organization, ProjectStatus, ProjectType } from '@modrinth/utils'
|
import type { Organization, ProjectStatus, ProjectType } from '@modrinth/utils'
|
||||||
import { formatNumber } from '@modrinth/utils'
|
|
||||||
|
|
||||||
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
|
import UpToDate from '~/assets/images/illustrations/up_to_date.svg?component'
|
||||||
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
import AdPlaceholder from '~/components/ui/AdPlaceholder.vue'
|
||||||
@@ -318,7 +319,8 @@ type ProjectV3 = Labrinth.Projects.v3.Project & {
|
|||||||
const vintl = useVIntl()
|
const vintl = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const { formatMessage } = vintl
|
||||||
|
|
||||||
const formatCompactNumber = useCompactNumber(true)
|
const formatNumber = useFormatNumber()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
|
||||||
const auth: { user: any } & any = await useAuth()
|
const auth: { user: any } & any = await useAuth()
|
||||||
const user = await useUser()
|
const user = await useUser()
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
anytime.
|
anytime.
|
||||||
</p>
|
</p>
|
||||||
<p class="m-0 text-[2rem] font-bold text-purple">
|
<p class="m-0 text-[2rem] font-bold text-purple">
|
||||||
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}/mo
|
{{ formatPrice(price.prices.intervals.monthly, price.currency_code) }}/mo
|
||||||
</p>
|
</p>
|
||||||
<p class="m-0 mb-4 text-secondary">
|
<p class="m-0 mb-4 text-secondary">
|
||||||
or save
|
or save
|
||||||
@@ -86,14 +86,15 @@
|
|||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
|
import { HeartIcon, ModrinthPlusIcon, SettingsIcon, SparklesIcon, StarIcon } from '@modrinth/assets'
|
||||||
import { injectNotificationManager, PurchaseModal, useVIntl } from '@modrinth/ui'
|
import { injectNotificationManager, PurchaseModal, useFormatPrice } from '@modrinth/ui'
|
||||||
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
import { calculateSavings, getCurrency } from '@modrinth/utils'
|
||||||
|
|
||||||
import { useBaseFetch } from '@/composables/fetch.js'
|
import { useBaseFetch } from '@/composables/fetch.js'
|
||||||
import { isPermission } from '@/utils/permissions.ts'
|
import { isPermission } from '@/utils/permissions.ts'
|
||||||
import { products } from '~/generated/state.json'
|
import { products } from '~/generated/state.json'
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
const title = 'Subscribe to Modrinth Plus!'
|
const title = 'Subscribe to Modrinth Plus!'
|
||||||
const description =
|
const description =
|
||||||
@@ -116,8 +117,6 @@ useHead({
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
const vintl = useVIntl()
|
|
||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
const auth = await useAuth()
|
const auth = await useAuth()
|
||||||
|
|||||||
@@ -182,7 +182,7 @@
|
|||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.createdOn, {
|
formatMessage(messages.createdOn, {
|
||||||
date: new Date(app.created).toLocaleDateString(),
|
date: formatDate(new Date(app.created)),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
@@ -257,6 +257,7 @@ import {
|
|||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
normalizeChildren,
|
normalizeChildren,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatDateTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
|
|
||||||
@@ -272,6 +273,7 @@ import {
|
|||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatDate = useFormatDateTime()
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
|
|||||||
@@ -25,12 +25,12 @@
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span>{{ formatPrice(vintl.locale, charge.amount, charge.currency_code) }}</span>
|
<span>{{ formatPrice(charge.amount, charge.currency_code) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
||||||
⋅
|
⋅
|
||||||
{{ $dayjs(charge.due).format('YYYY-MM-DD') }}
|
{{ formatDate(charge.due) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,8 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Badge, Breadcrumbs, useVIntl } from '@modrinth/ui'
|
import { Badge, Breadcrumbs, useFormatDateTime, useFormatPrice } from '@modrinth/ui'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
|
|
||||||
import { products } from '~/generated/state.json'
|
import { products } from '~/generated/state.json'
|
||||||
|
|
||||||
@@ -47,7 +46,12 @@ definePageMeta({
|
|||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
})
|
})
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const formatPrice = useFormatPrice()
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
})
|
||||||
|
|
||||||
const { data: charges } = await useAsyncData(
|
const { data: charges } = await useAsyncData(
|
||||||
'billing/payments',
|
'billing/payments',
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
<template v-if="midasCharge">
|
<template v-if="midasCharge">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
vintl.locale,
|
|
||||||
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
||||||
midasSubscriptionPrice.currency_code,
|
midasSubscriptionPrice.currency_code,
|
||||||
)
|
)
|
||||||
@@ -60,7 +59,7 @@
|
|||||||
{{ midasSubscription.interval }}
|
{{ midasSubscription.interval }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
{{ formatPrice(price.prices.intervals.monthly, price.currency_code) }}
|
||||||
/ month
|
/ month
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
@@ -77,7 +76,7 @@
|
|||||||
>
|
>
|
||||||
<span class="opacity-70">Next:</span>
|
<span class="opacity-70">Next:</span>
|
||||||
<span class="font-semibold text-contrast">
|
<span class="font-semibold text-contrast">
|
||||||
{{ formatPrice(vintl.locale, midasCharge.amount, midasCharge.currency_code) }}
|
{{ formatPrice(midasCharge.amount, midasCharge.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
<span>/{{ midasCharge.subscription_interval.replace('ly', '') }}</span>
|
<span>/{{ midasCharge.subscription_interval.replace('ly', '') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -90,21 +89,17 @@
|
|||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(midasCharge.amount * 12 - oppositePrice, midasCharge.currency_code)
|
||||||
vintl.locale,
|
|
||||||
midasCharge.amount * 12 - oppositePrice,
|
|
||||||
midasCharge.currency_code,
|
|
||||||
)
|
|
||||||
}}/year by switching to yearly billing!
|
}}/year by switching to yearly billing!
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ $dayjs(midasSubscription.created).format('MMMM D, YYYY') }}
|
Since {{ formatDate(midasSubscription.created) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
||||||
Renews {{ $dayjs(midasCharge.due).format('MMMM D, YYYY') }}
|
Renews {{ formatDate(midasCharge.due) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
||||||
Expires {{ $dayjs(midasCharge.due).format('MMMM D, YYYY') }}
|
Expires {{ formatDate(midasCharge.due) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -116,14 +111,13 @@
|
|||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Switches to {{ midasCharge.subscription_interval }} billing on
|
Switches to {{ midasCharge.subscription_interval }} billing on
|
||||||
{{ $dayjs(midasCharge.due).format('MMMM D, YYYY') }}
|
{{ formatDate(midasCharge.due) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<span v-else class="text-sm text-secondary">
|
<span v-else class="text-sm text-secondary">
|
||||||
Or
|
Or
|
||||||
{{ formatPrice(vintl.locale, price.prices.intervals.yearly, price.currency_code) }} /
|
{{ formatPrice(price.prices.intervals.yearly, price.currency_code) }} / year (save
|
||||||
year (save
|
|
||||||
{{
|
{{
|
||||||
calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly)
|
calculateSavings(price.prices.intervals.monthly, price.prices.intervals.yearly)
|
||||||
}}%)!
|
}}%)!
|
||||||
@@ -188,7 +182,6 @@
|
|||||||
v-tooltip="
|
v-tooltip="
|
||||||
midasCharge.subscription_interval === 'yearly'
|
midasCharge.subscription_interval === 'yearly'
|
||||||
? `Monthly billing will cost you an additional ${formatPrice(
|
? `Monthly billing will cost you an additional ${formatPrice(
|
||||||
vintl.locale,
|
|
||||||
oppositePrice * 12 - midasCharge.amount,
|
oppositePrice * 12 - midasCharge.amount,
|
||||||
midasCharge.currency_code,
|
midasCharge.currency_code,
|
||||||
)} per year`
|
)} per year`
|
||||||
@@ -307,7 +300,6 @@
|
|||||||
<span class="text-contrast">
|
<span class="text-contrast">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
vintl.locale,
|
|
||||||
getProductPrice(getPyroProduct(subscription), subscription.interval)
|
getProductPrice(getPyroProduct(subscription), subscription.interval)
|
||||||
.prices.intervals[subscription.interval],
|
.prices.intervals[subscription.interval],
|
||||||
getProductPrice(getPyroProduct(subscription), subscription.interval)
|
getProductPrice(getPyroProduct(subscription), subscription.interval)
|
||||||
@@ -333,7 +325,6 @@
|
|||||||
<span class="font-semibold text-contrast">
|
<span class="font-semibold text-contrast">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
vintl.locale,
|
|
||||||
getPyroCharge(subscription).amount,
|
getPyroCharge(subscription).amount,
|
||||||
getPyroCharge(subscription).currency_code,
|
getPyroCharge(subscription).currency_code,
|
||||||
)
|
)
|
||||||
@@ -351,13 +342,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="getPyroCharge(subscription)" class="mb-4 flex flex-col items-end">
|
<div v-if="getPyroCharge(subscription)" class="mb-4 flex flex-col items-end">
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ $dayjs(subscription.created).format('MMMM D, YYYY') }}
|
Since {{ formatDate(subscription.created) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="getPyroCharge(subscription).status === 'open'"
|
v-if="getPyroCharge(subscription).status === 'open'"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Renews {{ $dayjs(getPyroCharge(subscription).due).format('MMMM D, YYYY') }}
|
Renews {{ formatDate(getPyroCharge(subscription).due) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
@@ -371,7 +362,7 @@
|
|||||||
Switches to
|
Switches to
|
||||||
{{ getPyroCharge(subscription).subscription_interval }}
|
{{ getPyroCharge(subscription).subscription_interval }}
|
||||||
billing on
|
billing on
|
||||||
{{ $dayjs(getPyroCharge(subscription).due).format('MMMM D, YYYY') }}
|
{{ formatDate(getPyroCharge(subscription).due) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="getPyroCharge(subscription).status === 'processing'"
|
v-else-if="getPyroCharge(subscription).status === 'processing'"
|
||||||
@@ -384,7 +375,7 @@
|
|||||||
v-else-if="getPyroCharge(subscription).status === 'cancelled'"
|
v-else-if="getPyroCharge(subscription).status === 'cancelled'"
|
||||||
class="text-sm text-secondary"
|
class="text-sm text-secondary"
|
||||||
>
|
>
|
||||||
Expires {{ $dayjs(getPyroCharge(subscription).due).format('MMMM D, YYYY') }}
|
Expires {{ formatDate(getPyroCharge(subscription).due) }}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-else-if="getPyroCharge(subscription).status === 'failed'"
|
v-else-if="getPyroCharge(subscription).status === 'failed'"
|
||||||
@@ -624,9 +615,11 @@ import {
|
|||||||
paymentMethodMessages,
|
paymentMethodMessages,
|
||||||
PurchaseModal,
|
PurchaseModal,
|
||||||
ServerListing,
|
ServerListing,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatPrice,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { calculateSavings, formatPrice, getCurrency } from '@modrinth/utils'
|
import { calculateSavings, getCurrency } from '@modrinth/utils'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { useBaseFetch } from '@/composables/fetch.js'
|
import { useBaseFetch } from '@/composables/fetch.js'
|
||||||
@@ -655,8 +648,13 @@ useHead({
|
|||||||
|
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const formatPrice = useFormatPrice()
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const deleteModalMessages = defineMessages({
|
const deleteModalMessages = defineMessages({
|
||||||
title: {
|
title: {
|
||||||
|
|||||||
@@ -119,16 +119,7 @@
|
|||||||
<CopyCode :text="pat.access_token" />
|
<CopyCode :text="pat.access_token" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span
|
<span v-tooltip="pat.last_used ? formatDateTime(pat.last_used) : null">
|
||||||
v-tooltip="
|
|
||||||
pat.last_used
|
|
||||||
? formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(pat.last_used),
|
|
||||||
time: new Date(pat.last_used),
|
|
||||||
})
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template v-if="pat.last_used">
|
<template v-if="pat.last_used">
|
||||||
{{
|
{{
|
||||||
formatMessage(tokenMessages.lastUsed, {
|
formatMessage(tokenMessages.lastUsed, {
|
||||||
@@ -139,14 +130,7 @@
|
|||||||
<template v-else>{{ formatMessage(tokenMessages.neverUsed) }}</template>
|
<template v-else>{{ formatMessage(tokenMessages.neverUsed) }}</template>
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span
|
<span v-tooltip="formatDateTime(pat.expires)">
|
||||||
v-tooltip="
|
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(pat.expires),
|
|
||||||
time: new Date(pat.expires),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template v-if="new Date(pat.expires) > new Date()">
|
<template v-if="new Date(pat.expires) > new Date()">
|
||||||
{{
|
{{
|
||||||
formatMessage(tokenMessages.expiresIn, {
|
formatMessage(tokenMessages.expiresIn, {
|
||||||
@@ -163,14 +147,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span
|
<span v-tooltip="formatDateTime(pat.created)">
|
||||||
v-tooltip="
|
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(pat.created),
|
|
||||||
time: new Date(pat.created),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
formatMessage(commonMessages.createdAgoLabel, {
|
formatMessage(commonMessages.createdAgoLabel, {
|
||||||
ago: formatRelativeTime(pat.created),
|
ago: formatRelativeTime(pat.created),
|
||||||
@@ -222,6 +199,7 @@ import {
|
|||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
IntlFormatted,
|
IntlFormatted,
|
||||||
StyledInput,
|
StyledInput,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -240,6 +218,10 @@ const { addNotification } = injectNotificationManager()
|
|||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const createModalMessages = defineMessages({
|
const createModalMessages = defineMessages({
|
||||||
createTitle: {
|
createTitle: {
|
||||||
|
|||||||
@@ -15,14 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="session.city">{{ session.city }}, {{ session.country }} ⋅ </template>
|
<template v-if="session.city">{{ session.city }}, {{ session.country }} ⋅ </template>
|
||||||
<span
|
<span v-tooltip="formatDateTime(session.last_login)">
|
||||||
v-tooltip="
|
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(session.last_login),
|
|
||||||
time: new Date(session.last_login),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.lastAccessedAgoLabel, {
|
formatMessage(messages.lastAccessedAgoLabel, {
|
||||||
ago: formatRelativeTime(session.last_login),
|
ago: formatRelativeTime(session.last_login),
|
||||||
@@ -30,14 +23,7 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span
|
<span v-tooltip="formatDateTime(session.created)">
|
||||||
v-tooltip="
|
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(session.created),
|
|
||||||
time: new Date(session.created),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.createdAgoLabel, {
|
formatMessage(messages.createdAgoLabel, {
|
||||||
ago: formatRelativeTime(session.created),
|
ago: formatRelativeTime(session.created),
|
||||||
@@ -62,6 +48,7 @@ import {
|
|||||||
commonSettingsMessages,
|
commonSettingsMessages,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
injectNotificationManager,
|
injectNotificationManager,
|
||||||
|
useFormatDateTime,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -73,6 +60,10 @@ definePageMeta({
|
|||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
currentSessionLabel: {
|
currentSessionLabel: {
|
||||||
|
|||||||
@@ -155,27 +155,24 @@
|
|||||||
{{
|
{{
|
||||||
formatMessage(messages.profileProjectsLabel, {
|
formatMessage(messages.profileProjectsLabel, {
|
||||||
count: formatCompactNumber(projects?.length || 0),
|
count: formatCompactNumber(projects?.length || 0),
|
||||||
|
countPlural: formatCompactNumberPlural(projects?.length || 0),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="sumDownloads.toLocaleString()"
|
v-tooltip="formatNumber(sumDownloads)"
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
class="flex items-center gap-2 border-0 border-r border-solid border-divider pr-4 font-semibold"
|
||||||
>
|
>
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||||
{{
|
{{
|
||||||
formatMessage(messages.profileDownloadsLabel, {
|
formatMessage(messages.profileDownloadsLabel, {
|
||||||
count: formatCompactNumber(sumDownloads),
|
count: formatCompactNumber(sumDownloads),
|
||||||
|
countPlural: formatCompactNumberPlural(sumDownloads),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="formatDateTime(user.created)"
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(user.created),
|
|
||||||
time: new Date(user.created),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
class="flex items-center gap-2 font-semibold"
|
class="flex items-center gap-2 font-semibold"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="h-6 w-6 text-secondary" />
|
<CalendarIcon class="h-6 w-6 text-secondary" />
|
||||||
@@ -502,6 +499,9 @@ import {
|
|||||||
ProjectCard,
|
ProjectCard,
|
||||||
ProjectCardList,
|
ProjectCardList,
|
||||||
TagItem,
|
TagItem,
|
||||||
|
useCompactNumber,
|
||||||
|
useFormatDateTime,
|
||||||
|
useFormatNumber,
|
||||||
useRelativeTime,
|
useRelativeTime,
|
||||||
useVIntl,
|
useVIntl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
@@ -528,12 +528,14 @@ const cosmetics = useCosmetics()
|
|||||||
const tags = useGeneratedState()
|
const tags = useGeneratedState()
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { formatMessage } = vintl
|
const formatNumber = useFormatNumber()
|
||||||
|
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||||
const formatCompactNumber = useCompactNumber(true)
|
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const { addNotification } = injectNotificationManager()
|
const { addNotification } = injectNotificationManager()
|
||||||
|
|
||||||
@@ -542,11 +544,11 @@ const baseId = useId()
|
|||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
profileProjectsLabel: {
|
profileProjectsLabel: {
|
||||||
id: 'profile.label.projects',
|
id: 'profile.label.projects',
|
||||||
defaultMessage: '{count} {count, plural, one {project} other {projects}}',
|
defaultMessage: '{count} {countPlural, plural, one {project} other {projects}}',
|
||||||
},
|
},
|
||||||
profileDownloadsLabel: {
|
profileDownloadsLabel: {
|
||||||
id: 'profile.label.downloads',
|
id: 'profile.label.downloads',
|
||||||
defaultMessage: '{count} {count, plural, one {download} other {downloads}}',
|
defaultMessage: '{count} {countPlural, plural, one {download} other {downloads}}',
|
||||||
},
|
},
|
||||||
profileJoinedLabel: {
|
profileJoinedLabel: {
|
||||||
id: 'profile.label.joined',
|
id: 'profile.label.joined',
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"intl-messageformat": "^10.7.7",
|
"intl-messageformat": "^10.7.7",
|
||||||
|
"lru-cache": "^11.2.4",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"markdown-it": "^13.0.2",
|
"markdown-it": "^13.0.2",
|
||||||
"postprocessing": "^6.37.6",
|
"postprocessing": "^6.37.6",
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SpinnerIcon } from '@modrinth/assets'
|
import { SpinnerIcon } from '@modrinth/assets'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useVIntl } from '../../composables/i18n'
|
import { useFormatPrice } from '../../composables'
|
||||||
import Accordion from '../base/Accordion.vue'
|
import Accordion from '../base/Accordion.vue'
|
||||||
|
|
||||||
const { locale } = useVIntl()
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
export type BillingItem = {
|
export type BillingItem = {
|
||||||
title: string
|
title: string
|
||||||
@@ -38,7 +37,7 @@ const periodSuffix = computed(() => {
|
|||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<SpinnerIcon class="animate-spin size-4" />
|
<SpinnerIcon class="animate-spin size-4" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else> {{ formatPrice(locale, total, currency) }} </template
|
<template v-else> {{ formatPrice(total, currency) }} </template
|
||||||
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,7 +56,7 @@ const periodSuffix = computed(() => {
|
|||||||
<template v-if="loading">
|
<template v-if="loading">
|
||||||
<SpinnerIcon class="animate-spin size-4" />
|
<SpinnerIcon class="animate-spin size-4" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else> {{ formatPrice(locale, amount, currency) }} </template
|
<template v-else> {{ formatPrice(amount, currency) }} </template
|
||||||
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
><span class="text-xs text-secondary">{{ periodSuffix }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Labrinth } from '@modrinth/api-client'
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
import { InfoIcon } from '@modrinth/assets'
|
import { InfoIcon } from '@modrinth/assets'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
import { Menu } from 'floating-vue'
|
import { Menu } from 'floating-vue'
|
||||||
import { computed, inject, type Ref } from 'vue'
|
import { computed, inject, type Ref } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatPrice } from '../../composables'
|
||||||
import { type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
import { type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||||
@@ -30,7 +30,8 @@ const emit = defineEmits<{
|
|||||||
(e: 'select', plan: Labrinth.Billing.Internal.Product): void
|
(e: 'select', plan: Labrinth.Billing.Internal.Product): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
// TODO: Use DI framework when merged.
|
// TODO: Use DI framework when merged.
|
||||||
const selectedInterval = inject<Ref<ServerBillingInterval>>('selectedInterval')
|
const selectedInterval = inject<Ref<ServerBillingInterval>>('selectedInterval')
|
||||||
@@ -101,7 +102,7 @@ const mostPopularStyle = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="m-0 text-lg font-bold text-contrast">
|
<span class="m-0 text-lg font-bold text-contrast">
|
||||||
{{ formatPrice(locale, perMonth, currency, true) }}
|
{{ formatPrice(perMonth, currency, true) }}
|
||||||
<span class="text-sm font-semibold text-secondary">
|
<span class="text-sm font-semibold text-secondary">
|
||||||
/ month{{ selectedInterval !== 'monthly' ? `, billed ${selectedInterval}` : '' }}
|
/ month{{ selectedInterval !== 'monthly' ? `, billed ${selectedInterval}` : '' }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -232,7 +232,7 @@
|
|||||||
}}%
|
}}%
|
||||||
</span>
|
</span>
|
||||||
<span class="ml-auto text-lg" :class="{ 'text-secondary': selectedPlan !== interval }">
|
<span class="ml-auto text-lg" :class="{ 'text-secondary': selectedPlan !== interval }">
|
||||||
{{ formatPrice(locale, rawPrice, price.currency_code) }}
|
{{ formatPrice(rawPrice, price.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
<span class="text-xl text-secondary">Total</span>
|
<span class="text-xl text-secondary">Total</span>
|
||||||
<div class="flex items-baseline gap-2">
|
<div class="flex items-baseline gap-2">
|
||||||
<span class="text-2xl font-extrabold text-primary">
|
<span class="text-2xl font-extrabold text-primary">
|
||||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
{{ formatPrice(price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-lg text-secondary">/ {{ selectedPlan }}</span>
|
<span class="text-lg text-secondary">/ {{ selectedPlan }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,23 +304,21 @@
|
|||||||
}}
|
}}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="existingPlan" class="text-secondary text-end">
|
<span v-if="existingPlan" class="text-secondary text-end">
|
||||||
{{ formatPrice(locale, total - tax, price.currency_code) }}
|
{{ formatPrice(total - tax, price.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text-secondary text-end">
|
<span v-else class="text-secondary text-end">
|
||||||
{{ formatPrice(locale, total - tax, price.currency_code) }} /
|
{{ formatPrice(total - tax, price.currency_code) }} /
|
||||||
{{ selectedPlan }}
|
{{ selectedPlan }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span class="text-secondary">Tax</span>
|
<span class="text-secondary">Tax</span>
|
||||||
<span class="text-secondary text-end">{{
|
<span class="text-secondary text-end">{{ formatPrice(tax, price.currency_code) }}</span>
|
||||||
formatPrice(locale, tax, price.currency_code)
|
|
||||||
}}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
<div class="mt-4 flex justify-between border-0 border-t border-solid border-code-bg pt-4">
|
||||||
<span class="text-lg font-bold">Today's total</span>
|
<span class="text-lg font-bold">Today's total</span>
|
||||||
<span class="text-lg font-extrabold text-primary text-end">
|
<span class="text-lg font-extrabold text-primary text-end">
|
||||||
{{ formatPrice(locale, total, price.currency_code) }}
|
{{ formatPrice(total, price.currency_code) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -416,9 +414,9 @@
|
|||||||
<strong>By clicking "Subscribe", you are purchasing a recurring subscription.</strong>
|
<strong>By clicking "Subscribe", you are purchasing a recurring subscription.</strong>
|
||||||
<br />
|
<br />
|
||||||
You'll be charged
|
You'll be charged
|
||||||
{{ formatPrice(locale, price.prices.intervals[selectedPlan], price.currency_code) }}
|
{{ formatPrice(price.prices.intervals[selectedPlan], price.currency_code) }}
|
||||||
/ {{ selectedPlan }} plus applicable taxes starting
|
/ {{ selectedPlan }} plus applicable taxes starting
|
||||||
{{ existingPlan ? dayjs(renewalDate).format('MMMM D, YYYY') : 'today' }}, until you cancel.
|
{{ existingPlan ? formatDate(renewalDate) : 'today' }}, until you cancel.
|
||||||
<br />
|
<br />
|
||||||
You can cancel anytime from your settings page.
|
You can cancel anytime from your settings page.
|
||||||
</p>
|
</p>
|
||||||
@@ -545,12 +543,13 @@ import {
|
|||||||
UnknownIcon,
|
UnknownIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { calculateSavings, createStripeElements, formatPrice, getCurrency } from '@modrinth/utils'
|
import { calculateSavings, createStripeElements, getCurrency } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
import { computed, nextTick, reactive, ref, watch } from 'vue'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
|
|
||||||
import { useVIntl } from '../../composables/i18n'
|
import { useVIntl } from '../../composables/i18n'
|
||||||
|
import { useFormatDateTime, useFormatPrice } from '../../composables/index.ts'
|
||||||
import { paymentMethodMessages } from '../../utils/common-messages'
|
import { paymentMethodMessages } from '../../utils/common-messages'
|
||||||
import Admonition from '../base/Admonition.vue'
|
import Admonition from '../base/Admonition.vue'
|
||||||
import Checkbox from '../base/Checkbox.vue'
|
import Checkbox from '../base/Checkbox.vue'
|
||||||
@@ -560,7 +559,9 @@ import AnimatedLogo from '../brand/AnimatedLogo.vue'
|
|||||||
import NewModal from '../modal/NewModal.vue'
|
import NewModal from '../modal/NewModal.vue'
|
||||||
import LoaderIcon from '../servers/icons/LoaderIcon.vue'
|
import LoaderIcon from '../servers/icons/LoaderIcon.vue'
|
||||||
|
|
||||||
const { locale, formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
product: {
|
product: {
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Labrinth } from '@modrinth/api-client'
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
import { formatPrice } from '@modrinth/utils'
|
|
||||||
import { computed, provide } from 'vue'
|
import { computed, provide } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatPrice } from '../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||||
import OptionGroup from '../base/OptionGroup.vue'
|
import OptionGroup from '../base/OptionGroup.vue'
|
||||||
import ModalBasedServerPlan from './ModalBasedServerPlan.vue'
|
import ModalBasedServerPlan from './ModalBasedServerPlan.vue'
|
||||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
availableProducts: Labrinth.Billing.Internal.Product[]
|
availableProducts: Labrinth.Billing.Internal.Product[]
|
||||||
@@ -209,7 +210,7 @@ provide('selectedInterval', selectedInterval)
|
|||||||
<span class="text-2xl font-semibold text-contrast">Custom</span>
|
<span class="text-2xl font-semibold text-contrast">Custom</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="m-0 text-lg font-bold text-contrast">
|
<span class="m-0 text-lg font-bold text-contrast">
|
||||||
{{ formatPrice(locale, customStartingPrice, currency, true) }}
|
{{ formatPrice(customStartingPrice, currency, true) }}
|
||||||
<span class="text-sm font-semibold text-secondary">
|
<span class="text-sm font-semibold text-secondary">
|
||||||
/ month<template v-if="selectedInterval !== 'monthly'"
|
/ month<template v-if="selectedInterval !== 'monthly'"
|
||||||
>, billed {{ selectedInterval }}</template
|
>, billed {{ selectedInterval }}</template
|
||||||
@@ -221,7 +222,7 @@ provide('selectedInterval', selectedInterval)
|
|||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span v-if="customPricePerGb" class="text-sm text-secondary">
|
<span v-if="customPricePerGb" class="text-sm text-secondary">
|
||||||
From {{ formatPrice(locale, customPricePerGb, currency, true) }} / GB
|
From {{ formatPrice(customPricePerGb, currency, true) }} / GB
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { Archon, Labrinth } from '@modrinth/api-client'
|
|||||||
import { InfoIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
import { InfoIcon, SpinnerIcon, XIcon } from '@modrinth/assets'
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { formatPrice } from '../../../../utils'
|
import { useFormatPrice } from '../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils.ts'
|
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils.ts'
|
||||||
import { regionOverrides } from '../../utils/regions.ts'
|
import { regionOverrides } from '../../utils/regions.ts'
|
||||||
@@ -14,7 +14,8 @@ import type { RegionPing, ServerBillingInterval } from './ModrinthServersPurchas
|
|||||||
import ServersRegionButton from './ServersRegionButton.vue'
|
import ServersRegionButton from './ServersRegionButton.vue'
|
||||||
import ServersSpecs from './ServersSpecs.vue'
|
import ServersSpecs from './ServersSpecs.vue'
|
||||||
|
|
||||||
const { formatMessage, locale } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
regions: Archon.Servers.v1.Region[]
|
regions: Archon.Servers.v1.Region[]
|
||||||
@@ -283,7 +284,7 @@ onMounted(() => {
|
|||||||
<Slider v-model="selectedRam" :min="minRam" :max="maxRam" :step="2" unit="GB" />
|
<Slider v-model="selectedRam" :min="minRam" :max="maxRam" :step="2" unit="GB" />
|
||||||
<p v-if="selectedPrice" class="mt-2 mb-0">
|
<p v-if="selectedPrice" class="mt-2 mb-0">
|
||||||
<span class="text-contrast text-lg font-bold"
|
<span class="text-contrast text-lg font-bold"
|
||||||
>{{ formatPrice(locale, selectedPrice, currency, true) }} / month</span
|
>{{ formatPrice(selectedPrice, currency, true) }} / month</span
|
||||||
><span v-if="interval !== 'monthly'">, billed {{ interval }}</span>
|
><span v-if="interval !== 'monthly'">, billed {{ interval }}</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="bg-bg rounded-xl p-4 mt-2 text-secondary h-14">
|
<div class="bg-bg rounded-xl p-4 mt-2 text-secondary h-14">
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ import {
|
|||||||
SpinnerIcon,
|
SpinnerIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { formatPrice, getPingLevel } from '@modrinth/utils'
|
import { getPingLevel } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import type Stripe from 'stripe'
|
import type Stripe from 'stripe'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatPrice } from '../../composables'
|
||||||
import { useVIntl } from '../../composables/i18n'
|
import { useVIntl } from '../../composables/i18n'
|
||||||
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
import { getPriceForInterval, monthsInInterval } from '../../utils/product-utils'
|
||||||
import { regionOverrides } from '../../utils/regions'
|
import { regionOverrides } from '../../utils/regions'
|
||||||
@@ -27,8 +28,8 @@ import FormattedPaymentMethod from './FormattedPaymentMethod.vue'
|
|||||||
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
import type { ServerBillingInterval } from './ModrinthServersPurchaseModal.vue'
|
||||||
import ServersSpecs from './ServersSpecs.vue'
|
import ServersSpecs from './ServersSpecs.vue'
|
||||||
|
|
||||||
const vintl = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const { locale, formatMessage } = vintl
|
const formatPrice = useFormatPrice()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'changePaymentMethod' | 'reloadPaymentIntent'): void
|
(e: 'changePaymentMethod' | 'reloadPaymentIntent'): void
|
||||||
@@ -246,7 +247,7 @@ function setInterval(newInterval: ServerBillingInterval) {
|
|||||||
>Pay monthly</span
|
>Pay monthly</span
|
||||||
>
|
>
|
||||||
<span class="text-sm text-secondary flex items-center gap-1"
|
<span class="text-sm text-secondary flex items-center gap-1"
|
||||||
>{{ formatPrice(locale, monthlyPrice, currency, true) }} / month</span
|
>{{ formatPrice(monthlyPrice, currency, true) }} / month</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -268,17 +269,10 @@ function setInterval(newInterval: ServerBillingInterval) {
|
|||||||
>{{ interval === 'quarterly' ? 'Saving' : 'Save' }} 16%</span
|
>{{ interval === 'quarterly' ? 'Saving' : 'Save' }} 16%</span
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span class="text-sm text-secondary flex items-center gap-1"
|
<span class="text-sm text-secondary flex items-center gap-1">
|
||||||
>{{
|
{{ formatPrice((quarterlyPrice ?? 0) / monthsInInterval['quarterly'], currency, true) }} /
|
||||||
formatPrice(
|
month
|
||||||
locale,
|
</span>
|
||||||
(quarterlyPrice ?? 0) / monthsInInterval['quarterly'],
|
|
||||||
currency,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/ month</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -346,14 +340,14 @@ function setInterval(newInterval: ServerBillingInterval) {
|
|||||||
Today, you will be charged a prorated amount for the remainder of your current billing cycle.
|
Today, you will be charged a prorated amount for the remainder of your current billing cycle.
|
||||||
<br />
|
<br />
|
||||||
Your subscription will renew at
|
Your subscription will renew at
|
||||||
{{ formatPrice(locale, selectedPlanPriceForInterval, currency) }} / {{ period }} plus
|
{{ formatPrice(selectedPlanPriceForInterval, currency) }} / {{ period }} plus applicable taxes
|
||||||
applicable taxes at the end of your current billing interval, until you cancel. You can cancel
|
at the end of your current billing interval, until you cancel. You can cancel anytime from
|
||||||
anytime from your settings page.
|
your settings page.
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
You'll be charged
|
You'll be charged
|
||||||
<SpinnerIcon v-if="loading" class="animate-spin relative top-0.5 mx-2" /><template v-else>{{
|
<SpinnerIcon v-if="loading" class="animate-spin relative top-0.5 mx-2" /><template v-else>{{
|
||||||
formatPrice(locale, total, currency)
|
formatPrice(total, currency)
|
||||||
}}</template>
|
}}</template>
|
||||||
every {{ period }} plus applicable taxes starting today, until you cancel. You can cancel
|
every {{ period }} plus applicable taxes starting today, until you cancel. You can cancel
|
||||||
anytime from your settings page.
|
anytime from your settings page.
|
||||||
|
|||||||
@@ -47,12 +47,19 @@ import type { VersionEntry } from '@modrinth/utils/changelog'
|
|||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
import { useRelativeTime } from '../../composables'
|
import { useFormatDateTime, useRelativeTime } from '../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import AutoLink from '../base/AutoLink.vue'
|
import AutoLink from '../base/AutoLink.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
const formatDate = useFormatDateTime({
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -71,10 +78,10 @@ const props = withDefaults(
|
|||||||
const currentDate = ref(dayjs())
|
const currentDate = ref(dayjs())
|
||||||
const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtract(1, 'week')))
|
const recent = computed(() => props.entry.date.isAfter(currentDate.value.subtract(1, 'week')))
|
||||||
const future = computed(() => props.entry.date.isAfter(currentDate.value))
|
const future = computed(() => props.entry.date.isAfter(currentDate.value))
|
||||||
const dateTooltip = computed(() => props.entry.date.format('MMMM D, YYYY [at] h:mm A'))
|
const dateTooltip = computed(() => formatDateTime(props.entry.date.toDate()))
|
||||||
|
|
||||||
const relativeDate = computed(() => formatRelativeTime(props.entry.date.toISOString()))
|
const relativeDate = computed(() => formatRelativeTime(props.entry.date.toDate()))
|
||||||
const longDate = computed(() => props.entry.date.format('MMMM D, YYYY'))
|
const longDate = computed(() => formatDate(props.entry.date.toDate()))
|
||||||
const versionName = computed(() => props.entry.version ?? longDate.value)
|
const versionName = computed(() => props.entry.version ?? longDate.value)
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<!-- eslint-disable no-console -->
|
<!-- eslint-disable no-console -->
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatNumber } from '@modrinth/utils'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatNumber } from '../../composables/index.ts'
|
||||||
import Button from '../base/Button.vue'
|
import Button from '../base/Button.vue'
|
||||||
import Checkbox from '../base/Checkbox.vue'
|
import Checkbox from '../base/Checkbox.vue'
|
||||||
|
|
||||||
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
name: {
|
name: {
|
||||||
@@ -149,7 +150,7 @@ const chartOptions = ref({
|
|||||||
!props.hideTotal
|
!props.hideTotal
|
||||||
? `<div class="value">
|
? `<div class="value">
|
||||||
${props.prefix}
|
${props.prefix}
|
||||||
${formatNumber(series.reduce((a, b) => a + b[dataPointIndex], 0).toString(), false)}
|
${formatNumber(series.reduce((a, b) => a + b[dataPointIndex], 0).toString())}
|
||||||
${props.suffix}
|
${props.suffix}
|
||||||
</div>`
|
</div>`
|
||||||
: ``
|
: ``
|
||||||
@@ -163,7 +164,7 @@ const chartOptions = ref({
|
|||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
${props.prefix}
|
${props.prefix}
|
||||||
${formatNumber(value[dataPointIndex], false)}
|
${formatNumber(value[dataPointIndex])}
|
||||||
${props.suffix}
|
${props.suffix}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
<!-- eslint-disable eslint-comments/require-description -->
|
<!-- eslint-disable eslint-comments/require-description -->
|
||||||
<script setup>
|
<script setup>
|
||||||
import { formatNumber } from '@modrinth/utils'
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { defineAsyncComponent, ref } from 'vue'
|
import { defineAsyncComponent, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatNumber } from '../../composables/index.ts'
|
||||||
import Card from '../base/Card.vue'
|
import Card from '../base/Card.vue'
|
||||||
|
|
||||||
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
const VueApexCharts = defineAsyncComponent(() => import('vue3-apexcharts'))
|
||||||
|
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
value: {
|
value: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -124,7 +126,7 @@ const chartOptions = ref({
|
|||||||
</div>
|
</div>
|
||||||
<div class="value">
|
<div class="value">
|
||||||
${props.prefix}
|
${props.prefix}
|
||||||
${formatNumber(value[dataPointIndex], false)}
|
${formatNumber(value[dataPointIndex])}
|
||||||
${props.suffix}
|
${props.suffix}
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import dayjs from 'dayjs'
|
import { useFormatDateTime } from '../../composables'
|
||||||
|
|
||||||
import AutoLink from '../base/AutoLink.vue'
|
import AutoLink from '../base/AutoLink.vue'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
export interface Article {
|
export interface Article {
|
||||||
path: string
|
path: string
|
||||||
thumbnail: string
|
thumbnail: string
|
||||||
@@ -34,7 +35,7 @@ defineProps<{
|
|||||||
{{ article.summary }}
|
{{ article.summary }}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-auto text-sm text-secondary">
|
<div class="mt-auto text-sm text-secondary">
|
||||||
{{ dayjs(article.date).format('MMMM D, YYYY') }}
|
{{ formatDate(article.date) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import { computed, getCurrentInstance } from 'vue'
|
import { computed, getCurrentInstance } from 'vue'
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
|
||||||
|
import { useCompactNumber } from '../../composables'
|
||||||
import { useRelativeTime } from '../../composables/how-ago'
|
import { useRelativeTime } from '../../composables/how-ago'
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import { commonMessages } from '../../utils/common-messages'
|
import { commonMessages } from '../../utils/common-messages'
|
||||||
@@ -66,11 +67,7 @@ const hasContentListener = computed(() => typeof instance?.vnode.props?.onConten
|
|||||||
const hasUnlinkListener = computed(() => typeof instance?.vnode.props?.onUnlink === 'function')
|
const hasUnlinkListener = computed(() => typeof instance?.vnode.props?.onUnlink === 'function')
|
||||||
|
|
||||||
const formatTimeAgo = useRelativeTime()
|
const formatTimeAgo = useRelativeTime()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
const formatCompact = (n: number | undefined) => {
|
|
||||||
if (n === undefined) return ''
|
|
||||||
return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 2 }).format(n)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -165,12 +162,12 @@ const formatCompact = (n: number | undefined) => {
|
|||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<div v-if="project.downloads !== undefined" class="flex items-center gap-2 text-secondary">
|
<div v-if="project.downloads !== undefined" class="flex items-center gap-2 text-secondary">
|
||||||
<DownloadIcon class="size-5" />
|
<DownloadIcon class="size-5" />
|
||||||
<span class="font-medium">{{ formatCompact(project.downloads) }}</span>
|
<span class="font-medium">{{ formatCompactNumber(project.downloads) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="project.followers !== undefined" class="flex items-center gap-2 text-secondary">
|
<div v-if="project.followers !== undefined" class="flex items-center gap-2 text-secondary">
|
||||||
<HeartIcon class="size-5" />
|
<HeartIcon class="size-5" />
|
||||||
<span class="font-medium">{{ formatCompact(project.followers) }}</span>
|
<span class="font-medium">{{ formatCompactNumber(project.followers) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="categories?.length" class="flex flex-wrap gap-2">
|
<div v-if="categories?.length" class="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ import {
|
|||||||
import { capitalizeString, renderHighlightedString } from '@modrinth/utils'
|
import { capitalizeString, renderHighlightedString } from '@modrinth/utils'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatDateTime } from '../../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||||
import { commonMessages } from '../../../utils/common-messages'
|
import { commonMessages } from '../../../utils/common-messages'
|
||||||
import Avatar from '../../base/Avatar.vue'
|
import Avatar from '../../base/Avatar.vue'
|
||||||
@@ -188,6 +189,7 @@ import StyledInput from '../../base/StyledInput.vue'
|
|||||||
import NewModal from '../../modal/NewModal.vue'
|
import NewModal from '../../modal/NewModal.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
updateVersionHeader: {
|
updateVersionHeader: {
|
||||||
@@ -341,11 +343,7 @@ function getBadgeClasses(version: Labrinth.Versions.v2.Version): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatLongDate(dateString: string): string {
|
function formatLongDate(dateString: string): string {
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
return formatDate(new Date(dateString))
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatLoaderGameVersion(version: Labrinth.Versions.v2.Version): string {
|
function formatLoaderGameVersion(version: Labrinth.Versions.v2.Version): string {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
searchPlaceholder: {
|
searchPlaceholder: {
|
||||||
id: 'instances.modpack-content-modal.search-placeholder',
|
id: 'instances.modpack-content-modal.search-placeholder',
|
||||||
defaultMessage: 'Search {count} projects',
|
defaultMessage: 'Search {count, number} {count, plural, one {project} other {projects}}',
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
id: 'instances.modpack-content-modal.loading',
|
id: 'instances.modpack-content-modal.loading',
|
||||||
@@ -82,7 +82,7 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
selectedCount: {
|
selectedCount: {
|
||||||
id: 'instances.modpack-content-modal.selected-count',
|
id: 'instances.modpack-content-modal.selected-count',
|
||||||
defaultMessage: '{count} selected',
|
defaultMessage: '{count, number} selected',
|
||||||
},
|
},
|
||||||
enable: {
|
enable: {
|
||||||
id: 'instances.modpack-content-modal.enable',
|
id: 'instances.modpack-content-modal.enable',
|
||||||
|
|||||||
@@ -27,20 +27,20 @@
|
|||||||
v-tooltip="
|
v-tooltip="
|
||||||
capitalizeString(
|
capitalizeString(
|
||||||
formatMessage(commonMessages.projectDownloads, {
|
formatMessage(commonMessages.projectDownloads, {
|
||||||
count: formatNumber(project.downloads, false),
|
count: project.downloads,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="flex items-center gap-2 font-semibold cursor-help"
|
class="flex items-center gap-2 font-semibold cursor-help"
|
||||||
>
|
>
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||||
{{ formatNumber(project.downloads) }}
|
{{ formatCompactNumber(project.downloads) }}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
capitalizeString(
|
capitalizeString(
|
||||||
formatMessage(commonMessages.projectFollowers, {
|
formatMessage(commonMessages.projectFollowers, {
|
||||||
count: formatNumber(project.followers, false),
|
count: project.followers,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
>
|
>
|
||||||
<HeartIcon class="h-6 w-6 text-secondary" />
|
<HeartIcon class="h-6 w-6 text-secondary" />
|
||||||
<span class="font-semibold">
|
<span class="font-semibold">
|
||||||
{{ formatNumber(project.followers) }}
|
{{ formatCompactNumber(project.followers) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -74,11 +74,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Labrinth } from '@modrinth/api-client'
|
import type { Labrinth } from '@modrinth/api-client'
|
||||||
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
||||||
import { capitalizeString, formatNumber, type Project } from '@modrinth/utils'
|
import { capitalizeString, type Project } from '@modrinth/utils'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { useVIntl } from '../../composables'
|
import { useCompactNumber, useVIntl } from '../../composables'
|
||||||
import { commonMessages } from '../../utils'
|
import { commonMessages } from '../../utils'
|
||||||
import Avatar from '../base/Avatar.vue'
|
import Avatar from '../base/Avatar.vue'
|
||||||
import ContentPageHeader from '../base/ContentPageHeader.vue'
|
import ContentPageHeader from '../base/ContentPageHeader.vue'
|
||||||
@@ -89,6 +89,7 @@ import ServerDetails from './server/ServerDetails.vue'
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@@ -171,12 +171,7 @@
|
|||||||
class="flex flex-col justify-center gap-1 max-sm:flex-row max-sm:justify-start max-sm:gap-3 xl:contents"
|
class="flex flex-col justify-center gap-1 max-sm:flex-row max-sm:justify-start max-sm:gap-3 xl:contents"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-tooltip="
|
v-tooltip="formatDateTime(version.date_published)"
|
||||||
formatMessage(commonMessages.dateAtTimeTooltip, {
|
|
||||||
date: new Date(version.date_published),
|
|
||||||
time: new Date(version.date_published),
|
|
||||||
})
|
|
||||||
"
|
|
||||||
class="z-[1] flex cursor-help items-center gap-1 text-nowrap font-medium xl:self-center"
|
class="z-[1] flex cursor-help items-center gap-1 text-nowrap font-medium xl:self-center"
|
||||||
>
|
>
|
||||||
<CalendarIcon class="xl:hidden" />
|
<CalendarIcon class="xl:hidden" />
|
||||||
@@ -186,7 +181,7 @@
|
|||||||
class="pointer-events-none z-[1] flex items-center gap-1 font-medium xl:self-center"
|
class="pointer-events-none z-[1] flex items-center gap-1 font-medium xl:self-center"
|
||||||
>
|
>
|
||||||
<DownloadIcon class="xl:hidden" />
|
<DownloadIcon class="xl:hidden" />
|
||||||
{{ formatNumber(version.downloads) }}
|
{{ formatCompactNumber(version.downloads) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -227,12 +222,13 @@ import {
|
|||||||
FormattedTag,
|
FormattedTag,
|
||||||
Pagination,
|
Pagination,
|
||||||
TagItem,
|
TagItem,
|
||||||
|
useCompactNumber,
|
||||||
|
useFormatDateTime,
|
||||||
VersionChannelIndicator,
|
VersionChannelIndicator,
|
||||||
VersionFilterControl,
|
VersionFilterControl,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
formatBytes,
|
formatBytes,
|
||||||
formatNumber,
|
|
||||||
formatVersionsForDisplay,
|
formatVersionsForDisplay,
|
||||||
type GameVersionTag,
|
type GameVersionTag,
|
||||||
type Version,
|
type Version,
|
||||||
@@ -242,11 +238,15 @@ import { useRoute, useRouter } from 'vue-router'
|
|||||||
|
|
||||||
import { useRelativeTime } from '../../composables'
|
import { useRelativeTime } from '../../composables'
|
||||||
import { useVIntl } from '../../composables/i18n'
|
import { useVIntl } from '../../composables/i18n'
|
||||||
import { commonMessages } from '../../utils/common-messages'
|
|
||||||
import { getEnvironmentTags } from './settings/environment/environments'
|
import { getEnvironmentTags } from './settings/environment/environments'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
type VersionWithDisplayUrlEnding = Version & {
|
type VersionWithDisplayUrlEnding = Version & {
|
||||||
displayUrlEnding: string
|
displayUrlEnding: string
|
||||||
|
|||||||
@@ -36,10 +36,7 @@
|
|||||||
{{ formatMessage(commonMessages.projectFollowers, { count: project.followers }) }}
|
{{ formatMessage(commonMessages.projectFollowers, { count: project.followers }) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="project.approved" v-tooltip="formatDateTime(project.approved)">
|
||||||
v-if="project.approved"
|
|
||||||
v-tooltip="dayjs(project.approved).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
>
|
|
||||||
<CalendarIcon aria-hidden="true" />
|
<CalendarIcon aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
@@ -49,7 +46,7 @@
|
|||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else v-tooltip="dayjs(project.published).format('MMMM D, YYYY [at] h:mm A')">
|
<div v-else v-tooltip="formatDateTime(project.published)">
|
||||||
<CalendarIcon aria-hidden="true" />
|
<CalendarIcon aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
@@ -59,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="project.status === 'processing' && project.queued"
|
v-if="project.status === 'processing' && project.queued"
|
||||||
v-tooltip="dayjs(project.queued).format('MMMM D, YYYY [at] h:mm A')"
|
v-tooltip="formatDateTime(project.queued)"
|
||||||
>
|
>
|
||||||
<ScaleIcon aria-hidden="true" />
|
<ScaleIcon aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
@@ -70,10 +67,7 @@
|
|||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="hasVersions && project.updated" v-tooltip="formatDateTime(project.updated)">
|
||||||
v-if="hasVersions && project.updated"
|
|
||||||
v-tooltip="dayjs(project.updated).format('MMMM D, YYYY [at] h:mm A')"
|
|
||||||
>
|
|
||||||
<VersionIcon aria-hidden="true" />
|
<VersionIcon aria-hidden="true" />
|
||||||
<div>
|
<div>
|
||||||
{{
|
{{
|
||||||
@@ -95,16 +89,19 @@ import {
|
|||||||
VersionIcon,
|
VersionIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useRelativeTime } from '../../composables'
|
import { useFormatDateTime, useRelativeTime } from '../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||||
import { commonMessages } from '../../utils/common-messages'
|
import { commonMessages } from '../../utils/common-messages'
|
||||||
import { IntlFormatted } from '../base'
|
import { IntlFormatted } from '../base'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
project: Labrinth.Projects.v2.Project
|
project: Labrinth.Projects.v2.Project
|
||||||
|
|||||||
@@ -1,36 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { CalendarIcon, HistoryIcon } from '@modrinth/assets'
|
import { CalendarIcon, HistoryIcon } from '@modrinth/assets'
|
||||||
import { capitalizeString } from '@modrinth/utils'
|
import { capitalizeString } from '@modrinth/utils'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
import { useRelativeTime, useVIntl } from '../../../composables'
|
import { defineMessage, useFormatDateTime, useRelativeTime, useVIntl } from '../../../composables'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|
||||||
const formatRelativeTime = useRelativeTime()
|
const formatRelativeTime = useRelativeTime()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
date: Date
|
date: Date
|
||||||
type: 'updated' | 'published'
|
type: 'updated' | 'published'
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const formattedDate = computed(() => dayjs(props.date).format('MMMM D, YYYY [at] h:mm A'))
|
const formattedDate = computed(() => formatDateTime(props.date))
|
||||||
|
|
||||||
const types = {
|
const types = {
|
||||||
updated: {
|
updated: {
|
||||||
icon: HistoryIcon,
|
icon: HistoryIcon,
|
||||||
tooltip: {
|
tooltip: defineMessage({
|
||||||
id: 'project-card.date.updated.tooltip',
|
id: 'project-card.date.updated.tooltip',
|
||||||
defaultMessage: 'Updated {date}',
|
defaultMessage: 'Updated {date}',
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
published: {
|
published: {
|
||||||
icon: CalendarIcon,
|
icon: CalendarIcon,
|
||||||
tooltip: {
|
tooltip: defineMessage({
|
||||||
id: 'project-card.date.published.tooltip',
|
id: 'project-card.date.published.tooltip',
|
||||||
defaultMessage: 'Published {date}',
|
defaultMessage: 'Published {date}',
|
||||||
},
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
import { DownloadIcon, HeartIcon } from '@modrinth/assets'
|
||||||
|
|
||||||
import { capitalizeString, formatNumber } from '../../../../../utils'
|
import { capitalizeString } from '../../../../../utils'
|
||||||
import { useVIntl } from '../../../composables'
|
import { useCompactNumber, useVIntl } from '../../../composables'
|
||||||
import { commonMessages } from '../../../utils'
|
import { commonMessages } from '../../../utils'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const { formatCompactNumber } = useCompactNumber()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
downloads?: number
|
downloads?: number
|
||||||
@@ -19,7 +20,7 @@ defineProps<{
|
|||||||
v-tooltip="
|
v-tooltip="
|
||||||
capitalizeString(
|
capitalizeString(
|
||||||
formatMessage(commonMessages.projectDownloads, {
|
formatMessage(commonMessages.projectDownloads, {
|
||||||
count: formatNumber(downloads, false),
|
count: downloads,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -27,7 +28,7 @@ defineProps<{
|
|||||||
>
|
>
|
||||||
<DownloadIcon class="size-5 shrink-0" />
|
<DownloadIcon class="size-5 shrink-0" />
|
||||||
<span class="font-medium">
|
<span class="font-medium">
|
||||||
{{ formatNumber(downloads) }}
|
{{ formatCompactNumber(downloads) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -35,7 +36,7 @@ defineProps<{
|
|||||||
v-tooltip="
|
v-tooltip="
|
||||||
capitalizeString(
|
capitalizeString(
|
||||||
formatMessage(commonMessages.projectFollowers, {
|
formatMessage(commonMessages.projectFollowers, {
|
||||||
count: formatNumber(followers, false),
|
count: followers,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
@@ -43,7 +44,7 @@ defineProps<{
|
|||||||
>
|
>
|
||||||
<HeartIcon class="size-5 shrink-0" />
|
<HeartIcon class="size-5 shrink-0" />
|
||||||
<span class="font-medium">
|
<span class="font-medium">
|
||||||
{{ formatNumber(followers) }}
|
{{ formatCompactNumber(followers) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { OnlineIndicatorIcon } from '@modrinth/assets'
|
import { OnlineIndicatorIcon } from '@modrinth/assets'
|
||||||
|
|
||||||
import { formatNumber } from '../../../../../utils'
|
import { useCompactNumber, useFormatNumber, useVIntl } from '../../../composables'
|
||||||
import { useVIntl } from '../../../composables'
|
|
||||||
import { commonMessages } from '../../../utils'
|
import { commonMessages } from '../../../utils'
|
||||||
import { StatItem } from '../../base'
|
import { StatItem } from '../../base'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||||
|
const formatNumber = useFormatNumber()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
online: number
|
online: number
|
||||||
@@ -16,7 +17,12 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<StatItem
|
<StatItem
|
||||||
v-tooltip="`${formatNumber(online, true)} players online`"
|
v-tooltip="
|
||||||
|
formatMessage(commonMessages.projectOnlinePlayerCountTooltip, {
|
||||||
|
count: formatCompactNumber(online),
|
||||||
|
countPlural: formatCompactNumberPlural(online),
|
||||||
|
})
|
||||||
|
"
|
||||||
class="smart-clickable:allow-pointer-events w-max"
|
class="smart-clickable:allow-pointer-events w-max"
|
||||||
>
|
>
|
||||||
<OnlineIndicatorIcon
|
<OnlineIndicatorIcon
|
||||||
@@ -29,10 +35,8 @@ defineProps<{
|
|||||||
/>
|
/>
|
||||||
{{
|
{{
|
||||||
hideLabel
|
hideLabel
|
||||||
? formatNumber(online, false)
|
? formatNumber(online)
|
||||||
: formatMessage(commonMessages.projectOnlinePlayerCount, {
|
: formatMessage(commonMessages.projectOnlinePlayerCount, { count: online })
|
||||||
count: formatNumber(online, false),
|
|
||||||
})
|
|
||||||
}}
|
}}
|
||||||
</StatItem>
|
</StatItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const pingMessage = defineMessage({
|
const pingMessage = defineMessage({
|
||||||
id: 'project.server.ping.ms',
|
id: 'project.server.ping.ms',
|
||||||
defaultMessage: '{ping} ms',
|
defaultMessage: '{ping, number} ms',
|
||||||
})
|
})
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PlayIcon } from '@modrinth/assets'
|
import { PlayIcon } from '@modrinth/assets'
|
||||||
|
|
||||||
import { formatNumber } from '../../../../../utils'
|
import { useCompactNumber, useVIntl } from '../../../composables'
|
||||||
import { useVIntl } from '../../../composables'
|
|
||||||
import { commonMessages } from '../../../utils'
|
import { commonMessages } from '../../../utils'
|
||||||
import { StatItem } from '../../base'
|
import { StatItem } from '../../base'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const { formatCompactNumber, formatCompactNumberPlural } = useCompactNumber()
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
recentPlays: number
|
recentPlays: number
|
||||||
@@ -16,16 +16,20 @@ defineProps<{
|
|||||||
<template>
|
<template>
|
||||||
<StatItem
|
<StatItem
|
||||||
v-tooltip="
|
v-tooltip="
|
||||||
`${formatNumber(recentPlays, true)} recent play${recentPlays === 1 ? '' : 's'} from Modrinth in the past 2 weeks`
|
formatMessage(commonMessages.projectRecentPlaysTooltip, {
|
||||||
|
count: formatCompactNumber(recentPlays),
|
||||||
|
countPlural: formatCompactNumberPlural(recentPlays),
|
||||||
|
})
|
||||||
"
|
"
|
||||||
class="smart-clickable:allow-pointer-events w-max"
|
class="smart-clickable:allow-pointer-events w-max"
|
||||||
>
|
>
|
||||||
<PlayIcon />
|
<PlayIcon />
|
||||||
{{
|
{{
|
||||||
hideLabel
|
hideLabel
|
||||||
? formatNumber(recentPlays, true)
|
? formatCompactNumber(recentPlays)
|
||||||
: formatMessage(commonMessages.projectRecentPlays, {
|
: formatMessage(commonMessages.projectRecentPlays, {
|
||||||
count: formatNumber(recentPlays, true),
|
count: formatCompactNumber(recentPlays),
|
||||||
|
countPlural: formatCompactNumberPlural(recentPlays),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</StatItem>
|
</StatItem>
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ import {
|
|||||||
TriangleAlertIcon,
|
TriangleAlertIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { useQuery } from '@tanstack/vue-query'
|
import { useQuery } from '@tanstack/vue-query'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatDateTime } from '../../composables'
|
||||||
import { injectModrinthClient } from '../../providers/api-client'
|
import { injectModrinthClient } from '../../providers/api-client'
|
||||||
import Avatar from '../base/Avatar.vue'
|
import Avatar from '../base/Avatar.vue'
|
||||||
import CopyCode from '../base/CopyCode.vue'
|
import CopyCode from '../base/CopyCode.vue'
|
||||||
@@ -132,6 +132,8 @@ import ServersSpecs from '../billing/ServersSpecs.vue'
|
|||||||
import ServerIcon from './icons/ServerIcon.vue'
|
import ServerIcon from './icons/ServerIcon.vue'
|
||||||
import ServerInfoLabels from './labels/ServerInfoLabels.vue'
|
import ServerInfoLabels from './labels/ServerInfoLabels.vue'
|
||||||
|
|
||||||
|
const formatDate = useFormatDateTime({ dateStyle: 'long' })
|
||||||
|
|
||||||
export type PendingChange = {
|
export type PendingChange = {
|
||||||
planSize: string
|
planSize: string
|
||||||
cpu: number
|
cpu: number
|
||||||
@@ -249,12 +251,4 @@ const { data: image } = useQuery({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isConfiguring = computed(() => props.flows?.intro)
|
const isConfiguring = computed(() => props.flows?.intro)
|
||||||
|
|
||||||
const formatDate = (d: unknown) => {
|
|
||||||
try {
|
|
||||||
return dayjs(d as string).format('MMMM D, YYYY')
|
|
||||||
} catch {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import {
|
|||||||
UserRoundIcon,
|
UserRoundIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import dayjs from 'dayjs'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useFormatDateTime } from '../../../composables'
|
||||||
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
import { defineMessages, useVIntl } from '../../../composables/i18n'
|
||||||
import { commonMessages } from '../../../utils'
|
import { commonMessages } from '../../../utils'
|
||||||
import ButtonStyled from '../../base/ButtonStyled.vue'
|
import ButtonStyled from '../../base/ButtonStyled.vue'
|
||||||
@@ -20,6 +20,10 @@ import OverflowMenu, { type Option as OverflowOption } from '../../base/Overflow
|
|||||||
import ProgressBar from '../../base/ProgressBar.vue'
|
import ProgressBar from '../../base/ProgressBar.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
timeStyle: 'short',
|
||||||
|
dateStyle: 'long',
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'download' | 'rename' | 'restore' | 'retry'): void
|
(e: 'download' | 'rename' | 'restore' | 'retry'): void
|
||||||
@@ -254,7 +258,7 @@ const messages = defineMessages({
|
|||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<span class="w-full font-medium text-contrast md:text-center">
|
<span class="w-full font-medium text-contrast md:text-center">
|
||||||
{{ dayjs(backup.created_at).format('MMMM Do YYYY, h:mm A') }}
|
{{ formatDateTime(backup.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<!-- TODO: Uncomment when API supports size field -->
|
<!-- TODO: Uncomment when API supports size field -->
|
||||||
<!-- <span class="text-secondary">{{ formatBytes(backup.size) }}</span> -->
|
<!-- <span class="text-secondary">{{ formatBytes(backup.size) }}</span> -->
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ import {
|
|||||||
getFileExtensionIcon,
|
getFileExtensionIcon,
|
||||||
isEditableFile as isEditableFileExt,
|
isEditableFile as isEditableFileExt,
|
||||||
isImageFile,
|
isImageFile,
|
||||||
|
useFormatDateTime,
|
||||||
} from '@modrinth/ui'
|
} from '@modrinth/ui'
|
||||||
import { computed, ref, shallowRef } from 'vue'
|
import { computed, ref, shallowRef } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
@@ -123,6 +124,14 @@ const units = Object.freeze(['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB'])
|
|||||||
const route = shallowRef(useRoute())
|
const route = shallowRef(useRoute())
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
const formatDateTime = useFormatDateTime({
|
||||||
|
year: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric',
|
||||||
|
})
|
||||||
|
|
||||||
const containerClasses = computed(() => [
|
const containerClasses = computed(() => [
|
||||||
'group m-0 flex w-full select-none items-center justify-between overflow-hidden border-0 border-t border-solid border-surface-3 px-4 py-3 focus:!outline-none',
|
'group m-0 flex w-full select-none items-center justify-between overflow-hidden border-0 border-t border-solid border-surface-3 px-4 py-3 focus:!outline-none',
|
||||||
props.selected ? 'bg-surface-3' : props.index % 2 === 0 ? 'bg-surface-2' : 'file-row-alt',
|
props.selected ? 'bg-surface-3' : props.index % 2 === 0 ? 'bg-surface-2' : 'file-row-alt',
|
||||||
@@ -179,28 +188,12 @@ const iconComponent = computed(() => {
|
|||||||
|
|
||||||
const formattedModifiedDate = computed(() => {
|
const formattedModifiedDate = computed(() => {
|
||||||
const date = new Date(props.modified * 1000)
|
const date = new Date(props.modified * 1000)
|
||||||
return `${date.toLocaleDateString('en-US', {
|
return formatDateTime(date)
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}, ${date.toLocaleTimeString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
})}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const formattedCreationDate = computed(() => {
|
const formattedCreationDate = computed(() => {
|
||||||
const date = new Date(props.created * 1000)
|
const date = new Date(props.created * 1000)
|
||||||
return `${date.toLocaleDateString('en-US', {
|
return formatDateTime(date)
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
year: '2-digit',
|
|
||||||
})}, ${date.toLocaleTimeString('en-US', {
|
|
||||||
hour: 'numeric',
|
|
||||||
minute: 'numeric',
|
|
||||||
hour12: true,
|
|
||||||
})}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const isEditableFile = computed(() => {
|
const isEditableFile = computed(() => {
|
||||||
|
|||||||
38
packages/ui/src/composables/format-date-time.ts
Normal file
38
packages/ui/src/composables/format-date-time.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { LRUCache } from 'lru-cache'
|
||||||
|
|
||||||
|
import { injectI18n } from '../providers/i18n'
|
||||||
|
|
||||||
|
const formatterCache = new LRUCache<string, Intl.DateTimeFormat>({ max: 40 })
|
||||||
|
|
||||||
|
export function useFormatDateTime(options?: Intl.DateTimeFormatOptions) {
|
||||||
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
|
function format(date?: Date | number | string): string {
|
||||||
|
if (typeof date === 'number' || typeof date === 'string') {
|
||||||
|
date = new Date(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatter = getFormatter(locale.value, options)
|
||||||
|
return formatter!.format(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatter(locale: string, options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
|
||||||
|
let cacheKey = locale
|
||||||
|
if (options) {
|
||||||
|
const entries = Object.entries(options)
|
||||||
|
.filter(([, value]) => value !== undefined)
|
||||||
|
.sort()
|
||||||
|
.map(([key, value]) => `${key}=${value}`)
|
||||||
|
cacheKey = [locale, ...entries].join(':')
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatter = formatterCache.get(cacheKey)
|
||||||
|
if (!formatter) {
|
||||||
|
formatter = new Intl.DateTimeFormat(locale, options)
|
||||||
|
formatterCache.set(cacheKey, formatter)
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
78
packages/ui/src/composables/format-money.ts
Normal file
78
packages/ui/src/composables/format-money.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { LRUCache } from 'lru-cache'
|
||||||
|
|
||||||
|
import { injectI18n } from '../providers/i18n'
|
||||||
|
|
||||||
|
const formatterCache = new LRUCache<string, Intl.NumberFormat>({ max: 10 })
|
||||||
|
const maxDigitsCache = new LRUCache<string, number>({ max: 10 })
|
||||||
|
|
||||||
|
// `formatMoney(1234.56, 'USD')` → `$1,234.56`
|
||||||
|
export function useFormatMoney() {
|
||||||
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
|
function format(number: number, currency = 'USD'): string {
|
||||||
|
try {
|
||||||
|
const formatter = getFormatter(locale.value, currency)
|
||||||
|
return formatter!.format(number)
|
||||||
|
} catch {
|
||||||
|
return `${currency} ${number.toFixed(2)}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// `formatPrice(123456, 'USD')` → `$1,234.56`
|
||||||
|
export function useFormatPrice() {
|
||||||
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
|
function format(price: number, currency: string, trimZeros = false): string {
|
||||||
|
const maxDigits = getMaxDigits(currency)
|
||||||
|
const convertedPrice = price / Math.pow(10, maxDigits)
|
||||||
|
|
||||||
|
const minimumFractionDigits = trimZeros && Number.isInteger(convertedPrice) ? 0 : undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formatter = getFormatter(locale.value, currency, minimumFractionDigits)
|
||||||
|
return formatter.format(convertedPrice)
|
||||||
|
} catch {
|
||||||
|
return `${currency} ${convertedPrice}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatter(
|
||||||
|
locale: string,
|
||||||
|
currency: string,
|
||||||
|
minimumFractionDigits?: number,
|
||||||
|
): Intl.NumberFormat {
|
||||||
|
const cacheKey = `${locale}:${currency}:${minimumFractionDigits}`
|
||||||
|
let formatter = formatterCache.get(cacheKey)
|
||||||
|
if (!formatter) {
|
||||||
|
formatter = new Intl.NumberFormat(locale, {
|
||||||
|
style: 'currency',
|
||||||
|
currency,
|
||||||
|
minimumFractionDigits,
|
||||||
|
})
|
||||||
|
formatterCache.set(cacheKey, formatter)
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMaxDigits(currency: string): number {
|
||||||
|
let maxDigits = maxDigitsCache.get(currency)
|
||||||
|
if (!maxDigits) {
|
||||||
|
try {
|
||||||
|
const formatter = new Intl.NumberFormat(undefined, {
|
||||||
|
style: 'currency',
|
||||||
|
currency,
|
||||||
|
})
|
||||||
|
maxDigits = formatter.resolvedOptions().maximumFractionDigits || 2
|
||||||
|
} catch {
|
||||||
|
maxDigits = 2
|
||||||
|
}
|
||||||
|
maxDigitsCache.set(currency, maxDigits)
|
||||||
|
}
|
||||||
|
return maxDigits
|
||||||
|
}
|
||||||
74
packages/ui/src/composables/format-number.ts
Normal file
74
packages/ui/src/composables/format-number.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { LRUCache } from 'lru-cache'
|
||||||
|
|
||||||
|
import { injectI18n } from '../providers/i18n'
|
||||||
|
|
||||||
|
const formatterCache = new LRUCache<string, Intl.NumberFormat>({ max: 15 })
|
||||||
|
|
||||||
|
// `formatNumber(1234567)` → `1,234,567`
|
||||||
|
export function useFormatNumber() {
|
||||||
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
|
function format(value: number | bigint): string {
|
||||||
|
const formatter = getStandardFormatter(locale.value)
|
||||||
|
return formatter!.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// `formatCompactNumber(1234567)` → `1.23M`
|
||||||
|
//
|
||||||
|
// Use `formatCompactNumberPlural` over `{(here!), plural, one {...} other {...}}`
|
||||||
|
export function useCompactNumber() {
|
||||||
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
|
function formatCompactNumber(value: number | bigint): string {
|
||||||
|
if (value < 10_000) {
|
||||||
|
const standardFormatter = getStandardFormatter(locale.value)
|
||||||
|
return standardFormatter.format(value)
|
||||||
|
}
|
||||||
|
if (value < 1_000_000) {
|
||||||
|
const oneDigitCompactFormatter = getCompactFormatter(locale.value, 1)
|
||||||
|
return oneDigitCompactFormatter.format(value)
|
||||||
|
}
|
||||||
|
const twoDigitsCompactFormatter = getCompactFormatter(locale.value, 2)
|
||||||
|
return twoDigitsCompactFormatter.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCompactNumberPlural(value: number | bigint): string {
|
||||||
|
if (value < 10_000) {
|
||||||
|
return value.toString()
|
||||||
|
}
|
||||||
|
if (value < 1_000_000) {
|
||||||
|
const oneDigitCompactFormatter = getCompactFormatter(locale.value, 1)
|
||||||
|
return oneDigitCompactFormatter.format(value)
|
||||||
|
}
|
||||||
|
const twoDigitsCompactFormatter = getCompactFormatter(locale.value, 2)
|
||||||
|
return twoDigitsCompactFormatter.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { formatCompactNumber, formatCompactNumberPlural }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStandardFormatter(locale: string): Intl.NumberFormat {
|
||||||
|
const cacheKey = `${locale}:standard`
|
||||||
|
let formatter = formatterCache.get(cacheKey)
|
||||||
|
if (!formatter) {
|
||||||
|
formatter = new Intl.NumberFormat(locale)
|
||||||
|
formatterCache.set(cacheKey, formatter)
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompactFormatter(locale: string, maximumFractionDigits: number): Intl.NumberFormat {
|
||||||
|
const cacheKey = `${locale}:compact:${maximumFractionDigits}`
|
||||||
|
let formatter = formatterCache.get(cacheKey)
|
||||||
|
if (!formatter) {
|
||||||
|
formatter = new Intl.NumberFormat(locale, {
|
||||||
|
notation: 'compact',
|
||||||
|
maximumFractionDigits,
|
||||||
|
})
|
||||||
|
formatterCache.set(cacheKey, formatter)
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
@@ -1,35 +1,14 @@
|
|||||||
import { computed, type ComputedRef } from 'vue'
|
import { LRUCache } from 'lru-cache'
|
||||||
|
|
||||||
import { injectI18n } from '../providers/i18n'
|
import { injectI18n } from '../providers/i18n'
|
||||||
import { LOCALES } from './i18n.ts'
|
import { LOCALES } from './i18n.ts'
|
||||||
|
|
||||||
export type Formatter = (
|
const formatterCache = new LRUCache<string, Intl.RelativeTimeFormat>({ max: 5 })
|
||||||
value: Date | number | null | string | undefined,
|
|
||||||
options?: FormatOptions,
|
|
||||||
) => string
|
|
||||||
|
|
||||||
export interface FormatOptions {
|
export function useRelativeTime() {
|
||||||
roundingMode?: 'halfExpand' | 'floor' | 'ceil'
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatters = new Map<string, ComputedRef<Intl.RelativeTimeFormat>>()
|
|
||||||
|
|
||||||
export function useRelativeTime(): Formatter {
|
|
||||||
const { locale } = injectI18n()
|
const { locale } = injectI18n()
|
||||||
|
|
||||||
const formatterRef = computed(() => {
|
return (value: Date | number | string | null | undefined) => {
|
||||||
const localeDefinition = LOCALES.find((loc) => loc.code === locale.value)
|
|
||||||
return new Intl.RelativeTimeFormat(locale.value, {
|
|
||||||
numeric: localeDefinition?.numeric || 'auto',
|
|
||||||
style: 'long',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!formatters.has(locale.value)) {
|
|
||||||
formatters.set(locale.value, formatterRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (value: Date | number | null | string | undefined) => {
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@@ -50,7 +29,7 @@ export function useRelativeTime(): Formatter {
|
|||||||
const months = Math.round(diff / 2629746000)
|
const months = Math.round(diff / 2629746000)
|
||||||
const years = Math.round(diff / 31556952000)
|
const years = Math.round(diff / 31556952000)
|
||||||
|
|
||||||
const rtf = formatterRef.value
|
const rtf = getFormatter(locale.value)
|
||||||
|
|
||||||
if (Math.abs(seconds) < 60) {
|
if (Math.abs(seconds) < 60) {
|
||||||
return rtf.format(seconds, 'second')
|
return rtf.format(seconds, 'second')
|
||||||
@@ -69,3 +48,16 @@ export function useRelativeTime(): Formatter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFormatter(locale: string): Intl.RelativeTimeFormat {
|
||||||
|
let formatter = formatterCache.get(locale)
|
||||||
|
if (!formatter) {
|
||||||
|
const localeDefinition = LOCALES.find((loc) => loc.code === locale)
|
||||||
|
formatter = new Intl.RelativeTimeFormat(locale, {
|
||||||
|
numeric: localeDefinition?.numeric || 'auto',
|
||||||
|
style: 'long',
|
||||||
|
})
|
||||||
|
formatterCache.set(locale, formatter)
|
||||||
|
}
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
export * from './debug-logger'
|
export * from './debug-logger'
|
||||||
export * from './dynamic-font-size'
|
export * from './dynamic-font-size'
|
||||||
|
export * from './format-date-time'
|
||||||
|
export * from './format-money'
|
||||||
|
export * from './format-number'
|
||||||
export * from './how-ago'
|
export * from './how-ago'
|
||||||
export * from './i18n'
|
export * from './i18n'
|
||||||
export * from './i18n-debug'
|
export * from './i18n-debug'
|
||||||
|
|||||||
@@ -360,10 +360,10 @@
|
|||||||
"defaultMessage": "No projects match your search."
|
"defaultMessage": "No projects match your search."
|
||||||
},
|
},
|
||||||
"instances.modpack-content-modal.search-placeholder": {
|
"instances.modpack-content-modal.search-placeholder": {
|
||||||
"defaultMessage": "Search {count} projects"
|
"defaultMessage": "Search {count, number} {count, plural, one {project} other {projects}}"
|
||||||
},
|
},
|
||||||
"instances.modpack-content-modal.selected-count": {
|
"instances.modpack-content-modal.selected-count": {
|
||||||
"defaultMessage": "{count} selected"
|
"defaultMessage": "{count, number} selected"
|
||||||
},
|
},
|
||||||
"instances.updater-modal.badge.current": {
|
"instances.updater-modal.badge.current": {
|
||||||
"defaultMessage": "Current"
|
"defaultMessage": "Current"
|
||||||
@@ -818,6 +818,12 @@
|
|||||||
"payment-method.visa": {
|
"payment-method.visa": {
|
||||||
"defaultMessage": "Visa"
|
"defaultMessage": "Visa"
|
||||||
},
|
},
|
||||||
|
"project-card.date.published.tooltip": {
|
||||||
|
"defaultMessage": "Published {date}"
|
||||||
|
},
|
||||||
|
"project-card.date.updated.tooltip": {
|
||||||
|
"defaultMessage": "Updated {date}"
|
||||||
|
},
|
||||||
"project-card.environment.client": {
|
"project-card.environment.client": {
|
||||||
"defaultMessage": "Client"
|
"defaultMessage": "Client"
|
||||||
},
|
},
|
||||||
@@ -1008,7 +1014,7 @@
|
|||||||
"defaultMessage": "Server details"
|
"defaultMessage": "Server details"
|
||||||
},
|
},
|
||||||
"project.download-count-tooltip": {
|
"project.download-count-tooltip": {
|
||||||
"defaultMessage": "{count} {count, plural, one {download} other {downloads}}"
|
"defaultMessage": "{count, number} {count, plural, one {download} other {downloads}}"
|
||||||
},
|
},
|
||||||
"project.environment.client-and-server.description": {
|
"project.environment.client-and-server.description": {
|
||||||
"defaultMessage": "Required on both the client and server."
|
"defaultMessage": "Required on both the client and server."
|
||||||
@@ -1077,16 +1083,22 @@
|
|||||||
"defaultMessage": "Unknown environment"
|
"defaultMessage": "Unknown environment"
|
||||||
},
|
},
|
||||||
"project.follower-count-tooltip": {
|
"project.follower-count-tooltip": {
|
||||||
"defaultMessage": "{count} {count, plural, one {follower} other {followers}}"
|
"defaultMessage": "{count, number} {count, plural, one {follower} other {followers}}"
|
||||||
},
|
},
|
||||||
"project.online-player-count": {
|
"project.online-player-count": {
|
||||||
"defaultMessage": "{count} {count, plural, one {online} other {online}}"
|
"defaultMessage": "{count, number} online"
|
||||||
|
},
|
||||||
|
"project.online-player-count.tooltip": {
|
||||||
|
"defaultMessage": "{count} {countPlural, plural, one {player} other {players}} online"
|
||||||
},
|
},
|
||||||
"project.recent-plays": {
|
"project.recent-plays": {
|
||||||
"defaultMessage": "{count} {count, plural, one {recent play} other {recent plays}}"
|
"defaultMessage": "{count} {countPlural, plural, one {recent play} other {recent plays}}"
|
||||||
|
},
|
||||||
|
"project.recent-plays.tooltip": {
|
||||||
|
"defaultMessage": "{count} {countPlural, plural, one {recent play} other {recent plays}} from Modrinth in the past 2 weeks"
|
||||||
},
|
},
|
||||||
"project.server.ping.ms": {
|
"project.server.ping.ms": {
|
||||||
"defaultMessage": "{ping} ms"
|
"defaultMessage": "{ping, number} ms"
|
||||||
},
|
},
|
||||||
"project.settings.analytics.title": {
|
"project.settings.analytics.title": {
|
||||||
"defaultMessage": "Analytics"
|
"defaultMessage": "Analytics"
|
||||||
@@ -1958,9 +1970,6 @@
|
|||||||
"tag.loader.waterfall": {
|
"tag.loader.waterfall": {
|
||||||
"defaultMessage": "Waterfall"
|
"defaultMessage": "Waterfall"
|
||||||
},
|
},
|
||||||
"tooltip.date-at-time": {
|
|
||||||
"defaultMessage": "{date, date, long} at {time, time, short}"
|
|
||||||
},
|
|
||||||
"ui.component.unsaved-changes-popup.body": {
|
"ui.component.unsaved-changes-popup.body": {
|
||||||
"defaultMessage": "You have unsaved changes."
|
"defaultMessage": "You have unsaved changes."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,10 +77,6 @@ export const commonMessages = defineMessages({
|
|||||||
id: 'label.dashboard',
|
id: 'label.dashboard',
|
||||||
defaultMessage: 'Dashboard',
|
defaultMessage: 'Dashboard',
|
||||||
},
|
},
|
||||||
dateAtTimeTooltip: {
|
|
||||||
id: 'tooltip.date-at-time',
|
|
||||||
defaultMessage: '{date, date, long} at {time, time, short}',
|
|
||||||
},
|
|
||||||
declineButton: {
|
declineButton: {
|
||||||
id: 'button.decline',
|
id: 'button.decline',
|
||||||
defaultMessage: 'Decline',
|
defaultMessage: 'Decline',
|
||||||
@@ -387,19 +383,28 @@ export const commonMessages = defineMessages({
|
|||||||
},
|
},
|
||||||
projectDownloads: {
|
projectDownloads: {
|
||||||
id: 'project.download-count-tooltip',
|
id: 'project.download-count-tooltip',
|
||||||
defaultMessage: '{count} {count, plural, one {download} other {downloads}}',
|
defaultMessage: '{count, number} {count, plural, one {download} other {downloads}}',
|
||||||
},
|
},
|
||||||
projectFollowers: {
|
projectFollowers: {
|
||||||
id: 'project.follower-count-tooltip',
|
id: 'project.follower-count-tooltip',
|
||||||
defaultMessage: '{count} {count, plural, one {follower} other {followers}}',
|
defaultMessage: '{count, number} {count, plural, one {follower} other {followers}}',
|
||||||
},
|
},
|
||||||
projectOnlinePlayerCount: {
|
projectOnlinePlayerCount: {
|
||||||
id: 'project.online-player-count',
|
id: 'project.online-player-count',
|
||||||
defaultMessage: '{count} {count, plural, one {online} other {online}}',
|
defaultMessage: '{count, number} online',
|
||||||
|
},
|
||||||
|
projectOnlinePlayerCountTooltip: {
|
||||||
|
id: 'project.online-player-count.tooltip',
|
||||||
|
defaultMessage: '{count} {countPlural, plural, one {player} other {players}} online',
|
||||||
},
|
},
|
||||||
projectRecentPlays: {
|
projectRecentPlays: {
|
||||||
id: 'project.recent-plays',
|
id: 'project.recent-plays',
|
||||||
defaultMessage: '{count} {count, plural, one {recent play} other {recent plays}}',
|
defaultMessage: '{count} {countPlural, plural, one {recent play} other {recent plays}}',
|
||||||
|
},
|
||||||
|
projectRecentPlaysTooltip: {
|
||||||
|
id: 'project.recent-plays.tooltip',
|
||||||
|
defaultMessage:
|
||||||
|
'{count} {countPlural, plural, one {recent play} other {recent plays}} from Modrinth in the past 2 weeks',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -60,29 +60,6 @@ export const getCurrency = (userCountry) => {
|
|||||||
return countryCurrency[userCountry] ?? 'USD'
|
return countryCurrency[userCountry] ?? 'USD'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatPrice = (locale, price, currency, trimZeros = false) => {
|
|
||||||
let formatter = new Intl.NumberFormat(locale, {
|
|
||||||
style: 'currency',
|
|
||||||
currency,
|
|
||||||
})
|
|
||||||
|
|
||||||
const maxDigits = formatter.resolvedOptions().maximumFractionDigits
|
|
||||||
const convertedPrice = price / Math.pow(10, maxDigits)
|
|
||||||
|
|
||||||
let minimumFractionDigits = maxDigits
|
|
||||||
|
|
||||||
if (trimZeros && Number.isInteger(convertedPrice)) {
|
|
||||||
minimumFractionDigits = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
formatter = new Intl.NumberFormat(locale, {
|
|
||||||
style: 'currency',
|
|
||||||
currency,
|
|
||||||
minimumFractionDigits,
|
|
||||||
})
|
|
||||||
return formatter.format(convertedPrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const calculateSavings = (monthlyPlan, plan, months = 12) => {
|
export const calculateSavings = (monthlyPlan, plan, months = 12) => {
|
||||||
const monthlyAnnualized = monthlyPlan * months
|
const monthlyAnnualized = monthlyPlan * months
|
||||||
|
|
||||||
|
|||||||
@@ -93,40 +93,6 @@ export const sortedCategories = (tags, formatCategoryName, locale) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatNumber = (number, abbreviate = true) => {
|
|
||||||
const x = Number(number)
|
|
||||||
if (x >= 1000000 && abbreviate) {
|
|
||||||
return `${(x / 1000000).toFixed(2).toString()}M`
|
|
||||||
} else if (x >= 10000 && abbreviate) {
|
|
||||||
return `${(x / 1000).toFixed(1).toString()}k`
|
|
||||||
}
|
|
||||||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatDate(
|
|
||||||
date: dayjs.Dayjs,
|
|
||||||
options: Intl.DateTimeFormatOptions = {
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
},
|
|
||||||
): string {
|
|
||||||
return date.toDate().toLocaleDateString(undefined, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function formatMoney(number, abbreviate = false) {
|
|
||||||
const x = Number(number)
|
|
||||||
if (x >= 1000000 && abbreviate) {
|
|
||||||
return `$${(x / 1000000).toFixed(2).toString()}M`
|
|
||||||
} else if (x >= 10000 && abbreviate) {
|
|
||||||
return `$${(x / 1000).toFixed(2).toString()}k`
|
|
||||||
}
|
|
||||||
return `$${x
|
|
||||||
.toFixed(2)
|
|
||||||
.toString()
|
|
||||||
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const formatBytes = (bytes, decimals = 2) => {
|
export const formatBytes = (bytes, decimals = 2) => {
|
||||||
if (bytes === 0) return '0 Bytes'
|
if (bytes === 0) return '0 Bytes'
|
||||||
|
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -652,6 +652,9 @@ importers:
|
|||||||
jszip:
|
jszip:
|
||||||
specifier: ^3.10.1
|
specifier: ^3.10.1
|
||||||
version: 3.10.1
|
version: 3.10.1
|
||||||
|
lru-cache:
|
||||||
|
specifier: ^11.2.4
|
||||||
|
version: 11.2.5
|
||||||
markdown-it:
|
markdown-it:
|
||||||
specifier: ^13.0.2
|
specifier: ^13.0.2
|
||||||
version: 13.0.2
|
version: 13.0.2
|
||||||
@@ -9401,6 +9404,9 @@ packages:
|
|||||||
vue-component-type-helpers@3.2.4:
|
vue-component-type-helpers@3.2.4:
|
||||||
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
resolution: {integrity: sha512-05lR16HeZDcDpB23ku5b5f1fBOoHqFnMiKRr2CiEvbG5Ux4Yi0McmQBOET0dR0nxDXosxyVqv67q6CzS3AK8rw==}
|
||||||
|
|
||||||
|
vue-component-type-helpers@3.2.5:
|
||||||
|
resolution: {integrity: sha512-tkvNr+bU8+xD/onAThIe7CHFvOJ/BO6XCOrxMzeytJq40nTfpGDJuVjyCM8ccGZKfAbGk2YfuZyDMXM56qheZQ==}
|
||||||
|
|
||||||
vue-confetti-explosion@1.0.2:
|
vue-confetti-explosion@1.0.2:
|
||||||
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
|
resolution: {integrity: sha512-80OboM3/6BItIoZ6DpNcZFqGpF607kjIVc5af56oKgtFmt5yWehvJeoYhkzYlqxrqdBe0Ko4Ie3bWrmLau+dJw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -12755,7 +12761,7 @@ snapshots:
|
|||||||
storybook: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
storybook: 10.2.4(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
type-fest: 2.19.0
|
type-fest: 2.19.0
|
||||||
vue: 3.5.27(typescript@5.9.3)
|
vue: 3.5.27(typescript@5.9.3)
|
||||||
vue-component-type-helpers: 3.2.4
|
vue-component-type-helpers: 3.2.5
|
||||||
|
|
||||||
'@stripe/stripe-js@7.9.0': {}
|
'@stripe/stripe-js@7.9.0': {}
|
||||||
|
|
||||||
@@ -19520,6 +19526,8 @@ snapshots:
|
|||||||
|
|
||||||
vue-component-type-helpers@3.2.4: {}
|
vue-component-type-helpers@3.2.4: {}
|
||||||
|
|
||||||
|
vue-component-type-helpers@3.2.5: {}
|
||||||
|
|
||||||
vue-confetti-explosion@1.0.2(vue@3.5.27(typescript@5.9.3)):
|
vue-confetti-explosion@1.0.2(vue@3.5.27(typescript@5.9.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.27(typescript@5.9.3)
|
vue: 3.5.27(typescript@5.9.3)
|
||||||
|
|||||||
Reference in New Issue
Block a user