mirror of
https://github.com/elk-zone/elk.git
synced 2024-11-21 17:05:22 +03:00
feat: bump to latest vue 3.4.19 (#2607)
Co-authored-by: patak <matias.capeletto@gmail.com>
This commit is contained in:
parent
81ef8ff9aa
commit
36004a7eba
40 changed files with 601 additions and 451 deletions
|
@ -1,5 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { fetchAccountByHandle } from '~/composables/cache'
|
||||
|
||||
type WatcherType = [acc?: mastodon.v1.Account, h?: string, v?: boolean]
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
|
@ -11,16 +14,55 @@ const props = defineProps<{
|
|||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const account = computed(() => props.account || (props.handle ? useAccountByHandle(props.handle!) : undefined))
|
||||
const hoverCard = ref()
|
||||
const targetIsVisible = ref(false)
|
||||
const account = ref<mastodon.v1.Account | null | undefined>(props.account)
|
||||
|
||||
useIntersectionObserver(
|
||||
hoverCard,
|
||||
([{ intersectionRatio }]) => {
|
||||
targetIsVisible.value = intersectionRatio <= 0.75
|
||||
},
|
||||
)
|
||||
watch(
|
||||
() => [props.account, props.handle, targetIsVisible.value] satisfies WatcherType,
|
||||
([newAccount, newHandle, newVisible], oldProps) => {
|
||||
if (newAccount) {
|
||||
account.value = newAccount
|
||||
return
|
||||
}
|
||||
|
||||
if (!newVisible)
|
||||
return
|
||||
|
||||
if (newHandle) {
|
||||
const [_oldAccount, oldHandle, _oldVisible] = oldProps ?? [undefined, undefined, false]
|
||||
if (!oldHandle || newHandle !== oldHandle || !account.value) {
|
||||
// new handle can be wrong: using server instead of webDomain
|
||||
fetchAccountByHandle(newHandle).then((acc) => {
|
||||
if (newHandle === props.handle)
|
||||
account.value = acc
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
account.value = undefined
|
||||
}, { immediate: true, flush: 'post' },
|
||||
)
|
||||
|
||||
const userSettings = useUserSettings()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
|
||||
<slot />
|
||||
<template #popper>
|
||||
<AccountHoverCard v-if="account" :account="account" />
|
||||
</template>
|
||||
</VMenu>
|
||||
<slot v-else />
|
||||
<span ref="hoverCard">
|
||||
<VMenu v-if="!disabled && account && !getPreferences(userSettings, 'hideAccountHoverCard')" placement="bottom-start" :delay="{ show: 500, hide: 100 }" v-bind="$attrs" :close-on-content-click="false">
|
||||
<slot />
|
||||
<template #popper>
|
||||
<AccountHoverCard v-if="account" :account="account" />
|
||||
</template>
|
||||
</VMenu>
|
||||
<slot v-else />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import type { CommonRouteTabOption } from '../common/CommonRouteTabs.vue'
|
||||
import type { CommonRouteTabOption } from '~/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
|
||||
const server = computedEager(() => route.params.server as string)
|
||||
const account = computedEager(() => route.params.account as string)
|
||||
const server = computed(() => route.params.server as string)
|
||||
const account = computed(() => route.params.account as string)
|
||||
|
||||
const tabs = computed<CommonRouteTabOption[]>(() => [
|
||||
{
|
||||
|
|
|
@ -94,8 +94,8 @@ defineExpose({ createEntry, removeEntry, updateEntry })
|
|||
</template>
|
||||
<template v-else>
|
||||
<slot
|
||||
v-for="item, index of items"
|
||||
v-bind="{ key: item[keyProp as keyof U] }"
|
||||
v-for="(item, index) of items"
|
||||
v-bind="{ key: (item as U)[keyProp as keyof U] }"
|
||||
:item="item as U"
|
||||
:older="items[index + 1] as U"
|
||||
:newer="items[index - 1] as U"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
|
||||
|
||||
const { options, command, replace, preventScrollTop = false, moreOptions } = defineProps<{
|
||||
options: CommonRouteTabOption[]
|
||||
|
@ -10,22 +10,6 @@ const { options, command, replace, preventScrollTop = false, moreOptions } = def
|
|||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export interface CommonRouteTabOption {
|
||||
to: RouteLocationRaw
|
||||
display: string
|
||||
disabled?: boolean
|
||||
name?: string
|
||||
icon?: string
|
||||
hide?: boolean
|
||||
match?: boolean
|
||||
}
|
||||
export interface CommonRouteTabMoreOption {
|
||||
options: CommonRouteTabOption[]
|
||||
icon?: string
|
||||
tooltip?: string
|
||||
match?: boolean
|
||||
}
|
||||
const router = useRouter()
|
||||
|
||||
useCommands(() => command
|
||||
|
@ -60,7 +44,7 @@ useCommands(() => command
|
|||
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center text-secondary-light op50>{{ option.display }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="moreOptions?.options?.length">
|
||||
<template v-if="isHydrated && moreOptions?.options?.length">
|
||||
<CommonDropdown placement="bottom" flex cursor-pointer mx-1.25rem>
|
||||
<CommonTooltip placement="top" :content="moreOptions.tooltip || t('action.more')">
|
||||
<button
|
||||
|
|
|
@ -10,6 +10,7 @@ defineProps<Props>()
|
|||
|
||||
<template>
|
||||
<VTooltip
|
||||
v-if="isHydrated"
|
||||
v-bind="$attrs"
|
||||
auto-hide
|
||||
>
|
||||
|
|
|
@ -24,7 +24,7 @@ interface ShortcutItemGroup {
|
|||
const isMac = useIsMac()
|
||||
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
|
||||
|
||||
const shortcutItemGroups: ShortcutItemGroup[] = [
|
||||
const shortcutItemGroups = computed<ShortcutItemGroup[]>(() => [
|
||||
{
|
||||
name: t('magic_keys.groups.navigation.title'),
|
||||
items: [
|
||||
|
@ -79,7 +79,7 @@ const shortcutItemGroups: ShortcutItemGroup[] = [
|
|||
name: t('magic_keys.groups.media.title'),
|
||||
items: [],
|
||||
},
|
||||
]
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -91,10 +91,6 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
|
|||
return account
|
||||
}
|
||||
|
||||
export function useAccountByHandle(acct: string) {
|
||||
return useAsyncState(() => fetchAccountByHandle(acct), null).state
|
||||
}
|
||||
|
||||
export function useAccountById(id?: string | null) {
|
||||
return useAsyncState(() => fetchAccountById(id), null).state
|
||||
}
|
||||
|
|
|
@ -362,20 +362,20 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
|
|||
// Backward compatibility, respect webDomain in acct
|
||||
// In previous versions, acct was username@server instead of username@webDomain
|
||||
// for example: elk@m.webtoo.ls instead of elk@webtoo.ls
|
||||
// if (!all.value[id]) { // TODO: add back this condition in the future
|
||||
const [username, webDomain] = id.split('@')
|
||||
const server = currentServer.value
|
||||
if (webDomain && server && server !== webDomain) {
|
||||
const oldId = `${username}@${server}`
|
||||
const outdatedSettings = all.value[oldId]
|
||||
if (outdatedSettings) {
|
||||
const newAllValue = { ...all.value, [id]: outdatedSettings }
|
||||
delete newAllValue[oldId]
|
||||
all.value = newAllValue
|
||||
if (!all.value[id]) {
|
||||
const [username, webDomain] = id.split('@')
|
||||
const server = currentServer.value
|
||||
if (webDomain && server && server !== webDomain) {
|
||||
const oldId = `${username}@${server}`
|
||||
const outdatedSettings = all.value[oldId]
|
||||
if (outdatedSettings) {
|
||||
const newAllValue = { ...all.value, [id]: outdatedSettings }
|
||||
delete newAllValue[oldId]
|
||||
all.value = newAllValue
|
||||
}
|
||||
}
|
||||
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
||||
}
|
||||
// }
|
||||
all.value[id] = Object.assign(initial(), all.value[id] || {})
|
||||
return all.value[id]
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,7 +12,7 @@ export default defineNuxtConfig({
|
|||
tsConfig: {
|
||||
exclude: ['../service-worker'],
|
||||
vueCompilerOptions: {
|
||||
target: 3.3,
|
||||
target: 3.4,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -75,6 +75,11 @@ export default defineNuxtConfig({
|
|||
'./composables/settings',
|
||||
'./composables/tiptap/index.ts',
|
||||
],
|
||||
imports: [{
|
||||
name: 'useI18n',
|
||||
from: '~/utils/i18n',
|
||||
priority: 100,
|
||||
}],
|
||||
injectAtEnd: true,
|
||||
},
|
||||
vite: {
|
||||
|
@ -86,6 +91,20 @@ export default defineNuxtConfig({
|
|||
build: {
|
||||
target: 'esnext',
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@tiptap/vue-3', 'string-length', 'vue-virtual-scroller', 'emoji-mart', 'iso-639-1',
|
||||
'@tiptap/extension-placeholder', '@tiptap/extension-document', '@tiptap/extension-paragraph',
|
||||
'@tiptap/extension-text', '@tiptap/extension-mention', '@tiptap/extension-hard-break',
|
||||
'@tiptap/extension-bold', '@tiptap/extension-italic', '@tiptap/extension-code',
|
||||
'@tiptap/extension-history', 'prosemirror-state', 'browser-fs-access', 'blurhash',
|
||||
'@vueuse/integrations/useFocusTrap', '@tiptap/extension-code-block', 'prosemirror-highlight',
|
||||
'@tiptap/core', 'tippy.js', 'prosemirror-highlight/shiki', '@fnando/sparkline',
|
||||
'@vueuse/gesture', 'github-reserved-names', 'file-saver', 'slimeform', 'vue-advanced-cropper',
|
||||
'workbox-window', 'workbox-precaching', 'workbox-routing', 'workbox-cacheable-response',
|
||||
'workbox-strategies', 'workbox-expiration',
|
||||
],
|
||||
},
|
||||
},
|
||||
postcss: {
|
||||
plugins: {
|
||||
|
|
|
@ -150,6 +150,9 @@
|
|||
"nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"vue": "^3.4.19"
|
||||
},
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm lint-staged"
|
||||
},
|
||||
|
|
|
@ -11,7 +11,7 @@ definePageMeta({
|
|||
})
|
||||
|
||||
const route = useRoute()
|
||||
const id = computedEager(() => route.params.status as string)
|
||||
const id = computed(() => route.params.status as string)
|
||||
const main = ref<ComponentPublicInstance | null>(null)
|
||||
|
||||
const { data: status, pending, refresh: refreshStatus } = useAsyncData(
|
||||
|
@ -71,7 +71,7 @@ onReactivated(() => {
|
|||
<div xl:mt-4 mb="50vh" border="b base">
|
||||
<template v-if="!pendingContext">
|
||||
<StatusCard
|
||||
v-for="comment, i of context?.ancestors" :key="comment.id"
|
||||
v-for="(comment, i) of context?.ancestors" :key="comment.id"
|
||||
:status="comment" :actions="comment.visibility !== 'direct'" context="account"
|
||||
:has-older="true" :newer="context?.ancestors[i - 1]"
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
|||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const accountName = computedEager(() => toShortHandle(params.account as string))
|
||||
const accountName = computed(() => toShortHandle(params.account as string))
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const params = useRoute().params
|
||||
const handle = computedEager(() => params.account as string)
|
||||
const handle = computed(() => params.account as string)
|
||||
|
||||
definePageMeta({ name: 'account-followers' })
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
const params = useRoute().params
|
||||
const handle = computedEager(() => params.account as string)
|
||||
const handle = computed(() => params.account as string)
|
||||
|
||||
definePageMeta({ name: 'account-following' })
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import type { mastodon } from 'masto'
|
||||
|
||||
const params = useRoute().params
|
||||
const handle = computedEager(() => params.account as string)
|
||||
const handle = computed(() => params.account as string)
|
||||
|
||||
definePageMeta({ name: 'account-index' })
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ definePageMeta({ name: 'account-media' })
|
|||
|
||||
const { t } = useI18n()
|
||||
const params = useRoute().params
|
||||
const handle = computedEager(() => params.account as string)
|
||||
const handle = computed(() => params.account as string)
|
||||
|
||||
const account = await fetchAccountByHandle(handle.value)
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ definePageMeta({ name: 'account-replies' })
|
|||
|
||||
const { t } = useI18n()
|
||||
const params = useRoute().params
|
||||
const handle = computedEager(() => params.account as string)
|
||||
const handle = computed(() => params.account as string)
|
||||
|
||||
const account = await fetchAccountByHandle(handle.value)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
|
||||
import type { CommonRouteTabOption } from '~/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
|
@ -17,5 +17,5 @@ useHydratedHead({
|
|||
<p>{{ $t('tooltip.explore_posts_intro') }}</p>
|
||||
</CommonAlert>
|
||||
<!-- TODO: Tabs for trending statuses, tags, and links -->
|
||||
<TimelinePaginator :paginator="paginator" context="public" />
|
||||
<TimelinePaginator v-if="isHydrated" :paginator="paginator" context="public" />
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
|
||||
import type { CommonRouteTabOption } from '~/types'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
|||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const listId = computedEager(() => params.list as string)
|
||||
const listId = computed(() => params.list as string)
|
||||
|
||||
const paginator = useMastoClient().v1.lists.$select(listId.value).accounts.list()
|
||||
</script>
|
||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
|||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const listId = computedEager(() => params.list as string)
|
||||
const listId = computed(() => params.list as string)
|
||||
|
||||
const client = useMastoClient()
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
useHydratedHead({
|
||||
|
@ -13,7 +11,7 @@ useHydratedHead({
|
|||
<template #title>
|
||||
<NuxtLink to="/public" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||
<div i-ri:earth-line />
|
||||
<span>{{ t('title.federated_timeline') }}</span>
|
||||
<span>{{ $t('title.federated_timeline') }}</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
useHydratedHead({
|
||||
title: () => t('nav.search'),
|
||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
|||
})
|
||||
|
||||
const params = useRoute().params
|
||||
const tagName = computedEager(() => params.tag as string)
|
||||
const tagName = computed(() => params.tag as string)
|
||||
|
||||
const { client } = useMasto()
|
||||
const { data: tag, refresh } = await useAsyncData(() => client.value.v1.tags.$select(tagName.value).fetch(), { default: () => shallowRef() })
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
alias: ['/signin/callback'],
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { mastodon } from 'masto'
|
||||
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
|
||||
import type {
|
||||
CommonRouteTabMoreOption,
|
||||
CommonRouteTabOption,
|
||||
} from '~/components/common/CommonRouteTabs.vue'
|
||||
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'auth',
|
||||
|
@ -18,12 +15,12 @@ const tabs = computed<CommonRouteTabOption[]>(() => [
|
|||
{
|
||||
name: 'all',
|
||||
to: '/notifications',
|
||||
display: isHydrated.value ? t('tab.notifications_all') : '',
|
||||
display: t('tab.notifications_all'),
|
||||
},
|
||||
{
|
||||
name: 'mention',
|
||||
to: '/notifications/mention',
|
||||
display: isHydrated.value ? t('tab.notifications_mention') : '',
|
||||
display: t('tab.notifications_mention'),
|
||||
},
|
||||
])
|
||||
|
||||
|
@ -50,13 +47,12 @@ const filterIconMap: Record<mastodon.v1.NotificationType, string> = {
|
|||
'admin.report': 'i-ri:flag-line',
|
||||
}
|
||||
|
||||
const filterText = computed(() => (`${t('tab.notifications_more_tooltip')}${filter ? `: ${t(`tab.notifications_${filter}`)}` : ''}`))
|
||||
|
||||
const filterText = computed(() => `${t('tab.notifications_more_tooltip')}${filter.value ? `: ${t(`tab.notifications_${filter.value}`)}` : ''}`)
|
||||
const notificationFilterRoutes = computed<CommonRouteTabOption[]>(() => NOTIFICATION_FILTER_TYPES.map(
|
||||
name => ({
|
||||
name,
|
||||
to: `/notifications/${name}`,
|
||||
display: isHydrated.value ? t(`tab.notifications_${name}`) : '',
|
||||
display: t(`tab.notifications_${name}`),
|
||||
icon: filterIconMap[name],
|
||||
match: name === filter.value,
|
||||
}),
|
||||
|
@ -74,7 +70,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
|
|||
<template #title>
|
||||
<NuxtLink to="/notifications" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||
<div i-ri:notification-4-line />
|
||||
<span>{{ isHydrated ? t('nav.notifications') : '' }}</span>
|
||||
<span>{{ t('nav.notifications') }}</span>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
|
@ -82,7 +78,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
|
|||
<NuxtLink
|
||||
flex rounded-4 p1
|
||||
hover:bg-active cursor-pointer transition-100
|
||||
:title="isHydrated ? t('settings.notifications.show_btn') : ''"
|
||||
:title="t('settings.notifications.show_btn')"
|
||||
to="/settings/notifications"
|
||||
>
|
||||
<span aria-hidden="true" i-ri:notification-badge-line />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
const { t } = useI18n()
|
||||
|
||||
useHydratedHead({
|
||||
title: () => `${t('tab.notifications_all')} | ${t('nav.notifications')}`,
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ useHydratedHead({
|
|||
|
||||
const route = useRoute()
|
||||
|
||||
const isRootPath = computedEager(() => route.name === 'settings')
|
||||
const isRootPath = computed(() => route.name === 'settings')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -22,12 +22,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
|||
<template #title>
|
||||
<div timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||
<div i-ri:settings-3-line />
|
||||
<span>{{ isHydrated ? $t('nav.settings') : '' }}</span>
|
||||
<span>{{ $t('nav.settings') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div xl:w-97 lg:w-78 w-full>
|
||||
<SettingsItem
|
||||
v-if="isHydrated && currentUser"
|
||||
v-if="currentUser"
|
||||
command
|
||||
icon="i-ri:user-line"
|
||||
:text="$t('settings.profile.label')"
|
||||
|
@ -37,12 +37,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
|||
<SettingsItem
|
||||
command
|
||||
icon="i-ri-compasses-2-line"
|
||||
:text="isHydrated ? $t('settings.interface.label') : ''"
|
||||
:text="$t('settings.interface.label')"
|
||||
to="/settings/interface"
|
||||
:match="$route.path.startsWith('/settings/interface/')"
|
||||
/>
|
||||
<SettingsItem
|
||||
v-if="isHydrated && currentUser"
|
||||
v-if="currentUser"
|
||||
command
|
||||
icon="i-ri:notification-badge-line"
|
||||
:text="$t('settings.notifications_settings')"
|
||||
|
@ -52,28 +52,28 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
|||
<SettingsItem
|
||||
command
|
||||
icon="i-ri-globe-line"
|
||||
:text="isHydrated ? $t('settings.language.label') : ''"
|
||||
:text="$t('settings.language.label')"
|
||||
to="/settings/language"
|
||||
:match="$route.path.startsWith('/settings/language/')"
|
||||
/>
|
||||
<SettingsItem
|
||||
command
|
||||
icon="i-ri-equalizer-line"
|
||||
:text="isHydrated ? $t('settings.preferences.label') : ''"
|
||||
:text="$t('settings.preferences.label')"
|
||||
to="/settings/preferences"
|
||||
:match="$route.path.startsWith('/settings/preferences/')"
|
||||
/>
|
||||
<SettingsItem
|
||||
command
|
||||
icon="i-ri-group-line"
|
||||
:text="isHydrated ? $t('settings.users.label') : ''"
|
||||
:text="$t('settings.users.label')"
|
||||
to="/settings/users"
|
||||
:match="$route.path.startsWith('/settings/users/')"
|
||||
/>
|
||||
<SettingsItem
|
||||
command
|
||||
icon="i-ri:information-line"
|
||||
:text="isHydrated ? $t('settings.about.label') : ''"
|
||||
:text="$t('settings.about.label')"
|
||||
to="/settings/about"
|
||||
:match="$route.path.startsWith('/settings/about/')"
|
||||
/>
|
||||
|
|
|
@ -15,20 +15,20 @@ useHydratedHead({
|
|||
<MainContent back-on-small-screen>
|
||||
<template #title>
|
||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
||||
<span>{{ isHydrated ? $t('settings.notifications.label') : '' }}</span>
|
||||
<span>{{ $t('settings.notifications.label') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SettingsItem
|
||||
command
|
||||
:text="isHydrated ? $t('settings.notifications.notifications.label') : ''"
|
||||
:text="$t('settings.notifications.notifications.label')"
|
||||
to="/settings/notifications/notifications"
|
||||
/>
|
||||
<SettingsItem
|
||||
command
|
||||
:disabled="!pwaEnabled"
|
||||
:text="isHydrated ? $t('settings.notifications.push_notifications.label') : ''"
|
||||
:description="isHydrated ? $t('settings.notifications.push_notifications.description') : ''"
|
||||
:text="$t('settings.notifications.push_notifications.label')"
|
||||
:description="$t('settings.notifications.push_notifications.description')"
|
||||
to="/settings/notifications/push-notifications"
|
||||
/>
|
||||
</MainContent>
|
||||
|
|
|
@ -17,7 +17,7 @@ useHydratedHead({
|
|||
<MainContent back>
|
||||
<template #title>
|
||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
||||
<span>{{ isHydrated ? $t('settings.notifications.push_notifications.label') : '' }}</span>
|
||||
<span>{{ $t('settings.notifications.push_notifications.label') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<NotificationPreferences show />
|
||||
|
|
|
@ -111,7 +111,7 @@ onReactivated(refreshInfo)
|
|||
</template>
|
||||
|
||||
<form space-y-5 @submit.prevent="submit">
|
||||
<div v-if="isHydrated && account">
|
||||
<div v-if="account">
|
||||
<!-- banner -->
|
||||
<div of-hidden bg="gray-500/20" aspect="3">
|
||||
<CommonInputImage
|
||||
|
@ -182,7 +182,7 @@ onReactivated(refreshInfo)
|
|||
|
||||
<!-- metadata -->
|
||||
|
||||
<SettingsProfileMetadata v-if="isHydrated" v-model="form" />
|
||||
<SettingsProfileMetadata v-model="form" />
|
||||
|
||||
<!-- actions -->
|
||||
<div flex="~ gap2" justify-end>
|
||||
|
|
|
@ -14,22 +14,22 @@ useHydratedHead({
|
|||
<MainContent back-on-small-screen>
|
||||
<template #title>
|
||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
||||
<span>{{ isHydrated ? $t('settings.profile.label') : '' }}</span>
|
||||
<span>{{ $t('settings.profile.label') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SettingsItem
|
||||
command large
|
||||
icon="i-ri:user-settings-line"
|
||||
:text="isHydrated ? $t('settings.profile.appearance.label') : ''"
|
||||
:description="isHydrated ? $t('settings.profile.appearance.description') : ''"
|
||||
:text="$t('settings.profile.appearance.label')"
|
||||
:description="$t('settings.profile.appearance.description')"
|
||||
to="/settings/profile/appearance"
|
||||
/>
|
||||
<SettingsItem
|
||||
command large
|
||||
icon="i-ri:hashtag"
|
||||
:text="isHydrated ? $t('settings.profile.featured_tags.label') : ''"
|
||||
:description="isHydrated ? $t('settings.profile.featured_tags.description') : ''"
|
||||
:text="$t('settings.profile.featured_tags.label')"
|
||||
:description="$t('settings.profile.featured_tags.description')"
|
||||
to="/settings/profile/featured-tags"
|
||||
/>
|
||||
<SettingsItem
|
||||
|
|
|
@ -81,12 +81,12 @@ async function importTokens() {
|
|||
</div>
|
||||
<div my4 border="t base" />
|
||||
<button btn-text flex="~ gap-2" items-center @click="exportTokens">
|
||||
<div i-ri-download-2-line />
|
||||
<span block i-ri-download-2-line />
|
||||
{{ $t('settings.users.export') }}
|
||||
</button>
|
||||
</template>
|
||||
<button btn-text flex="~ gap-2" items-center @click="importTokens">
|
||||
<div i-ri-upload-2-line />
|
||||
<span block i-ri-upload-2-line />
|
||||
{{ $t('settings.users.import') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import FloatingVue from 'floating-vue'
|
||||
import { defineNuxtPlugin } from '#app'
|
||||
import { defineNuxtPlugin } from '#imports'
|
||||
|
||||
export default defineNuxtPlugin((nuxtApp) => {
|
||||
nuxtApp.vueApp.use(FloatingVue)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import type { VueI18n } from 'vue-i18n'
|
||||
import type { LocaleObject } from 'vue-i18n-routing'
|
||||
|
||||
export default defineNuxtPlugin(async (nuxt) => {
|
||||
const i18n = nuxt.vueApp.config.globalProperties.$i18n as VueI18n
|
||||
const { setLocale, locales } = i18n
|
||||
const userSettings = useUserSettings()
|
||||
const lang = computed(() => userSettings.value.language)
|
||||
|
||||
const supportLanguages = (locales as LocaleObject[]).map(locale => locale.code)
|
||||
if (!supportLanguages.includes(lang.value))
|
||||
userSettings.value.language = getDefaultLanguage(supportLanguages)
|
||||
|
||||
if (lang.value !== i18n.locale)
|
||||
await setLocale(userSettings.value.language)
|
||||
|
||||
watch([lang, isHydrated], () => {
|
||||
if (isHydrated.value && lang.value !== i18n.locale)
|
||||
setLocale(lang.value)
|
||||
}, { immediate: true })
|
||||
})
|
28
plugins/setup-i18n.ts
Normal file
28
plugins/setup-i18n.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
export default defineNuxtPlugin(async (nuxt) => {
|
||||
const t = nuxt.vueApp.config.globalProperties.$t
|
||||
const d = nuxt.vueApp.config.globalProperties.$d
|
||||
const n = nuxt.vueApp.config.globalProperties.$n
|
||||
|
||||
nuxt.vueApp.config.globalProperties.$t = wrapI18n(t)
|
||||
nuxt.vueApp.config.globalProperties.$d = wrapI18n(d)
|
||||
nuxt.vueApp.config.globalProperties.$n = wrapI18n(n)
|
||||
|
||||
if (process.client) {
|
||||
const i18n = nuxt.vueApp.config.globalProperties.$i18n as import('vue-i18n').VueI18n
|
||||
const { setLocale, locales } = i18n
|
||||
const userSettings = useUserSettings()
|
||||
const lang = computed(() => userSettings.value.language)
|
||||
|
||||
const supportLanguages = (locales as import('vue-i18n-routing').LocaleObject[]).map(locale => locale.code)
|
||||
if (!supportLanguages.includes(lang.value))
|
||||
userSettings.value.language = getDefaultLanguage(supportLanguages)
|
||||
|
||||
if (lang.value !== i18n.locale)
|
||||
await setLocale(userSettings.value.language)
|
||||
|
||||
watch([lang, isHydrated], () => {
|
||||
if (isHydrated.value && lang.value !== i18n.locale)
|
||||
setLocale(lang.value)
|
||||
}, { immediate: true })
|
||||
}
|
||||
})
|
715
pnpm-lock.yaml
715
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,6 @@
|
|||
import type { mastodon } from 'masto'
|
||||
import type { MarkNonNullable, Mutable } from './utils'
|
||||
import type { RouteLocationRaw } from '#vue-router'
|
||||
|
||||
export interface AppInfo {
|
||||
id: string
|
||||
|
@ -63,6 +64,22 @@ export interface ConfirmDialogLabel {
|
|||
}
|
||||
export type ConfirmDialogChoice = 'confirm' | 'cancel'
|
||||
|
||||
export interface CommonRouteTabOption {
|
||||
to: RouteLocationRaw
|
||||
display: string
|
||||
disabled?: boolean
|
||||
name?: string
|
||||
icon?: string
|
||||
hide?: boolean
|
||||
match?: boolean
|
||||
}
|
||||
export interface CommonRouteTabMoreOption {
|
||||
options: CommonRouteTabOption[]
|
||||
icon?: string
|
||||
tooltip?: string
|
||||
match?: boolean
|
||||
}
|
||||
|
||||
export interface ErrorDialogData {
|
||||
title: string
|
||||
messages: string[]
|
||||
|
|
23
utils/i18n.ts
Normal file
23
utils/i18n.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { useI18n as useOriginalI18n } from 'vue-i18n'
|
||||
|
||||
export function useI18n() {
|
||||
const {
|
||||
t,
|
||||
d,
|
||||
n,
|
||||
...rest
|
||||
} = useOriginalI18n()
|
||||
|
||||
return {
|
||||
...rest,
|
||||
t: wrapI18n(t),
|
||||
d: wrapI18n(d),
|
||||
n: wrapI18n(n),
|
||||
} satisfies ReturnType<typeof useOriginalI18n>
|
||||
}
|
||||
|
||||
export function wrapI18n<T extends (...args: any[]) => any>(t: T): T {
|
||||
return <T>((...args: any[]) => {
|
||||
return isHydrated.value ? t(...args) : ''
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue