forked from Mirrors/elk
Compare commits
22 commits
main
...
userquin/f
Author | SHA1 | Date | |
---|---|---|---|
|
6ce5eadcac | ||
|
4ab1de45f4 | ||
|
6d2f70b2ba | ||
|
fcf712ef42 | ||
|
531392f967 | ||
|
1caa59acbe | ||
|
a217c59590 | ||
|
086a2940f7 | ||
|
2531bd8f45 | ||
|
674adb48ba | ||
|
2303700b63 | ||
|
be30faaa0f | ||
|
cd03f3fa1e | ||
|
1e505711a1 | ||
|
a2a6b84ba7 | ||
|
7c355eeec7 | ||
|
8e19574ea1 | ||
|
c9d70de38e | ||
|
5893cf1a6e | ||
|
b6eed6b46b | ||
|
2a30510127 | ||
|
c546408326 |
11 changed files with 114 additions and 7 deletions
|
@ -17,6 +17,7 @@ cacheAccount(account)
|
|||
shrink
|
||||
overflow-hidden
|
||||
:to="getAccountRoute(account)"
|
||||
@click="$rememberAccountPosition(getAccountRoute(account).fullPath)"
|
||||
/>
|
||||
<div h-full p1 shrink-0>
|
||||
<AccountFollowButton :account="account" />
|
||||
|
|
|
@ -15,6 +15,7 @@ const { options, command, replace, preventScrollTop = false } = $defineProps<{
|
|||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
useCommands(() => command
|
||||
? options.map(tab => ({
|
||||
|
@ -24,7 +25,15 @@ useCommands(() => command
|
|||
icon: tab.icon ?? 'i-ri:file-list-2-line',
|
||||
onActivate: () => router.replace(tab.to),
|
||||
}))
|
||||
: [])
|
||||
: [],
|
||||
)
|
||||
|
||||
const handleClick = (to: RouteLocationRaw) => {
|
||||
if (preventScrollTop || nuxtApp.$preventScrollToTop(router.resolve(to).fullPath))
|
||||
return
|
||||
|
||||
nuxtApp.$scrollToTop()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -41,7 +50,7 @@ useCommands(() => command
|
|||
tabindex="1"
|
||||
hover:bg-active transition-100
|
||||
exact-active-class="children:(text-secondary !border-primary !op100 !text-base)"
|
||||
@click="!preventScrollTop && $scrollToTop()"
|
||||
@click="handleClick(option.to)"
|
||||
>
|
||||
<span ws-nowrap mxa sm:px2 sm:py3 xl:pb4 xl:pt5 py2 text-center border-b-3 text-secondary-light hover:text-secondary border-transparent>{{ option.display }}</span>
|
||||
</NuxtLink>
|
||||
|
|
|
@ -15,6 +15,7 @@ defineSlots<{
|
|||
}>()
|
||||
|
||||
const router = useRouter()
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
useCommand({
|
||||
scope: 'Navigation',
|
||||
|
@ -41,6 +42,13 @@ onMastoInit(async () => {
|
|||
// when we know there is no user.
|
||||
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value))
|
||||
const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly && !currentUser.value)
|
||||
|
||||
const handleClick = () => {
|
||||
if (nuxtApp.$preventScrollToTop(router.resolve(props.to).fullPath))
|
||||
return
|
||||
|
||||
nuxtApp.$scrollToTop()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -51,7 +59,7 @@ const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly &
|
|||
:active-class="activeClass"
|
||||
group focus:outline-none disabled:pointer-events-none
|
||||
:tabindex="noUserDisable ? -1 : null"
|
||||
@click="$scrollToTop"
|
||||
@click="handleClick"
|
||||
>
|
||||
<CommonTooltip :disabled="!isMediumScreen" :content="text" placement="right">
|
||||
<div
|
||||
|
|
|
@ -9,7 +9,10 @@ const { notification } = defineProps<{
|
|||
<template>
|
||||
<article flex flex-col relative>
|
||||
<template v-if="notification.type === 'follow'">
|
||||
<NuxtLink :to="getAccountRoute(notification.account)">
|
||||
<NuxtLink
|
||||
:to="getAccountRoute(notification.account)"
|
||||
@click="$rememberAccountPosition(getAccountRoute(notification.account).fullPath)"
|
||||
>
|
||||
<div
|
||||
flex items-center absolute
|
||||
ps-3 pe-4 inset-is-0
|
||||
|
|
|
@ -7,6 +7,7 @@ const props = defineProps<{
|
|||
command?: boolean
|
||||
}>()
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
const focusEditor = inject<typeof noop>('focus-editor', noop)
|
||||
|
||||
const { details, command } = $(props)
|
||||
|
|
|
@ -42,6 +42,7 @@ const statusRoute = $computed(() => getStatusRoute(status))
|
|||
|
||||
const el = ref<HTMLElement>()
|
||||
const router = useRouter()
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
function onclick(evt: MouseEvent | KeyboardEvent) {
|
||||
const path = evt.composedPath() as HTMLElement[]
|
||||
|
@ -56,6 +57,7 @@ function go(evt: MouseEvent | KeyboardEvent) {
|
|||
window.open(statusRoute.href)
|
||||
}
|
||||
else {
|
||||
nuxtApp.$rememberStatusPosition(status)
|
||||
cacheStatus(status)
|
||||
router.push(statusRoute)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { Paginator, WsEvents, mastodon } from 'masto'
|
||||
import type { PaginatorState } from '~/types'
|
||||
import { onReactivated } from '~/composables/vue'
|
||||
|
||||
export function usePaginator<T, P, U = T>(
|
||||
paginator: Paginator<T[], P>,
|
||||
|
@ -17,7 +18,10 @@ export function usePaginator<T, P, U = T>(
|
|||
const bound = reactive(useElementBounding(endAnchor))
|
||||
const isInScreen = $computed(() => bound.top < window.innerHeight * 2)
|
||||
const error = ref<unknown | undefined>()
|
||||
const loaded = ref(false)
|
||||
|
||||
const deactivated = useDeactivated()
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
async function update() {
|
||||
(items.value as U[]).unshift(...preprocess(prevItems.value as T[]))
|
||||
|
@ -87,13 +91,37 @@ export function usePaginator<T, P, U = T>(
|
|||
|
||||
await nextTick()
|
||||
bound.update()
|
||||
if (!loaded.value) {
|
||||
loaded.value = true
|
||||
await nextTick()
|
||||
nuxtApp.$restoreScrollPosition()
|
||||
}
|
||||
}
|
||||
|
||||
if (process.client) {
|
||||
const timeout = ref()
|
||||
useIntervalFn(() => {
|
||||
bound.update()
|
||||
}, 1000)
|
||||
|
||||
onDeactivated(() => {
|
||||
window.clearTimeout(timeout.value)
|
||||
loaded.value = false
|
||||
})
|
||||
onReactivated(() => {
|
||||
window.clearTimeout(timeout.value)
|
||||
if (isMastoInitialised.value) {
|
||||
if (!loaded.value)
|
||||
loaded.value = true
|
||||
|
||||
// TODO: apply timeout based in items length: be conservative for long lists on slow devices
|
||||
timeout.value = setTimeout(() => nuxtApp.$restoreScrollPosition(), 600)
|
||||
}
|
||||
else {
|
||||
loaded.value = false
|
||||
}
|
||||
})
|
||||
|
||||
if (!isMastoInitialised.value) {
|
||||
onMastoInit(() => {
|
||||
state.value = 'idle'
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import type { mastodon } from 'masto'
|
||||
|
||||
export const navigateToStatus = ({ status, focusReply = false }: { status: mastodon.v1.Status; focusReply?: boolean }) => navigateTo({ path: getStatusRoute(status).href, state: { focusReply } })
|
||||
export const navigateToStatus = ({ status, focusReply = false }: { status: mastodon.v1.Status; focusReply?: boolean }) => {
|
||||
useNuxtApp().$rememberStatusPosition(status)
|
||||
navigateTo({ path: getStatusRoute(status).href, state: { focusReply } })
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const STORAGE_KEY_HIDE_EXPLORE_NEWS_TIPS = 'elk-hide-explore-news-tips'
|
|||
export const STORAGE_KEY_HIDE_EXPLORE_TAGS_TIPS = 'elk-hide-explore-tags-tips'
|
||||
export const STORAGE_KEY_NOTIFICATION = 'elk-notification'
|
||||
export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy'
|
||||
export const STORAGE_KEY_LAST_SCROLL_POSITION = 'elk-last-scroll-position'
|
||||
|
||||
export const COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60 * 1000
|
||||
|
||||
|
|
44
plugins/remember-scroll-position.client.ts
Normal file
44
plugins/remember-scroll-position.client.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import type { mastodon } from 'masto'
|
||||
|
||||
import { STORAGE_KEY_LAST_SCROLL_POSITION } from '~/constants'
|
||||
|
||||
interface RestoreScroll {
|
||||
id: string
|
||||
type: 'status' | 'account'
|
||||
position: number
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const lastStatus = useSessionStorage<Record<string, RestoreScroll>>(STORAGE_KEY_LAST_SCROLL_POSITION, {})
|
||||
return {
|
||||
provide: {
|
||||
preventScrollToTop: (path: string) => {
|
||||
return !!lastStatus.value[path]
|
||||
},
|
||||
restoreScrollPosition: () => {
|
||||
const fullPath = useRoute().fullPath
|
||||
const restore = lastStatus.value[fullPath]
|
||||
if (restore) {
|
||||
const el = restore.type === 'status'
|
||||
? document.getElementById(`status-${restore.id}`)
|
||||
: document.querySelector(`a[href="${restore.id}"]`)
|
||||
if (el) {
|
||||
if (typeof restore.position === 'undefined')
|
||||
el.scrollIntoView()
|
||||
else
|
||||
window.scrollTo(0, restore.position)
|
||||
}
|
||||
else {
|
||||
delete lastStatus.value[fullPath]
|
||||
}
|
||||
}
|
||||
},
|
||||
rememberAccountPosition: (account: string) => {
|
||||
lastStatus.value[useRoute().fullPath] = { id: account, type: 'account', position: window.scrollY }
|
||||
},
|
||||
rememberStatusPosition: (status: mastodon.v1.Status) => {
|
||||
lastStatus.value[useRoute().fullPath] = { id: status.id, type: 'status', position: window.scrollY }
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
|
@ -1,7 +1,14 @@
|
|||
export default defineNuxtPlugin(() => {
|
||||
export default defineNuxtPlugin((nuxt) => {
|
||||
return {
|
||||
provide: {
|
||||
scrollToTop: () => {
|
||||
scrollToTop: (evt?: MouseEvent | KeyboardEvent) => {
|
||||
const path = evt?.composedPath?.() as HTMLElement[]
|
||||
const el = path?.find(el => el.tagName?.toUpperCase() === 'A') as HTMLAnchorElement
|
||||
if (el?.href) {
|
||||
if (nuxt.$preventScrollToTop(new URL(el.href, import.meta.url).pathname))
|
||||
return
|
||||
}
|
||||
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue