diff --git a/src/components/Article/CommentsTree.tsx b/src/components/Article/CommentsTree.tsx index f5e6e1be..2ef2cf52 100644 --- a/src/components/Article/CommentsTree.tsx +++ b/src/components/Article/CommentsTree.tsx @@ -10,7 +10,6 @@ import { useReactions } from '../../context/reactions' import { byCreated } from '../../utils/sortby' import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated' import { useLocalize } from '../../context/localize' -import Cookie from 'js-cookie' type CommentsOrder = 'createdAt' | 'rating' | 'newOnly' @@ -110,7 +109,7 @@ export const CommentsTree = (props: Props) => { return ( <>
-

+

{t('Comments')} {comments().length.toString() || ''} 0}>  +{newReactions().length} @@ -166,7 +165,7 @@ export const CommentsTree = (props: Props) => { +
{t('To write a comment, you must')}{' '} {t('sign up')} diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index b1fe6a7d..6600c160 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -1,7 +1,7 @@ import { capitalize, formatDate } from '../../utils' import { Icon } from '../_shared/Icon' import { AuthorCard } from '../Author/Card' -import { createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js' +import { createEffect, createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js' import type { Author, Shout } from '../../graphql/types.gen' import MD from './MD' import { SharePopup } from './SharePopup' @@ -13,7 +13,7 @@ import { useSession } from '../../context/session' import VideoPlayer from './VideoPlayer' import Slider from '../_shared/Slider' import { getPagePath } from '@nanostores/router' -import { router } from '../../stores/router' +import { router, useRouter } from '../../stores/router' import { useReactions } from '../../context/reactions' import { Title } from '@solidjs/meta' import { useLocalize } from '../../context/localize' @@ -65,19 +65,6 @@ export const FullArticle = (props: ArticleProps) => { props.article.topics[0] ) - onMount(() => { - const windowHash = window.location.hash - if (windowHash?.length > 0) { - const comments = document.querySelector(windowHash) - if (comments) { - window.scrollTo({ - top: comments.getBoundingClientRect().top, - behavior: 'smooth' - }) - } - } - }) - onMount(async () => { await loadReactionsBy({ by: { shout: props.article.slug } @@ -104,14 +91,6 @@ export const FullArticle = (props: ArticleProps) => { actions: { loadReactionsBy } } = useReactions() - let commentsRef: HTMLDivElement | undefined - const scrollToComments = () => { - if (!isReactionsLoaded()) { - return - } - commentsRef.scrollIntoView({ behavior: 'smooth' }) - } - return ( <> {props.article.title} @@ -205,12 +184,10 @@ export const FullArticle = (props: ArticleProps) => {
-
scrollToComments()}> -
- - {/*{props.article.stat?.commented || ''}*/} -
-
+
+ + {props.article.stat?.commented ?? ''} +
{ )}
-
+
{
-
{formattedDate()}
@@ -173,7 +172,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
- + {stat?.commented || t('Add comment')} diff --git a/src/components/Nav/ProfilePopup.tsx b/src/components/Nav/ProfilePopup.tsx index ea154d8b..7eed9739 100644 --- a/src/components/Nav/ProfilePopup.tsx +++ b/src/components/Nav/ProfilePopup.tsx @@ -29,7 +29,9 @@ export const ProfilePopup = (props: ProfilePopupProps) => { {t('Subscriptions')}
  • - {t('Comments')} + + {t('Comments')} +
  • {t('Bookmarks')} diff --git a/src/components/Views/Author.tsx b/src/components/Views/Author.tsx index e24f7139..15d2511e 100644 --- a/src/components/Views/Author.tsx +++ b/src/components/Views/Author.tsx @@ -26,7 +26,7 @@ type AuthorProps = { authorSlug: string } -type AuthorPageSearchParams = { +export type AuthorPageSearchParams = { by: '' | 'viewed' | 'rating' | 'commented' | 'recent' | 'followed' | 'about' | 'popular' } @@ -54,7 +54,11 @@ export const AuthorView = (props: AuthorProps) => { const { searchParams, changeSearchParam } = useRouter() - changeSearchParam('by', 'rating') + onMount(() => { + if (!searchParams().by) { + changeSearchParam('by', 'rating') + } + }) const loadMore = async () => { saveScrollPosition() @@ -131,7 +135,7 @@ export const AuthorView = (props: AuthorProps) => { */}
  • diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index e2717b6a..215c1e13 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -26,7 +26,7 @@ export const SearchView = (props: Props) => { const [query, setQuery] = createSignal(props.query) const [offset, setOffset] = createSignal(0) - const { searchParams, handleClientRouteLinkClick } = useRouter() + const { searchParams } = useRouter() let searchEl: HTMLInputElement const handleQueryChange = (_ev) => { setQuery(searchEl.value) @@ -72,18 +72,14 @@ export const SearchView = (props: Props) => { selected: searchParams().by === 'relevance' }} > - - {t('By relevance')} - + {t('By relevance')}
  • - - {t('Top rated')} - + {t('Top rated')}
  • diff --git a/src/graphql/query/articles-load-by.ts b/src/graphql/query/articles-load-by.ts index 4baad70a..b7929c2c 100644 --- a/src/graphql/query/articles-load-by.ts +++ b/src/graphql/query/articles-load-by.ts @@ -37,6 +37,7 @@ export default gql` viewed reacted rating + commented } } } diff --git a/src/pages/article.page.tsx b/src/pages/article.page.tsx index 8378bf37..bece4351 100644 --- a/src/pages/article.page.tsx +++ b/src/pages/article.page.tsx @@ -7,6 +7,7 @@ import { useRouter } from '../stores/router' import { Loading } from '../components/_shared/Loading' import { ReactionsProvider } from '../context/reactions' import { FullArticle } from '../components/Article/FullArticle' +import { setPageLoadManagerPromise } from '../utils/pageLoadManager' export const ArticlePage = (props: PageProps) => { const shouts = props.article ? [props.article] : [] @@ -33,7 +34,9 @@ export const ArticlePage = (props: PageProps) => { const articleValue = articleEntities()[slug()] if (!articleValue || !articleValue.body) { - await loadShout(slug()) + const loadShoutPromise = loadShout(slug()) + setPageLoadManagerPromise(loadShoutPromise) + await loadShoutPromise } }) diff --git a/src/stores/router.ts b/src/stores/router.ts index 2904b23a..94e8789f 100644 --- a/src/stores/router.ts +++ b/src/stores/router.ts @@ -2,6 +2,7 @@ import type { Accessor } from 'solid-js' import { createRouter, createSearchParams } from '@nanostores/router' import { isServer } from 'solid-js/web' import { useStore } from '@nanostores/solid' +import { getPageLoadManagerPromise } from '../utils/pageLoadManager' export const ROUTES = { home: '/', @@ -40,9 +41,8 @@ const routerStore = createRouter(ROUTES, { export const router = routerStore -const handleClientRouteLinkClick = (event) => { - const link = event.target.closest('a') - if ( +const checkOpenOnClient = (link: HTMLAnchorElement, event) => { + return ( link && event.button === 0 && link.target !== '_blank' && @@ -52,43 +52,84 @@ const handleClientRouteLinkClick = (event) => { !event.ctrlKey && !event.shiftKey && !event.altKey - ) { - const url = new URL(link.href) - if (url.origin === location.origin) { - event.preventDefault() + ) +} - if (url.hash) { - let selector = url.hash +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('#', '')}"]` - } + 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) - const headerOffset = 80 // 80px for header - const elementPosition = anchor ? anchor.getBoundingClientRect().top : 0 - const newScrollTop = elementPosition + window.scrollY - headerOffset + const anchor = document.querySelector(selector) + 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' - }) + window.scrollTo({ + top: newScrollTop, + behavior: 'smooth' + }) +} - return - } +const handleClientRouteLinkClick = async (event) => { + const link = event.target.closest('a') - routerStore.open(url.pathname) - const params = Object.fromEntries(new URLSearchParams(url.search)) - searchParamsStore.open(params) + if (!checkOpenOnClient(link, event)) { + return + } - window.scrollTo({ - top: 0, - left: 0 - }) + const url = new URL(link.href) + if (url.origin !== location.origin) { + return + } + + event.preventDefault() + + 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 + } + + await getPageLoadManagerPromise() + + const images = document.querySelectorAll('img') + + let imagesLoaded = 0 + + const imageLoadEventHandler = () => { + imagesLoaded++ + if (imagesLoaded === images.length) { + scrollToHash(url.hash) + images.forEach((image) => image.removeEventListener('load', imageLoadEventHandler)) + images.forEach((image) => image.removeEventListener('error', imageLoadEventHandler)) } } + + images.forEach((image) => { + if (image.complete) { + imagesLoaded++ + } + + image.addEventListener('load', imageLoadEventHandler) + image.addEventListener('error', imageLoadEventHandler) + }) } export const initRouter = (pathname: string, search: Record) => { @@ -129,7 +170,6 @@ export const useRouter = = Record< return { page, searchParams, - changeSearchParam, - handleClientRouteLinkClick + changeSearchParam } } diff --git a/src/utils/pageLoadManager.ts b/src/utils/pageLoadManager.ts new file mode 100644 index 00000000..d04a3d3d --- /dev/null +++ b/src/utils/pageLoadManager.ts @@ -0,0 +1,11 @@ +const pageLoadManager: { + promise: Promise +} = { promise: Promise.resolve() } + +export const getPageLoadManagerPromise = () => { + return pageLoadManager.promise +} + +export const setPageLoadManagerPromise = (promise: Promise) => { + pageLoadManager.promise = promise +}