From de4f1aaabf5a70af57f2d87b657c1bba9d09daf2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 4 Nov 2022 13:02:23 -0500 Subject: [PATCH 1/3] Notifications: exclude chat messages from "All" filter, improve exclude_types fallback --- app/soapbox/actions/notifications.ts | 6 ++++-- app/soapbox/utils/notification.ts | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/soapbox/actions/notifications.ts b/app/soapbox/actions/notifications.ts index 4edd76c22..db8fd688f 100644 --- a/app/soapbox/actions/notifications.ts +++ b/app/soapbox/actions/notifications.ts @@ -11,7 +11,7 @@ import { getFilters, regexFromFilters } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; import { unescapeHTML } from 'soapbox/utils/html'; -import { NOTIFICATION_TYPES } from 'soapbox/utils/notification'; +import { EXCLUDE_TYPES, NOTIFICATION_TYPES } from 'soapbox/utils/notification'; import { joinPublicPath } from 'soapbox/utils/static'; import { fetchRelationships } from './accounts'; @@ -195,7 +195,9 @@ const expandNotifications = ({ maxId }: Record = {}, done: () => an if (activeFilter === 'all') { if (features.notificationsIncludeTypes) { - params.types = NOTIFICATION_TYPES; + params.types = NOTIFICATION_TYPES.filter(type => !EXCLUDE_TYPES.includes(type as any)); + } else { + params.exclude_types = EXCLUDE_TYPES; } } else { if (features.notificationsIncludeTypes) { diff --git a/app/soapbox/utils/notification.ts b/app/soapbox/utils/notification.ts index 907a97434..49c360b92 100644 --- a/app/soapbox/utils/notification.ts +++ b/app/soapbox/utils/notification.ts @@ -14,6 +14,12 @@ const NOTIFICATION_TYPES = [ 'update', ] as const; +/** Notification types to exclude from the "All" filter by default. */ +const EXCLUDE_TYPES = [ + 'pleroma:chat_mention', + 'chat', // TruthSocial +] as const; + type NotificationType = typeof NOTIFICATION_TYPES[number]; /** Ensure the Notification is a valid, known type. */ @@ -21,6 +27,7 @@ const validType = (type: string): type is NotificationType => NOTIFICATION_TYPES export { NOTIFICATION_TYPES, + EXCLUDE_TYPES, NotificationType, validType, }; From 99bd9f5e8e41947b78c8c3e9d6cab8aea8aa75c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Nov 2022 11:46:15 +0100 Subject: [PATCH 2/3] Add form element on compose area MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/ui/hstack/hstack.tsx | 8 ++++++-- app/soapbox/components/ui/stack/stack.tsx | 16 ++++++++++------ .../features/compose/components/compose-form.tsx | 10 +++++++--- .../compose/components/polls/poll-form.tsx | 4 ++-- .../compose/components/spoiler-input.tsx | 4 ++-- 5 files changed, 27 insertions(+), 15 deletions(-) diff --git a/app/soapbox/components/ui/hstack/hstack.tsx b/app/soapbox/components/ui/hstack/hstack.tsx index a109da608..996320dea 100644 --- a/app/soapbox/components/ui/hstack/hstack.tsx +++ b/app/soapbox/components/ui/hstack/hstack.tsx @@ -40,6 +40,8 @@ interface IHStack { space?: keyof typeof spaces /** Whether to let the flexbox grow. */ grow?: boolean + /** HTML element to use for container. */ + element?: keyof JSX.IntrinsicElements, /** Extra CSS styles for the
*/ style?: React.CSSProperties /** Whether to let the flexbox wrap onto multiple lines. */ @@ -48,10 +50,12 @@ interface IHStack { /** Horizontal row of child elements. */ const HStack = forwardRef((props, ref) => { - const { space, alignItems, grow, justifyContent, wrap, className, ...filteredProps } = props; + const { space, alignItems, justifyContent, className, grow, element = 'div', wrap, ...filteredProps } = props; + + const Elem = element as 'div'; return ( -
{ - /** Size of the gap between elements. */ - space?: keyof typeof spaces /** Horizontal alignment of children. */ alignItems?: 'center' + /** Extra class names on the element. */ + className?: string /** Vertical alignment of children. */ justifyContent?: keyof typeof justifyContentOptions - /** Extra class names on the
element. */ - className?: string + /** Size of the gap between elements. */ + space?: keyof typeof spaces /** Whether to let the flexbox grow. */ grow?: boolean + /** HTML element to use for container. */ + element?: keyof JSX.IntrinsicElements, } /** Vertical stack of child elements. */ const Stack = React.forwardRef((props, ref: React.LegacyRef | undefined) => { - const { space, alignItems, justifyContent, className, grow, ...filteredProps } = props; + const { space, alignItems, justifyContent, className, grow, element = 'div', ...filteredProps } = props; + + const Elem = element as 'div'; return ( -
({ id, shouldCondense, autoFocus, clickab setComposeFocused(true); }; - const handleSubmit = () => { + const handleSubmit = (e?: React.FormEvent) => { if (text !== autosuggestTextareaRef.current?.textarea?.value) { // Something changed the text inside the textarea (e.g. browser extensions like Grammarly) // Update the state to match the current text @@ -142,6 +142,10 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab // Submit disabled: const fulltext = [spoilerText, countableText(text)].join(''); + if (e) { + e.preventDefault(); + } + if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > maxTootChars || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) { return; } @@ -261,7 +265,7 @@ const ComposeForm = ({ id, shouldCondense, autoFocus, clickab } return ( - + {scheduledStatusCount > 0 && ( ({ id, shouldCondense, autoFocus, clickab
)} -
diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index 4daf54048..e61f1975f 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -168,7 +168,7 @@ const PollForm: React.FC = ({ composeId }) => { -
diff --git a/app/soapbox/features/compose/components/spoiler-input.tsx b/app/soapbox/features/compose/components/spoiler-input.tsx index e6f53d04c..873450116 100644 --- a/app/soapbox/features/compose/components/spoiler-input.tsx +++ b/app/soapbox/features/compose/components/spoiler-input.tsx @@ -68,7 +68,7 @@ const SpoilerInput = React.forwardRef(({ />
-
@@ -77,4 +77,4 @@ const SpoilerInput = React.forwardRef(({ ); }); -export default SpoilerInput; \ No newline at end of file +export default SpoilerInput; From 5658ee77a468c76baf70db8a250af4085152ac9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 5 Nov 2022 16:55:23 +0100 Subject: [PATCH 3/3] Ask for confirmation before revoking current session MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../features/auth_token_list/index.tsx | 32 ++++++++++++++++--- .../ui/components/profile-dropdown.tsx | 2 ++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/auth_token_list/index.tsx b/app/soapbox/features/auth_token_list/index.tsx index dec5aedba..c63f20e13 100644 --- a/app/soapbox/features/auth_token_list/index.tsx +++ b/app/soapbox/features/auth_token_list/index.tsx @@ -1,26 +1,45 @@ import React, { useEffect } from 'react'; import { defineMessages, FormattedDate, useIntl } from 'react-intl'; +import { openModal } from 'soapbox/actions/modals'; import { fetchOAuthTokens, revokeOAuthTokenById } from 'soapbox/actions/security'; import { Button, Card, CardBody, CardHeader, CardTitle, Column, Spinner, Stack, Text } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { Token } from 'soapbox/reducers/security'; +import type { Map as ImmutableMap } from 'immutable'; + const messages = defineMessages({ header: { id: 'security.headers.tokens', defaultMessage: 'Sessions' }, revoke: { id: 'security.tokens.revoke', defaultMessage: 'Revoke' }, + revokeSessionHeading: { id: 'confirmations.revoke_session.heading', defaultMessage: 'Revoke current session' }, + revokeSessionMessage: { id: 'confirmations.revoke_session.message', defaultMessage: 'You are about to revoke your current session. You will be signed out.' }, + revokeSessionConfirm: { id: 'confirmations.revoke_session.confirm', defaultMessage: 'Revoke' }, }); interface IAuthToken { token: Token, + isCurrent: boolean, } -const AuthToken: React.FC = ({ token }) => { +const AuthToken: React.FC = ({ token, isCurrent }) => { const dispatch = useAppDispatch(); const intl = useIntl(); const handleRevoke = () => { - dispatch(revokeOAuthTokenById(token.id)); + if (isCurrent) + dispatch(openModal('CONFIRM', { + icon: require('@tabler/icons/alert-triangle.svg'), + heading: intl.formatMessage(messages.revokeSessionHeading), + message: intl.formatMessage(messages.revokeSessionMessage), + confirm: intl.formatMessage(messages.revokeSessionConfirm), + onConfirm: () => { + dispatch(revokeOAuthTokenById(token.id)); + }, + })); + else { + dispatch(revokeOAuthTokenById(token.id)); + } }; return ( @@ -42,7 +61,7 @@ const AuthToken: React.FC = ({ token }) => {
-
@@ -55,6 +74,11 @@ const AuthTokenList: React.FC = () => { const dispatch = useAppDispatch(); const intl = useIntl(); const tokens = useAppSelector(state => state.security.get('tokens').reverse()); + const currentTokenId = useAppSelector(state => { + const currentToken = state.auth.get('tokens').valueSeq().find((token: ImmutableMap) => token.get('me') === state.auth.get('me')); + + return currentToken?.get('id'); + }); useEffect(() => { dispatch(fetchOAuthTokens()); @@ -63,7 +87,7 @@ const AuthTokenList: React.FC = () => { const body = tokens ? (
{tokens.map((token) => ( - + ))}
) : ; diff --git a/app/soapbox/features/ui/components/profile-dropdown.tsx b/app/soapbox/features/ui/components/profile-dropdown.tsx index 0a9a3f297..8830515e4 100644 --- a/app/soapbox/features/ui/components/profile-dropdown.tsx +++ b/app/soapbox/features/ui/components/profile-dropdown.tsx @@ -39,6 +39,8 @@ const ProfileDropdown: React.FC = ({ account, children }) => { const features = useFeatures(); const intl = useIntl(); + useAppSelector((state) => console.log(state.auth.toJS())); + const authUsers = useAppSelector((state) => state.auth.get('users')); const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.get('id'))));