Merge remote-tracking branch 'hub/dev' into dev

This commit is contained in:
tonyrewin 2023-02-01 11:10:57 +03:00
commit eac6ad7d4c
14 changed files with 184 additions and 123 deletions

View File

@ -6,7 +6,7 @@ import { createMemo, For, Match, onMount, Show, Switch } from 'solid-js'
import type { Author, Shout } from '../../graphql/types.gen' import type { Author, Shout } from '../../graphql/types.gen'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import MD from './MD' import MD from './MD'
import { SharePopup } from './SharePopup' import { getShareUrl, SharePopup } from './SharePopup'
import { getDescription } from '../../utils/meta' import { getDescription } from '../../utils/meta'
import stylesHeader from '../Nav/Header.module.scss' import stylesHeader from '../Nav/Header.module.scss'
import styles from '../../styles/Article.module.scss' import styles from '../../styles/Article.module.scss'
@ -174,7 +174,6 @@ export const FullArticle = (props: ArticleProps) => {
title={props.article.title} title={props.article.title}
description={getDescription(props.article.body)} description={getDescription(props.article.body)}
imageUrl={props.article.cover} imageUrl={props.article.cover}
shareUrl={location.href}
containerCssClass={stylesHeader.control} containerCssClass={stylesHeader.control}
trigger={<Icon name="share-outline" class={styles.icon} />} trigger={<Icon name="share-outline" class={styles.icon} />}
/> />

View File

@ -1,7 +1,6 @@
import { Icon } from '../_shared/Icon' import { Icon } from '../_shared/Icon'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share' import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share'
import styles from '../_shared/Popup/Popup.module.scss' import styles from '../_shared/Popup/Popup.module.scss'
import type { PopupProps } from '../_shared/Popup' import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup' import { Popup } from '../_shared/Popup'
@ -13,6 +12,12 @@ type SharePopupProps = {
description: string description: string
} & Omit<PopupProps, 'children'> } & Omit<PopupProps, 'children'>
export const getShareUrl = (params: { pathname?: string } = {}) => {
if (typeof location === 'undefined') return ''
const pathname = params.pathname ?? location.pathname
return location.origin + pathname
}
export const SharePopup = (props: SharePopupProps) => { export const SharePopup = (props: SharePopupProps) => {
const [share] = createSocialShare(() => ({ const [share] = createSocialShare(() => ({
title: props.title, title: props.title,
@ -20,7 +25,7 @@ export const SharePopup = (props: SharePopupProps) => {
description: props.description description: props.description
})) }))
const copyLink = async () => { const copyLink = async () => {
await navigator.clipboard.writeText(window.location.href) await navigator.clipboard.writeText(props.shareUrl)
} }
return ( return (
<Popup {...props} variant="bordered"> <Popup {...props} variant="bordered">

View File

@ -9,7 +9,7 @@ import { locale } from '../../stores/ui'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { CardTopic } from './CardTopic' import { CardTopic } from './CardTopic'
import { RatingControl } from '../Article/RatingControl' import { RatingControl } from '../Article/RatingControl'
import { SharePopup } from '../Article/SharePopup' import { getShareUrl, SharePopup } from '../Article/SharePopup'
import stylesHeader from '../Nav/Header.module.scss' import stylesHeader from '../Nav/Header.module.scss'
import { getDescription } from '../../utils/meta' import { getDescription } from '../../utils/meta'
@ -73,12 +73,6 @@ export const ArticleCard = (props: ArticleCardProps) => {
const { cover, layout, slug, authors, stat } = props.article const { cover, layout, slug, authors, stat } = props.article
const [url, setUrl] = createSignal<string>(null)
onMount(() => {
const composeUrl = new URL(location.href)
setUrl(composeUrl.origin)
})
return ( return (
<section <section
class={clsx(styles.shoutCard, `${props.settings?.additionalClass || ''}`)} class={clsx(styles.shoutCard, `${props.settings?.additionalClass || ''}`)}
@ -203,7 +197,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
title={props.article['title']} title={props.article['title']}
description={getDescription(props.article['body'])} description={getDescription(props.article['body'])}
imageUrl={props.article['cover']} imageUrl={props.article['cover']}
shareUrl={`${url()}/${slug}`} shareUrl={getShareUrl({ pathname: `/${slug}` })}
trigger={ trigger={
<button> <button>
<Icon name="share-outline" class={clsx(styles.icon, styles.feedControlIcon)} /> <Icon name="share-outline" class={clsx(styles.icon, styles.feedControlIcon)} />

View File

@ -1,3 +1,11 @@
.sidebar {
max-height: calc(100vh - 120px);
overflow: auto;
padding-right: 1rem;
position: sticky;
top: 120px;
}
.counter { .counter {
align-items: center; align-items: center;
align-self: flex-start; align-self: flex-start;
@ -29,6 +37,7 @@
.settings { .settings {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 2em;
} }
a { a {

View File

@ -29,7 +29,7 @@ export const FeedSidebar = (props: FeedSidebarProps) => {
} }
return ( return (
<> <div class={styles.sidebar}>
<ul> <ul>
<li> <li>
<a href="#"> <a href="#">
@ -78,6 +78,7 @@ export const FeedSidebar = (props: FeedSidebarProps) => {
<Icon name="feed-notifications" class={styles.icon} /> <Icon name="feed-notifications" class={styles.icon} />
уведомления уведомления
</a> </a>
<span class={styles.counter}>283</span>
</li> </li>
</ul> </ul>
@ -119,6 +120,6 @@ export const FeedSidebar = (props: FeedSidebarProps) => {
{t('Feed settings')} {t('Feed settings')}
</a> </a>
</div> </div>
</> </div>
) )
} }

View File

@ -117,6 +117,10 @@
&:hover { &:hover {
background: none; background: none;
img {
filter: none;
}
} }
} }
} }

View File

@ -9,7 +9,7 @@ import styles from './Header.module.scss'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { HeaderAuth } from './HeaderAuth' import { HeaderAuth } from './HeaderAuth'
import { SharePopup } from '../Article/SharePopup' import { getShareUrl, SharePopup } from '../Article/SharePopup'
import { getDescription } from '../../utils/meta' import { getDescription } from '../../utils/meta'
const resources: { name: string; route: keyof Routes }[] = [ const resources: { name: string; route: keyof Routes }[] = [
@ -128,7 +128,7 @@ export const Header = (props: Props) => {
<SharePopup <SharePopup
title={props.title} title={props.title}
imageUrl={props.cover} imageUrl={props.cover}
shareUrl={location.href} shareUrl={getShareUrl()}
description={getDescription(props.articleBody)} description={getDescription(props.articleBody)}
onVisibilityChange={(isVisible) => { onVisibilityChange={(isVisible) => {
setIsSharePopupVisible(isVisible) setIsSharePopupVisible(isVisible)

View File

@ -14,16 +14,21 @@
} }
ul { ul {
margin-bottom: 3rem; margin: 0 0 3rem;
} }
li { li {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: 0 0 1rem; margin: 0 0 1rem;
white-space: nowrap;
width: 100%;
a { a {
margin-right: 0.5em; margin-right: 0.5em;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
} }
} }

View File

@ -78,12 +78,13 @@ export const FeedView = () => {
return ( return (
<> <>
<div class="wide-container feed"> <div class="wide-container feed">
<div class="row"> <div class="shift-content">
<div class={clsx('col-md-3', styles.feedNavigation)}> <div class={clsx('left-col', styles.feedNavigation)}>
<FeedSidebar authors={sortedAuthors()} /> <FeedSidebar authors={sortedAuthors()} />
</div> </div>
<div class="col-md-6"> <div class="row">
<div class="col-md-8">
<ul class="feed-filter"> <ul class="feed-filter">
<Show when={!!session()?.user?.slug}> <Show when={!!session()?.user?.slug}>
<li class="selected"> <li class="selected">
@ -130,7 +131,7 @@ export const FeedView = () => {
</Show> </Show>
</div> </div>
<aside class={clsx('col-md-3', styles.feedAside)}> <aside class={clsx('col-md-4', styles.feedAside)}>
<section class={styles.asideSection}> <section class={styles.asideSection}>
<h4>{t('Comments')}</h4> <h4>{t('Comments')}</h4>
<ul class={stylesArticle.comments}> <ul class={stylesArticle.comments}>
@ -187,6 +188,8 @@ export const FeedView = () => {
</section> </section>
</aside> </aside>
</div> </div>
</div>
<Show when={isLoadMoreButtonVisible()}> <Show when={isLoadMoreButtonVisible()}>
<p class="load-more-container"> <p class="load-more-container">
<button class="button" onClick={loadMore}> <button class="button" onClick={loadMore}>

View File

@ -2,7 +2,7 @@ import styles from './styles/CommentEditor.module.scss'
import './styles/ProseMirrorOverrides.scss' import './styles/ProseMirrorOverrides.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import Button from '../Button' import Button from '../Button'
import { createEffect, createMemo, onMount } from 'solid-js' import { createEffect, onMount, Show } from 'solid-js'
import { t } from '../../../utils/intl' import { t } from '../../../utils/intl'
//ProseMirror deps //ProseMirror deps
import { schema } from './schema' import { schema } from './schema'
@ -16,6 +16,8 @@ import { baseKeymap } from 'prosemirror-commands'
import { customKeymap } from '../../EditorNew/prosemirror/plugins/customKeymap' import { customKeymap } from '../../EditorNew/prosemirror/plugins/customKeymap'
import { placeholder } from '../../EditorNew/prosemirror/plugins/placeholder' import { placeholder } from '../../EditorNew/prosemirror/plugins/placeholder'
import { undo, redo, history } from 'prosemirror-history' import { undo, redo, history } from 'prosemirror-history'
import { useSession } from '../../../context/session'
import { showModal } from '../../../stores/ui'
type Props = { type Props = {
initialValue: string initialValue: string
@ -31,6 +33,7 @@ const getHtml = (state: EditorState) => {
} }
const CommentEditor = (props: Props) => { const CommentEditor = (props: Props) => {
const { session } = useSession()
const editorElRef: { current: HTMLDivElement } = { current: null } const editorElRef: { current: HTMLDivElement } = { current: null }
const menuElRef: { current: HTMLDivElement } = { current: null } const menuElRef: { current: HTMLDivElement } = { current: null }
const editorViewRef: { current: EditorView } = { current: null } const editorViewRef: { current: EditorView } = { current: null }
@ -65,7 +68,9 @@ const CommentEditor = (props: Props) => {
} }
createEffect(() => { createEffect(() => {
if (props.clear) clearEditor() if (props.clear) {
clearEditor()
}
}) })
return ( return (
@ -78,12 +83,28 @@ const CommentEditor = (props: Props) => {
<div class={styles.actions}> <div class={styles.actions}>
<div class={styles.menu} ref={(el) => (menuElRef.current = el)} /> <div class={styles.menu} ref={(el) => (menuElRef.current = el)} />
<div class={styles.buttons}> <div class={styles.buttons}>
<Show when={session()?.user?.slug}>
<Button value={t('Send')} variant="primary" onClick={handleSubmitButtonClick} /> <Button value={t('Send')} variant="primary" onClick={handleSubmitButtonClick} />
<Button value="Cancel" variant="secondary" onClick={clearEditor} /> </Show>
<Button value={t('cancel')} variant="secondary" onClick={clearEditor} />
</div> </div>
</div> </div>
</div> </div>
<div class={styles.helpText}>{'"Cmd-Z": Undo, "Cmd-Y": Redo'}</div> <div class={styles.helpText}>{'"Cmd-Z": Undo, "Cmd-Y": Redo'}</div>
<Show when={!session()?.user?.slug}>
<div class={styles.signInMessage} id="comments">
{t('To write a comment, you must')}&nbsp;
<span
class={styles.link}
onClick={(evt) => {
evt.preventDefault()
showModal('auth')
}}
>
{t('sign up or sign in')}
</span>
</div>
</Show>
</> </>
) )
} }

View File

@ -30,3 +30,21 @@
margin: 12px 0; margin: 12px 0;
font-style: italic; font-style: italic;
} }
.signInMessage {
background: #f1f2f3;
border-radius: 8px;
padding: 16px;
text-align: center;
font-size: 20px;
.link {
color: #2638d9;
cursor: pointer;
transition: 0.3s ease-in-out;
&:hover {
text-decoration: unset;
}
}
}

View File

@ -57,8 +57,8 @@
.feed-filter { .feed-filter {
display: flex; display: flex;
flex-wrap: wrap;
@include font-size(1.7rem); @include font-size(1.7rem);
font-weight: 500; font-weight: 500;
list-style: none; list-style: none;
margin: 0 0 1.6rem; margin: 0 0 1.6rem;
@ -66,7 +66,7 @@
li { li {
border-bottom: 4px solid transparent; border-bottom: 4px solid transparent;
margin: 0 1.4em 1em 0; margin: 0 1.4em 0.5em 0;
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;

View File

@ -691,11 +691,11 @@ astro-island {
.left-col { .left-col {
height: 100%; height: 100%;
padding-right: 2rem; padding-right: 2rem;
position: absolute;
right: 100%; right: 100%;
top: 0; top: 0;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
position: absolute;
width: 161px; width: 161px;
} }

View File

@ -1,7 +1,9 @@
export const getDescription = (body: string) => { export const getDescription = (body: string): string => {
if (!body) return null if (!body) {
return ''
}
const descriptionWordsArray = body const descriptionWordsArray = body
.slice(0, 150) .slice(0, 150) // meta description is roughly 155 characters long
.replaceAll(/<[^>]*>/g, '') .replaceAll(/<[^>]*>/g, '')
.split(' ') .split(' ')
return descriptionWordsArray.splice(0, descriptionWordsArray.length - 1).join(' ') + '...' return descriptionWordsArray.splice(0, descriptionWordsArray.length - 1).join(' ') + '...'