From 7e32f0d992e90a856de755cbb1bae41682193222 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Nov 2022 18:13:27 -0600 Subject: [PATCH 1/2] Add component to style markup from the API --- .../{status-content.css => markup.css} | 40 +++++++-------- app/soapbox/components/markup.tsx | 16 ++++++ app/soapbox/components/status-content.tsx | 51 +++---------------- app/soapbox/components/ui/text/text.tsx | 8 +-- .../ui/components/profile-fields-panel.tsx | 7 +-- .../ui/components/profile-info-panel.tsx | 12 ++--- app/soapbox/normalizers/account.ts | 10 ++++ 7 files changed, 66 insertions(+), 78 deletions(-) rename app/soapbox/components/{status-content.css => markup.css} (62%) create mode 100644 app/soapbox/components/markup.tsx diff --git a/app/soapbox/components/status-content.css b/app/soapbox/components/markup.css similarity index 62% rename from app/soapbox/components/status-content.css rename to app/soapbox/components/markup.css index 997df73f4..d89848f2c 100644 --- a/app/soapbox/components/status-content.css +++ b/app/soapbox/components/markup.css @@ -1,77 +1,77 @@ -.status-content p { +[data-markup] p { @apply mb-4 whitespace-pre-wrap; } -.status-content p:last-child { +[data-markup] p:last-child { @apply mb-0; } -.status-content a { +[data-markup] a { @apply text-primary-600 dark:text-accent-blue hover:underline; } -.status-content strong { +[data-markup] strong { @apply font-bold; } -.status-content em { +[data-markup] em { @apply italic; } -.status-content ul, -.status-content ol { +[data-markup] ul, +[data-markup] ol { @apply pl-10 mb-4; } -.status-content ul { +[data-markup] ul { @apply list-disc list-outside; } -.status-content ol { +[data-markup] ol { @apply list-decimal list-outside; } -.status-content blockquote { +[data-markup] blockquote { @apply py-1 pl-4 mb-4 border-l-4 border-solid border-gray-400 text-gray-500 dark:text-gray-400; } -.status-content code { +[data-markup] code { @apply cursor-text font-mono; } -.status-content p > code, -.status-content pre { +[data-markup] p > code, +[data-markup] pre { @apply bg-gray-100 dark:bg-primary-800; } /* Inline code */ -.status-content p > code { +[data-markup] p > code { @apply py-0.5 px-1 rounded-sm; } /* Code block */ -.status-content pre { +[data-markup] pre { @apply py-2 px-3 mb-4 leading-6 overflow-x-auto rounded-md break-all; } -.status-content pre:last-child { +[data-markup] pre:last-child { @apply mb-0; } /* Markdown images */ -.status-content img:not(.emojione):not([width][height]) { +[data-markup] img:not(.emojione):not([width][height]) { @apply w-full h-72 object-contain rounded-lg overflow-hidden my-4 block; } /* User setting to underline links */ -body.underline-links .status-content a { +body.underline-links [data-markup] a { @apply underline; } -.status-content .big-emoji img.emojione { +[data-markup] .big-emoji img.emojione { @apply inline w-9 h-9 p-1; } -.status-content .status-link { +[data-markup] .status-link { @apply hover:underline text-primary-600 dark:text-accent-blue hover:text-primary-800 dark:hover:text-accent-blue; } diff --git a/app/soapbox/components/markup.tsx b/app/soapbox/components/markup.tsx new file mode 100644 index 000000000..e20dcb3a2 --- /dev/null +++ b/app/soapbox/components/markup.tsx @@ -0,0 +1,16 @@ +import React from 'react'; + +import Text, { IText } from './ui/text/text'; +import './markup.css'; + +interface IMarkup extends IText { +} + +/** Styles HTML markup returned by the API, such as in account bios and statuses. */ +const Markup = React.forwardRef((props, ref) => { + return ( + + ); +}); + +export default Markup; \ No newline at end of file diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index 2b2754176..6bfbb8e18 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -10,8 +10,8 @@ import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content'; import { isRtl } from '../rtl'; +import Markup from './markup'; import Poll from './polls/poll'; -import './status-content.css'; import StopPropagation from './stop-propagation'; import type { Status, Mention } from 'soapbox/types/entities'; @@ -19,11 +19,6 @@ import type { Status, Mention } from 'soapbox/types/entities'; const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) const BIG_EMOJI_LIMIT = 10; -type Point = [ - x: number, - y: number, -] - interface IReadMoreButton { onClick: React.MouseEventHandler, } @@ -52,7 +47,6 @@ const StatusContent: React.FC = ({ status, onClick, collapsable const [collapsed, setCollapsed] = useState(false); const [onlyEmoji, setOnlyEmoji] = useState(false); - const startXY = useRef(); const node = useRef(null); const { greentext } = useSoapboxConfig(); @@ -138,29 +132,6 @@ const StatusContent: React.FC = ({ status, onClick, collapsable updateStatusLinks(); }); - const handleMouseDown: React.EventHandler = (e) => { - startXY.current = [e.clientX, e.clientY]; - }; - - const handleMouseUp: React.EventHandler = (e) => { - if (!startXY.current) return; - const target = e.target as HTMLElement; - const parentNode = target.parentNode as HTMLElement; - - const [startX, startY] = startXY.current; - const [deltaX, deltaY] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; - - if (target.localName === 'button' || target.localName === 'a' || (parentNode && (parentNode.localName === 'button' || parentNode.localName === 'a'))) { - return; - } - - if (deltaX + deltaY < 5 && e.button === 0 && !(e.ctrlKey || e.metaKey) && onClick) { - onClick(); - } - - startXY.current = undefined; - }; - const parsedHtml = useMemo((): string => { const html = translatable && status.translation ? status.translation.get('content')! : status.contentHtml; @@ -180,30 +151,24 @@ const StatusContent: React.FC = ({ status, onClick, collapsable const baseClassName = 'text-gray-900 dark:text-gray-100 break-words text-ellipsis overflow-hidden relative focus:outline-none'; const content = { __html: parsedHtml }; - const directionStyle: React.CSSProperties = { direction: 'ltr' }; - const className = classNames(baseClassName, 'status-content', { + const direction = isRtl(status.search_index) ? 'rtl' : 'ltr'; + const className = classNames(baseClassName, { 'cursor-pointer': onClick, 'whitespace-normal': withSpoiler, 'max-h-[300px]': collapsed, 'leading-normal big-emoji': onlyEmoji, }); - if (isRtl(status.search_index)) { - directionStyle.direction = 'rtl'; - } - if (onClick) { const output = [ -
, ]; @@ -219,14 +184,14 @@ const StatusContent: React.FC = ({ status, onClick, collapsable return
{output}
; } else { const output = [ -
, diff --git a/app/soapbox/components/ui/text/text.tsx b/app/soapbox/components/ui/text/text.tsx index 7669f3d2a..221c070fc 100644 --- a/app/soapbox/components/ui/text/text.tsx +++ b/app/soapbox/components/ui/text/text.tsx @@ -54,7 +54,9 @@ export type Sizes = keyof typeof sizes type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' type Directions = 'ltr' | 'rtl' -interface IText extends Pick, 'dangerouslySetInnerHTML'> { +interface IText extends Pick, 'dangerouslySetInnerHTML' | 'tabIndex' | 'lang'> { + /** Text content. */ + children?: React.ReactNode, /** How to align the text. */ align?: keyof typeof alignments, /** Extra class names for the outer element. */ @@ -84,8 +86,8 @@ interface IText extends Pick, 'danger } /** UI-friendly text container with dark mode support. */ -const Text: React.FC = React.forwardRef( - (props: IText, ref: React.LegacyRef) => { +const Text = React.forwardRef( + (props, ref) => { const { align, className, diff --git a/app/soapbox/features/ui/components/profile-fields-panel.tsx b/app/soapbox/features/ui/components/profile-fields-panel.tsx index dfa4dc84a..1ee911e34 100644 --- a/app/soapbox/features/ui/components/profile-fields-panel.tsx +++ b/app/soapbox/features/ui/components/profile-fields-panel.tsx @@ -2,7 +2,8 @@ import classNames from 'clsx'; import React from 'react'; import { defineMessages, useIntl, FormattedMessage, FormatDateOptions } from 'react-intl'; -import { Widget, Stack, HStack, Icon, Text } from 'soapbox/components/ui'; +import Markup from 'soapbox/components/markup'; +import { Widget, Stack, HStack, Icon } from 'soapbox/components/ui'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; import { CryptoAddress } from 'soapbox/features/ui/util/async-components'; @@ -51,7 +52,7 @@ const ProfileField: React.FC = ({ field }) => { return (
- +
= ({ field }) => { )} - +
diff --git a/app/soapbox/features/ui/components/profile-info-panel.tsx b/app/soapbox/features/ui/components/profile-info-panel.tsx index cf97b78e7..89239bd61 100644 --- a/app/soapbox/features/ui/components/profile-info-panel.tsx +++ b/app/soapbox/features/ui/components/profile-info-panel.tsx @@ -4,6 +4,7 @@ import React from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import Badge from 'soapbox/components/badge'; +import Markup from 'soapbox/components/markup'; import { Icon, HStack, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification-badge'; import { useSoapboxConfig } from 'soapbox/hooks'; @@ -139,13 +140,6 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => return (
- {/* Not sure if this is actual used. */} - {/*
- -
*/} - @@ -178,8 +172,8 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - {account.note.length > 0 && account.note !== '

' && ( - + {account.note.length > 0 && ( + )}
diff --git a/app/soapbox/normalizers/account.ts b/app/soapbox/normalizers/account.ts index 07d3ec1e6..39541facf 100644 --- a/app/soapbox/normalizers/account.ts +++ b/app/soapbox/normalizers/account.ts @@ -269,6 +269,15 @@ const fixBirthday = (account: ImmutableMap) => { return account.set('birthday', birthday || ''); }; +/** Rewrite `

` to empty string. */ +const fixNote = (account: ImmutableMap) => { + if (account.get('note') === '

') { + return account.set('note', ''); + } else { + return account; + } +}; + export const normalizeAccount = (account: Record) => { return AccountRecord( ImmutableMap(fromJS(account)).withMutations(account => { @@ -289,6 +298,7 @@ export const normalizeAccount = (account: Record) => { fixUsername(account); fixDisplayName(account); fixBirthday(account); + fixNote(account); addInternalFields(account); }), ); From 00989a7dfc169826eb73ea8c4c8b8264958674df Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Nov 2022 18:28:34 -0600 Subject: [PATCH 2/2] LandingPage: use Markup component for instance description --- app/soapbox/features/landing-page/index.tsx | 13 +++++-------- .../features/landing-page/instance-description.css | 14 -------------- 2 files changed, 5 insertions(+), 22 deletions(-) delete mode 100644 app/soapbox/features/landing-page/instance-description.css diff --git a/app/soapbox/features/landing-page/index.tsx b/app/soapbox/features/landing-page/index.tsx index b5332fa34..08b9c2242 100644 --- a/app/soapbox/features/landing-page/index.tsx +++ b/app/soapbox/features/landing-page/index.tsx @@ -2,14 +2,13 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { prepareRequest } from 'soapbox/actions/consumer-auth'; +import Markup from 'soapbox/components/markup'; import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui'; import VerificationBadge from 'soapbox/components/verification-badge'; import RegistrationForm from 'soapbox/features/auth-login/components/registration-form'; import { useAppDispatch, useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks'; import { capitalize } from 'soapbox/utils/strings'; -import './instance-description.css'; - const LandingPage = () => { const dispatch = useAppDispatch(); const features = useFeatures(); @@ -114,12 +113,10 @@ const LandingPage = () => { {instance.title} - - - +
diff --git a/app/soapbox/features/landing-page/instance-description.css b/app/soapbox/features/landing-page/instance-description.css deleted file mode 100644 index f67097de0..000000000 --- a/app/soapbox/features/landing-page/instance-description.css +++ /dev/null @@ -1,14 +0,0 @@ -/* Instance HTML from the API. */ -.instance-description a { - @apply underline; -} - -.instance-description b, -.instance-description strong { - @apply font-bold; -} - -.instance-description i, -.instance-description em { - @apply italic; -}