diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx index 18222a44ffd..6322dd965e1 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx @@ -12,6 +12,7 @@ import { Toggle } from '@/mastodon/components/form_fields/toggle_field'; import { injectIntl } from '@/mastodon/components/intl'; import SettingToggle from '../../notifications/components/setting_toggle'; +import { isRedesignEnabled } from '@/mastodon/utils/environment'; const messages = defineMessages({ placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' }, @@ -108,6 +109,16 @@ class ColumnSettings extends PureComponent { render () { const { settings, onChange } = this.props; + if (isRedesignEnabled()) { + return ( +
+ {this.modeSelect('any')} + {this.modeSelect('all')} + {this.modeSelect('none')} +
+ ) + } + return (
@@ -115,7 +126,7 @@ class ColumnSettings extends PureComponent { } />
- + diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings_modal.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings_modal.tsx new file mode 100644 index 00000000000..246d7e2b08e --- /dev/null +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings_modal.tsx @@ -0,0 +1,48 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { closeModal } from '@/mastodon/actions/modal'; +import { Button } from '@/mastodon/components/button/redesign'; +import { + ModalActions, + ModalShell, + ModalTitle, +} from '@/mastodon/components/modal_shell/redesign'; +import { useAppDispatch } from '@/mastodon/store'; + +import ColumnSettingsContainer from '../containers/column_settings_container'; + +const HashtagSettingsModal: React.FC<{ columnId: string; tagId: string }> = ({ + columnId, + tagId, +}) => { + const dispatch = useAppDispatch(); + + const handleCloseModal = useCallback(() => { + void dispatch( + closeModal({ modalType: 'HASHTAG_SETTINGS', ignoreFocus: false }), + ); + }, [dispatch]); + + return ( + + + + + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default HashtagSettingsModal; diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_column_menu.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_column_menu.tsx new file mode 100644 index 00000000000..12807911834 --- /dev/null +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_column_menu.tsx @@ -0,0 +1,114 @@ +import { useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { changeColumnParams } from '@/mastodon/actions/columns'; +import { openModal } from '@/mastodon/actions/modal'; +import { ColumnSettingsMenu } from '@/mastodon/components/column_header'; +import { MultiColumnMenuItems } from '@/mastodon/components/column_header/multicolumn_settings'; +import { + MenuItem, + MenuItemCheckbox, + MenuItemDivider, +} from '@/mastodon/components/menu'; +import type { MenuItemCheckboxChangeHandler } from '@/mastodon/components/menu/items'; +import { useIdentity } from '@/mastodon/identity_context'; +import { useAppDispatch } from 'mastodon/store'; + +import { useColumnSettings } from '../../public_timeline/components/feed_column_settings'; + +import { useHashtag, messages } from './hashtag_header'; + +export const HashtagColumnMenu: React.FC<{ + tagId: string; + multiColumn?: boolean; + columnId?: string; + onPin: () => void; + onMove: (dir: number) => void; +}> = ({ tagId, multiColumn, columnId, onPin, onMove }) => { + const dispatch = useAppDispatch(); + const { signedIn } = useIdentity(); + + const { tag, toggleFollow, toggleFeature } = useHashtag(tagId); + + const columnSettings = useColumnSettings(columnId); + const isLocalOnly = columnSettings.get('local') as boolean; + const toggleIsLocalOnly = useCallback( + ({ checked }) => { + dispatch(changeColumnParams(columnId, ['local'], checked)); + }, + [columnId, dispatch], + ); + + const openAdvancedSettings = useCallback(() => { + dispatch( + openModal({ + modalType: 'HASHTAG_SETTINGS', + modalProps: { columnId, tagId }, + }), + ); + }, [columnId, dispatch, tagId]); + + if (!tag || !signedIn) { + return null; + } + + const pinned = !!columnId; + + return ( + + } + > + + {tag.following ? ( + + ) : ( + + )} + + + {tag.featuring ? ( + + ) : ( + + )} + + {multiColumn && pinned && ( + <> + + + + + + + + + )} + {multiColumn && ( + + )} + + ); +}; diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx index fafe186cb41..a99f2ccbc0f 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/hashtag_header.tsx @@ -20,7 +20,7 @@ import { useIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions'; import { useAppDispatch } from 'mastodon/store'; -const messages = defineMessages({ +export const messages = defineMessages({ followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' }, unfollowHashtag: { id: 'hashtag.unfollow', @@ -76,11 +76,7 @@ const usesTodayRenderer = ( /> ); -export const HashtagHeader: React.FC<{ - tagId: string; -}> = ({ tagId }) => { - const intl = useIntl(); - const { signedIn, permissions } = useIdentity(); +export function useHashtag(tagId: string) { const dispatch = useAppDispatch(); const [tag, setTag] = useState(); @@ -94,54 +90,32 @@ export const HashtagHeader: React.FC<{ }); }, [dispatch, tagId, setTag]); - const menu = useMemo(() => { - const arr = []; - - if (tag && signedIn) { - const handleFeature = () => { - if (tag.featuring) { - void dispatch(unfeatureHashtag({ tagId })).then((result) => { - if (isFulfilled(result)) { - setTag(result.payload); - } - - return ''; - }); - } else { - void dispatch(featureHashtag({ tagId })).then((result) => { - if (isFulfilled(result)) { - setTag(result.payload); - } - - return ''; - }); - } - }; - - arr.push({ - text: intl.formatMessage( - tag.featuring ? messages.unfeature : messages.feature, - ), - action: handleFeature, - }); - - arr.push(null); - - if ( - (permissions & PERMISSION_MANAGE_TAXONOMIES) === - PERMISSION_MANAGE_TAXONOMIES - ) { - arr.push({ - text: intl.formatMessage(messages.adminModeration, { name: tagId }), - href: `/admin/tags/${tag.id}`, - }); - } + const toggleFeature = useCallback(() => { + if (!tag) { + return; } + if (tag.featuring) { + void dispatch(unfeatureHashtag({ tagId })).then((result) => { + if (isFulfilled(result)) { + setTag(result.payload); + } - return arr; - }, [setTag, dispatch, tagId, signedIn, permissions, intl, tag]); + return ''; + }); + } else { + void dispatch(featureHashtag({ tagId })).then((result) => { + if (isFulfilled(result)) { + setTag(result.payload); + } - const handleFollow = useCallback(() => { + return ''; + }); + } + }, [dispatch, tag, tagId]); + + const { signedIn } = useIdentity(); + + const toggleFollow = useCallback(() => { if (!signedIn || !tag) { return; } @@ -167,7 +141,44 @@ export const HashtagHeader: React.FC<{ return ''; }); } - }, [dispatch, setTag, signedIn, tag, tagId]); + }, [dispatch, signedIn, tag, tagId]); + + return { tag, toggleFollow, toggleFeature }; +} + +export const HashtagHeader: React.FC<{ + tagId: string; +}> = ({ tagId }) => { + const intl = useIntl(); + const { signedIn, permissions } = useIdentity(); + const { tag, toggleFeature, toggleFollow } = useHashtag(tagId); + + const menu = useMemo(() => { + const arr = []; + + if (tag && signedIn) { + arr.push({ + text: intl.formatMessage( + tag.featuring ? messages.unfeature : messages.feature, + ), + action: toggleFeature, + }); + + arr.push(null); + + if ( + (permissions & PERMISSION_MANAGE_TAXONOMIES) === + PERMISSION_MANAGE_TAXONOMIES + ) { + arr.push({ + text: intl.formatMessage(messages.adminModeration, { name: tagId }), + href: `/admin/tags/${tag.id}`, + }); + } + } + + return arr; + }, [tag, signedIn, intl, toggleFeature, permissions, tagId]); if (!tag) { return null; @@ -199,7 +210,7 @@ export const HashtagHeader: React.FC<{ {signedIn && (