From d448277bbe27a459ab0d894be9ee85c02993951e Mon Sep 17 00:00:00 2001 From: Sean King Date: Sat, 24 Oct 2020 21:44:22 -0600 Subject: [PATCH 01/20] Message when a profile is blocked changed --- app/soapbox/features/account_timeline/index.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/account_timeline/index.js b/app/soapbox/features/account_timeline/index.js index 21a55181b..7f0e12114 100644 --- a/app/soapbox/features/account_timeline/index.js +++ b/app/soapbox/features/account_timeline/index.js @@ -39,11 +39,12 @@ const mapStateToProps = (state, { params: { username }, withReplies = false }) = const path = withReplies ? `${accountId}:with_replies` : accountId; const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false); - const unavailable = (me === accountId) ? false : isBlocked; + const unavailable = (me === accountId) ? false : true; return { accountId, unavailable, + isBlocked, accountUsername, accountApId, isAccount: !!state.getIn(['accounts', accountId]), @@ -117,7 +118,7 @@ class AccountTimeline extends ImmutablePureComponent { } render() { - const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, accountId, unavailable, accountUsername } = this.props; + const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, accountId, isBlocked, unavailable, accountUsername } = this.props; if (!isAccount && accountId !== -1) { return ( @@ -135,6 +136,16 @@ class AccountTimeline extends ImmutablePureComponent { ); } + if (isBlocked) { + return ( + +
+ +
+
+ ); + } + if (unavailable) { return ( From 3d393deee4ba97d6f11ed77c43ff2709fd7acdf9 Mon Sep 17 00:00:00 2001 From: Sean King Date: Mon, 2 Nov 2020 08:25:21 -0700 Subject: [PATCH 02/20] A better way to handle block messages --- app/soapbox/features/account_timeline/index.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/soapbox/features/account_timeline/index.js b/app/soapbox/features/account_timeline/index.js index 7f0e12114..94762c918 100644 --- a/app/soapbox/features/account_timeline/index.js +++ b/app/soapbox/features/account_timeline/index.js @@ -39,7 +39,7 @@ const mapStateToProps = (state, { params: { username }, withReplies = false }) = const path = withReplies ? `${accountId}:with_replies` : accountId; const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false); - const unavailable = (me === accountId) ? false : true; + const unavailable = (me === accountId) ? false : isBlocked; return { accountId, @@ -136,21 +136,12 @@ class AccountTimeline extends ImmutablePureComponent { ); } - if (isBlocked) { - return ( - -
- -
-
- ); - } - if (unavailable) { return (
- + {isBlocked ? + : }
); From c80f87efaa889b0c83427bd68e73b33c639624d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 6 Sep 2021 21:54:48 +0200 Subject: [PATCH 03/20] Add emoji reacts page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- app/soapbox/actions/interactions.js | 39 +++++++ app/soapbox/components/account.js | 19 ++- app/soapbox/components/column_back_button.js | 10 +- app/soapbox/features/reactions/index.js | 110 ++++++++++++++++++ .../components/status_interaction_bar.js | 50 ++++---- app/soapbox/features/ui/components/column.js | 5 +- app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + .../reducers/__tests__/user_lists-test.js | 1 + app/soapbox/reducers/user_lists.js | 4 + app/styles/accounts.scss | 7 ++ app/styles/components/emoji-reacts.scss | 4 +- app/styles/ui.scss | 14 ++- 13 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 app/soapbox/features/reactions/index.js diff --git a/app/soapbox/actions/interactions.js b/app/soapbox/actions/interactions.js index aaa6b6143..1e2d729f1 100644 --- a/app/soapbox/actions/interactions.js +++ b/app/soapbox/actions/interactions.js @@ -28,6 +28,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 REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST'; +export const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS'; +export const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL'; + export const PIN_REQUEST = 'PIN_REQUEST'; export const PIN_SUCCESS = 'PIN_SUCCESS'; export const PIN_FAIL = 'PIN_FAIL'; @@ -359,6 +363,41 @@ export function fetchFavouritesFail(id, error) { }; } +export function fetchReactions(id) { + return (dispatch, getState) => { + dispatch(fetchReactionsRequest(id)); + + api(getState).get(`/api/v1/pleroma/statuses/${id}/reactions`).then(response => { + dispatch(importFetchedAccounts(response.data.map(({ accounts }) => accounts).flat())); + dispatch(fetchReactionsSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchReactionsFail(id, error)); + }); + }; +} + +export function fetchReactionsRequest(id) { + return { + type: REACTIONS_FETCH_REQUEST, + id, + }; +} + +export function fetchReactionsSuccess(id, reactions) { + return { + type: REACTIONS_FETCH_SUCCESS, + id, + reactions, + }; +} + +export function fetchReactionsFail(id, error) { + return { + type: REACTIONS_FETCH_FAIL, + error, + }; +} + export function pin(status) { return (dispatch, getState) => { if (!isLoggedIn(getState)) return; diff --git a/app/soapbox/components/account.js b/app/soapbox/components/account.js index c23c0420e..d4b616d5d 100644 --- a/app/soapbox/components/account.js +++ b/app/soapbox/components/account.js @@ -12,6 +12,7 @@ import RelativeTimestamp from './relative_timestamp'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import classNames from 'classnames'; +import emojify from 'soapbox/features/emoji/emoji'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, @@ -46,6 +47,7 @@ class Account extends ImmutablePureComponent { onActionClick: PropTypes.func, withDate: PropTypes.bool, withRelationship: PropTypes.bool, + reaction: PropTypes.string, }; static defaultProps = { @@ -78,7 +80,7 @@ class Account extends ImmutablePureComponent { } render() { - const { account, intl, hidden, onActionClick, actionIcon, actionTitle, me, withDate, withRelationship } = this.props; + const { account, intl, hidden, onActionClick, actionIcon, actionTitle, me, withDate, withRelationship, reaction } = this.props; if (!account) { return
; @@ -95,6 +97,7 @@ class Account extends ImmutablePureComponent { let buttons; let followedBy; + let emoji; if (onActionClick && actionIcon) { buttons = ; @@ -128,6 +131,15 @@ class Account extends ImmutablePureComponent { } } + if (reaction) { + emoji = ( + + ); + } + const createdAt = account.get('created_at'); const joinedAt = createdAt ? ( @@ -141,7 +153,10 @@ class Account extends ImmutablePureComponent {
-
+
+ {emoji} + +
diff --git a/app/soapbox/components/column_back_button.js b/app/soapbox/components/column_back_button.js index 1ce0d8f1f..bbc2fc701 100644 --- a/app/soapbox/components/column_back_button.js +++ b/app/soapbox/components/column_back_button.js @@ -5,12 +5,20 @@ import Icon from 'soapbox/components/icon'; export default class ColumnBackButton extends React.PureComponent { + static propTypes = { + to: PropTypes.string, + }; + static contextTypes = { router: PropTypes.object, }; handleClick = () => { - if (window.history && window.history.length === 1) { + const { to } = this.props; + + if (to) { + this.context.router.history.push(to); + } else if (window.history && window.history.length === 1) { this.context.router.history.push('/'); } else { this.context.router.history.goBack(); diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js new file mode 100644 index 000000000..260c8726b --- /dev/null +++ b/app/soapbox/features/reactions/index.js @@ -0,0 +1,110 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { OrderedSet as ImmutableOrderedSet } from 'immutable'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import LoadingIndicator from '../../components/loading_indicator'; +import MissingIndicator from '../../components/missing_indicator'; +import { fetchFavourites, fetchReactions } from '../../actions/interactions'; +import { fetchStatus } from '../../actions/statuses'; +import { FormattedMessage } from 'react-intl'; +import AccountContainer from '../../containers/account_container'; +import Column from '../ui/components/column'; +import ScrollableList from '../../components/scrollable_list'; +import { makeGetStatus } from '../../selectors'; +import { NavLink } from 'react-router-dom'; + +const mapStateToProps = (state, props) => { + const getStatus = makeGetStatus(); + const status = getStatus(state, { + id: props.params.statusId, + username: props.params.username, + }); + + const favourites = state.getIn(['user_lists', 'favourited_by', props.params.statusId]); + const reactions = state.getIn(['user_lists', 'reactions', props.params.statusId]); + const allReactions = favourites && reactions && ImmutableOrderedSet(favourites ? [{ accounts: favourites, count: favourites.size, name: '๐Ÿ‘' }] : []).union(reactions || []); + + return { + status, + reactions: allReactions, + accounts: allReactions && (props.params.reaction + ? allReactions.find(reaction => reaction.name === props.params.reaction).accounts.map(account => ({ id: account, reaction: props.params.reaction })) + : allReactions.map(reaction => reaction.accounts.map(account => ({ id: account, reaction: reaction.name }))).flatten()), + }; +}; + +export default @connect(mapStateToProps) +class Reactions extends ImmutablePureComponent { + + static propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.array.isRequired, + reactions: PropTypes.array, + accounts: PropTypes.array, + status: ImmutablePropTypes.map, + }; + + componentDidMount() { + this.props.dispatch(fetchFavourites(this.props.params.statusId)); + this.props.dispatch(fetchReactions(this.props.params.statusId)); + this.props.dispatch(fetchStatus(this.props.params.statusId)); + } + + componentDidUpdate(prevProps) { + const { params } = this.props; + if (params.statusId !== prevProps.params.statusId && params.statusId) { + this.props.dispatch(fetchFavourites(this.props.params.statusId)); + prevProps.dispatch(fetchReactions(params.statusId)); + prevProps.dispatch(fetchStatus(params.statusId)); + } + } + + render() { + const { params, reactions, accounts, status } = this.props; + const { username, statusId } = params; + + const back = `/@${username}/posts/${statusId}`; + + if (!accounts) { + return ( + + + + ); + } + + if (!status) { + return ( + + + + ); + } + + const emptyMessage = ; + + return ( + + { + reactions.size > 0 && ( +
+ All + {reactions?.map(reaction => {reaction.name} {reaction.count})} +
+ ) + } + + {accounts.map((account) => + , + )} + +
+ ); + } + +} diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js index d921efca0..fbd943a63 100644 --- a/app/soapbox/features/status/components/status_interaction_bar.js +++ b/app/soapbox/features/status/components/status_interaction_bar.js @@ -49,35 +49,45 @@ class StatusInteractionBar extends ImmutablePureComponent { return ''; } - render() { + getEmojiReacts = () => { + const { status } = this.props; + const emojiReacts = this.getNormalizedReacts(); const count = emojiReacts.reduce((acc, cur) => ( acc + cur.get('count') ), 0); - const repost = this.getRepost(); - const EmojiReactsContainer = () => ( -
-
- {emojiReacts.map((e, i) => ( - - - {e.get('count')} - - ))} + if (count > 0) { + return ( +
+
+ {emojiReacts.map((e, i) => ( + + + {e.get('count')} + + ))} +
+
+ {count} +
-
- {count} -
-
- ); + ); + } + + return ''; + }; + + render() { + const emojiReacts = this.getEmojiReacts(); + const repost = this.getRepost(); return (
- {count > 0 && } + {emojiReacts} {repost}
); diff --git a/app/soapbox/features/ui/components/column.js b/app/soapbox/features/ui/components/column.js index 5f31399e3..e915e5e70 100644 --- a/app/soapbox/features/ui/components/column.js +++ b/app/soapbox/features/ui/components/column.js @@ -12,12 +12,13 @@ export default class Column extends React.PureComponent { children: PropTypes.node, active: PropTypes.bool, backBtnSlim: PropTypes.bool, + back: PropTypes.string, }; render() { - const { heading, icon, children, active, backBtnSlim } = this.props; + const { heading, icon, children, active, backBtnSlim, back } = this.props; const columnHeaderId = heading && heading.replace(/ /g, '-'); - const backBtn = backBtnSlim ? () : (); + const backBtn = backBtnSlim ? () : (); return (
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 010242b0d..c6136f69b 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -56,6 +56,7 @@ import { Followers, Following, Reblogs, + Reactions, // Favourites, DirectTimeline, HashtagTimeline, @@ -261,6 +262,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 1fe50212c..61f6c2143 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -94,6 +94,10 @@ export function Reblogs() { return import(/* webpackChunkName: "features/reblogs" */'../../reblogs'); } +export function Reactions() { + return import(/* webpackChunkName: "features/reblogs" */'../../reactions'); +} + export function Favourites() { return import(/* webpackChunkName: "features/favourites" */'../../favourites'); } diff --git a/app/soapbox/reducers/__tests__/user_lists-test.js b/app/soapbox/reducers/__tests__/user_lists-test.js index feaaca3e6..7d571e208 100644 --- a/app/soapbox/reducers/__tests__/user_lists-test.js +++ b/app/soapbox/reducers/__tests__/user_lists-test.js @@ -10,6 +10,7 @@ describe('user_lists reducer', () => { favourited_by: ImmutableMap(), follow_requests: ImmutableMap(), blocks: ImmutableMap(), + reactions: ImmutableMap(), mutes: ImmutableMap(), groups: ImmutableMap(), groups_removed_accounts: ImmutableMap(), diff --git a/app/soapbox/reducers/user_lists.js b/app/soapbox/reducers/user_lists.js index 9afaf55b9..f0d80de77 100644 --- a/app/soapbox/reducers/user_lists.js +++ b/app/soapbox/reducers/user_lists.js @@ -14,6 +14,7 @@ import { import { REBLOGS_FETCH_SUCCESS, FAVOURITES_FETCH_SUCCESS, + REACTIONS_FETCH_SUCCESS, } from '../actions/interactions'; import { BLOCKS_FETCH_SUCCESS, @@ -37,6 +38,7 @@ const initialState = ImmutableMap({ following: ImmutableMap(), reblogged_by: ImmutableMap(), favourited_by: ImmutableMap(), + reactions: ImmutableMap(), follow_requests: ImmutableMap(), blocks: ImmutableMap(), mutes: ImmutableMap(), @@ -77,6 +79,8 @@ export default function userLists(state = initialState, action) { return state.setIn(['reblogged_by', action.id], ImmutableOrderedSet(action.accounts.map(item => item.id))); case FAVOURITES_FETCH_SUCCESS: return state.setIn(['favourited_by', action.id], ImmutableOrderedSet(action.accounts.map(item => item.id))); + case REACTIONS_FETCH_SUCCESS: + return state.setIn(['reactions', action.id], action.reactions.map(({ accounts, ...reaction }) => ({ ...reaction, accounts: ImmutableOrderedSet(accounts.map(account => account.id)) }))); case NOTIFICATIONS_UPDATE: return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state; case FOLLOW_REQUESTS_FETCH_SUCCESS: diff --git a/app/styles/accounts.scss b/app/styles/accounts.scss index ad2190be5..14f74f59a 100644 --- a/app/styles/accounts.scss +++ b/app/styles/accounts.scss @@ -216,6 +216,13 @@ .account__avatar-wrapper { float: left; margin-right: 12px; + + .emoji-react__emoji { + position: absolute; + top: 36px; + left: 32px; + z-index: 1; + } } .account__avatar { diff --git a/app/styles/components/emoji-reacts.scss b/app/styles/components/emoji-reacts.scss index 4fca2108c..bc69b0542 100644 --- a/app/styles/components/emoji-reacts.scss +++ b/app/styles/components/emoji-reacts.scss @@ -1,6 +1,8 @@ .emoji-react { display: inline-block; transition: 0.1s; + color: var(--primary-text-color--faint); + text-decoration: none; &__emoji { img { @@ -20,8 +22,6 @@ } .emoji-react--reblogs { - color: var(--primary-text-color--faint); - text-decoration: none; vertical-align: middle; display: inline-flex; diff --git a/app/styles/ui.scss b/app/styles/ui.scss index bf8dd9f7e..72c55a6bd 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -611,7 +611,8 @@ .notification__filter-bar, .search__filter-bar, -.account__section-headline { +.account__section-headline, +.reaction__filter-bar { border-bottom: 1px solid var(--brand-color--faint); cursor: default; display: flex; @@ -661,6 +662,17 @@ } } +.reaction__filter-bar { + overflow-x: auto; + overflow-y: hidden; + + a { + flex: unset; + padding: 15px 24px; + min-width: max-content; + } +} + ::-webkit-scrollbar-thumb { border-radius: 0; } From 275d898a63479e7ad344357eed374722ccb08623 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 9 Sep 2021 12:00:42 -0500 Subject: [PATCH 04/20] Webpack: simplify module.rules --- webpack/shared.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/shared.js b/webpack/shared.js index c0bbad931..505554a6c 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -65,7 +65,7 @@ module.exports = { }, module: { - rules: Object.keys(rules).map(key => rules[key]), + rules, }, plugins: [ From 660661451cab1ac269b821a1c23589a15f396a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 9 Sep 2021 20:44:12 +0200 Subject: [PATCH 05/20] Conditionally link to emoji reaction list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- .../components/status_interaction_bar.js | 43 +++++++++++++------ app/soapbox/utils/features.js | 1 + 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js index fbd943a63..186bec37d 100644 --- a/app/soapbox/features/status/components/status_interaction_bar.js +++ b/app/soapbox/features/status/components/status_interaction_bar.js @@ -1,18 +1,26 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import { FormattedNumber } from 'react-intl'; import emojify from 'soapbox/features/emoji/emoji'; import { reduceEmoji } from 'soapbox/utils/emoji_reacts'; import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; +import { getFeatures } from 'soapbox/utils/features'; import { Link } from 'react-router-dom'; import Icon from 'soapbox/components/icon'; import { getSoapboxConfig } from 'soapbox/actions/soapbox'; -const mapStateToProps = state => ({ - allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'), -}); +const mapStateToProps = state => { + const instance = state.get('instance'); + const features = getFeatures(instance); + + return { + allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'), + reactionList: features.exposableReactions, + }; +}; export default @connect(mapStateToProps) class StatusInteractionBar extends ImmutablePureComponent { @@ -21,6 +29,7 @@ class StatusInteractionBar extends ImmutablePureComponent { status: ImmutablePropTypes.map, me: SoapboxPropTypes.me, allowedEmoji: ImmutablePropTypes.list, + reactionList: PropTypes.bool, } getNormalizedReacts = () => { @@ -50,7 +59,7 @@ class StatusInteractionBar extends ImmutablePureComponent { } getEmojiReacts = () => { - const { status } = this.props; + const { status, reactionList } = this.props; const emojiReacts = this.getNormalizedReacts(); const count = emojiReacts.reduce((acc, cur) => ( @@ -61,15 +70,23 @@ class StatusInteractionBar extends ImmutablePureComponent { return (
- {emojiReacts.map((e, i) => ( - - - {e.get('count')} - - ))} + {emojiReacts.map((e, i) => { + const emojiReact = ( + <> + + {e.get('count')} + + ); + + if (reactionList) { + return {emojiReact}; + } + + return {emojiReact}; + })}
{count} diff --git a/app/soapbox/utils/features.js b/app/soapbox/utils/features.js index 331fa10b2..e2543757e 100644 --- a/app/soapbox/utils/features.js +++ b/app/soapbox/utils/features.js @@ -24,6 +24,7 @@ export const getFeatures = createSelector([ securityAPI: v.software === 'Pleroma', settingsStore: v.software === 'Pleroma', accountAliasesAPI: v.software === 'Pleroma', + exposableReactions: features.includes('exposable_reactions'), }; }); From 4c4739b5e87f462a6f6717411a33250c4013f247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Thu, 9 Sep 2021 23:35:25 +0200 Subject: [PATCH 06/20] Change chunk name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- app/soapbox/features/ui/util/async-components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 61f6c2143..2dda61d1f 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -95,7 +95,7 @@ export function Reblogs() { } export function Reactions() { - return import(/* webpackChunkName: "features/reblogs" */'../../reactions'); + return import(/* webpackChunkName: "features/reactions" */'../../reactions'); } export function Favourites() { From fc8bb223590fc5b0ff8167c690a49c24d820561b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 10 Sep 2021 00:23:49 +0200 Subject: [PATCH 07/20] Reactions page improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- app/soapbox/components/column_back_button.js | 6 ++---- app/soapbox/features/reactions/index.js | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/soapbox/components/column_back_button.js b/app/soapbox/components/column_back_button.js index bbc2fc701..54b700b63 100644 --- a/app/soapbox/components/column_back_button.js +++ b/app/soapbox/components/column_back_button.js @@ -16,10 +16,8 @@ export default class ColumnBackButton extends React.PureComponent { handleClick = () => { const { to } = this.props; - if (to) { - this.context.router.history.push(to); - } else if (window.history && window.history.length === 1) { - this.context.router.history.push('/'); + if (window.history && window.history.length === 1) { + this.context.router.history.push(to ? to : '/'); } else { this.context.router.history.goBack(); } diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js index 260c8726b..4eca0880b 100644 --- a/app/soapbox/features/reactions/index.js +++ b/app/soapbox/features/reactions/index.js @@ -91,7 +91,7 @@ class Reactions extends ImmutablePureComponent { reactions.size > 0 && (
All - {reactions?.map(reaction => {reaction.name} {reaction.count})} + {reactions?.filter(reaction => reaction.count).map(reaction => {reaction.name} {reaction.count})}
) } From 58bdf4b1c77c132f3db2f18381b90ffd7f419beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 10 Sep 2021 00:44:21 +0200 Subject: [PATCH 08/20] do not push filter changes to history MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- app/soapbox/features/reactions/index.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js index 4eca0880b..0e843cd33 100644 --- a/app/soapbox/features/reactions/index.js +++ b/app/soapbox/features/reactions/index.js @@ -13,7 +13,6 @@ import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ScrollableList from '../../components/scrollable_list'; import { makeGetStatus } from '../../selectors'; -import { NavLink } from 'react-router-dom'; const mapStateToProps = (state, props) => { const getStatus = makeGetStatus(); @@ -38,6 +37,10 @@ const mapStateToProps = (state, props) => { export default @connect(mapStateToProps) class Reactions extends ImmutablePureComponent { + static contextTypes = { + router: PropTypes.object.isRequired, + }; + static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.array.isRequired, @@ -61,6 +64,13 @@ class Reactions extends ImmutablePureComponent { } } + handleFilterChange = (reaction) => () => { + const { params } = this.props; + const { username, statusId } = params; + + this.context.router.history.replace(`/@${username}/posts/${statusId}/reactions/${reaction}`); + }; + render() { const { params, reactions, accounts, status } = this.props; const { username, statusId } = params; @@ -90,8 +100,8 @@ class Reactions extends ImmutablePureComponent { { reactions.size > 0 && (
- All - {reactions?.filter(reaction => reaction.count).map(reaction => {reaction.name} {reaction.count})} + + {reactions?.filter(reaction => reaction.count).map(reaction => )}
) } From 1dc78e4e95b4a19e6a7927906e1c2206fff1c9e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Fri, 10 Sep 2021 00:48:06 +0200 Subject: [PATCH 09/20] fix proptypes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikoล‚ajczak --- app/soapbox/features/reactions/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/soapbox/features/reactions/index.js b/app/soapbox/features/reactions/index.js index 0e843cd33..8e3ab9884 100644 --- a/app/soapbox/features/reactions/index.js +++ b/app/soapbox/features/reactions/index.js @@ -43,9 +43,9 @@ class Reactions extends ImmutablePureComponent { static propTypes = { params: PropTypes.object.isRequired, - dispatch: PropTypes.array.isRequired, - reactions: PropTypes.array, - accounts: PropTypes.array, + dispatch: PropTypes.func.isRequired, + reactions: ImmutablePropTypes.orderedSet, + accounts: ImmutablePropTypes.orderedSet, status: ImmutablePropTypes.map, }; @@ -101,7 +101,7 @@ class Reactions extends ImmutablePureComponent { reactions.size > 0 && (
- {reactions?.filter(reaction => reaction.count).map(reaction => )} + {reactions?.filter(reaction => reaction.count).map(reaction => )}
) } From 196284695b398bb53280c19342840ce53fa30c32 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 11:44:18 -0500 Subject: [PATCH 10/20] SoapboxConfig: allow authenticated profiles to be configured --- app/soapbox/actions/accounts.js | 4 ---- app/soapbox/actions/soapbox.js | 1 + .../features/favourited_statuses/index.js | 2 +- app/soapbox/features/pinned_statuses/index.js | 2 +- app/soapbox/features/soapbox_config/index.js | 9 ++++++++ app/soapbox/features/ui/index.js | 21 ++++++++++++------- 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index e215e5256..9fd4a478e 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -471,8 +471,6 @@ export function unsubscribeAccountFail(error) { export function fetchFollowers(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; - dispatch(fetchFollowersRequest(id)); api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { @@ -561,8 +559,6 @@ export function expandFollowersFail(id, error) { export function fetchFollowing(id) { return (dispatch, getState) => { - if (!isLoggedIn(getState)) return; - dispatch(fetchFollowingRequest(id)); api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { diff --git a/app/soapbox/actions/soapbox.js b/app/soapbox/actions/soapbox.js index d2abc83a6..64fbe1891 100644 --- a/app/soapbox/actions/soapbox.js +++ b/app/soapbox/actions/soapbox.js @@ -50,6 +50,7 @@ export const makeDefaultConfig = features => { limit: 1, }), aboutPages: ImmutableMap(), + authenticatedProfile: true, }); }; diff --git a/app/soapbox/features/favourited_statuses/index.js b/app/soapbox/features/favourited_statuses/index.js index 233acf640..527afd2de 100644 --- a/app/soapbox/features/favourited_statuses/index.js +++ b/app/soapbox/features/favourited_statuses/index.js @@ -13,7 +13,7 @@ import MissingIndicator from 'soapbox/components/missing_indicator'; const mapStateToProps = (state, { params }) => { const username = params.username || ''; const me = state.get('me'); - const meUsername = state.getIn(['accounts', me, 'username']); + const meUsername = state.getIn(['accounts', me, 'username'], ''); return { isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()), statusIds: state.getIn(['status_lists', 'favourites', 'items']), diff --git a/app/soapbox/features/pinned_statuses/index.js b/app/soapbox/features/pinned_statuses/index.js index dd5447051..bfa57417e 100644 --- a/app/soapbox/features/pinned_statuses/index.js +++ b/app/soapbox/features/pinned_statuses/index.js @@ -12,7 +12,7 @@ import MissingIndicator from 'soapbox/components/missing_indicator'; const mapStateToProps = (state, { params }) => { const username = params.username || ''; const me = state.get('me'); - const meUsername = state.getIn(['accounts', me, 'username']); + const meUsername = state.getIn(['accounts', me, 'username'], ''); return { isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()), statusIds: state.getIn(['status_lists', 'pins', 'items']), diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js index b0199c449..70da9c18b 100644 --- a/app/soapbox/features/soapbox_config/index.js +++ b/app/soapbox/features/soapbox_config/index.js @@ -51,6 +51,8 @@ const messages = defineMessages({ displayFqnLabel: { id: 'soapbox_config.display_fqn_label', defaultMessage: 'Display domain (eg @user@domain) for local accounts.' }, greentextLabel: { id: 'soapbox_config.greentext_label', defaultMessage: 'Enable greentext support' }, promoPanelIconsLink: { id: 'soapbox_config.hints.promo_panel_icons.link', defaultMessage: 'Soapbox Icons List' }, + authenticatedProfileLabel: { id: 'soapbox_config.authenticated_profile_label', defaultMessage: 'Profiles require authentication' }, + authenticatedProfileHint: { id: 'soapbox_config.authenticated_profile_hint', defaultMessage: 'Users must be logged-in to view replies and media on user profiles.' }, }); const listenerOptions = supportsPassiveEvents ? { passive: true } : false; @@ -279,6 +281,13 @@ class SoapboxConfig extends ImmutablePureComponent { checked={soapbox.get('greentext') === true} onChange={this.handleChange(['greentext'], (e) => e.target.checked)} /> + e.target.checked)} + />
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 73644d6f4..af09692bb 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -7,6 +7,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { connect } from 'react-redux'; import { Switch, withRouter } from 'react-router-dom'; import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; import NotificationsContainer from './containers/notifications_container'; import LoadingBarContainer from './containers/loading_bar_container'; @@ -44,6 +45,7 @@ import ProfileHoverCard from 'soapbox/components/profile_hover_card'; import { getAccessToken } from 'soapbox/utils/auth'; import { getFeatures } from 'soapbox/utils/features'; import { fetchCustomEmojis } from 'soapbox/actions/custom_emojis'; +import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import { Status, @@ -121,6 +123,7 @@ const mapStateToProps = state => { const me = state.get('me'); const account = state.getIn(['accounts', me]); const instance = state.get('instance'); + const soapbox = getSoapboxConfig(state); return { dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null, @@ -129,6 +132,7 @@ const mapStateToProps = state => { me, account, features: getFeatures(instance), + soapbox, }; }; @@ -166,6 +170,7 @@ class SwitchingColumnsArea extends React.PureComponent { children: PropTypes.node, location: PropTypes.object, onLayoutChange: PropTypes.func.isRequired, + soapbox: ImmutablePropTypes.map.isRequired, }; state = { @@ -194,7 +199,8 @@ class SwitchingColumnsArea extends React.PureComponent { } render() { - const { children } = this.props; + const { children, soapbox } = this.props; + const authenticatedProfile = soapbox.get('authenticatedProfile'); return ( @@ -254,10 +260,10 @@ class SwitchingColumnsArea extends React.PureComponent { - - - - + + + + @@ -314,6 +320,7 @@ class UI extends React.PureComponent { streamingUrl: PropTypes.string, account: PropTypes.object, features: PropTypes.object.isRequired, + soapbox: ImmutablePropTypes.map.isRequired, }; state = { @@ -594,7 +601,7 @@ class UI extends React.PureComponent { } render() { - const { streamingUrl, features } = this.props; + const { streamingUrl, features, soapbox } = this.props; const { draggingOver, mobile } = this.state; const { intl, children, location, dropdownMenuIsOpen, me } = this.props; @@ -644,7 +651,7 @@ class UI extends React.PureComponent {
- + {children} From 50caa0d1d8a84d138b1a18eda5334080af56b3e1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 15:58:29 -0500 Subject: [PATCH 11/20] Webpack: replace file-loader with asset/resource asset module --- .../crypto_donate/utils/coin_icons.js | 22 +++---------------- webpack/rules/file.js | 19 ++++------------ 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/app/soapbox/features/crypto_donate/utils/coin_icons.js b/app/soapbox/features/crypto_donate/utils/coin_icons.js index 2c0376bfa..39fe39ded 100644 --- a/app/soapbox/features/crypto_donate/utils/coin_icons.js +++ b/app/soapbox/features/crypto_donate/utils/coin_icons.js @@ -1,20 +1,4 @@ -// Does some trickery to import all the icons into the project -// See: https://stackoverflow.com/questions/42118296/dynamically-import-images-from-a-directory-using-webpack - -const icons = {}; - -function importAll(r) { - const pathRegex = /\.\/(.*)\.svg/i; - - r.keys().forEach((key) => { - const ticker = pathRegex.exec(key)[1]; - return icons[ticker] = r(key).default; - }); -} - -importAll(require.context('cryptocurrency-icons/svg/color/', true, /\.svg$/)); - -export default icons; - // For getting the icon -export const getCoinIcon = ticker => icons[ticker] || icons.generic || null; +export const getCoinIcon = ticker => { + return require(`cryptocurrency-icons/svg/color/${ticker.toLowerCase()}.svg`); +}; diff --git a/webpack/rules/file.js b/webpack/rules/file.js index d23a0a977..47516e576 100644 --- a/webpack/rules/file.js +++ b/webpack/rules/file.js @@ -1,20 +1,9 @@ -const { join } = require('path'); const { settings } = require('../configuration'); module.exports = { test: new RegExp(`(${settings.static_assets_extensions.join('|')})$`, 'i'), - use: [ - { - loader: 'file-loader', - options: { - name(file) { - if (file.includes(settings.source_path)) { - return 'packs/media/[path][name]-[contenthash].[ext]'; - } - return 'packs/media/[folder]/[name]-[contenthash:8].[ext]'; - }, - context: join(settings.source_path), - }, - }, - ], + type: 'asset/resource', + generator: { + filename: 'packs/media/[contenthash][ext]', + }, }; From 19181f40c348f894af0002d15502d93fe98b607f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 17:15:57 -0500 Subject: [PATCH 12/20] Webpack: bundle Twemoji icons as assets --- app/soapbox/components/autosuggest_emoji.js | 4 +--- .../features/compose/components/emoji_picker_dropdown.js | 8 +++----- app/soapbox/features/emoji/emoji.js | 5 ++--- webpack/shared.js | 6 ------ 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/soapbox/components/autosuggest_emoji.js b/app/soapbox/components/autosuggest_emoji.js index 6311061b0..da2df72a3 100644 --- a/app/soapbox/components/autosuggest_emoji.js +++ b/app/soapbox/components/autosuggest_emoji.js @@ -1,8 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light'; -import { join } from 'path'; -import { FE_SUBDIRECTORY } from 'soapbox/build_config'; export default class AutosuggestEmoji extends React.PureComponent { @@ -23,7 +21,7 @@ export default class AutosuggestEmoji extends React.PureComponent { return null; } - url = join(FE_SUBDIRECTORY, 'emoji', `${mapping.filename}.svg`); + url = require(`twemoji/assets/svg/${mapping.filename}.svg`); } return ( diff --git a/app/soapbox/features/compose/components/emoji_picker_dropdown.js b/app/soapbox/features/compose/components/emoji_picker_dropdown.js index 2d9f155fd..9823978c9 100644 --- a/app/soapbox/features/compose/components/emoji_picker_dropdown.js +++ b/app/soapbox/features/compose/components/emoji_picker_dropdown.js @@ -7,8 +7,6 @@ import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { supportsPassiveEvents } from 'detect-passive-events'; import { buildCustomEmojis } from '../../emoji/emoji'; -import { join } from 'path'; -import { FE_SUBDIRECTORY } from 'soapbox/build_config'; const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, @@ -29,7 +27,7 @@ const messages = defineMessages({ let EmojiPicker, Emoji; // load asynchronously -const backgroundImageFn = () => join(FE_SUBDIRECTORY, 'emoji', 'sheet_13.png'); +const backgroundImageFn = () => require('emoji-datasource/img/twitter/sheets/32.png'); const listenerOptions = supportsPassiveEvents ? { passive: true } : false; const categoriesSort = [ @@ -358,8 +356,8 @@ class EmojiPickerDropdown extends React.PureComponent {
๐Ÿ™‚
diff --git a/app/soapbox/features/emoji/emoji.js b/app/soapbox/features/emoji/emoji.js index eb0df79a7..fe1029c59 100644 --- a/app/soapbox/features/emoji/emoji.js +++ b/app/soapbox/features/emoji/emoji.js @@ -1,7 +1,5 @@ import unicodeMapping from './emoji_unicode_mapping_light'; import Trie from 'substring-trie'; -import { join } from 'path'; -import { FE_SUBDIRECTORY } from 'soapbox/build_config'; const trie = new Trie(Object.keys(unicodeMapping)); @@ -62,7 +60,8 @@ const emojify = (str, customEmojis = {}, autoplay = false) => { } else { // matched to unicode emoji const { filename, shortCode } = unicodeMapping[match]; const title = shortCode ? `:${shortCode}:` : ''; - replacement = `${match}`; + const src = require(`twemoji/assets/svg/${filename}.svg`); + replacement = `${match}`; rend = i + match.length; // If the matched character was followed by VS15 (for selecting text presentation), skip it. if (str.codePointAt(rend) === 65038) { diff --git a/webpack/shared.js b/webpack/shared.js index 505554a6c..372a0423f 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -89,12 +89,6 @@ module.exports = { new HtmlWebpackHarddiskPlugin(), new CopyPlugin({ patterns: [{ - from: join(__dirname, '../node_modules/twemoji/assets/svg'), - to: join(output.path, 'emoji'), - }, { - from: join(__dirname, '../node_modules/emoji-datasource/img/twitter/sheets/32.png'), - to: join(output.path, 'emoji/sheet_13.png'), - }, { from: join(__dirname, '../app/sounds'), to: join(output.path, 'sounds'), }, { From 3359bda7f83afe8278dc1cc05e01505519fb46e5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 17:23:37 -0500 Subject: [PATCH 13/20] Webpack: bundle sounds as assets --- app/soapbox/middleware/sounds.js | 11 ++++------- webpack/shared.js | 3 --- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/soapbox/middleware/sounds.js b/app/soapbox/middleware/sounds.js index a2fc7572f..6950e7618 100644 --- a/app/soapbox/middleware/sounds.js +++ b/app/soapbox/middleware/sounds.js @@ -1,8 +1,5 @@ 'use strict'; -import { join } from 'path'; -import { FE_SUBDIRECTORY } from 'soapbox/build_config'; - const createAudio = sources => { const audio = new Audio(); sources.forEach(({ type, src }) => { @@ -31,21 +28,21 @@ export default function soundsMiddleware() { const soundCache = { boop: createAudio([ { - src: join(FE_SUBDIRECTORY, '/sounds/boop.ogg'), + src: require('../../sounds/boop.ogg'), type: 'audio/ogg', }, { - src: join(FE_SUBDIRECTORY, '/sounds/boop.mp3'), + src: require('../../sounds/boop.mp3'), type: 'audio/mpeg', }, ]), chat: createAudio([ { - src: join(FE_SUBDIRECTORY, '/sounds/chat.oga'), + src: require('../../sounds/chat.oga'), type: 'audio/ogg', }, { - src: join(FE_SUBDIRECTORY, '/sounds/chat.mp3'), + src: require('../../sounds/chat.mp3'), type: 'audio/mpeg', }, ]), diff --git a/webpack/shared.js b/webpack/shared.js index 372a0423f..d3414d6c0 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -89,9 +89,6 @@ module.exports = { new HtmlWebpackHarddiskPlugin(), new CopyPlugin({ patterns: [{ - from: join(__dirname, '../app/sounds'), - to: join(output.path, 'sounds'), - }, { from: join(__dirname, '../app/instance'), to: join(output.path, 'instance'), }], From 0aa0828e130fd0f6139f8ca24f7ac0d480738099 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 17:43:35 -0500 Subject: [PATCH 14/20] Webpack: really don't serve audio with sw.js --- webpack/production.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webpack/production.js b/webpack/production.js index 9968a2ca9..181d533e6 100644 --- a/webpack/production.js +++ b/webpack/production.js @@ -31,13 +31,12 @@ module.exports = merge(sharedConfig, { '**/*_polyfills-*.js', // the user may not need polyfills '**/*.chunk.js', // only cache chunks when needed '**/*.woff2', // the user may have system-fonts enabled - // images/audio can be cached on-demand + // images can be cached on-demand '**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.svg', '**/*.mp3', - '**/*.ogg', ], }, externals: [ @@ -66,6 +65,9 @@ module.exports = merge(sharedConfig, { '**/*.woff', // Sounds return a 206 causing sw.js to crash // https://stackoverflow.com/a/66335638 + '**/*.ogg', + '**/*.oga', + '**/*.mp3', 'sounds/**/*', // Don't cache index.html 'index.html', From e5fd60dbdb50c6703bab5666cc76402e46f76148 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 19:18:51 -0500 Subject: [PATCH 15/20] Webpack: reorganize module assets rules --- webpack/configuration.js | 1 - webpack/rules/assets.js | 50 ++++++++++++++++++++++++++++++++++++++++ webpack/rules/file.js | 9 -------- webpack/rules/index.js | 4 ++-- 4 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 webpack/rules/assets.js delete mode 100644 webpack/rules/file.js diff --git a/webpack/configuration.js b/webpack/configuration.js index 0089469c4..960e25e60 100644 --- a/webpack/configuration.js +++ b/webpack/configuration.js @@ -12,7 +12,6 @@ const settings = { test_root_path: `${FE_BUILD_DIR}-test`, cache_path: 'tmp/cache', resolved_paths: [], - static_assets_extensions: [ '.jpg', '.jpeg', '.png', '.tiff', '.ico', '.svg', '.gif', '.eot', '.otf', '.ttf', '.woff', '.woff2', '.mp3', '.ogg', '.oga' ], extensions: [ '.mjs', '.js', '.sass', '.scss', '.css', '.module.sass', '.module.scss', '.module.css', '.png', '.svg', '.gif', '.jpeg', '.jpg' ], }; diff --git a/webpack/rules/assets.js b/webpack/rules/assets.js new file mode 100644 index 000000000..2c6fb3f0d --- /dev/null +++ b/webpack/rules/assets.js @@ -0,0 +1,50 @@ +// Asset modules +// https://webpack.js.org/guides/asset-modules/ + +const { resolve } = require('path'); + +// These are processed in reverse-order +// We use the name 'packs' instead of 'assets' for legacy reasons +module.exports = [{ + test: /\.(png|svg)/, + type: 'asset/resource', + include: [ + resolve('app', 'images'), + resolve('node_modules', 'emoji-datasource'), + ], + generator: { + filename: 'packs/images/[name]-[contenthash:8][ext]', + }, +}, { + test: /\.(ttf|eot|svg|woff|woff2)/, + type: 'asset/resource', + include: [ + resolve('app', 'fonts'), + resolve('node_modules', 'fork-awesome'), + resolve('node_modules', '@fontsource'), + ], + generator: { + filename: 'packs/fonts/[name]-[contenthash:8][ext]', + }, +}, { + test: /\.(ogg|oga|mp3)/, + type: 'asset/resource', + include: resolve('app', 'sounds'), + generator: { + filename: 'packs/sounds/[name]-[contenthash:8][ext]', + }, +}, { + test: /\.svg$/, + type: 'asset/resource', + include: resolve('node_modules', 'twemoji'), + generator: { + filename: 'packs/emoji/[name]-[contenthash:8][ext]', + }, +}, { + test: /\.svg$/, + type: 'asset/resource', + include: resolve('node_modules', 'cryptocurrency-icons'), + generator: { + filename: 'packs/images/crypto/[name]-[contenthash:8][ext]', + }, +}]; diff --git a/webpack/rules/file.js b/webpack/rules/file.js deleted file mode 100644 index 47516e576..000000000 --- a/webpack/rules/file.js +++ /dev/null @@ -1,9 +0,0 @@ -const { settings } = require('../configuration'); - -module.exports = { - test: new RegExp(`(${settings.static_assets_extensions.join('|')})$`, 'i'), - type: 'asset/resource', - generator: { - filename: 'packs/media/[contenthash][ext]', - }, -}; diff --git a/webpack/rules/index.js b/webpack/rules/index.js index 91a4abd19..d3290659e 100644 --- a/webpack/rules/index.js +++ b/webpack/rules/index.js @@ -3,14 +3,14 @@ const git = require('./babel-git'); const gitRefresh = require('./git-refresh'); const buildConfig = require('./babel-build-config'); const css = require('./css'); -const file = require('./file'); +const assets = require('./assets'); const nodeModules = require('./node_modules'); // Webpack loaders are processed in reverse order // https://webpack.js.org/concepts/loaders/#loader-features // Lastly, process static files using file loader module.exports = [ - file, + ...assets, css, nodeModules, babel, From ec474ba4c24a176e0d3fd6f411814255e46e5cfd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 19:30:47 -0500 Subject: [PATCH 16/20] Webpack: use one entrypoint --- app/application.js | 3 +++ webpack/shared.js | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/application.js b/app/application.js index a6d4ed199..96cedfeef 100644 --- a/app/application.js +++ b/app/application.js @@ -2,6 +2,9 @@ import loadPolyfills from './soapbox/load_polyfills'; require.context('./images/', true); +// Load stylesheet +require('./styles/application.scss'); + loadPolyfills().then(() => { require('./soapbox/main').default(); }).catch(e => { diff --git a/webpack/shared.js b/webpack/shared.js index d3414d6c0..0c4bd856e 100644 --- a/webpack/shared.js +++ b/webpack/shared.js @@ -30,10 +30,9 @@ const makeHtmlConfig = (params = {}) => { }; module.exports = { - entry: Object.assign( - { application: resolve('app/application.js') }, - { styles: resolve(join(settings.source_path, 'styles/application.scss')) }, - ), + entry: { + application: resolve('app/application.js'), + }, output: { filename: 'packs/js/[name]-[chunkhash].js', From 1c39e1b086022e31df6a62d1b56ae6c4937af8c6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 19:39:39 -0500 Subject: [PATCH 17/20] Webpack: fix some warnings from OfflinePlugin --- webpack/production.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/webpack/production.js b/webpack/production.js index 181d533e6..b633ec8ee 100644 --- a/webpack/production.js +++ b/webpack/production.js @@ -33,10 +33,7 @@ module.exports = merge(sharedConfig, { '**/*.woff2', // the user may have system-fonts enabled // images can be cached on-demand '**/*.png', - '**/*.jpg', - '**/*.jpeg', '**/*.svg', - '**/*.mp3', ], }, externals: [ @@ -77,6 +74,7 @@ module.exports = merge(sharedConfig, { // cacheName: 'soapbox', // minify: true, // }, + safeToUseOptionalCaches: true, }), ], }); From a720f3ea32d52fb6872f0235a45c5931d6da9d15 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 20:11:36 -0500 Subject: [PATCH 18/20] Webpack: OfflinePlugin improvements --- webpack/production.js | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/webpack/production.js b/webpack/production.js index b633ec8ee..cccfc4163 100644 --- a/webpack/production.js +++ b/webpack/production.js @@ -25,7 +25,19 @@ module.exports = merge(sharedConfig, { new OfflinePlugin({ caches: { main: [':rest:'], - additional: [':externals:'], + additional: [ + 'packs/emoji/1f602-*.svg', // used for emoji picker dropdown + 'packs/images/32-*.png', // used in emoji-mart + + // Default emoji reacts + 'packs/emoji/1f44d-*.svg', // Thumbs up + 'packs/emoji/2764-*.svg', // Heart + 'packs/emoji/1f606-*.svg', // Laughing + 'packs/emoji/1f62e-*.svg', // Surprised + 'packs/emoji/1f622-*.svg', // Crying + 'packs/emoji/1f629-*.svg', // Weary + 'packs/emoji/1f621-*.svg', // Angry (Spinster) + ], optional: [ '**/locale_*.js', // don't fetch every locale; the user only needs one '**/*_polyfills-*.js', // the user may not need polyfills @@ -36,22 +48,10 @@ module.exports = merge(sharedConfig, { '**/*.svg', ], }, - externals: [ - '/emoji/1f602.svg', // used for emoji picker dropdown - '/emoji/sheet_13.png', // used in emoji-mart - - // Default emoji reacts - '/emoji/1f44d.svg', // Thumbs up - '/emoji/2764.svg', // Heart - '/emoji/1f606.svg', // Laughing - '/emoji/1f62e.svg', // Surprised - '/emoji/1f622.svg', // Crying - '/emoji/1f629.svg', // Weary - '/emoji/1f621.svg', // Angry (Spinster) - ], excludes: [ '**/*.gz', '**/*.map', + '**/*.LICENSE.txt', 'stats.json', 'report.html', 'instance/**/*', @@ -65,9 +65,10 @@ module.exports = merge(sharedConfig, { '**/*.ogg', '**/*.oga', '**/*.mp3', - 'sounds/**/*', - // Don't cache index.html + // Don't serve index.html + // https://github.com/bromite/bromite/issues/1294 'index.html', + '404.html', ], // ServiceWorker: { // entry: join(__dirname, '../app/soapbox/service_worker/entry.js'), From 35d18fbe333ec36da4ecc0de0af2d3f5de6ff611 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 6 Sep 2021 19:03:23 -0500 Subject: [PATCH 19/20] Jest: fix SVG import breaking tests --- jest.config.js | 7 +++++++ package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/jest.config.js b/jest.config.js index d7b2fb44a..efe164457 100644 --- a/jest.config.js +++ b/jest.config.js @@ -31,4 +31,11 @@ module.exports = { '/app', ], 'testEnvironment': 'jsdom', + 'moduleNameMapper': { + '^.+.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$': 'jest-transform-stub', + }, + 'transform': { + '\\.[jt]sx?$': 'babel-jest', + '.+\\.(css|styl|less|sass|scss|png|jpg|svg|ttf|woff|woff2)$': 'jest-transform-stub', + }, }; diff --git a/package.json b/package.json index 018088f2d..419294299 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "intl-messageformat-parser": "^6.0.0", "intl-pluralrules": "^1.3.0", "is-nan": "^1.2.1", + "jest-transform-stub": "^2.0.0", "jsdoc": "~3.6.7", "lodash": "^4.7.11", "mark-loader": "^0.1.6", diff --git a/yarn.lock b/yarn.lock index 4876a41de..9e75304e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7239,6 +7239,11 @@ jest-snapshot@^27.1.0: pretty-format "^27.1.0" semver "^7.3.2" +jest-transform-stub@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz#19018b0851f7568972147a5d60074b55f0225a7d" + integrity sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg== + jest-util@^27.0.0: version "27.0.6" resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.0.6.tgz#e8e04eec159de2f4d5f57f795df9cdc091e50297" From d0630c765f9eaba6970de76b14adf0641c5ffad6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 10 Sep 2021 20:37:13 -0500 Subject: [PATCH 20/20] Jest: update emoji tests --- .../autosuggest_emoji-test.js.snap | 6 +++- .../__snapshots__/emoji_selector-test.js.snap | 12 +++---- .../features/emoji/__tests__/emoji-test.js | 32 +++++++++---------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/soapbox/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap b/app/soapbox/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap index 1c3727848..6bbb1eb74 100644 --- a/app/soapbox/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap +++ b/app/soapbox/components/__tests__/__snapshots__/autosuggest_emoji-test.js.snap @@ -20,7 +20,11 @@ exports[` renders native emoji 1`] = ` ๐Ÿ’™ :foobar:
diff --git a/app/soapbox/components/__tests__/__snapshots__/emoji_selector-test.js.snap b/app/soapbox/components/__tests__/__snapshots__/emoji_selector-test.js.snap index 06d7764ec..d009a5551 100644 --- a/app/soapbox/components/__tests__/__snapshots__/emoji_selector-test.js.snap +++ b/app/soapbox/components/__tests__/__snapshots__/emoji_selector-test.js.snap @@ -15,7 +15,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"๐Ÿ‘\\"", + "__html": "\\"๐Ÿ‘\\"", } } onClick={[Function]} @@ -26,7 +26,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"โค\\"", + "__html": "\\"โค\\"", } } onClick={[Function]} @@ -37,7 +37,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"๐Ÿ˜†\\"", + "__html": "\\"๐Ÿ˜†\\"", } } onClick={[Function]} @@ -48,7 +48,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"๐Ÿ˜ฎ\\"", + "__html": "\\"๐Ÿ˜ฎ\\"", } } onClick={[Function]} @@ -59,7 +59,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"๐Ÿ˜ข\\"", + "__html": "\\"๐Ÿ˜ข\\"", } } onClick={[Function]} @@ -70,7 +70,7 @@ exports[` renders correctly 1`] = ` className="emoji-react-selector__emoji" dangerouslySetInnerHTML={ Object { - "__html": "\\"๐Ÿ˜ฉ\\"", + "__html": "\\"๐Ÿ˜ฉ\\"", } } onClick={[Function]} diff --git a/app/soapbox/features/emoji/__tests__/emoji-test.js b/app/soapbox/features/emoji/__tests__/emoji-test.js index c8425c4c6..ce8d4e2a8 100644 --- a/app/soapbox/features/emoji/__tests__/emoji-test.js +++ b/app/soapbox/features/emoji/__tests__/emoji-test.js @@ -22,23 +22,23 @@ describe('emoji', () => { it('does unicode', () => { expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual( - '๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ'); + '๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ'); expect(emojify('๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง')).toEqual( - '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง'); - expect(emojify('๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ')).toEqual('๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ'); + '๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง'); + expect(emojify('๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ')).toEqual('๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ'); expect(emojify('\u2757')).toEqual( - 'โ—'); + 'โ—'); }); it('does multiple unicode', () => { expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual( - 'โ— #๏ธโƒฃ'); + 'โ— #๏ธโƒฃ'); expect(emojify('\u2757#\uFE0F\u20E3')).toEqual( - 'โ—#๏ธโƒฃ'); + 'โ—#๏ธโƒฃ'); expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual( - 'โ— #๏ธโƒฃ โ—'); + 'โ— #๏ธโƒฃ โ—'); expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual( - 'foo โ— #๏ธโƒฃ bar'); + 'foo โ— #๏ธโƒฃ bar'); }); it('ignores unicode inside of tags', () => { @@ -46,16 +46,16 @@ describe('emoji', () => { }); it('does multiple emoji properly (issue 5188)', () => { - expect(emojify('๐Ÿ‘Œ๐ŸŒˆ๐Ÿ’•')).toEqual('๐Ÿ‘Œ๐ŸŒˆ๐Ÿ’•'); - expect(emojify('๐Ÿ‘Œ ๐ŸŒˆ ๐Ÿ’•')).toEqual('๐Ÿ‘Œ ๐ŸŒˆ ๐Ÿ’•'); + expect(emojify('๐Ÿ‘Œ๐ŸŒˆ๐Ÿ’•')).toEqual('๐Ÿ‘Œ๐ŸŒˆ๐Ÿ’•'); + expect(emojify('๐Ÿ‘Œ ๐ŸŒˆ ๐Ÿ’•')).toEqual('๐Ÿ‘Œ ๐ŸŒˆ ๐Ÿ’•'); }); it('does an emoji that has no shortcode', () => { - expect(emojify('๐Ÿ‘โ€๐Ÿ—จ')).toEqual('๐Ÿ‘โ€๐Ÿ—จ'); + expect(emojify('๐Ÿ‘โ€๐Ÿ—จ')).toEqual('๐Ÿ‘โ€๐Ÿ—จ'); }); it('does an emoji whose filename is irregular', () => { - expect(emojify('โ†™๏ธ')).toEqual('โ†™๏ธ'); + expect(emojify('โ†™๏ธ')).toEqual('โ†™๏ธ'); }); it('avoid emojifying on invisible text', () => { @@ -67,16 +67,16 @@ describe('emoji', () => { it('avoid emojifying on invisible text with nested tags', () => { expect(emojify('๐Ÿ˜‡')) - .toEqual('๐Ÿ˜‡'); + .toEqual('๐Ÿ˜‡'); expect(emojify('๐Ÿ˜‡')) - .toEqual('๐Ÿ˜‡'); + .toEqual('๐Ÿ˜‡'); expect(emojify('๐Ÿ˜‡')) - .toEqual('๐Ÿ˜‡'); + .toEqual('๐Ÿ˜‡'); }); it('skips the textual presentation VS15 character', () => { expect(emojify('โœด๏ธŽ')) // This is U+2734 EIGHT POINTED BLACK STAR then U+FE0E VARIATION SELECTOR-15 - .toEqual('โœด'); + .toEqual('โœด'); }); }); });