Merge branch 'dev' of github.com:Discours/discoursio-webapp into dev

This commit is contained in:
Untone 2024-05-06 23:47:04 +03:00
commit 75c415aece
11 changed files with 87 additions and 78 deletions

View File

@ -448,6 +448,7 @@
"Write your colleagues name or email": "Write your colleague's name or email", "Write your colleagues name or email": "Write your colleague's name or email",
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats", "You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля", "You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
"You can't edit this post": "You can't edit this post",
"You were successfully authorized": "You were successfully authorized", "You were successfully authorized": "You were successfully authorized",
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses", "You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses",
"You've confirmed email": "You've confirmed email", "You've confirmed email": "You've confirmed email",

View File

@ -471,6 +471,7 @@
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac", "You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля", "You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
"You was successfully authorized": "Вы были успешно авторизованы", "You was successfully authorized": "Вы были успешно авторизованы",
"You can't edit this post": "Вы не можете редактировать этот материал",
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах", "You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах",
"You've confirmed email": "Вы подтвердили почту", "You've confirmed email": "Вы подтвердили почту",
"You've reached a non-existed page": "Вы попали на несуществующую страницу", "You've reached a non-existed page": "Вы попали на несуществующую страницу",

View File

@ -29,7 +29,7 @@ export const CommentsTree = (props: Props) => {
const [newReactions, setNewReactions] = createSignal<Reaction[]>([]) const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
const [clearEditor, setClearEditor] = createSignal(false) const [clearEditor, setClearEditor] = createSignal(false)
const [clickedReplyId, setClickedReplyId] = createSignal<number>() const [clickedReplyId, setClickedReplyId] = createSignal<number>()
const { reactionEntities, createReaction } = useReactions() const { reactionEntities, createReaction, loadReactionsBy } = useReactions()
const comments = createMemo(() => const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'), Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
@ -68,7 +68,9 @@ export const CommentsTree = (props: Props) => {
setCookie() setCookie()
} }
}) })
const [posting, setPosting] = createSignal(false)
const handleSubmitComment = async (value: string) => { const handleSubmitComment = async (value: string) => {
setPosting(true)
try { try {
await createReaction({ await createReaction({
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
@ -76,10 +78,12 @@ export const CommentsTree = (props: Props) => {
shout: props.shoutId, shout: props.shoutId,
}) })
setClearEditor(true) setClearEditor(true)
await loadReactionsBy({ by: { shout: props.shoutSlug } })
} catch (error) { } catch (error) {
console.error('[handleCreate reaction]:', error) console.error('[handleCreate reaction]:', error)
} }
setClearEditor(false) setClearEditor(false)
setPosting(false)
} }
return ( return (
@ -157,6 +161,7 @@ export const CommentsTree = (props: Props) => {
placeholder={t('Write a comment...')} placeholder={t('Write a comment...')}
onSubmit={(value) => handleSubmitComment(value)} onSubmit={(value) => handleSubmitComment(value)}
setClear={clearEditor()} setClear={clearEditor()}
isPosting={posting()}
/> />
</ShowIfAuthenticated> </ShowIfAuthenticated>
</> </>

View File

@ -36,6 +36,7 @@ import { UploadModalContent } from './UploadModalContent'
import { Figcaption } from './extensions/Figcaption' import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure' import { Figure } from './extensions/Figure'
import { Loading } from '../_shared/Loading'
import styles from './SimplifiedEditor.module.scss' import styles from './SimplifiedEditor.module.scss'
type Props = { type Props = {
@ -58,6 +59,7 @@ type Props = {
controlsAlwaysVisible?: boolean controlsAlwaysVisible?: boolean
autoFocus?: boolean autoFocus?: boolean
isCancelButtonVisible?: boolean isCancelButtonVisible?: boolean
isPosting?: boolean
} }
const DEFAULT_MAX_LENGTH = 400 const DEFAULT_MAX_LENGTH = 400
@ -365,12 +367,14 @@ const SimplifiedEditor = (props: Props) => {
<Show when={isCancelButtonVisible()}> <Show when={isCancelButtonVisible()}>
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} /> <Button value={t('Cancel')} variant="secondary" onClick={handleClear} />
</Show> </Show>
<Button <Show when={!props.isPosting} fallback={<Loading />}>
value={props.submitButtonText ?? t('Send')} <Button
variant="primary" value={props.submitButtonText ?? t('Send')}
disabled={isEmpty()} variant="primary"
onClick={() => props.onSubmit(html())} disabled={isEmpty()}
/> onClick={() => props.onSubmit(html())}
/>
</Show>
</div> </div>
</Show> </Show>
</div> </div>

View File

@ -335,7 +335,7 @@ export const Header = (props: Props) => {
<Show when={props.title}> <Show when={props.title}>
<div <div
class={clsx(styles.articleControls, 'col-auto', { class={clsx(styles.articleControls, 'col-auto', {
[styles.articleControlsAuthorized]: session()?.user?.id, // FIXME: use or remove [styles.articleControlsAuthorized]: session()?.user?.id,
})} })}
> >
<SharePopup <SharePopup

View File

@ -34,12 +34,12 @@ export const HeaderAuth = (props: Props) => {
const { page } = useRouter() const { page } = useRouter()
const { session, author, isSessionLoaded } = useSession() const { session, author, isSessionLoaded } = useSession()
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications() const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
const { form, toggleEditorPanel, publishShout } = useEditorContext() const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext()
const handleBellIconClick = (event: Event) => { const handleBellIconClick = (event: Event) => {
event.preventDefault() event.preventDefault()
if (!author()?.id) { if (!session()?.access_token) {
showModal('auth') showModal('auth')
return return
} }
@ -48,15 +48,21 @@ export const HeaderAuth = (props: Props) => {
} }
const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings') const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings')
const isNotificationsVisible = createMemo(() => author()?.id && !isEditorPage()) const isNotificationsVisible = createMemo(() => session()?.access_token && !isEditorPage())
const isSaveButtonVisible = createMemo(() => author()?.id && isEditorPage()) const isSaveButtonVisible = createMemo(() => session()?.access_token && isEditorPage())
const isCreatePostButtonVisible = createMemo(() => !isEditorPage()) const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
const isAuthenticatedControlsVisible = createMemo(() => author()?.id && session()?.user?.email_verified) const isAuthenticatedControlsVisible = createMemo(
() => session()?.access_token && session()?.user?.email_verified,
)
const handleBurgerButtonClick = () => { const handleBurgerButtonClick = () => {
toggleEditorPanel() toggleEditorPanel()
} }
const handleSaveButtonClick = () => {
saveShout(form)
}
const [width, setWidth] = createSignal(0) const [width, setWidth] = createSignal(0)
const [editorMode, setEditorMode] = createSignal(t('Editing')) const [editorMode, setEditorMode] = createSignal(t('Editing'))
@ -100,14 +106,8 @@ export const HeaderAuth = (props: Props) => {
<Show when={isSessionLoaded()} keyed={true}> <Show when={isSessionLoaded()} keyed={true}>
<div class={clsx('col-auto col-lg-7', styles.usernav)}> <div class={clsx('col-auto col-lg-7', styles.usernav)}>
<div class={styles.userControl}> <div class={styles.userControl}>
<Show when={isCreatePostButtonVisible() && author()?.id}> <Show when={isCreatePostButtonVisible() && session()?.access_token}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
// styles.userControlItemCreate,
)}
>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} /> <Icon name="pencil-outline" class={styles.icon} />
@ -214,18 +214,12 @@ export const HeaderAuth = (props: Props) => {
</div> </div>
</Show> </Show>
<Show when={isCreatePostButtonVisible() && !!author()?.id}> <Show when={isCreatePostButtonVisible() && !session()?.access_token}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
// styles.userControlItemCreate,
)}
>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} /> <Icon name="pencil" class={styles.icon} />
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} /> <Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
</a> </a>
</div> </div>
</Show> </Show>
@ -233,24 +227,19 @@ export const HeaderAuth = (props: Props) => {
<Show <Show
when={isAuthenticatedControlsVisible()} when={isAuthenticatedControlsVisible()}
fallback={ fallback={
<Show when={!author()?.id}> <Show when={!session()?.access_token}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}>
<a href="?m=auth&mode=login"> <a href="?m=auth&mode=login">
<span class={styles.textLabel}>{t('Enter')}</span> <span class={styles.textLabel}>{t('Enter')}</span>
<Icon name="key" class={styles.icon} /> <Icon name="key" class={styles.icon} />
<Icon name="key" class={clsx(styles.icon, styles.iconHover)} /> {/*<Icon name="user-default" class={clsx(styles.icon, styles.iconHover)} />*/}
</a> </a>
</div> </div>
</Show> </Show>
} }
> >
<Show when={!isSaveButtonVisible()}> <Show when={!isSaveButtonVisible()}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
class={clsx(
styles.userControlItem,
// styles.userControlItemInbox
)}
>
<a href={getPagePath(router, 'inbox')}> <a href={getPagePath(router, 'inbox')}>
<div classList={{ entered: page().path === '/inbox' }}> <div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" class={styles.icon} /> <Icon name="inbox-white" class={styles.icon} />
@ -262,7 +251,7 @@ export const HeaderAuth = (props: Props) => {
</Show> </Show>
</div> </div>
<Show when={author()?.id}> <Show when={session()?.access_token}>
<ProfilePopup <ProfilePopup
onVisibilityChange={(isVisible) => { onVisibilityChange={(isVisible) => {
props.setIsProfilePopupVisible(isVisible) props.setIsProfilePopupVisible(isVisible)

View File

@ -13,16 +13,20 @@ import { Loading } from '../../_shared/Loading'
import styles from './DraftsView.module.scss' import styles from './DraftsView.module.scss'
export const DraftsView = () => { export const DraftsView = () => {
const { session } = useSession() const { author, loadSession } = useSession()
const [drafts, setDrafts] = createSignal<Shout[]>([]) const [drafts, setDrafts] = createSignal<Shout[]>([])
createEffect( createEffect(
on( on(
() => session(), () => author(),
async (s) => { async (a) => {
if (s) { if (a) {
const loadedDrafts = await apiClient.getDrafts() const { shouts: loadedDrafts, error } = await apiClient.getDrafts()
setDrafts(loadedDrafts.reverse() || []) if (error) {
console.warn(error)
await loadSession()
}
setDrafts(loadedDrafts || [])
} }
}, },
), ),
@ -46,7 +50,7 @@ export const DraftsView = () => {
return ( return (
<div class={clsx(styles.DraftsView)}> <div class={clsx(styles.DraftsView)}>
<Show when={session()?.user?.id} fallback={<Loading />}> <Show when={author()?.id} fallback={<Loading />}>
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5"> <div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">

View File

@ -175,7 +175,7 @@ export const apiClient = {
console.debug('[graphql.client.core] deleteShout:', response) console.debug('[graphql.client.core] deleteShout:', response)
}, },
getDrafts: async (): Promise<Shout[]> => { getDrafts: async (): Promise<CommonResult> => {
const response = await apiClient.private.query(draftsLoad, {}).toPromise() const response = await apiClient.private.query(draftsLoad, {}).toPromise()
console.debug('[graphql.client.core] getDrafts:', response) console.debug('[graphql.client.core] getDrafts:', response)
return response.data.get_shouts_drafts return response.data.get_shouts_drafts

View File

@ -3,40 +3,43 @@ import { gql } from '@urql/core'
export default gql` export default gql`
query LoadDraftsQuery { query LoadDraftsQuery {
get_shouts_drafts { get_shouts_drafts {
id error
title shouts {
subtitle
slug
layout
cover
# community
media
main_topic
topics {
id id
title title
body subtitle
slug slug
stat { layout
shouts cover
authors # community
followers media
main_topic
topics {
id
title
body
slug
stat {
shouts
authors
followers
}
}
authors {
id
name
slug
pic
created_at
} }
}
authors {
id
name
slug
pic
created_at created_at
} published_at
created_at featured_at
published_at stat {
featured_at viewed
stat { rating
viewed commented
rating }
commented
} }
} }
} }

View File

@ -37,7 +37,8 @@ export const EditPage = () => {
const fail = async (error: string) => { const fail = async (error: string) => {
console.error(error) console.error(error)
await snackbar?.showSnackbar({ type: 'error', body: t(error) }) const errorMessage = error === 'forbidden' ? "You can't edit this post" : error
await snackbar?.showSnackbar({ type: 'error', body: t(errorMessage) })
redirectPage(router, 'drafts') redirectPage(router, 'drafts')
} }

View File

@ -69,6 +69,7 @@ export default defineConfig(({ mode, command }) => {
https: {}, https: {},
port: 3000, port: 3000,
}, },
sourcemap: isDev,
css: { css: {
devSourcemap: isDev, devSourcemap: isDev,
preprocessorOptions: { preprocessorOptions: {