From 0e6bb81b6a8d0871d7fcc6a99f26663b85b7949d Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Fri, 5 Jan 2024 22:31:28 +0300 Subject: [PATCH 1/3] Article action menu (#352) * Article action menu --- public/icons/copy.svg | 5 + public/locales/en/translation.json | 3 + public/locales/ru/translation.json | 3 + src/components/Article/FullArticle.tsx | 30 +++-- src/components/Article/SharePopup.tsx | 64 ++--------- .../Feed/ArticleCard/ArticleCard.tsx | 21 +++- .../FeedArticlePopup.module.scss | 5 + .../FeedArticlePopup/FeedArticlePopup.tsx | 24 ++-- src/components/Nav/Modal/Modal.module.scss | 3 +- src/components/Nav/Modal/Modal.tsx | 2 + .../TableOfContents.module.scss | 1 + .../_shared/Popup/Popup.module.scss | 19 +--- .../_shared/ShareLinks/ShareLinks.module.scss | 82 ++++++++++++++ .../_shared/ShareLinks/ShareLinks.tsx | 104 ++++++++++++++++++ src/components/_shared/ShareLinks/index.ts | 1 + .../_shared/ShareModal/ShareModal.tsx | 30 +++++ src/components/_shared/ShareModal/index.ts | 1 + src/stores/ui.ts | 2 + 18 files changed, 299 insertions(+), 101 deletions(-) create mode 100644 public/icons/copy.svg create mode 100644 src/components/_shared/ShareLinks/ShareLinks.module.scss create mode 100644 src/components/_shared/ShareLinks/ShareLinks.tsx create mode 100644 src/components/_shared/ShareLinks/index.ts create mode 100644 src/components/_shared/ShareModal/ShareModal.tsx create mode 100644 src/components/_shared/ShareModal/index.ts diff --git a/public/icons/copy.svg b/public/icons/copy.svg new file mode 100644 index 00000000..0665732f --- /dev/null +++ b/public/icons/copy.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index a9824169..345452b4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -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", @@ -326,6 +328,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", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 3bddf1fe..fd2b671a 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -75,6 +75,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": "Соавторы", @@ -228,6 +229,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": "Списки", @@ -344,6 +346,7 @@ "Send link again": "Прислать ссылку ещё раз", "Settings": "Настройки", "Share": "Поделиться", + "Share publication": "Поделиться публикацией", "Short opening": "Расскажите вашу историю...", "Show": "Показать", "Show lyrics": "Текст песни", diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 79a6ea20..dd072d35 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -12,12 +12,15 @@ import { useReactions } from '../../context/reactions' import { useSession } from '../../context/session' import { MediaItem } from '../../pages/types' import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router' +import { showModal } from '../../stores/ui' import { getImageUrl } 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' @@ -58,6 +61,8 @@ const imgSrcRegExp = /]+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 } = useLocalize() const { @@ -66,8 +71,6 @@ export const FullArticle = (props: Props) => { actions: { requireAuthentication }, } = useSession() - const [isReactionsLoaded, setIsReactionsLoaded] = createSignal(false) - const formattedDate = createMemo(() => formatDate(new Date(props.article.createdAt))) const mainTopic = createMemo( @@ -289,7 +292,7 @@ export const FullArticle = (props: Props) => { const description = getDescription(props.article.description || body()) const ogTitle = props.article.title const keywords = getKeywords(props.article) - + const shareUrl = getShareUrl({ pathname: `/${props.article.slug}` }) return ( <> @@ -412,7 +415,7 @@ export const FullArticle = (props: Props) => { - + {(triggerRef: (el) => void) => (
@@ -439,7 +442,7 @@ export const FullArticle = (props: Props) => {
- + {(triggerRef: (el) => void) => (
{ )} - + {(triggerRef: (el) => void) => (
setIsActionPopupActive(isVisible)} trigger={
@@ -492,9 +497,9 @@ export const FullArticle = (props: Props) => { showModal('share')} + onInviteClick={() => showModal('inviteCoAuthors')} + onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)} trigger={ - -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • - + ) } diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 32bf2ee5..5863a865 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -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' @@ -307,7 +310,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
    - + {(triggerRef: (el) => void) => (
    @@ -322,7 +325,7 @@ export const ArticleCard = (props: ArticleCardProps) => { - + {(triggerRef: (el) => void) => (
    + + ) } diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss index c983fe14..faf9a96c 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.module.scss @@ -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 { diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx index 3e237307..74b09880 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx @@ -1,29 +1,34 @@ 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 { ShareModal } from '../../_shared/ShareModal' 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 export const FeedArticlePopup = (props: FeedArticlePopupProps) => { const { t } = useLocalize() return ( <> - +
      +
    • + +
    • -
    • @@ -86,7 +85,6 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => { {/**/}
    - ) } diff --git a/src/components/Nav/Modal/Modal.module.scss b/src/components/Nav/Modal/Modal.module.scss index fa98a278..2beb11fa 100644 --- a/src/components/Nav/Modal/Modal.module.scss +++ b/src/components/Nav/Modal/Modal.module.scss @@ -1,7 +1,7 @@ .backdrop { align-items: center; - background: rgb(20 20 20 / 70%); display: flex; + background: rgba(20, 20, 20, 0.07); 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%; diff --git a/src/components/Nav/Modal/Modal.tsx b/src/components/Nav/Modal/Modal.tsx index 24f9c887..9069a230 100644 --- a/src/components/Nav/Modal/Modal.tsx +++ b/src/components/Nav/Modal/Modal.tsx @@ -4,6 +4,7 @@ import { redirectPage } from '@nanostores/router' import { clsx } from 'clsx' import { createEffect, createMemo, createSignal, Show } from 'solid-js' +import { useLocalize } from '../../../context/localize' import { useMediaQuery } from '../../../context/mediaQuery' import { router } from '../../../stores/router' import { hideModal, useModalStore } from '../../../stores/ui' @@ -24,6 +25,7 @@ interface Props { } export const Modal = (props: Props) => { + const { t } = useLocalize() const { modal } = useModalStore() const [visible, setVisible] = createSignal(false) const allowClose = createMemo(() => props.allowClose !== false) diff --git a/src/components/TableOfContents/TableOfContents.module.scss b/src/components/TableOfContents/TableOfContents.module.scss index 84785a04..2e5fe4ac 100644 --- a/src/components/TableOfContents/TableOfContents.module.scss +++ b/src/components/TableOfContents/TableOfContents.module.scss @@ -1,6 +1,7 @@ .TableOfContentsFixedWrapper { min-height: 100%; position: relative; + z-index: 1; top: 0; @include media-breakpoint-down(xl) { diff --git a/src/components/_shared/Popup/Popup.module.scss b/src/components/_shared/Popup/Popup.module.scss index f69995be..1d0c7d80 100644 --- a/src/components/_shared/Popup/Popup.module.scss +++ b/src/components/_shared/Popup/Popup.module.scss @@ -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 diff --git a/src/components/_shared/ShareLinks/ShareLinks.module.scss b/src/components/_shared/ShareLinks/ShareLinks.module.scss new file mode 100644 index 00000000..d65ee4aa --- /dev/null +++ b/src/components/_shared/ShareLinks/ShareLinks.module.scss @@ -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); + } +} diff --git a/src/components/_shared/ShareLinks/ShareLinks.tsx b/src/components/_shared/ShareLinks/ShareLinks.tsx new file mode 100644 index 00000000..89d5fefa --- /dev/null +++ b/src/components/_shared/ShareLinks/ShareLinks.tsx @@ -0,0 +1,104 @@ +import { getPagePath } from '@nanostores/router' +import { createSocialShare, FACEBOOK, TELEGRAM, TWITTER, VK } from '@solid-primitives/share' +import { Input } from '@thisbeyond/solid-select' +import { clsx } from 'clsx' +import { createSignal, Show } from 'solid-js' + +import { useLocalize } from '../../../context/localize' +import { useSnackbar } from '../../../context/snackbar' +import { router } from '../../../stores/router' +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 ( +
    +
      +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + +
    • +
    • + + + {t('Copy link')} + + } + > +
      + + + + + {(triggerRef: (el) => void) => ( +
      + +
      + )} +
      + + +
      {t('Link copied to clipboard')}
      +
      +
      +
      +
    • +
    +
    + ) +} diff --git a/src/components/_shared/ShareLinks/index.ts b/src/components/_shared/ShareLinks/index.ts new file mode 100644 index 00000000..0d99048a --- /dev/null +++ b/src/components/_shared/ShareLinks/index.ts @@ -0,0 +1 @@ +export { ShareLinks } from './ShareLinks' diff --git a/src/components/_shared/ShareModal/ShareModal.tsx b/src/components/_shared/ShareModal/ShareModal.tsx new file mode 100644 index 00000000..12734c59 --- /dev/null +++ b/src/components/_shared/ShareModal/ShareModal.tsx @@ -0,0 +1,30 @@ +import { useLocalize } from '../../../context/localize' +import { Modal } from '../../Nav/Modal' +import { Button } from '../Button' +import { ShareLinks } from '../ShareLinks' + +import styles from '../ShareLinks/ShareLinks.module.scss' + +type Props = { + modalTitle?: string + shareUrl?: string + title: string + imageUrl: string + description: string +} +export const ShareModal = (props: Props) => { + const { t } = useLocalize() + + return ( + +

    {t('Share publication')}

    + +
    + ) +} diff --git a/src/components/_shared/ShareModal/index.ts b/src/components/_shared/ShareModal/index.ts new file mode 100644 index 00000000..6a586378 --- /dev/null +++ b/src/components/_shared/ShareModal/index.ts @@ -0,0 +1 @@ +export { ShareModal } from './ShareModal' diff --git a/src/stores/ui.ts b/src/stores/ui.ts index 775dc46d..7fcb4fe5 100644 --- a/src/stores/ui.ts +++ b/src/stores/ui.ts @@ -24,6 +24,7 @@ export type ModalType = | 'followers' | 'following' | 'inviteCoAuthors' + | 'share' export const MODALS: Record = { auth: 'auth', @@ -40,6 +41,7 @@ export const MODALS: Record = { followers: 'followers', following: 'following', inviteCoAuthors: 'inviteCoAuthors', + share: 'share', } const [modal, setModal] = createSignal(null) From 56a66eca3833aa26a6a6417ef2f2e32db7830837 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Sat, 6 Jan 2024 07:06:58 +0300 Subject: [PATCH 2/3] Fix/markup (#354) * fix markup --- public/locales/en/translation.json | 1 + public/locales/ru/translation.json | 1 + src/components/Views/Expo/Expo.module.scss | 4 ---- src/components/Views/Feed/Feed.module.scss | 1 + src/components/Views/Feed/Feed.tsx | 6 ++++++ src/components/_shared/DropDown/DropDown.module.scss | 4 ++++ src/components/_shared/DropDown/DropDown.tsx | 9 ++++++--- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 345452b4..fc967d81 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -362,6 +362,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", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index fd2b671a..5694bc44 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -382,6 +382,7 @@ "Suggest an idea": "Предложить идею", "Support Discours": "Поддержите Дискурс", "Support the project": "Поддержать проект", + "Support us": "Помочь журналу", "Terms of use": "Правила сайта", "Text checking": "Проверка текста", "Thank you": "Благодарности", diff --git a/src/components/Views/Expo/Expo.module.scss b/src/components/Views/Expo/Expo.module.scss index bb32f0b4..190b4243 100644 --- a/src/components/Views/Expo/Expo.module.scss +++ b/src/components/Views/Expo/Expo.module.scss @@ -4,10 +4,6 @@ padding: 0 0 4rem; min-height: 100vh; - .navigation { - padding: 0; - } - .showMore { display: flex; width: 100%; diff --git a/src/components/Views/Feed/Feed.module.scss b/src/components/Views/Feed/Feed.module.scss index 3099733d..707b6431 100644 --- a/src/components/Views/Feed/Feed.module.scss +++ b/src/components/Views/Feed/Feed.module.scss @@ -205,6 +205,7 @@ margin-top: 0; margin-bottom: 0; min-width: 300px; + overflow: hidden; & > li { margin-bottom: 0; diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index 649792b6..907eab5d 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -203,6 +203,10 @@ export const Feed = (props: Props) => { ) const ogTitle = t('Feed') + const myPopupProps = { + horizontalAnchor: 'right', + } + return (
    @@ -256,6 +260,7 @@ export const Feed = (props: Props) => {
    { /> = { class?: string - popupProps?: PopupProps + popupProps?: Partial options: TOption[] currentOption: TOption triggerCssClass?: string @@ -56,9 +56,12 @@ export const DropDown = (props: Props) onVisibilityChange={(isVisible) => setIsPopupVisible(isVisible)} {...props.popupProps} > - p.value !== props.currentOption.value)}> + {(option) => ( -