Merge remote-tracking branch 'hub/main' into feature/sse-connect

This commit is contained in:
Untone 2024-01-08 10:11:40 +03:00
commit 45a5f0c542
27 changed files with 350 additions and 120 deletions

5
public/icons/copy.svg Normal file
View 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

View File

@ -72,6 +72,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": "Close",
"Co-author": "Co-author",
"Collaborate": "Help Edit",
"Collaborators": "Collaborators",
@ -217,6 +218,7 @@
"Last rev.": "Посл. изм.",
"Let's log in": "Let's log in",
"Link copied": "Link copied",
"Link copied to clipboard": "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",
@ -320,6 +322,7 @@
"Send link again": "Send link again",
"Settings": "Settings",
"Share": "Share",
"Share publication": "Share publication",
"Show": "Show",
"Show lyrics": "Show lyrics",
"Show more": "Show more",
@ -356,6 +359,7 @@
"Suggest an idea": "Suggest an idea",
"Support Discours": "Support Discours",
"Support the project": "Support the project",
"Support us": "Support us",
"Terms of use": "Site rules",
"Text checking": "Text checking",
"Thank you": "Thank you",

View File

@ -76,6 +76,7 @@
"Choose a post type": "Выберите тип публикации",
"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": "Выберите кому хотите написать",
"Close": "Закрыть",
"Co-author": "Соавтор",
"Collaborate": "Помочь редактировать",
"Collaborators": "Соавторы",
@ -229,6 +230,7 @@
"Last rev.": "Посл. изм.",
"Let's log in": "Давайте авторизуемся",
"Link copied": "Ссылка скопирована",
"Link copied to clipboard": "Ссылка скопирована в буфер обмена",
"Link sent, check your email": "Ссылка отправлена, проверьте почту",
"List of authors of the open editorial community": "Список авторов сообщества открытой редакции",
"Lists": "Списки",
@ -340,6 +342,7 @@
"Send link again": "Прислать ссылку ещё раз",
"Settings": "Настройки",
"Share": "Поделиться",
"Share publication": "Поделиться публикацией",
"Short opening": "Расскажите вашу историю...",
"Show": "Показать",
"Show lyrics": "Текст песни",
@ -378,6 +381,7 @@
"Suggest an idea": "Предложить идею",
"Support Discours": "Поддержите Дискурс",
"Support the project": "Поддержать проект",
"Support us": "Помочь журналу",
"Terms of use": "Правила сайта",
"Text checking": "Проверка текста",
"Thank you": "Благодарности",

View File

@ -14,12 +14,15 @@ import { MediaItem } from '../../pages/types'
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
import { capitalize } from '../../utils/capitalize'
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 { Icon } from '../_shared/Icon'
import { Image } from '../_shared/Image'
import { InviteCoAuthorsModal } from '../_shared/InviteCoAuthorsModal'
import { Lightbox } from '../_shared/Lightbox'
import { Popover } from '../_shared/Popover'
import { ShareModal } from '../_shared/ShareModal'
import { ImageSwiper } from '../_shared/SolidSwiper'
import { VideoPlayer } from '../_shared/VideoPlayer'
import { AuthorBadge } from '../Author/AuthorBadge'
@ -60,6 +63,8 @@ const imgSrcRegExp = /<img[^>]+src\s*=\s*["']([^"']+)["']/gi
export const FullArticle = (props: Props) => {
const [selectedImage, setSelectedImage] = createSignal('')
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)
const { t, formatDate, lang } = useLocalize()
const {
@ -68,8 +73,6 @@ export const FullArticle = (props: Props) => {
actions: { requireAuthentication },
} = useSession()
const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false)
const formattedDate = createMemo(() => formatDate(new Date(props.article.created_at * 1000)))
const mainTopic = createMemo(() => {
@ -292,12 +295,19 @@ export const FullArticle = (props: Props) => {
}
}
const ogImage = props.article.cover
? getImageUrl(props.article.cover, { width: 1200 })
: getImageUrl('production/image/logo_image.png')
const cover = props.article.cover ?? '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 ogTitle = props.article.title
const keywords = getKeywords(props.article)
const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` })
const getAuthorName = (a: Author) => {
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} />
</div>
<Popover content={t('Comment')}>
<Popover content={t('Comment')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div class={clsx(styles.shoutStatsItem)} ref={triggerRef} onClick={scrollToComments}>
<Icon name="comment" class={styles.icon} />
@ -453,7 +463,7 @@ export const FullArticle = (props: Props) => {
</div>
</div>
<Popover content={t('Add to bookmarks')}>
<Popover content={t('Add to bookmarks')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div
class={clsx(styles.shoutStatsItem, styles.shoutStatsItemBookmarks)}
@ -468,14 +478,16 @@ export const FullArticle = (props: Props) => {
)}
</Popover>
<Popover content={t('Share')}>
<Popover content={t('Share')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div class={styles.shoutStatsItem} ref={triggerRef}>
<SharePopup
title={props.article.title}
description={description}
imageUrl={props.article.cover}
shareUrl={shareUrl}
containerCssClass={stylesHeader.control}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={
<div class={styles.shoutStatsItemInner}>
<Icon name="share-outline" class={styles.icon} />
@ -506,9 +518,9 @@ export const FullArticle = (props: Props) => {
<FeedArticlePopup
isOwner={canEdit()}
containerCssClass={clsx(stylesHeader.control, styles.articlePopupOpener)}
title={props.article.title}
description={description}
imageUrl={props.article.cover}
onShareClick={() => showModal('share')}
onInviteClick={() => showModal('inviteCoAuthors')}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={
<button>
<Icon name="ellipsis" class={clsx(styles.icon)} />
@ -570,6 +582,13 @@ export const FullArticle = (props: Props) => {
<Show when={selectedImage()}>
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
</Show>
<InviteCoAuthorsModal title={t('Invite experts')} />
<ShareModal
title={props.article.title}
description={description}
imageUrl={props.article.cover}
shareUrl={shareUrl}
/>
</>
)
}

View File

@ -1,18 +1,13 @@
import type { PopupProps } from '../_shared/Popup'
import { createSocialShare, TWITTER, VK, FACEBOOK, TELEGRAM } from '@solid-primitives/share'
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 styles from '../_shared/Popup/Popup.module.scss'
import { ShareLinks } from '../_shared/ShareLinks'
type SharePopupProps = {
title: string
shareUrl?: string
shareUrl: string
imageUrl: string
description: string
onVisibilityChange?: (value: boolean) => void
@ -25,63 +20,22 @@ export const getShareUrl = (params: { pathname?: string } = {}) => {
}
export const SharePopup = (props: SharePopupProps) => {
const { t } = useLocalize()
const [isVisible, setIsVisible] = createSignal(false)
const {
actions: { showSnackbar },
} = useSnackbar()
createEffect(() => {
if (props.onVisibilityChange) {
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 (
<Popup {...props} variant="bordered" onVisibilityChange={(value) => setIsVisible(value)}>
<ul class="nodash">
<li>
<button role="button" class={styles.shareControl} onClick={() => share(VK)}>
<Icon name="vk-white" class={styles.icon} />
VK
</button>
</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>
<ShareLinks
variant="inPopup"
title={props.title}
shareUrl={props.shareUrl}
imageUrl={props.imageUrl}
description={props.description}
/>
</Popup>
)
}

View File

@ -107,7 +107,7 @@
height: 0;
margin-bottom: 1.6rem;
overflow: hidden;
padding-bottom: 56.2%; //16:9
padding-bottom: 56.2%; // 16:9
position: relative;
transform-origin: 50% 50%;
transition: transform 1s ease-in-out;

View File

@ -7,11 +7,14 @@ import { createMemo, createSignal, For, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { router, useRouter } from '../../../stores/router'
import { showModal } from '../../../stores/ui'
import { capitalize } from '../../../utils/capitalize'
import { getDescription } from '../../../utils/meta'
import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image'
import { InviteCoAuthorsModal } from '../../_shared/InviteCoAuthorsModal'
import { Popover } from '../../_shared/Popover'
import { ShareModal } from '../../_shared/ShareModal'
import { CoverImage } from '../../Article/CoverImage'
import { getShareUrl, SharePopup } from '../../Article/SharePopup'
import { ShoutRatingControl } from '../../Article/ShoutRatingControl'
@ -310,7 +313,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
<div class={styles.shoutCardDetailsContent}>
<Show when={canEdit()}>
<Popover content={t('Edit')}>
<Popover content={t('Edit')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
<a href={getPagePath(router, 'edit', { shoutId: props.article.id.toString() })}>
@ -325,7 +328,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
</Popover>
</Show>
<Popover content={t('Add to bookmarks')}>
<Popover content={t('Add to bookmarks')} disabled={isActionPopupActive()}>
{(triggerRef: (el) => void) => (
<div class={styles.shoutCardDetailsItem} ref={triggerRef}>
<button>
@ -348,6 +351,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
description={description}
imageUrl={props.article.cover}
shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={
<button>
<Icon name="share-outline" class={clsx(styles.icon, styles.feedControlIcon)} />
@ -366,9 +370,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
<FeedArticlePopup
isOwner={canEdit()}
containerCssClass={stylesHeader.control}
title={title}
description={description}
imageUrl={props.article.cover}
onShareClick={() => showModal('share')}
onInviteClick={() => showModal('inviteCoAuthors')}
onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)}
trigger={
<button>
<Icon name="ellipsis" class={clsx(styles.icon, styles.feedControlIcon)} />
@ -384,6 +388,13 @@ export const ArticleCard = (props: ArticleCardProps) => {
</section>
</Show>
</div>
<InviteCoAuthorsModal title={t('Invite experts')} />
<ShareModal
title={title}
description={description}
imageUrl={props.article.cover}
shareUrl={getShareUrl({ pathname: `/${props.article.slug}` })}
/>
</section>
)
}

View File

@ -5,6 +5,7 @@
padding: 0 !important;
text-align: left;
overflow: hidden;
margin-top: -14px;
@include media-breakpoint-down(md) {
left: auto !important;
@ -30,6 +31,10 @@
&.soon {
color: var(--black-300);
display: flex;
gap: 0.6rem;
width: 100%;
justify-content: space-between;
}
&:hover {
@ -41,6 +46,7 @@
li:first-child .action {
padding-top: 16px;
}
li:last-child .action {
padding-bottom: 16px;
}

View File

@ -1,29 +1,31 @@
import type { PopupProps } from '../../_shared/Popup'
import { clsx } from 'clsx'
import { createEffect, createSignal, Show } from 'solid-js'
import { Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { showModal } from '../../../stores/ui'
import { InviteCoAuthorsModal } from '../../_shared/InviteCoAuthorsModal'
import { Popup } from '../../_shared/Popup'
import { SoonChip } from '../../_shared/SoonChip'
import styles from './FeedArticlePopup.module.scss'
type FeedArticlePopupProps = {
title: string
imageUrl: string
isOwner: boolean
description: string
onInviteClick: () => void
onShareClick: () => void
} & Omit<PopupProps, 'children'>
export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
const { t } = useLocalize()
return (
<>
<Popup {...props} variant="tiny" popupCssClass={styles.feedArticlePopup}>
<Popup {...props} horizontalAnchor={'right'} variant="tiny" popupCssClass={styles.feedArticlePopup}>
<ul class={clsx('nodash', styles.actionList)}>
<li>
<button class={styles.action} role="button" onClick={props.onShareClick}>
{t('Share')}
</button>
</li>
<Show when={!props.isOwner}>
<li>
<button
@ -38,13 +40,7 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
</li>
</Show>
<li>
<button
class={styles.action}
role="button"
onClick={() => {
showModal('inviteCoAuthors')
}}
>
<button class={styles.action} role="button" onClick={props.onInviteClick}>
{t('Invite experts')}
</button>
</li>
@ -86,7 +82,6 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
{/*</li>*/}
</ul>
</Popup>
<InviteCoAuthorsModal title={t('Invite experts')} />
</>
)
}

View File

@ -2,7 +2,6 @@ import { clsx } from 'clsx'
import { createEffect, createSignal, on, Show } from 'solid-js'
import { useLocalize } from '../../../../context/localize'
// import { resetSortedArticles } from '../../../../stores/zine/articles'
import { Icon } from '../../../_shared/Icon'
import styles from './PasswordField.module.scss'

View File

@ -1,7 +1,7 @@
.backdrop {
align-items: center;
background: rgb(20 20 20 / 70%);
display: flex;
background: rgb(20 20 20 / 7%);
justify-content: center;
height: 100%;
left: 0;
@ -17,6 +17,7 @@
background: var(--background-color);
max-width: 1000px;
position: relative;
z-index: 1;
&:not([class*='col-']) {
width: 100%;
@ -124,11 +125,13 @@
.maxHeight {
height: 100%;
}
.container {
padding: 0;
height: 100%;
min-height: 100%;
}
.modalInner {
padding: 1rem 1rem 0;
height: 100%;

View File

@ -1,6 +1,7 @@
.TableOfContentsFixedWrapper {
min-height: 100%;
position: relative;
z-index: 1;
top: 0;
@include media-breakpoint-down(xl) {

View File

@ -7,9 +7,8 @@
.basicInfo {
display: flex;
flex-direction: row;
flex-flow: row nowrap;
align-items: flex-start;
flex-wrap: nowrap;
flex: 1;
gap: 1rem;
}

View File

@ -4,10 +4,6 @@
padding: 0 0 4rem;
min-height: 100vh;
.navigation {
padding: 0;
}
.showMore {
display: flex;
width: 100%;

View File

@ -147,7 +147,7 @@ export const Expo = (props: Props) => {
<div class={styles.Expo}>
<Show when={sortedArticles().length > 0} fallback={<Loading />}>
<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 })}>
<ConditionalWrapper
condition={Boolean(props.layout)}

View File

@ -195,6 +195,7 @@
justify-content: space-between;
align-items: center;
margin-bottom: 4rem;
@include media-breakpoint-down(sm) {
flex-direction: column-reverse;
align-items: flex-start;
@ -205,6 +206,7 @@
margin-top: 0;
margin-bottom: 0;
min-width: 300px;
overflow: hidden;
& > li {
margin-bottom: 0;
@ -213,8 +215,7 @@
.dropdowns {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-flow: row nowrap;
gap: 1rem;
justify-content: center;
}

View File

@ -269,6 +269,7 @@ export const FeedView = (props: Props) => {
<div class={styles.dropdowns}>
<Show when={searchParams().by && searchParams().by !== 'publish_date'}>
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
options={periods}
currentOption={currentPeriod()}
triggerCssClass={styles.periodSwitcher}
@ -276,6 +277,7 @@ export const FeedView = (props: Props) => {
/>
</Show>
<DropDown
popupProps={{ horizontalAnchor: 'right' }}
options={visibilities}
currentOption={currentVisibility()}
triggerCssClass={styles.periodSwitcher}

View File

@ -1,6 +1,7 @@
.trigger {
white-space: nowrap;
}
.chevron {
vertical-align: top;
@ -8,3 +9,7 @@
transform: rotate(180deg);
}
}
.active {
font-weight: 600;
}

View File

@ -14,7 +14,7 @@ export type Option = {
type Props<TOption> = {
class?: string
popupProps?: PopupProps
popupProps?: Partial<PopupProps>
options: TOption[]
currentOption: TOption
triggerCssClass?: string
@ -56,9 +56,12 @@ export const DropDown = <TOption extends Option = Option>(props: Props<TOption>)
onVisibilityChange={(isVisible) => setIsPopupVisible(isVisible)}
{...props.popupProps}
>
<For each={props.options.filter((p) => p.value !== props.currentOption.value)}>
<For each={props.options}>
{(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}
</div>
)}

View File

@ -15,7 +15,7 @@
position: absolute;
text-align: left;
top: calc(100% + 8px);
z-index: 100;
z-index: 101;
ul {
margin-bottom: 0;
@ -103,23 +103,6 @@
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

View 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);
}
}

View 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>
)
}

View File

@ -0,0 +1 @@
export { ShareLinks } from './ShareLinks'

View 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>
)
}

View File

@ -0,0 +1 @@
export { ShareModal } from './ShareModal'

View File

@ -25,6 +25,7 @@ export type ModalType =
| 'following'
| 'search'
| 'inviteCoAuthors'
| 'share'
export const MODALS: Record<ModalType, ModalType> = {
auth: 'auth',
@ -42,6 +43,7 @@ export const MODALS: Record<ModalType, ModalType> = {
following: 'following',
inviteCoAuthors: 'inviteCoAuthors',
search: 'search',
share: 'share',
}
const [modal, setModal] = createSignal<ModalType>(null)

View File

@ -1,5 +1,7 @@
import { thumborUrl } from './config'
const thumborPrefix = `${thumborUrl}/unsafe/`
const getSizeUrlPart = (options: { width?: number; height?: number } = {}) => {
const widthString = options.width ? options.width.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('//', '/')
)
}
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}`
}