From: kibigo! Date: Sun, 13 Nov 2022 08:02:35 +0000 (-0800) Subject: Fix inline spoiler text in modals X-Git-Url: https://git.xn--scling-oua.cat.family/?a=commitdiff_plain;h=7d64057ac99096e9dcad783d0298b2775753e35b;p=mastodon.git Fix inline spoiler text in modals This requires “connecting” ``, which isn’t “great” but is basically necessary at this point. On the plus side, this makes the code a bit DRYer. Due to the way modals work, updating status contents doesn’t actually trigger a React rerender. This is actually fine, because we (have to) update status contents live anyway, but it does require ensuring that appropriate event listeners get attached so that the spoiler can be toggled again. To keep listeners from being added multiple times (which wouldn’t have a negative effect but could be a performance leak), elements with listeners are now tracked in a `WeakSet`. --- diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 1bb0d72a4..147a42693 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -4,7 +4,7 @@ import PropTypes from 'prop-types'; import StatusPrepend from './status_prepend'; import StatusHeader from './status_header'; import StatusIcons from './status_icons'; -import StatusContent from './status_content'; +import StatusContentContainer from 'flavours/glitch/containers/status_content_container'; import StatusActionBar from './status_action_bar'; import AttachmentList from './attachment_list'; import Card from '../features/status/components/card'; @@ -775,14 +775,13 @@ class Status extends ImmutablePureComponent { settings={settings.get('status_icons')} /> - { return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host)); }; -@injectIntl -export default class StatusContent extends React.PureComponent { +const listening = new WeakSet(); + +export default class StatusContent extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map.isRequired, @@ -91,6 +93,29 @@ export default class StatusContent extends React.PureComponent { hidden: true, }; + /** Define listeners for inline spoiler text elements. */ + _defineSpoilerTextListeners () { + const node = this.contentsNode; + const { onToggleSpoilerText } = this.props; + if (node && onToggleSpoilerText) { + [...node.querySelectorAll('.spoilertext')].forEach((elt) => { + if (listening.has(elt)) return; + elt.querySelector('button').addEventListener( + 'click', + (e) => { + // Ordinarily, calling the `onSpoilerTextClict(elt)` method + // will trigger a rerender, but it may not in the case of + // modals. This means we may need to do a manual redefining + // of spoiler text listeners. + this.onSpoilerTextClick(elt, e); + this._defineSpoilerTextListeners(); + }, + ); + listening.add(elt); + }); + } + } + _updateStatusLinks () { const node = this.contentsNode; const { tagLinks, rewriteMentions } = this.props; @@ -99,13 +124,6 @@ export default class StatusContent extends React.PureComponent { return; } - [...node.querySelectorAll('.spoilertext')].forEach((elt) => { - elt.querySelector('button').addEventListener( - 'click', - this.onSpoilerTextClick.bind(this, elt), - ); - }); - const links = node.querySelectorAll('a'); for (var i = 0; i < links.length; ++i) { @@ -193,10 +211,12 @@ export default class StatusContent extends React.PureComponent { componentDidMount () { const node = this.contentsNode; - if (node) { + const { onToggleSpoilerText } = this.props; + if (node && onToggleSpoilerText) { // Replace spoiler texts with their internationalized versions. [...node.querySelectorAll('.spoilertext')].forEach((elt) => { - this.props.onToggleSpoilerText( + if (node.querySelector('.spoilertext--button').title) return; + onToggleSpoilerText( this.props.status, node, elt, @@ -208,10 +228,12 @@ export default class StatusContent extends React.PureComponent { // The `.onToggleSpoilerText()` method actually replaces the // `.spoilertext` elements, so we need to call this *after*. this._updateStatusLinks(); + this._defineSpoilerTextListeners(); } componentDidUpdate () { this._updateStatusLinks(); + this._defineSpoilerTextListeners(); if (this.props.onUpdate) this.props.onUpdate(); } diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index ee2e5c644..c50b6be4a 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -24,7 +24,6 @@ import { hideStatus, revealStatus, editStatus, - modifyStatusBody, } from 'flavours/glitch/actions/statuses'; import { initAddFilter, @@ -43,7 +42,6 @@ import { showAlertForError } from '../actions/alerts'; import AccountContainer from 'flavours/glitch/containers/account_container'; import Spoilers from '../components/spoilers'; import Icon from 'flavours/glitch/components/icon'; -import spoilertextify from 'flavours/glitch/utils/spoilertextify'; const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, @@ -234,26 +232,6 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ } }, - onToggleSpoilerText (status, oldBody, spoilerElement, intl, open) { - spoilerElement.replaceWith(spoilertextify( - spoilerElement.getAttribute('data-spoilertext-content'), - { - emojos: status.get('emojis').reduce((obj, emoji) => { - obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); - return obj; - }, {}), - intl, - open: open == null - ? !spoilerElement.classList.contains('open') - : !!open, - }, - )); - dispatch(modifyStatusBody( - status.get('id'), - oldBody.innerHTML, - )); - }, - deployPictureInPicture (status, type, mediaProps) { dispatch((_, getState) => { if (getState().getIn(['local_settings', 'media', 'pop_in_player'])) { diff --git a/app/javascript/flavours/glitch/containers/status_content_container.js b/app/javascript/flavours/glitch/containers/status_content_container.js new file mode 100644 index 000000000..05541ca17 --- /dev/null +++ b/app/javascript/flavours/glitch/containers/status_content_container.js @@ -0,0 +1,36 @@ +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import StatusContent from 'flavours/glitch/components/status_content'; +import { modifyStatusBody } from 'flavours/glitch/actions/statuses'; +import spoilertextify from 'flavours/glitch/utils/spoilertextify'; + +const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ + /** + * Modifies the spoiler to be open or closed and then rewrites the + * HTML of the status to reflect that state. + * + * This will also save any other changes to the HTML, for example + * link rewriting. + */ + onToggleSpoilerText (status, oldBody, spoilerElement, intl, open) { + spoilerElement.replaceWith(spoilertextify( + spoilerElement.getAttribute('data-spoilertext-content'), + { + emojos: status.get('emojis').reduce((obj, emoji) => { + obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); + return obj; + }, {}), + intl, + open: open == null + ? !spoilerElement.classList.contains('open') + : !!open, + }, + )); + dispatch(modifyStatusBody( + status.get('id'), + oldBody.innerHTML, + )); + }, +}); + +export default injectIntl(connect(null, mapDispatchToProps)(StatusContent)); \ No newline at end of file diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js index 7107c9db3..3db7e7651 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import StatusContent from 'flavours/glitch/components/status_content'; +import StatusContentContainer from 'flavours/glitch/containers/status_content_container'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; @@ -199,7 +199,7 @@ class Conversation extends ImmutablePureComponent { - · - } /> + } /> ); diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js index 9bd4e0f24..566b953ff 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from 'flavours/glitch/components/avatar'; import DisplayName from 'flavours/glitch/components/display_name'; -import StatusContent from 'flavours/glitch/components/status_content'; +import StatusContentContainer from 'flavours/glitch/containers/status_content_container'; import MediaGallery from 'flavours/glitch/components/media_gallery'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import { Link } from 'react-router-dom'; @@ -35,7 +35,6 @@ class DetailedStatus extends ImmutablePureComponent { onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, onToggleHidden: PropTypes.func, - onToggleSpoilerText: PropTypes.func, expanded: PropTypes.bool, measureHeight: PropTypes.bool, onHeightChange: PropTypes.func, @@ -116,7 +115,7 @@ class DetailedStatus extends ImmutablePureComponent { render () { const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; - const { expanded, onToggleHidden, onToggleSpoilerText, settings, usingPiP, intl } = this.props; + const { expanded, onToggleHidden, settings, usingPiP, intl } = this.props; const outerStyle = { boxSizing: 'border-box' }; const { compact } = this.props; @@ -299,7 +298,7 @@ class DetailedStatus extends ImmutablePureComponent { - { - spoilerElement.replaceWith(spoilertextify( - spoilerElement.getAttribute('data-spoilertext-content'), - { - emojos: status.get('emojis').reduce((obj, emoji) => { - obj[`:${emoji.get('shortcode')}:`] = emoji.toJS(); - return obj; - }, {}), - intl, - open: open == null - ? !spoilerElement.classList.contains('open') - : !!open, - }, - )); - this.props.dispatch(modifyStatusBody( - status.get('id'), - oldBody.innerHTML, - )); - } - handleToggleMediaVisibility = () => { this.setState({ showMedia: !this.state.showMedia }); } @@ -623,7 +601,6 @@ class Status extends ImmutablePureComponent { settings={settings} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} - onToggleSpoilerText={this.handleToggleSpoilerText} expanded={isExpanded} onToggleHidden={this.handleToggleHidden} domain={domain} diff --git a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js index aae2e4426..8173bcdc1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/actions_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/actions_modal.js @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import StatusContent from 'flavours/glitch/components/status_content'; +import StatusContentContainer from 'flavours/glitch/containers/status_content_container'; import Avatar from 'flavours/glitch/components/avatar'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import DisplayName from 'flavours/glitch/components/display_name'; @@ -73,7 +73,7 @@ export default class ActionsModal extends ImmutablePureComponent { - + ); diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js index 8d9496bb9..11947a15f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js @@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Button from 'flavours/glitch/components/button'; -import StatusContent from 'flavours/glitch/components/status_content'; +import StatusContentContainer from 'flavours/glitch/containers/status_content_container'; import Avatar from 'flavours/glitch/components/avatar'; import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; import DisplayName from 'flavours/glitch/components/display_name'; @@ -102,7 +102,7 @@ class BoostModal extends ImmutablePureComponent { - + {status.get('media_attachments').size > 0 && ( - + {status.get('media_attachments').size > 0 && (