From ef7a24d571475eb40bb7cadb4efe2e25e5fadbc3 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:22:43 +0300 Subject: [PATCH 1/4] fix article card actions (#364) * fix invite modal * change FeedArticlePopup hide logic * resolve conversation --- .../Feed/ArticleCard/ArticleCard.tsx | 4 +-- .../FeedArticlePopup/FeedArticlePopup.tsx | 34 +++++++++++++++---- src/components/Views/Feed/Feed.tsx | 3 ++ src/components/_shared/Popup/Popup.tsx | 13 +++++-- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index cd1ae0ed..ff275635 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -52,6 +52,7 @@ export type ArticleCardProps = { desktopCoverSize?: 'XS' | 'S' | 'M' | 'L' article: Shout onShare?: (article: Shout) => void + onInvite?: () => void } const desktopCoverImageWidths: Record = { @@ -368,7 +369,7 @@ export const ArticleCard = (props: ArticleCardProps) => { isOwner={canEdit()} containerCssClass={stylesHeader.control} onShareClick={() => props.onShare(props.article)} - onInviteClick={() => showModal('inviteCoAuthors')} + onInviteClick={props.onInvite} onVisibilityChange={(isVisible) => setIsActionPopupActive(isVisible)} trigger={ @@ -33,6 +47,7 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => { role="button" onClick={() => { alert('Help to edit') + setHidden(true) }} > {t('Help to edit')} @@ -40,7 +55,14 @@ export const FeedArticlePopup = (props: FeedArticlePopupProps) => {
  • -
  • diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index 6914a6c7..3ac0a0ef 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -17,6 +17,7 @@ import { getImageUrl } from '../../../utils/getImageUrl' import { getServerDate } from '../../../utils/getServerDate' import { DropDown } from '../../_shared/DropDown' import { Icon } from '../../_shared/Icon' +import { InviteCoAuthorsModal } from '../../_shared/InviteCoAuthorsModal' import { Loading } from '../../_shared/Loading' import { ShareModal } from '../../_shared/ShareModal' import { CommentDate } from '../../Article/CommentDate' @@ -290,6 +291,7 @@ export const Feed = (props: Props) => { {(article) => ( handleShare(shared)} + onInvite={() => showModal('inviteCoAuthors')} article={article} settings={{ isFeedMode: true }} desktopCoverSize="M" @@ -416,6 +418,7 @@ export const Feed = (props: Props) => { shareUrl={getShareUrl({ pathname: `/${shareData().slug}` })} /> + ) } diff --git a/src/components/_shared/Popup/Popup.tsx b/src/components/_shared/Popup/Popup.tsx index c0b04aec..4cd31afd 100644 --- a/src/components/_shared/Popup/Popup.tsx +++ b/src/components/_shared/Popup/Popup.tsx @@ -15,6 +15,7 @@ export type PopupProps = { onVisibilityChange?: (isVisible: boolean) => void horizontalAnchor?: HorizontalAnchor variant?: 'bordered' | 'tiny' + closePopup?: (isVisible: boolean) => void } export const Popup = (props: PopupProps) => { @@ -29,13 +30,19 @@ export const Popup = (props: PopupProps) => { const containerRef: { current: HTMLElement } = { current: null } + const closePopup = () => { + setIsVisible(false) + if (props.closePopup) { + props.closePopup + } + } + useOutsideClickHandler({ containerRef, predicate: () => isVisible(), - handler: () => { - setIsVisible(false) - }, + handler: () => closePopup(), }) + const toggle = () => setIsVisible((oldVisible) => !oldVisible) return ( (containerRef.current = el)}> From 9e6c9d9082d93dec1a67b47aa89c07dbd78efcc7 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Mon, 15 Jan 2024 18:05:38 +0300 Subject: [PATCH 2/4] Update Swiper Lib (#363) * Update Swiper Lib --- package-lock.json | 17 +-- package.json | 2 +- .../_shared/SolidSwiper/ArticleCardSwiper.tsx | 3 +- .../_shared/SolidSwiper/EditorSwiper.tsx | 3 +- .../_shared/SolidSwiper/ImageSwiper.tsx | 104 +++++++++--------- .../_shared/SolidSwiper/Swiper.module.scss | 69 ++++-------- .../_shared/SolidSwiper/swiper.d.ts | 6 + 7 files changed, 84 insertions(+), 120 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7f88e173..f25bccad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,7 +115,7 @@ "stylelint-config-standard-scss": "11.1.0", "stylelint-order": "6.0.3", "stylelint-scss": "5.3.1", - "swiper": "9.4.1", + "swiper": "11.0.5", "throttle-debounce": "5.0.0", "typescript": "5.2.2", "typograf": "7.1.0", @@ -17436,12 +17436,6 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true }, - "node_modules/ssr-window": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz", - "integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==", - "dev": true - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -17934,9 +17928,9 @@ } }, "node_modules/swiper": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/swiper/-/swiper-9.4.1.tgz", - "integrity": "sha512-1nT2T8EzUpZ0FagEqaN/YAhRj33F2x/lN6cyB0/xoYJDMf8KwTFT3hMOeoB8Tg4o3+P/CKqskP+WX0Df046fqA==", + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-11.0.5.tgz", + "integrity": "sha512-rhCwupqSyRnWrtNzWzemnBLMoyYuoDgGgspAm/8iBD3jCvAWycPLH4Z3TB0O5520DHLzMx94yUMH/B9Efpa48w==", "dev": true, "funding": [ { @@ -17948,9 +17942,6 @@ "url": "http://opencollective.com/swiper" } ], - "dependencies": { - "ssr-window": "^4.0.2" - }, "engines": { "node": ">= 4.7.0" } diff --git a/package.json b/package.json index 52328787..1c9101ed 100644 --- a/package.json +++ b/package.json @@ -136,7 +136,7 @@ "stylelint-config-standard-scss": "11.1.0", "stylelint-order": "6.0.3", "stylelint-scss": "5.3.1", - "swiper": "9.4.1", + "swiper": "11.0.5", "throttle-debounce": "5.0.0", "typescript": "5.2.2", "typograf": "7.1.0", diff --git a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx index df4d44be..60adf711 100644 --- a/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ArticleCardSwiper.tsx @@ -1,6 +1,7 @@ import { clsx } from 'clsx' import { For, onMount, Show } from 'solid-js' -import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' +import SwiperCore from 'swiper' +import { Manipulation, Navigation, Pagination } from 'swiper/modules' import { Shout } from '../../../graphql/types.gen' import { ArticleCard } from '../../Feed/ArticleCard' diff --git a/src/components/_shared/SolidSwiper/EditorSwiper.tsx b/src/components/_shared/SolidSwiper/EditorSwiper.tsx index dc9f06e6..6c5c7670 100644 --- a/src/components/_shared/SolidSwiper/EditorSwiper.tsx +++ b/src/components/_shared/SolidSwiper/EditorSwiper.tsx @@ -1,7 +1,8 @@ import { createFileUploader } from '@solid-primitives/upload' import { clsx } from 'clsx' import { createEffect, createSignal, For, Show, on, onMount, lazy } from 'solid-js' -import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' +import SwiperCore from 'swiper' +import { Manipulation, Navigation, Pagination } from 'swiper/modules' import { useLocalize } from '../../../context/localize' import { useSnackbar } from '../../../context/snackbar' diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 5364e47b..2aa782ff 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -1,6 +1,7 @@ import { clsx } from 'clsx' import { createEffect, createSignal, For, Show, on, onMount, onCleanup } from 'solid-js' -import SwiperCore, { Manipulation, Navigation, Pagination } from 'swiper' +import SwiperCore from 'swiper' +import { Manipulation, Navigation, Pagination } from 'swiper/modules' import { throttle } from 'throttle-debounce' import { MediaItem } from '../../../pages/types' @@ -27,7 +28,6 @@ export const ImageSwiper = (props: Props) => { const mainSwipeRef: { current: SwiperRef } = { current: null } const thumbSwipeRef: { current: SwiperRef } = { current: null } const swiperMainContainer: { current: HTMLDivElement } = { current: null } - const [slideIndex, setSlideIndex] = createSignal(0) const [isMobileView, setIsMobileView] = createSignal(false) const [selectedImage, setSelectedImage] = createSignal('') @@ -52,18 +52,13 @@ export const ImageSwiper = (props: Props) => { const { register } = await import('swiper/element/bundle') register() SwiperCore.use([Pagination, Navigation, Manipulation]) + mainSwipeRef.current.swiper.on('slideChange', handleSlideChange) }) onMount(() => { const updateDirection = () => { const width = window.innerWidth - const direction = width > MIN_WIDTH ? 'vertical' : 'horizontal' - if (direction === 'horizontal') { - setIsMobileView(true) - } else { - setIsMobileView(false) - } - thumbSwipeRef.current?.swiper?.changeDirection(direction) + setIsMobileView(width < MIN_WIDTH) } updateDirection() @@ -96,13 +91,58 @@ export const ImageSwiper = (props: Props) => {
    (swiperMainContainer.current = el)}> 0}> +
    +
    + (thumbSwipeRef.current = el)} + slides-per-view={'auto'} + space-between={isMobileView() ? 20 : 10} + auto-scroll-offset={1} + watch-overflow={true} + watch-slides-visibility={true} + direction={'horizontal'} + > + + {(slide, index) => ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + +
    + + )} + + +
    thumbSwipeRef.current.swiper.slidePrev()} + > + +
    +
    thumbSwipeRef.current.swiper.slideTo(10)} + > + +
    +
    +
    (mainSwipeRef.current = el)} slides-per-view={1} thumbs-swiper={'.thumbSwiper'} observer={true} - onSlideChange={handleSlideChange} + // slide-change={handleSlideChange} space-between={isMobileView() ? 20 : 10} > @@ -137,50 +177,6 @@ export const ImageSwiper = (props: Props) => { {slideIndex() + 1} / {props.images.length}
    -
    -
    - (thumbSwipeRef.current = el)} - slides-per-view={'auto'} - space-between={isMobileView() ? 20 : 10} - auto-scroll-offset={1} - watch-overflow={true} - watch-slides-visibility={true} - > - - {(slide, index) => ( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - -
    - - )} - - -
    thumbSwipeRef.current.swiper.slidePrev()} - > - -
    -
    thumbSwipeRef.current.swiper.slideNext()} - > - -
    -
    -
    diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index f4dbccf1..04c1ff99 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -29,6 +29,7 @@ background: var(--background-color-invert); color: var(--default-color-invert); display: flex; + flex-direction: column; align-items: center; justify-content: center; @@ -40,47 +41,37 @@ .container { margin: auto; position: relative; - padding: 24px 0; display: flex; + flex-direction: column; justify-content: center; gap: 20px; width: 100%; .thumbsHolder { - min-width: 110px; width: auto; } .thumbs { - padding: 52px 0; - width: 110px; overflow: hidden; - height: calc(var(--slide-height) + 40px); box-sizing: border-box; margin: 0; position: relative; .thumbsNav { height: 52px; - padding: 14px 0; - display: flex; - align-items: center; - justify-content: center; - width: 110px; - left: 0; - right: 0; - - .icon { - transform: rotate(90deg); - } + overflow: hidden; &.prev { - top: 0; + top: 50%; + left: 0; + transform: translateY(-50%); } &.next { - top: auto; - bottom: 0; + top: 50%; + right: 0; + left: unset; + transform: translateY(-50%); } } } @@ -88,43 +79,12 @@ &.mobileView { .container { - flex-direction: column-reverse; padding: 0; - - .thumbsHolder { - min-width: unset; - } - .thumbs { - width: 100%; - height: 80px; - padding: 0; - & swiper-slide { // bind to html element width: unset !important; } - - .thumbsNav { - height: 100%; - padding: 0; - width: 40px; - - .icon { - transform: none; - } - - &.prev { - top: 0; - left: 0; - } - - &.next { - top: 0; - right: 0; - left: unset; - } - } } } } @@ -260,6 +220,10 @@ margin-top: 24px; + * { + color: var(--default-color-invert) !important; //Force fix migration errors with inline styles + } + @include media-breakpoint-up(md) { // margin-left: calc((100% + 130px) * 0.15); margin-left: calc(15% + 24px); @@ -398,4 +362,9 @@ display: flex !important; } } + + .swiper-button-prev, + .swiper-rtl .swiper-button-next { + top: 200px !important; + } } diff --git a/src/components/_shared/SolidSwiper/swiper.d.ts b/src/components/_shared/SolidSwiper/swiper.d.ts index d94ccd21..e93993ce 100644 --- a/src/components/_shared/SolidSwiper/swiper.d.ts +++ b/src/components/_shared/SolidSwiper/swiper.d.ts @@ -37,10 +37,16 @@ declare module 'solid-js' { onSlideChange?: () => void onBeforeSlideChangeStart?: () => void class?: string + observer?: boolean + loop?: boolean + speed?: number + slidesPerGroupAuto?: boolean + navigation?: boolean breakpoints?: { [width: number]: SwiperOptions [ratio: string]: SwiperOptions } + direction?: 'horizontal' | 'vertical' autoplay?: AutoplayOptions | boolean } // eslint-disable-next-line @typescript-eslint/no-empty-interface From ec5e55b10b1e262a73363112080fd1dd51c56022 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Mon, 15 Jan 2024 21:41:45 +0300 Subject: [PATCH 3/4] Fix/minor fixies (#365) * Swiper thumbs update --- .../Feed/FeedArticlePopup/FeedArticlePopup.tsx | 13 +++++++------ src/components/_shared/Popup/Popup.tsx | 16 ++++++++-------- .../_shared/SolidSwiper/ImageSwiper.tsx | 5 +++-- .../_shared/SolidSwiper/Swiper.module.scss | 11 +++++++++-- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx index ef908535..6fb66509 100644 --- a/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx +++ b/src/components/Feed/FeedArticlePopup/FeedArticlePopup.tsx @@ -1,7 +1,7 @@ import type { PopupProps } from '../../_shared/Popup' import { clsx } from 'clsx' -import { createSignal, Show } from 'solid-js' +import { createEffect, createSignal, onMount, Show } from 'solid-js' import { useLocalize } from '../../../context/localize' import { Popup } from '../../_shared/Popup' @@ -17,12 +17,13 @@ type Props = { export const FeedArticlePopup = (props: Props) => { const { t } = useLocalize() - const [isHidden, setHidden] = createSignal(false) + const [hidePopup, setHidePopup] = createSignal(false) return ( <> isHidden()} + //TODO: fix hide logic + closePopup={hidePopup()} horizontalAnchor={'right'} variant="tiny" popupCssClass={styles.feedArticlePopup} @@ -34,7 +35,7 @@ export const FeedArticlePopup = (props: Props) => { role="button" onClick={() => { props.onShareClick() - setHidden(true) + setHidePopup(true) }} > {t('Share')} @@ -47,7 +48,7 @@ export const FeedArticlePopup = (props: Props) => { role="button" onClick={() => { alert('Help to edit') - setHidden(true) + setHidePopup(true) }} > {t('Help to edit')} @@ -60,7 +61,7 @@ export const FeedArticlePopup = (props: Props) => { role="button" onClick={() => { props.onInviteClick() - setHidden(true) + setHidePopup(false) }} > {t('Invite experts')} diff --git a/src/components/_shared/Popup/Popup.tsx b/src/components/_shared/Popup/Popup.tsx index 4cd31afd..d2c97a2b 100644 --- a/src/components/_shared/Popup/Popup.tsx +++ b/src/components/_shared/Popup/Popup.tsx @@ -15,7 +15,7 @@ export type PopupProps = { onVisibilityChange?: (isVisible: boolean) => void horizontalAnchor?: HorizontalAnchor variant?: 'bordered' | 'tiny' - closePopup?: (isVisible: boolean) => void + closePopup?: boolean } export const Popup = (props: PopupProps) => { @@ -29,13 +29,7 @@ export const Popup = (props: PopupProps) => { }) const containerRef: { current: HTMLElement } = { current: null } - - const closePopup = () => { - setIsVisible(false) - if (props.closePopup) { - props.closePopup - } - } + const closePopup = () => setIsVisible(false) useOutsideClickHandler({ containerRef, @@ -43,6 +37,12 @@ export const Popup = (props: PopupProps) => { handler: () => closePopup(), }) + createEffect(() => { + if (props.closePopup) { + closePopup() + } + }) + const toggle = () => setIsVisible((oldVisible) => !oldVisible) return ( (containerRef.current = el)}> diff --git a/src/components/_shared/SolidSwiper/ImageSwiper.tsx b/src/components/_shared/SolidSwiper/ImageSwiper.tsx index 2aa782ff..e9ebbda3 100644 --- a/src/components/_shared/SolidSwiper/ImageSwiper.tsx +++ b/src/components/_shared/SolidSwiper/ImageSwiper.tsx @@ -52,7 +52,7 @@ export const ImageSwiper = (props: Props) => { const { register } = await import('swiper/element/bundle') register() SwiperCore.use([Pagination, Navigation, Manipulation]) - mainSwipeRef.current.swiper.on('slideChange', handleSlideChange) + mainSwipeRef.current?.swiper?.on('slideChange', handleSlideChange) }) onMount(() => { @@ -102,6 +102,7 @@ export const ImageSwiper = (props: Props) => { watch-overflow={true} watch-slides-visibility={true} direction={'horizontal'} + slides-per-group-auto={true} > {(slide, index) => ( @@ -130,7 +131,7 @@ export const ImageSwiper = (props: Props) => { class={clsx(styles.navigation, styles.thumbsNav, styles.next, { [styles.disabled]: slideIndex() + 1 === props.images.length, })} - onClick={() => thumbSwipeRef.current.swiper.slideTo(10)} + onClick={() => thumbSwipeRef.current.swiper.slideNext()} >
    diff --git a/src/components/_shared/SolidSwiper/Swiper.module.scss b/src/components/_shared/SolidSwiper/Swiper.module.scss index 04c1ff99..3e9cdf82 100644 --- a/src/components/_shared/SolidSwiper/Swiper.module.scss +++ b/src/components/_shared/SolidSwiper/Swiper.module.scss @@ -52,14 +52,21 @@ } .thumbs { - overflow: hidden; + //overflow: hidden; box-sizing: border-box; margin: 0; position: relative; + & > swiper-container { + display: flex; + flex-direction: row; + gap: 10px; + } + .thumbsNav { - height: 52px; + height: 100%; overflow: hidden; + width: 24px; &.prev { top: 50%; From 2bb600c8c6dc2732ecf88f7c8291c0d94ee62880 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:13:23 +0300 Subject: [PATCH 4/4] Universal Figure with caption (#361) Figure with caption for images and embed --- src/components/Editor/Editor.tsx | 38 ++++++------- .../EditorFloatingMenu/EditorFloatingMenu.tsx | 25 ++++++++- src/components/Editor/Prosemirror.scss | 22 +++++++- src/components/Editor/SimplifiedEditor.tsx | 18 ++---- .../UploadModalContent.module.scss | 1 - src/components/Editor/extensions/Figure.ts | 21 ++++++- .../Editor/extensions/{Embed.ts => Iframe.ts} | 55 ++++++++++--------- src/components/Views/Feed/Feed.module.scss | 4 +- src/styles/app.scss | 5 +- src/utils/renderUploadedImage.ts | 18 ++---- 10 files changed, 121 insertions(+), 86 deletions(-) rename src/components/Editor/extensions/{Embed.ts => Iframe.ts} (51%) diff --git a/src/components/Editor/Editor.tsx b/src/components/Editor/Editor.tsx index 5720c807..18882032 100644 --- a/src/components/Editor/Editor.tsx +++ b/src/components/Editor/Editor.tsx @@ -42,10 +42,10 @@ import { FigureBubbleMenu, BlockquoteBubbleMenu, IncutBubbleMenu } from './Bubbl import { EditorFloatingMenu } from './EditorFloatingMenu' import Article from './extensions/Article' import { CustomBlockquote } from './extensions/CustomBlockquote' -import { Embed } from './extensions/Embed' import { Figcaption } from './extensions/Figcaption' import { Figure } from './extensions/Figure' import { Footnote } from './extensions/Footnote' +import { Iframe } from './extensions/Iframe' import { TrailingNode } from './extensions/TrailingNode' import { TextBubbleMenu } from './TextBubbleMenu' @@ -130,11 +130,6 @@ export const Editor = (props: Props) => { current: null, } - const ImageFigure = Figure.extend({ - name: 'capturedImage', - content: 'figcaption image', - }) - const handleClipboardPaste = async () => { try { const clipboardItems = await navigator.clipboard.read() @@ -163,22 +158,16 @@ export const Editor = (props: Props) => { .chain() .focus() .insertContent({ - type: 'capturedImage', + type: 'figure', + attrs: { 'data-type': 'image' }, content: [ { - type: 'figcaption', - content: [ - { - type: 'text', - text: result.originalFilename, - }, - ], + type: 'image', + attrs: { src: result.url }, }, { - type: 'image', - attrs: { - src: result.url, - }, + type: 'figcaption', + content: [{ type: 'text', text: result.originalFilename }], }, ], }) @@ -250,11 +239,11 @@ export const Editor = (props: Props) => { class: 'highlight', }, }), - ImageFigure, Image, + Iframe, + Figure, Figcaption, Footnote, - Embed, CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689 BubbleMenu.configure({ pluginKey: 'textBubbleMenu', @@ -265,8 +254,13 @@ export const Editor = (props: Props) => { const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection) setIsCommonMarkup(e.isActive('figcaption')) const result = - (view.hasFocus() && !empty && !isEmptyTextBlock && !e.isActive('image')) || - e.isActive('footnote') + (view.hasFocus() && + !empty && + !isEmptyTextBlock && + !e.isActive('image') && + !e.isActive('figure')) || + e.isActive('footnote') || + e.isActive('figcaption') setShouldShowTextBubbleMenu(result) return result }, diff --git a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx index de7a2e65..a348a430 100644 --- a/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx +++ b/src/components/Editor/EditorFloatingMenu/EditorFloatingMenu.tsx @@ -26,7 +26,7 @@ const embedData = async (data) => { const element = document.createRange().createContextualFragment(data) const { attributes } = element.firstChild as HTMLIFrameElement - const result: { src: string } = { src: '' } + const result: { src: string; width?: string; height?: string } = { src: '' } for (let i = 0; i < attributes.length; i++) { const attribute = attributes[i] @@ -45,7 +45,28 @@ export const EditorFloatingMenu = (props: FloatingMenuProps) => { const handleEmbedFormSubmit = async (value: string) => { // TODO: add support instagram embed (blockquote) const emb = await embedData(value) - props.editor.chain().focus().setIframe(emb).run() + props.editor + .chain() + .focus() + .insertContent({ + type: 'figure', + attrs: { 'data-type': 'iframe' }, + content: [ + { + type: 'iframe', + attrs: { + src: emb.src, + width: emb.width, + height: emb.height, + }, + }, + { + type: 'figcaption', + content: [{ type: 'text', text: t('Description') }], + }, + ], + }) + .run() } const validateEmbed = async (value) => { diff --git a/src/components/Editor/Prosemirror.scss b/src/components/Editor/Prosemirror.scss index 3529cb48..d84e01da 100644 --- a/src/components/Editor/Prosemirror.scss +++ b/src/components/Editor/Prosemirror.scss @@ -265,8 +265,26 @@ mark.highlight { } } -figure[data-type='capturedImage'] { - flex-direction: column-reverse; +.ProseMirror-hideselection figure[data-type='figure'] { + & > figcaption { + --selection-color: rgb(0 0 0 / 60%); + } +} + +figure[data-type='figure'] { + width: 100% !important; + + .iframe-wrapper { + position: relative; + overflow: hidden; + width: 100%; + height: auto; + + iframe { + display: block; + width: 100%; + } + } } /* stylelint-disable-next-line selector-type-no-unknown */ diff --git a/src/components/Editor/SimplifiedEditor.tsx b/src/components/Editor/SimplifiedEditor.tsx index 7d4783c4..a4aa4d7b 100644 --- a/src/components/Editor/SimplifiedEditor.tsx +++ b/src/components/Editor/SimplifiedEditor.tsx @@ -185,22 +185,16 @@ const SimplifiedEditor = (props: Props) => { .chain() .focus() .insertContent({ - type: 'capturedImage', + type: 'figure', + attrs: { 'data-type': 'image' }, content: [ { - type: 'figcaption', - content: [ - { - type: 'text', - text: image.originalFilename, - }, - ], + type: 'image', + attrs: { src: image.url }, }, { - type: 'image', - attrs: { - src: image.url, - }, + type: 'figcaption', + content: [{ type: 'text', text: image.originalFilename }], }, ], }) diff --git a/src/components/Editor/UploadModalContent/UploadModalContent.module.scss b/src/components/Editor/UploadModalContent/UploadModalContent.module.scss index c8cbb0ee..f34ec66b 100644 --- a/src/components/Editor/UploadModalContent/UploadModalContent.module.scss +++ b/src/components/Editor/UploadModalContent/UploadModalContent.module.scss @@ -81,7 +81,6 @@ .formHolder { width: 100%; margin-top: 24px; - border-bottom: 1px solid #000; } } diff --git a/src/components/Editor/extensions/Figure.ts b/src/components/Editor/extensions/Figure.ts index 1a252b18..c67bebc2 100644 --- a/src/components/Editor/extensions/Figure.ts +++ b/src/components/Editor/extensions/Figure.ts @@ -16,24 +16,39 @@ export const Figure = Node.create({ } }, group: 'block', - content: 'block figcaption', + content: '(image | iframe) figcaption', draggable: true, isolating: true, + atom: true, addAttributes() { return { 'data-float': null, + 'data-type': { default: null }, } }, parseHTML() { return [ { - tag: `figure[data-type="${this.name}"]`, + tag: 'figure', + getAttrs: (node) => { + if (!(node instanceof HTMLElement)) { + return + } + const img = node.querySelector('img') + const iframe = node.querySelector('iframe') + let dataType = null + if (img) { + dataType = 'image' + } else if (iframe) { + dataType = 'iframe' + } + return { 'data-type': dataType } + }, }, ] }, - renderHTML({ HTMLAttributes }) { return ['figure', mergeAttributes(HTMLAttributes, { 'data-type': this.name }), 0] }, diff --git a/src/components/Editor/extensions/Embed.ts b/src/components/Editor/extensions/Iframe.ts similarity index 51% rename from src/components/Editor/extensions/Embed.ts rename to src/components/Editor/extensions/Iframe.ts index ce842a26..63ebab2f 100644 --- a/src/components/Editor/extensions/Embed.ts +++ b/src/components/Editor/extensions/Iframe.ts @@ -1,4 +1,4 @@ -import { mergeAttributes, Node } from '@tiptap/core' +import { Node } from '@tiptap/core' export interface IframeOptions { allowFullscreen: boolean @@ -15,19 +15,35 @@ declare module '@tiptap/core' { } } -export const Embed = Node.create({ - name: 'embed', +export const Iframe = Node.create({ + name: 'iframe', group: 'block', - selectable: true, atom: true, - draggable: true, - addAttributes() { + + addOptions() { return { - src: { default: null }, - width: { default: null }, - height: { default: null }, + allowFullscreen: true, + HTMLAttributes: { + class: 'iframe-wrapper', + }, } }, + + addAttributes() { + return { + src: { + default: null, + }, + frameborder: { + default: 0, + }, + allowfullscreen: { + default: this.options.allowFullscreen, + parseHTML: () => this.options.allowFullscreen, + }, + } + }, + parseHTML() { return [ { @@ -35,28 +51,15 @@ export const Embed = Node.create({ }, ] }, + renderHTML({ HTMLAttributes }) { - return ['iframe', mergeAttributes(HTMLAttributes)] - }, - addNodeView() { - return ({ node }) => { - const div = document.createElement('div') - div.className = 'embed-wrapper' - const iframe = document.createElement('iframe') - iframe.width = node.attrs.width - iframe.height = node.attrs.height - iframe.allowFullscreen = node.attrs.allowFullscreen - iframe.src = node.attrs.src - div.append(iframe) - return { - dom: div, - } - } + return ['div', this.options.HTMLAttributes, ['iframe', HTMLAttributes]] }, + addCommands() { return { setIframe: - (options) => + (options: { src: string }) => ({ tr, dispatch }) => { const { selection } = tr const node = this.type.create(options) diff --git a/src/components/Views/Feed/Feed.module.scss b/src/components/Views/Feed/Feed.module.scss index f2905bc2..27a3854b 100644 --- a/src/components/Views/Feed/Feed.module.scss +++ b/src/components/Views/Feed/Feed.module.scss @@ -197,8 +197,8 @@ margin-bottom: 4rem; @include media-breakpoint-down(sm) { - flex-direction: column-reverse; - align-items: flex-start; + margin: 1rem 0 0; + flex-direction: column; gap: 1rem; } diff --git a/src/styles/app.scss b/src/styles/app.scss index ef3ea022..2d3a7ac2 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -582,8 +582,8 @@ figure { display: flex; flex-direction: column; width: fit-content; - gap: 16px; margin: 2em auto; + gap: 0.6rem; img { display: block; @@ -596,11 +596,8 @@ figure { figure { figcaption { color: rgb(0 0 0 / 60%); - @include font-size(1.2rem); - line-height: 1.5; - margin-top: 0.5em; } } diff --git a/src/utils/renderUploadedImage.ts b/src/utils/renderUploadedImage.ts index a6b2ac78..dfadf611 100644 --- a/src/utils/renderUploadedImage.ts +++ b/src/utils/renderUploadedImage.ts @@ -8,22 +8,16 @@ export const renderUploadedImage = (editor: Editor, image: UploadedFile) => { .chain() .focus() .insertContent({ - type: 'capturedImage', + type: 'figure', + attrs: { 'data-type': 'image' }, content: [ { - type: 'figcaption', - content: [ - { - type: 'text', - text: image.originalFilename ?? '', - }, - ], + type: 'image', + attrs: { src: image.url }, }, { - type: 'image', - attrs: { - src: image.url, - }, + type: 'figcaption', + content: [{ type: 'text', text: image.originalFilename }], }, ], })