Merge branch 'dev' of github.com:Discours/discoursio-webapp into feature/rating
This commit is contained in:
commit
7d515c4fe2
2
.github/workflows/node-ci.yml
vendored
2
.github/workflows/node-ci.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Check types
|
- name: Check types
|
||||||
run: npm run typecheck
|
run: npm run check:types
|
||||||
|
|
||||||
- name: Lint with Biome
|
- name: Lint with Biome
|
||||||
run: npm run check:code
|
run: npm run check:code
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
"lint:styles:fix": "stylelint **/*.{scss,css} --fix",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"typecheck": "tsc --noEmit",
|
"check:types": "tsc --noEmit",
|
||||||
"typecheck:watch": "tsc --noEmit --watch"
|
"check:types:watch": "tsc --noEmit --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"form-data": "4.0.0",
|
"form-data": "4.0.0",
|
||||||
|
|
|
@ -449,6 +449,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",
|
||||||
|
|
|
@ -472,6 +472,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": "Вы попали на несуществующую страницу",
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -151,7 +151,7 @@ export const Editor = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
showSnackbar({ body: t('Uploading image') })
|
showSnackbar({ body: t('Uploading image') })
|
||||||
const result = await handleImageUpload(uplFile)
|
const result = await handleImageUpload(uplFile, session()?.access_token)
|
||||||
|
|
||||||
editor()
|
editor()
|
||||||
.chain()
|
.chain()
|
||||||
|
|
|
@ -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>
|
||||||
|
<Show when={!props.isPosting} fallback={<Loading />}>
|
||||||
<Button
|
<Button
|
||||||
value={props.submitButtonText ?? t('Send')}
|
value={props.submitButtonText ?? t('Send')}
|
||||||
variant="primary"
|
variant="primary"
|
||||||
disabled={isEmpty()}
|
disabled={isEmpty()}
|
||||||
onClick={() => props.onSubmit(html())}
|
onClick={() => props.onSubmit(html())}
|
||||||
/>
|
/>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { Icon } from '../../_shared/Icon'
|
||||||
import { Loading } from '../../_shared/Loading'
|
import { Loading } from '../../_shared/Loading'
|
||||||
import { InlineForm } from '../InlineForm'
|
import { InlineForm } from '../InlineForm'
|
||||||
|
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import styles from './UploadModalContent.module.scss'
|
import styles from './UploadModalContent.module.scss'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -24,12 +25,12 @@ export const UploadModalContent = (props: Props) => {
|
||||||
const [uploadError, setUploadError] = createSignal<string | undefined>()
|
const [uploadError, setUploadError] = createSignal<string | undefined>()
|
||||||
const [dragActive, setDragActive] = createSignal(false)
|
const [dragActive, setDragActive] = createSignal(false)
|
||||||
const [dragError, setDragError] = createSignal<string | undefined>()
|
const [dragError, setDragError] = createSignal<string | undefined>()
|
||||||
|
const { session } = useSession()
|
||||||
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
|
||||||
const runUpload = async (file: UploadFile) => {
|
const runUpload = async (file: UploadFile) => {
|
||||||
try {
|
try {
|
||||||
setIsUploading(true)
|
setIsUploading(true)
|
||||||
const result = await handleImageUpload(file)
|
const result = await handleImageUpload(file, session()?.access_token)
|
||||||
props.onClose(result)
|
props.onClose(result)
|
||||||
setIsUploading(false)
|
setIsUploading(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -97,7 +97,12 @@ export const LoginForm = () => {
|
||||||
const { errors } = await signIn({ email: email(), password: password() })
|
const { errors } = await signIn({ email: email(), password: password() })
|
||||||
console.error('[signIn errors]', errors)
|
console.error('[signIn errors]', errors)
|
||||||
if (errors?.length > 0) {
|
if (errors?.length > 0) {
|
||||||
if (errors.some((error) => error.message.includes('bad user credentials'))) {
|
if (
|
||||||
|
errors.some(
|
||||||
|
(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'),
|
||||||
|
|
|
@ -61,7 +61,12 @@ export const SendResetLinkForm = () => {
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
})
|
})
|
||||||
console.debug('[SendResetLinkForm] authorizer response:', data)
|
console.debug('[SendResetLinkForm] authorizer response:', data)
|
||||||
if (errors?.some((error) => error.message.includes('bad user credentials'))) {
|
if (
|
||||||
|
errors?.some(
|
||||||
|
(error) =>
|
||||||
|
error.message.includes('bad user credentials') || error.message.includes('user not found'),
|
||||||
|
)
|
||||||
|
) {
|
||||||
setIsUserNotFound(true)
|
setIsUserNotFound(true)
|
||||||
}
|
}
|
||||||
if (data.message) setMessage(data.message)
|
if (data.message) setMessage(data.message)
|
||||||
|
|
|
@ -59,6 +59,7 @@ export const Header = (props: Props) => {
|
||||||
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
|
||||||
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
const [isZineVisible, setIsZineVisible] = createSignal(false)
|
||||||
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
const [isFeedVisible, setIsFeedVisible] = createSignal(false)
|
||||||
|
const { session } = useSession()
|
||||||
|
|
||||||
const toggleFixed = () => setFixed(!fixed())
|
const toggleFixed = () => setFixed(!fixed())
|
||||||
|
|
||||||
|
@ -68,7 +69,9 @@ export const Header = (props: Props) => {
|
||||||
let windowScrollTop = 0
|
let windowScrollTop = 0
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
if (topics()?.length) {
|
||||||
setRandomTopics(getRandomTopicsFromArray(topics()))
|
setRandomTopics(getRandomTopicsFromArray(topics()))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -330,7 +333,11 @@ export const Header = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
|
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
|
||||||
<Show when={props.title}>
|
<Show when={props.title}>
|
||||||
<div class={clsx(styles.articleControls, 'col-auto')}>
|
<div
|
||||||
|
class={clsx(styles.articleControls, 'col-auto', {
|
||||||
|
// FIXME: use or remove [styles.articleControlsAuthorized]: session()?.user?.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<SharePopup
|
<SharePopup
|
||||||
title={props.title}
|
title={props.title}
|
||||||
imageUrl={props.cover}
|
imageUrl={props.cover}
|
||||||
|
|
|
@ -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, saveShout, saveDraft, 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,22 +48,19 @@ 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()
|
||||||
}
|
}
|
||||||
// FIXME: use handler
|
|
||||||
const _handleSaveClick = () => {
|
const handleSaveButtonClick = () => {
|
||||||
const hasTopics = form.selectedTopics?.length > 0
|
|
||||||
if (hasTopics) {
|
|
||||||
saveShout(form)
|
saveShout(form)
|
||||||
} else {
|
|
||||||
saveDraft(form)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [width, setWidth] = createSignal(0)
|
const [width, setWidth] = createSignal(0)
|
||||||
|
@ -109,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} />
|
||||||
|
@ -223,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>
|
||||||
|
@ -242,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} />
|
||||||
|
@ -271,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)
|
||||||
|
|
|
@ -57,7 +57,7 @@ export const ProfileSettings = () => {
|
||||||
const [nameError, setNameError] = createSignal<string>()
|
const [nameError, setNameError] = createSignal<string>()
|
||||||
const { form, submit, updateFormField, setForm } = useProfileForm()
|
const { form, submit, updateFormField, setForm } = useProfileForm()
|
||||||
const { showSnackbar } = useSnackbar()
|
const { showSnackbar } = useSnackbar()
|
||||||
const { loadAuthor } = useSession()
|
const { loadAuthor, session } = useSession()
|
||||||
const { showConfirm } = useConfirm()
|
const { showConfirm } = useConfirm()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
@ -140,7 +140,7 @@ export const ProfileSettings = () => {
|
||||||
setUploadError(false)
|
setUploadError(false)
|
||||||
setIsUserpicUpdating(true)
|
setIsUserpicUpdating(true)
|
||||||
|
|
||||||
const result = await handleImageUpload(uploadFile)
|
const result = await handleImageUpload(uploadFile, session()?.access_token)
|
||||||
updateFormField('pic', result.url)
|
updateFormField('pic', result.url)
|
||||||
|
|
||||||
setUserpicFile(null)
|
setUserpicFile(null)
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { clsx } from 'clsx'
|
||||||
import { JSX, Show, createSignal } from 'solid-js'
|
import { JSX, Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { handleFileUpload } from '../../../utils/handleFileUpload'
|
import { handleFileUpload } from '../../../utils/handleFileUpload'
|
||||||
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
import { handleImageUpload } from '../../../utils/handleImageUpload'
|
||||||
|
@ -27,6 +28,7 @@ export const DropArea = (props: Props) => {
|
||||||
const [dragActive, setDragActive] = createSignal(false)
|
const [dragActive, setDragActive] = createSignal(false)
|
||||||
const [dropAreaError, setDropAreaError] = createSignal<string>()
|
const [dropAreaError, setDropAreaError] = createSignal<string>()
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
const { session } = useSession()
|
||||||
|
|
||||||
const runUpload = async (files) => {
|
const runUpload = async (files) => {
|
||||||
try {
|
try {
|
||||||
|
@ -35,7 +37,7 @@ export const DropArea = (props: Props) => {
|
||||||
const results: UploadedFile[] = []
|
const results: UploadedFile[] = []
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload
|
const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload
|
||||||
const result = await handler(file)
|
const result = await handler(file, session()?.access_token)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
props.onUpload(results)
|
props.onUpload(results)
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { Popover } from '../Popover'
|
||||||
|
|
||||||
import { SwiperRef } from './swiper'
|
import { SwiperRef } from './swiper'
|
||||||
|
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
import styles from './Swiper.module.scss'
|
import styles from './Swiper.module.scss'
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||||
|
@ -36,7 +37,7 @@ export const EditorSwiper = (props: Props) => {
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
const [slideIndex, setSlideIndex] = createSignal(0)
|
const [slideIndex, setSlideIndex] = createSignal(0)
|
||||||
const [slideBody, setSlideBody] = createSignal<string>()
|
const [slideBody, setSlideBody] = createSignal<string>()
|
||||||
|
const { session } = useSession()
|
||||||
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
const mainSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
const thumbSwipeRef: { current: SwiperRef } = { current: null }
|
||||||
|
|
||||||
|
@ -100,7 +101,7 @@ export const EditorSwiper = (props: Props) => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const results: UploadedFile[] = []
|
const results: UploadedFile[] = []
|
||||||
for (const file of selectedFiles) {
|
for (const file of selectedFiles) {
|
||||||
const result = await handleImageUpload(file)
|
const result = await handleImageUpload(file, session()?.access_token)
|
||||||
results.push(result)
|
results.push(result)
|
||||||
}
|
}
|
||||||
props.onImagesAdd(composeMediaItems(results))
|
props.onImagesAdd(composeMediaItems(results))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { gql } from '@urql/core'
|
||||||
export default gql`
|
export default gql`
|
||||||
query LoadDraftsQuery {
|
query LoadDraftsQuery {
|
||||||
get_shouts_drafts {
|
get_shouts_drafts {
|
||||||
|
error
|
||||||
|
shouts {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
subtitle
|
subtitle
|
||||||
|
@ -40,4 +42,5 @@ export default gql`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import { gql } from '@urql/core'
|
|
||||||
|
|
||||||
export default gql`
|
|
||||||
query MySubscriptionsQuery {
|
|
||||||
get_my_followed {
|
|
||||||
topics {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
slug
|
|
||||||
}
|
|
||||||
authors {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
pic
|
|
||||||
created_at
|
|
||||||
}
|
|
||||||
communities {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
slug
|
|
||||||
pic
|
|
||||||
created_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { PageContextBuiltInClientWithClientRouting } from 'vike/types'
|
import type { PageContextBuiltInClientWithClientRouting } from 'vike/types'
|
||||||
import type { PageContext } from './types'
|
import type { PageContext } from './types'
|
||||||
|
|
||||||
// import * as Sentry from '@sentry/browser'
|
import { init as SentryInit, replayIntegration } from '@sentry/browser'
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
import HttpApi from 'i18next-http-backend'
|
import HttpApi from 'i18next-http-backend'
|
||||||
import ICU from 'i18next-icu'
|
import ICU from 'i18next-icu'
|
||||||
|
@ -9,7 +9,7 @@ import { hydrate } from 'solid-js/web'
|
||||||
|
|
||||||
import { App } from '../components/App'
|
import { App } from '../components/App'
|
||||||
import { initRouter } from '../stores/router'
|
import { initRouter } from '../stores/router'
|
||||||
// import { SENTRY_DSN } from '../utils/config'
|
import { GLITCHTIP_DSN } from '../utils/config'
|
||||||
import { resolveHydrationPromise } from '../utils/hydrationPromise'
|
import { resolveHydrationPromise } from '../utils/hydrationPromise'
|
||||||
|
|
||||||
let layoutReady = false
|
let layoutReady = false
|
||||||
|
@ -20,13 +20,16 @@ export const render = async (pageContext: PageContextBuiltInClientWithClientRout
|
||||||
const { pathname, search } = window.location
|
const { pathname, search } = window.location
|
||||||
const searchParams = Object.fromEntries(new URLSearchParams(search))
|
const searchParams = Object.fromEntries(new URLSearchParams(search))
|
||||||
initRouter(pathname, searchParams)
|
initRouter(pathname, searchParams)
|
||||||
/*
|
|
||||||
if (SENTRY_DSN) {
|
SentryInit({
|
||||||
Sentry.init({
|
dsn: GLITCHTIP_DSN,
|
||||||
dsn: SENTRY_DSN,
|
tracesSampleRate: 0.01,
|
||||||
|
integrations: [replayIntegration()],
|
||||||
|
// Session Replay
|
||||||
|
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
|
||||||
|
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
|
||||||
})
|
})
|
||||||
}
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line import/no-named-as-default-member
|
// eslint-disable-next-line import/no-named-as-default-member
|
||||||
await i18next
|
await i18next
|
||||||
.use(HttpApi)
|
.use(HttpApi)
|
||||||
|
|
|
@ -5,6 +5,7 @@ export const cdnUrl = 'https://cdn.discours.io'
|
||||||
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
|
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
|
||||||
|
|
||||||
export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
|
export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
|
||||||
|
export const GLITCHTIP_DSN = import.meta.env.PUBLIC_GLITCHTIP_DSN || ''
|
||||||
|
|
||||||
const defaultSearchUrl = 'https://search.discours.io'
|
const defaultSearchUrl = 'https://search.discours.io'
|
||||||
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl
|
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl
|
||||||
|
|
|
@ -5,12 +5,15 @@ import { UploadedFile } from '../pages/types'
|
||||||
const apiBaseUrl = 'https://core.discours.io'
|
const apiBaseUrl = 'https://core.discours.io'
|
||||||
const apiUrl = `${apiBaseUrl}/upload`
|
const apiUrl = `${apiBaseUrl}/upload`
|
||||||
|
|
||||||
export const handleFileUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => {
|
export const handleFileUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', uploadFile.file, uploadFile.name)
|
formData.append('file', uploadFile.file, uploadFile.name)
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
Authorization: token,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
return response.json()
|
return response.json()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import { UploadedFile } from '../pages/types'
|
||||||
|
|
||||||
import { thumborUrl } from './config'
|
import { thumborUrl } from './config'
|
||||||
|
|
||||||
export const handleImageUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => {
|
export const handleImageUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('media', uploadFile.file, uploadFile.name)
|
formData.append('media', uploadFile.file, uploadFile.name)
|
||||||
|
const headers = token ? { Authorization: token } : {}
|
||||||
const response = await fetch(`${thumborUrl}/image`, {
|
const response = await fetch(`${thumborUrl}/image`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
|
headers,
|
||||||
})
|
})
|
||||||
|
|
||||||
const location = response.headers.get('Location')
|
const location = response.headers.get('Location')
|
||||||
|
|
|
@ -70,6 +70,7 @@ export default defineConfig(({ mode, command }) => {
|
||||||
https: {},
|
https: {},
|
||||||
port: 3000,
|
port: 3000,
|
||||||
},
|
},
|
||||||
|
sourcemap: isDev,
|
||||||
css: {
|
css: {
|
||||||
devSourcemap: isDev,
|
devSourcemap: isDev,
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user