From 5f8a22b452406173bc9c737170fb004da48baf2d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 12 Aug 2022 12:58:35 -0500 Subject: [PATCH] Native status embeds from Soapbox --- app/soapbox/components/status-action-bar.tsx | 14 ++--- app/soapbox/components/status.tsx | 9 ++- app/soapbox/components/ui/card/card.tsx | 4 +- app/soapbox/containers/soapbox.tsx | 7 +++ .../features/embedded-status/index.tsx | 50 ++++++++++++++++ .../features/ui/components/embed_modal.tsx | 59 +++---------------- app/soapbox/utils/features.ts | 6 -- app/styles/components/status.scss | 4 -- 8 files changed, 80 insertions(+), 73 deletions(-) create mode 100644 app/soapbox/features/embedded-status/index.tsx diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 0e6786fb6..a8b590709 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -284,7 +284,7 @@ const StatusActionBar: React.FC = ({ const handleEmbed = () => { dispatch(openModal('EMBED', { - url: status.get('url'), + status, onError: (error: any) => dispatch(showAlertForError(error)), })); }; @@ -362,13 +362,11 @@ const StatusActionBar: React.FC = ({ icon: require('@tabler/icons/link.svg'), }); - if (features.embeds) { - menu.push({ - text: intl.formatMessage(messages.embed), - action: handleEmbed, - icon: require('@tabler/icons/share.svg'), - }); - } + menu.push({ + text: intl.formatMessage(messages.embed), + action: handleEmbed, + icon: require('@tabler/icons/share.svg'), + }); } if (!me) { diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 37cf7a24b..3b27b4ad9 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -18,7 +18,7 @@ import StatusActionBar from './status-action-bar'; import StatusMedia from './status-media'; import StatusReplyMentions from './status-reply-mentions'; import StatusContent from './status_content'; -import { HStack, Text } from './ui'; +import { Card, HStack, Text } from './ui'; import type { Map as ImmutableMap } from 'immutable'; import type { @@ -47,6 +47,7 @@ export interface IStatus { featured?: boolean, hideActionBar?: boolean, hoverable?: boolean, + variant?: 'default' | 'rounded', } const Status: React.FC = (props) => { @@ -63,6 +64,7 @@ const Status: React.FC = (props) => { unread, group, hideActionBar, + variant = 'rounded', } = props; const intl = useIntl(); const history = useHistory(); @@ -318,7 +320,8 @@ const Status: React.FC = (props) => { > {prepend} -
= (props) => {
)} - + ); diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index 4c4b8baaa..6b7fea1f6 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -18,7 +18,7 @@ const messages = defineMessages({ interface ICard { /** The type of card. */ - variant?: 'rounded', + variant?: 'default' | 'rounded', /** Card size preset. */ size?: 'md' | 'lg' | 'xl', /** Extra classnames for the
element. */ @@ -28,7 +28,7 @@ interface ICard { } /** An opaque backdrop to hold a collection of related elements. */ -const Card = React.forwardRef(({ children, variant, size = 'md', className, ...filteredProps }, ref): JSX.Element => ( +const Card = React.forwardRef(({ children, variant = 'default', size = 'md', className, ...filteredProps }, ref): JSX.Element => (
{ )} + } + /> + + diff --git a/app/soapbox/features/embedded-status/index.tsx b/app/soapbox/features/embedded-status/index.tsx new file mode 100644 index 000000000..aeb4272e2 --- /dev/null +++ b/app/soapbox/features/embedded-status/index.tsx @@ -0,0 +1,50 @@ +import React, { useEffect, useState } from 'react'; + +import { fetchStatus } from 'soapbox/actions/statuses'; +import MissingIndicator from 'soapbox/components/missing_indicator'; +import Status from 'soapbox/components/status'; +import { Spinner } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { makeGetStatus } from 'soapbox/selectors'; + +interface IEmbeddedStatus { + params: { + statusId: string, + }, +} + +const getStatus = makeGetStatus(); + +/** Status to be presented in an iframe for embeds on external websites. */ +const EmbeddedStatus: React.FC = ({ params }) => { + const dispatch = useAppDispatch(); + const status = useAppSelector(state => getStatus(state, { id: params.statusId })); + + const [loading, setLoading] = useState(true); + + useEffect(() => { + dispatch(fetchStatus(params.statusId)) + .then(() => setLoading(false)) + .catch(() => setLoading(false)); + }, []); + + const renderInner = () => { + if (loading) { + return ; + } else if (status) { + return ; + } else { + return ; + } + }; + + return ( +
+
+ {renderInner()} +
+
+ ); +}; + +export default EmbeddedStatus; diff --git a/app/soapbox/features/ui/components/embed_modal.tsx b/app/soapbox/features/ui/components/embed_modal.tsx index 0b3be1bc1..6a1127686 100644 --- a/app/soapbox/features/ui/components/embed_modal.tsx +++ b/app/soapbox/features/ui/components/embed_modal.tsx @@ -1,52 +1,17 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React from 'react'; import { FormattedMessage } from 'react-intl'; -import api from 'soapbox/api'; import { Modal, Stack, Text, Input } from 'soapbox/components/ui'; -import { useAppDispatch } from 'soapbox/hooks'; -import type { RootState } from 'soapbox/store'; - -const fetchEmbed = (url: string) => { - return (dispatch: any, getState: () => RootState) => { - return api(getState).get('/api/oembed', { params: { url } }); - }; -}; +import type { Status } from 'soapbox/types/entities'; interface IEmbedModal { - url: string, - onError: (error: any) => void, + status: Status, } -const EmbedModal: React.FC = ({ url, onError }) => { - const dispatch = useAppDispatch(); - - const iframe = useRef(null); - const [oembed, setOembed] = useState(null); - - useEffect(() => { - - dispatch(fetchEmbed(url)).then(({ data }) => { - if (!iframe.current?.contentWindow) return; - setOembed(data); - - const iframeDocument = iframe.current.contentWindow.document; - - iframeDocument.open(); - iframeDocument.write(data.html); - iframeDocument.close(); - - const innerFrame = iframeDocument.querySelector('iframe'); - - iframeDocument.body.style.margin = '0'; - - if (innerFrame) { - innerFrame.width = '100%'; - } - }).catch(error => { - onError(error); - }); - }, [!!iframe.current]); +const EmbedModal: React.FC = ({ status }) => { + const url = `${location.origin}/embed/${status.id}`; + const embed = `