From 1130373c0207f76f9dc0ee2729992b6a9bf17f81 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 19 Feb 2022 22:49:12 -0500 Subject: [PATCH 1/4] pendingStatus: fix crash when replying to self --- app/soapbox/actions/compose.js | 2 +- app/soapbox/reducers/compose.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/actions/compose.js b/app/soapbox/actions/compose.js index 04b1c3cd7..3361188b6 100644 --- a/app/soapbox/actions/compose.js +++ b/app/soapbox/actions/compose.js @@ -219,7 +219,7 @@ export function submitCompose(routerHistory, force = false) { const status = state.getIn(['compose', 'text'], ''); const media = state.getIn(['compose', 'media_attachments']); - let to = state.getIn(['compose', 'to'], null); + let to = state.getIn(['compose', 'to']); if (!validateSchedule(state)) { dispatch(snackbar.error(messages.scheduleError)); diff --git a/app/soapbox/reducers/compose.js b/app/soapbox/reducers/compose.js index c0ee4af0b..57796af63 100644 --- a/app/soapbox/reducers/compose.js +++ b/app/soapbox/reducers/compose.js @@ -428,7 +428,7 @@ export default function compose(state = initialState, action) { case REDRAFT: return state.withMutations(map => { map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status))); - map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : null); + map.set('to', action.explicitAddressing ? getExplicitMentions(action.status.get('account', 'id'), action.status) : undefined); map.set('in_reply_to', action.status.get('in_reply_to_id')); map.set('privacy', action.status.get('visibility')); // TODO: Actually fix this rather than just removing it From 2635d9b109df9c3959befdfd99e04d5e35edc592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Mon, 21 Feb 2022 09:56:23 +0100 Subject: [PATCH 2/4] Compose: Show 'Replying to a post' if unchecked all mentions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../compose/components/reply_mentions.js | 16 ++++++++++++++-- .../containers/reply_mentions_container.js | 7 +++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/compose/components/reply_mentions.js b/app/soapbox/features/compose/components/reply_mentions.js index a6ebbbc7e..5bc6615ac 100644 --- a/app/soapbox/features/compose/components/reply_mentions.js +++ b/app/soapbox/features/compose/components/reply_mentions.js @@ -11,6 +11,7 @@ class ReplyMentions extends ImmutablePureComponent { onOpenMentionsModal: PropTypes.func.isRequired, explicitAddressing: PropTypes.bool, to: ImmutablePropTypes.orderedSet, + parentTo: ImmutablePropTypes.orderedSet, isReply: PropTypes.bool, }; @@ -21,12 +22,23 @@ class ReplyMentions extends ImmutablePureComponent { } render() { - const { explicitAddressing, to, isReply } = this.props; + const { explicitAddressing, to, parentTo, isReply } = this.props; - if (!explicitAddressing || !isReply || !to || to.size === 0) { + if (!explicitAddressing || !isReply || !to || (parentTo.size === 0)) { return null; } + if (to.size === 0) { + return ( + + + + ); + } + return ( { } const to = state.getIn(['compose', 'to']); + const me = state.get('me'); + const account = state.getIn(['accounts', me]); + + const parentTo = statusToMentionsAccountIdsArray(state, status, account); + return { to, + parentTo, isReply: true, explicitAddressing: true, }; From f9b934d8f5e77652bcd331c36d91b876110d0032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 22 Feb 2022 08:52:33 +0100 Subject: [PATCH 3/4] Fix 'View context' in media modals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/modal_root.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/components/modal_root.js b/app/soapbox/components/modal_root.js index 9cda3b890..99b6b1c0e 100644 --- a/app/soapbox/components/modal_root.js +++ b/app/soapbox/components/modal_root.js @@ -161,7 +161,7 @@ class ModalRoot extends React.PureComponent { if (this.unlistenHistory) { this.unlistenHistory(); } - if (!['FAVOURITES', 'MENTIONS', 'REACTIONS', 'REBLOGS'].includes(type)) { + if (!['FAVOURITES', 'MENTIONS', 'REACTIONS', 'REBLOGS', 'MEDIA'].includes(type)) { const { state } = this.history.location; if (state && state.soapboxModalKey === this._modalHistoryKey) { this.history.goBack(); From 16da9030ac0754638d105448810ac3d78ddb2092 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 23 Feb 2022 11:24:36 -0500 Subject: [PATCH 4/4] normalizeInstance: break out instance normalization into its own module --- .../normalizers/__tests__/instance-test.js | 101 ++++++++++++++++++ app/soapbox/normalizers/instance.js | 65 +++++++++++ app/soapbox/reducers/instance.js | 66 +----------- 3 files changed, 169 insertions(+), 63 deletions(-) create mode 100644 app/soapbox/normalizers/__tests__/instance-test.js create mode 100644 app/soapbox/normalizers/instance.js diff --git a/app/soapbox/normalizers/__tests__/instance-test.js b/app/soapbox/normalizers/__tests__/instance-test.js new file mode 100644 index 000000000..28eea3191 --- /dev/null +++ b/app/soapbox/normalizers/__tests__/instance-test.js @@ -0,0 +1,101 @@ +import { Map as ImmutableMap, fromJS } from 'immutable'; + +import { normalizeInstance } from '../instance'; + +describe('normalizeInstance()', () => { + it('normalizes an empty Map', () => { + const expected = ImmutableMap({ + description_limit: 1500, + configuration: ImmutableMap({ + statuses: ImmutableMap({ + max_characters: 500, + max_media_attachments: 4, + }), + polls: ImmutableMap({ + max_options: 4, + max_characters_per_option: 25, + min_expiration: 300, + max_expiration: 2629746, + }), + }), + version: '0.0.0', + }); + + const result = normalizeInstance(ImmutableMap()); + expect(result).toEqual(expected); + }); + + it('normalizes Pleroma instance with Mastodon configuration format', () => { + const instance = fromJS(require('soapbox/__fixtures__/pleroma-instance.json')); + + const expected = { + configuration: { + statuses: { + max_characters: 5000, + max_media_attachments: Infinity, + }, + polls: { + max_options: 20, + max_characters_per_option: 200, + min_expiration: 0, + max_expiration: 31536000, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); + + it('normalizes Mastodon instance with retained configuration', () => { + const instance = fromJS(require('soapbox/__fixtures__/mastodon-instance.json')); + + const expected = { + configuration: { + statuses: { + max_characters: 500, + max_media_attachments: 4, + characters_reserved_per_url: 23, + }, + media_attachments: { + image_size_limit: 10485760, + image_matrix_limit: 16777216, + video_size_limit: 41943040, + video_frame_rate_limit: 60, + video_matrix_limit: 2304000, + }, + polls: { + max_options: 4, + max_characters_per_option: 50, + min_expiration: 300, + max_expiration: 2629746, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); + + it('normalizes Mastodon 3.0.0 instance with default configuration', () => { + const instance = fromJS(require('soapbox/__fixtures__/mastodon-3.0.0-instance.json')); + + const expected = { + configuration: { + statuses: { + max_characters: 500, + max_media_attachments: 4, + }, + polls: { + max_options: 4, + max_characters_per_option: 25, + min_expiration: 300, + max_expiration: 2629746, + }, + }, + }; + + const result = normalizeInstance(instance); + expect(result.toJS()).toMatchObject(expected); + }); +}); diff --git a/app/soapbox/normalizers/instance.js b/app/soapbox/normalizers/instance.js new file mode 100644 index 000000000..d95990670 --- /dev/null +++ b/app/soapbox/normalizers/instance.js @@ -0,0 +1,65 @@ +import { Map as ImmutableMap } from 'immutable'; + +import { parseVersion, PLEROMA } from 'soapbox/utils/features'; +import { isNumber } from 'soapbox/utils/numbers'; + +// Use Mastodon defaults +const baseInstance = ImmutableMap({ + description_limit: 1500, + configuration: ImmutableMap({ + statuses: ImmutableMap({ + max_characters: 500, + max_media_attachments: 4, + }), + polls: ImmutableMap({ + max_options: 4, + max_characters_per_option: 25, + min_expiration: 300, + max_expiration: 2629746, + }), + }), + version: '0.0.0', +}); + +// Build Mastodon configuration from Pleroma instance +const pleromaToMastodonConfig = instance => { + return ImmutableMap({ + statuses: ImmutableMap({ + max_characters: instance.get('max_toot_chars'), + }), + polls: ImmutableMap({ + max_options: instance.getIn(['poll_limits', 'max_options']), + max_characters_per_option: instance.getIn(['poll_limits', 'max_option_chars']), + min_expiration: instance.getIn(['poll_limits', 'min_expiration']), + max_expiration: instance.getIn(['poll_limits', 'max_expiration']), + }), + }); +}; + +// Use new value only if old value is undefined +const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; + +// Get the software's default attachment limit +const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; + +// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format +export const normalizeInstance = instance => { + const { software } = parseVersion(instance.get('version')); + const mastodonConfig = pleromaToMastodonConfig(instance); + + return instance.withMutations(instance => { + // Merge configuration + instance.update('configuration', ImmutableMap(), configuration => ( + configuration.mergeDeepWith(mergeDefined, mastodonConfig) + )); + + // If max attachments isn't set, check the backend software + instance.updateIn(['configuration', 'statuses', 'max_media_attachments'], value => { + return isNumber(value) ? value : getAttachmentLimit(software); + }); + + // Merge defaults & cleanup + instance.mergeDeepWith(mergeDefined, baseInstance); + instance.deleteAll(['max_toot_chars', 'poll_limits']); + }); +}; diff --git a/app/soapbox/reducers/instance.js b/app/soapbox/reducers/instance.js index 8a79a2869..ac28e88ee 100644 --- a/app/soapbox/reducers/instance.js +++ b/app/soapbox/reducers/instance.js @@ -2,10 +2,9 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { ADMIN_CONFIG_UPDATE_REQUEST, ADMIN_CONFIG_UPDATE_SUCCESS } from 'soapbox/actions/admin'; import { PLEROMA_PRELOAD_IMPORT } from 'soapbox/actions/preload'; +import { normalizeInstance } from 'soapbox/normalizers/instance'; import KVStore from 'soapbox/storage/kv_store'; import { ConfigDB } from 'soapbox/utils/config_db'; -import { parseVersion, PLEROMA } from 'soapbox/utils/features'; -import { isNumber } from 'soapbox/utils/numbers'; import { INSTANCE_REMEMBER_SUCCESS, @@ -14,6 +13,8 @@ import { NODEINFO_FETCH_SUCCESS, } from '../actions/instance'; +const initialState = normalizeInstance(ImmutableMap()); + const nodeinfoToInstance = nodeinfo => { // Match Pleroma's develop branch return ImmutableMap({ @@ -30,67 +31,6 @@ const nodeinfoToInstance = nodeinfo => { }); }; -// Use Mastodon defaults -const initialState = ImmutableMap({ - description_limit: 1500, - configuration: ImmutableMap({ - statuses: ImmutableMap({ - max_characters: 500, - max_media_attachments: 4, - }), - polls: ImmutableMap({ - max_options: 4, - max_characters_per_option: 25, - min_expiration: 300, - max_expiration: 2629746, - }), - }), - version: '0.0.0', -}); - -// Build Mastodon configuration from Pleroma instance -const pleromaToMastodonConfig = instance => { - return ImmutableMap({ - statuses: ImmutableMap({ - max_characters: instance.get('max_toot_chars'), - }), - polls: ImmutableMap({ - max_options: instance.getIn(['poll_limits', 'max_options']), - max_characters_per_option: instance.getIn(['poll_limits', 'max_option_chars']), - min_expiration: instance.getIn(['poll_limits', 'min_expiration']), - max_expiration: instance.getIn(['poll_limits', 'max_expiration']), - }), - }); -}; - -// Use new value only if old value is undefined -const mergeDefined = (oldVal, newVal) => oldVal === undefined ? newVal : oldVal; - -// Get the software's default attachment limit -const getAttachmentLimit = software => software === PLEROMA ? Infinity : 4; - -// Normalize instance (Pleroma, Mastodon, etc.) to Mastodon's format -const normalizeInstance = instance => { - const { software } = parseVersion(instance.get('version')); - const mastodonConfig = pleromaToMastodonConfig(instance); - - return instance.withMutations(instance => { - // Merge configuration - instance.update('configuration', ImmutableMap(), configuration => ( - configuration.mergeDeepWith(mergeDefined, mastodonConfig) - )); - - // If max attachments isn't set, check the backend software - instance.updateIn(['configuration', 'statuses', 'max_media_attachments'], value => { - return isNumber(value) ? value : getAttachmentLimit(software); - }); - - // Merge defaults & cleanup - instance.mergeDeepWith(mergeDefined, initialState); - instance.deleteAll(['max_toot_chars', 'poll_limits']); - }); -}; - const importInstance = (state, instance) => { return normalizeInstance(instance); };