1
0
Fork 0
forked from gitea/nas

Add emoji reaction detail status

This commit is contained in:
KMY 2023-02-26 23:44:52 +09:00
parent de951a0ef9
commit a1485f242d
22 changed files with 393 additions and 16 deletions

View file

@ -33,6 +33,10 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
export const EMOJI_REACTIONS_FETCH_REQUEST = 'EMOJI_REACTIONS_FETCH_REQUEST';
export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS';
export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL';
export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL';
@ -427,6 +431,41 @@ export function fetchFavouritesFail(id, error) {
};
}
export function fetchEmojiReactions(id) {
return (dispatch, getState) => {
dispatch(fetchEmojiReactionsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => {
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
dispatch(fetchEmojiReactionsSuccess(id, response.data));
}).catch(error => {
dispatch(fetchEmojiReactionsFail(id, error));
});
};
}
export function fetchEmojiReactionsRequest(id) {
return {
type: EMOJI_REACTIONS_FETCH_REQUEST,
id,
};
}
export function fetchEmojiReactionsSuccess(id, accounts) {
return {
type: EMOJI_REACTIONS_FETCH_SUCCESS,
id,
accounts,
};
}
export function fetchEmojiReactionsFail(id, error) {
return {
type: EMOJI_REACTIONS_FETCH_FAIL,
error,
};
}
export function pin(status) {
return (dispatch, getState) => {
dispatch(pinRequest(status));

View file

@ -39,6 +39,7 @@ class Account extends ImmutablePureComponent {
actionTitle: PropTypes.string,
defaultAction: PropTypes.string,
onActionClick: PropTypes.func,
children: PropTypes.object,
};
static defaultProps = {
@ -70,7 +71,7 @@ class Account extends ImmutablePureComponent {
};
render () {
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size } = this.props;
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, children } = this.props;
if (!account) {
return (
@ -146,6 +147,10 @@ class Account extends ImmutablePureComponent {
<DisplayName account={account} />
</Link>
<div>
{children}
</div>
<div className='account__relationship'>
{buttons}
</div>

View file

@ -0,0 +1,33 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
import emojify from '../features/emoji/emoji';
import classNames from 'classnames';
export default class EmojiView extends React.PureComponent {
static propTypes = {
name: PropTypes.string,
url: PropTypes.string,
staticUrl: PropTypes.string,
};
render () {
const { name, url, staticUrl } = this.props;
let emojiHtml = null;
if (url) {
let customEmojis = {};
customEmojis[`:${name}:`] = { url, static_url: staticUrl };
emojiHtml = emojify(`:${name}:`, customEmojis);
} else {
emojiHtml = emojify(name);
}
return (
<span className='emoji' dangerouslySetInnerHTML={{ __html: emojiHtml }} />
);
}
}

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { injectIntl } from 'react-intl';
import emojify from '../features/emoji/emoji';
import classNames from 'classnames';
import EmojiView from './emoji_view';
class EmojiReactionButton extends React.PureComponent {
@ -32,15 +33,6 @@ class EmojiReactionButton extends React.PureComponent {
render () {
const { name, url, staticUrl, count, me } = this.props;
let emojiHtml = null;
if (url) {
let customEmojis = {};
customEmojis[`:${name}:`] = { url, static_url: staticUrl };
emojiHtml = emojify(`:${name}:`, customEmojis);
} else {
emojiHtml = emojify(name);
}
const classList = {
'emoji-reactions-bar__button': true,
'toggled': me,
@ -48,7 +40,9 @@ class EmojiReactionButton extends React.PureComponent {
return (
<button className={classNames(classList)} type='button' onClick={this.onClick}>
<span className='emoji' dangerouslySetInnerHTML={{ __html: emojiHtml }} />
<span className='emoji'>
<EmojiView name={name} url={url} staticUrl={staticUrl} />
</span>
<span className='count'>{count}</span>
</button>
);

View file

@ -0,0 +1,107 @@
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import ColumnHeader from 'mastodon/components/column_header';
import Icon from 'mastodon/components/icon';
import { fetchEmojiReactions, fetchFavourites } from 'mastodon/actions/interactions';
import LoadingIndicator from 'mastodon/components/loading_indicator';
import ScrollableList from 'mastodon/components/scrollable_list';
import AccountContainer from 'mastodon/containers/account_container';
import Column from 'mastodon/features/ui/components/column';
import { Helmet } from 'react-helmet';
import EmojiView from '../../components/emoji_view';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
});
const mapStateToProps = (state, props) => { return {
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]),
} };
export default @connect(mapStateToProps)
@injectIntl
class EmojiReactions extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
componentWillMount () {
if (!this.props.accountIds) {
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchEmojiReactions(nextProps.params.statusId));
}
}
handleRefresh = () => {
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
};
render () {
const { intl, accountIds, multiColumn } = this.props;
console.dir(accountIds);
if (!accountIds) {
return (
<Column>
<LoadingIndicator />
</Column>
);
}
let groups = {};
for (const emoji_reaction of accountIds) {
const key = emoji_reaction.account.id;
const value = emoji_reaction;
if (!groups[key]) groups[key] = [value];
else groups[key].push(value);
}
console.dir(groups)
const emptyMessage = <FormattedMessage id='empty_column.emoji_reactions' defaultMessage='No one has reacted with emoji this post yet. When someone does, they will show up here.' />;
return (
<Column bindToDocument={!multiColumn}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
extraButton={(
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)}
/>
<ScrollableList
scrollKey='emoji_reactions'
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
>
{Object.keys(groups).map((key, index) =>(
<AccountContainer key={index} id={key} withNote={false}>
<div style={ { 'max-width': '100px' } }>
{groups[key].map((value) => <EmojiView name={value.name} url={value.url} staticUrl={value.static_url} />)}
</div>
</AccountContainer>
))}
</ScrollableList>
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}

View file

@ -49,6 +49,8 @@ class DetailedStatus extends ImmutablePureComponent {
available: PropTypes.bool,
}),
onToggleMediaVisibility: PropTypes.func,
onEmojiReact: PropTypes.func,
onUnEmojiReact: PropTypes.func,
};
state = {
@ -191,7 +193,7 @@ class DetailedStatus extends ImmutablePureComponent {
let emojiReactionsBar = null;
if (status.get('emoji_reactions')) {
const emojiReactions = status.get('emoji_reactions');
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReaction={this.props.onEmojiReaction} OnUnEmojiReaction={this.props.OnUnEmojiReaction} />;
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
}
if (status.get('application')) {

View file

@ -14,6 +14,7 @@ import {
pin,
unpin,
emojiReact,
unEmojiReact,
} from '../../../actions/interactions';
import {
muteStatus,
@ -98,6 +99,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(emojiReact(status, emoji));
},
onUnEmojiReact (status, emoji) {
dispatch(unEmojiReact(status, emoji));
},
onPin (status) {
if (status.get('pinned')) {
dispatch(unpin(status));

View file

@ -24,6 +24,8 @@ import Column from '../ui/components/column';
import {
favourite,
unfavourite,
emojiReact,
unEmojiReact,
bookmark,
unbookmark,
reblog,
@ -254,6 +256,16 @@ class Status extends ImmutablePureComponent {
}
};
handleEmojiReact = (status, emoji) => {
const { dispatch } = this.props;
dispatch(emojiReact(status, emoji));
};
handleUnEmojiReact = (status, emoji) => {
const { dispatch } = this.props;
dispatch(unEmojiReact(status, emoji));
};
handlePin = (status) => {
if (status.get('pinned')) {
this.props.dispatch(unpin(status));
@ -644,6 +656,8 @@ class Status extends ImmutablePureComponent {
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
pictureInPicture={pictureInPicture}
onEmojiReact={this.handleEmojiReact}
onUnEmojiReact={this.handleUnEmojiReact}
/>
<ActionBar
@ -651,6 +665,7 @@ class Status extends ImmutablePureComponent {
status={status}
onReply={this.handleReplyClick}
onFavourite={this.handleFavouriteClick}
onEmojiReact={this.handleEmojiReact}
onReblog={this.handleReblogClick}
onBookmark={this.handleBookmarkClick}
onDelete={this.handleDeleteClick}

View file

@ -36,6 +36,7 @@ import {
Following,
Reblogs,
Favourites,
EmojiReactions,
DirectTimeline,
HashtagTimeline,
Notifications,
@ -206,6 +207,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/@:acct/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/@:acct/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/@:acct/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/@:acct/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
{/* Legacy routes, cannot be easily factored with other routes because they share a param name */}
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
@ -213,6 +215,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/statuses/:statusId/emoji_reactions' component={EmojiReactions} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />

View file

@ -78,6 +78,10 @@ export function Favourites () {
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
}
export function EmojiReactions () {
return import(/* webpackChunkName: "features/favourites" */'../../emoji_reactions');
}
export function FollowRequests () {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
}

View file

@ -26,6 +26,7 @@ import {
import {
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
EMOJI_REACTIONS_FETCH_SUCCESS,
} from '../actions/interactions';
import {
BLOCKS_FETCH_REQUEST,
@ -69,6 +70,7 @@ const initialState = ImmutableMap({
following: initialListState,
reblogged_by: initialListState,
favourited_by: initialListState,
emoji_reactioned_by: initialListState,
follow_requests: initialListState,
blocks: initialListState,
mutes: initialListState,
@ -133,6 +135,10 @@ export default function userLists(state = initialState, action) {
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
case FAVOURITES_FETCH_SUCCESS:
return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
case EMOJI_REACTIONS_FETCH_SUCCESS:
console.log('===================')
console.dir(state);
return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts));
case NOTIFICATIONS_UPDATE:
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
case FOLLOW_REQUESTS_FETCH_SUCCESS: