about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2022-09-20 23:51:21 +0200
committerGitHub <noreply@github.com>2022-09-20 23:51:21 +0200
commit50948b46aabc0756d85bc6641f0bd3bcc09bf7d4 (patch)
tree19dd9e761aec2a33d6bc3c11b47673da40cd965b /app/javascript
parent882e54c78678bd4247d70fe5b04571543769bcee (diff)
Add ability to filter followed accounts' posts by language (#19095)
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/features/account/components/header.js5
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js6
-rw-r--r--app/javascript/mastodon/features/account_timeline/containers/header_container.js8
-rw-r--r--app/javascript/mastodon/features/subscribed_languages_modal/index.js121
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js2
-rw-r--r--app/javascript/mastodon/locales/defaultMessages.json25
-rw-r--r--app/javascript/mastodon/locales/en.json4
7 files changed, 170 insertions, 1 deletions
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 1ad9341c7..8f2753c35 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -51,6 +51,7 @@ const messages = defineMessages({
   unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
   add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
+  languages: { id: 'account.languages', defaultMessage: 'Change subscribed languages' },
 });
 
 const dateFormatOptions = {
@@ -85,6 +86,7 @@ class Header extends ImmutablePureComponent {
     onEndorseToggle: PropTypes.func.isRequired,
     onAddToList: PropTypes.func.isRequired,
     onEditAccountNote: PropTypes.func.isRequired,
+    onChangeLanguages: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
     domain: PropTypes.string.isRequired,
     hidden: PropTypes.bool,
@@ -212,6 +214,9 @@ class Header extends ImmutablePureComponent {
           } else {
             menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
           }
+
+          menu.push({ text: intl.formatMessage(messages.languages), action: this.props.onChangeLanguages });
+          menu.push(null);
         }
 
         menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle });
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index fab0bc597..f9838442f 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -22,6 +22,7 @@ export default class Header extends ImmutablePureComponent {
     onUnblockDomain: PropTypes.func.isRequired,
     onEndorseToggle: PropTypes.func.isRequired,
     onAddToList: PropTypes.func.isRequired,
+    onChangeLanguages: PropTypes.func.isRequired,
     hideTabs: PropTypes.bool,
     domain: PropTypes.string.isRequired,
     hidden: PropTypes.bool,
@@ -91,6 +92,10 @@ export default class Header extends ImmutablePureComponent {
     this.props.onEditAccountNote(this.props.account);
   }
 
+  handleChangeLanguages = () => {
+    this.props.onChangeLanguages(this.props.account);
+  }
+
   render () {
     const { account, hidden, hideTabs } = this.props;
 
@@ -117,6 +122,7 @@ export default class Header extends ImmutablePureComponent {
           onEndorseToggle={this.handleEndorseToggle}
           onAddToList={this.handleAddToList}
           onEditAccountNote={this.handleEditAccountNote}
+          onChangeLanguages={this.handleChangeLanguages}
           domain={this.props.domain}
           hidden={hidden}
         />
diff --git a/app/javascript/mastodon/features/account_timeline/containers/header_container.js b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
index 371794dd7..3d6eb487d 100644
--- a/app/javascript/mastodon/features/account_timeline/containers/header_container.js
+++ b/app/javascript/mastodon/features/account_timeline/containers/header_container.js
@@ -121,12 +121,18 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     dispatch(unblockDomain(domain));
   },
 
-  onAddToList(account){
+  onAddToList (account) {
     dispatch(openModal('LIST_ADDER', {
       accountId: account.get('id'),
     }));
   },
 
+  onChangeLanguages (account) {
+    dispatch(openModal('SUBSCRIBED_LANGUAGES', {
+      accountId: account.get('id'),
+    }));
+  },
+
 });
 
 export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.js b/app/javascript/mastodon/features/subscribed_languages_modal/index.js
new file mode 100644
index 000000000..6a1bb2c47
--- /dev/null
+++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.js
@@ -0,0 +1,121 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable';
+import { languages as preloadedLanguages } from 'mastodon/initial_state';
+import Option from 'mastodon/features/report/components/option';
+import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
+import IconButton from 'mastodon/components/icon_button';
+import Button from 'mastodon/components/button';
+import { followAccount } from 'mastodon/actions/accounts';
+
+const messages = defineMessages({
+  close: { id: 'lightbox.close', defaultMessage: 'Close' },
+});
+
+const getAccountLanguages = createSelector([
+  (state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()),
+  state => state.get('statuses'),
+], (statusIds, statuses) =>
+  new ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language'))));
+
+const mapStateToProps = (state, { accountId }) => ({
+  acct: state.getIn(['accounts', accountId, 'acct']),
+  availableLanguages: getAccountLanguages(state, accountId),
+  selectedLanguages: ImmutableSet(state.getIn(['relationships', accountId, 'languages']) || ImmutableList()),
+});
+
+const mapDispatchToProps = (dispatch, { accountId }) => ({
+
+  onSubmit (languages) {
+    dispatch(followAccount(accountId, { languages }));
+  },
+
+});
+
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+class SubscribedLanguagesModal extends ImmutablePureComponent {
+
+  static propTypes = {
+    accountId: PropTypes.string.isRequired,
+    acct: PropTypes.string.isRequired,
+    availableLanguages: ImmutablePropTypes.setOf(PropTypes.string),
+    selectedLanguages: ImmutablePropTypes.setOf(PropTypes.string),
+    onClose: PropTypes.func.isRequired,
+    languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
+    intl: PropTypes.object.isRequired,
+    submit: PropTypes.func.isRequired,
+  };
+
+  static defaultProps = {
+    languages: preloadedLanguages,
+  };
+
+  state = {
+    selectedLanguages: this.props.selectedLanguages,
+  };
+
+  handleLanguageToggle = (value, checked) => {
+    const { selectedLanguages } = this.state;
+
+    if (checked) {
+      this.setState({ selectedLanguages: selectedLanguages.add(value) });
+    } else {
+      this.setState({ selectedLanguages: selectedLanguages.delete(value) });
+    }
+  };
+
+  handleSubmit = () => {
+    this.props.onSubmit(this.state.selectedLanguages.toArray());
+    this.props.onClose();
+  }
+
+  renderItem (value) {
+    const language = this.props.languages.find(language => language[0] === value);
+    const checked = this.state.selectedLanguages.includes(value);
+
+    return (
+      <Option
+        key={value}
+        name='languages'
+        value={value}
+        label={language[1]}
+        checked={checked}
+        onToggle={this.handleLanguageToggle}
+        multiple
+      />
+    );
+  }
+
+  render () {
+    const { acct, availableLanguages, selectedLanguages, intl, onClose } = this.props;
+
+    return (
+      <div className='modal-root__modal report-dialog-modal'>
+        <div className='report-modal__target'>
+          <IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
+          <FormattedMessage id='subscribed_languages.target' defaultMessage='Change subscribed languages for {target}' values={{ target: <strong>{acct}</strong> }} />
+        </div>
+
+        <div className='report-dialog-modal__container'>
+          <p className='report-dialog-modal__lead'><FormattedMessage id='subscribed_languages.lead' defaultMessage='Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.' /></p>
+
+          <div>
+            {availableLanguages.union(selectedLanguages).map(value => this.renderItem(value))}
+          </div>
+
+          <div className='flex-spacer' />
+
+          <div className='report-dialog-modal__actions'>
+            <Button disabled={is(this.state.selectedLanguages, this.props.selectedLanguages)} onClick={this.handleSubmit}><FormattedMessage id='subscribed_languages.save' defaultMessage='Save changes' /></Button>
+          </div>
+        </div>
+      </div>
+    );
+  }
+
+}
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index b2c30e079..dfa89f2ce 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -11,6 +11,7 @@ import VideoModal from './video_modal';
 import BoostModal from './boost_modal';
 import AudioModal from './audio_modal';
 import ConfirmationModal from './confirmation_modal';
+import SubscribedLanguagesModal from 'mastodon/features/subscribed_languages_modal';
 import FocalPointModal from './focal_point_modal';
 import {
   MuteModal,
@@ -39,6 +40,7 @@ const MODAL_COMPONENTS = {
   'LIST_ADDER': ListAdder,
   'COMPARE_HISTORY': CompareHistoryModal,
   'FILTER': FilterModal,
+  'SUBSCRIBED_LANGUAGES': () => Promise.resolve({ default: SubscribedLanguagesModal }),
 };
 
 export default class ModalRoot extends React.PureComponent {
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 13ef56922..4c208c3cb 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -1031,6 +1031,10 @@
         "id": "status.admin_account"
       },
       {
+        "defaultMessage": "Change subscribed languages",
+        "id": "account.languages"
+      },
+      {
         "defaultMessage": "Follows you",
         "id": "account.follows_you"
       },
@@ -3353,6 +3357,27 @@
   {
     "descriptors": [
       {
+        "defaultMessage": "Close",
+        "id": "lightbox.close"
+      },
+      {
+        "defaultMessage": "Change subscribed languages for {target}",
+        "id": "subscribed_languages.target"
+      },
+      {
+        "defaultMessage": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
+        "id": "subscribed_languages.lead"
+      },
+      {
+        "defaultMessage": "Save changes",
+        "id": "subscribed_languages.save"
+      }
+    ],
+    "path": "app/javascript/mastodon/features/subscribed_languages_modal/index.json"
+  },
+  {
+    "descriptors": [
+      {
         "defaultMessage": "Are you sure you want to block {name}?",
         "id": "confirmations.block.message"
       },
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 521bc4455..4f515b321 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -24,6 +24,7 @@
   "account.follows_you": "Follows you",
   "account.hide_reblogs": "Hide boosts from @{name}",
   "account.joined": "Joined {date}",
+  "account.languages": "Change subscribed languages",
   "account.link_verified_on": "Ownership of this link was checked on {date}",
   "account.locked_info": "This account privacy status is set to locked. The owner manually reviews who can follow them.",
   "account.media": "Media",
@@ -522,6 +523,9 @@
   "status.uncached_media_warning": "Not available",
   "status.unmute_conversation": "Unmute conversation",
   "status.unpin": "Unpin from profile",
+  "subscribed_languages.lead": "Only posts in selected languages will appear on your home and list timelines after the change. Select none to receive posts in all languages.",
+  "subscribed_languages.save": "Save changes",
+  "subscribed_languages.target": "Change subscribed languages for {target}",
   "suggestions.dismiss": "Dismiss suggestion",
   "suggestions.header": "You might be interested in…",
   "tabs_bar.federated_timeline": "Federated",