webapp/src/stores/router.ts

156 lines
3.8 KiB
TypeScript
Raw Normal View History

2022-09-22 09:37:49 +00:00
import type { Accessor } from 'solid-js'
2022-09-09 13:30:25 +00:00
import { createRouter, createSearchParams } from '@nanostores/router'
2022-09-09 11:53:35 +00:00
import { isServer } from 'solid-js/web'
2022-09-22 09:37:49 +00:00
import { useStore } from '@nanostores/solid'
import { hydrationPromise } from '../utils/hydrationPromise'
2022-09-09 11:53:35 +00:00
2023-02-10 01:19:20 +00:00
export const ROUTES = {
home: '/',
inbox: '/inbox',
connect: '/connect',
create: '/create',
2023-05-08 17:21:06 +00:00
edit: '/edit/:shoutId',
editSettings: '/edit/:shoutId/settings',
2023-04-21 15:10:23 +00:00
drafts: '/drafts',
2023-02-10 01:19:20 +00:00
topics: '/topics',
topic: '/topic/:slug',
authors: '/authors',
author: '/author/:slug',
feed: '/feed',
feedMy: '/feed/my',
feedNotifications: '/feed/notifications',
feedDiscussions: '/feed/discussions',
feedBookmarks: '/feed/bookmarks',
feedCollaborations: '/feed/collaborations',
2023-02-10 01:19:20 +00:00
search: '/search/:q?',
dogma: '/about/dogma',
discussionRules: '/about/discussion-rules',
guide: '/about/guide',
help: '/about/help',
manifest: '/about/manifest',
partners: '/about/partners',
principles: '/about/principles',
projects: '/about/projects',
termsOfUse: '/about/terms-of-use',
thanks: '/about/thanks',
expo: '/expo/:layout',
profileSettings: '/profile/settings',
profileSecurity: '/profile/security',
profileSubscriptions: '/profile/subscriptions',
fourOuFour: '/404',
article: '/:slug'
2023-02-10 01:19:20 +00:00
} as const
2022-09-09 11:53:35 +00:00
2022-09-22 09:37:49 +00:00
const searchParamsStore = createSearchParams()
2023-02-10 01:19:20 +00:00
const routerStore = createRouter(ROUTES, {
search: false,
links: false
})
2022-09-09 11:53:35 +00:00
2022-09-22 09:37:49 +00:00
export const router = routerStore
const checkOpenOnClient = (link: HTMLAnchorElement, event) => {
return (
2022-09-22 09:37:49 +00:00
link &&
event.button === 0 &&
link.target !== '_blank' &&
link.rel !== 'external' &&
!link.download &&
!event.metaKey &&
!event.ctrlKey &&
!event.shiftKey &&
!event.altKey
)
}
const scrollToHash = (hash: string) => {
let selector = hash
if (/^#\d+/.test(selector)) {
// id="1" fix
// https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers
selector = `[id="${selector.replace('#', '')}"]`
}
const anchor = document.querySelector(selector)
2023-04-03 15:23:02 +00:00
const headerOffset = 80 // 80px for header
const elementPosition = anchor ? anchor.getBoundingClientRect().top : 0
const newScrollTop = elementPosition + window.scrollY - headerOffset
window.scrollTo({
top: newScrollTop,
behavior: 'smooth'
})
}
const handleClientRouteLinkClick = async (event) => {
const link = event.target.closest('a')
if (!checkOpenOnClient(link, event)) {
return
}
const url = new URL(link.href)
if (url.origin !== location.origin) {
return
}
event.preventDefault()
await hydrationPromise
if (url.pathname) {
routerStore.open(url.pathname)
}
if (url.search) {
const params = Object.fromEntries(new URLSearchParams(url.search))
searchParamsStore.open(params)
}
if (!url.hash) {
window.scrollTo({
top: 0,
left: 0
})
return
2022-09-22 09:37:49 +00:00
}
2023-04-20 14:01:15 +00:00
scrollToHash(url.hash)
2022-09-22 09:37:49 +00:00
}
export const initRouter = (pathname: string, search?: Record<string, string>) => {
2022-09-22 09:37:49 +00:00
routerStore.open(pathname)
const params = Object.fromEntries(new URLSearchParams(search))
searchParamsStore.open(params)
if (!isServer) {
document.addEventListener('click', handleClientRouteLinkClick)
}
2022-09-09 11:53:35 +00:00
}
2022-09-22 09:37:49 +00:00
export const useRouter = <TSearchParams extends Record<string, string> = Record<string, string>>() => {
2022-10-25 16:25:42 +00:00
const page = useStore(routerStore)
const searchParams = useStore(searchParamsStore) as unknown as Accessor<TSearchParams>
const changeSearchParam = <TKey extends keyof TSearchParams>(
key: TKey,
value: TSearchParams[TKey],
replace = false
) => {
const newSearchParams = { ...searchParamsStore.get() }
if (value === null) {
delete newSearchParams[key.toString()]
} else {
newSearchParams[key.toString()] = value
}
2022-09-22 09:37:49 +00:00
2022-10-25 16:25:42 +00:00
searchParamsStore.open(newSearchParams, replace)
2022-09-22 09:37:49 +00:00
}
return {
2022-10-25 16:25:42 +00:00
page,
searchParams,
changeSearchParam
2022-09-22 09:37:49 +00:00
}
2022-09-09 11:53:35 +00:00
}