Merge branch 'dev' of https://github.com/Discours/discoursio-webapp into hotfix/expo
This commit is contained in:
commit
e4f7675606
|
@ -418,6 +418,7 @@
|
||||||
"Username": "Username",
|
"Username": "Username",
|
||||||
"Userpic": "Userpic",
|
"Userpic": "Userpic",
|
||||||
"Users": "Users",
|
"Users": "Users",
|
||||||
|
"User was not found": "User was not found",
|
||||||
"Video format not supported": "Video format not supported",
|
"Video format not supported": "Video format not supported",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"Views": "Views",
|
"Views": "Views",
|
||||||
|
@ -541,4 +542,4 @@
|
||||||
"Incorrect old password": "Incorrect old password",
|
"Incorrect old password": "Incorrect old password",
|
||||||
"Repeat new password": "Repeat new password",
|
"Repeat new password": "Repeat new password",
|
||||||
"Incorrect new password confirm": "Incorrect new password confirm"
|
"Incorrect new password confirm": "Incorrect new password confirm"
|
||||||
}
|
}
|
|
@ -550,6 +550,7 @@
|
||||||
"topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования",
|
"topicKeywords": "{topic}, Discours.io, статьи, журналистика, исследования",
|
||||||
"topics": "темы",
|
"topics": "темы",
|
||||||
"user already exist": "пользователь уже существует",
|
"user already exist": "пользователь уже существует",
|
||||||
|
"User was not found": "Пользователь не найден",
|
||||||
"verified": "уже подтверждён",
|
"verified": "уже подтверждён",
|
||||||
"video": "видео",
|
"video": "видео",
|
||||||
"view": "просмотр",
|
"view": "просмотр",
|
||||||
|
@ -568,4 +569,4 @@
|
||||||
"Incorrect old password": "Старый пароль не верен",
|
"Incorrect old password": "Старый пароль не верен",
|
||||||
"Repeat new password": "Повторите новый пароль",
|
"Repeat new password": "Повторите новый пароль",
|
||||||
"Incorrect new password confirm": "Неверное подтверждение нового пароля"
|
"Incorrect new password confirm": "Неверное подтверждение нового пароля"
|
||||||
}
|
}
|
|
@ -538,7 +538,7 @@ export const FullArticle = (props: Props) => {
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
||||||
<a
|
<a
|
||||||
href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}
|
href={getPagePath(router, 'edit', { shoutId: props.article?.id.toString() })}
|
||||||
class={styles.shoutStatsItemInner}
|
class={styles.shoutStatsItemInner}
|
||||||
>
|
>
|
||||||
<Icon name="pencil-outline" class={styles.icon} />
|
<Icon name="pencil-outline" class={styles.icon} />
|
||||||
|
|
|
@ -54,7 +54,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, 'inbox')
|
openPage(router, 'inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author.id.toString(),
|
initChat: props.author?.id.toString(),
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
requireAuthentication(() => {
|
requireAuthentication(() => {
|
||||||
openPage(router, 'inbox')
|
openPage(router, 'inbox')
|
||||||
changeSearchParams({
|
changeSearchParams({
|
||||||
initChat: props.author.id.toString(),
|
initChat: props.author?.id.toString(),
|
||||||
})
|
})
|
||||||
}, 'discussions')
|
}, 'discussions')
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export const Draft = (props: Props) => {
|
||||||
<div class={styles.actions}>
|
<div class={styles.actions}>
|
||||||
<a
|
<a
|
||||||
class={styles.actionItem}
|
class={styles.actionItem}
|
||||||
href={getPagePath(router, 'edit', { shoutId: props.shout.id.toString() })}
|
href={getPagePath(router, 'edit', { shoutId: props.shout?.id.toString() })}
|
||||||
>
|
>
|
||||||
{t('Edit')}
|
{t('Edit')}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -329,7 +329,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
||||||
<a href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}>
|
<a href={getPagePath(router, 'edit', { shoutId: props.article?.id.toString() })}>
|
||||||
<Icon name="pencil-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
<Icon name="pencil-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||||
<Icon
|
<Icon
|
||||||
name="pencil-outline-hover"
|
name="pencil-outline-hover"
|
||||||
|
|
|
@ -95,20 +95,15 @@ export const LoginForm = () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { errors } = await signIn({ email: email(), password: password() })
|
const { errors } = await signIn({ email: email(), password: password() })
|
||||||
console.error('[signIn errors]', errors)
|
|
||||||
if (errors?.length > 0) {
|
if (errors?.length > 0) {
|
||||||
if (
|
console.error('[signIn errors]', errors)
|
||||||
errors.some(
|
if (errors.some((error) => error.message.includes('user has not signed up email & password'))) {
|
||||||
(error) =>
|
|
||||||
error.message.includes('bad user credentials') || error.message.includes('user not found'),
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setValidationErrors((prev) => ({
|
setValidationErrors((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
password: t('Something went wrong, check email and password'),
|
password: t('Something went wrong, check email and password'),
|
||||||
}))
|
}))
|
||||||
} else if (errors.some((error) => error.message.includes('user not found'))) {
|
} else if (errors.some((error) => error.message.includes('user not found'))) {
|
||||||
setSubmitError('Пользователь не найден')
|
setSubmitError(t('User was not found'))
|
||||||
} else if (errors.some((error) => error.message.includes('email not verified'))) {
|
} else if (errors.some((error) => error.message.includes('email not verified'))) {
|
||||||
setSubmitError(
|
setSubmitError(
|
||||||
<div class={styles.info}>
|
<div class={styles.info}>
|
||||||
|
|
|
@ -103,7 +103,13 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<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() && session()?.access_token}>
|
<Show when={isCreatePostButtonVisible() && session()?.access_token}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div
|
||||||
|
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} />
|
||||||
|
@ -210,11 +216,17 @@ export const HeaderAuth = (props: Props) => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={isCreatePostButtonVisible() && !session()?.access_token}>
|
<Show when={isCreatePostButtonVisible() && !session()?.access_token}>
|
||||||
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
|
<div
|
||||||
|
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" class={styles.icon} />
|
<Icon name="pencil-outline" class={styles.icon} />
|
||||||
<Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
|
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
@ -227,7 +239,7 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<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="user-default" class={clsx(styles.icon, styles.iconHover)} />*/}
|
<Icon name="key" class={clsx(styles.icon, styles.iconHover)} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -45,7 +45,6 @@
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
// display: flex;
|
// display: flex;
|
||||||
|
@ -63,13 +62,11 @@
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include font-size(2.2rem);
|
@include font-size(2.2rem);
|
||||||
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
@include font-size(1.6rem);
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
margin: 0.8rem 0;
|
margin: 0.8rem 0;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
|
@ -107,7 +104,6 @@
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
@include font-size(1.4rem);
|
@include font-size(1.4rem);
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
color: var(--blue-500);
|
color: var(--blue-500);
|
||||||
|
@ -116,9 +112,7 @@
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: var(--black-400);
|
color: var(--black-400);
|
||||||
|
|
||||||
@include font-size(1.2rem);
|
@include font-size(1.2rem);
|
||||||
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const TopicBadge = (props: Props) => {
|
||||||
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
lang() === 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={clsx(styles.TopicBadge, props.subscriptionsMode)}>
|
<div class={clsx(styles.TopicBadge, { [styles.TopicBadgeSubscriptionsMode]: props.subscriptionsMode })}>
|
||||||
<div class={styles.content}>
|
<div class={styles.content}>
|
||||||
<div class={styles.basicInfo}>
|
<div class={styles.basicInfo}>
|
||||||
<Show when={props.subscriptionsMode}>
|
<Show when={props.subscriptionsMode}>
|
||||||
|
|
|
@ -37,7 +37,6 @@ export const ArticleCardSwiper = (props: Props) => {
|
||||||
[styles.Swiper]: props.slides.length > 1,
|
[styles.Swiper]: props.slides.length > 1,
|
||||||
[styles.articleMode]: true,
|
[styles.articleMode]: true,
|
||||||
[styles.ArticleCardSwiper]: props.slides.length > 1,
|
[styles.ArticleCardSwiper]: props.slides.length > 1,
|
||||||
[styles.unswiped]: props.slides.length === 1,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Show when={props.title}>
|
<Show when={props.title}>
|
||||||
|
|
|
@ -30,53 +30,57 @@ const ConnectContext = createContext<ConnectContextType>()
|
||||||
|
|
||||||
export const ConnectProvider = (props: { children: JSX.Element }) => {
|
export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
const [messageHandlers, setHandlers] = createSignal<MessageHandler[]>([])
|
const [messageHandlers, setHandlers] = createSignal<MessageHandler[]>([])
|
||||||
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
|
|
||||||
const [connected, setConnected] = createSignal(false)
|
const [connected, setConnected] = createSignal(false)
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
|
const [retried, setRetried] = createSignal<number>(0)
|
||||||
|
|
||||||
const addHandler = (handler: MessageHandler) => {
|
const addHandler = (handler: MessageHandler) => {
|
||||||
setHandlers((hhh) => [...hhh, handler])
|
setHandlers((hhh) => [...hhh, handler])
|
||||||
}
|
}
|
||||||
|
|
||||||
const [retried, setRetried] = createSignal<number>(0)
|
|
||||||
createEffect(async () => {
|
createEffect(async () => {
|
||||||
const token = session()?.access_token
|
const token = session()?.access_token
|
||||||
if (token && !connected()) {
|
if (token && !connected() && retried() <= RECONNECT_TIMES) {
|
||||||
console.info('[context.connect] init SSE connection')
|
console.info('[context.connect] init SSE connection')
|
||||||
await fetchEventSource('https://connect.discours.io', {
|
try {
|
||||||
method: 'GET',
|
await fetchEventSource('https://connect.discours.io', {
|
||||||
headers: {
|
method: 'GET',
|
||||||
'Content-Type': 'application/json',
|
headers: {
|
||||||
Authorization: token,
|
'Content-Type': 'application/json',
|
||||||
},
|
Authorization: token,
|
||||||
onmessage(event) {
|
},
|
||||||
const m: SSEMessage = JSON.parse(event.data || '{}')
|
onmessage(event) {
|
||||||
console.log('[context.connect] Received message:', m)
|
const m: SSEMessage = JSON.parse(event.data || '{}')
|
||||||
|
console.log('[context.connect] Received message:', m)
|
||||||
// Iterate over all registered handlers and call them
|
messageHandlers().forEach((handler) => handler(m))
|
||||||
messageHandlers().forEach((handler) => handler(m))
|
},
|
||||||
},
|
onopen: (response) => {
|
||||||
async onopen(response) {
|
console.log('[context.connect] SSE connection opened', response)
|
||||||
console.log('[context.connect] SSE connection opened', response)
|
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
||||||
if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
|
setConnected(true)
|
||||||
setConnected(true)
|
setRetried(0)
|
||||||
} else if (response.status === 401) {
|
return Promise.resolve()
|
||||||
throw new Error('unauthorized')
|
}
|
||||||
} else {
|
return Promise.reject(`SSE: cannot connect to real-time updates, status: ${response.status}`)
|
||||||
setRetried((r) => r + 1)
|
},
|
||||||
throw new Error('Internal Error')
|
onclose() {
|
||||||
}
|
console.log('[context.connect] SSE connection closed by server')
|
||||||
},
|
setConnected(false)
|
||||||
onclose() {
|
if (retried() < RECONNECT_TIMES) {
|
||||||
console.log('[context.connect] SSE connection closed by server')
|
setRetried((r) => r + 1)
|
||||||
setConnected(false)
|
}
|
||||||
},
|
},
|
||||||
onerror(err) {
|
onerror(err) {
|
||||||
if (err.message === 'unauthorized' || retried() > RECONNECT_TIMES) {
|
console.error('[context.connect] SSE connection error:', err)
|
||||||
throw err // rethrow to stop the operation
|
setConnected(false)
|
||||||
}
|
if (retried() < RECONNECT_TIMES) {
|
||||||
},
|
setRetried((r) => r + 1)
|
||||||
})
|
} else throw Error(err)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[context.connect] SSE connection failed:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -225,9 +225,12 @@ export const SessionProvider = (props: {
|
||||||
const appdata = session()?.user.app_data
|
const appdata = session()?.user.app_data
|
||||||
if (appdata) {
|
if (appdata) {
|
||||||
const { profile } = appdata
|
const { profile } = appdata
|
||||||
setAuthor(profile)
|
if (profile?.id) {
|
||||||
addAuthors([profile])
|
setAuthor(profile)
|
||||||
if (!profile) loadAuthor()
|
addAuthors([profile])
|
||||||
|
} else {
|
||||||
|
setTimeout(loadAuthor, 15)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|
|
@ -18,7 +18,7 @@ import styles from '../styles/Create.module.scss'
|
||||||
const handleCreate = async (layout: LayoutType) => {
|
const handleCreate = async (layout: LayoutType) => {
|
||||||
const shout = await apiClient.createArticle({ article: { layout: layout } })
|
const shout = await apiClient.createArticle({ article: { layout: layout } })
|
||||||
redirectPage(router, 'edit', {
|
redirectPage(router, 'edit', {
|
||||||
shoutId: shout.id.toString(),
|
shoutId: shout?.id.toString(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js'
|
import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on } from 'solid-js'
|
||||||
|
|
||||||
import { AuthGuard } from '../components/AuthGuard'
|
import { AuthGuard } from '../components/AuthGuard'
|
||||||
import { Loading } from '../components/_shared/Loading'
|
import { Loading } from '../components/_shared/Loading'
|
||||||
|
@ -7,7 +7,7 @@ import { useLocalize } from '../context/localize'
|
||||||
import { useSession } from '../context/session'
|
import { useSession } from '../context/session'
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { Shout } from '../graphql/schema/core.gen'
|
import { Shout } from '../graphql/schema/core.gen'
|
||||||
import { router } from '../stores/router'
|
import { router, useRouter } from '../stores/router'
|
||||||
|
|
||||||
import { redirectPage } from '@nanostores/router'
|
import { redirectPage } from '@nanostores/router'
|
||||||
import { useSnackbar } from '../context/snackbar'
|
import { useSnackbar } from '../context/snackbar'
|
||||||
|
@ -33,6 +33,7 @@ const getContentTypeTitle = (layout: LayoutType) => {
|
||||||
export const EditPage = () => {
|
export const EditPage = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { session } = useSession()
|
const { session } = useSession()
|
||||||
|
const { page } = useRouter()
|
||||||
const snackbar = useSnackbar()
|
const snackbar = useSnackbar()
|
||||||
|
|
||||||
const fail = async (error: string) => {
|
const fail = async (error: string) => {
|
||||||
|
@ -45,12 +46,22 @@ export const EditPage = () => {
|
||||||
const [shoutId, setShoutId] = createSignal<number>(0)
|
const [shoutId, setShoutId] = createSignal<number>(0)
|
||||||
const [shout, setShout] = createSignal<Shout>()
|
const [shout, setShout] = createSignal<Shout>()
|
||||||
|
|
||||||
onMount(() => {
|
createEffect(
|
||||||
const shoutId = window.location.pathname.split('/').pop()
|
on(
|
||||||
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
|
() => page(),
|
||||||
console.debug(`editing shout ${shoutIdFromUrl}`)
|
(p) => {
|
||||||
if (shoutIdFromUrl) setShoutId(shoutIdFromUrl)
|
if (p?.path) {
|
||||||
})
|
console.debug(p?.path)
|
||||||
|
const shoutId = p?.path.split('/').pop()
|
||||||
|
const shoutIdFromUrl = Number.parseInt(shoutId ?? '0', 10)
|
||||||
|
console.debug(`editing shout ${shoutIdFromUrl}`)
|
||||||
|
if (shoutIdFromUrl) {
|
||||||
|
setShoutId(shoutIdFromUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on([session, shout, shoutId], async ([ses, sh, shid]) => {
|
on([session, shout, shoutId], async ([ses, sh, shid]) => {
|
||||||
|
@ -63,6 +74,7 @@ export const EditPage = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
{ defer: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
const title = createMemo(() => {
|
const title = createMemo(() => {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user