Merge remote-tracking branch 'hub/main' into feature/sse-connect
This commit is contained in:
commit
45a5f0c542
5
public/icons/copy.svg
Normal file
5
public/icons/copy.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="ic24_copy">
|
||||||
|
<path id="vector" fill-rule="evenodd" clip-rule="evenodd" d="M4 6C4 3.79086 5.79086 2 8 2H12C14.2091 2 16 3.79086 16 6V14C16 16.2091 14.2091 18 12 18H8C5.79086 18 4 16.2091 4 14V6ZM8 4C6.89543 4 6 4.89543 6 6V14C6 15.1046 6.89543 16 8 16H12C13.1046 16 14 15.1046 14 14V6C14 4.89543 13.1046 4 12 4H8ZM16.6344 6.90064C16.9109 6.42258 17.5227 6.25922 18.0007 6.53576C19.1937 7.22587 20 8.5182 20 10V18C20 20.2092 18.2091 22 16 22H12C10.5182 22 9.22586 21.1937 8.53575 20.0007C8.2592 19.5227 8.42257 18.911 8.90063 18.6344C9.37869 18.3579 9.99041 18.5212 10.267 18.9993C10.6143 19.5997 11.261 20 12 20H16C17.1046 20 18 19.1046 18 18V10C18 9.261 17.5997 8.61429 16.9993 8.26697C16.5212 7.99043 16.3579 7.3787 16.6344 6.90064Z" fill="#141414"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 866 B |
|
@ -72,6 +72,7 @@
|
||||||
"Choose a post type": "Choose a post type",
|
"Choose a post type": "Choose a post type",
|
||||||
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.",
|
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Choose a title image for the article. You can immediately see how the publication card will look like.",
|
||||||
"Choose who you want to write to": "Choose who you want to write to",
|
"Choose who you want to write to": "Choose who you want to write to",
|
||||||
|
"Close": "Close",
|
||||||
"Co-author": "Co-author",
|
"Co-author": "Co-author",
|
||||||
"Collaborate": "Help Edit",
|
"Collaborate": "Help Edit",
|
||||||
"Collaborators": "Collaborators",
|
"Collaborators": "Collaborators",
|
||||||
|
@ -217,6 +218,7 @@
|
||||||
"Last rev.": "Посл. изм.",
|
"Last rev.": "Посл. изм.",
|
||||||
"Let's log in": "Let's log in",
|
"Let's log in": "Let's log in",
|
||||||
"Link copied": "Link copied",
|
"Link copied": "Link copied",
|
||||||
|
"Link copied to clipboard": "Link copied to clipboard",
|
||||||
"Link sent, check your email": "Link sent, check your email",
|
"Link sent, check your email": "Link sent, check your email",
|
||||||
"List of authors of the open editorial community": "List of authors of the open editorial community",
|
"List of authors of the open editorial community": "List of authors of the open editorial community",
|
||||||
"Lists": "Lists",
|
"Lists": "Lists",
|
||||||
|
@ -320,6 +322,7 @@
|
||||||
"Send link again": "Send link again",
|
"Send link again": "Send link again",
|
||||||
"Settings": "Settings",
|
"Settings": "Settings",
|
||||||
"Share": "Share",
|
"Share": "Share",
|
||||||
|
"Share publication": "Share publication",
|
||||||
"Show": "Show",
|
"Show": "Show",
|
||||||
"Show lyrics": "Show lyrics",
|
"Show lyrics": "Show lyrics",
|
||||||
"Show more": "Show more",
|
"Show more": "Show more",
|
||||||
|
@ -356,6 +359,7 @@
|
||||||
"Suggest an idea": "Suggest an idea",
|
"Suggest an idea": "Suggest an idea",
|
||||||
"Support Discours": "Support Discours",
|
"Support Discours": "Support Discours",
|
||||||
"Support the project": "Support the project",
|
"Support the project": "Support the project",
|
||||||
|
"Support us": "Support us",
|
||||||
"Terms of use": "Site rules",
|
"Terms of use": "Site rules",
|
||||||
"Text checking": "Text checking",
|
"Text checking": "Text checking",
|
||||||
"Thank you": "Thank you",
|
"Thank you": "Thank you",
|
||||||
|
|
|
@ -76,6 +76,7 @@
|
||||||
"Choose a post type": "Выберите тип публикации",
|
"Choose a post type": "Выберите тип публикации",
|
||||||
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.",
|
"Choose a title image for the article. You can immediately see how the publication card will look like.": "Выберите заглавное изображение для статьи. Тут же сразу можно увидеть как будет выглядеть карточка публикации.",
|
||||||
"Choose who you want to write to": "Выберите кому хотите написать",
|
"Choose who you want to write to": "Выберите кому хотите написать",
|
||||||
|
"Close": "Закрыть",
|
||||||
"Co-author": "Соавтор",
|
"Co-author": "Соавтор",
|
||||||
"Collaborate": "Помочь редактировать",
|
"Collaborate": "Помочь редактировать",
|
||||||
"Collaborators": "Соавторы",
|
"Collaborators": "Соавторы",
|
||||||
|
@ -229,6 +230,7 @@
|
||||||
"Last rev.": "Посл. изм.",
|
"Last rev.": "Посл. изм.",
|
||||||
"Let's log in": "Давайте авторизуемся",
|
"Let's log in": "Давайте авторизуемся",
|
||||||
"Link copied": "Ссылка скопирована",
|
"Link copied": "Ссылка скопирована",
|
||||||
|
"Link copied to clipboard": "Ссылка скопирована в буфер обмена",
|
||||||
"Link sent, check your email": "Ссылка отправлена, проверьте почту",
|
"Link sent, check your email": "Ссылка отправлена, проверьте почту",
|
||||||
"List of authors of the open editorial community": "Список авторов сообщества открытой редакции",
|
"List of authors of the open editorial community": "Список авторов сообщества открытой редакции",
|
||||||
"Lists": "Списки",
|
"Lists": "Списки",
|
||||||
|
@ -340,6 +342,7 @@
|
||||||
"Send link again": "Прислать ссылку ещё раз",
|
"Send link again": "Прислать ссылку ещё раз",
|
||||||
"Settings": "Настройки",
|
"Settings": "Настройки",
|
||||||
"Share": "Поделиться",
|
"Share": "Поделиться",
|
||||||
|
"Share publication": "Поделиться публикацией",
|
||||||
"Short opening": "Расскажите вашу историю...",
|
"Short opening": "Расскажите вашу историю...",
|
||||||
"Show": "Показать",
|
"Show": "Показать",
|
||||||
"Show lyrics": "Текст песни",
|
"Show lyrics": "Текст песни",
|
||||||
|
@ -378,6 +381,7 @@
|
||||||
"Suggest an idea": "Предложить идею",
|
"Suggest an idea": "Предложить идею",
|
||||||
"Support Discours": "Поддержите Дискурс",
|
"Support Discours": "Поддержите Дискурс",
|
||||||
"Support the project": "Поддержать проект",
|
"Support the project": "Поддержать проект",
|
||||||
|
"Support us": "Помочь журналу",
|
||||||
"Terms of use": "Правила сайта",
|
"Terms of use": "Правила сайта",
|
||||||
"Text checking": "Проверка текста",
|
"Text checking": "Проверка текста",
|
||||||
"Thank you": "Благодарности",
|
"Thank you": "Благодарности",
|
||||||
|
|
|
@ -14,12 +14,15 @@ import { MediaItem } from '../../pages/types'
|
||||||
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
import { capitalize } from '../../utils/capitalize'
|
||||||
import { isCyrillic } from '../../utils/cyrillic'
|
import { isCyrillic } from '../../utils/cyrillic'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { showModal } from '../../stores/ui'
|
||||||
|
import { getImageUrl, getOpenGraphImageUrl } from '../../utils/getImageUrl'
|
||||||
import { getDescription, getKeywords } from '../../utils/meta'
|
import { getDescription, getKeywords } from '../../utils/meta'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
import { Image } from '../_shared/Image'
|
import { Image } from '../_shared/Image'
|
||||||
|
import { InviteCoAuthorsModal } from '../_shared/InviteCoAuthorsModal'
|
||||||
import { Lightbox } from '../_shared/Lightbox'
|
import { Lightbox } from '../_shared/Lightbox'
|
||||||
import { Popover } from '../_shared/Popover'
|
import { Popover } from '../_shared/Popover'
|
||||||
|
import { ShareModal } from '../_shared/ShareModal'
|
||||||
import { ImageSwiper } from '../_shared/SolidSwiper'
|
import { ImageSwiper } from '../_shared/SolidSwiper'
|
||||||
import { VideoPlayer } from '../_shared/VideoPlayer'
|
import { VideoPlayer } from '../_shared/VideoPlayer'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
|
@ -60,6 +63,8 @@ const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
|
||||||
|
|
||||||
export const FullArticle = (props: Props) => {
|
export const FullArticle = (props: Props) => {
|
||||||
const [selectedImage, setSelectedImage] = createSignal('')
|
const [selectedImage, setSelectedImage] = createSignal('')
|
||||||
|
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
||||||
|
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
|
||||||
|
|
||||||
const { t, formatDate, lang } = useLocalize()
|
const { t, formatDate, lang } = useLocalize()
|
||||||
const {
|
const {
|
||||||
|
@ -68,8 +73,6 @@ export const FullArticle = (props: Props) => {
|
||||||
actions: { requireAuthentication },
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
|
|
||||||
|
|
||||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.created_at * 1000)))
|
const formattedDate = createMemo(() => formatDate(new Date(props.article.created_at * 1000)))
|
||||||
|
|
||||||
const mainTopic = createMemo(() => {
|
const mainTopic = createMemo(() => {
|
||||||
|
@ -292,12 +295,19 @@ export const FullArticle = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ogImage = props.article.cover
|
const cover = props.article.cover ?? 'production/image/logo_image.png'
|
||||||
? getImageUrl(props.article.cover, { width: 1200 })
|
|
||||||
: getImageUrl('production/image/logo_image.png')
|
const ogImage = getOpenGraphImageUrl(cover, {
|
||||||
|
title: props.article.title,
|
||||||
|
topic: mainTopic().title,
|
||||||
|
author: props.article.authors[0].name,
|
||||||
|
width: 1200,
|
||||||
|
})
|
||||||
|
|
||||||
const description = getDescription(props.article.description || body())
|
const description = getDescription(props.article.description || body())
|
||||||
const ogTitle = props.article.title
|
const ogTitle = props.article.title
|
||||||
const keywords = getKeywords(props.article)
|
const keywords = getKeywords(props.article)
|
||||||
|
const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` })
|
||||||
const getAuthorName = (a: Author) => {
|
const getAuthorName = (a: Author) => {
|
||||||
return lang() === 'en' && isCyrillic(a.name) ? capitalize(a.slug.replace(/-/, ' ')) : a.name
|
return lang() === 'en' && isCyrillic(a.name) ? capitalize(a.slug.replace(/-/, ' ')) : a.name
|
||||||
}
|
}
|
||||||
|
@ -426,7 +436,7 @@ export const FullArticle = (props: Props) => {
|
||||||
<ShoutRatingControl shout={props.article} class={styles.ratingControl} />
|
<ShoutRatingControl shout={props.article} class={styles.ratingControl} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popover content={t('Comment')}>
|
<Popover content={t('Comment')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={clsx(styles.shoutStatsItem)} ref={triggerRef} onClick={scrollToComments}>
|
<div class={clsx(styles.shoutStatsItem)} ref={triggerRef} onClick={scrollToComments}>
|
||||||
<Icon name="comment" class={styles.icon} />
|
<Icon name="comment" class={styles.icon} />
|
||||||
|
@ -453,7 +463,7 @@ export const FullArticle = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Popover content={t('Add to bookmarks')}>
|
<Popover content={t('Add to bookmarks')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div
|
<div
|
||||||
class={clsx(styles.shoutStatsItem, styles.shoutStatsItemBookmarks)}
|
class={clsx(styles.shoutStatsItem, styles.shoutStatsItemBookmarks)}
|
||||||
|
@ -468,14 +478,16 @@ export const FullArticle = (props: Props) => {
|
||||||
)}
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<Popover content={t('Share')}>
|
<Popover content={t('Share')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
<div class={styles.shoutStatsItem} ref={triggerRef}>
|
||||||
<SharePopup
|
<SharePopup
|
||||||
title={props.article.title}
|
title={props.article.title}
|
||||||
description={description}
|
description={description}
|
||||||
imageUrl={props.article.cover}
|
imageUrl={props.article.cover}
|
||||||
|
shareUrl={shareUrl}
|
||||||
containerCssClass={stylesHeader.control}
|
containerCssClass={stylesHeader.control}
|
||||||
|
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
|
||||||
trigger={
|
trigger={
|
||||||
<div class={styles.shoutStatsItemInner}>
|
<div class={styles.shoutStatsItemInner}>
|
||||||
<Icon name="share-outline" class={styles.icon} />
|
<Icon name="share-outline" class={styles.icon} />
|
||||||
|
@ -506,9 +518,9 @@ export const FullArticle = (props: Props) => {
|
||||||
<FeedArticlePopup
|
<FeedArticlePopup
|
||||||
isOwner={canEdit()}
|
isOwner={canEdit()}
|
||||||
containerCssClass={clsx(stylesHeader.control, styles.articlePopupOpener)}
|
containerCssClass={clsx(stylesHeader.control, styles.articlePopupOpener)}
|
||||||
title={props.article.title}
|
onShareClick={() => showModal('share')}
|
||||||
description={description}
|
onInviteClick={() => showModal('inviteCoAuthors')}
|
||||||
imageUrl={props.article.cover}
|
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
|
||||||
trigger={
|
trigger={
|
||||||
<button>
|
<button>
|
||||||
<Icon name="ellipsis" class={clsx(styles.icon)} />
|
<Icon name="ellipsis" class={clsx(styles.icon)} />
|
||||||
|
@ -570,6 +582,13 @@ export const FullArticle = (props: Props) => {
|
||||||
<Show when={selectedImage()}>
|
<Show when={selectedImage()}>
|
||||||
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
|
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
|
||||||
</Show>
|
</Show>
|
||||||
|
<InviteCoAuthorsModal title={t('Invite experts')} />
|
||||||
|
<ShareModal
|
||||||
|
title={props.article.title}
|
||||||
|
description={description}
|
||||||
|
imageUrl={props.article.cover}
|
||||||
|
shareUrl={shareUrl}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,13 @@
|
||||||
import type { PopupProps } from '../_shared/Popup'
|
import type { PopupProps } from '../_shared/Popup'
|
||||||
|
|
||||||
import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share'
|
|
||||||
import { createEffect, createSignal } from 'solid-js'
|
import { createEffect, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
|
||||||
import { Icon } from '../_shared/Icon'
|
|
||||||
import { Popup } from '../_shared/Popup'
|
import { Popup } from '../_shared/Popup'
|
||||||
|
import { ShareLinks } from '../_shared/ShareLinks'
|
||||||
import styles from '../_shared/Popup/Popup.module.scss'
|
|
||||||
|
|
||||||
type SharePopupProps = {
|
type SharePopupProps = {
|
||||||
title: string
|
title: string
|
||||||
shareUrl?: string
|
shareUrl: string
|
||||||
imageUrl: string
|
imageUrl: string
|
||||||
description: string
|
description: string
|
||||||
onVisibilityChange?: (value: boolean) => void
|
onVisibilityChange?: (value: boolean) => void
|
||||||
|
@ -25,63 +20,22 @@ export const getShareUrl = (params: { pathname?: string } = {}) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SharePopup = (props: SharePopupProps) => {
|
export const SharePopup = (props: SharePopupProps) => {
|
||||||
const { t } = useLocalize()
|
|
||||||
const [isVisible, setIsVisible] = createSignal(false)
|
const [isVisible, setIsVisible] = createSignal(false)
|
||||||
const {
|
|
||||||
actions: { showSnackbar },
|
|
||||||
} = useSnackbar()
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (props.onVisibilityChange) {
|
if (props.onVisibilityChange) {
|
||||||
props.onVisibilityChange(isVisible())
|
props.onVisibilityChange(isVisible())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [share] = createSocialShare(() => ({
|
|
||||||
title: props.title,
|
|
||||||
url: props.shareUrl,
|
|
||||||
description: props.description,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const copyLink = async () => {
|
|
||||||
await navigator.clipboard.writeText(props.shareUrl)
|
|
||||||
showSnackbar({ body: t('Link copied') })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popup {...props} variant="bordered" onVisibilityChange={(value) => setIsVisible(value)}>
|
<Popup {...props} variant="bordered" onVisibilityChange={(value) => setIsVisible(value)}>
|
||||||
<ul class="nodash">
|
<ShareLinks
|
||||||
<li>
|
variant="inPopup"
|
||||||
<button role="button" class={styles.shareControl} onClick={() => share(VK)}>
|
title={props.title}
|
||||||
<Icon name="vk-white" class={styles.icon} />
|
shareUrl={props.shareUrl}
|
||||||
VK
|
imageUrl={props.imageUrl}
|
||||||
</button>
|
description={props.description}
|
||||||
</li>
|
/>
|
||||||
<li>
|
|
||||||
<button role="button" class={styles.shareControl} onClick={() => share(FACEBOOK)}>
|
|
||||||
<Icon name="facebook-white" class={styles.icon} />
|
|
||||||
Facebook
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button role="button" class={styles.shareControl} onClick={() => share(TWITTER)}>
|
|
||||||
<Icon name="twitter-white" class={styles.icon} />
|
|
||||||
Twitter
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button role="button" class={styles.shareControl} onClick={() => share(TELEGRAM)}>
|
|
||||||
<Icon name="telegram-white" class={styles.icon} />
|
|
||||||
Telegram
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button role="button" class={styles.shareControl} onClick={copyLink}>
|
|
||||||
<Icon name="link-white" class={styles.icon} />
|
|
||||||
{t('Copy link')}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</Popup>
|
</Popup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
height: 0;
|
height: 0;
|
||||||
margin-bottom: 1.6rem;
|
margin-bottom: 1.6rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: 56.2%; //16:9
|
padding-bottom: 56.2%; // 16:9
|
||||||
position: relative;
|
position: relative;
|
||||||
transform-origin: 50% 50%;
|
transform-origin: 50% 50%;
|
||||||
transition: transform 1s ease-in-out;
|
transition: transform 1s ease-in-out;
|
||||||
|
|
|
@ -7,11 +7,14 @@ import { createMemo, createSignal, For, Show } from 'solid-js'
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
|
import { showModal } from '../../../stores/ui'
|
||||||
import { capitalize } from '../../../utils/capitalize'
|
import { capitalize } from '../../../utils/capitalize'
|
||||||
import { getDescription } from '../../../utils/meta'
|
import { getDescription } from '../../../utils/meta'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
|
import { InviteCoAuthorsModal } from '../../_shared/InviteCoAuthorsModal'
|
||||||
import { Popover } from '../../_shared/Popover'
|
import { Popover } from '../../_shared/Popover'
|
||||||
|
import { ShareModal } from '../../_shared/ShareModal'
|
||||||
import { CoverImage } from '../../Article/CoverImage'
|
import { CoverImage } from '../../Article/CoverImage'
|
||||||
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
|
||||||
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
|
||||||
|
@ -310,7 +313,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
|
|
||||||
<div class={styles.shoutCardDetailsContent}>
|
<div class={styles.shoutCardDetailsContent}>
|
||||||
<Show when={canEdit()}>
|
<Show when={canEdit()}>
|
||||||
<Popover content={t('Edit')}>
|
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
||||||
<a href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}>
|
<a href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}>
|
||||||
|
@ -325,7 +328,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</Popover>
|
</Popover>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Popover content={t('Add to bookmarks')}>
|
<Popover content={t('Add to bookmarks')} disabled={isActionPopupActive()}>
|
||||||
{(triggerRef: (el) => void) => (
|
{(triggerRef: (el) => void) => (
|
||||||
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
|
||||||
<button>
|
<button>
|
||||||
|
@ -348,6 +351,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
description={description}
|
description={description}
|
||||||
imageUrl={props.article.cover}
|
imageUrl={props.article.cover}
|
||||||
shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })}
|
shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })}
|
||||||
|
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
|
||||||
trigger={
|
trigger={
|
||||||
<button>
|
<button>
|
||||||
<Icon name="share-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
<Icon name="share-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||||
|
@ -366,9 +370,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
<FeedArticlePopup
|
<FeedArticlePopup
|
||||||
isOwner={canEdit()}
|
isOwner={canEdit()}
|
||||||
containerCssClass={stylesHeader.control}
|
containerCssClass={stylesHeader.control}
|
||||||
title={title}
|
onShareClick={() => showModal('share')}
|
||||||
description={description}
|
onInviteClick={() => showModal('inviteCoAuthors')}
|
||||||
imageUrl={props.article.cover}
|
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
|
||||||
trigger={
|
trigger={
|
||||||
<button>
|
<button>
|
||||||
<Icon name="ellipsis" class={clsx(styles.icon, styles.feedControlIcon)} />
|
<Icon name="ellipsis" class={clsx(styles.icon, styles.feedControlIcon)} />
|
||||||
|
@ -384,6 +388,13 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
</section>
|
</section>
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
<InviteCoAuthorsModal title={t('Invite experts')} />
|
||||||
|
<ShareModal
|
||||||
|
title={title}
|
||||||
|
description={description}
|
||||||
|
imageUrl={props.article.cover}
|
||||||
|
shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-top: -14px;
|
||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
left: auto !important;
|
left: auto !important;
|
||||||
|
@ -30,6 +31,10 @@
|
||||||
|
|
||||||
&.soon {
|
&.soon {
|
||||||
color: var(--black-300);
|
color: var(--black-300);
|
||||||
|
display: flex;
|
||||||
|
gap: 0.6rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -41,6 +46,7 @@
|
||||||
li:first-child .action {
|
li:first-child .action {
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
li:last-child .action {
|
li:last-child .action {
|
||||||
padding-bottom: 16px;
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
import type { PopupProps } from '../../_shared/Popup'
|
import type { PopupProps } from '../../_shared/Popup'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createSignal, Show } from 'solid-js'
|
import { Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { showModal } from '../../../stores/ui'
|
|
||||||
import { InviteCoAuthorsModal } from '../../_shared/InviteCoAuthorsModal'
|
|
||||||
import { Popup } from '../../_shared/Popup'
|
import { Popup } from '../../_shared/Popup'
|
||||||
import { SoonChip } from '../../_shared/SoonChip'
|
import { SoonChip } from '../../_shared/SoonChip'
|
||||||
|
|
||||||
import styles from './FeedArticlePopup.module.scss'
|
import styles from './FeedArticlePopup.module.scss'
|
||||||
|
|
||||||
type FeedArticlePopupProps = {
|
type FeedArticlePopupProps = {
|
||||||
title: string
|
|
||||||
imageUrl: string
|
|
||||||
isOwner: boolean
|
isOwner: boolean
|
||||||
description: string
|
onInviteClick: () => void
|
||||||
|
onShareClick: () => void
|
||||||
} & Omit<PopupProps, 'children'>
|
} & Omit<PopupProps, 'children'>
|
||||||
|
|
||||||
export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Popup {...props} variant="tiny" popupCssClass={styles.feedArticlePopup}>
|
<Popup {...props} horizontalAnchor={'right'} variant="tiny" popupCssClass={styles.feedArticlePopup}>
|
||||||
<ul class={clsx('nodash', styles.actionList)}>
|
<ul class={clsx('nodash', styles.actionList)}>
|
||||||
|
<li>
|
||||||
|
<button class={styles.action} role="button" onClick={props.onShareClick}>
|
||||||
|
{t('Share')}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<Show when={!props.isOwner}>
|
<Show when={!props.isOwner}>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
|
@ -38,13 +40,7 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
||||||
</li>
|
</li>
|
||||||
</Show>
|
</Show>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button class={styles.action} role="button" onClick={props.onInviteClick}>
|
||||||
class={styles.action}
|
|
||||||
role="button"
|
|
||||||
onClick={() => {
|
|
||||||
showModal('inviteCoAuthors')
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('Invite experts')}
|
{t('Invite experts')}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -86,7 +82,6 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
|
||||||
{/*</li>*/}
|
{/*</li>*/}
|
||||||
</ul>
|
</ul>
|
||||||
</Popup>
|
</Popup>
|
||||||
<InviteCoAuthorsModal title={t('Invite experts')} />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { clsx } from 'clsx'
|
||||||
import { createEffect, createSignal, on, Show } from 'solid-js'
|
import { createEffect, createSignal, on, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../../context/localize'
|
import { useLocalize } from '../../../../context/localize'
|
||||||
// import { resetSortedArticles } from '../../../../stores/zine/articles'
|
|
||||||
import { Icon } from '../../../_shared/Icon'
|
import { Icon } from '../../../_shared/Icon'
|
||||||
|
|
||||||
import styles from './PasswordField.module.scss'
|
import styles from './PasswordField.module.scss'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.backdrop {
|
.backdrop {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: rgb(20 20 20 / 70%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background: rgb(20 20 20 / 7%);
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&:not([class*='col-']) {
|
&:not([class*='col-']) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -124,11 +125,13 @@
|
||||||
.maxHeight {
|
.maxHeight {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modalInner {
|
.modalInner {
|
||||||
padding: 1rem 1rem 0;
|
padding: 1rem 1rem 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.TableOfContentsFixedWrapper {
|
.TableOfContentsFixedWrapper {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
@include media-breakpoint-down(xl) {
|
@include media-breakpoint-down(xl) {
|
||||||
|
|
|
@ -7,9 +7,8 @@
|
||||||
|
|
||||||
.basicInfo {
|
.basicInfo {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-wrap: nowrap;
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
padding: 0 0 4rem;
|
padding: 0 0 4rem;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
.navigation {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.showMore {
|
.showMore {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -147,7 +147,7 @@ export const Expo = (props: Props) => {
|
||||||
<div class={styles.Expo}>
|
<div class={styles.Expo}>
|
||||||
<Show when={sortedArticles().length > 0} fallback={<Loading />}>
|
<Show when={sortedArticles().length > 0} fallback={<Loading />}>
|
||||||
<div class="wide-container">
|
<div class="wide-container">
|
||||||
<ul class={clsx('view-switcher', styles.navigation)}>
|
<ul class={clsx('view-switcher')}>
|
||||||
<li class={clsx({ 'view-switcher__item--selected': !props.layout })}>
|
<li class={clsx({ 'view-switcher__item--selected': !props.layout })}>
|
||||||
<ConditionalWrapper
|
<ConditionalWrapper
|
||||||
condition={Boolean(props.layout)}
|
condition={Boolean(props.layout)}
|
||||||
|
|
|
@ -195,6 +195,7 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4rem;
|
||||||
|
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
@ -205,6 +206,7 @@
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
& > li {
|
& > li {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -213,8 +215,7 @@
|
||||||
|
|
||||||
.dropdowns {
|
.dropdowns {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-flow: row nowrap;
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,7 @@ export const FeedView = (props: Props) => {
|
||||||
<div class={styles.dropdowns}>
|
<div class={styles.dropdowns}>
|
||||||
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
|
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
|
||||||
<DropDown
|
<DropDown
|
||||||
|
popupProps={{ horizontalAnchor: 'right' }}
|
||||||
options={periods}
|
options={periods}
|
||||||
currentOption={currentPeriod()}
|
currentOption={currentPeriod()}
|
||||||
triggerCssClass={styles.periodSwitcher}
|
triggerCssClass={styles.periodSwitcher}
|
||||||
|
@ -276,6 +277,7 @@ export const FeedView = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<DropDown
|
<DropDown
|
||||||
|
popupProps={{ horizontalAnchor: 'right' }}
|
||||||
options={visibilities}
|
options={visibilities}
|
||||||
currentOption={currentVisibility()}
|
currentOption={currentVisibility()}
|
||||||
triggerCssClass={styles.periodSwitcher}
|
triggerCssClass={styles.periodSwitcher}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.trigger {
|
.trigger {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chevron {
|
.chevron {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
|
||||||
|
@ -8,3 +9,7 @@
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ export type Option = {
|
||||||
|
|
||||||
type Props<TOption> = {
|
type Props<TOption> = {
|
||||||
class?: string
|
class?: string
|
||||||
popupProps?: PopupProps
|
popupProps?: Partial<PopupProps>
|
||||||
options: TOption[]
|
options: TOption[]
|
||||||
currentOption: TOption
|
currentOption: TOption
|
||||||
triggerCssClass?: string
|
triggerCssClass?: string
|
||||||
|
@ -56,9 +56,12 @@ export const DropDown = <TOption extends Option = Option>(props: Props<TOption>)
|
||||||
onVisibilityChange={(isVisible) => setIsPopupVisible(isVisible)}
|
onVisibilityChange={(isVisible) => setIsPopupVisible(isVisible)}
|
||||||
{...props.popupProps}
|
{...props.popupProps}
|
||||||
>
|
>
|
||||||
<For each={props.options.filter((p) => p.value !== props.currentOption.value)}>
|
<For each={props.options}>
|
||||||
{(option) => (
|
{(option) => (
|
||||||
<div class="link" onClick={() => props.onChange(option)}>
|
<div
|
||||||
|
class={clsx('link', { [styles.active]: props.currentOption.value === option.value })}
|
||||||
|
onClick={() => props.onChange(option)}
|
||||||
|
>
|
||||||
{option.title}
|
{option.title}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
top: calc(100% + 8px);
|
top: calc(100% + 8px);
|
||||||
z-index: 100;
|
z-index: 101;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -103,23 +103,6 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.shareControl {
|
|
||||||
text-align: left;
|
|
||||||
transition:
|
|
||||||
color 0.3s,
|
|
||||||
background-color 0.3s;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #000;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.icon img {
|
|
||||||
filter: invert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: animation
|
// TODO: animation
|
||||||
|
|
82
src/components/_shared/ShareLinks/ShareLinks.module.scss
Normal file
82
src/components/_shared/ShareLinks/ShareLinks.module.scss
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
.ShareLinks {
|
||||||
|
.shareControl {
|
||||||
|
text-align: left;
|
||||||
|
transition:
|
||||||
|
color 0.3s,
|
||||||
|
background-color 0.3s;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #000;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.icon img {
|
||||||
|
filter: invert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 3.6rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: inline-block;
|
||||||
|
filter: invert(1);
|
||||||
|
max-height: 2rem;
|
||||||
|
max-width: 2rem;
|
||||||
|
transition: filter 0.3s;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.inModal {
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shareControl {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0 -12px;
|
||||||
|
width: calc(100% + 24px);
|
||||||
|
transition: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkInput {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: -2rem !important;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-right: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
right: 2px;
|
||||||
|
width: 40px;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.isCopied {
|
||||||
|
@include font-size(1.6rem);
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 1.2rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
color: var(--blue-500);
|
||||||
|
}
|
||||||
|
}
|
101
src/components/_shared/ShareLinks/ShareLinks.tsx
Normal file
101
src/components/_shared/ShareLinks/ShareLinks.tsx
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import { createSocialShare, FACEBOOK, TELEGRAM, TWITTER, VK } from '@solid-primitives/share'
|
||||||
|
import { clsx } from 'clsx'
|
||||||
|
import { createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { useSnackbar } from '../../../context/snackbar'
|
||||||
|
import { Icon } from '../Icon'
|
||||||
|
import { Popover } from '../Popover'
|
||||||
|
|
||||||
|
import styles from './ShareLinks.module.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
shareUrl: string
|
||||||
|
imageUrl?: string
|
||||||
|
class?: string
|
||||||
|
variant: 'inModal' | 'inPopup'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ShareLinks = (props: Props) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
const [isLinkCopied, setIsLinkCopied] = createSignal(false)
|
||||||
|
const {
|
||||||
|
actions: { showSnackbar },
|
||||||
|
} = useSnackbar()
|
||||||
|
|
||||||
|
const [share] = createSocialShare(() => ({
|
||||||
|
title: props.title,
|
||||||
|
url: props.shareUrl,
|
||||||
|
description: props.description,
|
||||||
|
}))
|
||||||
|
const copyLink = async () => {
|
||||||
|
await navigator.clipboard.writeText(props.shareUrl)
|
||||||
|
if (props.variant === 'inModal') {
|
||||||
|
setIsLinkCopied(true)
|
||||||
|
setTimeout(() => setIsLinkCopied(false), 3000)
|
||||||
|
} else {
|
||||||
|
showSnackbar({ body: t('Link copied') })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class={clsx(styles.ShareLinks, props.class, { [styles.inModal]: props.variant === 'inModal' })}>
|
||||||
|
<ul class="nodash">
|
||||||
|
<li>
|
||||||
|
<button role="button" class={styles.shareControl} onClick={() => share(FACEBOOK)}>
|
||||||
|
<Icon name="facebook-white" class={styles.icon} />
|
||||||
|
Facebook
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button role="button" class={styles.shareControl} onClick={() => share(TWITTER)}>
|
||||||
|
<Icon name="twitter-white" class={styles.icon} />
|
||||||
|
Twitter
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button role="button" class={styles.shareControl} onClick={() => share(TELEGRAM)}>
|
||||||
|
<Icon name="telegram-white" class={styles.icon} />
|
||||||
|
Telegram
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button role="button" class={styles.shareControl} onClick={() => share(VK)}>
|
||||||
|
<Icon name="vk-white" class={styles.icon} />
|
||||||
|
VK
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Show
|
||||||
|
when={props.variant === 'inModal'}
|
||||||
|
fallback={
|
||||||
|
<button role="button" class={styles.shareControl} onClick={copyLink}>
|
||||||
|
<Icon name="link-white" class={styles.icon} />
|
||||||
|
{t('Copy link')}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<form class={clsx('pretty-form__item', styles.linkInput)}>
|
||||||
|
<input type="text" name="link" readonly value={props.shareUrl} />
|
||||||
|
<label for="link">{t('Copy link')}</label>
|
||||||
|
|
||||||
|
<Popover content={t('Copy link')}>
|
||||||
|
{(triggerRef: (el) => void) => (
|
||||||
|
<div class={styles.copyButton} onClick={copyLink} ref={triggerRef}>
|
||||||
|
<Icon name="copy" class={styles.icon} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<Show when={isLinkCopied()}>
|
||||||
|
<div class={styles.isCopied}>{t('Link copied to clipboard')}</div>
|
||||||
|
</Show>
|
||||||
|
</form>
|
||||||
|
</Show>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/_shared/ShareLinks/index.ts
Normal file
1
src/components/_shared/ShareLinks/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { ShareLinks } from './ShareLinks'
|
27
src/components/_shared/ShareModal/ShareModal.tsx
Normal file
27
src/components/_shared/ShareModal/ShareModal.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { useLocalize } from '../../../context/localize'
|
||||||
|
import { Modal } from '../../Nav/Modal'
|
||||||
|
import { ShareLinks } from '../ShareLinks'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
modalTitle?: string
|
||||||
|
shareUrl?: string
|
||||||
|
title: string
|
||||||
|
imageUrl: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
export const ShareModal = (props: Props) => {
|
||||||
|
const { t } = useLocalize()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal name="share" variant="medium" allowClose={true}>
|
||||||
|
<h2>{t('Share publication')}</h2>
|
||||||
|
<ShareLinks
|
||||||
|
variant="inModal"
|
||||||
|
title={props.title}
|
||||||
|
shareUrl={props.shareUrl}
|
||||||
|
imageUrl={props.imageUrl}
|
||||||
|
description={props.description}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
1
src/components/_shared/ShareModal/index.ts
Normal file
1
src/components/_shared/ShareModal/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { ShareModal } from './ShareModal'
|
|
@ -25,6 +25,7 @@ export type ModalType =
|
||||||
| 'following'
|
| 'following'
|
||||||
| 'search'
|
| 'search'
|
||||||
| 'inviteCoAuthors'
|
| 'inviteCoAuthors'
|
||||||
|
| 'share'
|
||||||
|
|
||||||
export const MODALS: Record<ModalType, ModalType> = {
|
export const MODALS: Record<ModalType, ModalType> = {
|
||||||
auth: 'auth',
|
auth: 'auth',
|
||||||
|
@ -42,6 +43,7 @@ export const MODALS: Record<ModalType, ModalType> = {
|
||||||
following: 'following',
|
following: 'following',
|
||||||
inviteCoAuthors: 'inviteCoAuthors',
|
inviteCoAuthors: 'inviteCoAuthors',
|
||||||
search: 'search',
|
search: 'search',
|
||||||
|
share: 'share',
|
||||||
}
|
}
|
||||||
|
|
||||||
const [modal, setModal] = createSignal<ModalType>(null)
|
const [modal, setModal] = createSignal<ModalType>(null)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { thumborUrl } from './config'
|
import { thumborUrl } from './config'
|
||||||
|
|
||||||
|
const thumborPrefix = `${thumborUrl}/unsafe/`
|
||||||
|
|
||||||
const getSizeUrlPart = (options: { width?: number; height?: number } = {}) => {
|
const getSizeUrlPart = (options: { width?: number; height?: number } = {}) => {
|
||||||
const widthString = options.width ? options.width.toString() : ''
|
const widthString = options.width ? options.width.toString() : ''
|
||||||
const heightString = options.height ? options.height.toString() : ''
|
const heightString = options.height ? options.height.toString() : ''
|
||||||
|
@ -19,3 +21,27 @@ export const getImageUrl = (src: string, options: { width?: number; height?: num
|
||||||
`${thumborUrl}/unsafe/${sizeUrlPart}${sourceUrl}`.replace('https://', '').replace('//', '/')
|
`${thumborUrl}/unsafe/${sizeUrlPart}${sourceUrl}`.replace('https://', '').replace('//', '/')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getOpenGraphImageUrl = (
|
||||||
|
src: string,
|
||||||
|
options: {
|
||||||
|
topic: string
|
||||||
|
title: string
|
||||||
|
author: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const sizeUrlPart = getSizeUrlPart(options)
|
||||||
|
|
||||||
|
const filtersPart = `filters:discourstext('${encodeURIComponent(options.topic)}','${encodeURIComponent(
|
||||||
|
options.author,
|
||||||
|
)}','${encodeURIComponent(options.title)}')/`
|
||||||
|
|
||||||
|
if (src.startsWith(thumborPrefix)) {
|
||||||
|
const thumborKey = src.replace(thumborPrefix, '')
|
||||||
|
return `${thumborUrl}/unsafe/${sizeUrlPart}${filtersPart}${thumborKey}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${thumborUrl}/unsafe/${sizeUrlPart}${filtersPart}${src}`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user