diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6a4c244..3b2a108d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Posts: letterbox images to 19:6 again. - Status Info: moved context (repost, pinned) to improve UX. +- Posts: remove file icon from empty link previews. ### Fixed - Layout: use accent color for "floating action button" (mobile compose button). @@ -29,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Modals: close modal when navigating to a different page. - Modals: fix "View context" button in media modal. - Posts: let unauthenticated users to translate posts if allowed by backend. +- Chats: fix jumpy scrollbar. ## [3.0.0] - 2022-12-25 diff --git a/app/soapbox/actions/notifications.ts b/app/soapbox/actions/notifications.ts index 6ac655143..972735e54 100644 --- a/app/soapbox/actions/notifications.ts +++ b/app/soapbox/actions/notifications.ts @@ -107,7 +107,10 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record< // Desktop notifications try { - if (showAlert && !filtered) { + // eslint-disable-next-line compat/compat + const isNotificationsEnabled = window.Notification?.permission === 'granted'; + + if (showAlert && !filtered && isNotificationsEnabled) { const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); diff --git a/app/soapbox/components/status-content.tsx b/app/soapbox/components/status-content.tsx index c361c2289..bedef1fa6 100644 --- a/app/soapbox/components/status-content.tsx +++ b/app/soapbox/components/status-content.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import React, { useState, useRef, useEffect, useMemo } from 'react'; +import React, { useState, useRef, useLayoutEffect, useMemo } from 'react'; import { FormattedMessage } from 'react-intl'; import { useHistory } from 'react-router-dom'; @@ -119,7 +119,7 @@ const StatusContent: React.FC = ({ status, onClick, collapsable } }; - useEffect(() => { + useLayoutEffect(() => { maybeSetCollapsed(); maybeSetOnlyEmoji(); updateStatusLinks(); diff --git a/app/soapbox/components/ui/form-group/form-group.tsx b/app/soapbox/components/ui/form-group/form-group.tsx index 7c8abf346..d33c6d2a9 100644 --- a/app/soapbox/components/ui/form-group/form-group.tsx +++ b/app/soapbox/components/ui/form-group/form-group.tsx @@ -29,7 +29,7 @@ const FormGroup: React.FC = (props) => { if (React.isValidElement(inputChildren[0])) { firstChild = React.cloneElement( inputChildren[0], - { id: formFieldId, hasError }, + { id: formFieldId }, ); } const isCheckboxFormGroup = firstChild?.type === Checkbox; diff --git a/app/soapbox/components/ui/input/input.tsx b/app/soapbox/components/ui/input/input.tsx index 34473fc2c..9087943b6 100644 --- a/app/soapbox/components/ui/input/input.tsx +++ b/app/soapbox/components/ui/input/input.tsx @@ -33,8 +33,6 @@ interface IInput extends Pick, 'maxL value?: string | number, /** Change event handler for the input. */ onChange?: (event: React.ChangeEvent) => void, - /** Whether to display the input in red. */ - hasError?: boolean, /** An element to display as prefix to input. Cannot be used with icon. */ prepend?: React.ReactElement, /** An element to display as suffix to input. Cannot be used with password type. */ @@ -48,7 +46,7 @@ const Input = React.forwardRef( (props, ref) => { const intl = useIntl(); - const { type = 'text', icon, className, outerClassName, hasError, append, prepend, theme = 'normal', ...filteredProps } = props; + const { type = 'text', icon, className, outerClassName, append, prepend, theme = 'normal', ...filteredProps } = props; const [revealed, setRevealed] = React.useState(false); @@ -91,7 +89,6 @@ const Input = React.forwardRef( 'rounded-md bg-white dark:bg-gray-900 border-gray-400 dark:border-gray-800': theme === 'normal', 'rounded-full bg-gray-200 border-gray-200 dark:bg-gray-800 dark:border-gray-800 focus:bg-white': theme === 'search', 'pr-7 rtl:pl-7 rtl:pr-3': isPassword || append, - 'text-red-600 border-red-600': hasError, 'pl-8': typeof icon !== 'undefined', 'pl-16': typeof prepend !== 'undefined', }, className)} diff --git a/app/soapbox/features/account-gallery/index.tsx b/app/soapbox/features/account-gallery/index.tsx index b27d1b117..1e8dc77f2 100644 --- a/app/soapbox/features/account-gallery/index.tsx +++ b/app/soapbox/features/account-gallery/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { useParams } from 'react-router-dom'; @@ -66,10 +66,7 @@ const AccountGallery = () => { const hasMore = useAppSelector((state) => state.timelines.get(`account:${accountId}:media`)?.hasMore); const [width, setWidth] = useState(323); - - const handleRef = (c: HTMLDivElement) => { - if (c) setWidth(c.offsetWidth); - }; + const node = useRef(null); const handleScrollToBottom = () => { if (hasMore) { @@ -99,6 +96,12 @@ const AccountGallery = () => { } }; + useLayoutEffect(() => { + if (node.current) { + setWidth(node.current.offsetWidth); + } + }, [node.current]); + useEffect(() => { if (accountId && accountId !== -1) { dispatch(fetchAccount(accountId)); @@ -140,7 +143,7 @@ const AccountGallery = () => { return ( -
+
{attachments.map((attachment, index) => attachment === null ? ( 0 ? (attachments.get(index - 1)?.id || null) : null} onLoadMore={handleLoadMore} /> ) : ( diff --git a/app/soapbox/features/auth-login/components/registration-form.tsx b/app/soapbox/features/auth-login/components/registration-form.tsx index d3836ecc3..cc8cff2d5 100644 --- a/app/soapbox/features/auth-login/components/registration-form.tsx +++ b/app/soapbox/features/auth-login/components/registration-form.tsx @@ -239,7 +239,6 @@ const RegistrationForm: React.FC = ({ inviteToken }) => { pattern='^[a-zA-Z\d_-]+' onChange={onUsernameChange} value={params.get('username', '')} - hasError={usernameUnavailable} required /> diff --git a/app/soapbox/features/chats/components/chat-message-list.tsx b/app/soapbox/features/chats/components/chat-message-list.tsx index 4b5098a7d..cdf032178 100644 --- a/app/soapbox/features/chats/components/chat-message-list.tsx +++ b/app/soapbox/features/chats/components/chat-message-list.tsx @@ -66,6 +66,21 @@ const List: Components['List'] = React.forwardRef((props, ref) => { return
; }); +const Scroller: Components['Scroller'] = React.forwardRef((props, ref) => { + const { style, context, ...rest } = props; + + return ( +
+ ); +}); + interface IChatMessageList { /** Chat the messages are being rendered from. */ chat: IChat, @@ -472,6 +487,7 @@ const ChatMessageList: React.FC = ({ chat }) => { }} components={{ List, + Scroller, Header: () => { if (hasNextPage || isFetchingNextPage) { return ; diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx index 638a7fea1..4af6b0240 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx @@ -238,7 +238,7 @@ const ChatPageMain = () => {
diff --git a/app/soapbox/features/soapbox-config/index.tsx b/app/soapbox/features/soapbox-config/index.tsx index bb96fd6bc..ae224cf70 100644 --- a/app/soapbox/features/soapbox-config/index.tsx +++ b/app/soapbox/features/soapbox-config/index.tsx @@ -39,6 +39,7 @@ const messages = defineMessages({ customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' }, rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Advanced: Edit raw JSON data' }, rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Edit the settings data directly. Changes made directly to the JSON file will override the form fields above. Click "Save" to apply your changes.' }, + rawJSONInvalid: { id: 'soapbox_config.raw_json_invalid', defaultMessage: 'is invalid' }, verifiedCanEditNameLabel: { id: 'soapbox_config.verified_can_edit_name_label', defaultMessage: 'Allow verified users to edit their own display name.' }, displayFqnLabel: { id: 'soapbox_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' }, greentextLabel: { id: 'soapbox_config.greentext_label', defaultMessage: 'Enable greentext support' }, @@ -394,11 +395,13 @@ const SoapboxConfig: React.FC = () => { expanded={jsonEditorExpanded} onToggle={toggleJSONEditor} > - +