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'
|
2023-05-01 18:32:32 +00:00
|
|
|
import { AuthorCard } from '../Author/AuthorCard'
|
2023-07-14 13:06:21 +00:00
|
|
|
import { AudioPlayer } from './AudioPlayer'
|
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'
|
2023-01-30 10:39:36 +00:00
|
|
|
import { getDescription } from '../../utils/meta'
|
2023-03-03 18:26:26 +00:00
|
|
|
import { ShoutRatingControl } from './ShoutRatingControl'
|
2022-11-23 19:14:59 +00:00
|
|
|
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'
|
2023-06-10 14:10:05 +00:00
|
|
|
import { VideoPlayer } from '../_shared/VideoPlayer'
|
2023-02-10 11:11:24 +00:00
|
|
|
import { getPagePath } from '@nanostores/router'
|
2023-05-01 18:32:32 +00:00
|
|
|
import { router, useRouter } 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'
|
2023-05-12 13:45:31 +00:00
|
|
|
import { imageProxy } from '../../utils/imageProxy'
|
2023-05-17 04:04:38 +00:00
|
|
|
import { Popover } from '../_shared/Popover'
|
2023-07-02 05:08:42 +00:00
|
|
|
import article from '../Editor/extensions/Article'
|
2023-07-17 19:07:37 +00:00
|
|
|
import { createEffect, For, createMemo, onMount, Show, createSignal } from 'solid-js'
|
2023-07-14 13:06:21 +00:00
|
|
|
import { MediaItem } from '../../pages/types'
|
|
|
|
import { AudioHeader } from './AudioHeader'
|
2023-07-17 17:14:34 +00:00
|
|
|
import { SolidSwiper } from '../_shared/SolidSwiper'
|
2022-11-27 11:00:44 +00:00
|
|
|
|
2022-09-09 11:53:35 +00:00
|
|
|
interface ArticleProps {
|
|
|
|
article: Shout
|
2023-04-17 10:31:20 +00:00
|
|
|
scrollToComments?: boolean
|
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-06-14 17:19:30 +00:00
|
|
|
const {
|
|
|
|
user,
|
|
|
|
isAuthenticated,
|
|
|
|
actions: { requireAuthentication }
|
|
|
|
} = 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)
|
2023-06-14 17:19:30 +00:00
|
|
|
|
2023-05-01 18:32:32 +00:00
|
|
|
const handleBookmarkButtonClick = (ev) => {
|
2023-06-14 17:19:30 +00:00
|
|
|
requireAuthentication(() => {
|
|
|
|
// TODO: implement bookmark clicked
|
|
|
|
ev.preventDefault()
|
|
|
|
}, 'bookmark')
|
2022-11-27 11:00:44 +00:00
|
|
|
}
|
|
|
|
|
2023-07-17 19:07:37 +00:00
|
|
|
const body = createMemo(() => {
|
|
|
|
if (props.article.layout === 'literature') {
|
|
|
|
try {
|
|
|
|
const media = JSON.parse(props.article.media)
|
|
|
|
if (media.length > 0) {
|
|
|
|
return media[0].body
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return props.article.body
|
|
|
|
})
|
2023-07-02 05:08:42 +00:00
|
|
|
const media = createMemo(() => {
|
|
|
|
return JSON.parse(props.article.media || '[]')
|
|
|
|
})
|
2022-11-27 11:00:44 +00:00
|
|
|
|
2023-04-17 10:31:20 +00:00
|
|
|
const commentsRef: { current: HTMLDivElement } = { current: null }
|
|
|
|
const scrollToComments = () => {
|
|
|
|
window.scrollTo({
|
|
|
|
top: commentsRef.current.offsetTop - 96,
|
|
|
|
left: 0,
|
|
|
|
behavior: 'smooth'
|
|
|
|
})
|
|
|
|
}
|
2023-05-26 11:30:27 +00:00
|
|
|
|
2023-05-01 18:32:32 +00:00
|
|
|
const { searchParams, changeSearchParam } = useRouter()
|
2023-04-17 10:31:20 +00:00
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
if (props.scrollToComments) {
|
|
|
|
scrollToComments()
|
|
|
|
}
|
|
|
|
})
|
2023-05-01 18:32:32 +00:00
|
|
|
|
2023-04-20 14:01:15 +00:00
|
|
|
createEffect(() => {
|
|
|
|
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
|
|
|
|
scrollToComments()
|
|
|
|
changeSearchParam('scrollTo', null)
|
|
|
|
}
|
|
|
|
})
|
2023-04-17 10:31:20 +00:00
|
|
|
|
2023-05-26 11:30:27 +00:00
|
|
|
createEffect(() => {
|
|
|
|
if (searchParams().commentId && isReactionsLoaded()) {
|
|
|
|
const commentElement = document.querySelector(`[id='comment_${searchParams().commentId}']`)
|
|
|
|
if (commentElement) {
|
|
|
|
commentElement.scrollIntoView({ behavior: 'smooth' })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2023-02-17 09:21:02 +00:00
|
|
|
const {
|
2023-03-03 18:26:26 +00:00
|
|
|
actions: { loadReactionsBy }
|
2023-02-17 09:21:02 +00:00
|
|
|
} = useReactions()
|
|
|
|
|
2023-07-17 19:07:37 +00:00
|
|
|
console.log('!!! props.s:', props.article.layout === 'literature')
|
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">
|
2023-07-02 05:08:42 +00:00
|
|
|
{/*TODO: Check styles.shoutTopic*/}
|
2023-07-17 17:14:34 +00:00
|
|
|
<Show when={props.article.layout !== 'audio'}>
|
|
|
|
<div class={styles.shoutHeader}>
|
|
|
|
<Show when={mainTopic()}>
|
|
|
|
<div class={styles.shoutTopic}>
|
|
|
|
<a
|
|
|
|
href={getPagePath(router, 'topic', { slug: props.article.mainTopic })}
|
|
|
|
class={styles.mainTopicLink}
|
|
|
|
>
|
|
|
|
{mainTopic().title}
|
|
|
|
</a>
|
2023-07-14 13:06:21 +00:00
|
|
|
</div>
|
2023-07-17 17:14:34 +00:00
|
|
|
</Show>
|
|
|
|
|
|
|
|
<h1>{props.article.title}</h1>
|
|
|
|
<Show when={props.article.subtitle}>
|
|
|
|
<h4>{capitalize(props.article.subtitle, false)}</h4>
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
<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>
|
|
|
|
<Show when={props.article.cover && props.article.layout !== 'video'}>
|
|
|
|
<div
|
|
|
|
class={styles.shoutCover}
|
|
|
|
style={{ 'background-image': `url('${imageProxy(props.article.cover)}')` }}
|
|
|
|
/>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
<Show when={props.article.layout === 'audio'}>
|
|
|
|
<AudioHeader
|
|
|
|
title={props.article.title}
|
|
|
|
cover={props.article.cover}
|
|
|
|
artistData={media()?.[0]}
|
|
|
|
topic={mainTopic()}
|
|
|
|
/>
|
|
|
|
<Show when={media().length > 0}>
|
|
|
|
<div class="media-items">
|
|
|
|
<AudioPlayer media={media()} articleSlug={props.article.slug} body={body()} />
|
2023-05-08 17:21:06 +00:00
|
|
|
</div>
|
2023-07-17 17:14:34 +00:00
|
|
|
</Show>
|
|
|
|
</Show>
|
|
|
|
<Show when={props.article.layout === 'image'}>
|
|
|
|
<SolidSwiper images={media()} />
|
|
|
|
</Show>
|
2023-07-02 05:08:42 +00:00
|
|
|
<Show when={media() && props.article.layout === 'video'}>
|
|
|
|
<div class="media-items">
|
|
|
|
<For each={media() || []}>
|
|
|
|
{(m: MediaItem) => (
|
|
|
|
<div class={styles.shoutMediaBody}>
|
|
|
|
<VideoPlayer videoUrl={m.url} title={m.title} description={m.body} />
|
|
|
|
<Show when={m?.body}>
|
|
|
|
<MD body={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>
|
2023-03-10 17:42:48 +00:00
|
|
|
</article>
|
|
|
|
</div>
|
2023-02-09 22:54:53 +00:00
|
|
|
</div>
|
2022-11-26 21:27:54 +00:00
|
|
|
|
2023-03-23 17:54:33 +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} />
|
2022-11-23 19:14:59 +00:00
|
|
|
</div>
|
2023-02-09 22:54:53 +00:00
|
|
|
|
2023-05-17 04:04:38 +00:00
|
|
|
<Popover content={t('Comment')}>
|
|
|
|
{(triggerRef: (el) => void) => (
|
|
|
|
<div class={styles.shoutStatsItem} ref={triggerRef} onClick={scrollToComments}>
|
|
|
|
<Icon name="comment" class={styles.icon} />
|
2023-07-09 18:34:59 +00:00
|
|
|
<Icon name="comment-hover" class={clsx(styles.icon, styles.iconHover)} />
|
2023-05-17 04:04:38 +00:00
|
|
|
{props.article.stat?.commented ?? ''}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Popover>
|
2023-07-09 18:34:59 +00:00
|
|
|
|
|
|
|
<Show when={props.article.stat?.viewed}>
|
|
|
|
<div class={clsx(styles.shoutStatsItem, styles.shoutStatsItemViews)}>
|
|
|
|
<Icon name="eye" class={styles.icon} />
|
|
|
|
<Icon name="eye" class={clsx(styles.icon, styles.iconHover)} />
|
|
|
|
{props.article.stat?.viewed}
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
|
2023-05-17 04:04:38 +00:00
|
|
|
<Popover content={t('Share')}>
|
|
|
|
{(triggerRef: (el) => void) => (
|
|
|
|
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
|
|
|
<SharePopup
|
|
|
|
title={props.article.title}
|
|
|
|
description={getDescription(props.article.body)}
|
|
|
|
imageUrl={props.article.cover}
|
|
|
|
containerCssClass={stylesHeader.control}
|
|
|
|
trigger={
|
|
|
|
<div class={styles.shoutStatsItemInner}>
|
|
|
|
<Icon name="share-outline" class={styles.icon} />
|
2023-07-09 18:34:59 +00:00
|
|
|
<Icon name="share-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
2023-05-17 04:04:38 +00:00
|
|
|
</div>
|
|
|
|
}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Popover>
|
|
|
|
<Popover content={t('Add to bookmarks')}>
|
|
|
|
{(triggerRef: (el) => void) => (
|
|
|
|
<div class={styles.shoutStatsItem} ref={triggerRef} onClick={handleBookmarkButtonClick}>
|
2023-03-29 20:18:25 +00:00
|
|
|
<div class={styles.shoutStatsItemInner}>
|
2023-05-17 04:04:38 +00:00
|
|
|
<Icon name="bookmark" class={styles.icon} />
|
2023-07-09 18:34:59 +00:00
|
|
|
<Icon name="bookmark-hover" class={clsx(styles.icon, styles.iconHover)} />
|
2023-03-29 20:18:25 +00:00
|
|
|
</div>
|
2023-05-17 04:04:38 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Popover>
|
2023-03-10 17:42:48 +00:00
|
|
|
<Show when={canEdit()}>
|
2023-05-17 04:04:38 +00:00
|
|
|
<Popover content={t('Edit')}>
|
|
|
|
{(triggerRef: (el) => void) => (
|
|
|
|
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
|
|
|
<a
|
|
|
|
href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}
|
|
|
|
class={styles.shoutStatsItemInner}
|
|
|
|
>
|
|
|
|
<Icon name="pencil-outline" class={styles.icon} />
|
2023-07-09 18:34:59 +00:00
|
|
|
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} />
|
2023-05-17 04:04:38 +00:00
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</Popover>
|
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}>
|
2023-05-10 01:46:39 +00:00
|
|
|
<Show when={isAuthenticated() && !canEdit()}>
|
2023-03-10 17:42:48 +00:00
|
|
|
<button class="button">{t('Cooperate')}</button>
|
|
|
|
</Show>
|
|
|
|
<Show when={canEdit()}>
|
|
|
|
<button class="button button--light">{t('Invite to collab')}</button>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
|
2023-05-26 11:30:27 +00:00
|
|
|
<Show when={props.article.topics.length}>
|
|
|
|
<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>
|
|
|
|
</Show>
|
2023-03-10 17:42:48 +00:00
|
|
|
|
|
|
|
<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">
|
2023-04-11 14:07:57 +00:00
|
|
|
<AuthorCard author={a} hasLink={true} liteButtons={true} />
|
2023-03-10 17:42:48 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</For>
|
|
|
|
</div>
|
2023-04-17 10:31:20 +00:00
|
|
|
<div id="comments" ref={(el) => (commentsRef.current = el)}>
|
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
|
|
|
)
|
|
|
|
}
|