feat: allow choosing favorite buttons in bottom navigation bar (#2761)
This commit is contained in:
parent
2a6a994da1
commit
2cb070c83c
|
@ -1,60 +1,45 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Component } from 'vue'
|
||||||
|
import type { NavButtonName } from '../../composables/settings'
|
||||||
|
|
||||||
|
import { STORAGE_KEY_BOTTOM_NAV_BUTTONS } from '~/constants'
|
||||||
|
|
||||||
|
import { NavButtonExplore, NavButtonFederated, NavButtonHome, NavButtonLocal, NavButtonMention, NavButtonMoreMenu, NavButtonNotification, NavButtonSearch } from '#components'
|
||||||
|
|
||||||
|
interface NavButton {
|
||||||
|
name: string
|
||||||
|
component: Component
|
||||||
|
}
|
||||||
|
|
||||||
|
const navButtons: NavButton[] = [
|
||||||
|
{ name: 'home', component: NavButtonHome },
|
||||||
|
{ name: 'search', component: NavButtonSearch },
|
||||||
|
{ name: 'notification', component: NavButtonNotification },
|
||||||
|
{ name: 'mention', component: NavButtonMention },
|
||||||
|
{ name: 'explore', component: NavButtonExplore },
|
||||||
|
{ name: 'local', component: NavButtonLocal },
|
||||||
|
{ name: 'federated', component: NavButtonFederated },
|
||||||
|
{ name: 'moreMenu', component: NavButtonMoreMenu },
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultSelectedNavButtonNames: NavButtonName[] = currentUser.value
|
||||||
|
? ['home', 'search', 'notification', 'mention', 'moreMenu']
|
||||||
|
: ['explore', 'local', 'federated', 'moreMenu']
|
||||||
|
const selectedNavButtonNames = useLocalStorage<NavButtonName[]>(STORAGE_KEY_BOTTOM_NAV_BUTTONS, defaultSelectedNavButtonNames)
|
||||||
|
|
||||||
|
const selectedNavButtons = computed(() => selectedNavButtonNames.value.map(name => navButtons.find(navButton => navButton.name === name)))
|
||||||
|
|
||||||
// only one icon can be lit up at the same time
|
// only one icon can be lit up at the same time
|
||||||
import { STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE } from '~/constants'
|
|
||||||
|
|
||||||
const moreMenuVisible = ref(false)
|
const moreMenuVisible = ref(false)
|
||||||
|
|
||||||
const { notifications } = useNotifications()
|
|
||||||
const lastAccessedNotificationRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE, '')
|
|
||||||
const lastAccessedExploreRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, '')
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- This weird styles above are used for scroll locking, don't change it unless you know exactly what you're doing. -->
|
||||||
<nav
|
<nav
|
||||||
h-14 border="t base" flex flex-row text-xl
|
h-14 border="t base" flex flex-row text-xl
|
||||||
of-y-scroll scrollbar-hide overscroll-none
|
of-y-scroll scrollbar-hide overscroll-none
|
||||||
class="after-content-empty after:(h-[calc(100%+0.5px)] w-0.1px pointer-events-none)"
|
class="after-content-empty after:(h-[calc(100%+0.5px)] w-0.1px pointer-events-none)"
|
||||||
>
|
>
|
||||||
<!-- These weird styles above are used for scroll locking, don't change it unless you know exactly what you're doing. -->
|
<Component :is="navButton!.component" v-for="navButton in selectedNavButtons" :key="navButton!.name" :active-class="moreMenuVisible ? '' : 'text-primary'" />
|
||||||
<template v-if="currentUser">
|
|
||||||
<NuxtLink to="/home" :aria-label="$t('nav.home')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:home-5-line />
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/search" :aria-label="$t('nav.search')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:search-line />
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink :to="`/notifications/${lastAccessedNotificationRoute}`" :aria-label="$t('nav.notifications')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div flex relative>
|
|
||||||
<div class="i-ri:notification-4-line" text-xl />
|
|
||||||
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
|
|
||||||
{{ notifications < 10 ? notifications : '•' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink to="/conversations" :aria-label="$t('nav.conversations')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:at-line />
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<NuxtLink :to="`/${currentServer}/explore/${lastAccessedExploreRoute}`" :aria-label="$t('nav.explore')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:compass-3-line />
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink group :to="`/${currentServer}/public/local`" :aria-label="$t('nav.local')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:group-2-line />
|
|
||||||
</NuxtLink>
|
|
||||||
<NuxtLink :to="`/${currentServer}/public`" :aria-label="$t('nav.federated')" :active-class="moreMenuVisible ? '' : 'text-primary'" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
|
||||||
<div i-ri:earth-line />
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
<NavBottomMoreMenu v-slot="{ toggleVisible, show }" v-model="moreMenuVisible" flex flex-row items-center place-content-center h-full flex-1 cursor-pointer>
|
|
||||||
<button
|
|
||||||
flex items-center place-content-center h-full flex-1 class="select-none"
|
|
||||||
:class="show ? '!text-primary' : ''"
|
|
||||||
aria-label="More menu"
|
|
||||||
@click="toggleVisible"
|
|
||||||
>
|
|
||||||
<span :class="show ? 'i-ri:close-fill' : 'i-ri:more-fill'" />
|
|
||||||
</button>
|
|
||||||
</NavBottomMoreMenu>
|
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
15
components/nav/button/Explore.vue
Normal file
15
components/nav/button/Explore.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE } from '~/constants'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const lastAccessedExploreRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE, '')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink :to="`/${currentServer}/explore/${lastAccessedExploreRoute}`" :aria-label="$t('nav.explore')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div i-ri:compass-3-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
11
components/nav/button/Federated.vue
Normal file
11
components/nav/button/Federated.vue
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink :to="`/${currentServer}/public`" :aria-label="$t('nav.federated')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div i-ri:earth-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
11
components/nav/button/Home.vue
Normal file
11
components/nav/button/Home.vue
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink to="/home" :aria-label="$t('nav.home')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div i-ri:home-5-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
11
components/nav/button/Local.vue
Normal file
11
components/nav/button/Local.vue
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink group :to="`/${currentServer}/public/local`" :aria-label="$t('nav.local')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div i-ri:group-2-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
15
components/nav/button/Mention.vue
Normal file
15
components/nav/button/Mention.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink
|
||||||
|
to="/conversations" :aria-label="$t('nav.conversations')"
|
||||||
|
:active-class="activeClass" flex flex-row items-center place-content-center h-full
|
||||||
|
flex-1 class="coarse-pointer:select-none" @click="$scrollToTop"
|
||||||
|
>
|
||||||
|
<div i-ri:at-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
19
components/nav/button/MoreMenu.vue
Normal file
19
components/nav/button/MoreMenu.vue
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineModel<boolean>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NavBottomMoreMenu
|
||||||
|
v-slot="{ toggleVisible, show }" v-model="modelValue!" flex flex-row items-center
|
||||||
|
place-content-center h-full flex-1 cursor-pointer
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
flex items-center place-content-center h-full flex-1 class="select-none"
|
||||||
|
:class="show ? '!text-primary' : ''"
|
||||||
|
aria-label="More menu"
|
||||||
|
@click="toggleVisible"
|
||||||
|
>
|
||||||
|
<span :class="show ? 'i-ri:close-fill' : 'i-ri:more-fill'" />
|
||||||
|
</button>
|
||||||
|
</NavBottomMoreMenu>
|
||||||
|
</template>
|
20
components/nav/button/Notification.vue
Normal file
20
components/nav/button/Notification.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE } from '~/constants'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
const { notifications } = useNotifications()
|
||||||
|
const lastAccessedNotificationRoute = useLocalStorage(STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE, '')
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink :to="`/notifications/${lastAccessedNotificationRoute}`" :aria-label="$t('nav.notifications')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div flex relative>
|
||||||
|
<div class="i-ri:notification-4-line" text-xl />
|
||||||
|
<div v-if="notifications" class="top-[-0.3rem] right-[-0.3rem]" absolute font-bold rounded-full h-4 w-4 text-xs bg-primary text-inverted flex items-center justify-center>
|
||||||
|
{{ notifications < 10 ? notifications : '•' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
11
components/nav/button/Search.vue
Normal file
11
components/nav/button/Search.vue
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
activeClass: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NuxtLink to="/search" :aria-label="$t('nav.search')" :active-class="activeClass" flex flex-row items-center place-content-center h-full flex-1 class="coarse-pointer:select-none" @click="$scrollToTop">
|
||||||
|
<div i-ri:search-line />
|
||||||
|
</NuxtLink>
|
||||||
|
</template>
|
125
components/settings/SettingsBottomNav.vue
Normal file
125
components/settings/SettingsBottomNav.vue
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { NavButtonName } from '~/composables/settings'
|
||||||
|
import { STORAGE_KEY_BOTTOM_NAV_BUTTONS } from '~/constants'
|
||||||
|
|
||||||
|
interface NavButton {
|
||||||
|
name: NavButtonName
|
||||||
|
label: string
|
||||||
|
icon: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableNavButtons: NavButton[] = [
|
||||||
|
{ name: 'home', label: 'nav.home', icon: 'i-ri:home-5-line' },
|
||||||
|
{ name: 'search', label: 'nav.search', icon: 'i-ri:search-line' },
|
||||||
|
{ name: 'notification', label: 'nav.notifications', icon: 'i-ri:notification-4-line' },
|
||||||
|
{ name: 'mention', label: 'nav.conversations', icon: 'i-ri:at-line' },
|
||||||
|
{ name: 'explore', label: 'nav.explore', icon: 'i-ri:compass-3-line' },
|
||||||
|
{ name: 'local', label: 'nav.local', icon: 'i-ri:group-2-line' },
|
||||||
|
{ name: 'federated', label: 'nav.federated', icon: 'i-ri:earth-line' },
|
||||||
|
{ name: 'moreMenu', label: 'nav.more_menu', icon: 'i-ri:more-fill' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const defaultSelectedNavButtonNames = computed<NavButtonName[]>(() =>
|
||||||
|
currentUser.value
|
||||||
|
? ['home', 'search', 'notification', 'mention', 'moreMenu']
|
||||||
|
: ['explore', 'local', 'federated', 'moreMenu'],
|
||||||
|
)
|
||||||
|
const navButtonNamesSetting = useLocalStorage<NavButtonName[]>(STORAGE_KEY_BOTTOM_NAV_BUTTONS, defaultSelectedNavButtonNames.value)
|
||||||
|
const selectedNavButtonNames = ref(navButtonNamesSetting.value)
|
||||||
|
|
||||||
|
const selectedNavButtons = computed<NavButton[]>(() =>
|
||||||
|
selectedNavButtonNames.value.map(name =>
|
||||||
|
availableNavButtons.find(navButton => navButton.name === name)!,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const canSave = computed(() =>
|
||||||
|
selectedNavButtonNames.value.length > 0
|
||||||
|
&& selectedNavButtonNames.value.includes('moreMenu')
|
||||||
|
&& JSON.stringify(selectedNavButtonNames.value) !== JSON.stringify(navButtonNamesSetting.value),
|
||||||
|
)
|
||||||
|
|
||||||
|
function isAdded(name: NavButtonName) {
|
||||||
|
return selectedNavButtonNames.value.includes(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
function append(navButtonName: NavButtonName) {
|
||||||
|
const maxButtonNumber = 5
|
||||||
|
if (selectedNavButtonNames.value.length < maxButtonNumber)
|
||||||
|
selectedNavButtonNames.value = [...selectedNavButtonNames.value, navButtonName]
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(navButtonName: NavButtonName) {
|
||||||
|
selectedNavButtonNames.value = selectedNavButtonNames.value.filter(name => name !== navButtonName)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
selectedNavButtonNames.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
selectedNavButtonNames.value = defaultSelectedNavButtonNames.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
navButtonNamesSetting.value = selectedNavButtonNames.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- preview -->
|
||||||
|
<div flex="~ gap4 wrap" items-center select-settings h-14 p0>
|
||||||
|
<nav
|
||||||
|
v-for="availableNavButton in selectedNavButtons" :key="availableNavButton.name"
|
||||||
|
flex="~ 1" items-center justify-center text-xl
|
||||||
|
scrollbar-hide overscroll-none
|
||||||
|
>
|
||||||
|
<button btn-base :class="availableNavButton.icon" mx-4 tabindex="-1" />
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- button selection -->
|
||||||
|
<div flex="~ gap4 wrap" py4>
|
||||||
|
<button
|
||||||
|
v-for="{ name, label, icon } in availableNavButtons"
|
||||||
|
:key="name"
|
||||||
|
btn-text flex="~ gap-2" items-center p2 border="~ base rounded" bg-base ws-nowrap
|
||||||
|
:class="isAdded(name) ? 'text-secondary hover:text-second bg-auto' : ''"
|
||||||
|
type="button"
|
||||||
|
role="switch"
|
||||||
|
:aria-checked="isAdded(name)"
|
||||||
|
@click="isAdded(name) ? remove(name) : append(name)"
|
||||||
|
>
|
||||||
|
<span :class="icon" />
|
||||||
|
{{ label ? $t(label) : 'More menu' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div flex="~ col" gap-y-4 gap-x-2 py-1 sm="~ justify-end flex-row">
|
||||||
|
<button
|
||||||
|
btn-outline font-bold py2 full-w sm-wa flex="~ gap2 center"
|
||||||
|
type="button"
|
||||||
|
:disabled="selectedNavButtonNames.length === 0"
|
||||||
|
@click="clear"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" class="block i-ri:delete-bin-line" />
|
||||||
|
{{ $t('action.clear') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
btn-outline font-bold py2 full-w sm-wa flex="~ gap2 center"
|
||||||
|
type="button"
|
||||||
|
@click="reset"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" class="block i-ri:repeat-line" />
|
||||||
|
{{ $t('action.reset') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
btn-solid font-bold py2 full-w sm-wa flex="~ gap2 center"
|
||||||
|
:disabled="!canSave"
|
||||||
|
@click="save"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" i-ri:save-2-fill />
|
||||||
|
{{ $t('action.save') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -7,6 +7,8 @@ export type OldFontSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
|
||||||
|
|
||||||
export type ColorMode = 'light' | 'dark' | 'system'
|
export type ColorMode = 'light' | 'dark' | 'system'
|
||||||
|
|
||||||
|
export type NavButtonName = 'home' | 'search' | 'notification' | 'mention' | 'explore' | 'local' | 'federated' | 'moreMenu'
|
||||||
|
|
||||||
export interface PreferencesSettings {
|
export interface PreferencesSettings {
|
||||||
hideAltIndicatorOnPosts: boolean
|
hideAltIndicatorOnPosts: boolean
|
||||||
hideGifIndicatorOnPosts: boolean
|
hideGifIndicatorOnPosts: boolean
|
||||||
|
|
|
@ -24,6 +24,7 @@ export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy'
|
||||||
export const STORAGE_KEY_PWA_HIDE_INSTALL = 'elk-pwa-hide-install'
|
export const STORAGE_KEY_PWA_HIDE_INSTALL = 'elk-pwa-hide-install'
|
||||||
export const STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE = 'elk-last-accessed-notification-route'
|
export const STORAGE_KEY_LAST_ACCESSED_NOTIFICATION_ROUTE = 'elk-last-accessed-notification-route'
|
||||||
export const STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE = 'elk-last-accessed-explore-route'
|
export const STORAGE_KEY_LAST_ACCESSED_EXPLORE_ROUTE = 'elk-last-accessed-explore-route'
|
||||||
|
export const STORAGE_KEY_BOTTOM_NAV_BUTTONS = 'elk-bottom-nav-buttons'
|
||||||
|
|
||||||
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
export const HANDLED_MASTO_URLS = /^(https?:\/\/)?([\w\d-]+\.)+\w+\/(@[@\w\d-\.]+)(\/objects)?(\/\d+)?$/
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
"boost": "Boost",
|
"boost": "Boost",
|
||||||
"boost_count": "{0}",
|
"boost_count": "{0}",
|
||||||
"boosted": "Boosted",
|
"boosted": "Boosted",
|
||||||
|
"clear": "Clear",
|
||||||
"clear_publish_failed": "Clear publish errors",
|
"clear_publish_failed": "Clear publish errors",
|
||||||
"clear_save_failed": "Clear save errors",
|
"clear_save_failed": "Clear save errors",
|
||||||
"clear_upload_failed": "Clear file upload errors",
|
"clear_upload_failed": "Clear file upload errors",
|
||||||
|
@ -316,6 +317,7 @@
|
||||||
"list": "List",
|
"list": "List",
|
||||||
"lists": "Lists",
|
"lists": "Lists",
|
||||||
"local": "Local",
|
"local": "Local",
|
||||||
|
"more_menu": "More menu",
|
||||||
"muted_users": "Muted users",
|
"muted_users": "Muted users",
|
||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"privacy": "Privacy",
|
"privacy": "Privacy",
|
||||||
|
@ -450,6 +452,8 @@
|
||||||
"label": "Account settings"
|
"label": "Account settings"
|
||||||
},
|
},
|
||||||
"interface": {
|
"interface": {
|
||||||
|
"bottom_nav": "Bottom Navigation",
|
||||||
|
"bottom_nav_instructions": "Choose your favorite navigation buttons up to five for the bottom navigation. Must include the \"More menu\" button.",
|
||||||
"color_mode": "Color Mode",
|
"color_mode": "Color Mode",
|
||||||
"dark_mode": "Dark",
|
"dark_mode": "Dark",
|
||||||
"default": " (default)",
|
"default": " (default)",
|
||||||
|
|
|
@ -30,6 +30,15 @@ useHydratedHead({
|
||||||
</p>
|
</p>
|
||||||
<SettingsThemeColors />
|
<SettingsThemeColors />
|
||||||
</div>
|
</div>
|
||||||
|
<div space-y-2>
|
||||||
|
<p font-medium>
|
||||||
|
{{ $t('settings.interface.bottom_nav') }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ $t('settings.interface.bottom_nav_instructions') }}
|
||||||
|
</p>
|
||||||
|
<SettingsBottomNav />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MainContent>
|
</MainContent>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in a new issue