webapp/src/components/Article/FullArticle.tsx

272 lines
9.1 KiB
TypeScript
Raw Normal View History

2022-11-26 21:27:54 +00:00
import { capitalize, formatDate } from '../../utils'
2022-11-14 17:41:05 +00:00
import { Icon } from '../_shared/Icon'
2022-09-09 11:53:35 +00:00
import { AuthorCard } from '../Author/Card'
2023-04-04 00:09:56 +00:00
import { createMemo, createSignal, For, Match, onMount, Show, Switch } from 'solid-js'
2023-03-02 18:48:39 +00:00
import type { Author, Shout } from '../../graphql/types.gen'
2022-10-07 11:02:34 +00:00
import MD from './MD'
2023-02-10 01:19:20 +00:00
import { SharePopup } from './SharePopup'
import { getDescription } from '../../utils/meta'
import { ShoutRatingControl } from './ShoutRatingControl'
import { clsx } from 'clsx'
2022-11-26 16:51:08 +00:00
import { CommentsTree } from './CommentsTree'
2022-11-27 11:00:44 +00:00
import { useSession } from '../../context/session'
2022-11-27 11:35:27 +00:00
import VideoPlayer from './VideoPlayer'
2022-11-27 17:02:04 +00:00
import Slider from '../_shared/Slider'
2023-02-10 11:11:24 +00:00
import { getPagePath } from '@nanostores/router'
2023-04-04 00:09:56 +00:00
import { router } from '../../stores/router'
2023-02-17 09:21:02 +00:00
import { useReactions } from '../../context/reactions'
import { Title } from '@solidjs/meta'
import { useLocalize } from '../../context/localize'
2023-03-08 16:35:13 +00:00
import stylesHeader from '../Nav/Header.module.scss'
import styles from './Article.module.scss'
2022-11-27 11:00:44 +00:00
2022-09-09 11:53:35 +00:00
interface ArticleProps {
article: Shout
}
2022-11-27 11:00:44 +00:00
interface MediaItem {
url?: string
pic?: string
title?: string
body?: string
}
const MediaView = (props: { media: MediaItem; kind: Shout['layout'] }) => {
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
2022-11-27 11:00:44 +00:00
return (
<>
2022-11-27 17:02:04 +00:00
<Switch fallback={<a href={props.media.url}>{t('Cannot show this media type')}</a>}>
2022-11-27 11:00:44 +00:00
<Match when={props.kind === 'audio'}>
<div>
<h5>{props.media.title}</h5>
<audio controls>
<source src={props.media.url} />
</audio>
<hr />
</div>
</Match>
<Match when={props.kind === 'video'}>
2022-11-27 11:35:27 +00:00
<VideoPlayer url={props.media.url} />
2022-11-27 11:00:44 +00:00
</Match>
</Switch>
</>
)
}
2022-09-09 11:53:35 +00:00
export const FullArticle = (props: ArticleProps) => {
2023-02-17 09:21:02 +00:00
const { t } = useLocalize()
2023-03-29 08:51:27 +00:00
const { user, isAuthenticated } = useSession()
2023-02-28 17:13:14 +00:00
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
2022-09-09 11:53:35 +00:00
const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt)))
2022-12-07 18:38:05 +00:00
const mainTopic = createMemo(
() =>
props.article.topics?.find((topic) => topic?.slug === props.article.mainTopic) ||
props.article.topics[0]
)
2023-02-28 17:13:14 +00:00
onMount(async () => {
await loadReactionsBy({
by: { shout: props.article.slug }
})
setIsReactionsLoaded(true)
})
2023-03-29 08:51:27 +00:00
const canEdit = () => props.article.authors?.some((a) => a.slug === user()?.slug)
2022-11-27 11:00:44 +00:00
const bookmark = (ev) => {
// TODO: implement bookmark clicked
ev.preventDefault()
}
const body = createMemo(() => props.article.body)
const media = createMemo(() => {
const mi = JSON.parse(props.article.media || '[]')
console.debug(mi)
return mi
})
2023-02-17 09:21:02 +00:00
const {
actions: { loadReactionsBy }
2023-02-17 09:21:02 +00:00
} = useReactions()
2022-09-09 11:53:35 +00:00
return (
2023-02-09 22:54:53 +00:00
<>
2023-02-17 09:21:02 +00:00
<Title>{props.article.title}</Title>
2023-03-08 16:35:13 +00:00
<div class="wide-container">
2023-03-10 17:42:48 +00:00
<div class="row">
<article class="col-md-16 col-lg-14 col-xl-12 offset-md-5">
<div class={styles.shoutHeader}>
<div class={styles.shoutTopic}>
<a
href={getPagePath(router, 'topic', { slug: props.article.mainTopic })}
class={styles.mainTopicLink}
>
{mainTopic().title}
</a>
</div>
2022-09-09 11:53:35 +00:00
2023-03-10 17:42:48 +00:00
<h1>{props.article.title}</h1>
<Show when={props.article.subtitle}>
<h4>{capitalize(props.article.subtitle, false)}</h4>
</Show>
2022-09-09 11:53:35 +00:00
2023-03-10 17:42:48 +00:00
<div class={styles.shoutAuthor}>
<For each={props.article.authors}>
{(a: Author, index) => (
<>
<Show when={index() > 0}>, </Show>
<a href={getPagePath(router, 'author', { slug: a.slug })}>{a.name}</a>
</>
)}
</For>
</div>
<div
class={styles.shoutCover}
style={{ 'background-image': `url('${props.article.cover}')` }}
/>
2022-11-27 11:00:44 +00:00
</div>
2023-03-10 17:42:48 +00:00
<Show when={media() && props.article.layout !== 'image'}>
<div class="media-items">
<For each={media() || []}>
{(m: MediaItem) => (
<div class={styles.shoutMediaBody}>
<MediaView media={m} kind={props.article.layout} />
<Show when={m?.body}>
<div innerHTML={m.body} />
</Show>
</div>
)}
</For>
</div>
</Show>
<Show when={body()}>
<div class={styles.shoutBody}>
<Show when={!body().startsWith('<')} fallback={<div innerHTML={body()} />}>
<MD body={body()} />
</Show>
</div>
</Show>
</article>
</div>
2023-02-09 22:54:53 +00:00
</div>
2022-11-26 21:27:54 +00:00
2023-02-09 22:54:53 +00:00
<Show when={media() && props.article.layout === 'image'}>
<Slider slidesPerView={1} isPageGallery={true} isCardsWithCover={true} hasThumbs={true}>
<For each={media() || []}>
2023-03-08 16:35:13 +00:00
{(m) => (
2023-02-09 22:54:53 +00:00
<div class="swiper-slide">
<div class="swiper-slide__inner">
<img src={m.url || m.pic} alt={m.title} loading="lazy" />
<div class="swiper-lazy-preloader swiper-lazy-preloader-white" />
<div class="image-description" innerHTML={m.title} />
</div>
2022-09-13 09:59:04 +00:00
</div>
)}
</For>
2023-02-09 22:54:53 +00:00
</Slider>
</Show>
2022-09-09 11:53:35 +00:00
<div class="wide-container">
2023-03-10 17:42:48 +00:00
<div class="row">
<div class="col-md-16 offset-md-5">
<div class={styles.shoutStats}>
<div class={styles.shoutStatsItem}>
<ShoutRatingControl shout={props.article} class={styles.ratingControl} />
</div>
2023-02-09 22:54:53 +00:00
2023-03-10 17:42:48 +00:00
<Show when={props.article.stat?.viewed}>
<div class={clsx(styles.shoutStatsItem)}>
<Icon name="eye" class={clsx(styles.icon, styles.iconEye)} />
{props.article.stat?.viewed}
</div>
</Show>
2023-02-09 22:54:53 +00:00
2023-04-03 13:11:02 +00:00
<a href="#comments" class={styles.shoutStatsItem}>
2023-03-10 17:42:48 +00:00
<Icon name="comment" class={styles.icon} />
2023-04-03 13:38:44 +00:00
{props.article.stat?.commented ?? ''}
2023-04-03 13:11:02 +00:00
</a>
2023-02-09 22:54:53 +00:00
<div class={styles.shoutStatsItem}>
2023-03-10 17:42:48 +00:00
<SharePopup
title={props.article.title}
description={getDescription(props.article.body)}
imageUrl={props.article.cover}
containerCssClass={stylesHeader.control}
2023-03-29 20:18:25 +00:00
trigger={
<div class={styles.shoutStatsItemInner}>
<Icon name="share-outline" class={styles.icon} />
</div>
}
2023-03-10 17:42:48 +00:00
/>
2023-02-09 22:54:53 +00:00
</div>
2023-03-10 17:42:48 +00:00
<div class={styles.shoutStatsItem} onClick={bookmark}>
2023-03-29 20:18:25 +00:00
<div class={styles.shoutStatsItemInner}>
<Icon name="bookmark" class={styles.icon} />
</div>
2023-02-09 22:54:53 +00:00
</div>
2023-03-10 17:42:48 +00:00
<Show when={canEdit()}>
<div class={styles.shoutStatsItem}>
2023-03-29 20:18:25 +00:00
<a href="/edit" class={styles.shoutStatsItemInner}>
<Icon name="edit" class={clsx(styles.icon, styles.iconEdit)} />
2023-03-10 17:42:48 +00:00
{t('Edit')}
</a>
2023-02-09 22:54:53 +00:00
</div>
2023-03-10 17:42:48 +00:00
</Show>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalData)}>
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemAdditionalDataItem)}>
{formattedDate()}
2023-02-09 22:54:53 +00:00
</div>
2023-03-10 17:42:48 +00:00
</div>
</div>
<div class={styles.help}>
<Show when={isAuthenticated()}>
<button class="button">{t('Cooperate')}</button>
</Show>
<Show when={canEdit()}>
<button class="button button--light">{t('Invite to collab')}</button>
</Show>
</div>
<div class={styles.topicsList}>
<For each={props.article.topics}>
{(topic) => (
<div class={styles.shoutTopic}>
<a href={getPagePath(router, 'topic', { slug: topic.slug })}>{topic.title}</a>
</div>
)}
</For>
</div>
<div class={styles.shoutAuthorsList}>
<Show when={props.article.authors.length > 1}>
<h4>{t('Authors')}</h4>
</Show>
<For each={props.article.authors}>
{(a) => (
<div class="col-xl-12">
<AuthorCard author={a} compact={false} hasLink={true} liteButtons={true} />
</div>
)}
</For>
</div>
<div id="comments">
2023-03-10 17:42:48 +00:00
<Show when={isReactionsLoaded()}>
<CommentsTree
shoutId={props.article.id}
shoutSlug={props.article.slug}
commentAuthors={props.article.authors}
/>
</Show>
</div>
2023-02-09 22:54:53 +00:00
</div>
2022-09-09 11:53:35 +00:00
</div>
</div>
2023-02-09 22:54:53 +00:00
</>
2022-09-09 11:53:35 +00:00
)
}