Compare commits

...

22 commits

Author SHA1 Message Date
userquin
6ce5eadcac Merge branch 'main' into userquin/feat-remember-last-position 2023-01-09 18:21:41 +01:00
userquin
4ab1de45f4 chore: update entries 2023-01-09 18:21:16 +01:00
userquin
6d2f70b2ba Merge branch 'main' into userquin/feat-remember-last-position 2023-01-09 12:55:43 +01:00
userquin
fcf712ef42 chore: call only once useRoute 2023-01-09 12:55:15 +01:00
userquin
531392f967 Merge branch 'main' into userquin/feat-remember-last-position 2023-01-09 10:55:11 +01:00
userquin
1caa59acbe chore: don't use capture 2023-01-09 01:00:51 +01:00
userquin
a217c59590 Merge branch 'main' into userquin/feat-remember-last-position 2023-01-09 01:00:26 +01:00
userquin
086a2940f7 chore: use account instead follow 2023-01-08 18:47:14 +01:00
userquin
2531bd8f45 chore: use scroll y position on restore (page with tabs not working properly) 2023-01-08 18:43:33 +01:00
userquin
674adb48ba chore: prevent scroll on tabs and side nav 2023-01-08 18:42:06 +01:00
userquin
2303700b63 Merge branch 'main' into userquin/feat-remember-last-position 2023-01-08 17:36:49 +01:00
userquin
be30faaa0f chore: avoid scroll to top when switching tabs 2023-01-08 17:33:19 +01:00
userquin
cd03f3fa1e Merge branch 'main' into userquin/feat-remember-last-position 2023-01-08 16:24:06 +01:00
userquin
1e505711a1 fix: status type 2023-01-08 09:45:12 +01:00
userquin
a2a6b84ba7 chore: include remember position 2023-01-08 09:39:11 +01:00
userquin
7c355eeec7 Merge branch 'main' into userquin/feat-remember-last-position
# Conflicts:
#	components/status/StatusActions.vue
2023-01-08 09:31:24 +01:00
userquin
8e19574ea1 chore: cleanup checks 2023-01-07 19:11:10 +01:00
userquin
c9d70de38e chore: add TODO for slow devices and long lists 2023-01-07 19:10:52 +01:00
userquin
5893cf1a6e chore: include notification card + accounts 2023-01-07 18:30:56 +01:00
userquin
b6eed6b46b chore: include follow and state 2023-01-07 18:30:18 +01:00
userquin
2a30510127 chore: prevent scroll on nav links 2023-01-07 18:29:36 +01:00
userquin
c546408326 feat: restore scroll position 2023-01-07 16:27:09 +01:00
11 changed files with 114 additions and 7 deletions

View file

@ -17,6 +17,7 @@ cacheAccount(account)
shrink shrink
overflow-hidden overflow-hidden
:to="getAccountRoute(account)" :to="getAccountRoute(account)"
@click="$rememberAccountPosition(getAccountRoute(account).fullPath)"
/> />
<div h-full p1 shrink-0> <div h-full p1 shrink-0>
<AccountFollowButton :account="account" /> <AccountFollowButton :account="account" />

View file

@ -15,6 +15,7 @@ const { options, command, replace, preventScrollTop = false } = $defineProps<{
}>() }>()
const router = useRouter() const router = useRouter()
const nuxtApp = useNuxtApp()
useCommands(() => command useCommands(() => command
? options.map(tab => ({ ? options.map(tab => ({
@ -24,7 +25,15 @@ useCommands(() => command
icon: tab.icon ?? 'i-ri:file-list-2-line', icon: tab.icon ?? 'i-ri:file-list-2-line',
onActivate: () => router.replace(tab.to), onActivate: () => router.replace(tab.to),
})) }))
: []) : [],
)
const handleClick = (to: RouteLocationRaw) => {
if (preventScrollTop || nuxtApp.$preventScrollToTop(router.resolve(to).fullPath))
return
nuxtApp.$scrollToTop()
}
</script> </script>
<template> <template>
@ -41,7 +50,7 @@ useCommands(() => command
tabindex="1" tabindex="1"
hover:bg-active transition-100 hover:bg-active transition-100
exact-active-class="children:(text-secondary !border-primary !op100 !text-base)" 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> <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> </NuxtLink>

View file

@ -15,6 +15,7 @@ defineSlots<{
}>() }>()
const router = useRouter() const router = useRouter()
const nuxtApp = useNuxtApp()
useCommand({ useCommand({
scope: 'Navigation', scope: 'Navigation',
@ -41,6 +42,13 @@ onMastoInit(async () => {
// when we know there is no user. // when we know there is no user.
const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value)) const noUserDisable = computed(() => !isMastoInitialised.value || (props.userOnly && !currentUser.value))
const noUserVisual = 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> </script>
<template> <template>
@ -51,7 +59,7 @@ const noUserVisual = computed(() => isMastoInitialised.value && props.userOnly &
:active-class="activeClass" :active-class="activeClass"
group focus:outline-none disabled:pointer-events-none group focus:outline-none disabled:pointer-events-none
:tabindex="noUserDisable ? -1 : null" :tabindex="noUserDisable ? -1 : null"
@click="$scrollToTop" @click="handleClick"
> >
<CommonTooltip :disabled="!isMediumScreen" :content="text" placement="right"> <CommonTooltip :disabled="!isMediumScreen" :content="text" placement="right">
<div <div

View file

@ -9,7 +9,10 @@ const { notification } = defineProps<{
<template> <template>
<article flex flex-col relative> <article flex flex-col relative>
<template v-if="notification.type === 'follow'"> <template v-if="notification.type === 'follow'">
<NuxtLink :to="getAccountRoute(notification.account)"> <NuxtLink
:to="getAccountRoute(notification.account)"
@click="$rememberAccountPosition(getAccountRoute(notification.account).fullPath)"
>
<div <div
flex items-center absolute flex items-center absolute
ps-3 pe-4 inset-is-0 ps-3 pe-4 inset-is-0

View file

@ -7,6 +7,7 @@ const props = defineProps<{
command?: boolean command?: boolean
}>() }>()
const nuxtApp = useNuxtApp()
const focusEditor = inject<typeof noop>('focus-editor', noop) const focusEditor = inject<typeof noop>('focus-editor', noop)
const { details, command } = $(props) const { details, command } = $(props)

View file

@ -42,6 +42,7 @@ const statusRoute = $computed(() => getStatusRoute(status))
const el = ref<HTMLElement>() const el = ref<HTMLElement>()
const router = useRouter() const router = useRouter()
const nuxtApp = useNuxtApp()
function onclick(evt: MouseEvent | KeyboardEvent) { function onclick(evt: MouseEvent | KeyboardEvent) {
const path = evt.composedPath() as HTMLElement[] const path = evt.composedPath() as HTMLElement[]
@ -56,6 +57,7 @@ function go(evt: MouseEvent | KeyboardEvent) {
window.open(statusRoute.href) window.open(statusRoute.href)
} }
else { else {
nuxtApp.$rememberStatusPosition(status)
cacheStatus(status) cacheStatus(status)
router.push(statusRoute) router.push(statusRoute)
} }

View file

@ -1,5 +1,6 @@
import type { Paginator, WsEvents, mastodon } from 'masto' import type { Paginator, WsEvents, mastodon } from 'masto'
import type { PaginatorState } from '~/types' import type { PaginatorState } from '~/types'
import { onReactivated } from '~/composables/vue'
export function usePaginator<T, P, U = T>( export function usePaginator<T, P, U = T>(
paginator: Paginator<T[], P>, paginator: Paginator<T[], P>,
@ -17,7 +18,10 @@ export function usePaginator<T, P, U = T>(
const bound = reactive(useElementBounding(endAnchor)) const bound = reactive(useElementBounding(endAnchor))
const isInScreen = $computed(() => bound.top < window.innerHeight * 2) const isInScreen = $computed(() => bound.top < window.innerHeight * 2)
const error = ref<unknown | undefined>() const error = ref<unknown | undefined>()
const loaded = ref(false)
const deactivated = useDeactivated() const deactivated = useDeactivated()
const nuxtApp = useNuxtApp()
async function update() { async function update() {
(items.value as U[]).unshift(...preprocess(prevItems.value as T[])) (items.value as U[]).unshift(...preprocess(prevItems.value as T[]))
@ -87,13 +91,37 @@ export function usePaginator<T, P, U = T>(
await nextTick() await nextTick()
bound.update() bound.update()
if (!loaded.value) {
loaded.value = true
await nextTick()
nuxtApp.$restoreScrollPosition()
}
} }
if (process.client) { if (process.client) {
const timeout = ref()
useIntervalFn(() => { useIntervalFn(() => {
bound.update() bound.update()
}, 1000) }, 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) { if (!isMastoInitialised.value) {
onMastoInit(() => { onMastoInit(() => {
state.value = 'idle' state.value = 'idle'

View file

@ -1,3 +1,6 @@
import type { mastodon } from 'masto' 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 } })
}

View file

@ -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_HIDE_EXPLORE_TAGS_TIPS = 'elk-hide-explore-tags-tips'
export const STORAGE_KEY_NOTIFICATION = 'elk-notification' export const STORAGE_KEY_NOTIFICATION = 'elk-notification'
export const STORAGE_KEY_NOTIFICATION_POLICY = 'elk-notification-policy' 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 export const COOKIE_MAX_AGE = 10 * 365 * 24 * 60 * 60 * 1000

View 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 }
},
},
}
})

View file

@ -1,7 +1,14 @@
export default defineNuxtPlugin(() => { export default defineNuxtPlugin((nuxt) => {
return { return {
provide: { 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' }) window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
}, },
}, },