forked from Mirrors/elk
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">
|
<script setup lang="ts">
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
|
import { fetchAccountByHandle } from '~/composables/cache'
|
||||||
|
|
||||||
|
type WatcherType = [acc?: mastodon.v1.Account, h?: string, v?: boolean]
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
|
@ -11,11 +14,49 @@ const props = defineProps<{
|
||||||
disabled?: boolean
|
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()
|
const userSettings = useUserSettings()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<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">
|
<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 />
|
<slot />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
|
@ -23,4 +64,5 @@ const userSettings = useUserSettings()
|
||||||
</template>
|
</template>
|
||||||
</VMenu>
|
</VMenu>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CommonRouteTabOption } from '../common/CommonRouteTabs.vue'
|
import type { CommonRouteTabOption } from '~/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const server = computedEager(() => route.params.server as string)
|
const server = computed(() => route.params.server as string)
|
||||||
const account = computedEager(() => route.params.account as string)
|
const account = computed(() => route.params.account as string)
|
||||||
|
|
||||||
const tabs = computed<CommonRouteTabOption[]>(() => [
|
const tabs = computed<CommonRouteTabOption[]>(() => [
|
||||||
{
|
{
|
||||||
|
|
|
@ -94,8 +94,8 @@ defineExpose({ createEntry, removeEntry, updateEntry })
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<slot
|
<slot
|
||||||
v-for="item, index of items"
|
v-for="(item, index) of items"
|
||||||
v-bind="{ key: item[keyProp as keyof U] }"
|
v-bind="{ key: (item as U)[keyProp as keyof U] }"
|
||||||
:item="item as U"
|
:item="item as U"
|
||||||
:older="items[index + 1] as U"
|
:older="items[index + 1] as U"
|
||||||
:newer="items[index - 1] as U"
|
:newer="items[index - 1] as U"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
|
||||||
|
|
||||||
const { options, command, replace, preventScrollTop = false, moreOptions } = defineProps<{
|
const { options, command, replace, preventScrollTop = false, moreOptions } = defineProps<{
|
||||||
options: CommonRouteTabOption[]
|
options: CommonRouteTabOption[]
|
||||||
|
@ -10,22 +10,6 @@ const { options, command, replace, preventScrollTop = false, moreOptions } = def
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { t } = useI18n()
|
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()
|
const router = useRouter()
|
||||||
|
|
||||||
useCommands(() => command
|
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>
|
<span ws-nowrap mxa sm:px2 sm:py3 py2 text-center text-secondary-light op50>{{ option.display }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="moreOptions?.options?.length">
|
<template v-if="isHydrated && moreOptions?.options?.length">
|
||||||
<CommonDropdown placement="bottom" flex cursor-pointer mx-1.25rem>
|
<CommonDropdown placement="bottom" flex cursor-pointer mx-1.25rem>
|
||||||
<CommonTooltip placement="top" :content="moreOptions.tooltip || t('action.more')">
|
<CommonTooltip placement="top" :content="moreOptions.tooltip || t('action.more')">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -10,6 +10,7 @@ defineProps<Props>()
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<VTooltip
|
<VTooltip
|
||||||
|
v-if="isHydrated"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
auto-hide
|
auto-hide
|
||||||
>
|
>
|
||||||
|
|
|
@ -24,7 +24,7 @@ interface ShortcutItemGroup {
|
||||||
const isMac = useIsMac()
|
const isMac = useIsMac()
|
||||||
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
|
const modifierKeyName = computed(() => isMac.value ? '⌘' : 'Ctrl')
|
||||||
|
|
||||||
const shortcutItemGroups: ShortcutItemGroup[] = [
|
const shortcutItemGroups = computed<ShortcutItemGroup[]>(() => [
|
||||||
{
|
{
|
||||||
name: t('magic_keys.groups.navigation.title'),
|
name: t('magic_keys.groups.navigation.title'),
|
||||||
items: [
|
items: [
|
||||||
|
@ -79,7 +79,7 @@ const shortcutItemGroups: ShortcutItemGroup[] = [
|
||||||
name: t('magic_keys.groups.media.title'),
|
name: t('magic_keys.groups.media.title'),
|
||||||
items: [],
|
items: [],
|
||||||
},
|
},
|
||||||
]
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -91,10 +91,6 @@ export async function fetchAccountByHandle(acct: string): Promise<mastodon.v1.Ac
|
||||||
return account
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAccountByHandle(acct: string) {
|
|
||||||
return useAsyncState(() => fetchAccountByHandle(acct), null).state
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAccountById(id?: string | null) {
|
export function useAccountById(id?: string | null) {
|
||||||
return useAsyncState(() => fetchAccountById(id), null).state
|
return useAsyncState(() => fetchAccountById(id), null).state
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,7 +362,7 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
|
||||||
// Backward compatibility, respect webDomain in acct
|
// Backward compatibility, respect webDomain in acct
|
||||||
// In previous versions, acct was username@server instead of username@webDomain
|
// In previous versions, acct was username@server instead of username@webDomain
|
||||||
// for example: elk@m.webtoo.ls instead of elk@webtoo.ls
|
// for example: elk@m.webtoo.ls instead of elk@webtoo.ls
|
||||||
// if (!all.value[id]) { // TODO: add back this condition in the future
|
if (!all.value[id]) {
|
||||||
const [username, webDomain] = id.split('@')
|
const [username, webDomain] = id.split('@')
|
||||||
const server = currentServer.value
|
const server = currentServer.value
|
||||||
if (webDomain && server && server !== webDomain) {
|
if (webDomain && server && server !== webDomain) {
|
||||||
|
@ -374,8 +374,8 @@ export function useUserLocalStorage<T extends object>(key: string, initial: () =
|
||||||
all.value = newAllValue
|
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]
|
return all.value[id]
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default defineNuxtConfig({
|
||||||
tsConfig: {
|
tsConfig: {
|
||||||
exclude: ['../service-worker'],
|
exclude: ['../service-worker'],
|
||||||
vueCompilerOptions: {
|
vueCompilerOptions: {
|
||||||
target: 3.3,
|
target: 3.4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -75,6 +75,11 @@ export default defineNuxtConfig({
|
||||||
'./composables/settings',
|
'./composables/settings',
|
||||||
'./composables/tiptap/index.ts',
|
'./composables/tiptap/index.ts',
|
||||||
],
|
],
|
||||||
|
imports: [{
|
||||||
|
name: 'useI18n',
|
||||||
|
from: '~/utils/i18n',
|
||||||
|
priority: 100,
|
||||||
|
}],
|
||||||
injectAtEnd: true,
|
injectAtEnd: true,
|
||||||
},
|
},
|
||||||
vite: {
|
vite: {
|
||||||
|
@ -86,6 +91,20 @@ export default defineNuxtConfig({
|
||||||
build: {
|
build: {
|
||||||
target: 'esnext',
|
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: {
|
postcss: {
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|
|
@ -150,6 +150,9 @@
|
||||||
"nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch"
|
"nuxt-security@0.13.1": "patches/nuxt-security@0.13.1.patch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"vue": "^3.4.19"
|
||||||
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "pnpm lint-staged"
|
"pre-commit": "pnpm lint-staged"
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,7 @@ definePageMeta({
|
||||||
})
|
})
|
||||||
|
|
||||||
const route = useRoute()
|
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 main = ref<ComponentPublicInstance | null>(null)
|
||||||
|
|
||||||
const { data: status, pending, refresh: refreshStatus } = useAsyncData(
|
const { data: status, pending, refresh: refreshStatus } = useAsyncData(
|
||||||
|
@ -71,7 +71,7 @@ onReactivated(() => {
|
||||||
<div xl:mt-4 mb="50vh" border="b base">
|
<div xl:mt-4 mb="50vh" border="b base">
|
||||||
<template v-if="!pendingContext">
|
<template v-if="!pendingContext">
|
||||||
<StatusCard
|
<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"
|
:status="comment" :actions="comment.visibility !== 'direct'" context="account"
|
||||||
:has-older="true" :newer="context?.ancestors[i - 1]"
|
:has-older="true" :newer="context?.ancestors[i - 1]"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
||||||
})
|
})
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const accountName = computedEager(() => toShortHandle(params.account as string))
|
const accountName = computed(() => toShortHandle(params.account as string))
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = computedEager(() => params.account as string)
|
const handle = computed(() => params.account as string)
|
||||||
|
|
||||||
definePageMeta({ name: 'account-followers' })
|
definePageMeta({ name: 'account-followers' })
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = computedEager(() => params.account as string)
|
const handle = computed(() => params.account as string)
|
||||||
|
|
||||||
definePageMeta({ name: 'account-following' })
|
definePageMeta({ name: 'account-following' })
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = computedEager(() => params.account as string)
|
const handle = computed(() => params.account as string)
|
||||||
|
|
||||||
definePageMeta({ name: 'account-index' })
|
definePageMeta({ name: 'account-index' })
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ definePageMeta({ name: 'account-media' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = computedEager(() => params.account as string)
|
const handle = computed(() => params.account as string)
|
||||||
|
|
||||||
const account = await fetchAccountByHandle(handle.value)
|
const account = await fetchAccountByHandle(handle.value)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ definePageMeta({ name: 'account-replies' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const handle = computedEager(() => params.account as string)
|
const handle = computed(() => params.account as string)
|
||||||
|
|
||||||
const account = await fetchAccountByHandle(handle.value)
|
const account = await fetchAccountByHandle(handle.value)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
|
import type { CommonRouteTabOption } from '~/types'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
|
|
@ -17,5 +17,5 @@ useHydratedHead({
|
||||||
<p>{{ $t('tooltip.explore_posts_intro') }}</p>
|
<p>{{ $t('tooltip.explore_posts_intro') }}</p>
|
||||||
</CommonAlert>
|
</CommonAlert>
|
||||||
<!-- TODO: Tabs for trending statuses, tags, and links -->
|
<!-- TODO: Tabs for trending statuses, tags, and links -->
|
||||||
<TimelinePaginator :paginator="paginator" context="public" />
|
<TimelinePaginator v-if="isHydrated" :paginator="paginator" context="public" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CommonRouteTabOption } from '~/components/common/CommonRouteTabs.vue'
|
import type { CommonRouteTabOption } from '~/types'
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
||||||
})
|
})
|
||||||
|
|
||||||
const params = useRoute().params
|
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()
|
const paginator = useMastoClient().v1.lists.$select(listId.value).accounts.list()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
||||||
})
|
})
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const listId = computedEager(() => params.list as string)
|
const listId = computed(() => params.list as string)
|
||||||
|
|
||||||
const client = useMastoClient()
|
const client = useMastoClient()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
useHydratedHead({
|
useHydratedHead({
|
||||||
|
@ -13,7 +11,7 @@ useHydratedHead({
|
||||||
<template #title>
|
<template #title>
|
||||||
<NuxtLink to="/public" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
<NuxtLink to="/public" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||||
<div i-ri:earth-line />
|
<div i-ri:earth-line />
|
||||||
<span>{{ t('title.federated_timeline') }}</span>
|
<span>{{ $t('title.federated_timeline') }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
useHydratedHead({
|
useHydratedHead({
|
||||||
title: () => t('nav.search'),
|
title: () => t('nav.search'),
|
||||||
|
|
|
@ -4,7 +4,7 @@ definePageMeta({
|
||||||
})
|
})
|
||||||
|
|
||||||
const params = useRoute().params
|
const params = useRoute().params
|
||||||
const tagName = computedEager(() => params.tag as string)
|
const tagName = computed(() => params.tag as string)
|
||||||
|
|
||||||
const { client } = useMasto()
|
const { client } = useMasto()
|
||||||
const { data: tag, refresh } = await useAsyncData(() => client.value.v1.tags.$select(tagName.value).fetch(), { default: () => shallowRef() })
|
const { data: tag, refresh } = await useAsyncData(() => client.value.v1.tags.$select(tagName.value).fetch(), { default: () => shallowRef() })
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from 'vue-i18n'
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
alias: ['/signin/callback'],
|
alias: ['/signin/callback'],
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { mastodon } from 'masto'
|
import type { mastodon } from 'masto'
|
||||||
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
|
import { NOTIFICATION_FILTER_TYPES } from '~/constants'
|
||||||
import type {
|
import type { CommonRouteTabMoreOption, CommonRouteTabOption } from '~/types'
|
||||||
CommonRouteTabMoreOption,
|
|
||||||
CommonRouteTabOption,
|
|
||||||
} from '~/components/common/CommonRouteTabs.vue'
|
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: 'auth',
|
middleware: 'auth',
|
||||||
|
@ -18,12 +15,12 @@ const tabs = computed<CommonRouteTabOption[]>(() => [
|
||||||
{
|
{
|
||||||
name: 'all',
|
name: 'all',
|
||||||
to: '/notifications',
|
to: '/notifications',
|
||||||
display: isHydrated.value ? t('tab.notifications_all') : '',
|
display: t('tab.notifications_all'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mention',
|
name: 'mention',
|
||||||
to: '/notifications/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',
|
'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(
|
const notificationFilterRoutes = computed<CommonRouteTabOption[]>(() => NOTIFICATION_FILTER_TYPES.map(
|
||||||
name => ({
|
name => ({
|
||||||
name,
|
name,
|
||||||
to: `/notifications/${name}`,
|
to: `/notifications/${name}`,
|
||||||
display: isHydrated.value ? t(`tab.notifications_${name}`) : '',
|
display: t(`tab.notifications_${name}`),
|
||||||
icon: filterIconMap[name],
|
icon: filterIconMap[name],
|
||||||
match: name === filter.value,
|
match: name === filter.value,
|
||||||
}),
|
}),
|
||||||
|
@ -74,7 +70,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
|
||||||
<template #title>
|
<template #title>
|
||||||
<NuxtLink to="/notifications" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
<NuxtLink to="/notifications" timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||||
<div i-ri:notification-4-line />
|
<div i-ri:notification-4-line />
|
||||||
<span>{{ isHydrated ? t('nav.notifications') : '' }}</span>
|
<span>{{ t('nav.notifications') }}</span>
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -82,7 +78,7 @@ const moreOptions = computed<CommonRouteTabMoreOption>(() => ({
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
flex rounded-4 p1
|
flex rounded-4 p1
|
||||||
hover:bg-active cursor-pointer transition-100
|
hover:bg-active cursor-pointer transition-100
|
||||||
:title="isHydrated ? t('settings.notifications.show_btn') : ''"
|
:title="t('settings.notifications.show_btn')"
|
||||||
to="/settings/notifications"
|
to="/settings/notifications"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true" i-ri:notification-badge-line />
|
<span aria-hidden="true" i-ri:notification-badge-line />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
useHydratedHead({
|
useHydratedHead({
|
||||||
title: () => `${t('tab.notifications_all')} | ${t('nav.notifications')}`,
|
title: () => `${t('tab.notifications_all')} | ${t('nav.notifications')}`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -11,7 +11,7 @@ useHydratedHead({
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const isRootPath = computedEager(() => route.name === 'settings')
|
const isRootPath = computed(() => route.name === 'settings')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -22,12 +22,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
||||||
<template #title>
|
<template #title>
|
||||||
<div timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
<div timeline-title-style flex items-center gap-2 @click="$scrollToTop">
|
||||||
<div i-ri:settings-3-line />
|
<div i-ri:settings-3-line />
|
||||||
<span>{{ isHydrated ? $t('nav.settings') : '' }}</span>
|
<span>{{ $t('nav.settings') }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div xl:w-97 lg:w-78 w-full>
|
<div xl:w-97 lg:w-78 w-full>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
v-if="isHydrated && currentUser"
|
v-if="currentUser"
|
||||||
command
|
command
|
||||||
icon="i-ri:user-line"
|
icon="i-ri:user-line"
|
||||||
:text="$t('settings.profile.label')"
|
:text="$t('settings.profile.label')"
|
||||||
|
@ -37,12 +37,12 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
icon="i-ri-compasses-2-line"
|
icon="i-ri-compasses-2-line"
|
||||||
:text="isHydrated ? $t('settings.interface.label') : ''"
|
:text="$t('settings.interface.label')"
|
||||||
to="/settings/interface"
|
to="/settings/interface"
|
||||||
:match="$route.path.startsWith('/settings/interface/')"
|
:match="$route.path.startsWith('/settings/interface/')"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
v-if="isHydrated && currentUser"
|
v-if="currentUser"
|
||||||
command
|
command
|
||||||
icon="i-ri:notification-badge-line"
|
icon="i-ri:notification-badge-line"
|
||||||
:text="$t('settings.notifications_settings')"
|
:text="$t('settings.notifications_settings')"
|
||||||
|
@ -52,28 +52,28 @@ const isRootPath = computedEager(() => route.name === 'settings')
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
icon="i-ri-globe-line"
|
icon="i-ri-globe-line"
|
||||||
:text="isHydrated ? $t('settings.language.label') : ''"
|
:text="$t('settings.language.label')"
|
||||||
to="/settings/language"
|
to="/settings/language"
|
||||||
:match="$route.path.startsWith('/settings/language/')"
|
:match="$route.path.startsWith('/settings/language/')"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
icon="i-ri-equalizer-line"
|
icon="i-ri-equalizer-line"
|
||||||
:text="isHydrated ? $t('settings.preferences.label') : ''"
|
:text="$t('settings.preferences.label')"
|
||||||
to="/settings/preferences"
|
to="/settings/preferences"
|
||||||
:match="$route.path.startsWith('/settings/preferences/')"
|
:match="$route.path.startsWith('/settings/preferences/')"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
icon="i-ri-group-line"
|
icon="i-ri-group-line"
|
||||||
:text="isHydrated ? $t('settings.users.label') : ''"
|
:text="$t('settings.users.label')"
|
||||||
to="/settings/users"
|
to="/settings/users"
|
||||||
:match="$route.path.startsWith('/settings/users/')"
|
:match="$route.path.startsWith('/settings/users/')"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
icon="i-ri:information-line"
|
icon="i-ri:information-line"
|
||||||
:text="isHydrated ? $t('settings.about.label') : ''"
|
:text="$t('settings.about.label')"
|
||||||
to="/settings/about"
|
to="/settings/about"
|
||||||
:match="$route.path.startsWith('/settings/about/')"
|
:match="$route.path.startsWith('/settings/about/')"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,20 +15,20 @@ useHydratedHead({
|
||||||
<MainContent back-on-small-screen>
|
<MainContent back-on-small-screen>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
:text="isHydrated ? $t('settings.notifications.notifications.label') : ''"
|
:text="$t('settings.notifications.notifications.label')"
|
||||||
to="/settings/notifications/notifications"
|
to="/settings/notifications/notifications"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command
|
command
|
||||||
:disabled="!pwaEnabled"
|
:disabled="!pwaEnabled"
|
||||||
:text="isHydrated ? $t('settings.notifications.push_notifications.label') : ''"
|
:text="$t('settings.notifications.push_notifications.label')"
|
||||||
:description="isHydrated ? $t('settings.notifications.push_notifications.description') : ''"
|
:description="$t('settings.notifications.push_notifications.description')"
|
||||||
to="/settings/notifications/push-notifications"
|
to="/settings/notifications/push-notifications"
|
||||||
/>
|
/>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
|
|
|
@ -17,7 +17,7 @@ useHydratedHead({
|
||||||
<MainContent back>
|
<MainContent back>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<NotificationPreferences show />
|
<NotificationPreferences show />
|
||||||
|
|
|
@ -111,7 +111,7 @@ onReactivated(refreshInfo)
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<form space-y-5 @submit.prevent="submit">
|
<form space-y-5 @submit.prevent="submit">
|
||||||
<div v-if="isHydrated && account">
|
<div v-if="account">
|
||||||
<!-- banner -->
|
<!-- banner -->
|
||||||
<div of-hidden bg="gray-500/20" aspect="3">
|
<div of-hidden bg="gray-500/20" aspect="3">
|
||||||
<CommonInputImage
|
<CommonInputImage
|
||||||
|
@ -182,7 +182,7 @@ onReactivated(refreshInfo)
|
||||||
|
|
||||||
<!-- metadata -->
|
<!-- metadata -->
|
||||||
|
|
||||||
<SettingsProfileMetadata v-if="isHydrated" v-model="form" />
|
<SettingsProfileMetadata v-model="form" />
|
||||||
|
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div flex="~ gap2" justify-end>
|
<div flex="~ gap2" justify-end>
|
||||||
|
|
|
@ -14,22 +14,22 @@ useHydratedHead({
|
||||||
<MainContent back-on-small-screen>
|
<MainContent back-on-small-screen>
|
||||||
<template #title>
|
<template #title>
|
||||||
<div text-lg font-bold flex items-center gap-2 @click="$scrollToTop">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command large
|
command large
|
||||||
icon="i-ri:user-settings-line"
|
icon="i-ri:user-settings-line"
|
||||||
:text="isHydrated ? $t('settings.profile.appearance.label') : ''"
|
:text="$t('settings.profile.appearance.label')"
|
||||||
:description="isHydrated ? $t('settings.profile.appearance.description') : ''"
|
:description="$t('settings.profile.appearance.description')"
|
||||||
to="/settings/profile/appearance"
|
to="/settings/profile/appearance"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
command large
|
command large
|
||||||
icon="i-ri:hashtag"
|
icon="i-ri:hashtag"
|
||||||
:text="isHydrated ? $t('settings.profile.featured_tags.label') : ''"
|
:text="$t('settings.profile.featured_tags.label')"
|
||||||
:description="isHydrated ? $t('settings.profile.featured_tags.description') : ''"
|
:description="$t('settings.profile.featured_tags.description')"
|
||||||
to="/settings/profile/featured-tags"
|
to="/settings/profile/featured-tags"
|
||||||
/>
|
/>
|
||||||
<SettingsItem
|
<SettingsItem
|
||||||
|
|
|
@ -81,12 +81,12 @@ async function importTokens() {
|
||||||
</div>
|
</div>
|
||||||
<div my4 border="t base" />
|
<div my4 border="t base" />
|
||||||
<button btn-text flex="~ gap-2" items-center @click="exportTokens">
|
<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') }}
|
{{ $t('settings.users.export') }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<button btn-text flex="~ gap-2" items-center @click="importTokens">
|
<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') }}
|
{{ $t('settings.users.import') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import FloatingVue from 'floating-vue'
|
import FloatingVue from 'floating-vue'
|
||||||
import { defineNuxtPlugin } from '#app'
|
import { defineNuxtPlugin } from '#imports'
|
||||||
|
|
||||||
export default defineNuxtPlugin((nuxtApp) => {
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
nuxtApp.vueApp.use(FloatingVue)
|
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 { mastodon } from 'masto'
|
||||||
import type { MarkNonNullable, Mutable } from './utils'
|
import type { MarkNonNullable, Mutable } from './utils'
|
||||||
|
import type { RouteLocationRaw } from '#vue-router'
|
||||||
|
|
||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
id: string
|
id: string
|
||||||
|
@ -63,6 +64,22 @@ export interface ConfirmDialogLabel {
|
||||||
}
|
}
|
||||||
export type ConfirmDialogChoice = 'confirm' | 'cancel'
|
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 {
|
export interface ErrorDialogData {
|
||||||
title: string
|
title: string
|
||||||
messages: 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