diff --git a/app/assets/fonts/soapbox/soapbox.eot b/app/assets/fonts/soapbox/soapbox.eot deleted file mode 100644 index d66bb100a..000000000 Binary files a/app/assets/fonts/soapbox/soapbox.eot and /dev/null differ diff --git a/app/assets/fonts/soapbox/soapbox.svg b/app/assets/fonts/soapbox/soapbox.svg deleted file mode 100644 index 20d08a586..000000000 --- a/app/assets/fonts/soapbox/soapbox.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - -Generated by IcoMoon - - - - - - - - \ No newline at end of file diff --git a/app/assets/fonts/soapbox/soapbox.ttf b/app/assets/fonts/soapbox/soapbox.ttf deleted file mode 100644 index df64210fb..000000000 Binary files a/app/assets/fonts/soapbox/soapbox.ttf and /dev/null differ diff --git a/app/assets/fonts/soapbox/soapbox.woff b/app/assets/fonts/soapbox/soapbox.woff deleted file mode 100644 index 1902dbc36..000000000 Binary files a/app/assets/fonts/soapbox/soapbox.woff and /dev/null differ 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/media-gallery.tsx b/app/soapbox/components/media-gallery.tsx index 42b961dc1..9244f05d5 100644 --- a/app/soapbox/components/media-gallery.tsx +++ b/app/soapbox/components/media-gallery.tsx @@ -160,16 +160,22 @@ const Item: React.FC = ({ ); } else if (attachment.type === 'image') { - const letterboxed = shouldLetterbox(attachment); + const letterboxed = total === 1 && shouldLetterbox(attachment); thumbnail = ( - + ); } else if (attachment.type === 'gifv') { diff --git a/app/soapbox/components/profile-hover-card.tsx b/app/soapbox/components/profile-hover-card.tsx index 367c788e0..8ade427ca 100644 --- a/app/soapbox/components/profile-hover-card.tsx +++ b/app/soapbox/components/profile-hover-card.tsx @@ -136,7 +136,7 @@ export const ProfileHoverCard: React.FC = ({ visible = true } ) : null} - {account.source.get('note', '').length > 0 && ( + {account.note.length > 0 && ( )} diff --git a/app/soapbox/components/status-action-bar.tsx b/app/soapbox/components/status-action-bar.tsx index 011fc63a8..6735c1b29 100644 --- a/app/soapbox/components/status-action-bar.tsx +++ b/app/soapbox/components/status-action-bar.tsx @@ -1,4 +1,3 @@ -import classNames from 'clsx'; import { List as ImmutableList } from 'immutable'; import React from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -16,6 +15,7 @@ import { initReport } from 'soapbox/actions/reports'; import { deleteStatus, editStatus, toggleMuteStatus } from 'soapbox/actions/statuses'; import EmojiButtonWrapper from 'soapbox/components/emoji-button-wrapper'; import StatusActionButton from 'soapbox/components/status-action-button'; +import { HStack } from 'soapbox/components/ui'; import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks'; import { isLocal } from 'soapbox/utils/accounts'; @@ -127,8 +127,6 @@ const StatusActionBar: React.FC = ({ } else { onOpenUnauthorizedModal('REPLY'); } - - e.stopPropagation(); }; const handleShareClick = () => { @@ -146,18 +144,13 @@ const StatusActionBar: React.FC = ({ } else { onOpenUnauthorizedModal('FAVOURITE'); } - - e.stopPropagation(); }; const handleBookmarkClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(toggleBookmark(status)); }; const handleReblogClick: React.EventHandler = e => { - e.stopPropagation(); - if (me) { const modalReblog = () => dispatch(toggleReblog(status)); const boostModal = settings.get('boostModal'); @@ -172,8 +165,6 @@ const StatusActionBar: React.FC = ({ }; const handleQuoteClick: React.EventHandler = (e) => { - e.stopPropagation(); - if (me) { dispatch(quoteCompose(status)); } else { @@ -199,12 +190,10 @@ const StatusActionBar: React.FC = ({ }; const handleDeleteClick: React.EventHandler = (e) => { - e.stopPropagation(); doDeleteStatus(); }; const handleRedraftClick: React.EventHandler = (e) => { - e.stopPropagation(); doDeleteStatus(true); }; @@ -213,35 +202,29 @@ const StatusActionBar: React.FC = ({ }; const handlePinClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(togglePin(status)); }; const handleMentionClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(mentionCompose(status.account as Account)); }; const handleDirectClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(directCompose(status.account as Account)); }; const handleChatClick: React.EventHandler = (e) => { - e.stopPropagation(); const account = status.account as Account; dispatch(launchChat(account.id, history)); }; const handleMuteClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(initMuteModal(status.account as Account)); }; const handleBlockClick: React.EventHandler = (e) => { - e.stopPropagation(); - const account = status.get('account') as Account; + dispatch(openModal('CONFIRM', { icon: require('@tabler/icons/ban.svg'), heading: , @@ -257,7 +240,6 @@ const StatusActionBar: React.FC = ({ }; const handleOpen: React.EventHandler = (e) => { - e.stopPropagation(); history.push(`/@${status.getIn(['account', 'acct'])}/posts/${status.id}`); }; @@ -269,12 +251,10 @@ const StatusActionBar: React.FC = ({ }; const handleReport: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(initReport(status.account as Account, status)); }; const handleConversationMuteClick: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(toggleMuteStatus(status)); }; @@ -282,8 +262,6 @@ const StatusActionBar: React.FC = ({ const { uri } = status; const textarea = document.createElement('textarea'); - e.stopPropagation(); - textarea.textContent = uri; textarea.style.position = 'fixed'; @@ -300,18 +278,15 @@ const StatusActionBar: React.FC = ({ }; const onModerate: React.MouseEventHandler = (e) => { - e.stopPropagation(); const account = status.account as Account; dispatch(openModal('ACCOUNT_MODERATION', { accountId: account.id })); }; const handleDeleteStatus: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(deleteStatusModal(intl, status.id)); }; const handleToggleStatusSensitivity: React.EventHandler = (e) => { - e.stopPropagation(); dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive)); }; @@ -550,74 +525,75 @@ const StatusActionBar: React.FC = ({ const canShare = ('share' in navigator) && status.visibility === 'public'; return ( -
- + + e.stopPropagation()} + > + - {(features.quotePosts && me) ? ( - - {reblogButton} - - ) : ( - reblogButton - )} + {(features.quotePosts && me) ? ( + + {reblogButton} + + ) : ( + reblogButton + )} - {features.emojiReacts ? ( - + {features.emojiReacts ? ( + + + + ) : ( - - ) : ( - - )} + )} - {canShare && ( - - )} + {canShare && ( + + )} - - - -
+ + + + + ); }; diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index a495a9f82..df35a90ac 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -10,19 +10,14 @@ 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 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, } @@ -49,7 +44,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(); @@ -131,29 +125,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; @@ -173,30 +144,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 = [ -
, ]; @@ -212,14 +177,14 @@ const StatusContent: React.FC = ({ status, onClick, collapsable return
{output}
; } else { const output = [ -
, diff --git a/app/soapbox/components/status-media.tsx b/app/soapbox/components/status-media.tsx index c4f809425..e6af91bc1 100644 --- a/app/soapbox/components/status-media.tsx +++ b/app/soapbox/components/status-media.tsx @@ -167,7 +167,16 @@ const StatusMedia: React.FC = ({ ); } - return media; + if (media) { + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
e.stopPropagation()}> + {media} +
+ ); + } else { + return null; + } }; export default StatusMedia; diff --git a/app/soapbox/components/status.tsx b/app/soapbox/components/status.tsx index 812d915ea..2d31f6fc1 100644 --- a/app/soapbox/components/status.tsx +++ b/app/soapbox/components/status.tsx @@ -110,6 +110,11 @@ const Status: React.FC = (props) => { const handleClick = (e?: React.MouseEvent): void => { e?.stopPropagation(); + // If the user is selecting text, don't focus the status. + if (getSelection()?.toString().length) { + return; + } + if (!e || !(e.ctrlKey || e.metaKey)) { if (onClick) { onClick(); diff --git a/app/soapbox/components/still-image.tsx b/app/soapbox/components/still-image.tsx index 24e212ce5..1f4f1a261 100644 --- a/app/soapbox/components/still-image.tsx +++ b/app/soapbox/components/still-image.tsx @@ -12,10 +12,14 @@ interface IStillImage { src: string, /** Extra CSS styles on the outer
element. */ style?: React.CSSProperties, + /** Whether to display the image contained vs filled in its container. */ + letterboxed?: boolean, + /** Whether to show the file extension in the corner. */ + showExt?: boolean, } /** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */ -const StillImage: React.FC = ({ alt, className, src, style }) => { +const StillImage: React.FC = ({ alt, className, src, style, letterboxed = false, showExt = false }) => { const settings = useSettings(); const autoPlayGif = settings.get('autoPlayGif'); @@ -34,10 +38,56 @@ const StillImage: React.FC = ({ alt, className, src, style }) => { } }; + /** ClassNames shared between the `` and `` elements. */ + const baseClassName = classNames('w-full h-full block', { + 'object-contain': letterboxed, + 'object-cover': !letterboxed, + }); + return ( -
- {alt} - {hoverToPlay && } +
+ {alt} + + {hoverToPlay && ( + + )} + + {(hoverToPlay && showExt) && ( +
+ +
+ )} +
+ ); +}; + +interface IExtensionBadge { + /** File extension. */ + ext: string, +} + +/** Badge displaying a file extension. */ +const ExtensionBadge: React.FC = ({ ext }) => { + return ( +
+ {ext}
); }; diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index 59f6ee1bc..a2d181e3a 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -33,7 +33,7 @@ const Card = React.forwardRef(({ children, variant = 'def ref={ref} {...filteredProps} className={classNames({ - 'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none overflow-hidden': variant === 'rounded', + 'bg-white dark:bg-primary-900 text-gray-900 dark:text-gray-100 shadow-lg dark:shadow-none overflow-hidden isolate': variant === 'rounded', [sizes[size]]: variant === 'rounded', }, className)} > diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx index 996320dea..996ccd64d 100644 --- a/app/soapbox/components/ui/hstack/hstack.tsx +++ b/app/soapbox/components/ui/hstack/hstack.tsx @@ -27,7 +27,7 @@ const spaces = { 8: 'space-x-8', }; -interface IHStack { +interface IHStack extends Pick, 'onClick'> { /** Vertical alignment of children. */ alignItems?: keyof typeof alignItemsOptions /** Extra class names on the
element. */ 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/account-gallery/components/media-item.tsx b/app/soapbox/features/account-gallery/components/media-item.tsx index a2a171bfc..4242094b5 100644 --- a/app/soapbox/features/account-gallery/components/media-item.tsx +++ b/app/soapbox/features/account-gallery/components/media-item.tsx @@ -74,6 +74,7 @@ const MediaItem: React.FC = ({ attachment, displayWidth, onOpenMedia src={attachment.preview_url} alt={attachment.description} style={{ objectPosition: `${x}% ${y}%` }} + className='w-full h-full rounded-lg overflow-hidden' /> ); } else if (['gifv', 'video'].indexOf(attachment.type) !== -1) { diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx index 4aa4d3b05..d3d7865b6 100644 --- a/app/soapbox/features/account/components/header.tsx +++ b/app/soapbox/features/account/components/header.tsx @@ -551,13 +551,12 @@ const Header: React.FC = ({ account }) => { )}
-
+
{account.header && ( )} @@ -577,7 +576,7 @@ const Header: React.FC = ({ account }) => {
diff --git a/app/soapbox/features/audio/index.tsx b/app/soapbox/features/audio/index.tsx index 0bef3c3d9..ecbae5b16 100644 --- a/app/soapbox/features/audio/index.tsx +++ b/app/soapbox/features/audio/index.tsx @@ -449,6 +449,7 @@ const Audio: React.FC = (props) => { onMouseLeave={handleMouseLeave} tabIndex={0} onKeyDown={handleKeyDown} + onClick={e => e.stopPropagation()} >
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; -} 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/features/ui/components/user-panel.tsx b/app/soapbox/features/ui/components/user-panel.tsx index 8f0fbf517..918d94efd 100644 --- a/app/soapbox/features/ui/components/user-panel.tsx +++ b/app/soapbox/features/ui/components/user-panel.tsx @@ -36,13 +36,9 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain })
-
+
{header && ( - + )}
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); }), ); diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index 2c19b2511..513a34b94 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -1,5 +1,5 @@ import escapeTextContentForBrowser from 'escape-html'; -import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import emojify from 'soapbox/features/emoji/emoji'; import { normalizeStatus } from 'soapbox/normalizers'; @@ -195,6 +195,24 @@ const simulateFavourite = ( return state.set(statusId, updatedStatus); }; +interface Translation { + content: string, + detected_source_language: string, + provider: string, +} + +/** Import translation from translation service into the store. */ +const importTranslation = (state: State, statusId: string, translation: Translation) => { + const map = ImmutableMap(translation); + const result = map.set('content', stripCompatibilityFeatures(map.get('content', ''))); + return state.setIn([statusId, 'translation'], result); +}; + +/** Delete translation from the store. */ +const deleteTranslation = (state: State, statusId: string) => { + return state.deleteIn([statusId, 'translation']); +}; + const initialState: State = ImmutableMap(); export default function statuses(state = initialState, action: AnyAction): State { @@ -258,9 +276,9 @@ export default function statuses(state = initialState, action: AnyAction): State case STATUS_DELETE_FAIL: return incrementReplyCount(state, action.params); case STATUS_TRANSLATE_SUCCESS: - return state.setIn([action.id, 'translation'], fromJS(action.translation)); + return importTranslation(state, action.id, action.translation); case STATUS_TRANSLATE_UNDO: - return state.deleteIn([action.id, 'translation']); + return deleteTranslation(state, action.id); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); default: diff --git a/app/styles/accounts.scss b/app/styles/accounts.scss index e4a0833c7..e24a19ddf 100644 --- a/app/styles/accounts.scss +++ b/app/styles/accounts.scss @@ -72,10 +72,6 @@ a .account__avatar { bottom: 0; right: 0; z-index: 1; - - &.still-image { - position: absolute; - } } } diff --git a/app/styles/application.scss b/app/styles/application.scss index 96ef432fd..9ec32ed71 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -44,7 +44,6 @@ @import 'components/columns'; @import 'components/search'; @import 'components/react-toggle'; -@import 'components/still-image'; @import 'components/video-player'; @import 'components/audio-player'; @import 'components/profile-hover-card'; diff --git a/app/styles/chats.scss b/app/styles/chats.scss index 499e0640f..1975a6a2c 100644 --- a/app/styles/chats.scss +++ b/app/styles/chats.scss @@ -352,11 +352,6 @@ &__preview { background-color: transparent; } - - &__item-thumbnail img, - &__item-thumbnail .still-image img { - object-fit: contain; - } } .chat-messages__divider { diff --git a/app/styles/components/media-gallery.scss b/app/styles/components/media-gallery.scss index b04a776fe..0c71a43dd 100644 --- a/app/styles/components/media-gallery.scss +++ b/app/styles/components/media-gallery.scss @@ -57,33 +57,8 @@ line-height: 0; position: relative; z-index: 1; - - &, - .still-image { - height: 100%; - width: 100%; - - img { - @apply object-cover rounded-lg; - } - } - - .still-image--play-on-hover::before { - content: 'GIF'; - position: absolute; - color: var(--primary-text-color); - background: var(--foreground-color); - bottom: 6px; - left: 6px; - padding: 2px 6px; - border-radius: 2px; - font-size: 11px; - font-weight: 600; - pointer-events: none; - opacity: 0.9; - transition: opacity 0.1s ease; - line-height: 18px; - } + height: 100%; + width: 100%; video { width: 100%; @@ -92,17 +67,6 @@ } } -.status__wrapper { - .media-gallery__item-thumbnail.letterboxed { - &, - .still-image { - img { - object-fit: contain; - } - } - } -} - .media-gallery__preview { @apply bg-gray-200 dark:bg-gray-900 rounded-lg; width: 100%; @@ -113,23 +77,6 @@ left: 0; z-index: 0; - .still-image--play-on-hover::before { - content: 'GIF'; - position: absolute; - color: var(--primary-text-color); - background: var(--foreground-color); - bottom: 6px; - left: 6px; - padding: 2px 6px; - border-radius: 2px; - font-size: 11px; - font-weight: 600; - pointer-events: none; - opacity: 0.9; - transition: opacity 0.1s ease; - line-height: 18px; - } - &--hidden { display: none; } diff --git a/app/styles/components/still-image.scss b/app/styles/components/still-image.scss deleted file mode 100644 index 0b86d6ada..000000000 --- a/app/styles/components/still-image.scss +++ /dev/null @@ -1,28 +0,0 @@ -.still-image { - position: relative; - overflow: hidden; - - img, - canvas { - width: 100%; - height: 100%; - display: block; - object-fit: cover; - font-family: inherit; - } - - &--play-on-hover { - img { - position: absolute; - visibility: hidden; - } - - &:hover img { - visibility: visible; - } - - &:hover canvas { - visibility: hidden; - } - } -} diff --git a/app/styles/emoji-picker.scss b/app/styles/emoji-picker.scss index 90830d576..7408e85ab 100644 --- a/app/styles/emoji-picker.scss +++ b/app/styles/emoji-picker.scss @@ -9,7 +9,7 @@ } .emoji-mart .emoji-mart-emoji { - @apply p-1.5; + @apply p-1.5 align-middle; } .emoji-mart-bar { diff --git a/app/styles/fonts.scss b/app/styles/fonts.scss index fbb21c381..bbd6b751a 100644 --- a/app/styles/fonts.scss +++ b/app/styles/fonts.scss @@ -58,25 +58,3 @@ line-height: #{$px + "px"}; line-height: #{$rem + "rem"}; } - -// Soapbox icon font -@font-face { - font-family: 'soapbox'; - src: url('../assets/fonts/soapbox/soapbox.eot?pryg6i'); - src: url('../assets/fonts/soapbox/soapbox.eot?pryg6i#iefix') format('embedded-opentype'), - url('../assets/fonts/soapbox/soapbox.ttf?pryg6i') format('truetype'), - url('../assets/fonts/soapbox/soapbox.woff?pryg6i') format('woff'), - url('../assets/fonts/soapbox/soapbox.svg?pryg6i#soapbox') format('svg'); - font-weight: normal; - font-style: normal; -} - -.fa-fediverse::before { - font-family: 'soapbox'; - content: "\e901"; -} - -.fa-spinster::before { - font-family: 'soapbox'; - content: "\e900"; -}