Merge remote-tracking branch 'hub/dev' into hotfix/posting

This commit is contained in:
Untone 2024-02-02 20:37:38 +03:00
commit 01d11aca5b
34 changed files with 137 additions and 143 deletions

View File

@ -29,6 +29,7 @@ import { VideoPlayer } from '../_shared/VideoPlayer'
import { AuthorBadge } from '../Author/AuthorBadge' import { AuthorBadge } from '../Author/AuthorBadge'
import { CardTopic } from '../Feed/CardTopic' import { CardTopic } from '../Feed/CardTopic'
import { FeedArticlePopup } from '../Feed/FeedArticlePopup' import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
import { Modal } from '../Nav/Modal'
import { TableOfContents } from '../TableOfContents' import { TableOfContents } from '../TableOfContents'
import { AudioHeader } from './AudioHeader' import { AudioHeader } from './AudioHeader'
@ -79,7 +80,7 @@ export const FullArticle = (props: Props) => {
actions: { requireAuthentication }, actions: { requireAuthentication },
} = useSession() } = useSession()
const formattedDate = createMemo(() => formatDate(new Date(props.article.created_at * 1000))) const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
const mainTopic = createMemo(() => { const mainTopic = createMemo(() => {
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
@ -308,6 +309,8 @@ export const FullArticle = (props: Props) => {
const aspectRatio = width / height const aspectRatio = width / height
iframe.style.width = `${containerWidth}px` iframe.style.width = `${containerWidth}px`
iframe.style.height = `${Math.round(containerWidth / aspectRatio) + 40}px` iframe.style.height = `${Math.round(containerWidth / aspectRatio) + 40}px`
} else {
iframe.style.height = `${containerWidth}px`
} }
}) })
} }
@ -618,7 +621,9 @@ export const FullArticle = (props: Props) => {
<Show when={selectedImage()}> <Show when={selectedImage()}>
<Lightbox image={selectedImage()} onClose={handleLightboxClose} /> <Lightbox image={selectedImage()} onClose={handleLightboxClose} />
</Show> </Show>
<Modal variant="medium" name="inviteMembers">
<InviteMembers variant={'coauthors'} title={t('Invite experts')} /> <InviteMembers variant={'coauthors'} title={t('Invite experts')} />
</Modal>
<ShareModal <ShareModal
title={props.article.title} title={props.article.title}
description={description} description={description}

View File

@ -94,11 +94,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
const mainTopicTitle = const mainTopicTitle =
mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || '' mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
const formattedDate = createMemo<string>(() => { const formattedDate = createMemo<string>(() =>
let r = '' props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
if (props.article.created_at) r = formatDate(new Date(props.article.created_at * 1000)) )
return r
})
const { title, subtitle } = getTitleAndSubtitle(props.article) const { title, subtitle } = getTitleAndSubtitle(props.article)

View File

@ -188,11 +188,10 @@
line-height: 16px; line-height: 16px;
margin-top: 0.3em; margin-top: 0.3em;
/* Red/500 */ color: var(--danger-color);
color: #d00820;
a { a {
color: #d00820; color: var(--danger-color);
border-color: #d00820; border-color: #d00820;
&:hover { &:hover {

View File

@ -1,11 +1,11 @@
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { ApiResponse, ForgotPasswordResponse } from '@authorizerdev/authorizer-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createSignal, JSX, Show } from 'solid-js' import { createSignal, JSX, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
// import { ApiError } from '../../../graphql/error'
import { useRouter } from '../../../stores/router' import { useRouter } from '../../../stores/router'
import { validateEmail } from '../../../utils/validateEmail' import { validateEmail } from '../../../utils/validateEmail'
@ -29,16 +29,14 @@ export const ForgotPasswordForm = () => {
const { const {
actions: { forgotPassword }, actions: { forgotPassword },
} = useSession() } = useSession()
const [submitError, setSubmitError] = createSignal('')
const [isSubmitting, setIsSubmitting] = createSignal(false) const [isSubmitting, setIsSubmitting] = createSignal(false)
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({}) const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
const [isUserNotFount, setIsUserNotFound] = createSignal(false) const [isUserNotFound, setIsUserNotFound] = createSignal(false)
const authFormRef: { current: HTMLFormElement } = { current: null } const authFormRef: { current: HTMLFormElement } = { current: null }
const [message, setMessage] = createSignal<string>('') const [message, setMessage] = createSignal<string>('')
const handleSubmit = async (event: Event) => { const handleSubmit = async (event: Event) => {
event.preventDefault() event.preventDefault()
setSubmitError('')
setIsUserNotFound(false) setIsUserNotFound(false)
const newValidationErrors: ValidationErrors = {} const newValidationErrors: ValidationErrors = {}
@ -66,15 +64,8 @@ export const ForgotPasswordForm = () => {
redirect_uri: window.location.origin, redirect_uri: window.location.origin,
}) })
console.debug('[ForgotPasswordForm] authorizer response:', data) console.debug('[ForgotPasswordForm] authorizer response:', data)
setMessage(data.message) if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
console.warn(errors)
if (errors.some((e) => e.cause === 'user_not_found')) {
setIsUserNotFound(true) setIsUserNotFound(true)
return
} else {
const errorText = errors.map((e) => e.message).join(' ') // FIXME
setSubmitError(errorText)
} }
} catch (error) { } catch (error) {
console.error(error) console.error(error)
@ -111,37 +102,27 @@ export const ForgotPasswordForm = () => {
/> />
<label for="email">{t('Email')}</label> <label for="email">{t('Email')}</label>
</div> <Show when={isUserNotFound()}>
<div class={styles.validationError}>
<Show when={submitError()}>
<div class={styles.authInfo}>
<ul>
<li class={styles.warn}>{submitError()}</li>
</ul>
</div>
</Show>
<Show when={isUserNotFount()}>
<div class={styles.authSubtitle}>
{t("We can't find you, check email or")}{' '} {t("We can't find you, check email or")}{' '}
<a <span
href="#" class={'link'}
onClick={(event) => { onClick={() =>
event.preventDefault()
changeSearchParams({ changeSearchParams({
mode: 'register', mode: 'login',
}) })
}} }
> >
{t('register')} {t('register')}
</a> </span>
</div>
</Show>
<Show when={validationErrors().email}> <Show when={validationErrors().email}>
<div class={styles.validationError}>{validationErrors().email}</div> <div class={styles.validationError}>{validationErrors().email}</div>
</Show> </Show>
</div> </div>
</Show>
<div> <div style={{ 'margin-top': '5rem' }}>
<button <button
class={clsx('button', styles.submitButton)} class={clsx('button', styles.submitButton)}
disabled={isSubmitting() || Boolean(message())} disabled={isSubmitting() || Boolean(message())}

View File

@ -1,7 +1,7 @@
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { createSignal, Show } from 'solid-js' import { createEffect, createSignal, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
@ -106,26 +106,22 @@ export const LoginForm = () => {
setIsSubmitting(true) setIsSubmitting(true)
try { try {
await signIn({ email: email(), password: password() }) const { errors } = await signIn({ email: email(), password: password() })
if (errors?.length > 0) {
if (errors.some((error) => error.message.includes('bad user credentials'))) {
setValidationErrors((prev) => ({
...prev,
password: t('Something went wrong, check email and password'),
}))
} else {
setSubmitError(t('Error'))
}
return
}
hideModal() hideModal()
showSnackbar({ body: t('Welcome!') }) showSnackbar({ body: t('Welcome!') })
} catch (error) { } catch (error) {
console.error(error) console.error(error)
if (error instanceof ApiError) {
if (error.code === 'email_not_confirmed') {
setSubmitError(t('Please, confirm email'))
setIsEmailNotConfirmed(true)
return
}
if (error.code === 'user_not_found') {
setSubmitError(t('Something went wrong, check email and password'))
return
}
}
setSubmitError(error.message) setSubmitError(error.message)
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
@ -170,6 +166,11 @@ export const LoginForm = () => {
</div> </div>
<PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} /> <PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} />
<Show when={validationErrors().password}>
<div class={styles.validationError} style={{ position: 'static', 'font-size': '1.4rem' }}>
{validationErrors().password}
</div>
</Show>
<div> <div>
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit"> <button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">

View File

@ -6,7 +6,6 @@ import { Show, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
// import { ApiError } from '../../../graphql/error'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks' import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
import { useRouter } from '../../../stores/router' import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
@ -113,15 +112,10 @@ export const RegisterForm = () => {
confirm_password: password(), confirm_password: password(),
redirect_uri: window.location.origin, redirect_uri: window.location.origin,
} }
await signUp(opts) const { errors } = await signUp(opts)
if (errors && errors.some((error) => error.message.includes('has already signed up'))) {
setIsSuccess(true) setValidationErrors((prev) => ({
} catch (error) { ...prev,
console.error(error)
if (error) {
if (error.message.includes('has already signed up')) {
setValidationErrors((errors) => ({
...errors,
email: ( email: (
<> <>
{t('User with this email already exists')},{' '} {t('User with this email already exists')},{' '}
@ -139,8 +133,9 @@ export const RegisterForm = () => {
), ),
})) }))
} }
setIsSuccess(true)
} catch (error) {
console.error(error) console.error(error)
}
} finally { } finally {
setIsSubmitting(false) setIsSubmitting(false)
} }

View File

@ -29,13 +29,7 @@ const getSearchCoincidences = ({ str, intersection }: { str: string; intersectio
const prepareSearchResults = (list: Shout[], searchValue: string) => const prepareSearchResults = (list: Shout[], searchValue: string) =>
list.sort(byScore()).map((article, index) => ({ list.sort(byScore()).map((article, index) => ({
...article, ...article,
body: article.body,
cover: article.cover,
created_at: article.created_at,
id: index, id: index,
slug: article.slug,
authors: article.authors,
topics: article.topics,
title: article.title title: article.title
? getSearchCoincidences({ ? getSearchCoincidences({
str: article.title, str: article.title,

View File

@ -5,7 +5,7 @@ import { createStore } from 'solid-js/store'
import { ShoutForm, useEditorContext } from '../../context/editor' import { ShoutForm, useEditorContext } from '../../context/editor'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
import { ShoutVisibility, type Shout, type Topic } from '../../graphql/schema/core.gen' import { type Shout, type Topic } from '../../graphql/schema/core.gen'
import { LayoutType, MediaItem } from '../../pages/types' import { LayoutType, MediaItem } from '../../pages/types'
import { useRouter } from '../../stores/router' import { useRouter } from '../../stores/router'
import { clone } from '../../utils/clone' import { clone } from '../../utils/clone'
@ -182,10 +182,10 @@ export const EditView = (props: Props) => {
const hasChanges = !deepEqual(form, prevForm) const hasChanges = !deepEqual(form, prevForm)
if (hasChanges) { if (hasChanges) {
setSaving(true) setSaving(true)
if (props.shout?.visibility === ShoutVisibility.Authors) { if (props.shout?.published_at) {
await saveDraft(form)
} else {
saveDraftToLocalStorage(form) saveDraftToLocalStorage(form)
} else {
await saveDraft(form)
} }
setPrevForm(clone(form)) setPrevForm(clone(form))
setTimeout(() => { setTimeout(() => {

View File

@ -45,7 +45,7 @@ export const Expo = (props: Props) => {
}) })
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => { const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
const filters = { published: true, ...additionalFilters } const filters = { featured: true, ...additionalFilters }
if (!filters.layouts) filters.layouts = [] if (!filters.layouts) filters.layouts = []
if (props.layout) { if (props.layout) {

View File

@ -1,5 +1,3 @@
import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/schema/core.gen'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { Meta } from '@solidjs/meta' import { Meta } from '@solidjs/meta'
import { clsx } from 'clsx' import { clsx } from 'clsx'
@ -9,6 +7,12 @@ import { useLocalize } from '../../../context/localize'
import { useReactions } from '../../../context/reactions' import { useReactions } from '../../../context/reactions'
import { useSession } from '../../../context/session' import { useSession } from '../../../context/session'
import { apiClient } from '../../../graphql/client/core' import { apiClient } from '../../../graphql/client/core'
import {
type Author,
type LoadShoutsOptions,
type Reaction,
type Shout,
} from '../../../graphql/schema/core.gen'
import { router, useRouter } from '../../../stores/router' import { router, useRouter } from '../../../stores/router'
import { showModal } from '../../../stores/ui' import { showModal } from '../../../stores/ui'
import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles' import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles'
@ -35,7 +39,7 @@ export const FEED_PAGE_SIZE = 20
const UNRATED_ARTICLES_COUNT = 5 const UNRATED_ARTICLES_COUNT = 5
type FeedPeriod = 'week' | 'month' | 'year' type FeedPeriod = 'week' | 'month' | 'year'
type VisibilityMode = 'all' | 'community' | 'public' type VisibilityMode = 'all' | 'community' | 'featured'
type PeriodItem = { type PeriodItem = {
value: FeedPeriod value: FeedPeriod
@ -96,7 +100,7 @@ export const FeedView = (props: Props) => {
const { t } = useLocalize() const { t } = useLocalize()
const monthPeriod: PeriodItem = { value: 'month', title: t('This month') } const monthPeriod: PeriodItem = { value: 'month', title: t('This month') }
const visibilityAll = { value: 'public', title: t('All') } const visibilityAll = { value: 'featured', title: t('All') }
const periods: PeriodItem[] = [ const periods: PeriodItem[] = [
{ value: 'week', title: t('This week') }, { value: 'week', title: t('This week') },
@ -106,7 +110,7 @@ export const FeedView = (props: Props) => {
const visibilities: VisibilityItem[] = [ const visibilities: VisibilityItem[] = [
{ value: 'community', title: t('All') }, { value: 'community', title: t('All') },
{ value: 'public', title: t('Published') }, { value: 'featured', title: t('Published') },
] ]
const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>() const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
@ -190,7 +194,10 @@ export const FeedView = (props: Props) => {
if (visibilityMode === 'all') { if (visibilityMode === 'all') {
options.filters = { ...options.filters } options.filters = { ...options.filters }
} else if (visibilityMode) { } else if (visibilityMode) {
options.filters = { ...options.filters, published: visibilityMode === 'public' } options.filters = {
...options.filters,
featured: visibilityMode === 'featured',
}
} }
if (searchParams().by && searchParams().by !== 'publish_date') { if (searchParams().by && searchParams().by !== 'publish_date') {

View File

@ -60,7 +60,7 @@ export const HomeView = (props: Props) => {
loadTopMonthArticles() loadTopMonthArticles()
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) { if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
filters: { published: true }, filters: { featured: true },
limit: CLIENT_LOAD_ARTICLES_COUNT, limit: CLIENT_LOAD_ARTICLES_COUNT,
offset: sortedArticles().length, offset: sortedArticles().length,
}) })
@ -80,7 +80,7 @@ export const HomeView = (props: Props) => {
saveScrollPosition() saveScrollPosition()
const { hasMore } = await loadShouts({ const { hasMore } = await loadShouts({
filters: { published: true }, filters: { featured: true },
limit: LOAD_MORE_PAGE_SIZE, limit: LOAD_MORE_PAGE_SIZE,
offset: sortedArticles().length, offset: sortedArticles().length,
}) })

View File

@ -111,7 +111,7 @@ export const InviteMembers = (props: Props) => {
} }
return ( return (
<Modal variant="medium" name="inviteMembers"> <>
<h2>{props.title || t('Invite collaborators')}</h2> <h2>{props.title || t('Invite collaborators')}</h2>
<div class={clsx(styles.InviteMembers)}> <div class={clsx(styles.InviteMembers)}>
<div class={styles.searchHeader}> <div class={styles.searchHeader}>
@ -182,6 +182,6 @@ export const InviteMembers = (props: Props) => {
/> />
</div> </div>
</div> </div>
</Modal> </>
) )
} }

View File

@ -6,7 +6,7 @@ import { Accessor, createContext, createSignal, useContext } from 'solid-js'
import { createStore, SetStoreFunction } from 'solid-js/store' import { createStore, SetStoreFunction } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient } from '../graphql/client/core'
import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen' import { Topic, TopicInput } from '../graphql/schema/core.gen'
import { router, useRouter } from '../stores/router' import { router, useRouter } from '../stores/router'
import { slugify } from '../utils/slugify' import { slugify } from '../utils/slugify'
@ -158,10 +158,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
const shout = await updateShout(formToSave, { publish: false }) const shout = await updateShout(formToSave, { publish: false })
removeDraftFromLocalStorage(formToSave.shoutId) removeDraftFromLocalStorage(formToSave.shoutId)
if (shout.visibility === ShoutVisibility.Authors) { if (shout.published_at) {
openPage(router, 'drafts')
} else {
openPage(router, 'article', { slug: shout.slug }) openPage(router, 'article', { slug: shout.slug })
} else {
openPage(router, 'drafts')
} }
} catch (error) { } catch (error) {
console.error('[saveShout]', error) console.error('[saveShout]', error)

View File

@ -1,4 +1,4 @@
import { createEffect, createSignal, createContext, Accessor, useContext, JSX, onMount } from 'solid-js' import { createEffect, createSignal, createContext, Accessor, useContext, JSX } from 'solid-js'
import { createStore } from 'solid-js/store' import { createStore } from 'solid-js/store'
import { apiClient } from '../graphql/client/core' import { apiClient } from '../graphql/client/core'

View File

@ -59,8 +59,8 @@ export type SessionContextType = {
callback: (() => Promise<void>) | (() => void), callback: (() => Promise<void>) | (() => void),
modalSource: AuthModalSource, modalSource: AuthModalSource,
) => void ) => void
signUp: (params: SignupInput) => Promise<AuthToken | void> signUp: (params: SignupInput) => Promise<{ data: AuthToken; errors: Error[] }>
signIn: (params: LoginInput) => Promise<void> signIn: (params: LoginInput) => Promise<{ data: AuthToken; errors: Error[] }>
signOut: () => Promise<void> signOut: () => Promise<void>
oauth: (provider: string) => Promise<void> oauth: (provider: string) => Promise<void>
forgotPassword: ( forgotPassword: (
@ -273,16 +273,20 @@ export const SessionProvider = (props: {
}) })
// authorizer api proxy methods // authorizer api proxy methods
const authenticate = async (authFunction, params) => {
const resp = await authFunction(params)
console.debug('[context.session] authenticate:', resp)
if (resp?.data && !resp.errors) {
setSession(resp.data)
}
return { data: resp?.data, errors: resp?.errors }
}
const signUp = async (params: SignupInput) => { const signUp = async (params: SignupInput) => {
const authResult: ApiResponse<AuthToken> = await authorizer().signup(params) return authenticate(authorizer().signup, params)
if (authResult?.data) setSession(authResult.data)
if (authResult?.errors) console.error(authResult.errors)
} }
const signIn = async (params: LoginInput) => { const signIn = async (params: LoginInput) => {
const authResult: ApiResponse<AuthToken> = await authorizer().login(params) return authenticate(authorizer().login, params)
if (authResult?.data) setSession(authResult.data)
if (authResult?.errors) console.error(authResult.errors)
} }
const signOut = async () => { const signOut = async () => {

View File

@ -28,7 +28,6 @@ export const inboxClient = {
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => { loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
const resp = await inboxClient.private.query(myChats, options).toPromise() const resp = await inboxClient.private.query(myChats, options).toPromise()
console.log('!!! resp:', resp)
return resp.data.load_chats.chats return resp.data.load_chats.chats
}, },

View File

@ -7,7 +7,6 @@ export default gql`
title title
lead lead
description description
visibility
subtitle subtitle
slug slug
layout layout
@ -45,6 +44,7 @@ export default gql`
created_at created_at
updated_at updated_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -34,6 +34,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -32,6 +32,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -26,6 +26,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -29,6 +29,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -34,6 +34,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -50,6 +50,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -35,6 +35,7 @@ export default gql`
} }
created_at created_at
published_at published_at
featured_at
stat { stat {
viewed viewed

View File

@ -16,7 +16,7 @@ export const onBeforeRender = async (pageContext: PageContext) => {
} }
const authorShouts = await apiClient.getShouts({ const authorShouts = await apiClient.getShouts({
filters: { author: slug, published: false }, filters: { author: slug, featured: false },
limit: PRERENDERED_ARTICLES_COUNT, limit: PRERENDERED_ARTICLES_COUNT,
}) })
const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } } const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } }

View File

@ -21,7 +21,7 @@ export const AuthorPage = (props: PageProps) => {
const preload = () => { const preload = () => {
return Promise.all([ return Promise.all([
loadShouts({ loadShouts({
filters: { author: slug(), published: false }, filters: { author: slug(), featured: false },
limit: PRERENDERED_ARTICLES_COUNT, limit: PRERENDERED_ARTICLES_COUNT,
}), }),
loadAuthor({ slug: slug() }), loadAuthor({ slug: slug() }),

View File

@ -13,7 +13,7 @@ const handleFeedLoadShouts = (options: LoadShoutsOptions) => {
return loadShouts({ return loadShouts({
...options, ...options,
filters: { filters: {
published: false, featured: false,
...options.filters, ...options.filters,
}, },
}) })

View File

@ -6,7 +6,7 @@ import { apiClient } from '../graphql/client/core'
export const onBeforeRender = async (_pageContext: PageContext) => { export const onBeforeRender = async (_pageContext: PageContext) => {
const homeShouts = await apiClient.getShouts({ const homeShouts = await apiClient.getShouts({
filters: { published: true }, filters: { featured: true },
limit: PRERENDERED_ARTICLES_COUNT, limit: PRERENDERED_ARTICLES_COUNT,
}) })

View File

@ -20,7 +20,7 @@ export const HomePage = (props: PageProps) => {
} }
await Promise.all([ await Promise.all([
loadShouts({ filters: { published: true }, limit: PRERENDERED_ARTICLES_COUNT }), loadShouts({ filters: { featured: true }, limit: PRERENDERED_ARTICLES_COUNT }),
loadRandomTopics({ amount: RANDOM_TOPICS_COUNT }), loadRandomTopics({ amount: RANDOM_TOPICS_COUNT }),
]) ])

View File

@ -16,7 +16,7 @@ export const onBeforeRender = async (pageContext: PageContext) => {
} }
const topicShouts = await apiClient.getShouts({ const topicShouts = await apiClient.getShouts({
filters: { topic: topic.slug, published: true }, filters: { topic: topic.slug, featured: true },
limit: PRERENDERED_ARTICLES_COUNT, limit: PRERENDERED_ARTICLES_COUNT,
}) })

View File

@ -21,7 +21,7 @@ export const TopicPage = (props: PageProps) => {
const preload = () => const preload = () =>
Promise.all([ Promise.all([
loadShouts({ loadShouts({
filters: { topic: slug(), published: true }, filters: { topic: slug(), featured: true },
limit: PRERENDERED_ARTICLES_COUNT, limit: PRERENDERED_ARTICLES_COUNT,
offset: 0, offset: 0,
}), }),

View File

@ -131,7 +131,7 @@ export const loadShouts = async (
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
options.limit += 1 options.limit += 1
const newShouts = await apiClient.getShouts(options) const newShouts = await apiClient.getShouts(options)
const hasMore = newShouts ?? newShouts.length === options.limit + 1 const hasMore = newShouts?.length === options.limit + 1
if (hasMore) { if (hasMore) {
newShouts.splice(-1) newShouts.splice(-1)
@ -148,7 +148,7 @@ export const loadMyFeed = async (
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
options.limit += 1 options.limit += 1
const newShouts = await apiClient.getMyFeed(options) const newShouts = await apiClient.getMyFeed(options)
const hasMore = newShouts ?? newShouts.length === options.limit + 1 const hasMore = newShouts?.length === options.limit + 1
if (hasMore) { if (hasMore) {
newShouts.splice(-1) newShouts.splice(-1)
@ -165,7 +165,7 @@ export const loadShoutsSearch = async (
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
options.limit += 1 options.limit += 1
const newShouts = await apiClient.getShoutsSearch(options) const newShouts = await apiClient.getShoutsSearch(options)
const hasMore = newShouts ?? newShouts.length === options.limit + 1 const hasMore = newShouts?.length === options.limit + 1
if (hasMore) { if (hasMore) {
newShouts.splice(-1) newShouts.splice(-1)
@ -193,7 +193,7 @@ export const loadTopMonthArticles = async (): Promise<void> => {
const after = Math.floor(daysago / 1000) const after = Math.floor(daysago / 1000)
const options: LoadShoutsOptions = { const options: LoadShoutsOptions = {
filters: { filters: {
published: true, featured: true,
after, after,
}, },
order_by: 'likes_stat', order_by: 'likes_stat',
@ -208,7 +208,7 @@ const TOP_ARTICLES_COUNT = 10
export const loadTopArticles = async (): Promise<void> => { export const loadTopArticles = async (): Promise<void> => {
const options: LoadShoutsOptions = { const options: LoadShoutsOptions = {
filters: { published: true }, filters: { featured: true },
order_by: 'likes_stat', order_by: 'likes_stat',
limit: TOP_ARTICLES_COUNT, limit: TOP_ARTICLES_COUNT,
} }

View File

@ -29,7 +29,7 @@ export const resetSortedLayoutShouts = () => {
export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => { export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => {
options.limit += 1 options.limit += 1
const newLayoutShouts = await apiClient.getShouts(options) const newLayoutShouts = await apiClient.getShouts(options)
const hasMore = newLayoutShouts.length === options.limit + 1 const hasMore = newLayoutShouts?.length === options.limit + 1
if (hasMore) { if (hasMore) {
newLayoutShouts.splice(-1) newLayoutShouts.splice(-1)

View File

@ -6,6 +6,10 @@ export const byCreated = (a: Shout | Reaction, b: Shout | Reaction) => {
return a?.created_at - b?.created_at return a?.created_at - b?.created_at
} }
export const byPublished = (a: Shout, b: Shout) => {
return a.published_at - b.published_at
}
export const byLength = ( export const byLength = (
a: (Shout | Reaction | Topic | Author)[], a: (Shout | Reaction | Topic | Author)[],
b: (Shout | Reaction | Topic | Author)[], b: (Shout | Reaction | Topic | Author)[],