From 6326eeb083b3898993a6e599872d764a51dc1b06 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 30 Jun 2023 09:45:11 -0500 Subject: [PATCH 01/14] Fix mentions --- app/soapbox/reducers/notifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/reducers/notifications.ts b/app/soapbox/reducers/notifications.ts index 8462f873f..f2147b2cf 100644 --- a/app/soapbox/reducers/notifications.ts +++ b/app/soapbox/reducers/notifications.ts @@ -93,7 +93,7 @@ const isValid = (notification: APIEntity) => { } // Mastodon can return status notifications with a null status - if (['mention', 'reblog', 'favourite', 'poll', 'status'].includes(notification.type) && !notification.status.id) { + if (['mention', 'reblog', 'favourite', 'poll', 'status'].includes(notification.type) && !notification.getIn(['status', 'id'])) { return false; } From b8c42c9371b6fee078cad057209757d6a7aa98a0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 30 Jun 2023 11:52:37 -0500 Subject: [PATCH 02/14] Fix edit profile Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1451 --- app/soapbox/features/edit-profile/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/edit-profile/index.tsx b/app/soapbox/features/edit-profile/index.tsx index 721879068..eef30bfd3 100644 --- a/app/soapbox/features/edit-profile/index.tsx +++ b/app/soapbox/features/edit-profile/index.tsx @@ -188,7 +188,7 @@ const EditProfile: React.FC = () => { useEffect(() => { if (account) { const credentials = accountToCredentials(account); - const strangerNotifications = account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true; + const strangerNotifications = account.pleroma?.notification_settings?.block_from_strangers === true; setData(credentials); setMuteStrangers(strangerNotifications); } From 6f99396bc4c77e08eb149df39c8b41af8973ed3c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 1 Jul 2023 23:14:27 -0500 Subject: [PATCH 03/14] Fix profile fields panel from not showing up --- app/soapbox/pages/profile-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/pages/profile-page.tsx b/app/soapbox/pages/profile-page.tsx index b4c746473..2274db5c2 100644 --- a/app/soapbox/pages/profile-page.tsx +++ b/app/soapbox/pages/profile-page.tsx @@ -120,7 +120,7 @@ const ProfilePage: React.FC = ({ params, children }) => { {Component => } - {account && !account.fields.length && ( + {account && account.fields.length && ( {Component => } From 174be975c85ec98021ded62a81272dea4a84b873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 2 Jul 2023 13:18:52 +0200 Subject: [PATCH 04/14] Support Mastodon nightly version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/utils/__tests__/features.test.ts | 10 ++++++++++ app/soapbox/utils/features.ts | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/soapbox/utils/__tests__/features.test.ts b/app/soapbox/utils/__tests__/features.test.ts index 3ba9c90ba..f214393d0 100644 --- a/app/soapbox/utils/__tests__/features.test.ts +++ b/app/soapbox/utils/__tests__/features.test.ts @@ -40,6 +40,7 @@ describe('parseVersion', () => { software: 'TruthSocial', version: '1.0.0', compatVersion: '3.4.1', + build: 'nightly-20230627', }); }); @@ -62,6 +63,15 @@ describe('parseVersion', () => { build: 'cofe', }); }); + + it('with Mastodon nightly build', () => { + const version = '4.1.2+nightly-20230627'; + expect(parseVersion(version)).toEqual({ + software: 'Mastodon', + version: '4.1.2', + compatVersion: '4.1.2', + }); + }); }); describe('getFeatures', () => { diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 77ba331cb..126248e5d 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -992,7 +992,7 @@ interface Backend { /** Get information about the software from its version string */ export const parseVersion = (version: string): Backend => { - const regex = /^([\w+.]*)(?: \(compatible; ([\w]*) (.*)\))?$/; + const regex = /^([\w+.-]*)(?: \(compatible; ([\w]*) (.*)\))?$/; const match = regex.exec(version); const semverString = match && (match[3] || match[1]); From 8646aa5572efe3e97844be21bf9bbba09bc5d441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 2 Jul 2023 13:19:27 +0200 Subject: [PATCH 05/14] Add Followed hashtags page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/sidebar-menu.tsx | 10 ++++++++++ .../{followed_tags => followed-tags}/index.tsx | 0 app/soapbox/features/ui/components/link-footer.tsx | 3 +++ app/soapbox/features/ui/index.tsx | 2 ++ app/soapbox/features/ui/util/async-components.ts | 4 ++++ app/soapbox/locales/en.json | 3 +++ .../reducers/{followed_tags.ts => followed-tags.ts} | 0 app/soapbox/reducers/index.ts | 2 +- app/soapbox/utils/__tests__/features.test.ts | 2 +- 9 files changed, 24 insertions(+), 2 deletions(-) rename app/soapbox/features/{followed_tags => followed-tags}/index.tsx (100%) rename app/soapbox/reducers/{followed_tags.ts => followed-tags.ts} (100%) diff --git a/app/soapbox/components/sidebar-menu.tsx b/app/soapbox/components/sidebar-menu.tsx index f77e9f5ed..11786f4ad 100644 --- a/app/soapbox/components/sidebar-menu.tsx +++ b/app/soapbox/components/sidebar-menu.tsx @@ -28,6 +28,7 @@ const messages = defineMessages({ domainBlocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, + followedTags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' }, soapboxConfig: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' }, accountMigration: { id: 'navigation_bar.account_migration', defaultMessage: 'Move account' }, accountAliases: { id: 'navigation_bar.account_aliases', defaultMessage: 'Account aliases' }, @@ -305,6 +306,15 @@ const SidebarMenu: React.FC = (): JSX.Element | null => { /> )} + {features.followedHashtagsList && ( + + )} + {account.admin && ( { {(features.filters || features.filtersV2) && ( )} + {features.followedHashtagsList && ( + + )} {features.federating && ( )} diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 847b29d26..f0cc8c561 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -135,6 +135,7 @@ import { GroupMembershipRequests, Announcements, EditGroup, + FollowedTags, } from './util/async-components'; import { WrappedRoute } from './util/react-router-helpers'; @@ -293,6 +294,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => {(features.filters || features.filtersV2) && } {(features.filters || features.filtersV2) && } {(features.filters || features.filtersV2) && } + {(features.followedHashtagsList) && } diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 0b50f0194..4891185fa 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -637,3 +637,7 @@ export function Announcements() { export function EditAnnouncementModal() { return import(/* webpackChunkName: "features/admin/announcements" */'../components/modals/edit-announcement-modal'); } + +export function FollowedTags() { + return import(/* webpackChunkName: "features/followed-tags" */'../../followed-tags'); +} diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index d863da71e..123d02398 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -350,6 +350,7 @@ "column.filters.title": "Title", "column.filters.whole_word": "Whole word", "column.follow_requests": "Follow requests", + "column.followed_tags": "Followed hashtags", "column.followers": "Followers", "column.following": "Following", "column.group_blocked_members": "Banned Members", @@ -676,6 +677,7 @@ "empty_column.filters": "You haven't created any muted words yet.", "empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.", "empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.", + "empty_column.followed_tags": "You haven't followed any hashtag yet.", "empty_column.group": "There are no posts in this group yet.", "empty_column.group_blocks": "The group hasn't banned any users yet.", "empty_column.group_membership_requests": "There are no pending membership requests for this group.", @@ -1083,6 +1085,7 @@ "navigation_bar.favourites": "Likes", "navigation_bar.filters": "Filters", "navigation_bar.follow_requests": "Follow requests", + "navigation_bar.followed_tags": "Followed hashtags", "navigation_bar.import_data": "Import data", "navigation_bar.in_reply_to": "In reply to", "navigation_bar.invites": "Invites", diff --git a/app/soapbox/reducers/followed_tags.ts b/app/soapbox/reducers/followed-tags.ts similarity index 100% rename from app/soapbox/reducers/followed_tags.ts rename to app/soapbox/reducers/followed-tags.ts diff --git a/app/soapbox/reducers/index.ts b/app/soapbox/reducers/index.ts index e2504a968..a52ea0aa3 100644 --- a/app/soapbox/reducers/index.ts +++ b/app/soapbox/reducers/index.ts @@ -29,7 +29,7 @@ import custom_emojis from './custom-emojis'; import domain_lists from './domain-lists'; import dropdown_menu from './dropdown-menu'; import filters from './filters'; -import followed_tags from './followed_tags'; +import followed_tags from './followed-tags'; import group_memberships from './group-memberships'; import group_relationships from './group-relationships'; import groups from './groups'; diff --git a/app/soapbox/utils/__tests__/features.test.ts b/app/soapbox/utils/__tests__/features.test.ts index f214393d0..da9985b13 100644 --- a/app/soapbox/utils/__tests__/features.test.ts +++ b/app/soapbox/utils/__tests__/features.test.ts @@ -40,7 +40,6 @@ describe('parseVersion', () => { software: 'TruthSocial', version: '1.0.0', compatVersion: '3.4.1', - build: 'nightly-20230627', }); }); @@ -70,6 +69,7 @@ describe('parseVersion', () => { software: 'Mastodon', version: '4.1.2', compatVersion: '4.1.2', + build: 'nightly-20230627', }); }); }); From b0bdb78543552e39fd4c425b037c609ade751b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 2 Jul 2023 12:49:09 +0200 Subject: [PATCH 06/14] Fix hotkey navigation in media modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/status-list.tsx | 13 +- app/soapbox/components/ui/card/card.tsx | 1 + app/soapbox/containers/soapbox.tsx | 186 +++++++++++++-- .../feed-suggestions/feed-suggestions.tsx | 64 +++-- .../features/status/components/thread.tsx | 9 +- app/soapbox/features/ui/index.tsx | 222 ++++-------------- 6 files changed, 265 insertions(+), 230 deletions(-) diff --git a/app/soapbox/components/status-list.tsx b/app/soapbox/components/status-list.tsx index 439803e02..1d4165d22 100644 --- a/app/soapbox/components/status-list.tsx +++ b/app/soapbox/components/status-list.tsx @@ -178,8 +178,15 @@ const StatusList: React.FC = ({ )); }; - const renderFeedSuggestions = (): React.ReactNode => { - return ; + const renderFeedSuggestions = (statusId: string): React.ReactNode => { + return ( + + ); }; const renderStatuses = (): React.ReactNode[] => { @@ -201,7 +208,7 @@ const StatusList: React.FC = ({ } } else if (statusId.startsWith('末suggestions-')) { if (soapboxConfig.feedInjection) { - acc.push(renderFeedSuggestions()); + acc.push(renderFeedSuggestions(statusId)); } } else if (statusId.startsWith('末pending-')) { acc.push(renderPendingStatus(statusId)); diff --git a/app/soapbox/components/ui/card/card.tsx b/app/soapbox/components/ui/card/card.tsx index 4b8d9799d..a57fc9a1a 100644 --- a/app/soapbox/components/ui/card/card.tsx +++ b/app/soapbox/components/ui/card/card.tsx @@ -27,6 +27,7 @@ interface ICard { className?: string /** Elements inside the card. */ children: React.ReactNode + tabIndex?: number } /** An opaque backdrop to hold a collection of related elements. */ diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index aa935624d..64b66656e 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -2,18 +2,21 @@ import { QueryClientProvider } from '@tanstack/react-query'; import clsx from 'clsx'; -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Toaster } from 'react-hot-toast'; +import { HotKeys } from 'react-hotkeys'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; -import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom'; +import { BrowserRouter, Switch, Redirect, Route, useHistory } from 'react-router-dom'; import { CompatRouter } from 'react-router-dom-v5-compat'; // @ts-ignore: it doesn't have types import { ScrollContext } from 'react-router-scroll-4'; +import { resetCompose } from 'soapbox/actions/compose'; import { loadInstance } from 'soapbox/actions/instance'; import { fetchMe } from 'soapbox/actions/me'; +import { openModal } from 'soapbox/actions/modals'; import { loadSoapboxConfig, getSoapboxConfig } from 'soapbox/actions/soapbox'; import { fetchVerificationConfig } from 'soapbox/actions/verification'; import * as BuildConfig from 'soapbox/build-config'; @@ -64,6 +67,34 @@ store.dispatch(preload() as any); // This happens synchronously store.dispatch(checkOnboardingStatus() as any); +const keyMap = { + help: '?', + new: 'n', + search: ['s', '/'], + forceNew: 'option+n', + reply: 'r', + favourite: 'f', + react: 'e', + boost: 'b', + mention: 'm', + open: ['enter', 'o'], + openProfile: 'p', + moveDown: ['down', 'j'], + moveUp: ['up', 'k'], + back: 'backspace', + goToHome: 'g h', + goToNotifications: 'g n', + goToFavourites: 'g f', + goToPinned: 'g p', + goToProfile: 'g u', + goToBlocked: 'g b', + goToMuted: 'g m', + goToRequests: 'g r', + toggleHidden: 'x', + toggleSensitive: 'h', + openMedia: 'a', +}; + /** Load initial data from the backend */ const loadInitial = () => { // @ts-ignore @@ -89,6 +120,10 @@ const loadInitial = () => { const SoapboxMount = () => { useCachedLocationHandler(); + const hotkeys = useRef(null); + + const history = useHistory(); + const dispatch = useAppDispatch(); const me = useAppSelector(state => state.me); const instance = useInstance(); const { account } = useOwnAccount(); @@ -106,6 +141,109 @@ const SoapboxMount = () => { return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey); }; + const handleHotkeyNew = (e?: KeyboardEvent) => { + e?.preventDefault(); + if (!hotkeys.current) return; + + const element = hotkeys.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; + + if (element) { + element.focus(); + } + }; + + const handleHotkeySearch = (e?: KeyboardEvent) => { + e?.preventDefault(); + if (!hotkeys.current) return; + + const element = hotkeys.current.querySelector('input#search') as HTMLInputElement; + + if (element) { + element.focus(); + } + }; + + const handleHotkeyForceNew = (e?: KeyboardEvent) => { + handleHotkeyNew(e); + dispatch(resetCompose()); + }; + + const handleHotkeyBack = () => { + if (window.history && window.history.length === 1) { + history.push('/'); + } else { + history.goBack(); + } + }; + + const setHotkeysRef: React.LegacyRef = (c: any) => { + hotkeys.current = c; + + if (!me || !hotkeys.current) return; + + // @ts-ignore + hotkeys.current.__mousetrap__.stopCallback = (_e, element) => { + return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName); + }; + }; + + const handleHotkeyToggleHelp = () => { + dispatch(openModal('HOTKEYS')); + }; + + const handleHotkeyGoToHome = () => { + history.push('/'); + }; + + const handleHotkeyGoToNotifications = () => { + history.push('/notifications'); + }; + + const handleHotkeyGoToFavourites = () => { + if (!account) return; + history.push(`/@${account.username}/favorites`); + }; + + const handleHotkeyGoToPinned = () => { + if (!account) return; + history.push(`/@${account.username}/pins`); + }; + + const handleHotkeyGoToProfile = () => { + if (!account) return; + history.push(`/@${account.username}`); + }; + + const handleHotkeyGoToBlocked = () => { + history.push('/blocks'); + }; + + const handleHotkeyGoToMuted = () => { + history.push('/mutes'); + }; + + const handleHotkeyGoToRequests = () => { + history.push('/follow_requests'); + }; + + type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void }; + + const handlers: HotkeyHandlers = { + help: handleHotkeyToggleHelp, + new: handleHotkeyNew, + search: handleHotkeySearch, + forceNew: handleHotkeyForceNew, + back: handleHotkeyBack, + goToHome: handleHotkeyGoToHome, + goToNotifications: handleHotkeyGoToNotifications, + goToFavourites: handleHotkeyGoToFavourites, + goToPinned: handleHotkeyGoToPinned, + goToProfile: handleHotkeyGoToProfile, + goToBlocked: handleHotkeyGoToBlocked, + goToMuted: handleHotkeyGoToMuted, + goToRequests: handleHotkeyGoToRequests, + }; + /** Render the onboarding flow. */ const renderOnboarding = () => ( @@ -176,31 +314,33 @@ const SoapboxMount = () => { - - } - /> - + + + } + /> + - - {renderBody()} + + {renderBody()} - - {Component => } - + + {Component => } + - + -
- -
-
-
+
+ +
+
+
+
diff --git a/app/soapbox/features/feed-suggestions/feed-suggestions.tsx b/app/soapbox/features/feed-suggestions/feed-suggestions.tsx index cac53d103..6db977449 100644 --- a/app/soapbox/features/feed-suggestions/feed-suggestions.tsx +++ b/app/soapbox/features/feed-suggestions/feed-suggestions.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { HotKeys } from 'react-hotkeys'; import { defineMessages, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -61,34 +62,59 @@ const SuggestionItem: React.FC = ({ accountId }) => { ); }; -const FeedSuggestions = () => { +interface IFeedSuggesetions { + statusId: string + onMoveUp?: (statusId: string, featured?: boolean) => void + onMoveDown?: (statusId: string, featured?: boolean) => void +} + +const FeedSuggestions: React.FC = ({ statusId, onMoveUp, onMoveDown }) => { const intl = useIntl(); const suggestedProfiles = useAppSelector((state) => state.suggestions.items); const isLoading = useAppSelector((state) => state.suggestions.isLoading); if (!isLoading && suggestedProfiles.size === 0) return null; + const handleHotkeyMoveUp = (e?: KeyboardEvent): void => { + if (onMoveUp) { + onMoveUp(statusId); + } + }; + + const handleHotkeyMoveDown = (e?: KeyboardEvent): void => { + if (onMoveDown) { + onMoveDown(statusId); + } + }; + + const handlers = { + moveUp: handleHotkeyMoveUp, + moveDown: handleHotkeyMoveDown, + }; + return ( - - - + + + + - - {intl.formatMessage(messages.viewAll)} - - - - - - {suggestedProfiles.slice(0, 4).map((suggestedProfile) => ( - - ))} + + {intl.formatMessage(messages.viewAll)} + - - + + + + {suggestedProfiles.slice(0, 4).map((suggestedProfile) => ( + + ))} + + + + ); }; diff --git a/app/soapbox/features/status/components/thread.tsx b/app/soapbox/features/status/components/thread.tsx index 058f02a08..e50ee8615 100644 --- a/app/soapbox/features/status/components/thread.tsx +++ b/app/soapbox/features/status/components/thread.tsx @@ -263,15 +263,12 @@ const Thread = (props: IThread) => { }; const _selectChild = (index: number) => { + if (!useWindowScroll) index = index + 1; scroller.current?.scrollIntoView({ index, behavior: 'smooth', done: () => { - const element = document.querySelector(`#thread [data-index="${index}"] .focusable`); - - if (element) { - element.focus(); - } + node.current?.querySelector(`[data-index="${index}"] .focusable`)?.focus(); }, }); }; @@ -465,4 +462,4 @@ const Thread = (props: IThread) => { ); }; -export default Thread; \ No newline at end of file +export default Thread; diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 847b29d26..187c52c09 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -2,13 +2,11 @@ import clsx from 'clsx'; import React, { useEffect, useRef } from 'react'; -import { HotKeys } from 'react-hotkeys'; import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom'; import { fetchFollowRequests } from 'soapbox/actions/accounts'; import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin'; import { fetchAnnouncements } from 'soapbox/actions/announcements'; -import { resetCompose } from 'soapbox/actions/compose'; import { fetchCustomEmojis } from 'soapbox/actions/custom-emojis'; import { fetchFilters } from 'soapbox/actions/filters'; import { fetchMarker } from 'soapbox/actions/markers'; @@ -154,34 +152,6 @@ const GroupMembershipRequestsSlug = withHoc(GroupMembershipRequests as any, Grou const EmptyPage = HomePage; -const keyMap = { - help: '?', - new: 'n', - search: ['s', '/'], - forceNew: 'option+n', - reply: 'r', - favourite: 'f', - react: 'e', - boost: 'b', - mention: 'm', - open: ['enter', 'o'], - openProfile: 'p', - moveDown: ['down', 'j'], - moveUp: ['up', 'k'], - back: 'backspace', - goToHome: 'g h', - goToNotifications: 'g n', - goToFavourites: 'g f', - goToPinned: 'g p', - goToProfile: 'g u', - goToBlocked: 'g b', - goToMuted: 'g m', - goToRequests: 'g r', - toggleHidden: 'x', - toggleSensitive: 'h', - openMedia: 'a', -}; - interface ISwitchingColumnsArea { children: React.ReactNode } @@ -396,7 +366,6 @@ const UI: React.FC = ({ children }) => { const userStream = useRef(null); const nostrStream = useRef(null); const node = useRef(null); - const hotkeys = useRef(null); const me = useAppSelector(state => state.me); const { account } = useOwnAccount(); @@ -527,91 +496,6 @@ const UI: React.FC = ({ children }) => { } }, [pendingPolicy, !!account]); - const handleHotkeyNew = (e?: KeyboardEvent) => { - e?.preventDefault(); - if (!node.current) return; - - const element = node.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; - - if (element) { - element.focus(); - } - }; - - const handleHotkeySearch = (e?: KeyboardEvent) => { - e?.preventDefault(); - if (!node.current) return; - - const element = node.current.querySelector('input#search') as HTMLInputElement; - - if (element) { - element.focus(); - } - }; - - const handleHotkeyForceNew = (e?: KeyboardEvent) => { - handleHotkeyNew(e); - dispatch(resetCompose()); - }; - - const handleHotkeyBack = () => { - if (window.history && window.history.length === 1) { - history.push('/'); - } else { - history.goBack(); - } - }; - - const setHotkeysRef: React.LegacyRef = (c: any) => { - hotkeys.current = c; - - if (!me || !hotkeys.current) return; - - // @ts-ignore - hotkeys.current.__mousetrap__.stopCallback = (_e, element) => { - return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName); - }; - }; - - const handleHotkeyToggleHelp = () => { - dispatch(openModal('HOTKEYS')); - }; - - const handleHotkeyGoToHome = () => { - history.push('/'); - }; - - const handleHotkeyGoToNotifications = () => { - history.push('/notifications'); - }; - - const handleHotkeyGoToFavourites = () => { - if (!account) return; - history.push(`/@${account.username}/favorites`); - }; - - const handleHotkeyGoToPinned = () => { - if (!account) return; - history.push(`/@${account.username}/pins`); - }; - - const handleHotkeyGoToProfile = () => { - if (!account) return; - history.push(`/@${account.username}`); - }; - - const handleHotkeyGoToBlocked = () => { - history.push('/blocks'); - }; - - const handleHotkeyGoToMuted = () => { - history.push('/mutes'); - }; - - const handleHotkeyGoToRequests = () => { - history.push('/follow_requests'); - }; - const shouldHideFAB = (): boolean => { const path = location.pathname; return Boolean(path.match(/^\/posts\/|^\/search|^\/getting-started|^\/chats/)); @@ -620,85 +504,65 @@ const UI: React.FC = ({ children }) => { // Wait for login to succeed or fail if (me === null) return null; - type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void }; - - const handlers: HotkeyHandlers = { - help: handleHotkeyToggleHelp, - new: handleHotkeyNew, - search: handleHotkeySearch, - forceNew: handleHotkeyForceNew, - back: handleHotkeyBack, - goToHome: handleHotkeyGoToHome, - goToNotifications: handleHotkeyGoToNotifications, - goToFavourites: handleHotkeyGoToFavourites, - goToPinned: handleHotkeyGoToPinned, - goToProfile: handleHotkeyGoToProfile, - goToBlocked: handleHotkeyGoToBlocked, - goToMuted: handleHotkeyGoToMuted, - goToRequests: handleHotkeyGoToRequests, - }; - const style: React.CSSProperties = { pointerEvents: dropdownMenuIsOpen ? 'none' : undefined, }; return ( - -
-
+
+
- + -
- +
+ - - - {!standalone && } - + + + {!standalone && } + - - {children} - - + + {children} + + - {(me && !shouldHideFAB()) && ( -
- -
- )} + {(me && !shouldHideFAB()) && ( +
+ +
+ )} - {me && ( - - {Component => } - - )} - - {me && features.chats && ( - - {Component => ( -
- -
- )} -
- )} - - - + {me && ( + {Component => } + )} - - {Component => } + {me && features.chats && ( + + {Component => ( +
+ +
+ )}
-
+ )} + + + + {Component => } + + + + {Component => } +
- +
); }; From bd4ae72248fcf5ce43b945b98a4c98e6f9f656e4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 2 Jul 2023 14:01:48 -0500 Subject: [PATCH 07/14] Fix "0" in place of ProfileFieldsPanel when no fields are set Fixes https://gitlab.com/soapbox-pub/soapbox/-/issues/1453 --- app/soapbox/pages/profile-page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/pages/profile-page.tsx b/app/soapbox/pages/profile-page.tsx index 2274db5c2..b2351e05b 100644 --- a/app/soapbox/pages/profile-page.tsx +++ b/app/soapbox/pages/profile-page.tsx @@ -120,7 +120,7 @@ const ProfilePage: React.FC = ({ params, children }) => { {Component => } - {account && account.fields.length && ( + {(account && account.fields.length > 0) && ( {Component => } From 702d8a843e16150c20647f85b89dcc4646a4b170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 2 Jul 2023 22:21:05 +0200 Subject: [PATCH 08/14] Show year for older chat messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../features/chats/components/chat-message-list.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/chats/components/chat-message-list.tsx b/app/soapbox/features/chats/components/chat-message-list.tsx index b06c34664..69ad7690b 100644 --- a/app/soapbox/features/chats/components/chat-message-list.tsx +++ b/app/soapbox/features/chats/components/chat-message-list.tsx @@ -109,9 +109,13 @@ const ChatMessageList: React.FC = ({ chat }) => { return []; } + const currentYear = new Date().getFullYear(); + return chatMessages.reduce((acc: any, curr: any, idx: number) => { const lastMessage = formattedChatMessages[idx - 1]; + const messageDate = new Date(curr.created_at); + if (lastMessage) { switch (timeChange(lastMessage, curr)) { case 'today': @@ -123,7 +127,14 @@ const ChatMessageList: React.FC = ({ chat }) => { case 'date': acc.push({ type: 'divider', - text: intl.formatDate(new Date(curr.created_at), { weekday: 'short', hour: 'numeric', minute: '2-digit', month: 'short', day: 'numeric' }), + text: intl.formatDate(messageDate, { + weekday: 'short', + hour: 'numeric', + minute: '2-digit', + month: 'short', + day: 'numeric', + year: messageDate.getFullYear() !== currentYear ? '2-digit' : undefined, + }), }); break; } From 32c8a9426719035290f097ef1739f5ec66ef4a4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sun, 2 Jul 2023 22:39:10 +0200 Subject: [PATCH 09/14] Move Hotkeys to a new component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/containers/soapbox.tsx | 148 +--------------- .../features/ui/util/global-hotkeys.tsx | 159 ++++++++++++++++++ 2 files changed, 164 insertions(+), 143 deletions(-) create mode 100644 app/soapbox/features/ui/util/global-hotkeys.tsx diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 64b66656e..16f977289 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -2,21 +2,17 @@ import { QueryClientProvider } from '@tanstack/react-query'; import clsx from 'clsx'; -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; -import { HotKeys } from 'react-hotkeys'; import { IntlProvider } from 'react-intl'; import { Provider } from 'react-redux'; -import { BrowserRouter, Switch, Redirect, Route, useHistory } from 'react-router-dom'; +import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom'; import { CompatRouter } from 'react-router-dom-v5-compat'; // @ts-ignore: it doesn't have types import { ScrollContext } from 'react-router-scroll-4'; - -import { resetCompose } from 'soapbox/actions/compose'; import { loadInstance } from 'soapbox/actions/instance'; import { fetchMe } from 'soapbox/actions/me'; -import { openModal } from 'soapbox/actions/modals'; import { loadSoapboxConfig, getSoapboxConfig } from 'soapbox/actions/soapbox'; import { fetchVerificationConfig } from 'soapbox/actions/verification'; import * as BuildConfig from 'soapbox/build-config'; @@ -33,6 +29,7 @@ import { OnboardingWizard, WaitlistPage, } from 'soapbox/features/ui/util/async-components'; +import GlobalHotkeys from 'soapbox/features/ui/util/global-hotkeys'; import { createGlobals } from 'soapbox/globals'; import { useAppSelector, @@ -67,34 +64,6 @@ store.dispatch(preload() as any); // This happens synchronously store.dispatch(checkOnboardingStatus() as any); -const keyMap = { - help: '?', - new: 'n', - search: ['s', '/'], - forceNew: 'option+n', - reply: 'r', - favourite: 'f', - react: 'e', - boost: 'b', - mention: 'm', - open: ['enter', 'o'], - openProfile: 'p', - moveDown: ['down', 'j'], - moveUp: ['up', 'k'], - back: 'backspace', - goToHome: 'g h', - goToNotifications: 'g n', - goToFavourites: 'g f', - goToPinned: 'g p', - goToProfile: 'g u', - goToBlocked: 'g b', - goToMuted: 'g m', - goToRequests: 'g r', - toggleHidden: 'x', - toggleSensitive: 'h', - openMedia: 'a', -}; - /** Load initial data from the backend */ const loadInitial = () => { // @ts-ignore @@ -120,10 +89,6 @@ const loadInitial = () => { const SoapboxMount = () => { useCachedLocationHandler(); - const hotkeys = useRef(null); - - const history = useHistory(); - const dispatch = useAppDispatch(); const me = useAppSelector(state => state.me); const instance = useInstance(); const { account } = useOwnAccount(); @@ -141,109 +106,6 @@ const SoapboxMount = () => { return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey); }; - const handleHotkeyNew = (e?: KeyboardEvent) => { - e?.preventDefault(); - if (!hotkeys.current) return; - - const element = hotkeys.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; - - if (element) { - element.focus(); - } - }; - - const handleHotkeySearch = (e?: KeyboardEvent) => { - e?.preventDefault(); - if (!hotkeys.current) return; - - const element = hotkeys.current.querySelector('input#search') as HTMLInputElement; - - if (element) { - element.focus(); - } - }; - - const handleHotkeyForceNew = (e?: KeyboardEvent) => { - handleHotkeyNew(e); - dispatch(resetCompose()); - }; - - const handleHotkeyBack = () => { - if (window.history && window.history.length === 1) { - history.push('/'); - } else { - history.goBack(); - } - }; - - const setHotkeysRef: React.LegacyRef = (c: any) => { - hotkeys.current = c; - - if (!me || !hotkeys.current) return; - - // @ts-ignore - hotkeys.current.__mousetrap__.stopCallback = (_e, element) => { - return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName); - }; - }; - - const handleHotkeyToggleHelp = () => { - dispatch(openModal('HOTKEYS')); - }; - - const handleHotkeyGoToHome = () => { - history.push('/'); - }; - - const handleHotkeyGoToNotifications = () => { - history.push('/notifications'); - }; - - const handleHotkeyGoToFavourites = () => { - if (!account) return; - history.push(`/@${account.username}/favorites`); - }; - - const handleHotkeyGoToPinned = () => { - if (!account) return; - history.push(`/@${account.username}/pins`); - }; - - const handleHotkeyGoToProfile = () => { - if (!account) return; - history.push(`/@${account.username}`); - }; - - const handleHotkeyGoToBlocked = () => { - history.push('/blocks'); - }; - - const handleHotkeyGoToMuted = () => { - history.push('/mutes'); - }; - - const handleHotkeyGoToRequests = () => { - history.push('/follow_requests'); - }; - - type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void }; - - const handlers: HotkeyHandlers = { - help: handleHotkeyToggleHelp, - new: handleHotkeyNew, - search: handleHotkeySearch, - forceNew: handleHotkeyForceNew, - back: handleHotkeyBack, - goToHome: handleHotkeyGoToHome, - goToNotifications: handleHotkeyGoToNotifications, - goToFavourites: handleHotkeyGoToFavourites, - goToPinned: handleHotkeyGoToPinned, - goToProfile: handleHotkeyGoToProfile, - goToBlocked: handleHotkeyGoToBlocked, - goToMuted: handleHotkeyGoToMuted, - goToRequests: handleHotkeyGoToRequests, - }; - /** Render the onboarding flow. */ const renderOnboarding = () => ( @@ -314,7 +176,7 @@ const SoapboxMount = () => { - + {
- + diff --git a/app/soapbox/features/ui/util/global-hotkeys.tsx b/app/soapbox/features/ui/util/global-hotkeys.tsx new file mode 100644 index 000000000..aeb4d42fb --- /dev/null +++ b/app/soapbox/features/ui/util/global-hotkeys.tsx @@ -0,0 +1,159 @@ +import React, { useRef } from 'react'; +import { HotKeys } from 'react-hotkeys'; +import { useHistory } from 'react-router-dom'; + +import { resetCompose } from 'soapbox/actions/compose'; +import { openModal } from 'soapbox/actions/modals'; +import { useAppSelector, useAppDispatch, useOwnAccount } from 'soapbox/hooks'; + +const keyMap = { + help: '?', + new: 'n', + search: ['s', '/'], + forceNew: 'option+n', + reply: 'r', + favourite: 'f', + react: 'e', + boost: 'b', + mention: 'm', + open: ['enter', 'o'], + openProfile: 'p', + moveDown: ['down', 'j'], + moveUp: ['up', 'k'], + back: 'backspace', + goToHome: 'g h', + goToNotifications: 'g n', + goToFavourites: 'g f', + goToPinned: 'g p', + goToProfile: 'g u', + goToBlocked: 'g b', + goToMuted: 'g m', + goToRequests: 'g r', + toggleHidden: 'x', + toggleSensitive: 'h', + openMedia: 'a', +}; + +interface IGlobalHotkeys { + children: React.ReactNode +} + +const GlobalHotkeys: React.FC = ({ children }) => { + const hotkeys = useRef(null); + + const history = useHistory(); + const dispatch = useAppDispatch(); + const me = useAppSelector(state => state.me); + const { account } = useOwnAccount(); + + const handleHotkeyNew = (e?: KeyboardEvent) => { + e?.preventDefault(); + if (!hotkeys.current) return; + + const element = hotkeys.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; + + if (element) { + element.focus(); + } + }; + + const handleHotkeySearch = (e?: KeyboardEvent) => { + e?.preventDefault(); + if (!hotkeys.current) return; + + const element = hotkeys.current.querySelector('input#search') as HTMLInputElement; + + if (element) { + element.focus(); + } + }; + + const handleHotkeyForceNew = (e?: KeyboardEvent) => { + handleHotkeyNew(e); + dispatch(resetCompose()); + }; + + const handleHotkeyBack = () => { + if (window.history && window.history.length === 1) { + history.push('/'); + } else { + history.goBack(); + } + }; + + const setHotkeysRef: React.LegacyRef = (c: any) => { + hotkeys.current = c; + + if (!me || !hotkeys.current) return; + + // @ts-ignore + hotkeys.current.__mousetrap__.stopCallback = (_e, element) => { + return ['TEXTAREA', 'SELECT', 'INPUT', 'EM-EMOJI-PICKER'].includes(element.tagName); + }; + }; + + const handleHotkeyToggleHelp = () => { + dispatch(openModal('HOTKEYS')); + }; + + const handleHotkeyGoToHome = () => { + history.push('/'); + }; + + const handleHotkeyGoToNotifications = () => { + history.push('/notifications'); + }; + + const handleHotkeyGoToFavourites = () => { + if (!account) return; + history.push(`/@${account.username}/favorites`); + }; + + const handleHotkeyGoToPinned = () => { + if (!account) return; + history.push(`/@${account.username}/pins`); + }; + + const handleHotkeyGoToProfile = () => { + if (!account) return; + history.push(`/@${account.username}`); + }; + + const handleHotkeyGoToBlocked = () => { + history.push('/blocks'); + }; + + const handleHotkeyGoToMuted = () => { + history.push('/mutes'); + }; + + const handleHotkeyGoToRequests = () => { + history.push('/follow_requests'); + }; + + type HotkeyHandlers = { [key: string]: (keyEvent?: KeyboardEvent) => void }; + + const handlers: HotkeyHandlers = { + help: handleHotkeyToggleHelp, + new: handleHotkeyNew, + search: handleHotkeySearch, + forceNew: handleHotkeyForceNew, + back: handleHotkeyBack, + goToHome: handleHotkeyGoToHome, + goToNotifications: handleHotkeyGoToNotifications, + goToFavourites: handleHotkeyGoToFavourites, + goToPinned: handleHotkeyGoToPinned, + goToProfile: handleHotkeyGoToProfile, + goToBlocked: handleHotkeyGoToBlocked, + goToMuted: handleHotkeyGoToMuted, + goToRequests: handleHotkeyGoToRequests, + }; + + return ( + + {children} + + ); +}; + +export default GlobalHotkeys; From 55b1f9be673e8aa3b3d14cbaec298e7fcb8635db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 3 Jul 2023 13:39:11 +0200 Subject: [PATCH 10/14] Focus the correct status in media modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/status/components/thread.tsx | 7 +++++-- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/soapbox/features/status/components/thread.tsx b/app/soapbox/features/status/components/thread.tsx index e50ee8615..9a9e5fe8c 100644 --- a/app/soapbox/features/status/components/thread.tsx +++ b/app/soapbox/features/status/components/thread.tsx @@ -122,6 +122,9 @@ const Thread = (props: IThread) => { }; }); + let initialTopMostItemIndex = ancestorsIds.size; + if (!useWindowScroll && initialTopMostItemIndex !== 0) initialTopMostItemIndex = ancestorsIds.size + 1; + const [showMedia, setShowMedia] = useState(status?.visibility === 'self' ? false : defaultMediaVisibility(status, displayMedia)); const node = useRef(null); @@ -407,7 +410,7 @@ const Thread = (props: IThread) => { if (!useWindowScroll) { // Add padding to the top of the Thread (for Media Modal) - children.push(
); + children.push(
); } if (hasAncestors) { @@ -444,7 +447,7 @@ const Thread = (props: IThread) => { hasMore={!!next} onLoadMore={handleLoadMore} placeholderComponent={() => } - initialTopMostItemIndex={ancestorsIds.size} + initialTopMostItemIndex={initialTopMostItemIndex} useWindowScroll={useWindowScroll} itemClassName={itemClassName} className={ diff --git a/package.json b/package.json index 6e5bd9aea..f4e602788 100644 --- a/package.json +++ b/package.json @@ -163,7 +163,7 @@ "react-sticky-box": "^2.0.0", "react-swipeable-views": "^0.14.0", "react-textarea-autosize": "^8.3.4", - "react-virtuoso": "^4.0.8", + "react-virtuoso": "^4.3.11", "redux": "^4.1.1", "redux-immutable": "^4.0.0", "redux-thunk": "^2.2.0", diff --git a/yarn.lock b/yarn.lock index 7c5985dee..4d572b473 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14866,10 +14866,10 @@ react-transition-group@^2.2.1: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-virtuoso@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.0.8.tgz#6543573e5b2da8cd5808bd687655cf24d7930dfe" - integrity sha512-ne9QzKajqwDT13t2nt5uktuFkyBTjRsJCdF06gdwcPVP6lrWt/VE5tkKf2OVtMqfethR8/FHuAYDOLyT5YtddQ== +react-virtuoso@^4.3.11: + version "4.3.11" + resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.3.11.tgz#ab24e707287ef1b4bb5b52f3b14795ba896e9768" + integrity sha512-0YrCvQ5GsIKRcN34GxrzhSJGuMNI+hGxWci5cTVuPQ8QWTEsrKfCyqm7YNBMmV3pu7onG1YVUBo86CyCXdejXg== react@^18.0.0: version "18.2.0" From bf6bf879a02ea3d6c57ad8ea310ee4fd1332d107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 3 Jul 2023 13:53:41 +0200 Subject: [PATCH 11/14] Move GlobalHotkeys back to features/ui for now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/containers/soapbox.tsx | 45 +++++----- app/soapbox/features/ui/index.tsx | 89 ++++++++++--------- .../features/ui/util/global-hotkeys.tsx | 11 +-- 3 files changed, 73 insertions(+), 72 deletions(-) diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 16f977289..ec63d966d 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -29,7 +29,6 @@ import { OnboardingWizard, WaitlistPage, } from 'soapbox/features/ui/util/async-components'; -import GlobalHotkeys from 'soapbox/features/ui/util/global-hotkeys'; import { createGlobals } from 'soapbox/globals'; import { useAppSelector, @@ -176,33 +175,31 @@ const SoapboxMount = () => { - - - } - /> - + + } + /> + - - {renderBody()} + + {renderBody()} - - {Component => } - + + {Component => } + - + -
- -
-
-
-
+
+ +
+ +
diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 040f6c677..ec3231688 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -135,6 +135,7 @@ import { EditGroup, FollowedTags, } from './util/async-components'; +import GlobalHotkeys from './util/global-hotkeys'; import { WrappedRoute } from './util/react-router-helpers'; // Dummy import, to make sure that ends up in the application bundle. @@ -511,60 +512,62 @@ const UI: React.FC = ({ children }) => { }; return ( -
-
+ +
+
- + -
- +
+ - - - {!standalone && } - + + + {!standalone && } + - - {children} - - + + {children} + + - {(me && !shouldHideFAB()) && ( -
- -
- )} + {(me && !shouldHideFAB()) && ( +
+ +
+ )} - {me && ( - + {me && ( + + {Component => } + + )} + + {me && features.chats && ( + + {Component => ( +
+ +
+ )} +
+ )} + + + {Component => } - )} - {me && features.chats && ( - - {Component => ( -
- -
- )} + + {Component => } - )} - - - - {Component => } - - - - {Component => } - +
-
+ ); }; diff --git a/app/soapbox/features/ui/util/global-hotkeys.tsx b/app/soapbox/features/ui/util/global-hotkeys.tsx index aeb4d42fb..e54528a58 100644 --- a/app/soapbox/features/ui/util/global-hotkeys.tsx +++ b/app/soapbox/features/ui/util/global-hotkeys.tsx @@ -36,9 +36,10 @@ const keyMap = { interface IGlobalHotkeys { children: React.ReactNode + node: React.MutableRefObject } -const GlobalHotkeys: React.FC = ({ children }) => { +const GlobalHotkeys: React.FC = ({ children, node }) => { const hotkeys = useRef(null); const history = useHistory(); @@ -48,9 +49,9 @@ const GlobalHotkeys: React.FC = ({ children }) => { const handleHotkeyNew = (e?: KeyboardEvent) => { e?.preventDefault(); - if (!hotkeys.current) return; + if (!node.current) return; - const element = hotkeys.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; + const element = node.current.querySelector('textarea#compose-textarea') as HTMLTextAreaElement; if (element) { element.focus(); @@ -59,9 +60,9 @@ const GlobalHotkeys: React.FC = ({ children }) => { const handleHotkeySearch = (e?: KeyboardEvent) => { e?.preventDefault(); - if (!hotkeys.current) return; + if (!node.current) return; - const element = hotkeys.current.querySelector('input#search') as HTMLInputElement; + const element = node.current.querySelector('input#search') as HTMLInputElement; if (element) { element.focus(); From a9db41de89cdfcfabf469c938c903790b17d5934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 5 Jul 2023 21:39:50 +0200 Subject: [PATCH 12/14] Use AvatarPicker/HeaderPicker on Edit Profile page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../api/hooks/groups/useUpdateGroup.ts | 6 +- app/soapbox/components/still-image.tsx | 2 +- .../components/avatar-picker.tsx} | 18 ++-- .../components/header-picker.tsx} | 26 ++++-- .../components/profile-preview.tsx | 49 ----------- app/soapbox/features/edit-profile/index.tsx | 85 ++++++------------- app/soapbox/features/group/edit-group.tsx | 9 +- .../manage-group-modal/steps/details-step.tsx | 8 +- app/soapbox/hooks/forms/useImageField.ts | 10 ++- app/soapbox/hooks/forms/usePreview.ts | 2 +- 10 files changed, 79 insertions(+), 136 deletions(-) rename app/soapbox/features/{group/components/group-avatar-picker.tsx => edit-profile/components/avatar-picker.tsx} (61%) rename app/soapbox/features/{group/components/group-header-picker.tsx => edit-profile/components/header-picker.tsx} (64%) delete mode 100644 app/soapbox/features/edit-profile/components/profile-preview.tsx diff --git a/app/soapbox/api/hooks/groups/useUpdateGroup.ts b/app/soapbox/api/hooks/groups/useUpdateGroup.ts index bb3a37dc2..eb69670b1 100644 --- a/app/soapbox/api/hooks/groups/useUpdateGroup.ts +++ b/app/soapbox/api/hooks/groups/useUpdateGroup.ts @@ -6,8 +6,8 @@ import { groupSchema } from 'soapbox/schemas'; interface UpdateGroupParams { display_name?: string note?: string - avatar?: File - header?: File + avatar?: File | '' | null + header?: File | '' | null group_visibility?: string discoverable?: boolean tags?: string[] @@ -30,4 +30,4 @@ function useUpdateGroup(groupId: string) { }; } -export { useUpdateGroup }; \ No newline at end of file +export { useUpdateGroup }; diff --git a/app/soapbox/components/still-image.tsx b/app/soapbox/components/still-image.tsx index cdebaf359..4a3caf01e 100644 --- a/app/soapbox/components/still-image.tsx +++ b/app/soapbox/components/still-image.tsx @@ -67,7 +67,7 @@ const StillImage: React.FC = ({ alt, className, src, style, letterb )} diff --git a/app/soapbox/features/group/components/group-avatar-picker.tsx b/app/soapbox/features/edit-profile/components/avatar-picker.tsx similarity index 61% rename from app/soapbox/features/group/components/group-avatar-picker.tsx rename to app/soapbox/features/edit-profile/components/avatar-picker.tsx index b13dfe80e..09a6b0d29 100644 --- a/app/soapbox/features/group/components/group-avatar-picker.tsx +++ b/app/soapbox/features/edit-profile/components/avatar-picker.tsx @@ -1,19 +1,25 @@ import clsx from 'clsx'; import React from 'react'; +import { FormattedMessage } from 'react-intl'; -import Icon from 'soapbox/components/icon'; -import { Avatar, HStack } from 'soapbox/components/ui'; +import { Avatar, Icon, HStack } from 'soapbox/components/ui'; interface IMediaInput { + className?: string src: string | undefined accept: string onChange: React.ChangeEventHandler disabled?: boolean } -const AvatarPicker = React.forwardRef(({ src, onChange, accept, disabled }, ref) => { +const AvatarPicker = React.forwardRef(({ className, src, onChange, accept, disabled }, ref) => { return ( -