Prosemirror integrate (#86)

Comment Editor
This commit is contained in:
Ilya Y 2023-01-26 09:59:43 +03:00 committed by GitHub
parent f22d6c60c5
commit 87a99d9e8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 421 additions and 68 deletions

View File

@ -122,12 +122,31 @@
@include font-size(1.5rem); @include font-size(1.5rem);
line-height: 1.47; line-height: 1.47;
blockquote {
margin-left: 0;
padding-left: 10px;
font-style: italic;
font-weight: 400;
color: #9fa1a7;
border-left: 2px solid #696969;
p,
.paragraph {
margin-bottom: 0;
}
}
} }
.commentAuthor, .commentAuthor,
.commentDate, .commentDate,
.commentRating { .commentRating {
@include font-size(1.2rem); @include font-size(1.2rem);
} }
.articleAuthor {
color: #2638d9;
font-size: 12px;
margin-right: 12px;
}
.commentDate { .commentDate {
color: rgb(0 0 0 / 30%); color: rgb(0 0 0 / 30%);
flex: 1; flex: 1;

View File

@ -13,18 +13,20 @@ import stylesHeader from '../Nav/Header.module.scss'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { ReactionKind } from '../../graphql/types.gen' import { ReactionKind } from '../../graphql/types.gen'
import GrowingTextarea from '../_shared/GrowingTextarea' import CommentEditor from '../_shared/CommentEditor'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
type Props = { type Props = {
comment: Reaction comment: Reaction
compact?: boolean compact?: boolean
reactions?: Reaction[] reactions?: Reaction[]
isArticleAuthor?: boolean
} }
const Comment = (props: Props) => { const Comment = (props: Props) => {
const [isReplyVisible, setIsReplyVisible] = createSignal(false) const [isReplyVisible, setIsReplyVisible] = createSignal(false)
const [loading, setLoading] = createSignal(false) const [loading, setLoading] = createSignal<boolean>(false)
const [errorMessage, setErrorMessage] = createSignal<string | null>(null) const [submitted, setSubmitted] = createSignal<boolean>(false)
const { session } = useSession() const { session } = useSession()
const canEdit = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug) const canEdit = createMemo(() => props.comment.createdBy?.slug === session()?.user?.slug)
@ -58,12 +60,13 @@ const Comment = (props: Props) => {
} }
) )
setIsReplyVisible(false) setIsReplyVisible(false)
setSubmitted(true)
setLoading(false) setLoading(false)
} catch (error) { } catch (error) {
console.error('[handleCreate reaction]:', error) console.error('[handleCreate reaction]:', error)
setErrorMessage(t('Something went wrong, please try again'))
} }
} }
const formattedDate = createMemo(() => const formattedDate = createMemo(() =>
formatDate(new Date(comment()?.createdAt), { hour: 'numeric', minute: 'numeric' }) formatDate(new Date(comment()?.createdAt), { hour: 'numeric', minute: 'numeric' })
) )
@ -94,6 +97,10 @@ const Comment = (props: Props) => {
/> />
</div> </div>
<Show when={props.isArticleAuthor}>
<div class={styles.articleAuthor}>{t('Author')}</div>
</Show>
<div class={styles.commentDate}>{formattedDate()}</div> <div class={styles.commentDate}>{formattedDate()}</div>
<div <div
class={styles.commentRating} class={styles.commentRating}
@ -164,14 +171,13 @@ const Comment = (props: Props) => {
</div> </div>
<Show when={isReplyVisible()}> <Show when={isReplyVisible()}>
<GrowingTextarea <ShowOnlyOnClient>
placeholder={t('Write comment')} <CommentEditor
submitButtonText={t('Send')} initialValue={''}
cancelButtonText={t('cancel')} clear={submitted()}
submit={(value) => handleCreate(value)} onSubmit={(value) => handleCreate(value)}
loading={loading()} />
errorMessage={errorMessage()} </ShowOnlyOnClient>
/>
</Show> </Show>
</Show> </Show>
</div> </div>
@ -179,7 +185,13 @@ const Comment = (props: Props) => {
<Show when={props.reactions}> <Show when={props.reactions}>
<ul> <ul>
<For each={props.reactions.filter((r) => r.replyTo === props.comment.id)}> <For each={props.reactions.filter((r) => r.replyTo === props.comment.id)}>
{(reaction) => <Comment reactions={props.reactions} comment={reaction} />} {(reaction) => (
<Comment
isArticleAuthor={props.isArticleAuthor}
reactions={props.reactions}
comment={reaction}
/>
)}
</For> </For>
</ul> </ul>
</Show> </Show>

View File

@ -7,14 +7,21 @@ import type { Reaction } from '../../graphql/types.gen'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { byCreated, byStat } from '../../utils/sortby' import { byCreated, byStat } from '../../utils/sortby'
import { Loading } from '../Loading' import { Loading } from '../Loading'
import GrowingTextarea from '../_shared/GrowingTextarea' import { Author, ReactionKind } from '../../graphql/types.gen'
import { ReactionKind } from '../../graphql/types.gen'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import CommentEditor from '../_shared/CommentEditor'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
const ARTICLE_COMMENTS_PAGE_SIZE = 50 const ARTICLE_COMMENTS_PAGE_SIZE = 50
const MAX_COMMENT_LEVEL = 6 const MAX_COMMENT_LEVEL = 6
export const CommentsTree = (props: { shoutSlug: string; shoutId: number }) => { type Props = {
commentAuthors: Author[]
shoutSlug: string
shoutId: number
}
export const CommentsTree = (props: Props) => {
const [getCommentsPage, setCommentsPage] = createSignal(0) const [getCommentsPage, setCommentsPage] = createSignal(0)
const [commentsOrder, setCommentsOrder] = createSignal<'rating' | 'createdAt'>('createdAt') const [commentsOrder, setCommentsOrder] = createSignal<'rating' | 'createdAt'>('createdAt')
const [isCommentsLoading, setIsCommentsLoading] = createSignal(false) const [isCommentsLoading, setIsCommentsLoading] = createSignal(false)
@ -47,11 +54,9 @@ export const CommentsTree = (props: { shoutSlug: string; shoutId: number }) => {
} }
onMount(async () => await loadMore()) onMount(async () => await loadMore())
const [loading, setLoading] = createSignal<boolean>(false) const [submitted, setSubmitted] = createSignal<boolean>(false)
const [errorMessage, setErrorMessage] = createSignal<string | null>(null)
const handleSubmitComment = async (value) => { const handleSubmitComment = async (value) => {
try { try {
setLoading(true)
await createReaction( await createReaction(
{ {
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
@ -64,12 +69,12 @@ export const CommentsTree = (props: { shoutSlug: string; shoutId: number }) => {
slug: session().user.slug slug: session().user.slug
} }
) )
setLoading(false) setSubmitted(true)
} catch (error) { } catch (error) {
setErrorMessage(t('Something went wrong, please try again'))
console.error('[handleCreate reaction]:', error) console.error('[handleCreate reaction]:', error)
} }
} }
return ( return (
<div> <div>
<Show when={!isCommentsLoading()} fallback={<Loading />}> <Show when={!isCommentsLoading()} fallback={<Loading />}>
@ -109,20 +114,25 @@ export const CommentsTree = (props: { shoutSlug: string; shoutId: number }) => {
.reverse() .reverse()
.filter((r) => !r.replyTo)} .filter((r) => !r.replyTo)}
> >
{(reaction) => <Comment reactions={reactions()} comment={reaction} />} {(reaction) => (
<Comment
isArticleAuthor={Boolean(props.commentAuthors.some((a) => a.slug === session()?.user.slug))}
reactions={reactions()}
comment={reaction}
/>
)}
</For> </For>
</ul> </ul>
<Show when={isLoadMoreButtonVisible()}> <Show when={isLoadMoreButtonVisible()}>
<button onClick={loadMore}>{t('Load more')}</button> <button onClick={loadMore}>{t('Load more')}</button>
</Show> </Show>
<GrowingTextarea <ShowOnlyOnClient>
placeholder={t('Write comment')} <CommentEditor
submitButtonText={t('Send')} initialValue={t('Write a comment...')}
cancelButtonText={t('cancel')} clear={submitted()}
submit={(value) => handleSubmitComment(value)} onSubmit={(value) => handleSubmitComment(value)}
loading={loading()} />
errorMessage={errorMessage()} </ShowOnlyOnClient>
/>
</Show> </Show>
</div> </div>
) )

View File

@ -224,7 +224,11 @@ export const FullArticle = (props: ArticleProps) => {
)} )}
</For> </For>
</div> </div>
<CommentsTree shoutSlug={props.article?.slug} shoutId={props.article?.id} /> <CommentsTree
shoutId={props.article?.id}
shoutSlug={props.article?.slug}
commentAuthors={props.article?.authors}
/>
</div> </div>
</div> </div>
) )

View File

@ -35,7 +35,7 @@ const CreateModalContent = (props: Props) => {
}) })
}) })
if (usersId().length > 1 && theme().length === 1) { if (usersId().length > 1 && theme().length === 1) {
setTheme(t('group_chat')) setTheme(t('Group Chat'))
} }
}) })

View File

@ -5,12 +5,12 @@ import GroupDialogAvatar from './GroupDialogAvatar'
import formattedTime from '../../utils/formatDateTime' import formattedTime from '../../utils/formatDateTime'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import styles from './DialogCard.module.scss' import styles from './DialogCard.module.scss'
import { t } from '../../utils/intl'
type DialogProps = { type DialogProps = {
online?: boolean online?: boolean
message?: string message?: string
counter?: number counter?: number
title?: string
ownId: number ownId: number
members: ChatMember[] members: ChatMember[]
onClick?: () => void onClick?: () => void
@ -23,6 +23,7 @@ const DialogCard = (props: DialogProps) => {
const companions = createMemo( const companions = createMemo(
() => props.members && props.members.filter((member) => member.id !== props.ownId) () => props.members && props.members.filter((member) => member.id !== props.ownId)
) )
const names = createMemo(() => const names = createMemo(() =>
companions() companions()
?.map((companion) => companion.name) ?.map((companion) => companion.name)
@ -40,14 +41,16 @@ const DialogCard = (props: DialogProps) => {
onClick={props.onClick} onClick={props.onClick}
> >
<div class={styles.avatar}> <div class={styles.avatar}>
<Switch fallback={<DialogAvatar name={props.members[0].name} url={props.members[0].userpic} />}> <Switch fallback={<DialogAvatar name={props.members[0].slug} url={props.members[0].userpic} />}>
<Match when={props.members.length >= 3}> <Match when={props.members.length >= 3}>
<GroupDialogAvatar users={props.members} /> <GroupDialogAvatar users={props.members} />
</Match> </Match>
</Switch> </Switch>
</div> </div>
<div class={styles.row}> <div class={styles.row}>
<div class={styles.name}>{props.title}</div> <div class={styles.name}>
{companions()?.length > 1 ? t('Group Chat') : companions()[0]?.name}
</div>
<div class={styles.message}> <div class={styles.message}>
<Switch> <Switch>
<Match when={props.message && !props.isChatHeader}>{props.message}</Match> <Match when={props.message && !props.isChatHeader}>{props.message}</Match>

View File

@ -9,7 +9,7 @@ import MessagesFallback from '../Inbox/MessagesFallback'
import QuotedMessage from '../Inbox/QuotedMessage' import QuotedMessage from '../Inbox/QuotedMessage'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { loadMessages, loadRecipients } from '../../stores/inbox' import { loadRecipients } from '../../stores/inbox'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Modal } from '../Nav/Modal' import { Modal } from '../Nav/Modal'
import { showModal } from '../../stores/ui' import { showModal } from '../../stores/ui'
@ -155,7 +155,7 @@ export const InboxView = () => {
return messages().find((message) => message.id === messageId) return messages().find((message) => message.id === messageId)
} }
const handleKeyDown = (event) => { const handleKeyDown = async (event) => {
if (event.keyCode === 13 && event.shiftKey) return if (event.keyCode === 13 && event.shiftKey) return
if (event.keyCode === 13 && !event.shiftKey && postMessageText().trim().length > 0) { if (event.keyCode === 13 && !event.shiftKey && postMessageText().trim().length > 0) {
event.preventDefault() event.preventDefault()
@ -217,7 +217,6 @@ export const InboxView = () => {
<DialogCard <DialogCard
onClick={() => handleOpenChat(chat)} onClick={() => handleOpenChat(chat)}
isOpened={chat.id === currentDialog()?.id} isOpened={chat.id === currentDialog()?.id}
title={chat.title || chat.members[0].name}
members={chat.members} members={chat.members}
ownId={currentUserId()} ownId={currentUserId()}
lastUpdate={chat.updatedAt} lastUpdate={chat.updatedAt}

View File

@ -0,0 +1,91 @@
import styles from './styles/CommentEditor.module.scss'
import './styles/ProseMirrorOverrides.scss'
import { clsx } from 'clsx'
import Button from '../Button'
import { createEffect, createMemo, onMount } from 'solid-js'
import { t } from '../../../utils/intl'
//ProseMirror deps
import { schema } from './schema'
import { EditorState } from 'prosemirror-state'
import { EditorView } from 'prosemirror-view'
import { DOMSerializer } from 'prosemirror-model'
import { renderGrouped } from 'prosemirror-menu'
import { buildMenuItems } from './menu'
import { keymap } from 'prosemirror-keymap'
import { baseKeymap } from 'prosemirror-commands'
import { customKeymap } from '../../EditorNew/prosemirror/plugins/customKeymap'
import { placeholder } from '../../EditorNew/prosemirror/plugins/placeholder'
import { undo, redo, history } from 'prosemirror-history'
type Props = {
initialValue: string
onSubmit: (value: string) => void
clear?: boolean
}
const htmlContainer = typeof document === 'undefined' ? null : document.createElement('div')
const getHtml = (state: EditorState) => {
const fragment = DOMSerializer.fromSchema(schema).serializeFragment(state.doc.content)
htmlContainer.replaceChildren(fragment)
return htmlContainer.innerHTML
}
const CommentEditor = (props: Props) => {
const editorElRef: { current: HTMLDivElement } = { current: null }
const menuElRef: { current: HTMLDivElement } = { current: null }
const editorViewRef: { current: EditorView } = { current: null }
const initEditor = () => {
editorViewRef.current = new EditorView(editorElRef.current, {
state: EditorState.create({
schema,
plugins: [
history(),
customKeymap(),
placeholder(props.initialValue),
keymap({ 'Mod-z': undo, 'Mod-y': redo }),
keymap(baseKeymap)
]
})
})
}
onMount(() => {
initEditor()
const { dom } = renderGrouped(editorViewRef.current, buildMenuItems(schema))
menuElRef.current.appendChild(dom)
})
const handleSubmitButtonClick = () => {
props.onSubmit(getHtml(editorViewRef.current.state))
}
const clearEditor = () => {
editorViewRef.current.destroy()
initEditor()
}
createEffect(() => {
if (props.clear) clearEditor()
})
return (
<>
<div class={styles.commentEditor}>
<div
class={clsx('ProseMirrorOverrides', styles.textarea)}
ref={(el) => (editorElRef.current = el)}
/>
<div class={styles.actions}>
<div class={styles.menu} ref={(el) => (menuElRef.current = el)} />
<div class={styles.buttons}>
<Button value={t('Send')} variant="primary" onClick={handleSubmitButtonClick} />
<Button value="Cancel" variant="secondary" onClick={clearEditor} />
</div>
</div>
</div>
<div class={styles.helpText}>{'"Cmd-Z": Undo, "Cmd-Y": Redo'}</div>
</>
)
}
export default CommentEditor

View File

@ -0,0 +1 @@
export { default } from './CommentEditor'

View File

@ -0,0 +1,72 @@
import { blockTypeItem, icons, MenuItem, wrapItem } from 'prosemirror-menu'
import { toggleMark } from 'prosemirror-commands'
const markActive = (state, type) => {
const { from, $from, to, empty } = state.selection
if (empty) return type.isInSet(state.storedMarks || $from.marks())
return state.doc.rangeHasMark(from, to, type)
}
const cmdItem = (cmd, options) => {
const passedOptions = {
label: options.title,
run: cmd
}
for (const prop in options) passedOptions[prop] = options[prop]
if ((!options.enable || options.enable === true) && !options.select) {
passedOptions[options.enable ? 'enable' : 'select'] = (state) => cmd(state)
}
return new MenuItem(passedOptions)
}
const markItem = (markType, options) => {
const passedOptions = {
active(state) {
return markActive(state, markType)
},
enable: true
}
for (const prop in options) passedOptions[prop] = options[prop]
return cmdItem(toggleMark(markType), passedOptions)
}
//TODO: вывести тип для схемы
export const buildMenuItems = (schema) => {
const toggleStrong = markItem(schema.marks.strong, {
title: 'Toggle strong style',
icon: {
width: 14,
height: 16,
path: 'M9.82857 7.76C10.9371 6.99429 11.7143 5.73714 11.7143 4.57143C11.7143 1.98857 9.71428 0 7.14286 0H0V16H8.04571C10.4343 16 12.2857 14.0571 12.2857 11.6686C12.2857 9.93143 11.3029 8.44571 9.82857 7.76ZM3.42799 2.85708H6.85656C7.80513 2.85708 8.57085 3.6228 8.57085 4.57137C8.57085 5.51994 7.80513 6.28565 6.85656 6.28565H3.42799V2.85708ZM3.42799 13.1429H7.42799C8.37656 13.1429 9.14228 12.3772 9.14228 11.4286C9.14228 10.4801 8.37656 9.71434 7.42799 9.71434H3.42799V13.1429Z'
}
})
const toggleEm = markItem(schema.marks.em, {
title: 'Toggle emphasis',
icon: {
width: 14,
height: 16,
path: 'M4.39216 0V3.42857H6.81882L3.06353 12.5714H0V16H8.78431V12.5714H6.35765L10.1129 3.42857H13.1765V0H4.39216Z'
}
})
// const toggleLink = linkItem(schema.marks.link)
// const insertImage = insertImageItem(schema.nodes.image)
const wrapBlockQuote = wrapItem(schema.nodes.blockquote, {
title: 'Wrap in block quote',
icon: icons.blockquote
})
const inlineMenu = [toggleStrong, toggleEm, wrapBlockQuote]
return [inlineMenu]
}

View File

@ -0,0 +1,21 @@
import { Plugin } from 'prosemirror-state'
import { DecorationSet, Decoration } from 'prosemirror-view'
export const placeholder = (text: string): Plugin =>
new Plugin({
props: {
decorations(state) {
const { doc } = state
if (doc.childCount > 1 || !doc.firstChild.isTextblock || doc.firstChild.content.size > 0) {
return
}
const div = document.createElement('div')
div.setAttribute('contenteditable', 'false')
div.textContent = text
return DecorationSet.create(doc, [Decoration.widget(1, div)])
}
}
})

View File

@ -0,0 +1,43 @@
import { Schema } from 'prosemirror-model'
export const schema = new Schema({
nodes: {
doc: {
content: 'block+'
},
text: {
group: 'inline',
inline: true
},
paragraph: {
content: 'inline*',
group: 'block',
toDOM: function toDOM(node) {
return ['p', { class: 'paragraph' }, 0]
}
},
blockquote: {
content: 'block+',
group: 'block',
defining: true,
parseDOM: [{ tag: 'blockquote' }],
toDOM() {
return ['blockquote', 0]
}
}
},
marks: {
strong: {
toDOM() {
return ['strong', 0]
},
parseDOM: [{ tag: 'strong' }, { tag: 'b' }, { style: 'font-weight=bold' }]
},
em: {
toDOM() {
return ['em', 0]
},
parseDOM: [{ tag: 'em' }, { tag: 'i' }, { style: 'font-style=italic' }]
}
}
})

View File

@ -0,0 +1,32 @@
.commentEditor {
border: 2px solid #e8e8e8;
border-radius: 8px;
padding: 0 16px 16px;
.textarea {
min-height: 1em;
overflow: hidden;
}
.actions {
display: flex;
flex-direction: row;
justify-content: space-between;
.menu,
.buttons {
display: flex;
flex-direction: row;
}
.buttons {
gap: 10px;
}
}
}
.helpText {
font-size: 12px;
color: #696969;
margin: 12px 0;
font-style: italic;
}

View File

@ -0,0 +1,14 @@
.ProseMirrorOverrides > .ProseMirror {
.paragraph {
font-size: 15px;
line-height: 1.1em;
}
blockquote {
padding-left: 10px;
font-style: italic;
font-weight: 400;
color: #9fa1a7;
border-left: 2px solid #696969;
}
}

View File

@ -1 +0,0 @@
export { default } from './GrowingTextarea'

View File

@ -1,4 +1,4 @@
import { createContext, createSignal, useContext } from 'solid-js' import { createContext, createEffect, createSignal, useContext } from 'solid-js'
import type { Accessor, JSX } from 'solid-js' import type { Accessor, JSX } from 'solid-js'
// import { createChatClient } from '../graphql/privateGraphQLClient' // import { createChatClient } from '../graphql/privateGraphQLClient'
import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen' import type { Chat, Message, MutationCreateMessageArgs } from '../graphql/types.gen'

View File

@ -6,6 +6,10 @@ export default gql`
error error
chat { chat {
id id
members {
id
slug
}
} }
} }
} }

View File

@ -9,6 +9,7 @@ export default gql`
id id
slug slug
userpic userpic
online
} }
error error
} }

View File

@ -84,14 +84,6 @@ export type ChatMember = {
userpic?: Maybe<Scalars['String']> userpic?: Maybe<Scalars['String']>
} }
export type Collab = {
authors: Array<Maybe<Scalars['String']>>
chat?: Maybe<Chat>
createdAt: Scalars['Int']
invites?: Maybe<Array<Maybe<Scalars['String']>>>
shout?: Maybe<Shout>
}
export type Collection = { export type Collection = {
amount?: Maybe<Scalars['Int']> amount?: Maybe<Scalars['Int']>
createdAt: Scalars['DateTime'] createdAt: Scalars['DateTime']
@ -113,6 +105,30 @@ export type Community = {
slug: Scalars['String'] slug: Scalars['String']
} }
export type DraftCollab = {
authors: Array<Maybe<Scalars['Int']>>
body?: Maybe<Scalars['String']>
chat?: Maybe<Chat>
cover?: Maybe<Scalars['String']>
createdAt: Scalars['Int']
layout?: Maybe<Scalars['String']>
slug?: Maybe<Scalars['String']>
subtitle?: Maybe<Scalars['String']>
title?: Maybe<Scalars['String']>
topics?: Maybe<Array<Maybe<Scalars['String']>>>
updatedAt?: Maybe<Scalars['Int']>
}
export type DraftInput = {
authors?: InputMaybe<Array<InputMaybe<Scalars['Int']>>>
body?: InputMaybe<Scalars['String']>
cover?: InputMaybe<Scalars['String']>
slug?: InputMaybe<Scalars['String']>
subtitle?: InputMaybe<Scalars['String']>
title?: InputMaybe<Scalars['String']>
topics?: InputMaybe<Array<InputMaybe<Scalars['Int']>>>
}
export enum FollowingEntity { export enum FollowingEntity {
Author = 'AUTHOR', Author = 'AUTHOR',
Community = 'COMMUNITY', Community = 'COMMUNITY',
@ -167,28 +183,30 @@ export type MessagesBy = {
} }
export type Mutation = { export type Mutation = {
acceptCoauthor: Result
confirmEmail: AuthResult confirmEmail: AuthResult
createChat: Result createChat: Result
createDraft: Result
createMessage: Result createMessage: Result
createReaction: Result createReaction: Result
createShout: Result createShout: Result
createTopic: Result createTopic: Result
deleteChat: Result deleteChat: Result
deleteDraft: Result
deleteMessage: Result deleteMessage: Result
deleteReaction: Result deleteReaction: Result
deleteShout: Result deleteShout: Result
destroyTopic: Result destroyTopic: Result
follow: Result follow: Result
getSession: AuthResult getSession: AuthResult
inviteCoauthor: Result inviteAccept: Result
inviteAuthor: Result
markAsRead: Result markAsRead: Result
rateUser: Result rateUser: Result
registerUser: AuthResult registerUser: AuthResult
removeCoauthor: Result
sendLink: Result sendLink: Result
unfollow: Result unfollow: Result
updateChat: Result updateChat: Result
updateDraft: Result
updateMessage: Result updateMessage: Result
updateOnlineStatus: Result updateOnlineStatus: Result
updateProfile: Result updateProfile: Result
@ -197,10 +215,6 @@ export type Mutation = {
updateTopic: Result updateTopic: Result
} }
export type MutationAcceptCoauthorArgs = {
shout: Scalars['Int']
}
export type MutationConfirmEmailArgs = { export type MutationConfirmEmailArgs = {
token: Scalars['String'] token: Scalars['String']
} }
@ -210,6 +224,10 @@ export type MutationCreateChatArgs = {
title?: InputMaybe<Scalars['String']> title?: InputMaybe<Scalars['String']>
} }
export type MutationCreateDraftArgs = {
draft: DraftInput
}
export type MutationCreateMessageArgs = { export type MutationCreateMessageArgs = {
body: Scalars['String'] body: Scalars['String']
chat: Scalars['String'] chat: Scalars['String']
@ -232,6 +250,10 @@ export type MutationDeleteChatArgs = {
chatId: Scalars['String'] chatId: Scalars['String']
} }
export type MutationDeleteDraftArgs = {
draft: Scalars['Int']
}
export type MutationDeleteMessageArgs = { export type MutationDeleteMessageArgs = {
chatId: Scalars['String'] chatId: Scalars['String']
id: Scalars['Int'] id: Scalars['Int']
@ -254,9 +276,13 @@ export type MutationFollowArgs = {
what: FollowingEntity what: FollowingEntity
} }
export type MutationInviteCoauthorArgs = { export type MutationInviteAcceptArgs = {
author: Scalars['String'] draft: Scalars['Int']
shout: Scalars['Int'] }
export type MutationInviteAuthorArgs = {
author: Scalars['Int']
draft: Scalars['Int']
} }
export type MutationMarkAsReadArgs = { export type MutationMarkAsReadArgs = {
@ -275,11 +301,6 @@ export type MutationRegisterUserArgs = {
password?: InputMaybe<Scalars['String']> password?: InputMaybe<Scalars['String']>
} }
export type MutationRemoveCoauthorArgs = {
author: Scalars['String']
shout: Scalars['Int']
}
export type MutationSendLinkArgs = { export type MutationSendLinkArgs = {
email: Scalars['String'] email: Scalars['String']
lang?: InputMaybe<Scalars['String']> lang?: InputMaybe<Scalars['String']>
@ -295,6 +316,10 @@ export type MutationUpdateChatArgs = {
chat: ChatInput chat: ChatInput
} }
export type MutationUpdateDraftArgs = {
draft: DraftInput
}
export type MutationUpdateMessageArgs = { export type MutationUpdateMessageArgs = {
body: Scalars['String'] body: Scalars['String']
chatId: Scalars['String'] chatId: Scalars['String']
@ -345,11 +370,11 @@ export type ProfileInput = {
export type Query = { export type Query = {
authorsAll: Array<Maybe<Author>> authorsAll: Array<Maybe<Author>>
getAuthor?: Maybe<User> getAuthor?: Maybe<User>
getCollabs: Array<Maybe<Collab>>
getTopic?: Maybe<Topic> getTopic?: Maybe<Topic>
isEmailUsed: Scalars['Boolean'] isEmailUsed: Scalars['Boolean']
loadAuthorsBy: Array<Maybe<Author>> loadAuthorsBy: Array<Maybe<Author>>
loadChats: Result loadChats: Result
loadDrafts: Array<Maybe<DraftCollab>>
loadMessagesBy: Result loadMessagesBy: Result
loadReactionsBy: Array<Maybe<Reaction>> loadReactionsBy: Array<Maybe<Reaction>>
loadRecipients: Result loadRecipients: Result
@ -545,6 +570,7 @@ export type Result = {
chats?: Maybe<Array<Maybe<Chat>>> chats?: Maybe<Array<Maybe<Chat>>>
communities?: Maybe<Array<Maybe<Community>>> communities?: Maybe<Array<Maybe<Community>>>
community?: Maybe<Community> community?: Maybe<Community>
drafts?: Maybe<Array<Maybe<DraftCollab>>>
error?: Maybe<Scalars['String']> error?: Maybe<Scalars['String']>
members?: Maybe<Array<Maybe<ChatMember>>> members?: Maybe<Array<Maybe<ChatMember>>>
message?: Maybe<Message> message?: Maybe<Message>

View File

@ -166,6 +166,7 @@
"actions": "действия", "actions": "действия",
"all topics": "все темы", "all topics": "все темы",
"author": "автор", "author": "автор",
"Author": "Автор",
"authors": "авторы", "authors": "авторы",
"collections": "коллекции", "collections": "коллекции",
"community": "сообщество", "community": "сообщество",
@ -192,7 +193,7 @@
"discourse_theme": "Тема дискурса", "discourse_theme": "Тема дискурса",
"cancel": "Отмена", "cancel": "Отмена",
"Send": "Отправить", "Send": "Отправить",
"group_chat": "Общий чат", "Group Chat": "Общий чат",
"Choose who you want to write to": "Выберите кому хотите написать", "Choose who you want to write to": "Выберите кому хотите написать",
"Start conversation": "Начать беседу", "Start conversation": "Начать беседу",
"Profile settings": "Настройки профиля", "Profile settings": "Настройки профиля",
@ -218,6 +219,7 @@
"It does not look like url": "Это не похоже на ссылку", "It does not look like url": "Это не похоже на ссылку",
"Something went wrong, please try again": "Что-то пошло не так, попробуйте еще раз", "Something went wrong, please try again": "Что-то пошло не так, попробуйте еще раз",
"To write a comment, you must": "Чтобы написать комментарий, необходимо", "To write a comment, you must": "Чтобы написать комментарий, необходимо",
"Write a comment...": "Написать комментарий..."
"Add comment": "Комментировать", "Add comment": "Комментировать",
"My subscriptions": "Подписки" "My subscriptions": "Подписки"
} }

View File

@ -1,4 +1,4 @@
import type { Reaction, ReactionInput, User } from '../../graphql/types.gen' import type { Reaction, ReactionInput } from '../../graphql/types.gen'
import { apiClient } from '../../utils/apiClient' import { apiClient } from '../../utils/apiClient'
import { createSignal } from 'solid-js' import { createSignal } from 'solid-js'
// TODO: import { roomConnect } from '../../utils/p2p' // TODO: import { roomConnect } from '../../utils/p2p'

View File

@ -296,12 +296,12 @@ export const apiClient = {
createMessage: async (options: MutationCreateMessageArgs) => { createMessage: async (options: MutationCreateMessageArgs) => {
const resp = await privateGraphQLClient.mutation(createMessage, options).toPromise() const resp = await privateGraphQLClient.mutation(createMessage, options).toPromise()
return resp.data.createMessage return resp.data.createMessage.message
}, },
getChatMessages: async (options: QueryLoadMessagesByArgs) => { getChatMessages: async (options: QueryLoadMessagesByArgs) => {
const resp = await privateGraphQLClient.query(chatMessagesLoadBy, options).toPromise() const resp = await privateGraphQLClient.query(chatMessagesLoadBy, options).toPromise()
return resp.data.loadChat return resp.data.loadMessagesBy.messages
}, },
getRecipients: async (options: QueryLoadRecipientsArgs) => { getRecipients: async (options: QueryLoadRecipientsArgs) => {
const resp = await privateGraphQLClient.query(loadRecipients, options).toPromise() const resp = await privateGraphQLClient.query(loadRecipients, options).toPromise()