Merge remote-tracking branch 'gitlab/dev' into create-shout-2

This commit is contained in:
bniwredyc 2023-04-17 13:04:01 +02:00
commit 61a6448b61
15 changed files with 148 additions and 62 deletions

View File

@ -144,6 +144,7 @@ img {
.shoutStatsItem { .shoutStatsItem {
@include font-size(1.5rem); @include font-size(1.5rem);
font-weight: 500; font-weight: 500;
display: inline-block; display: inline-block;
margin: 0 3.2rem 1em 0; margin: 0 3.2rem 1em 0;
@ -175,6 +176,19 @@ img {
a { a {
border: none; border: none;
text-decoration: none;
&:hover {
background: unset;
color: #000;
img {
filter: unset;
}
}
}
&:hover {
cursor: pointer;
} }
} }

View File

@ -1,7 +1,7 @@
import styles from './Comment.module.scss' import styles from './Comment.module.scss'
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { AuthorCard } from '../Author/Card' import { AuthorCard } from '../Author/Card'
import { Show, createMemo, createSignal, For, lazy, Suspense } from 'solid-js' import { Show, createMemo, createSignal, For, lazy, Suspense, createEffect } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import type { Author, Reaction } from '../../graphql/types.gen' import type { Author, Reaction } from '../../graphql/types.gen'
import MD from './MD' import MD from './MD'
@ -30,6 +30,7 @@ export const Comment = (props: Props) => {
const [isReplyVisible, setIsReplyVisible] = createSignal(false) const [isReplyVisible, setIsReplyVisible] = createSignal(false)
const [loading, setLoading] = createSignal<boolean>(false) const [loading, setLoading] = createSignal<boolean>(false)
const [editMode, setEditMode] = createSignal<boolean>(false) const [editMode, setEditMode] = createSignal<boolean>(false)
const [submitted, setSubmitted] = createSignal<boolean>(false)
const { session } = useSession() const { session } = useSession()
const { const {
@ -66,6 +67,7 @@ export const Comment = (props: Props) => {
}) })
setIsReplyVisible(false) setIsReplyVisible(false)
setLoading(false) setLoading(false)
setSubmitted(true)
} catch (error) { } catch (error) {
console.error('[handleCreate reaction]:', error) console.error('[handleCreate reaction]:', error)
} }
@ -94,6 +96,7 @@ export const Comment = (props: Props) => {
} }
const createdAt = new Date(comment()?.createdAt) const createdAt = new Date(comment()?.createdAt)
return ( return (
<li class={clsx(styles.comment, { [styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen })}> <li class={clsx(styles.comment, { [styles.isNew]: !isCommentAuthor() && createdAt > props.lastSeen })}>
<Show when={!!body()}> <Show when={!!body()}>
@ -105,7 +108,6 @@ export const Comment = (props: Props) => {
<Userpic <Userpic
user={comment().createdBy as Author} user={comment().createdBy as Author}
isBig={false} isBig={false}
isAuthorsList={false}
class={clsx({ class={clsx({
[styles.compactUserpic]: props.compact [styles.compactUserpic]: props.compact
})} })}
@ -202,7 +204,12 @@ export const Comment = (props: Props) => {
<Show when={isReplyVisible()}> <Show when={isReplyVisible()}>
<Suspense fallback={<p>{t('Loading')}</p>}> <Suspense fallback={<p>{t('Loading')}</p>}>
<CommentEditor placeholder={''} onSubmit={(value) => handleCreate(value)} /> <CommentEditor
placeholder={''}
clear={submitted()}
onSubmit={(value) => handleCreate(value)}
cancel={() => setIsReplyVisible(false)}
/>
</Suspense> </Suspense>
</Show> </Show>
</Show> </Show>

View File

@ -39,15 +39,16 @@ type Props = {
} }
export const CommentsTree = (props: Props) => { export const CommentsTree = (props: Props) => {
const { session } = useSession()
const { t } = useLocalize()
const [commentsOrder, setCommentsOrder] = createSignal<CommentsOrder>('createdAt') const [commentsOrder, setCommentsOrder] = createSignal<CommentsOrder>('createdAt')
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
const [submitted, setSubmitted] = createSignal(false)
const { const {
reactionEntities, reactionEntities,
actions: { createReaction } actions: { createReaction }
} = useReactions() } = useReactions()
const { t } = useLocalize()
const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
const comments = createMemo(() => const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT') Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT')
) )
@ -78,7 +79,7 @@ export const CommentsTree = (props: Props) => {
setCookie() setCookie()
} else if (currentDate > dateFromLocalStorage) { } else if (currentDate > dateFromLocalStorage) {
const newComments = comments().filter((c) => { const newComments = comments().filter((c) => {
if (c.replyTo) { if (c.replyTo || c.createdBy.slug === session()?.user.slug) {
return return
} }
const created = new Date(c.createdAt) const created = new Date(c.createdAt)
@ -89,8 +90,6 @@ export const CommentsTree = (props: Props) => {
} }
}) })
const { session } = useSession()
const [submitted, setSubmitted] = createSignal<boolean>(false)
const handleSubmitComment = async (value) => { const handleSubmitComment = async (value) => {
try { try {
await createReaction({ await createReaction({
@ -113,41 +112,42 @@ export const CommentsTree = (props: Props) => {
<span class={styles.newReactions}>&nbsp;+{newReactions().length}</span> <span class={styles.newReactions}>&nbsp;+{newReactions().length}</span>
</Show> </Show>
</h2> </h2>
<Show when={comments().length > 0}>
<ul class={clsx(styles.commentsViewSwitcher, 'view-switcher')}> <ul class={clsx(styles.commentsViewSwitcher, 'view-switcher')}>
<Show when={newReactions().length > 0}> <Show when={newReactions().length > 0}>
<li classList={{ selected: commentsOrder() === 'newOnly' }}> <li classList={{ selected: commentsOrder() === 'newOnly' }}>
<Button
variant="inline"
value={t('New only')}
onClick={() => {
setCommentsOrder('newOnly')
}}
class={styles.commentsViewSwitcherButton}
/>
</li>
</Show>
<li classList={{ selected: commentsOrder() === 'createdAt' }}>
<Button <Button
variant="inline" variant="inline"
value={t('New only')} value={t('By time')}
onClick={() => { onClick={() => {
setCommentsOrder('newOnly') setCommentsOrder('createdAt')
}} }}
class={styles.commentsViewSwitcherButton} class={styles.commentsViewSwitcherButton}
/> />
</li> </li>
</Show> <li classList={{ selected: commentsOrder() === 'rating' }}>
<li classList={{ selected: commentsOrder() === 'createdAt' }}> <Button
<Button variant="inline"
variant="inline" value={t('By rating')}
value={t('By time')} onClick={() => {
onClick={() => { setCommentsOrder('rating')
setCommentsOrder('createdAt') }}
}} class={styles.commentsViewSwitcherButton}
class={styles.commentsViewSwitcherButton} />
/> </li>
</li> </ul>
<li classList={{ selected: commentsOrder() === 'rating' }}> </Show>
<Button
variant="inline"
value={t('By rating')}
onClick={() => {
setCommentsOrder('rating')
}}
class={styles.commentsViewSwitcherButton}
/>
</li>
</ul>
</div> </div>
<ul class={styles.comments}> <ul class={styles.comments}>
<For each={sortedComments().filter((r) => !r.replyTo)}> <For each={sortedComments().filter((r) => !r.replyTo)}>

View File

@ -22,6 +22,7 @@ import styles from './Article.module.scss'
interface ArticleProps { interface ArticleProps {
article: Shout article: Shout
scrollToComments?: boolean
} }
interface MediaItem { interface MediaItem {
@ -87,6 +88,22 @@ export const FullArticle = (props: ArticleProps) => {
return mi return mi
}) })
const commentsRef: { current: HTMLDivElement } = { current: null }
const scrollToComments = () => {
window.scrollTo({
top: commentsRef.current.offsetTop - 96,
left: 0,
behavior: 'smooth'
})
}
createEffect(() => {
if (props.scrollToComments) {
scrollToComments()
}
})
const { const {
actions: { loadReactionsBy } actions: { loadReactionsBy }
} = useReactions() } = useReactions()
@ -122,10 +139,12 @@ export const FullArticle = (props: ArticleProps) => {
)} )}
</For> </For>
</div> </div>
<div <Show when={props.article.cover}>
class={styles.shoutCover} <div
style={{ 'background-image': `url('${props.article.cover}')` }} class={styles.shoutCover}
/> style={{ 'background-image': `url('${props.article.cover}')` }}
/>
</Show>
</div> </div>
<Show when={media() && props.article.layout !== 'image'}> <Show when={media() && props.article.layout !== 'image'}>
@ -183,12 +202,10 @@ export const FullArticle = (props: ArticleProps) => {
{props.article.stat?.viewed} {props.article.stat?.viewed}
</div> </div>
</Show> </Show>
<div class={styles.shoutStatsItem} onClick={scrollToComments}>
<a href="#comments" class={styles.shoutStatsItem}>
<Icon name="comment" class={styles.icon} /> <Icon name="comment" class={styles.icon} />
{props.article.stat?.commented ?? ''} {props.article.stat?.commented ?? ''}
</a> </div>
<div class={styles.shoutStatsItem}> <div class={styles.shoutStatsItem}>
<SharePopup <SharePopup
title={props.article.title} title={props.article.title}
@ -251,12 +268,12 @@ export const FullArticle = (props: ArticleProps) => {
<For each={props.article.authors}> <For each={props.article.authors}>
{(a) => ( {(a) => (
<div class="col-xl-12"> <div class="col-xl-12">
<AuthorCard author={a} compact={false} hasLink={true} liteButtons={true} /> <AuthorCard author={a} hasLink={true} liteButtons={true} />
</div> </div>
)} )}
</For> </For>
</div> </div>
<div id="comments"> <div id="comments" ref={(el) => (commentsRef.current = el)}>
<Show when={isReactionsLoaded()}> <Show when={isReactionsLoaded()}>
<CommentsTree <CommentsTree
shoutId={props.article.id} shoutId={props.article.id}

View File

@ -16,7 +16,7 @@ import { useLocalize } from '../../context/localize'
interface AuthorCardProps { interface AuthorCardProps {
caption?: string caption?: string
compact?: boolean hideWriteButton?: boolean
hideDescription?: boolean hideDescription?: boolean
hideFollow?: boolean hideFollow?: boolean
hasLink?: boolean hasLink?: boolean
@ -166,7 +166,7 @@ export const AuthorCard = (props: AuthorCardProps) => {
</button> </button>
</Show> </Show>
<Show when={!props.compact && !props.isAuthorsList}> <Show when={!props.hideWriteButton}>
<button <button
class={styles.button} class={styles.button}
classList={{ classList={{

View File

@ -6,7 +6,7 @@ export const AuthorFull = (props: { author: Author }) => {
return ( return (
<div class="row"> <div class="row">
<div class="col-md-18 col-lg-16 user-details"> <div class="col-md-18 col-lg-16 user-details">
<AuthorCard author={props.author} compact={false} isAuthorPage={true} /> <AuthorCard author={props.author} isAuthorPage={true} />
</div> </div>
</div> </div>
) )

View File

@ -69,7 +69,7 @@ export const Beside = (props: BesideProps) => {
<Show when={props.wrapper === 'author'}> <Show when={props.wrapper === 'author'}>
<AuthorCard <AuthorCard
author={value as Author} author={value as Author}
compact={true} hideWriteButton={true}
hasLink={true} hasLink={true}
truncateBio={true} truncateBio={true}
/> />

View File

@ -15,9 +15,11 @@ import { useLocalize } from '../../context/localize'
type Props = { type Props = {
title?: string title?: string
slug?: string
isHeaderFixed?: boolean isHeaderFixed?: boolean
articleBody?: string articleBody?: string
cover?: string cover?: string
scrollToComments?: (value: boolean) => void
} }
export const Header = (props: Props) => { export const Header = (props: Props) => {
@ -78,6 +80,10 @@ export const Header = (props: Props) => {
}) })
}) })
const scrollToComments = (value) => {
props.scrollToComments(value)
}
return ( return (
<header <header
class={styles.mainHeader} class={styles.mainHeader}
@ -138,9 +144,9 @@ export const Header = (props: Props) => {
containerCssClass={styles.control} containerCssClass={styles.control}
trigger={<Icon name="share-outline" class={styles.icon} />} trigger={<Icon name="share-outline" class={styles.icon} />}
/> />
<a href={getPagePath(router, 'inbox')} class={styles.control}> <div onClick={() => scrollToComments(true)} class={styles.control}>
<Icon name="comments-outline" class={styles.icon} /> <Icon name="comments-outline" class={styles.icon} />
</a> </div>
<a href={getPagePath(router, 'create')} class={styles.control}> <a href={getPagePath(router, 'create')} class={styles.control}>
<Icon name="pencil-outline" class={styles.icon} /> <Icon name="pencil-outline" class={styles.icon} />
</a> </a>

View File

@ -99,7 +99,7 @@ export const AuthorView = (props: AuthorProps) => {
}) })
setCommented(data) setCommented(data)
} catch (error) { } catch (error) {
console.error('[getReactionsBy]:', error) console.error('[getReactionsBy comment]', error)
} }
} }
}) })
@ -209,6 +209,19 @@ export const AuthorView = (props: AuthorProps) => {
</ul> </ul>
</div> </div>
</Match> </Match>
<Match when={searchParams().by === 'followed'}>
<div class="wide-container">
<div class="row">
<For each={followers()}>
{(follower: Author) => (
<div class="col-md-6 col-lg-4">
<AuthorCard author={follower} hideWriteButton={true} hasLink={true} />
</div>
)}
</For>
</div>
</div>
</Match>
<Match when={searchParams().by === 'rating'}> <Match when={searchParams().by === 'rating'}>
<Row1 article={sortedArticles()[0]} /> <Row1 article={sortedArticles()[0]} />
<Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} /> <Row2 articles={sortedArticles().slice(1, 3)} isEqual={true} />

View File

@ -122,7 +122,7 @@ export const FeedView = () => {
<For each={topAuthors().slice(0, 5)}> <For each={topAuthors().slice(0, 5)}>
{(author) => ( {(author) => (
<li> <li>
<AuthorCard author={author} compact={true} hasLink={true} /> <AuthorCard author={author} hideWriteButton={true} hasLink={true} />
</li> </li>
)} )}
</For> </For>
@ -153,7 +153,7 @@ export const FeedView = () => {
<AuthorCard <AuthorCard
author={comment.createdBy as Author} author={comment.createdBy as Author}
isFeedMode={true} isFeedMode={true}
compact={true} hideWriteButton={true}
hideFollow={true} hideFollow={true}
/> />
<div class={clsx('text-truncate', styles.commentArticleTitle)}> <div class={clsx('text-truncate', styles.commentArticleTitle)}>

View File

@ -21,6 +21,7 @@ type Props = {
placeholder?: string placeholder?: string
onSubmit: (value: string) => void onSubmit: (value: string) => void
clear?: boolean clear?: boolean
cancel?: () => void
initialContent?: string initialContent?: string
} }
@ -69,6 +70,9 @@ const CommentEditor = (props: Props) => {
const clearEditor = () => { const clearEditor = () => {
editorViewRef.current.destroy() editorViewRef.current.destroy()
initEditor() initEditor()
if (props.cancel) {
props.cancel()
}
} }
createEffect(() => { createEffect(() => {
@ -76,7 +80,6 @@ const CommentEditor = (props: Props) => {
clearEditor() clearEditor()
} }
}) })
return ( return (
<div class={styles.commentEditor}> <div class={styles.commentEditor}>
<div class={clsx('ProseMirrorOverrides', styles.textarea)} ref={(el) => (editorElRef.current = el)} /> <div class={clsx('ProseMirrorOverrides', styles.textarea)} ref={(el) => (editorElRef.current = el)} />

View File

@ -2,7 +2,7 @@ import type { JSX } from 'solid-js'
import { Header } from '../Nav/Header' import { Header } from '../Nav/Header'
import { Footer } from '../Discours/Footer' import { Footer } from '../Discours/Footer'
import { Show } from 'solid-js' import { createEffect, createSignal, Show } from 'solid-js'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import '../../styles/app.scss' import '../../styles/app.scss'
import styles from './PageLayout.module.scss' import styles from './PageLayout.module.scss'
@ -10,6 +10,7 @@ import { Meta } from '@solidjs/meta'
type PageLayoutProps = { type PageLayoutProps = {
headerTitle?: string headerTitle?: string
slug?: string
articleBody?: string articleBody?: string
cover?: string cover?: string
children: JSX.Element children: JSX.Element
@ -17,19 +18,29 @@ type PageLayoutProps = {
hideFooter?: boolean hideFooter?: boolean
class?: string class?: string
withPadding?: boolean withPadding?: boolean
scrollToComments?: (value: boolean) => void
} }
export const PageLayout = (props: PageLayoutProps) => { export const PageLayout = (props: PageLayoutProps) => {
const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed const isHeaderFixed = props.isHeaderFixed === undefined ? true : props.isHeaderFixed
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
createEffect(() => {
if (props.scrollToComments) {
props.scrollToComments(scrollToComments())
}
})
return ( return (
<> <>
<Meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta name="viewport" content="width=device-width, initial-scale=1" />
<Header <Header
slug={props.slug}
title={props.headerTitle} title={props.headerTitle}
articleBody={props.articleBody} articleBody={props.articleBody}
cover={props.articleBody} cover={props.articleBody}
isHeaderFixed={isHeaderFixed} isHeaderFixed={isHeaderFixed}
scrollToComments={(value) => setScrollToComments(value)}
/> />
<main <main
class={clsx('main-content', { class={clsx('main-content', {

View File

@ -2,6 +2,10 @@
position: relative; position: relative;
} }
.trigger {
cursor: pointer;
}
.popup { .popup {
background: #fff; background: #fff;
color: #000; color: #000;

View File

@ -37,7 +37,9 @@ export const Popup = (props: PopupProps) => {
return ( return (
<span class={clsx(styles.container, props.containerCssClass)} ref={(el) => (containerRef.current = el)}> <span class={clsx(styles.container, props.containerCssClass)} ref={(el) => (containerRef.current = el)}>
<span onClick={toggle}>{props.trigger}</span> <span class={styles.trigger} onClick={toggle}>
{props.trigger}
</span>
<Show when={isVisible()}> <Show when={isVisible()}>
<div <div
class={clsx(styles.popup, props.popupCssClass, { class={clsx(styles.popup, props.popupCssClass, {

View File

@ -1,4 +1,4 @@
import { createMemo, onMount, Show } from 'solid-js' import { createMemo, createSignal, onMount, Show } from 'solid-js'
import type { Shout } from '../graphql/types.gen' import type { Shout } from '../graphql/types.gen'
import { PageLayout } from '../components/_shared/PageLayout' import { PageLayout } from '../components/_shared/PageLayout'
import type { PageProps } from './types' import type { PageProps } from './types'
@ -48,12 +48,21 @@ export const ArticlePage = (props: PageProps) => {
script.dataset.ackeeDomainId = '1004abeb-89b2-4e85-ad97-74f8d2c8ed2d' script.dataset.ackeeDomainId = '1004abeb-89b2-4e85-ad97-74f8d2c8ed2d'
document.body.appendChild(script) document.body.appendChild(script)
}) })
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
return ( return (
<PageLayout headerTitle={article()?.title || ''} articleBody={article()?.body} cover={article()?.cover}> <PageLayout
headerTitle={article()?.title || ''}
slug={article()?.slug}
articleBody={article()?.body}
cover={article()?.cover}
scrollToComments={(value) => {
setScrollToComments(value)
}}
>
<ReactionsProvider> <ReactionsProvider>
<Show when={Boolean(article())} fallback={<Loading />}> <Show when={Boolean(article())} fallback={<Loading />}>
<FullArticle article={article()} /> <FullArticle article={article()} scrollToComments={scrollToComments()} />
</Show> </Show>
</ReactionsProvider> </ReactionsProvider>
</PageLayout> </PageLayout>