diff --git a/app/soapbox/components/status_action_bar.js b/app/soapbox/components/status_action_bar.tsx similarity index 74% rename from app/soapbox/components/status_action_bar.js rename to app/soapbox/components/status_action_bar.tsx index 6f9dfe35d..047544567 100644 --- a/app/soapbox/components/status_action_bar.js +++ b/app/soapbox/components/status_action_bar.tsx @@ -1,10 +1,8 @@ import classNames from 'classnames'; import { List as ImmutableList } from 'immutable'; -import PropTypes from 'prop-types'; import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages, injectIntl, IntlShape } from 'react-intl'; import { connect } from 'react-redux'; import { withRouter } from 'react-router-dom'; @@ -20,12 +18,16 @@ import { isUserTouching } from 'soapbox/is_mobile'; import { isStaff, isAdmin } from 'soapbox/utils/accounts'; import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji_reacts'; import { getFeatures } from 'soapbox/utils/features'; -import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; import { openModal } from '../actions/modals'; import { IconButton, Hoverable } from './ui'; +import type { History } from 'history'; +import type { AnyAction, Dispatch } from 'redux'; +import type { RootState } from 'soapbox/store'; +import type { Status } from 'soapbox/types/entities'; +import type { Features } from 'soapbox/utils/features'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -72,63 +74,70 @@ const messages = defineMessages({ quotePost: { id: 'status.quote', defaultMessage: 'Quote post' }, }); -class StatusActionBar extends ImmutablePureComponent { +interface IStatusActionBar { + status: Status, + onOpenUnauthorizedModal: (modalType?: string) => void, + onOpenReblogsModal: (acct: string, statusId: string) => void, + onReply: (status: Status, history: History) => void, + onFavourite: (status: Status) => void, + onBookmark: (status: Status) => void, + onReblog: (status: Status, e: React.MouseEvent) => void, + onQuote: (status: Status, history: History) => void, + onDelete: (status: Status, history: History, redraft?: boolean) => void, + onDirect: (account: any, history: History) => void, + onChat: (account: any, history: History) => void, + onMention: (account: any, history: History) => void, + onMute: (account: any) => void, + onBlock: (status: Status) => void, + onReport: (status: Status) => void, + onEmbed: (status: Status) => void, + onDeactivateUser: (status: Status) => void, + onDeleteUser: (status: Status) => void, + onToggleStatusSensitivity: (status: Status) => void, + onDeleteStatus: (status: Status) => void, + onMuteConversation: (status: Status) => void, + onPin: (status: Status) => void, + withDismiss: boolean, + withGroupAdmin: boolean, + intl: IntlShape, + me: string | null | false | undefined, + isStaff: boolean, + isAdmin: boolean, + allowedEmoji: ImmutableList, + emojiSelectorFocused: boolean, + handleEmojiSelectorUnfocus: () => void, + features: Features, + history: History, + dispatch: Dispatch, +} - static propTypes = { - status: ImmutablePropTypes.record.isRequired, - onOpenUnauthorizedModal: PropTypes.func.isRequired, - onOpenReblogsModal: PropTypes.func.isRequired, - onReply: PropTypes.func, - onFavourite: PropTypes.func, - onBookmark: PropTypes.func, - onReblog: PropTypes.func, - onQuote: PropTypes.func, - onDelete: PropTypes.func, - onDirect: PropTypes.func, - onChat: PropTypes.func, - onMention: PropTypes.func, - onMute: PropTypes.func, - onBlock: PropTypes.func, - onReport: PropTypes.func, - onEmbed: PropTypes.func, - onDeactivateUser: PropTypes.func, - onDeleteUser: PropTypes.func, - onToggleStatusSensitivity: PropTypes.func, - onDeleteStatus: PropTypes.func, - onMuteConversation: PropTypes.func, - onPin: PropTypes.func, - withDismiss: PropTypes.bool, - withGroupAdmin: PropTypes.bool, - intl: PropTypes.object.isRequired, - me: SoapboxPropTypes.me, - isStaff: PropTypes.bool.isRequired, - isAdmin: PropTypes.bool.isRequired, - allowedEmoji: ImmutablePropTypes.list, - emojiSelectorFocused: PropTypes.bool, - handleEmojiSelectorUnfocus: PropTypes.func.isRequired, - features: PropTypes.object.isRequired, - history: PropTypes.object, - }; +interface IStatusActionBarState { + emojiSelectorVisible: boolean, +} - static defaultProps = { +class StatusActionBar extends ImmutablePureComponent { + + static defaultProps: Partial = { isStaff: false, } + node?: HTMLDivElement = undefined; + state = { emojiSelectorVisible: false, } // Avoid checking props that are functions (and whose equality will always // evaluate to false. See react-immutable-pure-component for usage. + // @ts-ignore: the type checker is wrong. updateOnProps = [ 'status', 'withDismiss', 'emojiSelectorFocused', ] - handleReplyClick = (event) => { + handleReplyClick = () => { const { me, onReply, onOpenUnauthorizedModal, status } = this.props; - event.stopPropagation(); if (me) { onReply(status, this.props.history); @@ -137,9 +146,7 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleShareClick = (e) => { - e.stopPropagation(); - + handleShareClick = () => { navigator.share({ text: this.props.status.get('search_index'), url: this.props.status.get('url'), @@ -148,7 +155,7 @@ class StatusActionBar extends ImmutablePureComponent { }); } - handleLikeButtonHover = e => { + handleLikeButtonHover: React.EventHandler = () => { const { features } = this.props; if (features.emojiReacts && !isUserTouching()) { @@ -156,7 +163,7 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleLikeButtonLeave = e => { + handleLikeButtonLeave: React.EventHandler = () => { const { features } = this.props; if (features.emojiReacts && !isUserTouching()) { @@ -164,10 +171,11 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleLikeButtonClick = e => { + handleLikeButtonClick = () => { const { features } = this.props; - const meEmojiReact = getReactForStatus(this.props.status, this.props.allowedEmoji) || '👍'; + const reactForStatus = getReactForStatus(this.props.status, this.props.allowedEmoji); + const meEmojiReact = typeof reactForStatus === 'string' ? reactForStatus : '👍'; if (features.emojiReacts && isUserTouching()) { if (this.state.emojiSelectorVisible) { @@ -180,23 +188,23 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleReact = emoji => { + handleReact = (emoji: string): void => { const { me, dispatch, onOpenUnauthorizedModal, status } = this.props; if (me) { - dispatch(simpleEmojiReact(status, emoji)); + dispatch(simpleEmojiReact(status, emoji) as any); } else { onOpenUnauthorizedModal('FAVOURITE'); } this.setState({ emojiSelectorVisible: false }); } - handleReactClick = emoji => { - return e => { + handleReactClick = (emoji: string): React.EventHandler => { + return () => { this.handleReact(emoji); }; } - handleFavouriteClick = () => { + handleFavouriteClick: React.EventHandler = () => { const { me, onFavourite, onOpenUnauthorizedModal, status } = this.props; if (me) { onFavourite(status); @@ -205,12 +213,12 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleBookmarkClick = (e) => { + handleBookmarkClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onBookmark(this.props.status); } - handleReblogClick = e => { + handleReblogClick: React.EventHandler = e => { const { me, onReblog, onOpenUnauthorizedModal, status } = this.props; e.stopPropagation(); @@ -221,7 +229,7 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleQuoteClick = (e) => { + handleQuoteClick: React.EventHandler = (e) => { e.stopPropagation(); const { me, onQuote, onOpenUnauthorizedModal, status } = this.props; if (me) { @@ -231,47 +239,47 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleDeleteClick = (e) => { + handleDeleteClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDelete(this.props.status, this.props.history); } - handleRedraftClick = (e) => { + handleRedraftClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDelete(this.props.status, this.props.history, true); } - handlePinClick = (e) => { + handlePinClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onPin(this.props.status); } - handleMentionClick = (e) => { + handleMentionClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onMention(this.props.status.get('account'), this.props.history); } - handleDirectClick = (e) => { + handleDirectClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDirect(this.props.status.get('account'), this.props.history); } - handleChatClick = (e) => { + handleChatClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onChat(this.props.status.get('account'), this.props.history); } - handleMuteClick = (e) => { + handleMuteClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onMute(this.props.status.get('account')); } - handleBlockClick = (e) => { + handleBlockClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onBlock(this.props.status); } - handleOpen = (e) => { + handleOpen: React.EventHandler = (e) => { e.stopPropagation(); this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`); } @@ -280,17 +288,17 @@ class StatusActionBar extends ImmutablePureComponent { this.props.onEmbed(this.props.status); } - handleReport = (e) => { + handleReport: React.EventHandler = (e) => { e.stopPropagation(); this.props.onReport(this.props.status); } - handleConversationMuteClick = (e) => { + handleConversationMuteClick: React.EventHandler = (e) => { e.stopPropagation(); this.props.onMuteConversation(this.props.status); } - handleCopy = (e) => { + handleCopy: React.EventHandler = (e) => { const url = this.props.status.get('url'); const textarea = document.createElement('textarea'); @@ -311,38 +319,38 @@ class StatusActionBar extends ImmutablePureComponent { } } - handleGroupRemoveAccount = (e) => { - const { status } = this.props; + // handleGroupRemoveAccount: React.EventHandler = (e) => { + // const { status } = this.props; + // + // e.stopPropagation(); + // + // this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id'])); + // } + // + // handleGroupRemovePost: React.EventHandler = (e) => { + // const { status } = this.props; + // + // e.stopPropagation(); + // + // this.props.onGroupRemoveStatus(status.getIn(['group', 'id']), status.get('id')); + // } - e.stopPropagation(); - - this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id'])); - } - - handleGroupRemovePost = (e) => { - const { status } = this.props; - - e.stopPropagation(); - - this.props.onGroupRemoveStatus(status.getIn(['group', 'id']), status.get('id')); - } - - handleDeactivateUser = (e) => { + handleDeactivateUser: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDeactivateUser(this.props.status); } - handleDeleteUser = (e) => { + handleDeleteUser: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDeleteUser(this.props.status); } - handleDeleteStatus = (e) => { + handleDeleteStatus: React.EventHandler = (e) => { e.stopPropagation(); this.props.onDeleteStatus(this.props.status); } - handleToggleStatusSensitivity = (e) => { + handleToggleStatusSensitivity: React.EventHandler = (e) => { e.stopPropagation(); this.props.onToggleStatusSensitivity(this.props.status); } @@ -351,13 +359,14 @@ class StatusActionBar extends ImmutablePureComponent { const { me, status, onOpenUnauthorizedModal, onOpenReblogsModal } = this.props; if (!me) onOpenUnauthorizedModal(); - else onOpenReblogsModal(status.getIn(['account', 'acct']), status.get('id')); + else onOpenReblogsModal(String(status.getIn(['account', 'acct'])), status.id); } - _makeMenu = (publicStatus) => { - const { status, intl, withDismiss, withGroupAdmin, me, features, isStaff, isAdmin } = this.props; + _makeMenu = (publicStatus: boolean) => { + const { status, intl, withDismiss, me, features, isStaff, isAdmin } = this.props; const mutingConversation = status.get('muted'); const ownAccount = status.getIn(['account', 'id']) === me; + const username = String(status.getIn(['account', 'username'])); const menu = []; @@ -434,20 +443,20 @@ class StatusActionBar extends ImmutablePureComponent { }); } else { menu.push({ - text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.mention, { name: username }), action: this.handleMentionClick, icon: require('@tabler/icons/icons/at.svg'), }); // if (status.getIn(['account', 'pleroma', 'accepts_chat_messages'], false) === true) { // menu.push({ - // text: intl.formatMessage(messages.chat, { name: status.getIn(['account', 'username']) }), + // text: intl.formatMessage(messages.chat, { name: username }), // action: this.handleChatClick, // icon: require('@tabler/icons/icons/messages.svg'), // }); // } else { // menu.push({ - // text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), + // text: intl.formatMessage(messages.direct, { name: username }), // action: this.handleDirectClick, // icon: require('@tabler/icons/icons/mail.svg'), // }); @@ -455,17 +464,17 @@ class StatusActionBar extends ImmutablePureComponent { menu.push(null); menu.push({ - text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.mute, { name: username }), action: this.handleMuteClick, icon: require('@tabler/icons/icons/circle-x.svg'), }); menu.push({ - text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.block, { name: username }), action: this.handleBlockClick, icon: require('@tabler/icons/icons/ban.svg'), }); menu.push({ - text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.report, { name: username }), action: this.handleReport, icon: require('@tabler/icons/icons/flag.svg'), }); @@ -476,16 +485,16 @@ class StatusActionBar extends ImmutablePureComponent { if (isAdmin) { menu.push({ - text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.admin_account, { name: username }), href: `/pleroma/admin/#/users/${status.getIn(['account', 'id'])}/`, icon: require('@tabler/icons/icons/gavel.svg'), - action: (event) => event.stopPropagation(), + action: (event: Event) => event.stopPropagation(), }); menu.push({ text: intl.formatMessage(messages.admin_status), href: `/pleroma/admin/#/statuses/${status.get('id')}/`, icon: require('@tabler/icons/icons/pencil.svg'), - action: (event) => event.stopPropagation(), + action: (event: Event) => event.stopPropagation(), }); } @@ -497,12 +506,12 @@ class StatusActionBar extends ImmutablePureComponent { if (!ownAccount) { menu.push({ - text: intl.formatMessage(messages.deactivateUser, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.deactivateUser, { name: username }), action: this.handleDeactivateUser, icon: require('@tabler/icons/icons/user-off.svg'), }); menu.push({ - text: intl.formatMessage(messages.deleteUser, { name: status.getIn(['account', 'username']) }), + text: intl.formatMessage(messages.deleteUser, { name: username }), action: this.handleDeleteUser, icon: require('@tabler/icons/icons/user-minus.svg'), destructive: true, @@ -516,32 +525,32 @@ class StatusActionBar extends ImmutablePureComponent { } } - if (!ownAccount && withGroupAdmin) { - menu.push(null); - menu.push({ - text: intl.formatMessage(messages.group_remove_account), - action: this.handleGroupRemoveAccount, - icon: require('@tabler/icons/icons/user-x.svg'), - destructive: true, - }); - menu.push({ - text: intl.formatMessage(messages.group_remove_post), - action: this.handleGroupRemovePost, - icon: require('@tabler/icons/icons/trash.svg'), - destructive: true, - }); - } + // if (!ownAccount && withGroupAdmin) { + // menu.push(null); + // menu.push({ + // text: intl.formatMessage(messages.group_remove_account), + // action: this.handleGroupRemoveAccount, + // icon: require('@tabler/icons/icons/user-x.svg'), + // destructive: true, + // }); + // menu.push({ + // text: intl.formatMessage(messages.group_remove_post), + // action: this.handleGroupRemovePost, + // icon: require('@tabler/icons/icons/trash.svg'), + // destructive: true, + // }); + // } return menu; } - setRef = c => { + setRef = (c: HTMLDivElement) => { this.node = c; } componentDidMount() { - document.addEventListener('click', e => { - if (this.node && !this.node.contains(e.target)) + document.addEventListener('click', (e) => { + if (this.node && !this.node.contains(e.target as Node)) this.setState({ emojiSelectorVisible: false }); }); } @@ -555,9 +564,9 @@ class StatusActionBar extends ImmutablePureComponent { const reblogCount = status.get('reblogs_count'); const favouriteCount = status.get('favourites_count'); const emojiReactCount = reduceEmoji( - status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()), + (status.getIn(['pleroma', 'emoji_reactions']) || ImmutableList()) as ImmutableList, favouriteCount, - status.get('favourited'), + status.favourited, allowedEmoji, ).reduce((acc, cur) => acc + cur.get('count'), 0); const meEmojiReact = getReactForStatus(status, allowedEmoji); @@ -599,6 +608,7 @@ class StatusActionBar extends ImmutablePureComponent { reblogButton = ( + /> as any )} > - + ); @@ -716,10 +733,9 @@ class StatusActionBar extends ImmutablePureComponent { } -const mapStateToProps = state => { - const me = state.get('me'); - const account = state.getIn(['accounts', me]); - const instance = state.get('instance'); +const mapStateToProps = (state: RootState) => { + const { me, instance } = state; + const account = state.accounts.get(me); return { me, @@ -729,15 +745,15 @@ const mapStateToProps = state => { }; }; -const mapDispatchToProps = (dispatch, { status }) => ({ +const mapDispatchToProps = (dispatch: Dispatch, { status }: { status: Status}) => ({ dispatch, - onOpenUnauthorizedModal(action) { + onOpenUnauthorizedModal(action: AnyAction) { dispatch(openModal('UNAUTHORIZED', { action, - ap_id: status.get('url'), + ap_id: status.url, })); }, - onOpenReblogsModal(username, statusId) { + onOpenReblogsModal(username: string, statusId: string) { dispatch(openModal('REBLOGS', { username, statusId, @@ -745,6 +761,9 @@ const mapDispatchToProps = (dispatch, { status }) => ({ }, }); +// @ts-ignore export default withRouter(injectIntl( + // @ts-ignore connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true }, + // @ts-ignore )(StatusActionBar))); diff --git a/app/soapbox/components/ui/status/status-action-button.tsx b/app/soapbox/components/ui/status/status-action-button.tsx index 350e84851..210829a97 100644 --- a/app/soapbox/components/ui/status/status-action-button.tsx +++ b/app/soapbox/components/ui/status/status-action-button.tsx @@ -32,7 +32,7 @@ const StatusActionCounter: React.FC = ({ to = '#', onClick interface IStatusActionButton { icon: string, onClick: () => void, - count: number, + count?: number, active?: boolean, title?: string, to?: string, diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index 869b9de61..cd3316a16 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -211,13 +211,13 @@ export default function statuses(state = initialState, action: AnyAction): State return state .updateIn( [action.status.get('id'), 'pleroma', 'emoji_reactions'], - emojiReacts => simulateEmojiReact(emojiReacts, action.emoji), + emojiReacts => simulateEmojiReact(emojiReacts as any, action.emoji), ); case UNEMOJI_REACT_REQUEST: return state .updateIn( [action.status.get('id'), 'pleroma', 'emoji_reactions'], - emojiReacts => simulateUnEmojiReact(emojiReacts, action.emoji), + emojiReacts => simulateUnEmojiReact(emojiReacts as any, action.emoji), ); case FAVOURITE_FAIL: return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false); diff --git a/app/soapbox/types/soapbox.ts b/app/soapbox/types/soapbox.ts index 386734d3b..32c2f681c 100644 --- a/app/soapbox/types/soapbox.ts +++ b/app/soapbox/types/soapbox.ts @@ -5,12 +5,15 @@ import { SoapboxConfigRecord, } from 'soapbox/normalizers/soapbox/soapbox_config'; +type Me = string | null | false | undefined; + type PromoPanelItem = ReturnType; type FooterItem = ReturnType; type CryptoAddress = ReturnType; type SoapboxConfig = ReturnType; export { + Me, PromoPanelItem, FooterItem, CryptoAddress, diff --git a/app/soapbox/utils/accounts.ts b/app/soapbox/utils/accounts.ts index 3536cc966..aec0159e7 100644 --- a/app/soapbox/utils/accounts.ts +++ b/app/soapbox/utils/accounts.ts @@ -29,15 +29,15 @@ export const getAcct = (account: Account, displayFqn: boolean): string => ( displayFqn === true ? account.fqn : account.acct ); -export const isStaff = (account: ImmutableMap = ImmutableMap()): boolean => ( +export const isStaff = (account: Account): boolean => ( [isAdmin, isModerator].some(f => f(account) === true) ); -export const isAdmin = (account: ImmutableMap): boolean => ( +export const isAdmin = (account: Account): boolean => ( account.getIn(['pleroma', 'is_admin']) === true ); -export const isModerator = (account: ImmutableMap): boolean => ( +export const isModerator = (account: Account): boolean => ( account.getIn(['pleroma', 'is_moderator']) === true ); diff --git a/app/soapbox/utils/emoji_reacts.js b/app/soapbox/utils/emoji_reacts.ts similarity index 57% rename from app/soapbox/utils/emoji_reacts.js rename to app/soapbox/utils/emoji_reacts.ts index 788c9c867..3b3fc05d9 100644 --- a/app/soapbox/utils/emoji_reacts.js +++ b/app/soapbox/utils/emoji_reacts.ts @@ -3,31 +3,36 @@ import { List as ImmutableList, } from 'immutable'; +import type { Me } from 'soapbox/types/soapbox'; + // https://emojipedia.org/facebook // I've customized them. -export const ALLOWED_EMOJI = [ +export const ALLOWED_EMOJI = ImmutableList([ '👍', '❤️', '😆', '😮', '😢', '😩', -]; +]); -export const sortEmoji = emojiReacts => ( +type Account = ImmutableMap; +type EmojiReact = ImmutableMap; + +export const sortEmoji = (emojiReacts: ImmutableList): ImmutableList => ( emojiReacts.sortBy(emojiReact => -emojiReact.get('count')) ); -export const mergeEmoji = emojiReacts => ( +export const mergeEmoji = (emojiReacts: ImmutableList): ImmutableList => ( emojiReacts // TODO: Merge similar emoji ); -export const mergeEmojiFavourites = (emojiReacts = ImmutableList(), favouritesCount, favourited) => { +export const mergeEmojiFavourites = (emojiReacts = ImmutableList(), favouritesCount: number, favourited: boolean) => { if (!favouritesCount) return emojiReacts; const likeIndex = emojiReacts.findIndex(emojiReact => emojiReact.get('name') === '👍'); if (likeIndex > -1) { - const likeCount = emojiReacts.getIn([likeIndex, 'count']); - favourited = favourited || emojiReacts.getIn([likeIndex, 'me'], false); + const likeCount = Number(emojiReacts.getIn([likeIndex, 'count'])); + favourited = favourited || Boolean(emojiReacts.getIn([likeIndex, 'me'], false)); return emojiReacts .setIn([likeIndex, 'count'], likeCount + favouritesCount) .setIn([likeIndex, 'me'], favourited); @@ -36,24 +41,24 @@ export const mergeEmojiFavourites = (emojiReacts = ImmutableList(), favouritesCo } }; -const hasMultiReactions = (emojiReacts, account) => ( +const hasMultiReactions = (emojiReacts: ImmutableList, account: Account): boolean => ( emojiReacts.filter( e => e.get('accounts').filter( - a => a.get('id') === account.get('id'), + (a: Account) => a.get('id') === account.get('id'), ).count() > 0, ).count() > 1 ); -const inAccounts = (accounts, id) => ( +const inAccounts = (accounts: ImmutableList, id: string): boolean => ( accounts.filter(a => a.get('id') === id).count() > 0 ); -export const oneEmojiPerAccount = (emojiReacts, me) => { +export const oneEmojiPerAccount = (emojiReacts: ImmutableList, me: Me) => { emojiReacts = emojiReacts.reverse(); return emojiReacts.reduce((acc, cur, idx) => { const accounts = cur.get('accounts', ImmutableList()) - .filter(a => !hasMultiReactions(acc, a)); + .filter((a: Account) => !hasMultiReactions(acc, a)); return acc.set(idx, cur.merge({ accounts: accounts, @@ -65,30 +70,31 @@ export const oneEmojiPerAccount = (emojiReacts, me) => { .reverse(); }; -export const filterEmoji = (emojiReacts, allowedEmoji=ALLOWED_EMOJI) => ( +export const filterEmoji = (emojiReacts: ImmutableList, allowedEmoji=ALLOWED_EMOJI): ImmutableList => ( emojiReacts.filter(emojiReact => ( allowedEmoji.includes(emojiReact.get('name')) ))); -export const reduceEmoji = (emojiReacts, favouritesCount, favourited, allowedEmoji=ALLOWED_EMOJI) => ( +export const reduceEmoji = (emojiReacts: ImmutableList, favouritesCount: number, favourited: boolean, allowedEmoji=ALLOWED_EMOJI): ImmutableList => ( filterEmoji(sortEmoji(mergeEmoji(mergeEmojiFavourites( emojiReacts, favouritesCount, favourited, ))), allowedEmoji)); -export const getReactForStatus = (status, allowedEmoji=ALLOWED_EMOJI) => { - return reduceEmoji( +export const getReactForStatus = (status: any, allowedEmoji=ALLOWED_EMOJI): string => { + return String(reduceEmoji( status.getIn(['pleroma', 'emoji_reactions'], ImmutableList()), status.get('favourites_count', 0), status.get('favourited'), allowedEmoji, ).filter(e => e.get('me') === true) - .getIn([0, 'name']); + .getIn([0, 'name'], '')); }; -export const simulateEmojiReact = (emojiReacts, emoji) => { +export const simulateEmojiReact = (emojiReacts: ImmutableList, emoji: string) => { const idx = emojiReacts.findIndex(e => e.get('name') === emoji); - if (idx > -1) { - const emojiReact = emojiReacts.get(idx); + const emojiReact = emojiReacts.get(idx); + + if (emojiReact) { return emojiReacts.set(idx, emojiReact.merge({ count: emojiReact.get('count') + 1, me: true, @@ -102,12 +108,13 @@ export const simulateEmojiReact = (emojiReacts, emoji) => { } }; -export const simulateUnEmojiReact = (emojiReacts, emoji) => { +export const simulateUnEmojiReact = (emojiReacts: ImmutableList, emoji: string) => { const idx = emojiReacts.findIndex(e => e.get('name') === emoji && e.get('me') === true); - if (idx > -1) { - const emojiReact = emojiReacts.get(idx); + const emojiReact = emojiReacts.get(idx); + + if (emojiReact) { const newCount = emojiReact.get('count') - 1; if (newCount < 1) return emojiReacts.delete(idx); return emojiReacts.set(idx, emojiReact.merge({