Merge remote-tracking branch 'origin/dev' into navigation
# Conflicts: # src/components/Nav/Header.tsx # src/layouts/zine.astro
This commit is contained in:
commit
000bfb4e45
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"*.{js,ts,tsx,json,scss,css,html,astro}": "prettier --write",
|
||||
"*.{js,ts,tsx,json,scss,css,html}": "prettier --write",
|
||||
"package.json": "sort-package-json",
|
||||
"*.{scss,css}": "stylelint",
|
||||
"*.{ts,tsx,js}": "eslint --fix",
|
||||
|
|
|
@ -11,6 +11,7 @@ import { renderMarkdown } from '@astrojs/markdown-remark'
|
|||
import { markdownOptions } from '../../../mdx.config'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { session } from '../../stores/auth'
|
||||
import { incrementView, loadArticle } from '../../stores/zine/articles'
|
||||
|
||||
const MAX_COMMENT_LEVEL = 6
|
||||
|
||||
|
@ -43,14 +44,24 @@ export const FullArticle = (props: ArticleProps) => {
|
|||
const auth = useStore(session)
|
||||
|
||||
onMount(() => {
|
||||
const b: string = props.article?.body
|
||||
if (b?.toString().startsWith('<')) {
|
||||
setBody(b)
|
||||
} else {
|
||||
renderMarkdown(b, markdownOptions).then(({ code }) => setBody(code))
|
||||
if (!props.article.body) {
|
||||
loadArticle({ slug: props.article.slug })
|
||||
}
|
||||
})
|
||||
|
||||
// onMount(() => {
|
||||
// const b: string = props.article?.body
|
||||
// if (b?.toString().startsWith('<')) {
|
||||
// setBody(b)
|
||||
// } else {
|
||||
// renderMarkdown(b, markdownOptions).then(({ code }) => setBody(code))
|
||||
// }
|
||||
// })
|
||||
|
||||
onMount(() => {
|
||||
incrementView({ articleSlug: props.article.slug })
|
||||
})
|
||||
|
||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
|
||||
|
||||
const mainTopic = () =>
|
||||
|
|
|
@ -8,9 +8,8 @@ import { t } from '../../utils/intl'
|
|||
import { useModalStore, showModal, useWarningsStore } from '../../stores/ui'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { session as ssession } from '../../stores/auth'
|
||||
import { route, router } from '../../stores/router'
|
||||
import { handleClientRouteLinkClick, router } from '../../stores/router'
|
||||
import './Header.scss'
|
||||
import { Shout } from '../../graphql/types.gen'
|
||||
|
||||
const resources = [
|
||||
{ name: t('zine'), href: '/' },
|
||||
|
@ -19,7 +18,7 @@ const resources = [
|
|||
//{ name: t('community'), href: '/community' }
|
||||
]
|
||||
|
||||
export const Header = (props: Shout) => {
|
||||
export const Header = () => {
|
||||
// signals
|
||||
const [getIsScrollingBottom, setIsScrollingBottom] = createSignal(false)
|
||||
const [getIsScrolled, setIsScrolled] = createSignal(false)
|
||||
|
@ -45,8 +44,21 @@ export const Header = (props: Shout) => {
|
|||
|
||||
// derived
|
||||
const authorized = createMemo(() => session()?.user?.slug)
|
||||
const enterClick = route(() => showModal('auth'))
|
||||
const bellClick = createMemo(() => (authorized() ? route(toggleWarnings) : enterClick))
|
||||
|
||||
const handleEnterClick = (ev) => {
|
||||
showModal('auth')
|
||||
handleClientRouteLinkClick(ev)
|
||||
}
|
||||
|
||||
const handleBellIconClick = (ev) => {
|
||||
if (!authorized()) {
|
||||
handleEnterClick(ev)
|
||||
return
|
||||
}
|
||||
|
||||
toggleWarnings()
|
||||
handleClientRouteLinkClick(ev)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
let scrollTop = window.scrollY
|
||||
|
@ -76,11 +88,12 @@ export const Header = (props: Shout) => {
|
|||
<div class="wide-container">
|
||||
<nav class="row header__inner" classList={{ fixed: fixed() }}>
|
||||
<div class="main-logo col-auto">
|
||||
<a href="/" onClick={route}>
|
||||
<a href="/" onClick={handleClientRouteLinkClick}>
|
||||
<img src="/logo.svg" alt={t('Discours')} />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col main-navigation">
|
||||
{/*FIXME article header*/}
|
||||
<div class="article-header">
|
||||
Дискурс — независимый художественно-аналитический журнал с горизонтальной редакцией,
|
||||
основанный на принципах свободы слова, прямой демократии и совместного редактирования.
|
||||
|
@ -90,7 +103,7 @@ export const Header = (props: Shout) => {
|
|||
<For each={resources}>
|
||||
{(r: { href: string; name: string }) => (
|
||||
<li classList={{ selected: r.href === subpath() }}>
|
||||
<a href={r.href} onClick={route}>
|
||||
<a href={r.href} onClick={handleClientRouteLinkClick}>
|
||||
{r.name}
|
||||
</a>
|
||||
</li>
|
||||
|
@ -101,7 +114,7 @@ export const Header = (props: Shout) => {
|
|||
<div class="usernav">
|
||||
<div class="usercontrol col">
|
||||
<div class="usercontrol__item">
|
||||
<a href="#auth" onClick={bellClick}>
|
||||
<a href="#auth" onClick={handleBellIconClick}>
|
||||
<div>
|
||||
<Icon name="bell-white" counter={authorized() ? getWarnings().length : 1} />
|
||||
</div>
|
||||
|
@ -118,7 +131,7 @@ export const Header = (props: Shout) => {
|
|||
when={authorized()}
|
||||
fallback={
|
||||
<div class="usercontrol__item loginbtn">
|
||||
<a href="#auth" onClick={enterClick}>
|
||||
<a href="#auth" onClick={handleEnterClick}>
|
||||
{t('enter')}
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
margin-right: 2.2rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -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, params as paramsStore } from '../../stores/router'
|
||||
import { params as paramsStore, handleClientRouteLinkClick } from '../../stores/router'
|
||||
import { session } from '../../stores/auth'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import '../../styles/AllTopics.scss'
|
||||
|
@ -18,7 +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 ((!params()['by'] || params()['by'] === 'abc') && abc().length === 0) {
|
||||
console.log('[authors] default grouping by abc')
|
||||
|
@ -49,17 +51,17 @@ export const AllAuthorsPage = (props: any) => {
|
|||
<div class="col">
|
||||
<ul class="view-switcher">
|
||||
<li classList={{ selected: params()['by'] === 'shouts' }}>
|
||||
<a href="/authors?by=shouts" onClick={route}>
|
||||
<a href="/authors?by=shouts" onClick={handleClientRouteLinkClick}>
|
||||
{t('By shouts')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: params()['by'] === 'rating' }}>
|
||||
<a href="/authors?by=rating" onClick={route}>
|
||||
<a href="/authors?by=rating" onClick={handleClientRouteLinkClick}>
|
||||
{t('By rating')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: !params()['by'] || params()['by'] === 'abc' }}>
|
||||
<a href="/authors" onClick={route}>
|
||||
<a href="/authors" onClick={handleClientRouteLinkClick}>
|
||||
{t('By alphabet')}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -4,25 +4,30 @@ import { byFirstChar, sortBy } from '../../utils/sortby'
|
|||
import Icon from '../Nav/Icon'
|
||||
import { t } from '../../utils/intl'
|
||||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { params as paramstore, route } from '../../stores/router'
|
||||
import { params as paramstore, handleClientRouteLinkClick, router } from '../../stores/router'
|
||||
import { TopicCard } from '../Topic/Card'
|
||||
import { session } from '../../stores/auth'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { groupByTitle } from '../../utils/groupby'
|
||||
import '../../styles/AllTopics.scss'
|
||||
import { groupByTitle } from '../../utils/groupby'
|
||||
|
||||
export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
||||
const [sortedTopics, setSortedTopics] = createSignal<Partial<Topic>[]>([])
|
||||
const [sortedKeys, setSortedKeys] = createSignal<string[]>()
|
||||
const [abc, setAbc] = createSignal([])
|
||||
const { getSortedTopics: topicslist } = useTopicsStore({ topics: props.topics || [] })
|
||||
const { getSortedTopics } = useTopicsStore({ topics: props.topics || [] })
|
||||
const auth = useStore(session)
|
||||
|
||||
const subscribed = (s) => Boolean(auth()?.info?.topics && auth()?.info?.topics?.includes(s || ''))
|
||||
|
||||
const params = useStore(paramstore)
|
||||
|
||||
console.log({ router })
|
||||
|
||||
createEffect(() => {
|
||||
if (abc().length === 0 && (!params()['by'] || params()['by'] === 'abc')) {
|
||||
console.log('[topics] default grouping by abc')
|
||||
const grouped = { ...groupByTitle(topicslist()) }
|
||||
const grouped = { ...groupByTitle(getSortedTopics()) }
|
||||
grouped['A-Z'] = sortBy(grouped['A-Z'], byFirstChar)
|
||||
setAbc(grouped)
|
||||
const keys = Object.keys(abc)
|
||||
|
@ -30,88 +35,95 @@ export const AllTopicsPage = (props: { topics?: Topic[] }) => {
|
|||
setSortedKeys(keys as string[])
|
||||
} else {
|
||||
console.log('[topics] sorting by ' + params()['by'])
|
||||
setSortedTopics(sortBy(topicslist(), params()['by']))
|
||||
setSortedTopics(sortBy(getSortedTopics(), params()['by']))
|
||||
}
|
||||
}, [topicslist(), params()])
|
||||
}, [getSortedTopics(), params()])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div class="all-topics-page">
|
||||
<Show when={Boolean(sortedTopics()?.length)}>
|
||||
<div class="wide-container">
|
||||
<div class="shift-content">
|
||||
<div class="row">
|
||||
<div class="col-md-9 page-header">
|
||||
<h1>{t('Topics')}</h1>
|
||||
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
||||
</div>
|
||||
<div class="all-topics-page">
|
||||
<Show when={getSortedTopics().length > 0}>
|
||||
<div class="wide-container">
|
||||
<div class="shift-content">
|
||||
<div class="row">
|
||||
<div class="col-md-9 page-header">
|
||||
<h1>{t('Topics')}</h1>
|
||||
<p>{t('Subscribe what you like to tune your personal feed')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ul class="view-switcher">
|
||||
<li classList={{ selected: params()['by'] === 'shouts' }}>
|
||||
<a href="/topics?by=shouts" onClick={route}>
|
||||
{t('By shouts')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: params()['by'] === 'authors' }}>
|
||||
<a href="/topics?by=authors" onClick={route}>
|
||||
{t('By authors')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: params()['by'] === 'abc' }}>
|
||||
<a href="/topics" onClick={route}>
|
||||
{t('By alphabet')}
|
||||
</a>
|
||||
</li>
|
||||
<li class="view-switcher__search">
|
||||
<a href="/topic/search">
|
||||
<Icon name="search" />
|
||||
{t('Search topic')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ul class="view-switcher">
|
||||
<li classList={{ selected: params()['by'] === 'shouts' }}>
|
||||
<a href="/topics?by=shouts" onClick={handleClientRouteLinkClick}>
|
||||
{t('By shouts')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: params()['by'] === 'authors' }}>
|
||||
<a href="/topics?by=authors" onClick={handleClientRouteLinkClick}>
|
||||
{t('By authors')}
|
||||
</a>
|
||||
</li>
|
||||
<li classList={{ selected: params()['by'] === 'abc' }}>
|
||||
<a href="/topics" onClick={handleClientRouteLinkClick}>
|
||||
{t('By alphabet')}
|
||||
</a>
|
||||
</li>
|
||||
<li class="view-switcher__search">
|
||||
<a href="/topic/search">
|
||||
<Icon name="search" />
|
||||
{t('Search topic')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Show
|
||||
when={params()['by'] === 'abc'}
|
||||
fallback={() => (
|
||||
<div class="stats">
|
||||
<For each={sortedTopics()}>
|
||||
{(topic: Topic) => (
|
||||
<TopicCard topic={topic} compact={false} subscribed={subscribed(topic.slug)} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<div class="stats">
|
||||
<For each={getSortedTopics()}>
|
||||
{(topic) => (
|
||||
<TopicCard topic={topic} compact={false} subscribed={subscribed(topic.slug)} />
|
||||
)}
|
||||
>
|
||||
<For each={sortedKeys() || []}>
|
||||
{(letter: string) => (
|
||||
<div class="group">
|
||||
<h2>{letter}</h2>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<For each={abc()[letter]}>
|
||||
{(topic: Partial<Topic>) => (
|
||||
<div class="topic col-sm-6 col-md-3">
|
||||
<div class="topic-title">
|
||||
<a href={`/topic/${topic.slug}`}>{topic.title}</a>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</For>
|
||||
</div>
|
||||
|
||||
{/*FIXME*/}
|
||||
{/*<Show*/}
|
||||
{/* when={params()['by'] === 'abc'}*/}
|
||||
{/* fallback={() => (*/}
|
||||
{/* <div class="stats">*/}
|
||||
{/* <For each={getSortedTopics()}>*/}
|
||||
{/* {(topic: Topic) => (*/}
|
||||
{/* <TopicCard topic={topic} compact={false} subscribed={subscribed(topic.slug)} />*/}
|
||||
{/* )}*/}
|
||||
{/* </For>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/*>*/}
|
||||
{/* <For each={sortedKeys() || []}>*/}
|
||||
{/* {(letter: string) => (*/}
|
||||
{/* <div class="group">*/}
|
||||
{/* <h2>{letter}</h2>*/}
|
||||
{/* <div class="container">*/}
|
||||
{/* <div class="row">*/}
|
||||
{/* <For each={abc()[letter]}>*/}
|
||||
{/* {(topic: Partial<Topic>) => (*/}
|
||||
{/* <div class="topic col-sm-6 col-md-3">*/}
|
||||
{/* <div class="topic-title">*/}
|
||||
{/* <a href={`/topic/${topic.slug}`}>{topic.title}</a>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* </For>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* </div>*/}
|
||||
{/* )}*/}
|
||||
{/* </For>*/}
|
||||
{/*</Show>*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import { t } from '../../utils/intl'
|
|||
import { useTopicsStore } from '../../stores/zine/topics'
|
||||
import { loadPublishedArticles, useArticlesStore } from '../../stores/zine/articles'
|
||||
import { useAuthorsStore } from '../../stores/zine/authors'
|
||||
import { router } from '../../stores/router'
|
||||
|
||||
type HomeProps = {
|
||||
randomTopics: Topic[]
|
||||
|
@ -26,7 +27,7 @@ type HomeProps = {
|
|||
offset?: number
|
||||
}
|
||||
|
||||
const LAYOUTS = ['article', 'prose', 'music', 'video', 'image']
|
||||
// const LAYOUTS = ['article', 'prose', 'music', 'video', 'image']
|
||||
|
||||
export const HomePage = (props: HomeProps) => {
|
||||
const [someLayout, setSomeLayout] = createSignal([] as Shout[])
|
||||
|
|
|
@ -99,7 +99,7 @@ export const TopicPage = (props: TopicProps) => {
|
|||
<Show when={sortedArticles().length > 5}>
|
||||
<Beside
|
||||
title={t('Topic is supported by')}
|
||||
values={getAuthorsByTopic()[topic().slug]}
|
||||
values={getAuthorsByTopic()[topic().slug].slice(0, 7)}
|
||||
beside={sortedArticles()[6]}
|
||||
wrapper={'author'}
|
||||
/>
|
||||
|
|
15
src/components/providers/ServerRouterProvider.tsx
Normal file
15
src/components/providers/ServerRouterProvider.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { isServer } from 'solid-js/web'
|
||||
import { router } from '../../stores/router'
|
||||
|
||||
type Props = {
|
||||
href: string
|
||||
children: any
|
||||
}
|
||||
|
||||
export const ServerRouterProvider = (props: Props) => {
|
||||
if (isServer) {
|
||||
router.open(props.href)
|
||||
}
|
||||
|
||||
return props.children
|
||||
}
|
|
@ -4,12 +4,10 @@ import { useStore } from '@nanostores/solid'
|
|||
import { Suspense } from 'solid-js'
|
||||
import { Header } from '../components/Nav/Header'
|
||||
import { locale as langstore } from '../stores/ui'
|
||||
import { router } from '../stores/router'
|
||||
import { t } from '../utils/intl'
|
||||
|
||||
const { title } = Astro.params
|
||||
const locale = useStore(langstore)
|
||||
router.open(Astro.url.pathname) // mb doesn't work!
|
||||
---
|
||||
|
||||
<html lang={locale() || 'ru'}>
|
||||
|
|
|
@ -3,30 +3,33 @@ import '../styles/app.scss'
|
|||
import { Suspense } from 'solid-js'
|
||||
import { Header } from '../components/Nav/Header'
|
||||
import { Footer } from '../components/Discours/Footer'
|
||||
import { ServerRouterProvider } from '../components/providers/ServerRouterProvider'
|
||||
import { t } from '../utils/intl'
|
||||
import { locale as langstore } from '../stores/ui'
|
||||
import { useStore } from '@nanostores/solid'
|
||||
import { router } from '../stores/router'
|
||||
|
||||
const { pathname, search } = Astro.url
|
||||
|
||||
// FIXME always returns ru
|
||||
const locale = useStore(langstore)
|
||||
// FIXME why
|
||||
router.open(Astro.url.pathname) // looks like doesn't work!
|
||||
---
|
||||
|
||||
<html lang={locale() || 'en'}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title>{t('Discours')}</title>
|
||||
</head>
|
||||
<body>
|
||||
<Header client:load />
|
||||
<main class="main-content">
|
||||
<Suspense>
|
||||
<slot />
|
||||
</Suspense>
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
<ServerRouterProvider href={pathname + search}>
|
||||
<html lang={locale() || 'en'}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<title>{t('Discours')}</title>
|
||||
</head>
|
||||
<body>
|
||||
<Header client:load />
|
||||
<main class="main-content">
|
||||
<Suspense>
|
||||
<slot />
|
||||
</Suspense>
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
</ServerRouterProvider>
|
||||
|
|
|
@ -15,8 +15,8 @@ Astro.response.headers.set('Cache-Control', 's-maxage=1, stale-while-revalidate'
|
|||
|
||||
<Zine>
|
||||
<HomePage
|
||||
randomTopics={randomTopics}
|
||||
recentPublishedArticles={recentPublished}
|
||||
randomTopics={randomTopics}
|
||||
topMonthArticles={topMonth}
|
||||
topOverallArticles={topOverall}
|
||||
client:load
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { createRouter, createSearchParams } from '@nanostores/router'
|
||||
import { createEffect } from 'solid-js'
|
||||
import { isServer } from 'solid-js/web'
|
||||
|
||||
// Types for :params in route templates
|
||||
|
@ -41,24 +40,15 @@ export const router = createRouter<Routes>(
|
|||
}
|
||||
)
|
||||
|
||||
// suppresses reload
|
||||
export const route = (ev) => {
|
||||
// eslint-disable-next-line unicorn/consistent-function-scoping
|
||||
const _route = (ev) => {
|
||||
const href: string = ev.target.href
|
||||
console.log('[router] faster link', href)
|
||||
ev.stopPropoganation()
|
||||
ev.preventDefault()
|
||||
router.open(href)
|
||||
}
|
||||
if (typeof ev === 'function') {
|
||||
return _route
|
||||
} else if (!isServer && ev?.target && ev.target.href) {
|
||||
_route(ev)
|
||||
}
|
||||
export const handleClientRouteLinkClick = (ev) => {
|
||||
const href = ev.target.href
|
||||
console.log('[router] faster link', href)
|
||||
ev.stopPropagation()
|
||||
ev.preventDefault()
|
||||
router.open(href)
|
||||
}
|
||||
|
||||
if (!isServer) {
|
||||
console.log('[router] client runtime')
|
||||
createEffect(() => router.open(window.location.pathname), [window.location])
|
||||
const { pathname, search } = window.location
|
||||
router.open(pathname + search)
|
||||
}
|
||||
|
|
|
@ -170,6 +170,14 @@ export const loadSearchResults = async ({
|
|||
addSortedArticles(newArticles)
|
||||
}
|
||||
|
||||
export const incrementView = async ({ articleSlug }: { articleSlug: string }): Promise<void> => {
|
||||
await apiClient.incrementView({ articleSlug })
|
||||
}
|
||||
|
||||
export const loadArticle = async ({ slug }: { slug: string }): Promise<Shout> => {
|
||||
return await apiClient.getArticle({ slug })
|
||||
}
|
||||
|
||||
type InitialState = {
|
||||
sortedArticles?: Shout[]
|
||||
topRatedArticles?: Shout[]
|
||||
|
|
|
@ -14,7 +14,9 @@ let topicEntitiesStore: WritableAtom<{ [topicSlug: string]: Topic }>
|
|||
let sortedTopicsStore: ReadableAtom<Topic[]>
|
||||
let topTopicsStore: ReadableAtom<Topic[]>
|
||||
let randomTopicsStore: WritableAtom<Topic[]>
|
||||
let topicsByAuthorStore: WritableAtom<{ [authorSlug: string]: Topic[] }>
|
||||
let topicsByAuthorStore: WritableAtom<{ [authorSlug: string]: Topic[] }> = atom<{
|
||||
[authorSlug: string]: Topic[]
|
||||
}>({})
|
||||
|
||||
const initStore = (initial?: Record<string, Topic>) => {
|
||||
if (topicEntitiesStore) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import authReset from '../graphql/mutation/auth-reset'
|
|||
import authForget from '../graphql/mutation/auth-forget'
|
||||
import authResend from '../graphql/mutation/auth-resend'
|
||||
import authorsBySlugs from '../graphql/query/authors-by-slugs'
|
||||
import incrementView from '../graphql/mutation/increment-view'
|
||||
|
||||
const log = getLogger('api-client')
|
||||
|
||||
|
@ -81,7 +82,7 @@ export const apiClient = {
|
|||
})
|
||||
.toPromise()
|
||||
|
||||
return response.data.searchQuery
|
||||
return response.data?.searchQuery || []
|
||||
},
|
||||
getRecentArticles: async ({
|
||||
limit = FEED_SIZE,
|
||||
|
@ -266,5 +267,8 @@ export const apiClient = {
|
|||
const response = await privateGraphQLClient.mutation(reactionDestroy, { id }).toPromise()
|
||||
|
||||
return response.data.deleteReaction
|
||||
},
|
||||
incrementView: async ({ articleSlug }) => {
|
||||
await privateGraphQLClient.mutation(incrementView, { shout: articleSlug })
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user