diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 0a6fcf1c..0148697d 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -214,13 +214,13 @@ export const ArticleCard = (props: ArticleCardProps) => {
- {title} +
- {subtitle} +
diff --git a/src/components/Nav/HeaderAuth.tsx b/src/components/Nav/HeaderAuth.tsx index 44b3f17a..20b51d70 100644 --- a/src/components/Nav/HeaderAuth.tsx +++ b/src/components/Nav/HeaderAuth.tsx @@ -128,10 +128,10 @@ export const HeaderAuth = (props: Props) => {
- +
diff --git a/src/components/Nav/Modal/Modal.tsx b/src/components/Nav/Modal/Modal.tsx index 24f9c887..4e25cbfb 100644 --- a/src/components/Nav/Modal/Modal.tsx +++ b/src/components/Nav/Modal/Modal.tsx @@ -55,7 +55,7 @@ export const Modal = (props: Props) => { return (
+ `${str.replace( + new RegExp(intersection, 'gi'), + (casePreservedMatch) => `${casePreservedMatch}`, + )}` + +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 ( -
+
{ + setInputValue(event.target.value) + + handleSearch() + }} /> - -

- Для поиска публикаций, искусства, комментариев, интересных вам авторов и тем, просто начните - вводить ваш запрос -

-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
+ - - - - - -
+

-

-
-
- - - - - - - - - - - - - - - - - - - - + + + + {(article: Shout) => ( +
+ +
+ )} +
+ + {/* +

+ +

+
*/} +
+ + +

+ + + + {/* @@TODO handle filter */} + {/* +

+ + {(filter) => ( + + )} + +
+
*/} + + {/* @@TODO handle topics */} + {/* +
+
+
+ + {(topic) => ( + + )} + +
-
- + */} +
) } diff --git a/src/components/Nav/SearchModal/SearchResultItem.tsx b/src/components/Nav/SearchModal/SearchResultItem.tsx new file mode 100644 index 00000000..b4b0fbb0 --- /dev/null +++ b/src/components/Nav/SearchModal/SearchResultItem.tsx @@ -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 +} diff --git a/src/graphql/types.gen.ts b/src/graphql/types.gen.ts new file mode 100644 index 00000000..60674764 --- /dev/null +++ b/src/graphql/types.gen.ts @@ -0,0 +1,726 @@ +import gql from 'graphql-tag' +export type Maybe = T | null +export type InputMaybe = Maybe +export type Exact = { [K in keyof T]: T[K] } +export type MakeOptional = Omit & { [SubKey in K]?: Maybe } +export type MakeMaybe = Omit & { [SubKey in K]: Maybe } +export type MakeEmpty = { [_ in K]?: never } +export type Incremental = + | 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 + token?: Maybe + user?: Maybe +} + +export type Author = { + about?: Maybe + bio?: Maybe + caption?: Maybe + createdAt?: Maybe + id: Scalars['Int']['output'] + lastSeen?: Maybe + links?: Maybe>> + name: Scalars['String']['output'] + roles?: Maybe>> + slug: Scalars['String']['output'] + stat?: Maybe + userpic?: Maybe +} + +export type AuthorStat = { + commented?: Maybe + followers?: Maybe + followings?: Maybe + rating?: Maybe + shouts?: Maybe +} + +export type AuthorsBy = { + createdAt?: InputMaybe + days?: InputMaybe + lastSeen?: InputMaybe + name?: InputMaybe + order?: InputMaybe + slug?: InputMaybe + stat?: InputMaybe + topic?: InputMaybe +} + +export type Chat = { + admins?: Maybe>> + createdAt: Scalars['Int']['output'] + createdBy: Scalars['Int']['output'] + description?: Maybe + id: Scalars['String']['output'] + members?: Maybe>> + messages?: Maybe>> + private?: Maybe + title?: Maybe + unread?: Maybe + updatedAt: Scalars['Int']['output'] + users?: Maybe>> +} + +export type ChatInput = { + description?: InputMaybe + id: Scalars['String']['input'] + title?: InputMaybe +} + +export type ChatMember = { + id: Scalars['Int']['output'] + lastSeen?: Maybe + name: Scalars['String']['output'] + online?: Maybe + slug: Scalars['String']['output'] + userpic?: Maybe +} + +export type Collection = { + amount?: Maybe + createdAt: Scalars['DateTime']['output'] + createdBy: User + desc?: Maybe + id: Scalars['Int']['output'] + publishedAt?: Maybe + slug: Scalars['String']['output'] + title: Scalars['String']['output'] +} + +export type Community = { + createdAt: Scalars['DateTime']['output'] + createdBy: User + desc?: Maybe + 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 + fromRandomCount?: InputMaybe + limit: Scalars['Int']['input'] +} + +export type LoadShoutsFilters = { + author?: InputMaybe + excludeLayout?: InputMaybe + fromDate?: InputMaybe + layout?: InputMaybe + reacted?: InputMaybe + toDate?: InputMaybe + topic?: InputMaybe + visibility?: InputMaybe +} + +export type LoadShoutsOptions = { + filters?: InputMaybe + limit: Scalars['Int']['input'] + offset?: InputMaybe + order_by?: InputMaybe + order_by_desc?: InputMaybe + with_author_captions?: InputMaybe +} + +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 + seen?: Maybe + updatedAt?: Maybe +} + +export enum MessageStatus { + Deleted = 'DELETED', + New = 'NEW', + Updated = 'UPDATED', +} + +export type MessagesBy = { + author?: InputMaybe + body?: InputMaybe + chat?: InputMaybe + days?: InputMaybe + order?: InputMaybe + stat?: InputMaybe +} + +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> + title?: InputMaybe +} + +export type MutationCreateMessageArgs = { + body: Scalars['String']['input'] + chat: Scalars['String']['input'] + replyTo?: InputMaybe +} + +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> +} + +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 + password?: InputMaybe +} + +export type MutationSendLinkArgs = { + email: Scalars['String']['input'] + lang?: InputMaybe + template?: InputMaybe +} + +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 + shout_id: Scalars['Int']['input'] + shout_input?: InputMaybe +} + +export type MutationUpdateTopicArgs = { + input: TopicInput +} + +export type MySubscriptionsQueryResult = { + authors: Array> + topics: Array> +} + +export type Notification = { + createdAt: Scalars['DateTime']['output'] + data?: Maybe + id: Scalars['Int']['output'] + occurrences: Scalars['Int']['output'] + reaction?: Maybe + seen: Scalars['Boolean']['output'] + shout?: Maybe + type: NotificationType +} + +export enum NotificationType { + NewComment = 'NEW_COMMENT', + NewReply = 'NEW_REPLY', +} + +export type NotificationsQueryParams = { + limit?: InputMaybe + offset?: InputMaybe +} + +export type NotificationsQueryResult = { + notifications: Array> + 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 + bio?: InputMaybe + links?: InputMaybe>> + name?: InputMaybe + slug?: InputMaybe + userpic?: InputMaybe +} + +export type Query = { + authorsAll: Array> + getAuthor?: Maybe + getTopic?: Maybe + isEmailUsed: Scalars['Boolean']['output'] + loadAuthorsBy: Array> + loadChats: Result + loadDrafts: Array> + loadMessagesBy: Result + loadMySubscriptions?: Maybe + loadNotifications: NotificationsQueryResult + loadRandomTopShouts: Array> + loadRandomTopicShouts: RandomTopicShoutsQueryResult + loadReactionsBy: Array> + loadRecipients: Result + loadShout?: Maybe + loadShouts: Array> + loadUnratedShouts: Array> + markdownBody: Scalars['String']['output'] + myFeed?: Maybe>> + searchMessages: Result + searchRecipients: Result + signIn: AuthResult + signOut: AuthResult + topicsAll: Array> + topicsByAuthor: Array> + topicsByCommunity: Array> + topicsRandom: Array> + userFollowedAuthors: Array> + userFollowedTopics: Array> + userFollowers: Array> +} + +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 + limit?: InputMaybe + offset?: InputMaybe +} + +export type QueryLoadChatsArgs = { + limit?: InputMaybe + offset?: InputMaybe +} + +export type QueryLoadMessagesByArgs = { + by: MessagesBy + limit?: InputMaybe + offset?: InputMaybe +} + +export type QueryLoadNotificationsArgs = { + params: NotificationsQueryParams +} + +export type QueryLoadRandomTopShoutsArgs = { + params?: InputMaybe +} + +export type QueryLoadRandomTopicShoutsArgs = { + limit: Scalars['Int']['input'] +} + +export type QueryLoadReactionsByArgs = { + by: ReactionBy + limit?: InputMaybe + offset?: InputMaybe +} + +export type QueryLoadRecipientsArgs = { + limit?: InputMaybe + offset?: InputMaybe +} + +export type QueryLoadShoutArgs = { + shout_id?: InputMaybe + slug?: InputMaybe +} + +export type QueryLoadShoutsArgs = { + options?: InputMaybe +} + +export type QueryLoadUnratedShoutsArgs = { + limit: Scalars['Int']['input'] +} + +export type QueryMarkdownBodyArgs = { + body: Scalars['String']['input'] +} + +export type QueryMyFeedArgs = { + options?: InputMaybe +} + +export type QuerySearchMessagesArgs = { + by: MessagesBy + limit?: InputMaybe + offset?: InputMaybe +} + +export type QuerySearchRecipientsArgs = { + limit?: InputMaybe + offset?: InputMaybe + query: Scalars['String']['input'] +} + +export type QuerySignInArgs = { + email: Scalars['String']['input'] + lang?: InputMaybe + password?: InputMaybe +} + +export type QueryTopicsByAuthorArgs = { + author: Scalars['String']['input'] +} + +export type QueryTopicsByCommunityArgs = { + community: Scalars['String']['input'] +} + +export type QueryTopicsRandomArgs = { + amount?: InputMaybe +} + +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> + topic: Topic +} + +export type Rating = { + rater: Scalars['String']['output'] + value: Scalars['Int']['output'] +} + +export type Reaction = { + body?: Maybe + createdAt: Scalars['DateTime']['output'] + createdBy: User + deletedAt?: Maybe + deletedBy?: Maybe + id: Scalars['Int']['output'] + kind: ReactionKind + old_id?: Maybe + old_thread?: Maybe + range?: Maybe + replyTo?: Maybe + shout: Shout + stat?: Maybe + updatedAt?: Maybe +} + +export type ReactionBy = { + comment?: InputMaybe + createdBy?: InputMaybe + days?: InputMaybe + search?: InputMaybe + shout?: InputMaybe + shouts?: InputMaybe>> + sort?: InputMaybe + topic?: InputMaybe +} + +export type ReactionInput = { + body?: InputMaybe + kind: ReactionKind + range?: InputMaybe + replyTo?: InputMaybe + 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 + reaction?: Maybe + status?: Maybe +} + +export type Resource = { + id: Scalars['Int']['output'] + name: Scalars['String']['output'] +} + +export type Result = { + author?: Maybe + authors?: Maybe>> + chat?: Maybe + chats?: Maybe>> + communities?: Maybe>> + community?: Maybe + error?: Maybe + members?: Maybe>> + message?: Maybe + messages?: Maybe>> + reaction?: Maybe + reactions?: Maybe>> + shout?: Maybe + shouts?: Maybe>> + slugs?: Maybe>> + topic?: Maybe + topics?: Maybe>> +} + +export type Role = { + community: Scalars['String']['output'] + desc?: Maybe + id: Scalars['Int']['output'] + name: Scalars['String']['output'] + permissions: Array +} + +export type Shout = { + authors?: Maybe>> + body: Scalars['String']['output'] + community?: Maybe + cover?: Maybe + createdAt: Scalars['DateTime']['output'] + deletedAt?: Maybe + deletedBy?: Maybe + description?: Maybe + id: Scalars['Int']['output'] + lang?: Maybe + layout?: Maybe + lead?: Maybe + mainTopic?: Maybe + media?: Maybe + publishedAt?: Maybe + slug: Scalars['String']['output'] + stat?: Maybe + subtitle?: Maybe + title?: Maybe + topics?: Maybe>> + updatedAt?: Maybe + updatedBy?: Maybe + versionOf?: Maybe + visibility?: Maybe +} + +export type ShoutInput = { + authors?: InputMaybe>> + body?: InputMaybe + community?: InputMaybe + cover?: InputMaybe + description?: InputMaybe + layout?: InputMaybe + lead?: InputMaybe + mainTopic?: InputMaybe + media?: InputMaybe + slug?: InputMaybe + subtitle?: InputMaybe + title?: InputMaybe + topics?: InputMaybe>> +} + +export type Stat = { + commented?: Maybe + ranking?: Maybe + rating?: Maybe + reacted?: Maybe + viewed?: Maybe +} + +export type Token = { + createdAt: Scalars['DateTime']['output'] + expiresAt?: Maybe + id: Scalars['Int']['output'] + ownerId: Scalars['Int']['output'] + usedAt?: Maybe + value: Scalars['String']['output'] +} + +export type Topic = { + body?: Maybe + id: Scalars['Int']['output'] + oid?: Maybe + pic?: Maybe + slug: Scalars['String']['output'] + stat?: Maybe + title?: Maybe +} + +export type TopicInput = { + body?: InputMaybe + id?: InputMaybe + pic?: InputMaybe + slug: Scalars['String']['input'] + title?: InputMaybe +} + +export type TopicStat = { + authors: Scalars['Int']['output'] + followers: Scalars['Int']['output'] + shouts: Scalars['Int']['output'] +} + +export type User = { + about?: Maybe + bio?: Maybe + communities?: Maybe>> + createdAt: Scalars['DateTime']['output'] + email?: Maybe + emailConfirmed?: Maybe + id: Scalars['Int']['output'] + lastSeen?: Maybe + links?: Maybe>> + muted?: Maybe + name?: Maybe + oauth?: Maybe + oid?: Maybe + password?: Maybe + ratings?: Maybe>> + slug: Scalars['String']['output'] + updatedAt?: Maybe + username: Scalars['String']['output'] + userpic?: Maybe +} diff --git a/src/styles/app.scss b/src/styles/app.scss index f3fae241..af875c73 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -1071,3 +1071,8 @@ iframe { .img-align-column { clear: both; } + +.blackModeIntersection { + color: var(--default-color); + background: #fef2f2; +} diff --git a/src/utils/apiClient.ts b/src/utils/apiClient.ts new file mode 100644 index 00000000..ed0dbcc1 --- /dev/null +++ b/src/utils/apiClient.ts @@ -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', + }, + }) + }, +} diff --git a/src/utils/config.ts b/src/utils/config.ts index 78f683e5..1cfd89a5 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -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