diff --git a/src/components/Views/AllAuthors.tsx b/src/components/Views/AllAuthors.tsx index 7d57da95..8194f168 100644 --- a/src/components/Views/AllAuthors.tsx +++ b/src/components/Views/AllAuthors.tsx @@ -1,4 +1,4 @@ -import { createEffect, createSignal, For, onMount, Show } from 'solid-js' +import { createEffect, createSignal, For, Show } from 'solid-js' import type { Author } from '../../graphql/types.gen' import { AuthorCard } from '../Author/Card' import { byFirstChar, sortBy } from '../../utils/sortby' @@ -6,7 +6,7 @@ import { groupByName } from '../../utils/groupby' import Icon from '../Nav/Icon' import { t } from '../../utils/intl' import { useAuthorsStore } from '../../stores/zine/authors' -import { route, by, setBy, SortBy } from '../../stores/router' +import { route, params as paramsStore } from '../../stores/router' import { session } from '../../stores/auth' import { useStore } from '@nanostores/solid' import '../../styles/AllTopics.scss' @@ -18,9 +18,9 @@ export const AllAuthorsPage = (props: any) => { const [abc, setAbc] = createSignal([]) const auth = useStore(session) const subscribed = (s) => Boolean(auth()?.info?.authors && auth()?.info?.authors?.includes(s || '')) - + const params = useStore(paramsStore) createEffect(() => { - if (!by() && abc().length === 0) { + if ((!params()['by'] || params()['by'] === 'abc') && abc().length === 0) { console.log('[authors] default grouping by abc') const grouped = { ...groupByName(authorslist()) } grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar) @@ -29,12 +29,10 @@ export const AllAuthorsPage = (props: any) => { keys.sort() setSortedKeys(keys as string[]) } else { - console.log('[authors] sorting by ' + by()) - setSortedAuthors(sortBy(authorslist(), by())) + console.log('[authors] sorting by ' + params()['by']) + setSortedAuthors(sortBy(authorslist(), params()['by'])) } - }, [by()]) - - onMount(() => setBy('' as SortBy)) + }, [authorslist(), params()]) return (
@@ -50,17 +48,17 @@ export const AllAuthorsPage = (props: any) => {
(
diff --git a/src/components/Views/AllTopics.tsx b/src/components/Views/AllTopics.tsx index f4f96c7a..f76332f9 100644 --- a/src/components/Views/AllTopics.tsx +++ b/src/components/Views/AllTopics.tsx @@ -1,10 +1,10 @@ -import { createEffect, createSignal, For, onMount, Show } from 'solid-js' +import { createEffect, createSignal, For, Show } from 'solid-js' import type { Topic } from '../../graphql/types.gen' import { byFirstChar, sortBy } from '../../utils/sortby' import Icon from '../Nav/Icon' import { t } from '../../utils/intl' import { useTopicsStore } from '../../stores/zine/topics' -import { by, route, setBy } from '../../stores/router' +import { params as paramstore, route } from '../../stores/router' import { TopicCard } from '../Topic/Card' import { session } from '../../stores/auth' import { useStore } from '@nanostores/solid' @@ -18,9 +18,9 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => { const { getSortedTopics: topicslist } = useTopicsStore({ topics: props.topics }) const auth = useStore(session) const subscribed = (s) => Boolean(auth()?.info?.topics && auth()?.info?.topics?.includes(s || '')) - + const params = useStore(paramstore) createEffect(() => { - if (!by() && abc().length === 0) { + if (abc().length === 0 && (!params()['by'] || params()['by'] === 'abc')) { console.log('[topics] default grouping by abc') const grouped = { ...groupByTitle(topicslist()) } grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar) @@ -29,12 +29,12 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => { keys.sort() setSortedKeys(keys as string[]) } else { - console.log('[topics] sorting by ' + by()) - setSortedTopics(sortBy(topicslist(), by())) + console.log('[topics] sorting by ' + params()['by']) + setSortedTopics(sortBy(topicslist(), params()['by'])) } - }, [topicslist(), by()]) + }, [topicslist(), params()]) - onMount(() => setBy('')) + // onMount(() => setBy('')) return ( <> @@ -52,17 +52,17 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
(
diff --git a/src/components/Views/ArticlePage.tsx b/src/components/Views/ArticlePage.tsx index d6b325bc..697bb4de 100644 --- a/src/components/Views/ArticlePage.tsx +++ b/src/components/Views/ArticlePage.tsx @@ -9,8 +9,6 @@ import { slug } from '../../stores/router' import '../../styles/Article.scss' -import '../../styles/Article.scss' - interface ArticlePageProps { article: Shout slug: string diff --git a/src/components/Views/Author.tsx b/src/components/Views/Author.tsx index 4b7e1f36..86f17542 100644 --- a/src/components/Views/Author.tsx +++ b/src/components/Views/Author.tsx @@ -5,11 +5,12 @@ import Row3 from '../Feed/Row3' import AuthorFull from '../Author/Full' import { t } from '../../utils/intl' import { useAuthorsStore } from '../../stores/zine/authors' -import { by, setBy } from '../../stores/router' +import { params as paramsStore } from '../../stores/router' import { useArticlesStore } from '../../stores/zine/articles' import '../../styles/Topic.scss' import Beside from '../Feed/Beside' +import { useStore } from '@nanostores/solid' type AuthorProps = { authorArticles: Shout[] @@ -17,6 +18,7 @@ type AuthorProps = { } export const AuthorPage = (props: AuthorProps) => { + const params = useStore(paramsStore) const { getSortedArticles: articles, getArticlesByAuthors: articlesByAuthors } = useArticlesStore({ sortedArticles: props.authorArticles }) @@ -43,13 +45,17 @@ export const AuthorPage = (props: AuthorProps) => { } }, [articles()]) const title = createMemo(() => { - const m = by() + const m = params()['by'] if (m === 'viewed') return t('Top viewed') if (m === 'rating') return t('Top rated') if (m === 'commented') return t('Top discussed') return t('Top recent') }) + const setBy = (what: string) => { + params()['by'] = what + } + return (
{t('Loading')}
}> @@ -57,22 +63,22 @@ export const AuthorPage = (props: AuthorProps) => {
    -
  • +
  • -
  • +
  • -
  • +
  • -
  • +
  • diff --git a/src/components/Views/FeedSettings.tsx b/src/components/Views/FeedSettings.tsx index 0bf6bcae..aa8077f6 100644 --- a/src/components/Views/FeedSettings.tsx +++ b/src/components/Views/FeedSettings.tsx @@ -1,16 +1,18 @@ import '../../styles/FeedSettings.scss' import { t } from '../../utils/intl' -import { setBy, by } from '../../stores/router' // global routing signals +import { params } from '../../stores/router' // global routing signals +import { useStore } from '@nanostores/solid' export const FeedSettings = (props: any) => { - console.log('[feed-settings] setup articles by', by()) + const args = useStore(params) + console.log('[feed-settings] setup articles by', args()['by']) return (

    {t('Feed settings')}

    • - setBy('topics')}> + (args()['by'] = 'topics')}> {t('topics')}
    • @@ -20,12 +22,12 @@ export const FeedSettings = (props: any) => { */}
    • - setBy('authors')}> + (args()['by'] = 'authors')}> {t('authors')}
    • - setBy('reacted')}> + (args()['by'] = 'reacted')}> {t('reactions')}
    • diff --git a/src/components/Views/Search.tsx b/src/components/Views/Search.tsx index 0a79ba77..1ff18a37 100644 --- a/src/components/Views/Search.tsx +++ b/src/components/Views/Search.tsx @@ -2,16 +2,17 @@ import { Show, For, createSignal, createMemo } from 'solid-js' import '../../styles/Search.scss' import type { Shout } from '../../graphql/types.gen' import { ArticleCard } from '../Feed/Card' -import { sortBy } from '../../utils/sortby' import { t } from '../../utils/intl' -import { by, setBy } from '../../stores/router' +import { params } from '../../stores/router' import { useArticlesStore } from '../../stores/zine/articles' +import { useStore } from '@nanostores/solid' type Props = { results: Shout[] } export const SearchPage = (props: Props) => { + const args = useStore(params) const { getSortedArticles } = useArticlesStore({ sortedArticles: props.results }) // FIXME server sort @@ -62,12 +63,12 @@ export const SearchPage = (props: Props) => {
      • - setBy('relevance')}> + (args()['by'] = 'relevance')}> {t('By relevance')}
      • - setBy('rating')}> + (args()['by'] = 'rating')}> {t('Top rated')}
      • diff --git a/src/components/Views/Topic.tsx b/src/components/Views/Topic.tsx index 02b86802..8315dabb 100644 --- a/src/components/Views/Topic.tsx +++ b/src/components/Views/Topic.tsx @@ -7,9 +7,10 @@ import { ArticleCard } from '../Feed/Card' import '../../styles/Topic.scss' import { FullTopic } from '../Topic/Full' import { t } from '../../utils/intl' -import { by, setBy } from '../../stores/router' +import { params } from '../../stores/router' import { useTopicsStore } from '../../stores/zine/topics' import { useArticlesStore } from '../../stores/zine/articles' +import { useStore } from '@nanostores/solid' interface TopicProps { topic: Topic @@ -17,6 +18,7 @@ interface TopicProps { } export const TopicPage = (props: TopicProps) => { + const args = useStore(params) const { getAuthorsByTopic } = useTopicsStore({ topics: [props.topic] }) const { getSortedArticles: sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles }) const topic = createMemo(() => props.topic) @@ -43,23 +45,23 @@ export const TopicPage = (props: TopicProps) => {
          -
        • -
        • -
        • -
        • -
        • -
        • -
        • -
        • diff --git a/src/stores/auth.ts b/src/stores/auth.ts index 9e5701eb..dc4fd095 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -2,7 +2,51 @@ import { atom, action } from 'nanostores' import type { AuthResult } from '../graphql/types.gen' import { getLogger } from '../utils/logger' import { resetToken, setToken } from '../graphql/privateGraphQLClient' +import { apiClient } from '../utils/apiClient' const log = getLogger('auth-store') -export const session = atom() +export const session = atom() + +export const signIn = action(session, 'signIn', async (store, params) => { + const s = await apiClient.signIn(params) + store.set(s) + setToken(s.token) + log.debug('signed in') +}) + +export const signUp = action(session, 'signUp', async (store, params) => { + const s = await apiClient.signUp(params) + store.set(s) + setToken(s.token) + log.debug('signed up') +}) + +export const signOut = action(session, 'signOut', (store) => { + store.set(null) + resetToken() + log.debug('signed out') +}) + +export const emailChecks = atom<{ [email: string]: boolean }>({}) + +export const signCheck = action(emailChecks, 'signCheck', async (store, params) => { + store.set(await apiClient.signCheck(params)) +}) + +export const resetCode = atom() + +export const signReset = action(resetCode, 'signReset', async (_store, params) => { + await apiClient.signReset(params) // { email } + resetToken() +}) + +export const signResend = action(resetCode, 'signResend', async (_store, params) => { + await apiClient.signResend(params) // { email } +}) + +export const signResetConfirm = action(session, 'signResetConfirm', async (store, params) => { + const auth = await apiClient.signResetConfirm(params) // { code } + setToken(auth.token) + store.set(auth) +}) diff --git a/src/stores/router.ts b/src/stores/router.ts index fee11a7f..982f1b46 100644 --- a/src/stores/router.ts +++ b/src/stores/router.ts @@ -1,5 +1,6 @@ -import { createRouter } from '@nanostores/router' -import { createEffect, createSignal } from 'solid-js' +import { createRouter, createSearchParams } from '@nanostores/router' +import { onMount } from 'nanostores' +import { createEffect, createMemo, createSignal } from 'solid-js' import { isServer } from 'solid-js/web' // Types for :params in route templates @@ -18,6 +19,7 @@ interface Routes { topic: 'slug' } +export const params = createSearchParams() export const router = createRouter( { home: '/', @@ -40,61 +42,18 @@ export const router = createRouter( } ) -router.listen((r) => setResource(r.path)) +const [resource, setResource] = createSignal('') +const slug = createMemo(() => { + const s = resource().split('/').pop() + return (Boolean(s) && router.routes.filter((x) => x[0] === s).length === 0 && s) || '' +}) -// signals - -const [getPage, setPage] = createSignal(1) -const [getSize, setSize] = createSignal(10) - -export type SortBy = - | 'rating' - | 'reacted' - | 'commented' - | 'viewed' - | 'relevance' - | 'topics' - | 'authors' - | 'shouts' - | 'recent' // NOTE: not in Entity.stat - | '' // abc - -const [by, setBy] = createSignal('') -const [slug, setSlug] = createSignal('') -const [resource, setResource] = createSignal(router?.get()?.path || '') - -const isSlug = (s) => - Boolean(s) && // filter binded subroutes - router.routes.filter((x) => x[0] === s).length === 0 - -const encodeParams = (dict) => - Object.entries(dict) - .map((item: [string, string]) => (item[1] ? item[0] + '=' + encodeURIComponent(item[1]) + '&' : '')) - .join('') - .slice(0, -1) - -const scanParams = (href) => { - // FIXME parse query - // console.debug('[router] read url to set store', href) - // return href - // .split('?') - // .pop() - // .split('&') - // .forEach((arg: string) => { - // if (arg.startsWith('by=')) { - // setBy(arg.replace('by=', '')) - // } else if (arg.startsWith('page=')) { - // setPage(Number.parseInt(arg.replace('page=', '') || '0', 10)) - // } else if (arg.startsWith('size=')) setSize(Number.parseInt(arg.replace('size=', '') || '0', 10)) - // }) -} const _route = (ev) => { const href: string = ev.target.href console.log('[router] faster link', href) ev.stopPropoganation() ev.preventDefault() router.open(href) - scanParams(href) } const route = (ev) => { @@ -105,37 +64,13 @@ const route = (ev) => { } } -const updateParams = () => { - // get request search query params - const paramsDict = { - by: by(), // sort name - page: getPage(), // page number - size: getSize() // entries per page - // TODO: add q for /search - } - console.log('[router] updated url with stored params') - return paramsDict -} - -const slugDetect = () => { - const params = encodeParams(updateParams()) - const route = resource() + (params ? '?' + params : '') - router.open(route) // window.pushState wrapper - const s = resource() - .split('/') - .filter((x) => x.length > 0) - .pop() - if (isSlug(s)) { - console.log('[router] detected slug {' + s + '}') - setSlug(s) - } -} - -createEffect(slugDetect, [resource()]) - if (!isServer) { console.log('[router] client runtime') createEffect(() => router.open(window.location.pathname), [window.location]) } -export { slug, route, setPage, getPage, getSize, setSize, by, setBy, resource } +onMount(router, () => { + router.listen((r) => setResource(r.path)) +}) + +export { slug, route, resource } diff --git a/src/stores/zine/articles.ts b/src/stores/zine/articles.ts index d027db06..fa448466 100644 --- a/src/stores/zine/articles.ts +++ b/src/stores/zine/articles.ts @@ -3,7 +3,7 @@ import type { Shout } from '../../graphql/types.gen' import type { WritableAtom } from 'nanostores' import { useStore } from '@nanostores/solid' import { apiClient } from '../../utils/apiClient' -import { getPage, setPage } from '../router' +import { params } from '../router' let articleEntitiesStore: WritableAtom> let sortedArticlesStore: WritableAtom @@ -96,11 +96,13 @@ export const useArticlesStore = ({ sortedArticles }: InitialState) => { } export const loadMoreAll = () => { - setPage(getPage() + 1) - loadRecentAllArticles({ page: getPage() + 1 }) + const searchParams = useStore(params) + const pn = Number.parseInt(searchParams()['page'], 10) + loadRecentAllArticles({ page: pn + 1 }) } export const loadMorePublished = () => { - setPage(getPage() + 1) - loadRecentPublishedArticles({ page: getPage() + 1 }) + const searchParams = useStore(params) + const pn = Number.parseInt(searchParams()['page'], 10) + loadRecentPublishedArticles({ page: pn + 1 }) }