router with createSearchParams store
This commit is contained in:
parent
4c59293678
commit
37bfaec46e
|
@ -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 type { Author } from '../../graphql/types.gen'
|
||||||
import { AuthorCard } from '../Author/Card'
|
import { AuthorCard } from '../Author/Card'
|
||||||
import { byFirstChar, sortBy } from '../../utils/sortby'
|
import { byFirstChar, sortBy } from '../../utils/sortby'
|
||||||
|
@ -6,7 +6,7 @@ import { groupByName } from '../../utils/groupby'
|
||||||
import Icon from '../Nav/Icon'
|
import Icon from '../Nav/Icon'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
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 { session } from '../../stores/auth'
|
||||||
import { useStore } from '@nanostores/solid'
|
import { useStore } from '@nanostores/solid'
|
||||||
import '../../styles/AllTopics.scss'
|
import '../../styles/AllTopics.scss'
|
||||||
|
@ -18,9 +18,9 @@ export const AllAuthorsPage = (props: any) => {
|
||||||
const [abc, setAbc] = createSignal([])
|
const [abc, setAbc] = createSignal([])
|
||||||
const auth = useStore(session)
|
const auth = useStore(session)
|
||||||
const subscribed = (s) => Boolean(auth()?.info?.authors && auth()?.info?.authors?.includes(s || ''))
|
const subscribed = (s) => Boolean(auth()?.info?.authors && auth()?.info?.authors?.includes(s || ''))
|
||||||
|
const params = useStore(paramsStore)
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!by() && abc().length === 0) {
|
if ((!params()['by'] || params()['by'] === 'abc') && abc().length === 0) {
|
||||||
console.log('[authors] default grouping by abc')
|
console.log('[authors] default grouping by abc')
|
||||||
const grouped = { ...groupByName(authorslist()) }
|
const grouped = { ...groupByName(authorslist()) }
|
||||||
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
||||||
|
@ -29,12 +29,10 @@ export const AllAuthorsPage = (props: any) => {
|
||||||
keys.sort()
|
keys.sort()
|
||||||
setSortedKeys(keys as string[])
|
setSortedKeys(keys as string[])
|
||||||
} else {
|
} else {
|
||||||
console.log('[authors] sorting by ' + by())
|
console.log('[authors] sorting by ' + params()['by'])
|
||||||
setSortedAuthors(sortBy(authorslist(), by()))
|
setSortedAuthors(sortBy(authorslist(), params()['by']))
|
||||||
}
|
}
|
||||||
}, [by()])
|
}, [authorslist(), params()])
|
||||||
|
|
||||||
onMount(() => setBy('' as SortBy))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="all-topics-page">
|
<div class="all-topics-page">
|
||||||
|
@ -50,17 +48,17 @@ export const AllAuthorsPage = (props: any) => {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li classList={{ selected: by() === 'shouts' }}>
|
<li classList={{ selected: params()['by'] === 'shouts' }}>
|
||||||
<a href="/authors?by=shouts" onClick={route}>
|
<a href="/authors?by=shouts" onClick={route}>
|
||||||
{t('By shouts')}
|
{t('By shouts')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'rating' }}>
|
<li classList={{ selected: params()['by'] === 'rating' }}>
|
||||||
<a href="/authors?by=rating" onClick={route}>
|
<a href="/authors?by=rating" onClick={route}>
|
||||||
{t('By rating')}
|
{t('By rating')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: !by() }}>
|
<li classList={{ selected: !params()['by'] || params()['by'] === 'abc' }}>
|
||||||
<a href="/authors" onClick={route}>
|
<a href="/authors" onClick={route}>
|
||||||
{t('By alphabet')}
|
{t('By alphabet')}
|
||||||
</a>
|
</a>
|
||||||
|
@ -73,7 +71,7 @@ export const AllAuthorsPage = (props: any) => {
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<Show
|
<Show
|
||||||
when={!by()}
|
when={!params()['by'] || params()['by'] === 'abc'}
|
||||||
fallback={() => (
|
fallback={() => (
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<For each={sortedAuthors()}>
|
<For each={sortedAuthors()}>
|
||||||
|
|
|
@ -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 type { Topic } from '../../graphql/types.gen'
|
||||||
import { byFirstChar, sortBy } from '../../utils/sortby'
|
import { byFirstChar, sortBy } from '../../utils/sortby'
|
||||||
import Icon from '../Nav/Icon'
|
import Icon from '../Nav/Icon'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { useTopicsStore } from '../../stores/zine/topics'
|
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 { TopicCard } from '../Topic/Card'
|
||||||
import { session } from '../../stores/auth'
|
import { session } from '../../stores/auth'
|
||||||
import { useStore } from '@nanostores/solid'
|
import { useStore } from '@nanostores/solid'
|
||||||
|
@ -18,9 +18,9 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
||||||
const { getSortedTopics: topicslist } = useTopicsStore({ topics: props.topics })
|
const { getSortedTopics: topicslist } = useTopicsStore({ topics: props.topics })
|
||||||
const auth = useStore(session)
|
const auth = useStore(session)
|
||||||
const subscribed = (s) => Boolean(auth()?.info?.topics && auth()?.info?.topics?.includes(s || ''))
|
const subscribed = (s) => Boolean(auth()?.info?.topics && auth()?.info?.topics?.includes(s || ''))
|
||||||
|
const params = useStore(paramstore)
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!by() && abc().length === 0) {
|
if (abc().length === 0 && (!params()['by'] || params()['by'] === 'abc')) {
|
||||||
console.log('[topics] default grouping by abc')
|
console.log('[topics] default grouping by abc')
|
||||||
const grouped = { ...groupByTitle(topicslist()) }
|
const grouped = { ...groupByTitle(topicslist()) }
|
||||||
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
||||||
|
@ -29,12 +29,12 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
||||||
keys.sort()
|
keys.sort()
|
||||||
setSortedKeys(keys as string[])
|
setSortedKeys(keys as string[])
|
||||||
} else {
|
} else {
|
||||||
console.log('[topics] sorting by ' + by())
|
console.log('[topics] sorting by ' + params()['by'])
|
||||||
setSortedTopics(sortBy(topicslist(), by()))
|
setSortedTopics(sortBy(topicslist(), params()['by']))
|
||||||
}
|
}
|
||||||
}, [topicslist(), by()])
|
}, [topicslist(), params()])
|
||||||
|
|
||||||
onMount(() => setBy(''))
|
// onMount(() => setBy(''))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -52,17 +52,17 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li classList={{ selected: by() === 'shouts' }}>
|
<li classList={{ selected: params()['by'] === 'shouts' }}>
|
||||||
<a href="/topics?by=shouts" onClick={route}>
|
<a href="/topics?by=shouts" onClick={route}>
|
||||||
{t('By shouts')}
|
{t('By shouts')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'authors' }}>
|
<li classList={{ selected: params()['by'] === 'authors' }}>
|
||||||
<a href="/topics?by=authors" onClick={route}>
|
<a href="/topics?by=authors" onClick={route}>
|
||||||
{t('By authors')}
|
{t('By authors')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: !by() }}>
|
<li classList={{ selected: params()['by'] === 'abc' }}>
|
||||||
<a href="/topics" onClick={route}>
|
<a href="/topics" onClick={route}>
|
||||||
{t('By alphabet')}
|
{t('By alphabet')}
|
||||||
</a>
|
</a>
|
||||||
|
@ -76,7 +76,7 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<Show
|
<Show
|
||||||
when={!by()}
|
when={params()['by'] === 'abc'}
|
||||||
fallback={() => (
|
fallback={() => (
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<For each={sortedTopics()}>
|
<For each={sortedTopics()}>
|
||||||
|
|
|
@ -9,8 +9,6 @@ import { slug } from '../../stores/router'
|
||||||
|
|
||||||
import '../../styles/Article.scss'
|
import '../../styles/Article.scss'
|
||||||
|
|
||||||
import '../../styles/Article.scss'
|
|
||||||
|
|
||||||
interface ArticlePageProps {
|
interface ArticlePageProps {
|
||||||
article: Shout
|
article: Shout
|
||||||
slug: string
|
slug: string
|
||||||
|
|
|
@ -5,11 +5,12 @@ import Row3 from '../Feed/Row3'
|
||||||
import AuthorFull from '../Author/Full'
|
import AuthorFull from '../Author/Full'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
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 { useArticlesStore } from '../../stores/zine/articles'
|
||||||
|
|
||||||
import '../../styles/Topic.scss'
|
import '../../styles/Topic.scss'
|
||||||
import Beside from '../Feed/Beside'
|
import Beside from '../Feed/Beside'
|
||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
|
||||||
type AuthorProps = {
|
type AuthorProps = {
|
||||||
authorArticles: Shout[]
|
authorArticles: Shout[]
|
||||||
|
@ -17,6 +18,7 @@ type AuthorProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthorPage = (props: AuthorProps) => {
|
export const AuthorPage = (props: AuthorProps) => {
|
||||||
|
const params = useStore(paramsStore)
|
||||||
const { getSortedArticles: articles, getArticlesByAuthors: articlesByAuthors } = useArticlesStore({
|
const { getSortedArticles: articles, getArticlesByAuthors: articlesByAuthors } = useArticlesStore({
|
||||||
sortedArticles: props.authorArticles
|
sortedArticles: props.authorArticles
|
||||||
})
|
})
|
||||||
|
@ -43,13 +45,17 @@ export const AuthorPage = (props: AuthorProps) => {
|
||||||
}
|
}
|
||||||
}, [articles()])
|
}, [articles()])
|
||||||
const title = createMemo(() => {
|
const title = createMemo(() => {
|
||||||
const m = by()
|
const m = params()['by']
|
||||||
if (m === 'viewed') return t('Top viewed')
|
if (m === 'viewed') return t('Top viewed')
|
||||||
if (m === 'rating') return t('Top rated')
|
if (m === 'rating') return t('Top rated')
|
||||||
if (m === 'commented') return t('Top discussed')
|
if (m === 'commented') return t('Top discussed')
|
||||||
return t('Top recent')
|
return t('Top recent')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const setBy = (what: string) => {
|
||||||
|
params()['by'] = what
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="container author-page">
|
<div class="container author-page">
|
||||||
<Show when={author()} fallback={<div class="center">{t('Loading')}</div>}>
|
<Show when={author()} fallback={<div class="center">{t('Loading')}</div>}>
|
||||||
|
@ -57,22 +63,22 @@ export const AuthorPage = (props: AuthorProps) => {
|
||||||
<div class="row group__controls">
|
<div class="row group__controls">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li classList={{ selected: by() === '' }}>
|
<li classList={{ selected: !params()['by'] || params()['by'] === 'recent' }}>
|
||||||
<button type="button" onClick={() => setBy('')}>
|
<button type="button" onClick={() => setBy('')}>
|
||||||
{t('Recent')}
|
{t('Recent')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'rating' }}>
|
<li classList={{ selected: params()['by'] === 'rating' }}>
|
||||||
<button type="button" onClick={() => setBy('rating')}>
|
<button type="button" onClick={() => setBy('rating')}>
|
||||||
{t('Popular')}
|
{t('Popular')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'viewed' }}>
|
<li classList={{ selected: params()['by'] === 'viewed' }}>
|
||||||
<button type="button" onClick={() => setBy('viewed')}>
|
<button type="button" onClick={() => setBy('viewed')}>
|
||||||
{t('Views')}
|
{t('Views')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'commented' }}>
|
<li classList={{ selected: params()['by'] === 'commented' }}>
|
||||||
<button type="button" onClick={() => setBy('commented')}>
|
<button type="button" onClick={() => setBy('commented')}>
|
||||||
{t('Discussing')}
|
{t('Discussing')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import '../../styles/FeedSettings.scss'
|
import '../../styles/FeedSettings.scss'
|
||||||
import { t } from '../../utils/intl'
|
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) => {
|
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 (
|
return (
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>{t('Feed settings')}</h1>
|
<h1>{t('Feed settings')}</h1>
|
||||||
|
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li class="selected">
|
<li class="selected">
|
||||||
<a href="?by=topics" onClick={() => setBy('topics')}>
|
<a href="?by=topics" onClick={() => (args()['by'] = 'topics')}>
|
||||||
{t('topics')}
|
{t('topics')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -20,12 +22,12 @@ export const FeedSettings = (props: any) => {
|
||||||
</a>
|
</a>
|
||||||
</li>*/}
|
</li>*/}
|
||||||
<li>
|
<li>
|
||||||
<a href="?by=authors" onClick={() => setBy('authors')}>
|
<a href="?by=authors" onClick={() => (args()['by'] = 'authors')}>
|
||||||
{t('authors')}
|
{t('authors')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="?by=reacted" onClick={() => setBy('reacted')}>
|
<a href="?by=reacted" onClick={() => (args()['by'] = 'reacted')}>
|
||||||
{t('reactions')}
|
{t('reactions')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -2,16 +2,17 @@ import { Show, For, createSignal, createMemo } from 'solid-js'
|
||||||
import '../../styles/Search.scss'
|
import '../../styles/Search.scss'
|
||||||
import type { Shout } from '../../graphql/types.gen'
|
import type { Shout } from '../../graphql/types.gen'
|
||||||
import { ArticleCard } from '../Feed/Card'
|
import { ArticleCard } from '../Feed/Card'
|
||||||
import { sortBy } from '../../utils/sortby'
|
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { by, setBy } from '../../stores/router'
|
import { params } from '../../stores/router'
|
||||||
import { useArticlesStore } from '../../stores/zine/articles'
|
import { useArticlesStore } from '../../stores/zine/articles'
|
||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
results: Shout[]
|
results: Shout[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchPage = (props: Props) => {
|
export const SearchPage = (props: Props) => {
|
||||||
|
const args = useStore(params)
|
||||||
const { getSortedArticles } = useArticlesStore({ sortedArticles: props.results })
|
const { getSortedArticles } = useArticlesStore({ sortedArticles: props.results })
|
||||||
|
|
||||||
// FIXME server sort
|
// FIXME server sort
|
||||||
|
@ -62,12 +63,12 @@ export const SearchPage = (props: Props) => {
|
||||||
|
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li class="selected">
|
<li class="selected">
|
||||||
<a href="?by=relevance" onClick={() => setBy('relevance')}>
|
<a href="?by=relevance" onClick={() => (args()['by'] = 'relevance')}>
|
||||||
{t('By relevance')}
|
{t('By relevance')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="?by=rating" onClick={() => setBy('rating')}>
|
<a href="?by=rating" onClick={() => (args()['by'] = 'rating')}>
|
||||||
{t('Top rated')}
|
{t('Top rated')}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -7,9 +7,10 @@ import { ArticleCard } from '../Feed/Card'
|
||||||
import '../../styles/Topic.scss'
|
import '../../styles/Topic.scss'
|
||||||
import { FullTopic } from '../Topic/Full'
|
import { FullTopic } from '../Topic/Full'
|
||||||
import { t } from '../../utils/intl'
|
import { t } from '../../utils/intl'
|
||||||
import { by, setBy } from '../../stores/router'
|
import { params } from '../../stores/router'
|
||||||
import { useTopicsStore } from '../../stores/zine/topics'
|
import { useTopicsStore } from '../../stores/zine/topics'
|
||||||
import { useArticlesStore } from '../../stores/zine/articles'
|
import { useArticlesStore } from '../../stores/zine/articles'
|
||||||
|
import { useStore } from '@nanostores/solid'
|
||||||
|
|
||||||
interface TopicProps {
|
interface TopicProps {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
|
@ -17,6 +18,7 @@ interface TopicProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TopicPage = (props: TopicProps) => {
|
export const TopicPage = (props: TopicProps) => {
|
||||||
|
const args = useStore(params)
|
||||||
const { getAuthorsByTopic } = useTopicsStore({ topics: [props.topic] })
|
const { getAuthorsByTopic } = useTopicsStore({ topics: [props.topic] })
|
||||||
const { getSortedArticles: sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
|
const { getSortedArticles: sortedArticles } = useArticlesStore({ sortedArticles: props.topicArticles })
|
||||||
const topic = createMemo(() => props.topic)
|
const topic = createMemo(() => props.topic)
|
||||||
|
@ -43,23 +45,23 @@ export const TopicPage = (props: TopicProps) => {
|
||||||
<div class="row group__controls">
|
<div class="row group__controls">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<ul class="view-switcher">
|
<ul class="view-switcher">
|
||||||
<li classList={{ selected: !by() }}>
|
<li classList={{ selected: args()['by'] === 'recent' || !args()['by'] }}>
|
||||||
<button type="button" onClick={() => setBy('recent')}>
|
<button type="button" onClick={() => (args()['by'] = 'recent')}>
|
||||||
{t('Recent')}
|
{t('Recent')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'rating' }}>
|
<li classList={{ selected: args()['by'] === 'rating' }}>
|
||||||
<button type="button" onClick={() => setBy('rating')}>
|
<button type="button" onClick={() => (args()['by'] = 'rating')}>
|
||||||
{t('Popular')}
|
{t('Popular')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'viewed' }}>
|
<li classList={{ selected: args()['by'] === 'viewed' }}>
|
||||||
<button type="button" onClick={() => setBy('viewed')}>
|
<button type="button" onClick={() => (args()['by'] = 'viewed')}>
|
||||||
{t('Views')}
|
{t('Views')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li classList={{ selected: by() === 'commented' }}>
|
<li classList={{ selected: args()['by'] === 'commented' }}>
|
||||||
<button type="button" onClick={() => setBy('commented')}>
|
<button type="button" onClick={() => (args()['by'] = 'commented')}>
|
||||||
{t('Discussing')}
|
{t('Discussing')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -2,7 +2,51 @@ import { atom, action } from 'nanostores'
|
||||||
import type { AuthResult } from '../graphql/types.gen'
|
import type { AuthResult } from '../graphql/types.gen'
|
||||||
import { getLogger } from '../utils/logger'
|
import { getLogger } from '../utils/logger'
|
||||||
import { resetToken, setToken } from '../graphql/privateGraphQLClient'
|
import { resetToken, setToken } from '../graphql/privateGraphQLClient'
|
||||||
|
import { apiClient } from '../utils/apiClient'
|
||||||
|
|
||||||
const log = getLogger('auth-store')
|
const log = getLogger('auth-store')
|
||||||
|
|
||||||
export const session = atom<AuthResult & { email: string }>()
|
export const session = atom<AuthResult>()
|
||||||
|
|
||||||
|
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<string>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { createRouter } from '@nanostores/router'
|
import { createRouter, createSearchParams } from '@nanostores/router'
|
||||||
import { createEffect, createSignal } from 'solid-js'
|
import { onMount } from 'nanostores'
|
||||||
|
import { createEffect, createMemo, createSignal } from 'solid-js'
|
||||||
import { isServer } from 'solid-js/web'
|
import { isServer } from 'solid-js/web'
|
||||||
|
|
||||||
// Types for :params in route templates
|
// Types for :params in route templates
|
||||||
|
@ -18,6 +19,7 @@ interface Routes {
|
||||||
topic: 'slug'
|
topic: 'slug'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const params = createSearchParams()
|
||||||
export const router = createRouter<Routes>(
|
export const router = createRouter<Routes>(
|
||||||
{
|
{
|
||||||
home: '/',
|
home: '/',
|
||||||
|
@ -40,61 +42,18 @@ export const router = createRouter<Routes>(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
router.listen((r) => setResource(r.path))
|
const [resource, setResource] = createSignal<string>('')
|
||||||
|
const slug = createMemo<string>(() => {
|
||||||
|
const s = resource().split('/').pop()
|
||||||
|
return (Boolean(s) && router.routes.filter((x) => x[0] === s).length === 0 && s) || ''
|
||||||
|
})
|
||||||
|
|
||||||
// signals
|
|
||||||
|
|
||||||
const [getPage, setPage] = createSignal<number>(1)
|
|
||||||
const [getSize, setSize] = createSignal<number>(10)
|
|
||||||
|
|
||||||
export type SortBy =
|
|
||||||
| 'rating'
|
|
||||||
| 'reacted'
|
|
||||||
| 'commented'
|
|
||||||
| 'viewed'
|
|
||||||
| 'relevance'
|
|
||||||
| 'topics'
|
|
||||||
| 'authors'
|
|
||||||
| 'shouts'
|
|
||||||
| 'recent' // NOTE: not in Entity.stat
|
|
||||||
| '' // abc
|
|
||||||
|
|
||||||
const [by, setBy] = createSignal<SortBy>('')
|
|
||||||
const [slug, setSlug] = createSignal('')
|
|
||||||
const [resource, setResource] = createSignal<string>(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 _route = (ev) => {
|
||||||
const href: string = ev.target.href
|
const href: string = ev.target.href
|
||||||
console.log('[router] faster link', href)
|
console.log('[router] faster link', href)
|
||||||
ev.stopPropoganation()
|
ev.stopPropoganation()
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
router.open(href)
|
router.open(href)
|
||||||
scanParams(href)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = (ev) => {
|
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) {
|
if (!isServer) {
|
||||||
console.log('[router] client runtime')
|
console.log('[router] client runtime')
|
||||||
createEffect(() => router.open(window.location.pathname), [window.location])
|
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 }
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { Shout } from '../../graphql/types.gen'
|
||||||
import type { WritableAtom } from 'nanostores'
|
import type { WritableAtom } from 'nanostores'
|
||||||
import { useStore } from '@nanostores/solid'
|
import { useStore } from '@nanostores/solid'
|
||||||
import { apiClient } from '../../utils/apiClient'
|
import { apiClient } from '../../utils/apiClient'
|
||||||
import { getPage, setPage } from '../router'
|
import { params } from '../router'
|
||||||
|
|
||||||
let articleEntitiesStore: WritableAtom<Record<string, Shout>>
|
let articleEntitiesStore: WritableAtom<Record<string, Shout>>
|
||||||
let sortedArticlesStore: WritableAtom<Shout[]>
|
let sortedArticlesStore: WritableAtom<Shout[]>
|
||||||
|
@ -96,11 +96,13 @@ export const useArticlesStore = ({ sortedArticles }: InitialState) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadMoreAll = () => {
|
export const loadMoreAll = () => {
|
||||||
setPage(getPage() + 1)
|
const searchParams = useStore(params)
|
||||||
loadRecentAllArticles({ page: getPage() + 1 })
|
const pn = Number.parseInt(searchParams()['page'], 10)
|
||||||
|
loadRecentAllArticles({ page: pn + 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadMorePublished = () => {
|
export const loadMorePublished = () => {
|
||||||
setPage(getPage() + 1)
|
const searchParams = useStore(params)
|
||||||
loadRecentPublishedArticles({ page: getPage() + 1 })
|
const pn = Number.parseInt(searchParams()['page'], 10)
|
||||||
|
loadRecentPublishedArticles({ page: pn + 1 })
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user