fix search modal behavior

This commit is contained in:
dog 2024-01-25 18:06:26 +03:00
parent afed10dfd2
commit f0dd3c9c88
10 changed files with 1023 additions and 156 deletions

View File

@ -214,13 +214,13 @@ export const ArticleCard = (props: ArticleCardProps) => {
<a href={getPagePath(router, 'article', { slug: props.article.slug })}>
<div class={styles.shoutCardTitle}>
<span class={styles.shoutCardLinkWrapper}>
<span class={styles.shoutCardLinkContainer}>{title}</span>
<span class={styles.shoutCardLinkContainer} innerHTML={title} />
</span>
</div>
<Show when={!props.settings?.nosubtitle && subtitle}>
<div class={styles.shoutCardSubtitle}>
<span class={styles.shoutCardLinkContainer}>{subtitle}</span>
<span class={styles.shoutCardLinkContainer} innerHTML={subtitle} />
</div>
</Show>
</a>

View File

@ -128,10 +128,10 @@ export const HeaderAuth = (props: Props) => {
<Show when={!isSaveButtonVisible()}>
<div class={styles.userControlItem}>
<button onClick={() => showModal('search')}>
<a href="?modal=search">
<Icon name="search" class={styles.icon} />
<Icon name="search" class={clsx(styles.icon, styles.iconHover)} />
</button>
</a>
</div>
</Show>

View File

@ -55,7 +55,7 @@ export const Modal = (props: Props) => {
return (
<Show when={visible()}>
<div
class={clsx(styles.backdrop, {
class={clsx(styles.backdrop, [styles[`modal-${props.name}`]], {
[styles.isMobile]: isMobileView(),
})}
onClick={handleHide}

View File

@ -1,13 +1,14 @@
@mixin search-filter-control {
background: rgb(64 64 64 / 50%);
border-radius: 10rem;
color: #fff;
@mixin searchFilterControl {
@include font-size(1.4rem);
font-weight: 500;
height: 4rem;
padding: 0 2rem;
background: rgb(64 64 64 / 0.5);
border-radius: 10rem;
color: #fff;
font-weight: 500;
white-space: nowrap;
&:hover {
@ -15,49 +16,60 @@
}
&:active {
color: rgb(255 255 255 / 40%);
color: rgb(255 255 255 / 0.4);
}
}
.searchForm {
.searchContainer {
position: relative;
}
.searchField {
background: none;
border: none;
border-bottom: 2px solid #fff;
color: #fff;
.searchInput {
@include font-size(4.8rem);
@include font-size(4.8rem);
width: 100%;
font-weight: bold;
outline: none;
padding: 0 0 0.5rem;
padding: 0 0 0.5rem;
&::placeholder {
color: rgb(255 255 255 / 32%);
}
background: none;
border: none;
border-bottom: 2px solid #fff;
color: #fff;
font-weight: bold;
outline: none;
&:not(:placeholder-shown) + .submitControl {
display: block;
}
&::placeholder {
color: rgb(255 255 255 / 0.32);
}
&:not(:placeholder-shown) + .searchButton img {
filter: invert(1);
}
&::-moz-selection,
&::selection {
color: #2638d9;
}
}
.submitControl {
display: none;
filter: invert(1);
height: 3.2rem;
.searchButton {
position: absolute;
right: 0;
top: 2rem;
width: 3.2rem;
height: 3.2rem;
& img {
filter: invert(0.4);
}
}
.searchDescription {
color: rgb(255 255 255 / 64%);
margin-bottom: 44px;
@include font-size(1.6rem);
color: rgb(255 255 255 / 0.64);
}
.topicsList {
@ -65,11 +77,12 @@
flex-wrap: wrap;
justify-content: center;
gap: 1rem;
margin-top: 9.6rem !important;
}
.topTopic {
@include search-filter-control;
@include searchFilterControl;
}
.filterSwitcher {
@ -95,9 +108,31 @@
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin: 6.4rem 0;
}
.filterResultsControl {
@include search-filter-control;
@include searchFilterControl;
}
.searchLoader {
width: 28px;
height: 28px;
border: 5px solid #fff;
border-bottom-color: transparent;
border-radius: 50%;
animation: rotation 1s linear infinite;
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View File

@ -1,140 +1,174 @@
import { openPage } from '@nanostores/router'
import { clsx } from 'clsx'
import { createSignal, Show, For } from 'solid-js'
import { Button } from '../../_shared/Button'
import { Icon } from '../../_shared/Icon'
import { SearchResultItem } from './SearchResultItem'
import { apiClient } from '../../../utils/apiClient'
import type { Shout } from '../../../graphql/schema/core.gen'
import { useLocalize } from '../../../context/localize'
import { router, useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
import { Icon } from '../../_shared/Icon'
import styles from './SearchModal.module.scss'
// @@TODO handle empty article options after backend support (subtitle, cover, etc.)
// @@TODO implement load more
// @@TODO implement FILTERS & TOPICS
const getSearchCoincidences = ({ str, intersection }: { str: string; intersection: string }) =>
`<span>${str.replace(
new RegExp(intersection, 'gi'),
(casePreservedMatch) => `<span class="blackModeIntersection">${casePreservedMatch}</span>`,
)}</span>`
const prepareSearchResults = (list, searchValue) =>
list.map((article, index) => ({
...article,
body: '',
cover: '',
createdAt: '',
id: index,
slug: article.slug,
authors: [],
topics: [],
title: article.title
? getSearchCoincidences({
str: article.title,
intersection: searchValue,
})
: '',
subtitle: article.subtitle
? getSearchCoincidences({
str: article.subtitle,
intersection: searchValue,
})
: '',
}))
export const SearchModal = () => {
const { t } = useLocalize()
const { changeSearchParams } = useRouter()
let qElement: HTMLInputElement | undefined
const submitQuery = async (ev) => {
ev.preventDefault()
changeSearchParams({}, true)
hideModal()
openPage(router, 'search', { q: qElement.value })
const [inputValue, setInputValue] = createSignal('')
const [searchResultsList, setSearchResultsList] = createSignal<[] | null>([])
const [isLoading, setIsLoading] = createSignal(false)
// const [isLoadMoreButtonVisible, setIsLoadMoreButtonVisible] = createSignal(false)
const handleSearch = async () => {
const searchValue = inputValue() || ''
if (Boolean(searchValue) && searchValue.length > 2) {
setIsLoading(true)
try {
const response = await apiClient.getSearchResults(searchValue)
const searchResult = await response.json()
if (searchResult.length > 0) {
const preparedSearchResultsList = prepareSearchResults(searchResult, searchValue)
setSearchResultsList(preparedSearchResultsList)
} else {
setSearchResultsList(null)
}
} catch (error) {
console.log('search request failed', error)
} finally {
setIsLoading(false)
}
}
}
return (
<form onSubmit={submitQuery} class={styles.searchForm}>
<div class={styles.searchContainer}>
<input
type="text"
name="q"
type="search"
placeholder={t('Site search')}
ref={qElement}
class={styles.searchField}
class={styles.searchInput}
onInput={(event) => {
setInputValue(event.target.value)
handleSearch()
}}
/>
<button type="submit" class={styles.submitControl}>
<Icon name="search" />
</button>
<p class={styles.searchDescription}>
Для поиска публикаций, искусства, комментариев, интересных вам авторов и&nbsp;тем, просто начните
вводить ваш запрос
</p>
<ul class={clsx('view-switcher', styles.filterSwitcher)}>
<li class="view-switcher__item view-switcher__item--selected">
<button type="button">{t('All')}</button>
</li>
<li class="view-switcher__item">
<button type="button">{t('Publications')}</button>
</li>
<li class="view-switcher__item">
<button type="button">{t('Topics')}</button>
</li>
</ul>
<Button
class={styles.searchButton}
onClick={handleSearch}
value={isLoading() ? <div class={styles.searchLoader} /> : <Icon name="search" />}
/>
<div class={styles.filterResults}>
<button type="button" class={styles.filterResultsControl}>
Период времени
</button>
<button type="button" class={styles.filterResultsControl}>
Рейтинг
</button>
<button type="button" class={styles.filterResultsControl}>
Тип постов
</button>
<button type="button" class={styles.filterResultsControl}>
Темы
</button>
<button type="button" class={styles.filterResultsControl}>
Авторы
</button>
<button type="button" class={styles.filterResultsControl}>
Сообщества
</button>
</div>
<p
class={styles.searchDescription}
innerHTML={t(
'To find publications, art, comments, authors and topics of interest to you, just start typing your query',
)}
/>
<div class="container-xl">
<div class="row">
<div class={clsx('col-md-18 offset-md-2', styles.topicsList)}>
<button type="button" class={styles.topTopic}>
За месяц
</button>
<button type="button" class={styles.topTopic}>
#репортажи
</button>
<button type="button" class={styles.topTopic}>
#интервью
</button>
<button type="button" class={styles.topTopic}>
#культура
</button>
<button type="button" class={styles.topTopic}>
#поэзия
</button>
<button type="button" class={styles.topTopic}>
#теории
</button>
<button type="button" class={styles.topTopic}>
#война в украине
</button>
<button type="button" class={styles.topTopic}>
#общество
</button>
<button type="button" class={styles.topTopic}>
#Экспериментальная Музыка
</button>
<button type="button" class={styles.topTopic}>
Рейтинг 300+
</button>
<button type="button" class={styles.topTopic}>
#Протесты
</button>
<button type="button" class={styles.topTopic}>
Музыка
</button>
<button type="button" class={styles.topTopic}>
#За линией Маннергейма
</button>
<button type="button" class={styles.topTopic}>
Тесты
</button>
<button type="button" class={styles.topTopic}>
Коллективные истории
</button>
<button type="button" class={styles.topTopic}>
#личный опыт
</button>
<button type="button" class={styles.topTopic}>
Тоня Самсонова
</button>
<button type="button" class={styles.topTopic}>
#личный опыт
</button>
<button type="button" class={styles.topTopic}>
#Секс
</button>
<button type="button" class={styles.topTopic}>
Молоко Plus
</button>
<Show when={!isLoading()}>
<Show when={searchResultsList()}>
<For each={searchResultsList()}>
{(article: Shout) => (
<div>
<SearchResultItem
article={article}
settings={{
noimage: true, // @@TODO remove flag after cover support
isFloorImportant: true,
isSingle: true,
nodate: true,
}}
/>
</div>
)}
</For>
{/* <Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container">
<button class="button" onClick={loadMore}>
{t('Load more')}
</button>
</p>
</Show> */}
</Show>
<Show when={!searchResultsList()}>
<p class={styles.searchDescription} innerHTML={t("We couldn't find anything for your request")} />
</Show>
</Show>
{/* @@TODO handle filter */}
{/* <Show when={FILTERS.length}>
<div class={styles.filterResults}>
<For each={FILTERS}>
{(filter) => (
<button
type="button"
class={styles.filterResultsControl}
onClick={() => setActiveFilter(filter)}
>
{filter.name}
</button>
)}
</For>
</div>
</Show> */}
{/* @@TODO handle topics */}
{/* <Show when={TOPICS.length}>
<div class="container-xl">
<div class="row">
<div class={clsx('col-md-18 offset-md-2', styles.topicsList)}>
<For each={TOPICS}>
{(topic) => (
<button type="button" class={styles.topTopic} onClick={() => setActiveTopic(topic)}>
{topic.name}
</button>
)}
</For>
</div>
</div>
</div>
</div>
</form>
</Show> */}
</div>
)
}

View File

@ -0,0 +1,33 @@
import { ArticleCard } from '../../Feed/ArticleCard'
import type { Shout } from '../../../graphql/schema/core.gen'
interface SearchCardProps {
settings?: {
noicon?: boolean
noimage?: boolean
nosubtitle?: boolean
noauthor?: boolean
nodate?: boolean
isGroup?: boolean
photoBottom?: boolean
additionalClass?: string
isFeedMode?: boolean
isFloorImportant?: boolean
isWithCover?: boolean
isBigTitle?: boolean
isVertical?: boolean
isShort?: boolean
withBorder?: boolean
isCompact?: boolean
isSingle?: boolean
isBeside?: boolean
withViewed?: boolean
noAuthorLink?: boolean
}
article: Shout
}
export const SearchResultItem = (props: SearchCardProps) => {
return <ArticleCard article={props.article} settings={props.settings} />
}

726
src/graphql/types.gen.ts Normal file
View File

@ -0,0 +1,726 @@
import gql from 'graphql-tag'
export type Maybe<T> = T | null
export type InputMaybe<T> = Maybe<T>
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }
export type MakeEmpty<T extends { [key: string]: unknown }, K extends keyof T> = { [_ in K]?: never }
export type Incremental<T> =
| T
| { [P in keyof T]?: P extends ' $fragmentName' | '__typename' ? T[P] : never }
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: { input: string; output: string }
String: { input: string; output: string }
Boolean: { input: boolean; output: boolean }
Int: { input: number; output: number }
Float: { input: number; output: number }
DateTime: { input: any; output: any }
}
export type AuthResult = {
error?: Maybe<Scalars['String']['output']>
token?: Maybe<Scalars['String']['output']>
user?: Maybe<User>
}
export type Author = {
about?: Maybe<Scalars['String']['output']>
bio?: Maybe<Scalars['String']['output']>
caption?: Maybe<Scalars['String']['output']>
createdAt?: Maybe<Scalars['DateTime']['output']>
id: Scalars['Int']['output']
lastSeen?: Maybe<Scalars['DateTime']['output']>
links?: Maybe<Array<Maybe<Scalars['String']['output']>>>
name: Scalars['String']['output']
roles?: Maybe<Array<Maybe<Role>>>
slug: Scalars['String']['output']
stat?: Maybe<AuthorStat>
userpic?: Maybe<Scalars['String']['output']>
}
export type AuthorStat = {
commented?: Maybe<Scalars['Int']['output']>
followers?: Maybe<Scalars['Int']['output']>
followings?: Maybe<Scalars['Int']['output']>
rating?: Maybe<Scalars['Int']['output']>
shouts?: Maybe<Scalars['Int']['output']>
}
export type AuthorsBy = {
createdAt?: InputMaybe<Scalars['DateTime']['input']>
days?: InputMaybe<Scalars['Int']['input']>
lastSeen?: InputMaybe<Scalars['DateTime']['input']>
name?: InputMaybe<Scalars['String']['input']>
order?: InputMaybe<Scalars['String']['input']>
slug?: InputMaybe<Scalars['String']['input']>
stat?: InputMaybe<Scalars['String']['input']>
topic?: InputMaybe<Scalars['String']['input']>
}
export type Chat = {
admins?: Maybe<Array<Maybe<Scalars['Int']['output']>>>
createdAt: Scalars['Int']['output']
createdBy: Scalars['Int']['output']
description?: Maybe<Scalars['String']['output']>
id: Scalars['String']['output']
members?: Maybe<Array<Maybe<ChatMember>>>
messages?: Maybe<Array<Maybe<Message>>>
private?: Maybe<Scalars['Boolean']['output']>
title?: Maybe<Scalars['String']['output']>
unread?: Maybe<Scalars['Int']['output']>
updatedAt: Scalars['Int']['output']
users?: Maybe<Array<Maybe<Scalars['Int']['output']>>>
}
export type ChatInput = {
description?: InputMaybe<Scalars['String']['input']>
id: Scalars['String']['input']
title?: InputMaybe<Scalars['String']['input']>
}
export type ChatMember = {
id: Scalars['Int']['output']
lastSeen?: Maybe<Scalars['DateTime']['output']>
name: Scalars['String']['output']
online?: Maybe<Scalars['Boolean']['output']>
slug: Scalars['String']['output']
userpic?: Maybe<Scalars['String']['output']>
}
export type Collection = {
amount?: Maybe<Scalars['Int']['output']>
createdAt: Scalars['DateTime']['output']
createdBy: User
desc?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
publishedAt?: Maybe<Scalars['DateTime']['output']>
slug: Scalars['String']['output']
title: Scalars['String']['output']
}
export type Community = {
createdAt: Scalars['DateTime']['output']
createdBy: User
desc?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
name: Scalars['String']['output']
pic: Scalars['String']['output']
slug: Scalars['String']['output']
}
export enum FollowingEntity {
Author = 'AUTHOR',
Community = 'COMMUNITY',
Reactions = 'REACTIONS',
Topic = 'TOPIC',
}
export type LoadRandomTopShoutsParams = {
filters?: InputMaybe<LoadShoutsFilters>
fromRandomCount?: InputMaybe<Scalars['Int']['input']>
limit: Scalars['Int']['input']
}
export type LoadShoutsFilters = {
author?: InputMaybe<Scalars['String']['input']>
excludeLayout?: InputMaybe<Scalars['String']['input']>
fromDate?: InputMaybe<Scalars['String']['input']>
layout?: InputMaybe<Scalars['String']['input']>
reacted?: InputMaybe<Scalars['Boolean']['input']>
toDate?: InputMaybe<Scalars['String']['input']>
topic?: InputMaybe<Scalars['String']['input']>
visibility?: InputMaybe<Scalars['String']['input']>
}
export type LoadShoutsOptions = {
filters?: InputMaybe<LoadShoutsFilters>
limit: Scalars['Int']['input']
offset?: InputMaybe<Scalars['Int']['input']>
order_by?: InputMaybe<Scalars['String']['input']>
order_by_desc?: InputMaybe<Scalars['Boolean']['input']>
with_author_captions?: InputMaybe<Scalars['Boolean']['input']>
}
export type Message = {
author: Scalars['Int']['output']
body: Scalars['String']['output']
chatId: Scalars['String']['output']
createdAt: Scalars['Int']['output']
id: Scalars['Int']['output']
replyTo?: Maybe<Scalars['Int']['output']>
seen?: Maybe<Scalars['Boolean']['output']>
updatedAt?: Maybe<Scalars['Int']['output']>
}
export enum MessageStatus {
Deleted = 'DELETED',
New = 'NEW',
Updated = 'UPDATED',
}
export type MessagesBy = {
author?: InputMaybe<Scalars['String']['input']>
body?: InputMaybe<Scalars['String']['input']>
chat?: InputMaybe<Scalars['String']['input']>
days?: InputMaybe<Scalars['Int']['input']>
order?: InputMaybe<Scalars['String']['input']>
stat?: InputMaybe<Scalars['String']['input']>
}
export type Mutation = {
confirmEmail: AuthResult
createChat: Result
createMessage: Result
createReaction: Result
createShout: Result
createTopic: Result
deleteChat: Result
deleteMessage: Result
deleteReaction: Result
deleteShout: Result
destroyTopic: Result
follow: Result
getSession: AuthResult
markAllNotificationsAsRead: Result
markAsRead: Result
markNotificationAsRead: Result
rateUser: Result
registerUser: AuthResult
sendLink: Result
unfollow: Result
updateChat: Result
updateMessage: Result
updateProfile: Result
updateReaction: Result
updateShout: Result
updateTopic: Result
}
export type MutationConfirmEmailArgs = {
token: Scalars['String']['input']
}
export type MutationCreateChatArgs = {
members: Array<InputMaybe<Scalars['Int']['input']>>
title?: InputMaybe<Scalars['String']['input']>
}
export type MutationCreateMessageArgs = {
body: Scalars['String']['input']
chat: Scalars['String']['input']
replyTo?: InputMaybe<Scalars['Int']['input']>
}
export type MutationCreateReactionArgs = {
reaction: ReactionInput
}
export type MutationCreateShoutArgs = {
inp: ShoutInput
}
export type MutationCreateTopicArgs = {
input: TopicInput
}
export type MutationDeleteChatArgs = {
chatId: Scalars['String']['input']
}
export type MutationDeleteMessageArgs = {
chatId: Scalars['String']['input']
id: Scalars['Int']['input']
}
export type MutationDeleteReactionArgs = {
id: Scalars['Int']['input']
}
export type MutationDeleteShoutArgs = {
shout_id: Scalars['Int']['input']
}
export type MutationDestroyTopicArgs = {
slug: Scalars['String']['input']
}
export type MutationFollowArgs = {
slug: Scalars['String']['input']
what: FollowingEntity
}
export type MutationMarkAsReadArgs = {
chatId: Scalars['String']['input']
ids: Array<InputMaybe<Scalars['Int']['input']>>
}
export type MutationMarkNotificationAsReadArgs = {
notification_id: Scalars['Int']['input']
}
export type MutationRateUserArgs = {
slug: Scalars['String']['input']
value: Scalars['Int']['input']
}
export type MutationRegisterUserArgs = {
email: Scalars['String']['input']
name?: InputMaybe<Scalars['String']['input']>
password?: InputMaybe<Scalars['String']['input']>
}
export type MutationSendLinkArgs = {
email: Scalars['String']['input']
lang?: InputMaybe<Scalars['String']['input']>
template?: InputMaybe<Scalars['String']['input']>
}
export type MutationUnfollowArgs = {
slug: Scalars['String']['input']
what: FollowingEntity
}
export type MutationUpdateChatArgs = {
chat: ChatInput
}
export type MutationUpdateMessageArgs = {
body: Scalars['String']['input']
chatId: Scalars['String']['input']
id: Scalars['Int']['input']
}
export type MutationUpdateProfileArgs = {
profile: ProfileInput
}
export type MutationUpdateReactionArgs = {
id: Scalars['Int']['input']
reaction: ReactionInput
}
export type MutationUpdateShoutArgs = {
publish?: InputMaybe<Scalars['Boolean']['input']>
shout_id: Scalars['Int']['input']
shout_input?: InputMaybe<ShoutInput>
}
export type MutationUpdateTopicArgs = {
input: TopicInput
}
export type MySubscriptionsQueryResult = {
authors: Array<Maybe<Author>>
topics: Array<Maybe<Topic>>
}
export type Notification = {
createdAt: Scalars['DateTime']['output']
data?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
occurrences: Scalars['Int']['output']
reaction?: Maybe<Scalars['Int']['output']>
seen: Scalars['Boolean']['output']
shout?: Maybe<Scalars['Int']['output']>
type: NotificationType
}
export enum NotificationType {
NewComment = 'NEW_COMMENT',
NewReply = 'NEW_REPLY',
}
export type NotificationsQueryParams = {
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type NotificationsQueryResult = {
notifications: Array<Maybe<Notification>>
totalCount: Scalars['Int']['output']
totalUnreadCount: Scalars['Int']['output']
}
export type Operation = {
id: Scalars['Int']['output']
name: Scalars['String']['output']
}
export type Permission = {
operation: Scalars['Int']['output']
resource: Scalars['Int']['output']
}
export type ProfileInput = {
about?: InputMaybe<Scalars['String']['input']>
bio?: InputMaybe<Scalars['String']['input']>
links?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>
name?: InputMaybe<Scalars['String']['input']>
slug?: InputMaybe<Scalars['String']['input']>
userpic?: InputMaybe<Scalars['String']['input']>
}
export type Query = {
authorsAll: Array<Maybe<Author>>
getAuthor?: Maybe<Author>
getTopic?: Maybe<Topic>
isEmailUsed: Scalars['Boolean']['output']
loadAuthorsBy: Array<Maybe<Author>>
loadChats: Result
loadDrafts: Array<Maybe<Shout>>
loadMessagesBy: Result
loadMySubscriptions?: Maybe<MySubscriptionsQueryResult>
loadNotifications: NotificationsQueryResult
loadRandomTopShouts: Array<Maybe<Shout>>
loadRandomTopicShouts: RandomTopicShoutsQueryResult
loadReactionsBy: Array<Maybe<Reaction>>
loadRecipients: Result
loadShout?: Maybe<Shout>
loadShouts: Array<Maybe<Shout>>
loadUnratedShouts: Array<Maybe<Shout>>
markdownBody: Scalars['String']['output']
myFeed?: Maybe<Array<Maybe<Shout>>>
searchMessages: Result
searchRecipients: Result
signIn: AuthResult
signOut: AuthResult
topicsAll: Array<Maybe<Topic>>
topicsByAuthor: Array<Maybe<Topic>>
topicsByCommunity: Array<Maybe<Topic>>
topicsRandom: Array<Maybe<Topic>>
userFollowedAuthors: Array<Maybe<Author>>
userFollowedTopics: Array<Maybe<Topic>>
userFollowers: Array<Maybe<Author>>
}
export type QueryGetAuthorArgs = {
slug: Scalars['String']['input']
}
export type QueryGetTopicArgs = {
slug: Scalars['String']['input']
}
export type QueryIsEmailUsedArgs = {
email: Scalars['String']['input']
}
export type QueryLoadAuthorsByArgs = {
by?: InputMaybe<AuthorsBy>
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QueryLoadChatsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QueryLoadMessagesByArgs = {
by: MessagesBy
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QueryLoadNotificationsArgs = {
params: NotificationsQueryParams
}
export type QueryLoadRandomTopShoutsArgs = {
params?: InputMaybe<LoadRandomTopShoutsParams>
}
export type QueryLoadRandomTopicShoutsArgs = {
limit: Scalars['Int']['input']
}
export type QueryLoadReactionsByArgs = {
by: ReactionBy
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QueryLoadRecipientsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QueryLoadShoutArgs = {
shout_id?: InputMaybe<Scalars['Int']['input']>
slug?: InputMaybe<Scalars['String']['input']>
}
export type QueryLoadShoutsArgs = {
options?: InputMaybe<LoadShoutsOptions>
}
export type QueryLoadUnratedShoutsArgs = {
limit: Scalars['Int']['input']
}
export type QueryMarkdownBodyArgs = {
body: Scalars['String']['input']
}
export type QueryMyFeedArgs = {
options?: InputMaybe<LoadShoutsOptions>
}
export type QuerySearchMessagesArgs = {
by: MessagesBy
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
}
export type QuerySearchRecipientsArgs = {
limit?: InputMaybe<Scalars['Int']['input']>
offset?: InputMaybe<Scalars['Int']['input']>
query: Scalars['String']['input']
}
export type QuerySignInArgs = {
email: Scalars['String']['input']
lang?: InputMaybe<Scalars['String']['input']>
password?: InputMaybe<Scalars['String']['input']>
}
export type QueryTopicsByAuthorArgs = {
author: Scalars['String']['input']
}
export type QueryTopicsByCommunityArgs = {
community: Scalars['String']['input']
}
export type QueryTopicsRandomArgs = {
amount?: InputMaybe<Scalars['Int']['input']>
}
export type QueryUserFollowedAuthorsArgs = {
slug: Scalars['String']['input']
}
export type QueryUserFollowedTopicsArgs = {
slug: Scalars['String']['input']
}
export type QueryUserFollowersArgs = {
slug: Scalars['String']['input']
}
export type RandomTopicShoutsQueryResult = {
shouts: Array<Maybe<Shout>>
topic: Topic
}
export type Rating = {
rater: Scalars['String']['output']
value: Scalars['Int']['output']
}
export type Reaction = {
body?: Maybe<Scalars['String']['output']>
createdAt: Scalars['DateTime']['output']
createdBy: User
deletedAt?: Maybe<Scalars['DateTime']['output']>
deletedBy?: Maybe<User>
id: Scalars['Int']['output']
kind: ReactionKind
old_id?: Maybe<Scalars['String']['output']>
old_thread?: Maybe<Scalars['String']['output']>
range?: Maybe<Scalars['String']['output']>
replyTo?: Maybe<Scalars['Int']['output']>
shout: Shout
stat?: Maybe<Stat>
updatedAt?: Maybe<Scalars['DateTime']['output']>
}
export type ReactionBy = {
comment?: InputMaybe<Scalars['Boolean']['input']>
createdBy?: InputMaybe<Scalars['String']['input']>
days?: InputMaybe<Scalars['Int']['input']>
search?: InputMaybe<Scalars['String']['input']>
shout?: InputMaybe<Scalars['String']['input']>
shouts?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>
sort?: InputMaybe<Scalars['String']['input']>
topic?: InputMaybe<Scalars['String']['input']>
}
export type ReactionInput = {
body?: InputMaybe<Scalars['String']['input']>
kind: ReactionKind
range?: InputMaybe<Scalars['String']['input']>
replyTo?: InputMaybe<Scalars['Int']['input']>
shout: Scalars['Int']['input']
}
export enum ReactionKind {
Accept = 'ACCEPT',
Agree = 'AGREE',
Ask = 'ASK',
Comment = 'COMMENT',
Disagree = 'DISAGREE',
Dislike = 'DISLIKE',
Disproof = 'DISPROOF',
Footnote = 'FOOTNOTE',
Like = 'LIKE',
Proof = 'PROOF',
Propose = 'PROPOSE',
Quote = 'QUOTE',
Reject = 'REJECT',
Remark = 'REMARK',
}
export enum ReactionStatus {
Changed = 'CHANGED',
Deleted = 'DELETED',
Explained = 'EXPLAINED',
New = 'NEW',
Updated = 'UPDATED',
}
export type ReactionUpdating = {
error?: Maybe<Scalars['String']['output']>
reaction?: Maybe<Reaction>
status?: Maybe<ReactionStatus>
}
export type Resource = {
id: Scalars['Int']['output']
name: Scalars['String']['output']
}
export type Result = {
author?: Maybe<Author>
authors?: Maybe<Array<Maybe<Author>>>
chat?: Maybe<Chat>
chats?: Maybe<Array<Maybe<Chat>>>
communities?: Maybe<Array<Maybe<Community>>>
community?: Maybe<Community>
error?: Maybe<Scalars['String']['output']>
members?: Maybe<Array<Maybe<ChatMember>>>
message?: Maybe<Message>
messages?: Maybe<Array<Maybe<Message>>>
reaction?: Maybe<Reaction>
reactions?: Maybe<Array<Maybe<Reaction>>>
shout?: Maybe<Shout>
shouts?: Maybe<Array<Maybe<Shout>>>
slugs?: Maybe<Array<Maybe<Scalars['String']['output']>>>
topic?: Maybe<Topic>
topics?: Maybe<Array<Maybe<Topic>>>
}
export type Role = {
community: Scalars['String']['output']
desc?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
name: Scalars['String']['output']
permissions: Array<Permission>
}
export type Shout = {
authors?: Maybe<Array<Maybe<Author>>>
body: Scalars['String']['output']
community?: Maybe<Scalars['String']['output']>
cover?: Maybe<Scalars['String']['output']>
createdAt: Scalars['DateTime']['output']
deletedAt?: Maybe<Scalars['DateTime']['output']>
deletedBy?: Maybe<User>
description?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
lang?: Maybe<Scalars['String']['output']>
layout?: Maybe<Scalars['String']['output']>
lead?: Maybe<Scalars['String']['output']>
mainTopic?: Maybe<Scalars['String']['output']>
media?: Maybe<Scalars['String']['output']>
publishedAt?: Maybe<Scalars['DateTime']['output']>
slug: Scalars['String']['output']
stat?: Maybe<Stat>
subtitle?: Maybe<Scalars['String']['output']>
title?: Maybe<Scalars['String']['output']>
topics?: Maybe<Array<Maybe<Topic>>>
updatedAt?: Maybe<Scalars['DateTime']['output']>
updatedBy?: Maybe<User>
versionOf?: Maybe<Scalars['String']['output']>
visibility?: Maybe<Scalars['String']['output']>
}
export type ShoutInput = {
authors?: InputMaybe<Array<InputMaybe<Scalars['String']['input']>>>
body?: InputMaybe<Scalars['String']['input']>
community?: InputMaybe<Scalars['Int']['input']>
cover?: InputMaybe<Scalars['String']['input']>
description?: InputMaybe<Scalars['String']['input']>
layout?: InputMaybe<Scalars['String']['input']>
lead?: InputMaybe<Scalars['String']['input']>
mainTopic?: InputMaybe<TopicInput>
media?: InputMaybe<Scalars['String']['input']>
slug?: InputMaybe<Scalars['String']['input']>
subtitle?: InputMaybe<Scalars['String']['input']>
title?: InputMaybe<Scalars['String']['input']>
topics?: InputMaybe<Array<InputMaybe<TopicInput>>>
}
export type Stat = {
commented?: Maybe<Scalars['Int']['output']>
ranking?: Maybe<Scalars['Int']['output']>
rating?: Maybe<Scalars['Int']['output']>
reacted?: Maybe<Scalars['Int']['output']>
viewed?: Maybe<Scalars['Int']['output']>
}
export type Token = {
createdAt: Scalars['DateTime']['output']
expiresAt?: Maybe<Scalars['DateTime']['output']>
id: Scalars['Int']['output']
ownerId: Scalars['Int']['output']
usedAt?: Maybe<Scalars['DateTime']['output']>
value: Scalars['String']['output']
}
export type Topic = {
body?: Maybe<Scalars['String']['output']>
id: Scalars['Int']['output']
oid?: Maybe<Scalars['String']['output']>
pic?: Maybe<Scalars['String']['output']>
slug: Scalars['String']['output']
stat?: Maybe<TopicStat>
title?: Maybe<Scalars['String']['output']>
}
export type TopicInput = {
body?: InputMaybe<Scalars['String']['input']>
id?: InputMaybe<Scalars['Int']['input']>
pic?: InputMaybe<Scalars['String']['input']>
slug: Scalars['String']['input']
title?: InputMaybe<Scalars['String']['input']>
}
export type TopicStat = {
authors: Scalars['Int']['output']
followers: Scalars['Int']['output']
shouts: Scalars['Int']['output']
}
export type User = {
about?: Maybe<Scalars['String']['output']>
bio?: Maybe<Scalars['String']['output']>
communities?: Maybe<Array<Maybe<Scalars['Int']['output']>>>
createdAt: Scalars['DateTime']['output']
email?: Maybe<Scalars['String']['output']>
emailConfirmed?: Maybe<Scalars['Boolean']['output']>
id: Scalars['Int']['output']
lastSeen?: Maybe<Scalars['DateTime']['output']>
links?: Maybe<Array<Maybe<Scalars['String']['output']>>>
muted?: Maybe<Scalars['Boolean']['output']>
name?: Maybe<Scalars['String']['output']>
oauth?: Maybe<Scalars['String']['output']>
oid?: Maybe<Scalars['String']['output']>
password?: Maybe<Scalars['String']['output']>
ratings?: Maybe<Array<Maybe<Rating>>>
slug: Scalars['String']['output']
updatedAt?: Maybe<Scalars['DateTime']['output']>
username: Scalars['String']['output']
userpic?: Maybe<Scalars['String']['output']>
}

View File

@ -1071,3 +1071,8 @@ iframe {
.img-align-column {
clear: both;
}
.blackModeIntersection {
color: var(--default-color);
background: #fef2f2;
}

31
src/utils/apiClient.ts Normal file
View File

@ -0,0 +1,31 @@
import { searchUrl } from './config'
type ApiErrorCode =
| 'unknown'
| 'email_not_confirmed'
| 'user_not_found'
| 'user_already_exists'
| 'token_expired'
| 'token_invalid'
| 'duplicate_slug'
export class ApiError extends Error {
code: ApiErrorCode
constructor(code: ApiErrorCode, message?: string) {
super(message)
this.code = code
}
}
export const apiClient = {
getSearchResults: async (searchValue: string) => {
return await fetch(`${searchUrl}/search?q=${searchValue}`, {
method: 'GET',
headers: {
accept: 'application/json',
'content-type': 'application/json; charset=utf-8',
},
})
},
}

View File

@ -5,3 +5,6 @@ export const cdnUrl = 'https://cdn.discours.io'
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
const defaultSearchUrl = 'https://search.discours.io'
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl