Refactor <HashtagHeader> to TypeScript (#33096)
		
	This commit is contained in:
		
					parent
					
						
							
								a1143c522b
							
						
					
				
			
			
				commit
				
					
						25387dc423
					
				
			
		
					 10 changed files with 238 additions and 237 deletions
				
			
		|  | @ -1,9 +1,5 @@ | |||
| import api, { getLinks } from '../api'; | ||||
| 
 | ||||
| export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST'; | ||||
| export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS'; | ||||
| export const HASHTAG_FETCH_FAIL    = 'HASHTAG_FETCH_FAIL'; | ||||
| 
 | ||||
| export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST'; | ||||
| export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS'; | ||||
| export const FOLLOWED_HASHTAGS_FETCH_FAIL    = 'FOLLOWED_HASHTAGS_FETCH_FAIL'; | ||||
|  | @ -12,39 +8,6 @@ export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUES | |||
| export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS'; | ||||
| export const FOLLOWED_HASHTAGS_EXPAND_FAIL    = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; | ||||
| 
 | ||||
| export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST'; | ||||
| export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS'; | ||||
| export const HASHTAG_FOLLOW_FAIL    = 'HASHTAG_FOLLOW_FAIL'; | ||||
| 
 | ||||
| export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST'; | ||||
| export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS'; | ||||
| export const HASHTAG_UNFOLLOW_FAIL    = 'HASHTAG_UNFOLLOW_FAIL'; | ||||
| 
 | ||||
| export const fetchHashtag = name => (dispatch) => { | ||||
|   dispatch(fetchHashtagRequest()); | ||||
| 
 | ||||
|   api().get(`/api/v1/tags/${name}`).then(({ data }) => { | ||||
|     dispatch(fetchHashtagSuccess(name, data)); | ||||
|   }).catch(err => { | ||||
|     dispatch(fetchHashtagFail(err)); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const fetchHashtagRequest = () => ({ | ||||
|   type: HASHTAG_FETCH_REQUEST, | ||||
| }); | ||||
| 
 | ||||
| export const fetchHashtagSuccess = (name, tag) => ({ | ||||
|   type: HASHTAG_FETCH_SUCCESS, | ||||
|   name, | ||||
|   tag, | ||||
| }); | ||||
| 
 | ||||
| export const fetchHashtagFail = error => ({ | ||||
|   type: HASHTAG_FETCH_FAIL, | ||||
|   error, | ||||
| }); | ||||
| 
 | ||||
| export const fetchFollowedHashtags = () => (dispatch) => { | ||||
|   dispatch(fetchFollowedHashtagsRequest()); | ||||
| 
 | ||||
|  | @ -116,57 +79,3 @@ export function expandFollowedHashtagsFail(error) { | |||
|     error, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export const followHashtag = name => (dispatch) => { | ||||
|   dispatch(followHashtagRequest(name)); | ||||
| 
 | ||||
|   api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => { | ||||
|     dispatch(followHashtagSuccess(name, data)); | ||||
|   }).catch(err => { | ||||
|     dispatch(followHashtagFail(name, err)); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const followHashtagRequest = name => ({ | ||||
|   type: HASHTAG_FOLLOW_REQUEST, | ||||
|   name, | ||||
| }); | ||||
| 
 | ||||
| export const followHashtagSuccess = (name, tag) => ({ | ||||
|   type: HASHTAG_FOLLOW_SUCCESS, | ||||
|   name, | ||||
|   tag, | ||||
| }); | ||||
| 
 | ||||
| export const followHashtagFail = (name, error) => ({ | ||||
|   type: HASHTAG_FOLLOW_FAIL, | ||||
|   name, | ||||
|   error, | ||||
| }); | ||||
| 
 | ||||
| export const unfollowHashtag = name => (dispatch) => { | ||||
|   dispatch(unfollowHashtagRequest(name)); | ||||
| 
 | ||||
|   api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => { | ||||
|     dispatch(unfollowHashtagSuccess(name, data)); | ||||
|   }).catch(err => { | ||||
|     dispatch(unfollowHashtagFail(name, err)); | ||||
|   }); | ||||
| }; | ||||
| 
 | ||||
| export const unfollowHashtagRequest = name => ({ | ||||
|   type: HASHTAG_UNFOLLOW_REQUEST, | ||||
|   name, | ||||
| }); | ||||
| 
 | ||||
| export const unfollowHashtagSuccess = (name, tag) => ({ | ||||
|   type: HASHTAG_UNFOLLOW_SUCCESS, | ||||
|   name, | ||||
|   tag, | ||||
| }); | ||||
| 
 | ||||
| export const unfollowHashtagFail = (name, error) => ({ | ||||
|   type: HASHTAG_UNFOLLOW_FAIL, | ||||
|   name, | ||||
|   error, | ||||
| }); | ||||
|  |  | |||
							
								
								
									
										17
									
								
								app/javascript/mastodon/actions/tags_typed.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/javascript/mastodon/actions/tags_typed.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| import { apiGetTag, apiFollowTag, apiUnfollowTag } from 'mastodon/api/tags'; | ||||
| import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; | ||||
| 
 | ||||
| export const fetchHashtag = createDataLoadingThunk( | ||||
|   'tags/fetch', | ||||
|   ({ tagId }: { tagId: string }) => apiGetTag(tagId), | ||||
| ); | ||||
| 
 | ||||
| export const followHashtag = createDataLoadingThunk( | ||||
|   'tags/follow', | ||||
|   ({ tagId }: { tagId: string }) => apiFollowTag(tagId), | ||||
| ); | ||||
| 
 | ||||
| export const unfollowHashtag = createDataLoadingThunk( | ||||
|   'tags/unfollow', | ||||
|   ({ tagId }: { tagId: string }) => apiUnfollowTag(tagId), | ||||
| ); | ||||
							
								
								
									
										11
									
								
								app/javascript/mastodon/api/tags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/javascript/mastodon/api/tags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| import { apiRequestPost, apiRequestGet } from 'mastodon/api'; | ||||
| import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; | ||||
| 
 | ||||
| export const apiGetTag = (tagId: string) => | ||||
|   apiRequestGet<ApiHashtagJSON>(`v1/tags/${tagId}`); | ||||
| 
 | ||||
| export const apiFollowTag = (tagId: string) => | ||||
|   apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/follow`); | ||||
| 
 | ||||
| export const apiUnfollowTag = (tagId: string) => | ||||
|   apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfollow`); | ||||
							
								
								
									
										13
									
								
								app/javascript/mastodon/api_types/tags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/javascript/mastodon/api_types/tags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| interface ApiHistoryJSON { | ||||
|   day: string; | ||||
|   accounts: string; | ||||
|   uses: string; | ||||
| } | ||||
| 
 | ||||
| export interface ApiHashtagJSON { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   url: string; | ||||
|   history: [ApiHistoryJSON, ...ApiHistoryJSON[]]; | ||||
|   following?: boolean; | ||||
| } | ||||
|  | @ -1,94 +0,0 @@ | |||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| 
 | ||||
| import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; | ||||
| import { Button } from 'mastodon/components/button'; | ||||
| import { ShortNumber } from 'mastodon/components/short_number'; | ||||
| import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; | ||||
| import { withIdentity } from 'mastodon/identity_context'; | ||||
| import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' }, | ||||
|   unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' }, | ||||
|   adminModeration: { id: 'hashtag.admin_moderation', defaultMessage: 'Open moderation interface for #{name}' }, | ||||
| }); | ||||
| 
 | ||||
| const usesRenderer = (displayNumber, pluralReady) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_uses' | ||||
|     defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const peopleRenderer = (displayNumber, pluralReady) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_accounts' | ||||
|     defaultMessage='{count, plural, one {{counter} participant} other {{counter} participants}}' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const usesTodayRenderer = (displayNumber, pluralReady) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_uses_today' | ||||
|     defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}} today' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| export const HashtagHeader = withIdentity(injectIntl(({ tag, intl, disabled, onClick, identity }) => { | ||||
|   if (!tag) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   const { signedIn, permissions } = identity; | ||||
|   const menu = []; | ||||
| 
 | ||||
|   if (signedIn && (permissions & PERMISSION_MANAGE_TAXONOMIES) === PERMISSION_MANAGE_TAXONOMIES ) { | ||||
|     menu.push({ text: intl.formatMessage(messages.adminModeration, { name: tag.get("name") }), href: `/admin/tags/${tag.get('id')}` }); | ||||
|   } | ||||
| 
 | ||||
|   const [uses, people] = tag.get('history').reduce((arr, day) => [arr[0] + day.get('uses') * 1, arr[1] + day.get('accounts') * 1], [0, 0]); | ||||
|   const dividingCircle = <span aria-hidden>{' · '}</span>; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className='hashtag-header'> | ||||
|       <div className='hashtag-header__header'> | ||||
|         <h1>#{tag.get('name')}</h1> | ||||
|         <div className='hashtag-header__header__buttons'> | ||||
|           { menu.length > 0 && <DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' /> } | ||||
|           <Button onClick={onClick} text={intl.formatMessage(tag.get('following') ? messages.unfollowHashtag : messages.followHashtag)} disabled={disabled} /> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div> | ||||
|         <ShortNumber value={uses} renderer={usesRenderer} /> | ||||
|         {dividingCircle} | ||||
|         <ShortNumber value={people} renderer={peopleRenderer} /> | ||||
|         {dividingCircle} | ||||
|         <ShortNumber value={tag.getIn(['history', 0, 'uses']) * 1} renderer={usesTodayRenderer} /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| })); | ||||
| 
 | ||||
| HashtagHeader.propTypes = { | ||||
|   tag: ImmutablePropTypes.map, | ||||
|   disabled: PropTypes.bool, | ||||
|   onClick: PropTypes.func, | ||||
|   intl: PropTypes.object, | ||||
| }; | ||||
|  | @ -0,0 +1,188 @@ | |||
| import { useCallback, useMemo, useState, useEffect } from 'react'; | ||||
| 
 | ||||
| import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| import { isFulfilled } from '@reduxjs/toolkit'; | ||||
| 
 | ||||
| import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; | ||||
| import { | ||||
|   fetchHashtag, | ||||
|   followHashtag, | ||||
|   unfollowHashtag, | ||||
| } from 'mastodon/actions/tags_typed'; | ||||
| import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; | ||||
| import { Button } from 'mastodon/components/button'; | ||||
| import { ShortNumber } from 'mastodon/components/short_number'; | ||||
| import DropdownMenu from 'mastodon/containers/dropdown_menu_container'; | ||||
| import { useIdentity } from 'mastodon/identity_context'; | ||||
| import { PERMISSION_MANAGE_TAXONOMIES } from 'mastodon/permissions'; | ||||
| import { useAppDispatch } from 'mastodon/store'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' }, | ||||
|   unfollowHashtag: { | ||||
|     id: 'hashtag.unfollow', | ||||
|     defaultMessage: 'Unfollow hashtag', | ||||
|   }, | ||||
|   adminModeration: { | ||||
|     id: 'hashtag.admin_moderation', | ||||
|     defaultMessage: 'Open moderation interface for #{name}', | ||||
|   }, | ||||
| }); | ||||
| 
 | ||||
| const usesRenderer = (displayNumber: React.ReactNode, pluralReady: number) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_uses' | ||||
|     defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const peopleRenderer = ( | ||||
|   displayNumber: React.ReactNode, | ||||
|   pluralReady: number, | ||||
| ) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_accounts' | ||||
|     defaultMessage='{count, plural, one {{counter} participant} other {{counter} participants}}' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| const usesTodayRenderer = ( | ||||
|   displayNumber: React.ReactNode, | ||||
|   pluralReady: number, | ||||
| ) => ( | ||||
|   <FormattedMessage | ||||
|     id='hashtag.counter_by_uses_today' | ||||
|     defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}} today' | ||||
|     values={{ | ||||
|       count: pluralReady, | ||||
|       counter: <strong>{displayNumber}</strong>, | ||||
|     }} | ||||
|   /> | ||||
| ); | ||||
| 
 | ||||
| export const HashtagHeader: React.FC<{ | ||||
|   tagId: string; | ||||
| }> = ({ tagId }) => { | ||||
|   const intl = useIntl(); | ||||
|   const { signedIn, permissions } = useIdentity(); | ||||
|   const dispatch = useAppDispatch(); | ||||
|   const [tag, setTag] = useState<ApiHashtagJSON>(); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     void dispatch(fetchHashtag({ tagId })).then((result) => { | ||||
|       if (isFulfilled(result)) { | ||||
|         setTag(result.payload); | ||||
|       } | ||||
| 
 | ||||
|       return ''; | ||||
|     }); | ||||
|   }, [dispatch, tagId, setTag]); | ||||
| 
 | ||||
|   const menu = useMemo(() => { | ||||
|     const tmp = []; | ||||
| 
 | ||||
|     if ( | ||||
|       tag && | ||||
|       signedIn && | ||||
|       (permissions & PERMISSION_MANAGE_TAXONOMIES) === | ||||
|         PERMISSION_MANAGE_TAXONOMIES | ||||
|     ) { | ||||
|       tmp.push({ | ||||
|         text: intl.formatMessage(messages.adminModeration, { name: tag.id }), | ||||
|         href: `/admin/tags/${tag.id}`, | ||||
|       }); | ||||
|     } | ||||
| 
 | ||||
|     return tmp; | ||||
|   }, [signedIn, permissions, intl, tag]); | ||||
| 
 | ||||
|   const handleFollow = useCallback(() => { | ||||
|     if (!signedIn || !tag) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (tag.following) { | ||||
|       setTag((hashtag) => hashtag && { ...hashtag, following: false }); | ||||
| 
 | ||||
|       void dispatch(unfollowHashtag({ tagId })).then((result) => { | ||||
|         if (isFulfilled(result)) { | ||||
|           setTag(result.payload); | ||||
|         } | ||||
| 
 | ||||
|         return ''; | ||||
|       }); | ||||
|     } else { | ||||
|       setTag((hashtag) => hashtag && { ...hashtag, following: true }); | ||||
| 
 | ||||
|       void dispatch(followHashtag({ tagId })).then((result) => { | ||||
|         if (isFulfilled(result)) { | ||||
|           setTag(result.payload); | ||||
|         } | ||||
| 
 | ||||
|         return ''; | ||||
|       }); | ||||
|     } | ||||
|   }, [dispatch, setTag, signedIn, tag, tagId]); | ||||
| 
 | ||||
|   if (!tag) { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   const [uses, people] = tag.history.reduce( | ||||
|     (arr, day) => [ | ||||
|       arr[0] + parseInt(day.uses), | ||||
|       arr[1] + parseInt(day.accounts), | ||||
|     ], | ||||
|     [0, 0], | ||||
|   ); | ||||
|   const dividingCircle = <span aria-hidden>{' · '}</span>; | ||||
| 
 | ||||
|   return ( | ||||
|     <div className='hashtag-header'> | ||||
|       <div className='hashtag-header__header'> | ||||
|         <h1>#{tag.name}</h1> | ||||
| 
 | ||||
|         <div className='hashtag-header__header__buttons'> | ||||
|           {menu.length > 0 && ( | ||||
|             <DropdownMenu | ||||
|               disabled={menu.length === 0} | ||||
|               items={menu} | ||||
|               icon='ellipsis-v' | ||||
|               iconComponent={MoreHorizIcon} | ||||
|               size={24} | ||||
|               direction='right' | ||||
|             /> | ||||
|           )} | ||||
| 
 | ||||
|           <Button | ||||
|             onClick={handleFollow} | ||||
|             text={intl.formatMessage( | ||||
|               tag.following ? messages.unfollowHashtag : messages.followHashtag, | ||||
|             )} | ||||
|             disabled={!signedIn} | ||||
|           /> | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div> | ||||
|         <ShortNumber value={uses} renderer={usesRenderer} /> | ||||
|         {dividingCircle} | ||||
|         <ShortNumber value={people} renderer={peopleRenderer} /> | ||||
|         {dividingCircle} | ||||
|         <ShortNumber | ||||
|           value={parseInt(tag.history[0].uses)} | ||||
|           renderer={usesTodayRenderer} | ||||
|         /> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | @ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl'; | |||
| 
 | ||||
| import { Helmet } from 'react-helmet'; | ||||
| 
 | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { connect } from 'react-redux'; | ||||
| 
 | ||||
| import { isEqual } from 'lodash'; | ||||
|  | @ -13,7 +12,6 @@ import { isEqual } from 'lodash'; | |||
| import TagIcon from '@/material-icons/400-24px/tag.svg?react'; | ||||
| import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns'; | ||||
| import { connectHashtagStream } from 'mastodon/actions/streaming'; | ||||
| import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags'; | ||||
| import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines'; | ||||
| import Column from 'mastodon/components/column'; | ||||
| import ColumnHeader from 'mastodon/components/column_header'; | ||||
|  | @ -26,7 +24,6 @@ import ColumnSettingsContainer from './containers/column_settings_container'; | |||
| 
 | ||||
| const mapStateToProps = (state, props) => ({ | ||||
|   hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0, | ||||
|   tag: state.getIn(['tags', props.params.id]), | ||||
| }); | ||||
| 
 | ||||
| class HashtagTimeline extends PureComponent { | ||||
|  | @ -38,7 +35,6 @@ class HashtagTimeline extends PureComponent { | |||
|     columnId: PropTypes.string, | ||||
|     dispatch: PropTypes.func.isRequired, | ||||
|     hasUnread: PropTypes.bool, | ||||
|     tag: ImmutablePropTypes.map, | ||||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -130,7 +126,6 @@ class HashtagTimeline extends PureComponent { | |||
| 
 | ||||
|     this._subscribe(dispatch, id, tags, local); | ||||
|     dispatch(expandHashtagTimeline(id, { tags, local })); | ||||
|     dispatch(fetchHashtag(id)); | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|  | @ -162,27 +157,10 @@ class HashtagTimeline extends PureComponent { | |||
|     dispatch(expandHashtagTimeline(id, { maxId, tags, local })); | ||||
|   }; | ||||
| 
 | ||||
|   handleFollow = () => { | ||||
|     const { dispatch, params, tag } = this.props; | ||||
|     const { id } = params; | ||||
|     const { signedIn } = this.props.identity; | ||||
| 
 | ||||
|     if (!signedIn) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (tag.get('following')) { | ||||
|       dispatch(unfollowHashtag(id)); | ||||
|     } else { | ||||
|       dispatch(followHashtag(id)); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { hasUnread, columnId, multiColumn, tag } = this.props; | ||||
|     const { hasUnread, columnId, multiColumn } = this.props; | ||||
|     const { id, local } = this.props.params; | ||||
|     const pinned = !!columnId; | ||||
|     const { signedIn } = this.props.identity; | ||||
| 
 | ||||
|     return ( | ||||
|       <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> | ||||
|  | @ -202,7 +180,7 @@ class HashtagTimeline extends PureComponent { | |||
|         </ColumnHeader> | ||||
| 
 | ||||
|         <StatusListContainer | ||||
|           prepend={pinned ? null : <HashtagHeader tag={tag} disabled={!signedIn} onClick={this.handleFollow} />} | ||||
|           prepend={pinned ? null : <HashtagHeader tagId={id} />} | ||||
|           alwaysPrepend | ||||
|           trackScroll={!pinned} | ||||
|           scrollKey={`hashtag_timeline-${columnId}`} | ||||
|  |  | |||
							
								
								
									
										7
									
								
								app/javascript/mastodon/models/tags.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/javascript/mastodon/models/tags.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,7 @@ | |||
| import type { ApiHashtagJSON } from 'mastodon/api_types/tags'; | ||||
| 
 | ||||
| export type Hashtag = ApiHashtagJSON; | ||||
| 
 | ||||
| export const createHashtag = (serverJSON: ApiHashtagJSON): Hashtag => ({ | ||||
|   ...serverJSON, | ||||
| }); | ||||
|  | @ -36,7 +36,6 @@ import settings from './settings'; | |||
| import status_lists from './status_lists'; | ||||
| import statuses from './statuses'; | ||||
| import { suggestionsReducer } from './suggestions'; | ||||
| import tags from './tags'; | ||||
| import timelines from './timelines'; | ||||
| import trends from './trends'; | ||||
| import user_lists from './user_lists'; | ||||
|  | @ -76,7 +75,6 @@ const reducers = { | |||
|   markers: markersReducer, | ||||
|   picture_in_picture: pictureInPictureReducer, | ||||
|   history, | ||||
|   tags, | ||||
|   followed_tags, | ||||
|   notificationPolicy: notificationPolicyReducer, | ||||
|   notificationRequests: notificationRequestsReducer, | ||||
|  |  | |||
|  | @ -1,26 +0,0 @@ | |||
| import { Map as ImmutableMap, fromJS } from 'immutable'; | ||||
| 
 | ||||
| import { | ||||
|   HASHTAG_FETCH_SUCCESS, | ||||
|   HASHTAG_FOLLOW_REQUEST, | ||||
|   HASHTAG_FOLLOW_FAIL, | ||||
|   HASHTAG_UNFOLLOW_REQUEST, | ||||
|   HASHTAG_UNFOLLOW_FAIL, | ||||
| } from 'mastodon/actions/tags'; | ||||
| 
 | ||||
| const initialState = ImmutableMap(); | ||||
| 
 | ||||
| export default function tags(state = initialState, action) { | ||||
|   switch(action.type) { | ||||
|   case HASHTAG_FETCH_SUCCESS: | ||||
|     return state.set(action.name, fromJS(action.tag)); | ||||
|   case HASHTAG_FOLLOW_REQUEST: | ||||
|   case HASHTAG_UNFOLLOW_FAIL: | ||||
|     return state.setIn([action.name, 'following'], true); | ||||
|   case HASHTAG_FOLLOW_FAIL: | ||||
|   case HASHTAG_UNFOLLOW_REQUEST: | ||||
|     return state.setIn([action.name, 'following'], false); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue