From 6769b2d68caf53530cb752d7f28ef1233e54c2f7 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 May 2020 21:42:23 -0500 Subject: [PATCH 01/38] Implement emojireact actions --- app/gabsocial/actions/emoji_reacts.js | 148 ++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 app/gabsocial/actions/emoji_reacts.js diff --git a/app/gabsocial/actions/emoji_reacts.js b/app/gabsocial/actions/emoji_reacts.js new file mode 100644 index 000000000..0b61d1eb4 --- /dev/null +++ b/app/gabsocial/actions/emoji_reacts.js @@ -0,0 +1,148 @@ +import api from '../api'; +import { importFetchedAccounts, importFetchedStatus } from './importer'; + +export const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST'; +export const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS'; +export const EMOJI_REACT_FAIL = 'EMOJI_REACT_FAIL'; + +export const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST'; +export const UNEMOJI_REACT_SUCCESS = 'UNEMOJI_REACT_SUCCESS'; +export const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL'; + +export const EMOJI_REACTS_FETCH_REQUEST = 'EMOJI_REACTS_FETCH_REQUEST'; +export const EMOJI_REACTS_FETCH_SUCCESS = 'EMOJI_REACTS_FETCH_SUCCESS'; +export const EMOJI_REACTS_FETCH_FAIL = 'EMOJI_REACTS_FETCH_FAIL'; + +export function fetchEmojiReacts(id, emoji) { + return (dispatch, getState) => { + if (!getState().get('me')) return; + + dispatch(fetchEmojiReactsRequest(id, emoji)); + + const url = emoji + ? `/api/v1/pleroma/statuses/${id}/reactions/${emoji}` + : `/api/v1/pleroma/statuses/${id}/reactions`; + + api(getState).get(url).then(response => { + response.data.forEach(emojiReact => { + dispatch(importFetchedAccounts(emojiReact.accounts)); + }); + dispatch(fetchEmojiReactsSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchEmojiReactsFail(id, error)); + }); + }; +}; + +export function emojiReact(status, emoji) { + return function(dispatch, getState) { + if (!getState().get('me')) return; + + dispatch(emojiReactRequest(status, emoji)); + + api(getState) + .put(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`) + .then(function(response) { + dispatch(importFetchedStatus(response.data)); + dispatch(emojiReactSuccess(status, emoji)); + }).catch(function(error) { + dispatch(emojiReactFail(status, emoji, error)); + }); + }; +}; + +export function unEmojiReact(status, emoji) { + return (dispatch, getState) => { + if (!getState().get('me')) return; + + dispatch(unEmojiReactRequest(status, emoji)); + + api(getState) + .delete(`/api/v1/pleroma/statuses/${status.get('id')}/reactions/${emoji}`) + .then(response => { + dispatch(importFetchedStatus(response.data)); + dispatch(unEmojiReactSuccess(status, emoji)); + }).catch(error => { + dispatch(unEmojiReactFail(status, emoji, error)); + }); + }; +}; + +export function fetchEmojiReactsRequest(id, emoji) { + return { + type: EMOJI_REACTS_FETCH_REQUEST, + id, + emoji, + }; +}; + +export function fetchEmojiReactsSuccess(id, emojiReacts) { + return { + type: EMOJI_REACTS_FETCH_SUCCESS, + id, + emojiReacts, + }; +}; + +export function fetchEmojiReactsFail(id, error) { + return { + type: EMOJI_REACTS_FETCH_FAIL, + error, + }; +}; + +export function emojiReactRequest(status, emoji) { + return { + type: EMOJI_REACT_REQUEST, + status, + emoji, + skipLoading: true, + }; +}; + +export function emojiReactSuccess(status, emoji) { + return { + type: EMOJI_REACT_SUCCESS, + status, + emoji, + skipLoading: true, + }; +}; + +export function emojiReactFail(status, emoji, error) { + return { + type: EMOJI_REACT_FAIL, + status, + emoji, + error, + skipLoading: true, + }; +}; + +export function unEmojiReactRequest(status, emoji) { + return { + type: UNEMOJI_REACT_REQUEST, + status, + emoji, + skipLoading: true, + }; +}; + +export function unEmojiReactSuccess(status, emoji) { + return { + type: UNEMOJI_REACT_SUCCESS, + status, + emoji, + skipLoading: true, + }; +}; + +export function unEmojiReactFail(status, emoji, error) { + return { + type: UNEMOJI_REACT_FAIL, + status, + emoji, + error, + skipLoading: true, + }; +}; From 1b85b8e3f1b2a0277e330ce6383aa7dd53cb1517 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 May 2020 22:54:05 -0500 Subject: [PATCH 02/38] Add StatusInteractionBar --- .../components/status_interaction_bar.js | 47 +++++++++++++++++++ app/gabsocial/features/status/index.js | 3 ++ 2 files changed, 50 insertions(+) create mode 100644 app/gabsocial/features/status/components/status_interaction_bar.js diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js new file mode 100644 index 000000000..9ce614253 --- /dev/null +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -0,0 +1,47 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import emojify from 'gabsocial/features/emoji/emoji'; + +// https://emojipedia.org/facebook/ +const ALLOWED_EMOJI = [ + '๐Ÿ‘', + 'โค๏ธ', + '๐Ÿ˜‚', + '๐Ÿ˜ฏ', + '๐Ÿ˜ข', + '๐Ÿ˜ก', +]; + +export class StatusInteractionBar extends React.Component { + + propTypes = { + status: ImmutablePropTypes.map, + } + + sortEmoji = emojiReacts => ( + emojiReacts // TODO: Sort + ); + + mergeEmoji = emojiReacts => ( + emojiReacts // TODO: Merge similar emoji + ); + + filterEmoji = emojiReacts => ( + emojiReacts.filter(emojiReact => ( + ALLOWED_EMOJI.includes(emojiReact.get('name')) + ))) + + render() { + const { status } = this.props; + const emojiReacts = status.getIn(['pleroma', 'emoji_reactions']); + + return ( + <> + {this.filterEmoji(emojiReacts).map(e => + + )} + + ); + } + +} diff --git a/app/gabsocial/features/status/index.js b/app/gabsocial/features/status/index.js index 4b9fc2436..424dda420 100644 --- a/app/gabsocial/features/status/index.js +++ b/app/gabsocial/features/status/index.js @@ -43,6 +43,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import Icon from 'gabsocial/components/icon'; import { getSettings } from 'gabsocial/actions/settings'; +import { StatusInteractionBar } from './components/status_interaction_bar'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -492,6 +493,8 @@ class Status extends ImmutablePureComponent { onToggleMediaVisibility={this.handleToggleMediaVisibility} /> + + Date: Tue, 19 May 2020 23:08:19 -0500 Subject: [PATCH 03/38] Add count to emoji reacts --- .../features/status/components/status_interaction_bar.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js index 9ce614253..c250f57d1 100644 --- a/app/gabsocial/features/status/components/status_interaction_bar.js +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -37,9 +37,12 @@ export class StatusInteractionBar extends React.Component { return ( <> - {this.filterEmoji(emojiReacts).map(e => - - )} + {this.filterEmoji(emojiReacts).map(e => ( + + + {e.get('count')} + + ))} ); } From 11c9f153e1e1a96c591addbbd8dfee3234e5020f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 May 2020 23:25:49 -0500 Subject: [PATCH 04/38] Display favourite count as likes --- .../status/components/status_interaction_bar.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js index c250f57d1..6859601e4 100644 --- a/app/gabsocial/features/status/components/status_interaction_bar.js +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -19,7 +19,7 @@ export class StatusInteractionBar extends React.Component { } sortEmoji = emojiReacts => ( - emojiReacts // TODO: Sort + emojiReacts // TODO: Sort by count ); mergeEmoji = emojiReacts => ( @@ -34,13 +34,24 @@ export class StatusInteractionBar extends React.Component { render() { const { status } = this.props; const emojiReacts = status.getIn(['pleroma', 'emoji_reactions']); + const likeCount = status.get('favourites_count'); return ( <> + {likeCount > 0 && + + {likeCount} + } {this.filterEmoji(emojiReacts).map(e => ( - - {e.get('count')} + + {e.get('count')} ))} From d562a25c78afb75db7442e8ef50f4fc27626dcdf Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 11:46:49 -0500 Subject: [PATCH 05/38] Add emojiReact utils with tests --- .../components/status_interaction_bar.js | 41 +------ .../utils/__tests__/emoji_reacts-test.js | 110 ++++++++++++++++++ app/gabsocial/utils/emoji_reacts.js | 40 +++++++ 3 files changed, 156 insertions(+), 35 deletions(-) create mode 100644 app/gabsocial/utils/__tests__/emoji_reacts-test.js create mode 100644 app/gabsocial/utils/emoji_reacts.js diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js index 6859601e4..bb276dd84 100644 --- a/app/gabsocial/features/status/components/status_interaction_bar.js +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -1,16 +1,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import emojify from 'gabsocial/features/emoji/emoji'; - -// https://emojipedia.org/facebook/ -const ALLOWED_EMOJI = [ - '๐Ÿ‘', - 'โค๏ธ', - '๐Ÿ˜‚', - '๐Ÿ˜ฏ', - '๐Ÿ˜ข', - '๐Ÿ˜ก', -]; +import { reduceEmoji } from 'gabsocial/utils/emoji_reacts'; export class StatusInteractionBar extends React.Component { @@ -18,35 +9,15 @@ export class StatusInteractionBar extends React.Component { status: ImmutablePropTypes.map, } - sortEmoji = emojiReacts => ( - emojiReacts // TODO: Sort by count - ); - - mergeEmoji = emojiReacts => ( - emojiReacts // TODO: Merge similar emoji - ); - - filterEmoji = emojiReacts => ( - emojiReacts.filter(emojiReact => ( - ALLOWED_EMOJI.includes(emojiReact.get('name')) - ))) - render() { const { status } = this.props; const emojiReacts = status.getIn(['pleroma', 'emoji_reactions']); - const likeCount = status.get('favourites_count'); + const favouritesCount = status.get('favourites_count'); return ( - <> - {likeCount > 0 && - - {likeCount} - } - {this.filterEmoji(emojiReacts).map(e => ( - +
+ {reduceEmoji(emojiReacts, favouritesCount).map((e, i) => ( + {e.get('count')} ))} - +
); } diff --git a/app/gabsocial/utils/__tests__/emoji_reacts-test.js b/app/gabsocial/utils/__tests__/emoji_reacts-test.js new file mode 100644 index 000000000..f0577c582 --- /dev/null +++ b/app/gabsocial/utils/__tests__/emoji_reacts-test.js @@ -0,0 +1,110 @@ +import { + sortEmoji, + mergeEmojiFavourites, + filterEmoji, + reduceEmoji, +} from '../emoji_reacts'; +import { fromJS } from 'immutable'; + +describe('filterEmoji', () => { + describe('with a mix of allowed and disallowed emoji', () => { + const emojiReacts = fromJS([ + { 'count': 1, 'me': true, 'name': '๐ŸŒต' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜‚' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ‘€' }, + { 'count': 1, 'me': true, 'name': '๐Ÿฉ' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ก' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ”ช' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ ' }, + ]); + it('filters only allowed emoji', () => { + expect(filterEmoji(emojiReacts)).toEqual(fromJS([ + { 'count': 1, 'me': true, 'name': '๐Ÿ˜‚' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ก' }, + ])); + }); + }); +}); + +describe('sortEmoji', () => { + describe('with an unsorted list of emoji', () => { + const emojiReacts = fromJS([ + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + { 'count': 3, 'me': true, 'name': '๐Ÿ˜ข' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ก' }, + { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜‚' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + ]); + it('sorts the emoji by count', () => { + expect(sortEmoji(emojiReacts)).toEqual(fromJS([ + { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜‚' }, + { 'count': 3, 'me': true, 'name': '๐Ÿ˜ข' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ก' }, + ])); + }); + }); +}); + +describe('mergeEmojiFavourites', () => { + const favouritesCount = 12; + + describe('with existing ๐Ÿ‘ reacts', () => { + const emojiReacts = fromJS([ + { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + ]); + it('combines ๐Ÿ‘ reacts with favourites', () => { + expect(mergeEmojiFavourites(emojiReacts, favouritesCount)).toEqual(fromJS([ + { 'count': 32, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + ])); + }); + }); + + describe('without existing ๐Ÿ‘ reacts', () => { + const emojiReacts = fromJS([ + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + ]); + it('adds ๐Ÿ‘ reacts to the map equaling favourite count', () => { + expect(mergeEmojiFavourites(emojiReacts, favouritesCount)).toEqual(fromJS([ + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + { 'count': 12, 'me': false, 'name': '๐Ÿ‘' }, + ])); + }); + }); +}); + +describe('reduceEmoji', () => { + describe('with a clusterfuck of emoji', () => { + const emojiReacts = fromJS([ + { 'count': 1, 'me': false, 'name': '๐Ÿ˜ก' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ”ช' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + { 'count': 3, 'me': false, 'name': '๐Ÿ˜ข' }, + { 'count': 1, 'me': true, 'name': '๐ŸŒต' }, + { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 7, 'me': false, 'name': '๐Ÿ˜‚' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 1, 'me': false, 'name': '๐Ÿ‘€' }, + { 'count': 1, 'me': false, 'name': '๐Ÿฉ' }, + ]); + it('sorts, filters, and combines emoji and favourites', () => { + expect(reduceEmoji(emojiReacts, 7)).toEqual(fromJS([ + { 'count': 27, 'me': true, 'name': '๐Ÿ‘' }, + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + { 'count': 7, 'me': false, 'name': '๐Ÿ˜‚' }, + { 'count': 3, 'me': false, 'name': '๐Ÿ˜ข' }, + { 'count': 1, 'me': false, 'name': '๐Ÿ˜ก' }, + ])); + }); + }); +}); diff --git a/app/gabsocial/utils/emoji_reacts.js b/app/gabsocial/utils/emoji_reacts.js new file mode 100644 index 000000000..b2426b9a2 --- /dev/null +++ b/app/gabsocial/utils/emoji_reacts.js @@ -0,0 +1,40 @@ +import { Map as ImmutableMap } from 'immutable'; + +// https://emojipedia.org/facebook/ +export const ALLOWED_EMOJI = [ + '๐Ÿ‘', + 'โค๏ธ', + '๐Ÿ˜‚', + '๐Ÿ˜ฏ', + '๐Ÿ˜ข', + '๐Ÿ˜ก', +]; + +export const sortEmoji = emojiReacts => ( + emojiReacts.sortBy(emojiReact => -emojiReact.get('count')) +); + +export const mergeEmoji = emojiReacts => ( + emojiReacts // TODO: Merge similar emoji +); + +export const mergeEmojiFavourites = (emojiReacts, favouritesCount) => { + const likeIndex = emojiReacts.findIndex(emojiReact => + emojiReact.get('name') === '๐Ÿ‘'); + if (likeIndex > -1) { + const likeCount = emojiReacts.getIn([likeIndex, 'count']); + return emojiReacts.setIn([likeIndex, 'count'], likeCount + favouritesCount); + } else { + return emojiReacts.push(ImmutableMap({ count: favouritesCount, me: false, name: '๐Ÿ‘' })); + } +}; + +export const filterEmoji = emojiReacts => ( + emojiReacts.filter(emojiReact => ( + ALLOWED_EMOJI.includes(emojiReact.get('name')) + ))); + +export const reduceEmoji = (emojiReacts, favouritesCount) => ( + sortEmoji(filterEmoji(mergeEmoji(mergeEmojiFavourites( + emojiReacts, favouritesCount + ))))); From 3547fd093afd60e3c5db15c1ae65b876fee7f085 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 11:55:30 -0500 Subject: [PATCH 06/38] Don't add like reacts when there are no favourites --- app/gabsocial/utils/__tests__/emoji_reacts-test.js | 6 ++++++ app/gabsocial/utils/emoji_reacts.js | 1 + 2 files changed, 7 insertions(+) diff --git a/app/gabsocial/utils/__tests__/emoji_reacts-test.js b/app/gabsocial/utils/__tests__/emoji_reacts-test.js index f0577c582..6acb5ec09 100644 --- a/app/gabsocial/utils/__tests__/emoji_reacts-test.js +++ b/app/gabsocial/utils/__tests__/emoji_reacts-test.js @@ -79,6 +79,12 @@ describe('mergeEmojiFavourites', () => { { 'count': 12, 'me': false, 'name': '๐Ÿ‘' }, ])); }); + it('does not add ๐Ÿ‘ reacts when there are no favourites', () => { + expect(mergeEmojiFavourites(emojiReacts, 0)).toEqual(fromJS([ + { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, + ])); + }); }); }); diff --git a/app/gabsocial/utils/emoji_reacts.js b/app/gabsocial/utils/emoji_reacts.js index b2426b9a2..bb45da7d4 100644 --- a/app/gabsocial/utils/emoji_reacts.js +++ b/app/gabsocial/utils/emoji_reacts.js @@ -19,6 +19,7 @@ export const mergeEmoji = emojiReacts => ( ); export const mergeEmojiFavourites = (emojiReacts, favouritesCount) => { + if (!favouritesCount) return emojiReacts; const likeIndex = emojiReacts.findIndex(emojiReact => emojiReact.get('name') === '๐Ÿ‘'); if (likeIndex > -1) { From c8f89ce8c1542d0dd9c503e7429a78c0b436a79e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 13:11:44 -0500 Subject: [PATCH 07/38] Style the StatusInteractionBar --- .../components/status_interaction_bar.js | 37 +++++++++----- app/styles/application.scss | 1 + .../gabsocial/components/emoji-reacts.scss | 49 +++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) create mode 100644 app/styles/gabsocial/components/emoji-reacts.scss diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js index bb276dd84..9b1cb4c3b 100644 --- a/app/gabsocial/features/status/components/status_interaction_bar.js +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -9,22 +9,37 @@ export class StatusInteractionBar extends React.Component { status: ImmutablePropTypes.map, } - render() { + getNormalizedReacts = () => { const { status } = this.props; const emojiReacts = status.getIn(['pleroma', 'emoji_reactions']); const favouritesCount = status.get('favourites_count'); + return reduceEmoji(emojiReacts, favouritesCount).reverse(); + } + + render() { + const emojiReacts = this.getNormalizedReacts(); + const count = emojiReacts.reduce((acc, cur) => ( + acc + cur.get('count') + ), 0); + + if (count < 1) return null; return ( -
- {reduceEmoji(emojiReacts, favouritesCount).map((e, i) => ( - - - {e.get('count')} - - ))} +
+
+ {emojiReacts.map((e, i) => ( + + + {e.get('count')} + + ))} +
+
+ {count} +
); } diff --git a/app/styles/application.scss b/app/styles/application.scss index a57193533..3b267a4bd 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -32,6 +32,7 @@ @import 'gabsocial/components/group-sidebar-panel'; @import 'gabsocial/components/sidebar-menu'; @import 'gabsocial/components/hotkeys-modal'; +@import 'gabsocial/components/emoji-reacts'; @import 'gabsocial/polls'; @import 'gabsocial/introduction'; diff --git a/app/styles/gabsocial/components/emoji-reacts.scss b/app/styles/gabsocial/components/emoji-reacts.scss new file mode 100644 index 000000000..71a5c18e1 --- /dev/null +++ b/app/styles/gabsocial/components/emoji-reacts.scss @@ -0,0 +1,49 @@ +.emoji-react { + display: inline-block; + transition: 0.1s; + + &__emoji { + img { + width: 20px; + height: 20px; + filter: drop-shadow(2px 0 0 #fff); // FIXME: Use theme color + } + } + + &__count { + display: none; + } + + + .emoji-react { + margin-right: -8px; + } +} + +.emoji-reacts { + display: inline-flex; + flex-direction: row-reverse; +} + +.emoji-reacts-container { + display: inline-flex; + + &:hover { + .emoji-react { + margin: 0; + + &__count { + display: inline; + } + } + + .emoji-reacts__count { + display: none; + } + } +} + +.emoji-reacts__count, +.emoji-react__count { + font-size: 12px; + font-weight: bold; +} From 92917ac3e5e55a152cecee050ab4cc649a7db4e8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 14:44:23 -0500 Subject: [PATCH 08/38] Style improvements, star icon -> thumbs up --- app/gabsocial/components/status_action_bar.js | 2 +- .../features/status/components/action_bar.js | 15 +++++++++++--- .../status/components/detailed_status.js | 20 +++++++------------ .../components/status_interaction_bar.js | 10 +++++++--- app/gabsocial/features/status/index.js | 3 --- app/styles/gabsocial/components.scss | 1 + .../gabsocial/components/emoji-reacts.scss | 6 ++++++ 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/gabsocial/components/status_action_bar.js b/app/gabsocial/components/status_action_bar.js index 7c5620876..3bd191c22 100644 --- a/app/gabsocial/components/status_action_bar.js +++ b/app/gabsocial/components/status_action_bar.js @@ -294,7 +294,7 @@ class StatusActionBar extends ImmutablePureComponent { {reblogCount !== 0 && {reblogCount}}
- + {favoriteCount !== 0 && {favoriteCount}}
{shareButton} diff --git a/app/gabsocial/features/status/components/action_bar.js b/app/gabsocial/features/status/components/action_bar.js index 5432dd0e5..e7e2a1c98 100644 --- a/app/gabsocial/features/status/components/action_bar.js +++ b/app/gabsocial/features/status/components/action_bar.js @@ -232,9 +232,18 @@ class ActionBar extends React.PureComponent { return (
-
-
-
+
+ + Reply +
+
+ + Boost +
+
+ + Like +
{shareButton}
diff --git a/app/gabsocial/features/status/components/detailed_status.js b/app/gabsocial/features/status/components/detailed_status.js index 9372c9e47..b18ad11e0 100644 --- a/app/gabsocial/features/status/components/detailed_status.js +++ b/app/gabsocial/features/status/components/detailed_status.js @@ -14,6 +14,7 @@ import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import classNames from 'classnames'; import Icon from 'gabsocial/components/icon'; import PollContainer from 'gabsocial/containers/poll_container'; +import { StatusInteractionBar } from './status_interaction_bar'; export default class DetailedStatus extends ImmutablePureComponent { @@ -92,7 +93,6 @@ export default class DetailedStatus extends ImmutablePureComponent { let applicationLink = ''; let reblogLink = ''; let reblogIcon = 'retweet'; - let favouriteLink = ''; if (this.props.measureHeight) { outerStyle.height = `${this.state.height}px`; @@ -169,15 +169,6 @@ export default class DetailedStatus extends ImmutablePureComponent { ); } - favouriteLink = ( - - - - - - - ); - return (
@@ -197,9 +188,12 @@ export default class DetailedStatus extends ImmutablePureComponent { {media}
- - - {applicationLink} ยท {reblogLink} ยท {favouriteLink} + +
+ {reblogLink} {applicationLink} ยท + + +
diff --git a/app/gabsocial/features/status/components/status_interaction_bar.js b/app/gabsocial/features/status/components/status_interaction_bar.js index 9b1cb4c3b..ac674b07c 100644 --- a/app/gabsocial/features/status/components/status_interaction_bar.js +++ b/app/gabsocial/features/status/components/status_interaction_bar.js @@ -22,9 +22,7 @@ export class StatusInteractionBar extends React.Component { acc + cur.get('count') ), 0); - if (count < 1) return null; - - return ( + const EmojiReactsContainer = () => (
{emojiReacts.map((e, i) => ( @@ -42,6 +40,12 @@ export class StatusInteractionBar extends React.Component {
); + + return ( +
+ {count > 0 && } +
+ ); } } diff --git a/app/gabsocial/features/status/index.js b/app/gabsocial/features/status/index.js index 424dda420..4b9fc2436 100644 --- a/app/gabsocial/features/status/index.js +++ b/app/gabsocial/features/status/index.js @@ -43,7 +43,6 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import Icon from 'gabsocial/components/icon'; import { getSettings } from 'gabsocial/actions/settings'; -import { StatusInteractionBar } from './components/status_interaction_bar'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -493,8 +492,6 @@ class Status extends ImmutablePureComponent { onToggleMediaVisibility={this.handleToggleMediaVisibility} /> - - Date: Wed, 20 May 2020 15:52:28 -0500 Subject: [PATCH 09/38] Use unicode heart instead of red heart --- app/gabsocial/utils/emoji_reacts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/gabsocial/utils/emoji_reacts.js b/app/gabsocial/utils/emoji_reacts.js index bb45da7d4..ae426dedc 100644 --- a/app/gabsocial/utils/emoji_reacts.js +++ b/app/gabsocial/utils/emoji_reacts.js @@ -3,7 +3,7 @@ import { Map as ImmutableMap } from 'immutable'; // https://emojipedia.org/facebook/ export const ALLOWED_EMOJI = [ '๐Ÿ‘', - 'โค๏ธ', + 'โค', '๐Ÿ˜‚', '๐Ÿ˜ฏ', '๐Ÿ˜ข', From 1901d39871bd3bc07f85ff10dab39305e6ac0c9f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 15:52:46 -0500 Subject: [PATCH 10/38] Add emojiReact popup and call action code --- .../features/status/components/action_bar.js | 25 ++++++++++++++- app/gabsocial/features/status/index.js | 16 ++++++++++ app/styles/gabsocial/components.scss | 1 + .../gabsocial/components/emoji-reacts.scss | 32 +++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app/gabsocial/features/status/components/action_bar.js b/app/gabsocial/features/status/components/action_bar.js index e7e2a1c98..8ca178a5c 100644 --- a/app/gabsocial/features/status/components/action_bar.js +++ b/app/gabsocial/features/status/components/action_bar.js @@ -8,6 +8,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenuContainer from '../../../containers/dropdown_menu_container'; import { defineMessages, injectIntl } from 'react-intl'; import { isStaff } from 'gabsocial/utils/accounts'; +import { ALLOWED_EMOJI } from 'gabsocial/utils/emoji_reacts'; +import emojify from 'gabsocial/features/emoji/emoji'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -59,6 +61,7 @@ class ActionBar extends React.PureComponent { onReply: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired, + onEmojiReact: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, @@ -105,6 +108,17 @@ class ActionBar extends React.PureComponent { } } + handleReactClick = emoji => { + return e => { + const { me } = this.props; + if (me) { + this.props.onEmojiReact(this.props.status, emoji); + } else { + this.props.onOpenUnauthorizedModal(); + } + }; + } + handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); } @@ -240,7 +254,16 @@ class ActionBar extends React.PureComponent { Boost
-
+
+
+ {ALLOWED_EMOJI.map(emoji => ( +
Like
diff --git a/app/gabsocial/features/status/index.js b/app/gabsocial/features/status/index.js index 4b9fc2436..ae648d4ce 100644 --- a/app/gabsocial/features/status/index.js +++ b/app/gabsocial/features/status/index.js @@ -17,6 +17,7 @@ import { pin, unpin, } from '../../actions/interactions'; +import { emojiReact, unEmojiReact } from '../../actions/emoji_reacts'; import { replyCompose, mentionCompose, @@ -161,6 +162,20 @@ class Status extends ImmutablePureComponent { this.setState({ showMedia: !this.state.showMedia }); } + handleEmojiReactClick = (status, emoji) => { + if (emoji === '๐Ÿ‘') { + this.handleFavouriteClick(status); return; + } + const hasReaction = status.getIn(['pleroma', 'emoji_reactions']) + .findIndex(e => e.get('name') === emoji && e.get('me') === true) > -1; + + if (hasReaction) { + this.props.dispatch(unEmojiReact(status, emoji)); + } else { + this.props.dispatch(emojiReact(status, emoji)); + } + } + handleFavouriteClick = (status) => { if (status.get('favourited')) { this.props.dispatch(unfavourite(status)); @@ -496,6 +511,7 @@ class Status extends ImmutablePureComponent { status={status} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} + onEmojiReact={this.handleEmojiReactClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onDirect={this.handleDirectClick} diff --git a/app/styles/gabsocial/components.scss b/app/styles/gabsocial/components.scss index 2f007f28c..c009a4c0d 100644 --- a/app/styles/gabsocial/components.scss +++ b/app/styles/gabsocial/components.scss @@ -2790,6 +2790,7 @@ a.status-card.compact:hover { .detailed-status__button { flex: 1 1 auto; text-align: center; + position: relative; } .column-settings__outer { diff --git a/app/styles/gabsocial/components/emoji-reacts.scss b/app/styles/gabsocial/components/emoji-reacts.scss index 261ce5155..486741d04 100644 --- a/app/styles/gabsocial/components/emoji-reacts.scss +++ b/app/styles/gabsocial/components/emoji-reacts.scss @@ -53,3 +53,35 @@ font-size: 12px; font-weight: bold; } + +.emoji-react-selector { + position: absolute; + bottom: 100%; + display: flex; + background-color: #fff; + padding: 8px; + border-radius: 9999px; + box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.1); + opacity: 0; + pointer-events: none; + transition: 0.1s; + + &__emoji { + display: block; + padding: 0 2px; + border: 0; + background: transparent; + + img { + width: 30px; + height: 30px; + } + } +} + +.detailed-status__button--favourite:hover { + .emoji-react-selector { + opacity: 1; + pointer-events: all; + } +} From cf772753ae5519d0c021d94394f1d7bce9b1abbe Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 16:08:29 -0500 Subject: [PATCH 11/38] Replace red heart with unicode heart in tests --- .../utils/__tests__/emoji_reacts-test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/gabsocial/utils/__tests__/emoji_reacts-test.js b/app/gabsocial/utils/__tests__/emoji_reacts-test.js index 6acb5ec09..8df3cb674 100644 --- a/app/gabsocial/utils/__tests__/emoji_reacts-test.js +++ b/app/gabsocial/utils/__tests__/emoji_reacts-test.js @@ -34,12 +34,12 @@ describe('sortEmoji', () => { { 'count': 1, 'me': true, 'name': '๐Ÿ˜ก' }, { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜‚' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, ]); it('sorts the emoji by count', () => { expect(sortEmoji(emojiReacts)).toEqual(fromJS([ { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜‚' }, { 'count': 3, 'me': true, 'name': '๐Ÿ˜ข' }, @@ -55,13 +55,13 @@ describe('mergeEmojiFavourites', () => { describe('with existing ๐Ÿ‘ reacts', () => { const emojiReacts = fromJS([ { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, ]); it('combines ๐Ÿ‘ reacts with favourites', () => { expect(mergeEmojiFavourites(emojiReacts, favouritesCount)).toEqual(fromJS([ { 'count': 32, 'me': true, 'name': '๐Ÿ‘' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, ])); }); @@ -69,19 +69,19 @@ describe('mergeEmojiFavourites', () => { describe('without existing ๐Ÿ‘ reacts', () => { const emojiReacts = fromJS([ - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, ]); it('adds ๐Ÿ‘ reacts to the map equaling favourite count', () => { expect(mergeEmojiFavourites(emojiReacts, favouritesCount)).toEqual(fromJS([ - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, { 'count': 12, 'me': false, 'name': '๐Ÿ‘' }, ])); }); it('does not add ๐Ÿ‘ reacts when there are no favourites', () => { expect(mergeEmojiFavourites(emojiReacts, 0)).toEqual(fromJS([ - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, ])); }); @@ -98,14 +98,14 @@ describe('reduceEmoji', () => { { 'count': 1, 'me': true, 'name': '๐ŸŒต' }, { 'count': 20, 'me': true, 'name': '๐Ÿ‘' }, { 'count': 7, 'me': false, 'name': '๐Ÿ˜‚' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 1, 'me': false, 'name': '๐Ÿ‘€' }, { 'count': 1, 'me': false, 'name': '๐Ÿฉ' }, ]); it('sorts, filters, and combines emoji and favourites', () => { expect(reduceEmoji(emojiReacts, 7)).toEqual(fromJS([ { 'count': 27, 'me': true, 'name': '๐Ÿ‘' }, - { 'count': 15, 'me': true, 'name': 'โค๏ธ' }, + { 'count': 15, 'me': true, 'name': 'โค' }, { 'count': 7, 'me': true, 'name': '๐Ÿ˜ฏ' }, { 'count': 7, 'me': false, 'name': '๐Ÿ˜‚' }, { 'count': 3, 'me': false, 'name': '๐Ÿ˜ข' }, From f402ac67b39cd5feabaff26d0643742ff8c38fec Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 16:52:32 -0500 Subject: [PATCH 12/38] Emoji chooser hover state --- app/styles/gabsocial/components/emoji-reacts.scss | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/styles/gabsocial/components/emoji-reacts.scss b/app/styles/gabsocial/components/emoji-reacts.scss index 486741d04..3b989b52b 100644 --- a/app/styles/gabsocial/components/emoji-reacts.scss +++ b/app/styles/gabsocial/components/emoji-reacts.scss @@ -59,7 +59,7 @@ bottom: 100%; display: flex; background-color: #fff; - padding: 8px; + padding: 5px 8px; border-radius: 9999px; box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.1); opacity: 0; @@ -68,13 +68,24 @@ &__emoji { display: block; - padding: 0 2px; + padding: 0; + margin: 0; border: 0; background: transparent; img { width: 30px; height: 30px; + padding: 3px; + transition: 0.1s; + } + + &:hover { + img { + width: 36px; + height: 36px; + padding: 0; + } } } } From fcd76431cb659c7efa8759c65a991e43b1b180b8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 20 May 2020 18:01:37 -0500 Subject: [PATCH 13/38] Support `pleroma:emoji_reaction` notification type --- .../notifications/components/notification.js | 41 +++++++++++++++++-- app/gabsocial/reducers/notifications.js | 1 + 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/gabsocial/features/notifications/components/notification.js b/app/gabsocial/features/notifications/components/notification.js index 5094ade36..bd13367dc 100644 --- a/app/gabsocial/features/notifications/components/notification.js +++ b/app/gabsocial/features/notifications/components/notification.js @@ -8,6 +8,7 @@ import Permalink from '../../../components/permalink'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; import Icon from 'gabsocial/components/icon'; +import emojify from 'gabsocial/features/emoji/emoji'; const notificationForScreenReader = (intl, message, timestamp) => { const output = [message]; @@ -141,19 +142,51 @@ class Notification extends ImmutablePureComponent { ); } + renderEmojiReact(notification, link) { + const { intl } = this.props; + + return ( + +
+
+
+ +
+ + + + +
+ +
+
+ ); + } + renderFavourite(notification, link) { const { intl } = this.props; return ( -
+
- +
- +
@@ -254,6 +287,8 @@ class Notification extends ImmutablePureComponent { return this.renderReblog(notification, link); case 'poll': return this.renderPoll(notification); + case 'pleroma:emoji_reaction': + return this.renderEmojiReact(notification, link); } return null; diff --git a/app/gabsocial/reducers/notifications.js b/app/gabsocial/reducers/notifications.js index 4b2267e5a..81db680c6 100644 --- a/app/gabsocial/reducers/notifications.js +++ b/app/gabsocial/reducers/notifications.js @@ -41,6 +41,7 @@ const notificationToMap = notification => ImmutableMap({ account: notification.account.id, created_at: notification.created_at, status: notification.status ? notification.status.id : null, + emoji: notification.emoji, }); const normalizeNotification = (state, notification) => { From 1c711ed1237b936d9f3f337eaf65458171ac167b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 21 May 2020 19:01:41 -0500 Subject: [PATCH 14/38] "reacted to" --> "liked" for favourite notifications --- .../features/notifications/components/notification.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/gabsocial/features/notifications/components/notification.js b/app/gabsocial/features/notifications/components/notification.js index bd13367dc..5fda69cfe 100644 --- a/app/gabsocial/features/notifications/components/notification.js +++ b/app/gabsocial/features/notifications/components/notification.js @@ -179,14 +179,14 @@ class Notification extends ImmutablePureComponent { return ( -
+
- +
From 6e13cb8c9bff6c2020f1a606e8d843e64868fa0a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 21 May 2020 20:33:55 -0500 Subject: [PATCH 15/38] Improve detailed status action buttons --- app/gabsocial/components/icon_button.js | 13 ++- .../features/status/components/action_bar.js | 28 ++++-- app/styles/application.scss | 1 + app/styles/gabsocial/components.scss | 79 +--------------- .../gabsocial/components/detailed-status.scss | 92 +++++++++++++++++++ 5 files changed, 125 insertions(+), 88 deletions(-) create mode 100644 app/styles/gabsocial/components/detailed-status.scss diff --git a/app/gabsocial/components/icon_button.js b/app/gabsocial/components/icon_button.js index f8816bbf7..afbddc117 100644 --- a/app/gabsocial/components/icon_button.js +++ b/app/gabsocial/components/icon_button.js @@ -64,6 +64,7 @@ export default class IconButton extends React.PureComponent { pressed, tabIndex, title, + text, } = this.props; const classes = classNames(className, 'icon-button', { @@ -84,11 +85,13 @@ export default class IconButton extends React.PureComponent { title={title} className={classes} onClick={this.handleClick} - style={style} tabIndex={tabIndex} disabled={disabled} > -
{shareButton} From e04ab557ac840a6f2c87c993a1f95ab5e2970568 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 23 May 2020 20:29:25 -0500 Subject: [PATCH 38/38] Make emoji reacts more responsive --- app/gabsocial/reducers/statuses.js | 8 ++++++ .../utils/__tests__/emoji_reacts-test.js | 26 +++++++++++++++++++ app/gabsocial/utils/emoji_reacts.js | 17 ++++++++++++ 3 files changed, 51 insertions(+) diff --git a/app/gabsocial/reducers/statuses.js b/app/gabsocial/reducers/statuses.js index 885cc221c..d41fc3834 100644 --- a/app/gabsocial/reducers/statuses.js +++ b/app/gabsocial/reducers/statuses.js @@ -10,9 +10,13 @@ import { STATUS_REVEAL, STATUS_HIDE, } from '../actions/statuses'; +import { + EMOJI_REACT_REQUEST, +} from '../actions/emoji_reacts'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; +import { simulateEmojiReact } from 'gabsocial/utils/emoji_reacts'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -37,6 +41,10 @@ export default function statuses(state = initialState, action) { return importStatuses(state, action.statuses); case FAVOURITE_REQUEST: return state.setIn([action.status.get('id'), 'favourited'], true); + case EMOJI_REACT_REQUEST: + const path = [action.status.get('id'), 'pleroma', 'emoji_reactions']; + const emojiReacts = state.getIn(path); + return state.setIn(path, simulateEmojiReact(emojiReacts, action.emoji)); case FAVOURITE_FAIL: return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false); case REBLOG_REQUEST: diff --git a/app/gabsocial/utils/__tests__/emoji_reacts-test.js b/app/gabsocial/utils/__tests__/emoji_reacts-test.js index b1b2c7bfc..fcce0a11b 100644 --- a/app/gabsocial/utils/__tests__/emoji_reacts-test.js +++ b/app/gabsocial/utils/__tests__/emoji_reacts-test.js @@ -5,6 +5,7 @@ import { oneEmojiPerAccount, reduceEmoji, getReactForStatus, + simulateEmojiReact, } from '../emoji_reacts'; import { fromJS } from 'immutable'; @@ -179,3 +180,28 @@ describe('getReactForStatus', () => { expect(getReactForStatus(status)).toEqual(undefined); }); }); + +describe('simulateEmojiReact', () => { + it('adds the emoji to the list', () => { + const emojiReacts = fromJS([ + { 'count': 2, 'me': false, 'name': '๐Ÿ‘' }, + { 'count': 2, 'me': false, 'name': 'โค' }, + ]); + expect(simulateEmojiReact(emojiReacts, 'โค')).toEqual(fromJS([ + { 'count': 2, 'me': false, 'name': '๐Ÿ‘' }, + { 'count': 3, 'me': true, 'name': 'โค' }, + ])); + }); + + it('creates the emoji if it didn\'t already exist', () => { + const emojiReacts = fromJS([ + { 'count': 2, 'me': false, 'name': '๐Ÿ‘' }, + { 'count': 2, 'me': false, 'name': 'โค' }, + ]); + expect(simulateEmojiReact(emojiReacts, '๐Ÿ˜ฏ')).toEqual(fromJS([ + { 'count': 2, 'me': false, 'name': '๐Ÿ‘' }, + { 'count': 2, 'me': false, 'name': 'โค' }, + { 'count': 1, 'me': true, 'name': '๐Ÿ˜ฏ' }, + ])); + }); +}); diff --git a/app/gabsocial/utils/emoji_reacts.js b/app/gabsocial/utils/emoji_reacts.js index 4e378eced..2cb1b3fc3 100644 --- a/app/gabsocial/utils/emoji_reacts.js +++ b/app/gabsocial/utils/emoji_reacts.js @@ -83,3 +83,20 @@ export const getReactForStatus = status => { ).filter(e => e.get('me') === true) .getIn([0, 'name']); }; + +export const simulateEmojiReact = (emojiReacts, emoji) => { + const idx = emojiReacts.findIndex(e => e.get('name') === emoji); + if (idx > -1) { + const emojiReact = emojiReacts.get(idx); + return emojiReacts.set(idx, emojiReact.merge({ + count: emojiReact.get('count') + 1, + me: true, + })); + } else { + return emojiReacts.push(ImmutableMap({ + count: 1, + me: true, + name: emoji, + })); + } +};