Merge pull request #226 from glitch-soc/glitch-theme
Glitch/Vanilla themes
This commit is contained in:
		
						commit
						34ba45e47c
					
				
					 393 changed files with 28112 additions and 4201 deletions
				
			
		|  | @ -1,93 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `actions/local_settings` |  | ||||||
| ======================== |  | ||||||
| 
 |  | ||||||
| >   For more information on the contents of this file, please contact: |  | ||||||
| > |  | ||||||
| >   - kibigo! [@kibi@glitch.social] |  | ||||||
| 
 |  | ||||||
| This file provides our Redux actions related to local settings. It |  | ||||||
| consists of the following: |  | ||||||
| 
 |  | ||||||
|  -  __`changesLocalSetting(key, value)` :__ |  | ||||||
|     Changes the local setting with the given `key` to the given |  | ||||||
|     `value`. `key` **MUST** be an array of strings, as required by |  | ||||||
|     `Immutable.Map.prototype.getIn()`. |  | ||||||
| 
 |  | ||||||
|  -  __`saveLocalSettings()` :__ |  | ||||||
|     Saves the local settings to `localStorage` as a JSON object. We |  | ||||||
|     shouldn't ever need to call this ourselves. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Constants: |  | ||||||
| ---------- |  | ||||||
| 
 |  | ||||||
| We provide the following constants: |  | ||||||
| 
 |  | ||||||
|  -  __`LOCAL_SETTING_CHANGE` :__ |  | ||||||
|     This string constant is used to dispatch a setting change to our |  | ||||||
|     reducer in `reducers/local_settings`, where the setting is |  | ||||||
|     actually changed. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `changeLocalSetting(key, value)`: |  | ||||||
| --------------------------------- |  | ||||||
| 
 |  | ||||||
| Changes the local setting with the given `key` to the given `value`. |  | ||||||
| `key` **MUST** be an array of strings, as required by |  | ||||||
| `Immutable.Map.prototype.getIn()`. |  | ||||||
| 
 |  | ||||||
| To accomplish this, we just dispatch a `LOCAL_SETTING_CHANGE` to our |  | ||||||
| reducer in `reducers/local_settings`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export function changeLocalSetting(key, value) { |  | ||||||
|   return dispatch => { |  | ||||||
|     dispatch({ |  | ||||||
|       type: LOCAL_SETTING_CHANGE, |  | ||||||
|       key, |  | ||||||
|       value, |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     dispatch(saveLocalSettings()); |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `saveLocalSettings()`: |  | ||||||
| ---------------------- |  | ||||||
| 
 |  | ||||||
| Saves the local settings to `localStorage` as a JSON object. |  | ||||||
| `changeLocalSetting()` calls this whenever it changes a setting. We |  | ||||||
| shouldn't ever need to call this ourselves. |  | ||||||
| 
 |  | ||||||
| >   __TODO :__ |  | ||||||
| >   Right now `saveLocalSettings()` doesn't keep track of which user |  | ||||||
| >   is currently signed in, but it might be better to give each user |  | ||||||
| >   their *own* local settings. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export function saveLocalSettings() { |  | ||||||
|   return (_, getState) => { |  | ||||||
|     const localSettings = getState().get('local_settings').toJS(); |  | ||||||
|     localStorage.setItem('mastodon-settings', JSON.stringify(localSettings)); |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  | @ -1,227 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<AccountHeader>` |  | ||||||
| ================= |  | ||||||
| 
 |  | ||||||
| >   For more information on the contents of this file, please contact: |  | ||||||
| > |  | ||||||
| >   - kibigo! [@kibi@glitch.social] |  | ||||||
| 
 |  | ||||||
| Original file by @gargron@mastodon.social et al as part of |  | ||||||
| tootsuite/mastodon. We've expanded it in order to handle user bio |  | ||||||
| frontmatter. |  | ||||||
| 
 |  | ||||||
| The `<AccountHeader>` component provides the header for account |  | ||||||
| timelines. It is a fairly simple component which mostly just consists |  | ||||||
| of a `render()` method. |  | ||||||
| 
 |  | ||||||
| __Props:__ |  | ||||||
| 
 |  | ||||||
|  -  __`account` (`ImmutablePropTypes.map`) :__ |  | ||||||
|     The account to render a header for. |  | ||||||
| 
 |  | ||||||
|  -  __`me` (`PropTypes.number.isRequired`) :__ |  | ||||||
|     The id of the currently-signed-in account. |  | ||||||
| 
 |  | ||||||
|  -  __`onFollow` (`PropTypes.func.isRequired`) :__ |  | ||||||
|     The function to call when the user clicks the "follow" button. |  | ||||||
| 
 |  | ||||||
|  -  __`intl` (`PropTypes.object.isRequired`) :__ |  | ||||||
|     Our internationalization object, inserted by `@injectIntl`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; |  | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import emojify from '../../../mastodon/features/emoji/emoji'; |  | ||||||
| import IconButton from '../../../mastodon/components/icon_button'; |  | ||||||
| import Avatar from '../../../mastodon/components/avatar'; |  | ||||||
| import { me } from '../../../mastodon/initial_state'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import { processBio } from '../../util/bio_metadata'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Inital setup: |  | ||||||
| ------------- |  | ||||||
| 
 |  | ||||||
| The `messages` constant is used to define any messages that we need |  | ||||||
| from inside props. In our case, these are the `unfollow`, `follow`, and |  | ||||||
| `requested` messages used in the `title` of our buttons. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const messages = defineMessages({ |  | ||||||
|   unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, |  | ||||||
|   follow: { id: 'account.follow', defaultMessage: 'Follow' }, |  | ||||||
|   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Implementation: |  | ||||||
| --------------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| @injectIntl |  | ||||||
| export default class AccountHeader extends ImmutablePureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     account  : ImmutablePropTypes.map, |  | ||||||
|     onFollow : PropTypes.func.isRequired, |  | ||||||
|     intl     : PropTypes.object.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ###  `render()` |  | ||||||
| 
 |  | ||||||
| The `render()` function is used to render our component. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { account, intl } = this.props; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| If no `account` is provided, then we can't render a header. Otherwise, |  | ||||||
| we get the `displayName` for the account, if available. If it's blank, |  | ||||||
| then we set the `displayName` to just be the `username` of the account. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if (!account) { |  | ||||||
|       return null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let displayName = account.get('display_name_html'); |  | ||||||
|     let info        = ''; |  | ||||||
|     let actionBtn   = ''; |  | ||||||
|     let following   = false; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Next, we handle the account relationships. If the account follows the |  | ||||||
| user, then we add an `info` message. If the user has requested a |  | ||||||
| follow, then we disable the `actionBtn` and display an hourglass. |  | ||||||
| Otherwise, if the account isn't blocked, we set the `actionBtn` to the |  | ||||||
| appropriate icon. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if (me !== account.get('id')) { |  | ||||||
|       if (account.getIn(['relationship', 'followed_by'])) { |  | ||||||
|         info = ( |  | ||||||
|           <span className='account--follows-info'> |  | ||||||
|             <FormattedMessage id='account.follows_you' defaultMessage='Follows you' /> |  | ||||||
|           </span> |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|       if (account.getIn(['relationship', 'requested'])) { |  | ||||||
|         actionBtn = ( |  | ||||||
|           <div className='account--action-button'> |  | ||||||
|             <IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} /> |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       } else if (!account.getIn(['relationship', 'blocking'])) { |  | ||||||
|         following = account.getIn(['relationship', 'following']); |  | ||||||
|         actionBtn = ( |  | ||||||
|           <div className='account--action-button'> |  | ||||||
|             <IconButton |  | ||||||
|               size={26} |  | ||||||
|               icon={following ? 'user-times' : 'user-plus'} |  | ||||||
|               active={following ? true : false} |  | ||||||
|               title={intl.formatMessage(following ? messages.unfollow : messages.follow)} |  | ||||||
|               onClick={this.props.onFollow} |  | ||||||
|             /> |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  we extract the `text` and |  | ||||||
| `metadata` from our account's `note` using `processBio()`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     const { text, metadata } = processBio(account.get('note')); |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Here, we render our component using all the things we've defined above. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <div className='account__header__wrapper'> |  | ||||||
|         <div |  | ||||||
|           className='account__header' |  | ||||||
|           style={{ backgroundImage: `url(${account.get('header')})` }} |  | ||||||
|         > |  | ||||||
|           <div> |  | ||||||
|             <a href={account.get('url')} target='_blank' rel='noopener'> |  | ||||||
|               <span className='account__header__avatar'> |  | ||||||
|                 <Avatar account={account} size={90} /> |  | ||||||
|               </span> |  | ||||||
|               <span |  | ||||||
|                 className='account__header__display-name' |  | ||||||
|                 dangerouslySetInnerHTML={{ __html: displayName }} |  | ||||||
|               /> |  | ||||||
|             </a> |  | ||||||
|             <span className='account__header__username'> |  | ||||||
|               @{account.get('acct')} |  | ||||||
|               {account.get('locked') ? <i className='fa fa-lock' /> : null} |  | ||||||
|             </span> |  | ||||||
|             <div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} /> |  | ||||||
| 
 |  | ||||||
|             {info} |  | ||||||
|             {actionBtn} |  | ||||||
|           </div> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         {metadata.length && ( |  | ||||||
|           <table className='account__metadata'> |  | ||||||
|             <tbody> |  | ||||||
|               {(() => { |  | ||||||
|                 let data = []; |  | ||||||
|                 for (let i = 0; i < metadata.length; i++) { |  | ||||||
|                   data.push( |  | ||||||
|                     <tr key={i}> |  | ||||||
|                       <th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th> |  | ||||||
|                       <td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td> |  | ||||||
|                     </tr> |  | ||||||
|                   ); |  | ||||||
|                 } |  | ||||||
|                 return data; |  | ||||||
|               })()} |  | ||||||
|             </tbody> |  | ||||||
|           </table> |  | ||||||
|         ) || null} |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,66 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<ComposeAdvancedOptionsContainer>` |  | ||||||
| =================================== |  | ||||||
| 
 |  | ||||||
| This container connects `<ComposeAdvancedOptions>` to the Redux store. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compose'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import ComposeAdvancedOptions from '.'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| State mapping: |  | ||||||
| -------------- |  | ||||||
| 
 |  | ||||||
| The `mapStateToProps()` function maps various state properties to the |  | ||||||
| props of our component. The only property we care about is |  | ||||||
| `compose.advanced_options`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const mapStateToProps = state => ({ |  | ||||||
|   values: state.getIn(['compose', 'advanced_options']), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Dispatch mapping: |  | ||||||
| ----------------- |  | ||||||
| 
 |  | ||||||
| The `mapDispatchToProps()` function maps dispatches to our store to the |  | ||||||
| various props of our component. We just need to provide a dispatch for |  | ||||||
| when an advanced option toggle changes. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const mapDispatchToProps = dispatch => ({ |  | ||||||
| 
 |  | ||||||
|   onChange (option) { |  | ||||||
|     dispatch(toggleComposeAdvancedOption(option)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions); |  | ||||||
|  | @ -1,163 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<ComposeAdvancedOptions>` |  | ||||||
| ========================== |  | ||||||
| 
 |  | ||||||
| >   For more information on the contents of this file, please contact: |  | ||||||
| > |  | ||||||
| >   - surinna [@srn@dev.glitch.social] |  | ||||||
| 
 |  | ||||||
| This adds an advanced options dropdown to the toot compose box, for |  | ||||||
| toggles that don't necessarily fit elsewhere. |  | ||||||
| 
 |  | ||||||
| __Props:__ |  | ||||||
| 
 |  | ||||||
|  -  __`values` (`ImmutablePropTypes.contains(…).isRequired`) :__ |  | ||||||
|     An Immutable map with the following values: |  | ||||||
| 
 |  | ||||||
|      -  __`do_not_federate` (`PropTypes.bool.isRequired`) :__ |  | ||||||
|         Specifies whether or not to federate the status. |  | ||||||
| 
 |  | ||||||
|  -  __`onChange` (`PropTypes.func.isRequired`) :__ |  | ||||||
|     The function to call when a toggle is changed. We pass this from |  | ||||||
|     our container to the toggle. |  | ||||||
| 
 |  | ||||||
|  -  __`intl` (`PropTypes.object.isRequired`) :__ |  | ||||||
|     Our internationalization object, inserted by `@injectIntl`. |  | ||||||
| 
 |  | ||||||
| __State:__ |  | ||||||
| 
 |  | ||||||
|  -  __`open` :__ |  | ||||||
|     This tells whether the dropdown is currently open or closed. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import { injectIntl, defineMessages } from 'react-intl'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import ComposeAdvancedOptionsToggle from './toggle'; |  | ||||||
| import ComposeDropdown from '../dropdown/index'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Inital setup: |  | ||||||
| ------------- |  | ||||||
| 
 |  | ||||||
| The `messages` constant is used to define any messages that we need |  | ||||||
| from inside props. These are the various titles and labels on our |  | ||||||
| toggles. |  | ||||||
| 
 |  | ||||||
| `iconStyle` styles the icon used for the dropdown button. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const messages = defineMessages({ |  | ||||||
|   local_only_short            : |  | ||||||
|     { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' }, |  | ||||||
|   local_only_long             : |  | ||||||
|     { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' }, |  | ||||||
|   advanced_options_icon_title : |  | ||||||
|     { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Implementation: |  | ||||||
| --------------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| @injectIntl |  | ||||||
| export default class ComposeAdvancedOptions extends React.PureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     values   : ImmutablePropTypes.contains({ |  | ||||||
|       do_not_federate : PropTypes.bool.isRequired, |  | ||||||
|     }).isRequired, |  | ||||||
|     onChange : PropTypes.func.isRequired, |  | ||||||
|     intl     : PropTypes.object.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ###  `render()` |  | ||||||
| 
 |  | ||||||
| `render()` actually puts our component on the screen. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { intl, values } = this.props; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| The `options` array provides all of the available advanced options |  | ||||||
| alongside their icon, text, and name. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
|     const options = [ |  | ||||||
|       { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' }, |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `anyEnabled` tells us if any of our advanced options have been enabled. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     const anyEnabled = values.some((enabled) => enabled); |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `optionElems` takes our `options` and creates |  | ||||||
| `<ComposeAdvancedOptionsToggle>`s out of them. We use the `name` of the |  | ||||||
| toggle as its `key` so that React can keep track of it. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     const optionElems = options.map((option) => { |  | ||||||
|       return ( |  | ||||||
|         <ComposeAdvancedOptionsToggle |  | ||||||
|           onChange={this.props.onChange} |  | ||||||
|           active={values.get(option.name)} |  | ||||||
|           key={option.name} |  | ||||||
|           name={option.name} |  | ||||||
|           shortText={intl.formatMessage(option.shortText)} |  | ||||||
|           longText={intl.formatMessage(option.longText)} |  | ||||||
|         /> |  | ||||||
|       ); |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Finally, we can render our component. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
|     return ( |  | ||||||
|       <ComposeDropdown |  | ||||||
|         title={intl.formatMessage(messages.advanced_options_icon_title)} |  | ||||||
|         icon='home' |  | ||||||
|         highlight={anyEnabled} |  | ||||||
|       > |  | ||||||
|         {optionElems} |  | ||||||
|       </ComposeDropdown> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,103 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<ComposeAdvancedOptionsToggle>` |  | ||||||
| ================================ |  | ||||||
| 
 |  | ||||||
| >   For more information on the contents of this file, please contact: |  | ||||||
| > |  | ||||||
| >   - surinna [@srn@dev.glitch.social] |  | ||||||
| 
 |  | ||||||
| This creates the toggle used by `<ComposeAdvancedOptions>`. |  | ||||||
| 
 |  | ||||||
| __Props:__ |  | ||||||
| 
 |  | ||||||
|  -  __`onChange` (`PropTypes.func`) :__ |  | ||||||
|     This provides the function to call when the toggle is |  | ||||||
|     (de-?)activated. |  | ||||||
| 
 |  | ||||||
|  -  __`active` (`PropTypes.bool`) :__ |  | ||||||
|     This prop controls whether the toggle is currently active or not. |  | ||||||
| 
 |  | ||||||
|  -  __`name` (`PropTypes.string`) :__ |  | ||||||
|     This identifies the toggle, and is sent to `onChange()` when it is |  | ||||||
|     called. |  | ||||||
| 
 |  | ||||||
|  -  __`shortText` (`PropTypes.string`) :__ |  | ||||||
|     This is a short string used as the title of the toggle. |  | ||||||
| 
 |  | ||||||
|  -  __`longText` (`PropTypes.string`) :__ |  | ||||||
|     This is a longer string used as a subtitle for the toggle. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import Toggle from 'react-toggle'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Implementation: |  | ||||||
| --------------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export default class ComposeAdvancedOptionsToggle extends React.PureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     onChange: PropTypes.func.isRequired, |  | ||||||
|     active: PropTypes.bool.isRequired, |  | ||||||
|     name: PropTypes.string.isRequired, |  | ||||||
|     shortText: PropTypes.string.isRequired, |  | ||||||
|     longText: PropTypes.string.isRequired, |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ###  `onToggle()` |  | ||||||
| 
 |  | ||||||
| The `onToggle()` function simply calls the `onChange()` prop with the |  | ||||||
| toggle's `name`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   onToggle = () => { |  | ||||||
|     this.props.onChange(this.props.name); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ###  `render()` |  | ||||||
| 
 |  | ||||||
| The `render()` function is used to render our component. We just render |  | ||||||
| a `<Toggle>` and place next to it our text. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   render() { |  | ||||||
|     const { active, shortText, longText } = this.props; |  | ||||||
|     return ( |  | ||||||
|       <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}> |  | ||||||
|         <div className='advanced-options-dropdown__option__toggle'> |  | ||||||
|           <Toggle checked={active} onChange={this.onToggle} /> |  | ||||||
|         </div> |  | ||||||
|         <div className='advanced-options-dropdown__option__content'> |  | ||||||
|           <strong>{shortText}</strong> |  | ||||||
|           {longText} |  | ||||||
|         </div> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,24 +0,0 @@ | ||||||
| //  Package imports  //
 |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import { closeModal } from '../../../mastodon/actions/modal'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import { changeLocalSetting } from '../../../glitch/actions/local_settings'; |  | ||||||
| import LocalSettings from '.'; |  | ||||||
| 
 |  | ||||||
| const mapStateToProps = state => ({ |  | ||||||
|   settings: state.get('local_settings'), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const mapDispatchToProps = dispatch => ({ |  | ||||||
|   onChange (setting, value) { |  | ||||||
|     dispatch(changeLocalSetting(setting, value)); |  | ||||||
|   }, |  | ||||||
|   onClose () { |  | ||||||
|     dispatch(closeModal()); |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default connect(mapStateToProps, mapDispatchToProps)(LocalSettings); |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<NotificationContainer>` |  | ||||||
| ========================= |  | ||||||
| 
 |  | ||||||
| This container connects `<Notification>`s to the Redux store. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import Notification from '.'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| const mapStateToProps = (state, props) => { |  | ||||||
|   // replace account id with object
 |  | ||||||
|   let leNotif = props.notification.set('account', state.getIn(['accounts', props.notification.get('account')])); |  | ||||||
| 
 |  | ||||||
|   // populate markedForDelete from state - is mysteriously lost somewhere
 |  | ||||||
|   for (let n of state.getIn(['notifications', 'items'])) { |  | ||||||
|     if (n.get('id') === props.notification.get('id')) { |  | ||||||
|       leNotif = leNotif.set('markedForDelete', n.get('markedForDelete')); |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return ({ |  | ||||||
|     notification: leNotif, |  | ||||||
|     settings: state.get('local_settings'), |  | ||||||
|     notifCleaning: state.getIn(['notifications', 'cleaningMode']), |  | ||||||
|   }); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| export default connect(mapStateToProps)(Notification); |  | ||||||
|  | @ -1,72 +0,0 @@ | ||||||
| //  `<NotificationFollow>`
 |  | ||||||
| //  ======================
 |  | ||||||
| 
 |  | ||||||
| //  * * * * * * *  //
 |  | ||||||
| 
 |  | ||||||
| //  Imports
 |  | ||||||
| //  -------
 |  | ||||||
| 
 |  | ||||||
| //  Package imports.
 |  | ||||||
| import React from 'react'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { FormattedMessage } from 'react-intl'; |  | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports.
 |  | ||||||
| import Permalink from '../../../mastodon/components/permalink'; |  | ||||||
| import AccountContainer from '../../../mastodon/containers/account_container'; |  | ||||||
| 
 |  | ||||||
| // Our imports.
 |  | ||||||
| import NotificationOverlayContainer from '../notification/overlay/container'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * *  //
 |  | ||||||
| 
 |  | ||||||
| //  Implementation
 |  | ||||||
| //  --------------
 |  | ||||||
| 
 |  | ||||||
| export default class NotificationFollow extends ImmutablePureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     id                   : PropTypes.string.isRequired, |  | ||||||
|     account              : ImmutablePropTypes.map.isRequired, |  | ||||||
|     notification         : ImmutablePropTypes.map.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { account, notification } = this.props; |  | ||||||
| 
 |  | ||||||
|     //  Links to the display name.
 |  | ||||||
|     const displayName = account.get('display_name_html') || account.get('username'); |  | ||||||
|     const link = ( |  | ||||||
|       <Permalink |  | ||||||
|         className='notification__display-name' |  | ||||||
|         href={account.get('url')} |  | ||||||
|         title={account.get('acct')} |  | ||||||
|         to={`/accounts/${account.get('id')}`} |  | ||||||
|         dangerouslySetInnerHTML={{ __html: displayName }} |  | ||||||
|       /> |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     //  Renders.
 |  | ||||||
|     return ( |  | ||||||
|       <div className='notification notification-follow'> |  | ||||||
|         <div className='notification__message'> |  | ||||||
|           <div className='notification__favourite-icon-wrapper'> |  | ||||||
|             <i className='fa fa-fw fa-user-plus' /> |  | ||||||
|           </div> |  | ||||||
| 
 |  | ||||||
|           <FormattedMessage |  | ||||||
|             id='notification.follow' |  | ||||||
|             defaultMessage='{name} followed you' |  | ||||||
|             values={{ name: link }} |  | ||||||
|           /> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         <AccountContainer id={account.get('id')} withNote={false} /> |  | ||||||
|         <NotificationOverlayContainer notification={notification} /> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,49 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<NotificationOverlayContainer>` |  | ||||||
| ========================= |  | ||||||
| 
 |  | ||||||
| This container connects `<NotificationOverlay>`s to the Redux store. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import NotificationOverlay from './notification_overlay'; |  | ||||||
| import { markNotificationForDelete } from '../../../../mastodon/actions/notifications'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Dispatch mapping: |  | ||||||
| ----------------- |  | ||||||
| 
 |  | ||||||
| The `mapDispatchToProps()` function maps dispatches to our store to the |  | ||||||
| various props of our component. We only need to provide a dispatch for |  | ||||||
| deleting notifications. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const mapDispatchToProps = dispatch => ({ |  | ||||||
|   onMarkForDelete(id, yes) { |  | ||||||
|     dispatch(markNotificationForDelete(id, yes)); |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| const mapStateToProps = state => ({ |  | ||||||
|   show: state.getIn(['notifications', 'cleaningMode']), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default connect(mapStateToProps, mapDispatchToProps)(NotificationOverlay); |  | ||||||
|  | @ -1,263 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<StatusContainer>` |  | ||||||
| =================== |  | ||||||
| 
 |  | ||||||
| Original file by @gargron@mastodon.social et al as part of |  | ||||||
| tootsuite/mastodon. Documentation by @kibi@glitch.social. The code |  | ||||||
| detecting reblogs has been moved here from <Status>. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| import { |  | ||||||
|   defineMessages, |  | ||||||
|   injectIntl, |  | ||||||
|   FormattedMessage, |  | ||||||
| } from 'react-intl'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import { makeGetStatus } from '../../../mastodon/selectors'; |  | ||||||
| import { |  | ||||||
|   replyCompose, |  | ||||||
|   mentionCompose, |  | ||||||
| } from '../../../mastodon/actions/compose'; |  | ||||||
| import { |  | ||||||
|   reblog, |  | ||||||
|   favourite, |  | ||||||
|   unreblog, |  | ||||||
|   unfavourite, |  | ||||||
|   pin, |  | ||||||
|   unpin, |  | ||||||
| } from '../../../mastodon/actions/interactions'; |  | ||||||
| import { blockAccount } from '../../../mastodon/actions/accounts'; |  | ||||||
| import { initMuteModal } from '../../../mastodon/actions/mutes'; |  | ||||||
| import { |  | ||||||
|   muteStatus, |  | ||||||
|   unmuteStatus, |  | ||||||
|   deleteStatus, |  | ||||||
| } from '../../../mastodon/actions/statuses'; |  | ||||||
| import { initReport } from '../../../mastodon/actions/reports'; |  | ||||||
| import { openModal } from '../../../mastodon/actions/modal'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import Status from '.'; |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Inital setup: |  | ||||||
| ------------- |  | ||||||
| 
 |  | ||||||
| The `messages` constant is used to define any messages that we will |  | ||||||
| need in our component. In our case, these are the various confirmation |  | ||||||
| messages used with statuses. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const messages = defineMessages({ |  | ||||||
|   deleteConfirm : { |  | ||||||
|     id             : 'confirmations.delete.confirm', |  | ||||||
|     defaultMessage : 'Delete', |  | ||||||
|   }, |  | ||||||
|   deleteMessage : { |  | ||||||
|     id             : 'confirmations.delete.message', |  | ||||||
|     defaultMessage : 'Are you sure you want to delete this status?', |  | ||||||
|   }, |  | ||||||
|   blockConfirm  : { |  | ||||||
|     id             : 'confirmations.block.confirm', |  | ||||||
|     defaultMessage : 'Block', |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| State mapping: |  | ||||||
| -------------- |  | ||||||
| 
 |  | ||||||
| The `mapStateToProps()` function maps various state properties to the |  | ||||||
| props of our component. We wrap this in a `makeMapStateToProps()` |  | ||||||
| function to give us closure and preserve `getStatus()` across function |  | ||||||
| calls. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const makeMapStateToProps = () => { |  | ||||||
|   const getStatus = makeGetStatus(); |  | ||||||
| 
 |  | ||||||
|   const mapStateToProps = (state, ownProps) => { |  | ||||||
| 
 |  | ||||||
|     let status = getStatus(state, ownProps.id); |  | ||||||
| 
 |  | ||||||
|     if(status === null) { |  | ||||||
|       console.error(`ERROR! NULL STATUS! ${ownProps.id}`); |  | ||||||
|       // work-around: find first good status
 |  | ||||||
|       for (let k of state.get('statuses').keys()) { |  | ||||||
|         status = getStatus(state, k); |  | ||||||
|         if (status !== null) break; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let reblogStatus = status.get('reblog', null); |  | ||||||
|     let account = undefined; |  | ||||||
|     let prepend = undefined; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Here we process reblogs. If our status is a reblog, then we create a |  | ||||||
| `prependMessage` to pass along to our `<Status>` along with the |  | ||||||
| reblogger's `account`, and set `coreStatus` (the one we will actually |  | ||||||
| render) to the status which has been reblogged. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if (reblogStatus !== null && typeof reblogStatus === 'object') { |  | ||||||
|       account = status.get('account'); |  | ||||||
|       status = reblogStatus; |  | ||||||
|       prepend = 'reblogged_by'; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Here are the props we pass to `<Status>`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     return { |  | ||||||
|       status      : status, |  | ||||||
|       account     : account || ownProps.account, |  | ||||||
|       settings    : state.get('local_settings'), |  | ||||||
|       prepend     : prepend || ownProps.prepend, |  | ||||||
|       reblogModal : state.getIn(['meta', 'boost_modal']), |  | ||||||
|       deleteModal : state.getIn(['meta', 'delete_modal']), |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   return mapStateToProps; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Dispatch mapping: |  | ||||||
| ----------------- |  | ||||||
| 
 |  | ||||||
| The `mapDispatchToProps()` function maps dispatches to our store to the |  | ||||||
| various props of our component. We need to provide dispatches for all |  | ||||||
| of the things you can do with a status: reply, reblog, favourite, et |  | ||||||
| cetera. |  | ||||||
| 
 |  | ||||||
| For a few of these dispatches, we open up confirmation modals; the rest |  | ||||||
| just immediately execute their corresponding actions. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const mapDispatchToProps = (dispatch, { intl }) => ({ |  | ||||||
| 
 |  | ||||||
|   onReply (status, router) { |  | ||||||
|     dispatch(replyCompose(status, router)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onModalReblog (status) { |  | ||||||
|     dispatch(reblog(status)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onReblog (status, e) { |  | ||||||
|     if (status.get('reblogged')) { |  | ||||||
|       dispatch(unreblog(status)); |  | ||||||
|     } else { |  | ||||||
|       if (e.shiftKey || !this.reblogModal) { |  | ||||||
|         this.onModalReblog(status); |  | ||||||
|       } else { |  | ||||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onFavourite (status) { |  | ||||||
|     if (status.get('favourited')) { |  | ||||||
|       dispatch(unfavourite(status)); |  | ||||||
|     } else { |  | ||||||
|       dispatch(favourite(status)); |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onPin (status) { |  | ||||||
|     if (status.get('pinned')) { |  | ||||||
|       dispatch(unpin(status)); |  | ||||||
|     } else { |  | ||||||
|       dispatch(pin(status)); |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onEmbed (status) { |  | ||||||
|     dispatch(openModal('EMBED', { url: status.get('url') })); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onDelete (status) { |  | ||||||
|     if (!this.deleteModal) { |  | ||||||
|       dispatch(deleteStatus(status.get('id'))); |  | ||||||
|     } else { |  | ||||||
|       dispatch(openModal('CONFIRM', { |  | ||||||
|         message: intl.formatMessage(messages.deleteMessage), |  | ||||||
|         confirm: intl.formatMessage(messages.deleteConfirm), |  | ||||||
|         onConfirm: () => dispatch(deleteStatus(status.get('id'))), |  | ||||||
|       })); |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onMention (account, router) { |  | ||||||
|     dispatch(mentionCompose(account, router)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onOpenMedia (media, index) { |  | ||||||
|     dispatch(openModal('MEDIA', { media, index })); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onOpenVideo (media, time) { |  | ||||||
|     dispatch(openModal('VIDEO', { media, time })); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onBlock (account) { |  | ||||||
|     dispatch(openModal('CONFIRM', { |  | ||||||
|       message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />, |  | ||||||
|       confirm: intl.formatMessage(messages.blockConfirm), |  | ||||||
|       onConfirm: () => dispatch(blockAccount(account.get('id'))), |  | ||||||
|     })); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onReport (status) { |  | ||||||
|     dispatch(initReport(status.get('account'), status)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onMute (account) { |  | ||||||
|     dispatch(initMuteModal(account)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onMuteConversation (status) { |  | ||||||
|     if (status.get('muted')) { |  | ||||||
|       dispatch(unmuteStatus(status.get('id'))); |  | ||||||
|     } else { |  | ||||||
|       dispatch(muteStatus(status.get('id'))); |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| export default injectIntl( |  | ||||||
|   connect(makeMapStateToProps, mapDispatchToProps)(Status) |  | ||||||
| ); |  | ||||||
|  | @ -1,79 +0,0 @@ | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import IconButton from '../../../../mastodon/components/icon_button'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import StatusGalleryItem from './item'; |  | ||||||
| 
 |  | ||||||
| const messages = defineMessages({ |  | ||||||
|   toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| @injectIntl |  | ||||||
| export default class StatusGallery extends React.PureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     sensitive: PropTypes.bool, |  | ||||||
|     media: ImmutablePropTypes.list.isRequired, |  | ||||||
|     letterbox: PropTypes.bool, |  | ||||||
|     fullwidth: PropTypes.bool, |  | ||||||
|     height: PropTypes.number.isRequired, |  | ||||||
|     onOpenMedia: PropTypes.func.isRequired, |  | ||||||
|     intl: PropTypes.object.isRequired, |  | ||||||
|     autoPlayGif: PropTypes.bool.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   state = { |  | ||||||
|     visible: !this.props.sensitive, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleOpen = () => { |  | ||||||
|     this.setState({ visible: !this.state.visible }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleClick = (index) => { |  | ||||||
|     this.props.onOpenMedia(this.props.media, index); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { media, intl, sensitive, letterbox, fullwidth } = this.props; |  | ||||||
| 
 |  | ||||||
|     let children; |  | ||||||
| 
 |  | ||||||
|     if (!this.state.visible) { |  | ||||||
|       let warning; |  | ||||||
| 
 |  | ||||||
|       if (sensitive) { |  | ||||||
|         warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />; |  | ||||||
|       } else { |  | ||||||
|         warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       children = ( |  | ||||||
|         <div role='button' tabIndex='0' className='media-spoiler' onClick={this.handleOpen}> |  | ||||||
|           <span className='media-spoiler__warning'>{warning}</span> |  | ||||||
|           <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } else { |  | ||||||
|       const size = media.take(4).size; |  | ||||||
|       children = media.take(4).map((attachment, i) => <StatusGalleryItem key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} letterbox={letterbox} />); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <div className={`media-gallery ${fullwidth ? 'full-width' : ''}`} style={{ height: `${this.props.height}px` }}> |  | ||||||
|         <div className={`spoiler-button ${this.state.visible ? 'spoiler-button--visible' : ''}`}> |  | ||||||
|           <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} /> |  | ||||||
|         </div> |  | ||||||
| 
 |  | ||||||
|         {children} |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,158 +0,0 @@ | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import { isIOS } from '../../../../mastodon/is_mobile'; |  | ||||||
| 
 |  | ||||||
| export default class StatusGalleryItem extends React.PureComponent { |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     attachment: ImmutablePropTypes.map.isRequired, |  | ||||||
|     index: PropTypes.number.isRequired, |  | ||||||
|     size: PropTypes.number.isRequired, |  | ||||||
|     letterbox: PropTypes.bool, |  | ||||||
|     onClick: PropTypes.func.isRequired, |  | ||||||
|     autoPlayGif: PropTypes.bool.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleMouseEnter = (e) => { |  | ||||||
|     if (this.hoverToPlay()) { |  | ||||||
|       e.target.play(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleMouseLeave = (e) => { |  | ||||||
|     if (this.hoverToPlay()) { |  | ||||||
|       e.target.pause(); |  | ||||||
|       e.target.currentTime = 0; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   hoverToPlay () { |  | ||||||
|     const { attachment, autoPlayGif } = this.props; |  | ||||||
|     return !autoPlayGif && attachment.get('type') === 'gifv'; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleClick = (e) => { |  | ||||||
|     const { index, onClick } = this.props; |  | ||||||
| 
 |  | ||||||
|     if (e.button === 0) { |  | ||||||
|       e.preventDefault(); |  | ||||||
|       onClick(index); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     e.stopPropagation(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { attachment, index, size, letterbox } = this.props; |  | ||||||
| 
 |  | ||||||
|     let width  = 50; |  | ||||||
|     let height = 100; |  | ||||||
|     let top    = 'auto'; |  | ||||||
|     let left   = 'auto'; |  | ||||||
|     let bottom = 'auto'; |  | ||||||
|     let right  = 'auto'; |  | ||||||
| 
 |  | ||||||
|     if (size === 1) { |  | ||||||
|       width = 100; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (size === 4 || (size === 3 && index > 0)) { |  | ||||||
|       height = 50; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (size === 2) { |  | ||||||
|       if (index === 0) { |  | ||||||
|         right = '2px'; |  | ||||||
|       } else { |  | ||||||
|         left = '2px'; |  | ||||||
|       } |  | ||||||
|     } else if (size === 3) { |  | ||||||
|       if (index === 0) { |  | ||||||
|         right = '2px'; |  | ||||||
|       } else if (index > 0) { |  | ||||||
|         left = '2px'; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (index === 1) { |  | ||||||
|         bottom = '2px'; |  | ||||||
|       } else if (index > 1) { |  | ||||||
|         top = '2px'; |  | ||||||
|       } |  | ||||||
|     } else if (size === 4) { |  | ||||||
|       if (index === 0 || index === 2) { |  | ||||||
|         right = '2px'; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (index === 1 || index === 3) { |  | ||||||
|         left = '2px'; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (index < 2) { |  | ||||||
|         bottom = '2px'; |  | ||||||
|       } else { |  | ||||||
|         top = '2px'; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let thumbnail = ''; |  | ||||||
| 
 |  | ||||||
|     if (attachment.get('type') === 'image') { |  | ||||||
|       const previewUrl = attachment.get('preview_url'); |  | ||||||
|       const previewWidth = attachment.getIn(['meta', 'small', 'width']); |  | ||||||
| 
 |  | ||||||
|       const originalUrl = attachment.get('url'); |  | ||||||
|       const originalWidth = attachment.getIn(['meta', 'original', 'width']); |  | ||||||
| 
 |  | ||||||
|       const srcSet = `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w`; |  | ||||||
|       const sizes = `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw`; |  | ||||||
| 
 |  | ||||||
|       thumbnail = ( |  | ||||||
|         <a |  | ||||||
|           className='media-gallery__item-thumbnail' |  | ||||||
|           href={attachment.get('remote_url') || originalUrl} |  | ||||||
|           onClick={this.handleClick} |  | ||||||
|           target='_blank' |  | ||||||
|         > |  | ||||||
|           <img |  | ||||||
|             className={letterbox ? 'letterbox' : ''} |  | ||||||
|             src={previewUrl} srcSet={srcSet} |  | ||||||
|             sizes={sizes} |  | ||||||
|             alt={attachment.get('description')} |  | ||||||
|             title={attachment.get('description')} |  | ||||||
|           /> |  | ||||||
|         </a> |  | ||||||
|       ); |  | ||||||
|     } else if (attachment.get('type') === 'gifv') { |  | ||||||
|       const autoPlay = !isIOS() && this.props.autoPlayGif; |  | ||||||
| 
 |  | ||||||
|       thumbnail = ( |  | ||||||
|         <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}> |  | ||||||
|           <video |  | ||||||
|             className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`} |  | ||||||
|             role='application' |  | ||||||
|             src={attachment.get('url')} |  | ||||||
|             onClick={this.handleClick} |  | ||||||
|             onMouseEnter={this.handleMouseEnter} |  | ||||||
|             onMouseLeave={this.handleMouseLeave} |  | ||||||
|             autoPlay={autoPlay} |  | ||||||
|             loop |  | ||||||
|             muted |  | ||||||
|           /> |  | ||||||
| 
 |  | ||||||
|           <span className='media-gallery__gifv__label'>GIF</span> |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}> |  | ||||||
|         {thumbnail} |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,760 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `<Status>` |  | ||||||
| ========== |  | ||||||
| 
 |  | ||||||
| Original file by @gargron@mastodon.social et al as part of |  | ||||||
| tootsuite/mastodon. *Heavily* rewritten (and documented!) by |  | ||||||
| @kibi@glitch.social as a part of glitch-soc/mastodon. The following |  | ||||||
| features have been added: |  | ||||||
| 
 |  | ||||||
|  -  Better separating the "guts" of statuses from their wrapper(s) |  | ||||||
|  -  Collapsing statuses |  | ||||||
|  -  Moving images inside of CWs |  | ||||||
| 
 |  | ||||||
| A number of aspects of this original file have been split off into |  | ||||||
| their own components for better maintainance; for these, see: |  | ||||||
| 
 |  | ||||||
|  -  <StatusHeader> |  | ||||||
|  -  <StatusPrepend> |  | ||||||
| 
 |  | ||||||
| …And, of course, the other <Status>-related components as well. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task'; |  | ||||||
| import { autoPlayGif } from '../../../mastodon/initial_state'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import StatusPrepend from './prepend'; |  | ||||||
| import StatusHeader from './header'; |  | ||||||
| import StatusContent from './content'; |  | ||||||
| import StatusActionBar from './action_bar'; |  | ||||||
| import StatusGallery from './gallery'; |  | ||||||
| import StatusPlayer from './player'; |  | ||||||
| import NotificationOverlayContainer from '../notification/overlay/container'; |  | ||||||
| 
 |  | ||||||
|                             /* * * * */ |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| The `<Status>` component: |  | ||||||
| ------------------------- |  | ||||||
| 
 |  | ||||||
| The `<Status>` component is a container for statuses. It consists of a |  | ||||||
| few parts: |  | ||||||
| 
 |  | ||||||
|  -  The `<StatusPrepend>`, which contains tangential information about |  | ||||||
|     the status, such as who reblogged it. |  | ||||||
|  -  The `<StatusHeader>`, which contains the avatar and username of the |  | ||||||
|     status author, as well as a media icon and the "collapse" toggle. |  | ||||||
|  -  The `<StatusContent>`, which contains the content of the status. |  | ||||||
|  -  The `<StatusActionBar>`, which provides actions to be performed |  | ||||||
|     on statuses, like reblogging or sending a reply. |  | ||||||
| 
 |  | ||||||
| ###  Context |  | ||||||
| 
 |  | ||||||
|  -  __`router` (`PropTypes.object`) :__ |  | ||||||
|     We need to get our router from the surrounding React context. |  | ||||||
| 
 |  | ||||||
| ###  Props |  | ||||||
| 
 |  | ||||||
|  -  __`id` (`PropTypes.number`) :__ |  | ||||||
|     The id of the status. |  | ||||||
| 
 |  | ||||||
|  -  __`status` (`ImmutablePropTypes.map`) :__ |  | ||||||
|     The status object, straight from the store. |  | ||||||
| 
 |  | ||||||
|  -  __`account` (`ImmutablePropTypes.map`) :__ |  | ||||||
|     Don't be confused by this one! This is **not** the account which |  | ||||||
|     posted the status, but the associated account with any further |  | ||||||
|     action (eg, a reblog or a favourite). |  | ||||||
| 
 |  | ||||||
|  -  __`settings` (`ImmutablePropTypes.map`) :__ |  | ||||||
|     These are our local settings, fetched from our store. We need this |  | ||||||
|     to determine how best to collapse our statuses, among other things. |  | ||||||
| 
 |  | ||||||
|  -  __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`, |  | ||||||
|     `onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`,
 |  | ||||||
|     `onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__ |  | ||||||
|     These are all functions passed through from the |  | ||||||
|     `<StatusContainer>`. We don't deal with them directly here. |  | ||||||
| 
 |  | ||||||
|  -  __`reblogModal`, `deleteModal` (`PropTypes.bool`) :__ |  | ||||||
|     These tell whether or not the user has modals activated for |  | ||||||
|     reblogging and deleting statuses. They are used by the `onReblog` |  | ||||||
|     and `onDelete` functions, but we don't deal with them here. |  | ||||||
| 
 |  | ||||||
|  -  __`muted` (`PropTypes.bool`) :__ |  | ||||||
|     This has nothing to do with a user or conversation mute! "Muted" is |  | ||||||
|     what Mastodon internally calls the subdued look of statuses in the |  | ||||||
|     notifications column. This should be `true` for notifications, and |  | ||||||
|     `false` otherwise. |  | ||||||
| 
 |  | ||||||
|  -  __`collapse` (`PropTypes.bool`) :__ |  | ||||||
|     This prop signals a directive from a higher power to (un)collapse |  | ||||||
|     a status. Most of the time it should be `undefined`, in which case |  | ||||||
|     we do nothing. |  | ||||||
| 
 |  | ||||||
|  -  __`prepend` (`PropTypes.string`) :__ |  | ||||||
|     The type of prepend: `'reblogged_by'`, `'reblog'`, or |  | ||||||
|     `'favourite'`. |  | ||||||
| 
 |  | ||||||
|  -  __`withDismiss` (`PropTypes.bool`) :__ |  | ||||||
|     Whether or not the status can be dismissed. Used for notifications. |  | ||||||
| 
 |  | ||||||
|  -  __`intersectionObserverWrapper` (`PropTypes.object`) :__ |  | ||||||
|     This holds our intersection observer. In Mastodon parlance, |  | ||||||
|     an "intersection" is just when the status is viewable onscreen. |  | ||||||
| 
 |  | ||||||
| ###  State |  | ||||||
| 
 |  | ||||||
|  -  __`isExpanded` :__ |  | ||||||
|     Should be either `true`, `false`, or `null`. The meanings of |  | ||||||
|     these values are as follows: |  | ||||||
| 
 |  | ||||||
|      -  __`true` :__ The status contains a CW and the CW is expanded. |  | ||||||
|      -  __`false` :__ The status is collapsed. |  | ||||||
|      -  __`null` :__ The status is not collapsed or expanded. |  | ||||||
| 
 |  | ||||||
|  -  __`isIntersecting` :__ |  | ||||||
|     This boolean tells us whether or not the status is currently |  | ||||||
|     onscreen. |  | ||||||
| 
 |  | ||||||
|  -  __`isHidden` :__ |  | ||||||
|     This boolean tells us if the status has been unrendered to save |  | ||||||
|     CPUs. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export default class Status extends ImmutablePureComponent { |  | ||||||
| 
 |  | ||||||
|   static contextTypes = { |  | ||||||
|     router                      : PropTypes.object, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     id                          : PropTypes.string, |  | ||||||
|     status                      : ImmutablePropTypes.map, |  | ||||||
|     account                     : ImmutablePropTypes.map, |  | ||||||
|     settings                    : ImmutablePropTypes.map, |  | ||||||
|     notification                : ImmutablePropTypes.map, |  | ||||||
|     onFavourite                 : PropTypes.func, |  | ||||||
|     onReblog                    : PropTypes.func, |  | ||||||
|     onModalReblog               : PropTypes.func, |  | ||||||
|     onDelete                    : PropTypes.func, |  | ||||||
|     onPin                       : PropTypes.func, |  | ||||||
|     onMention                   : PropTypes.func, |  | ||||||
|     onMute                      : PropTypes.func, |  | ||||||
|     onMuteConversation          : PropTypes.func, |  | ||||||
|     onBlock                     : PropTypes.func, |  | ||||||
|     onEmbed                     : PropTypes.func, |  | ||||||
|     onHeightChange              : PropTypes.func, |  | ||||||
|     onReport                    : PropTypes.func, |  | ||||||
|     onOpenMedia                 : PropTypes.func, |  | ||||||
|     onOpenVideo                 : PropTypes.func, |  | ||||||
|     reblogModal                 : PropTypes.bool, |  | ||||||
|     deleteModal                 : PropTypes.bool, |  | ||||||
|     muted                       : PropTypes.bool, |  | ||||||
|     collapse                    : PropTypes.bool, |  | ||||||
|     prepend                     : PropTypes.string, |  | ||||||
|     withDismiss                 : PropTypes.bool, |  | ||||||
|     intersectionObserverWrapper : PropTypes.object, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   state = { |  | ||||||
|     isExpanded                  : null, |  | ||||||
|     isIntersecting              : true, |  | ||||||
|     isHidden                    : false, |  | ||||||
|     markedForDelete             : false, |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ###  Implementation |  | ||||||
| 
 |  | ||||||
| ####  `updateOnProps` and `updateOnStates`. |  | ||||||
| 
 |  | ||||||
| `updateOnProps` and `updateOnStates` tell the component when to update. |  | ||||||
| We specify them explicitly because some of our props are dynamically= |  | ||||||
| generated functions, which would otherwise always trigger an update. |  | ||||||
| Of course, this means that if we add an important prop, we will need |  | ||||||
| to remember to specify it here. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   updateOnProps = [ |  | ||||||
|     'status', |  | ||||||
|     'account', |  | ||||||
|     'settings', |  | ||||||
|     'prepend', |  | ||||||
|     'boostModal', |  | ||||||
|     'muted', |  | ||||||
|     'collapse', |  | ||||||
|     'notification', |  | ||||||
|   ] |  | ||||||
| 
 |  | ||||||
|   updateOnStates = [ |  | ||||||
|     'isExpanded', |  | ||||||
|     'markedForDelete', |  | ||||||
|   ] |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `componentWillReceiveProps()`. |  | ||||||
| 
 |  | ||||||
| If our settings have changed to disable collapsed statuses, then we |  | ||||||
| need to make sure that we uncollapse every one. We do that by watching |  | ||||||
| for changes to `settings.collapsed.enabled` in |  | ||||||
| `componentWillReceiveProps()`. |  | ||||||
| 
 |  | ||||||
| We also need to watch for changes on the `collapse` prop---if this |  | ||||||
| changes to anything other than `undefined`, then we need to collapse or |  | ||||||
| uncollapse our status accordingly. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   componentWillReceiveProps (nextProps) { |  | ||||||
|     if (!nextProps.settings.getIn(['collapsed', 'enabled'])) { |  | ||||||
|       if (this.state.isExpanded === false) { |  | ||||||
|         this.setExpansion(null); |  | ||||||
|       } |  | ||||||
|     } else if ( |  | ||||||
|       nextProps.collapse !== this.props.collapse && |  | ||||||
|       nextProps.collapse !== undefined |  | ||||||
|     ) this.setExpansion(nextProps.collapse ? false : null); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `componentDidMount()`. |  | ||||||
| 
 |  | ||||||
| When mounting, we just check to see if our status should be collapsed, |  | ||||||
| and collapse it if so. We don't need to worry about whether collapsing |  | ||||||
| is enabled here, because `setExpansion()` already takes that into |  | ||||||
| account. |  | ||||||
| 
 |  | ||||||
| The cases where a status should be collapsed are: |  | ||||||
| 
 |  | ||||||
|  -  The `collapse` prop has been set to `true` |  | ||||||
|  -  The user has decided in local settings to collapse all statuses. |  | ||||||
|  -  The user has decided to collapse all notifications ('muted' |  | ||||||
|     statuses). |  | ||||||
|  -  The user has decided to collapse long statuses and the status is |  | ||||||
|     over 400px (without media, or 650px with). |  | ||||||
|  -  The status is a reply and the user has decided to collapse all |  | ||||||
|     replies. |  | ||||||
|  -  The status contains media and the user has decided to collapse all |  | ||||||
|     statuses with media. |  | ||||||
| 
 |  | ||||||
| We also start up our intersection observer to monitor our statuses. |  | ||||||
| `componentMounted` lets us know that everything has been set up |  | ||||||
| properly and our intersection observer is good to go. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   componentDidMount () { |  | ||||||
|     const { node, handleIntersection } = this; |  | ||||||
|     const { |  | ||||||
|       status, |  | ||||||
|       settings, |  | ||||||
|       collapse, |  | ||||||
|       muted, |  | ||||||
|       id, |  | ||||||
|       intersectionObserverWrapper, |  | ||||||
|       prepend, |  | ||||||
|     } = this.props; |  | ||||||
|     const autoCollapseSettings = settings.getIn(['collapsed', 'auto']); |  | ||||||
| 
 |  | ||||||
|     if ( |  | ||||||
|       collapse || |  | ||||||
|       autoCollapseSettings.get('all') || ( |  | ||||||
|         autoCollapseSettings.get('notifications') && muted |  | ||||||
|       ) || ( |  | ||||||
|         autoCollapseSettings.get('lengthy') && |  | ||||||
|         node.clientHeight > ( |  | ||||||
|           status.get('media_attachments').size && !muted ? 650 : 400 |  | ||||||
|         ) |  | ||||||
|       ) || ( |  | ||||||
|         autoCollapseSettings.get('reblogs') && |  | ||||||
|         prepend === 'reblogged_by' |  | ||||||
|       ) || ( |  | ||||||
|         autoCollapseSettings.get('replies') && |  | ||||||
|         status.get('in_reply_to_id', null) !== null |  | ||||||
|       ) || ( |  | ||||||
|         autoCollapseSettings.get('media') && |  | ||||||
|         !(status.get('spoiler_text').length) && |  | ||||||
|         status.get('media_attachments').size |  | ||||||
|       ) |  | ||||||
|     ) this.setExpansion(false); |  | ||||||
| 
 |  | ||||||
|     if (!intersectionObserverWrapper) return; |  | ||||||
|     else intersectionObserverWrapper.observe( |  | ||||||
|       id, |  | ||||||
|       node, |  | ||||||
|       handleIntersection |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     this.componentMounted = true; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `shouldComponentUpdate()`. |  | ||||||
| 
 |  | ||||||
| If the status is about to be both offscreen (not intersecting) and |  | ||||||
| hidden, then we only need to update it if it's not that way currently. |  | ||||||
| If the status is moving from offscreen to onscreen, then we *have* to |  | ||||||
| re-render, so that we can unhide the element if necessary. |  | ||||||
| 
 |  | ||||||
| If neither of these cases are true, we can leave it up to our |  | ||||||
| `updateOnProps` and `updateOnStates` arrays. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   shouldComponentUpdate (nextProps, nextState) { |  | ||||||
|     switch (true) { |  | ||||||
|     case !nextState.isIntersecting && nextState.isHidden: |  | ||||||
|       return this.state.isIntersecting || !this.state.isHidden; |  | ||||||
|     case nextState.isIntersecting && !this.state.isIntersecting: |  | ||||||
|       return true; |  | ||||||
|     default: |  | ||||||
|       return super.shouldComponentUpdate(nextProps, nextState); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `componentDidUpdate()`. |  | ||||||
| 
 |  | ||||||
| If our component is being rendered for any reason and an update has |  | ||||||
| triggered, this will save its height. |  | ||||||
| 
 |  | ||||||
| This is, frankly, a bit overkill, as the only instance when we |  | ||||||
| actually *need* to update the height right now should be when the |  | ||||||
| value of `isExpanded` has changed. But it makes for more readable |  | ||||||
| code and prevents bugs in the future where the height isn't set |  | ||||||
| properly after some change. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   componentDidUpdate () { |  | ||||||
|     if ( |  | ||||||
|       this.state.isIntersecting || !this.state.isHidden |  | ||||||
|     ) this.saveHeight(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `componentWillUnmount()`. |  | ||||||
| 
 |  | ||||||
| If our component is about to unmount, then we'd better unset |  | ||||||
| `this.componentMounted`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   componentWillUnmount () { |  | ||||||
|     this.componentMounted = false; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `handleIntersection()`. |  | ||||||
| 
 |  | ||||||
| `handleIntersection()` either hides the status (if it is offscreen) or |  | ||||||
| unhides it (if it is onscreen). It's called by |  | ||||||
| `intersectionObserverWrapper.observe()`. |  | ||||||
| 
 |  | ||||||
| If our status isn't intersecting, we schedule an idle task (using the |  | ||||||
| aptly-named `scheduleIdleTask()`) to hide the status at the next |  | ||||||
| available opportunity. |  | ||||||
| 
 |  | ||||||
| tootsuite/mastodon left us with the following enlightening comment |  | ||||||
| regarding this function: |  | ||||||
| 
 |  | ||||||
| >   Edge 15 doesn't support isIntersecting, but we can infer it |  | ||||||
| 
 |  | ||||||
| It then implements a polyfill (intersectionRect.height > 0) which isn't |  | ||||||
| actually sufficient. The short answer is, this behaviour isn't really |  | ||||||
| supported on Edge but we can get kinda close. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   handleIntersection = (entry) => { |  | ||||||
|     const isIntersecting = ( |  | ||||||
|       typeof entry.isIntersecting === 'boolean' ? |  | ||||||
|       entry.isIntersecting : |  | ||||||
|       entry.intersectionRect.height > 0 |  | ||||||
|     ); |  | ||||||
|     this.setState( |  | ||||||
|       (prevState) => { |  | ||||||
|         if (prevState.isIntersecting && !isIntersecting) { |  | ||||||
|           scheduleIdleTask(this.hideIfNotIntersecting); |  | ||||||
|         } |  | ||||||
|         return { |  | ||||||
|           isIntersecting : isIntersecting, |  | ||||||
|           isHidden       : false, |  | ||||||
|         }; |  | ||||||
|       } |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `hideIfNotIntersecting()`. |  | ||||||
| 
 |  | ||||||
| This function will hide the status if we're still not intersecting. |  | ||||||
| Hiding the status means that it will just render an empty div instead |  | ||||||
| of actual content, which saves RAMS and CPUs or some such. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   hideIfNotIntersecting = () => { |  | ||||||
|     if (!this.componentMounted) return; |  | ||||||
|     this.setState( |  | ||||||
|       (prevState) => ({ isHidden: !prevState.isIntersecting }) |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `saveHeight()`. |  | ||||||
| 
 |  | ||||||
| `saveHeight()` saves the height of our status so that when whe hide it |  | ||||||
| we preserve its dimensions. We only want to store our height, though, |  | ||||||
| if our status has content (otherwise, it would imply that it is |  | ||||||
| already hidden). |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   saveHeight = () => { |  | ||||||
|     if (this.node && this.node.children.length) { |  | ||||||
|       this.height = this.node.getBoundingClientRect().height; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `setExpansion()`. |  | ||||||
| 
 |  | ||||||
| `setExpansion()` sets the value of `isExpanded` in our state. It takes |  | ||||||
| one argument, `value`, which gives the desired value for `isExpanded`. |  | ||||||
| The default for this argument is `null`. |  | ||||||
| 
 |  | ||||||
| `setExpansion()` automatically checks for us whether toot collapsing |  | ||||||
| is enabled, so we don't have to. |  | ||||||
| 
 |  | ||||||
| We use a `switch` statement to simplify our code. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   setExpansion = (value) => { |  | ||||||
|     switch (true) { |  | ||||||
|     case value === undefined || value === null: |  | ||||||
|       this.setState({ isExpanded: null }); |  | ||||||
|       break; |  | ||||||
|     case !value && this.props.settings.getIn(['collapsed', 'enabled']): |  | ||||||
|       this.setState({ isExpanded: false }); |  | ||||||
|       break; |  | ||||||
|     case !!value: |  | ||||||
|       this.setState({ isExpanded: true }); |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `handleRef()`. |  | ||||||
| 
 |  | ||||||
| `handleRef()` just saves a reference to our status node to `this.node`. |  | ||||||
| It also saves our height, in case the height of our node has changed. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   handleRef = (node) => { |  | ||||||
|     this.node = node; |  | ||||||
|     this.saveHeight(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `parseClick()`. |  | ||||||
| 
 |  | ||||||
| `parseClick()` takes a click event and responds appropriately. |  | ||||||
| If our status is collapsed, then clicking on it should uncollapse it. |  | ||||||
| If `Shift` is held, then clicking on it should collapse it. |  | ||||||
| Otherwise, we open the url handed to us in `destination`, if |  | ||||||
| applicable. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   parseClick = (e, destination) => { |  | ||||||
|     const { router } = this.context; |  | ||||||
|     const { status } = this.props; |  | ||||||
|     const { isExpanded } = this.state; |  | ||||||
|     if (!router) return; |  | ||||||
|     if (destination === undefined) { |  | ||||||
|       destination = `/statuses/${ |  | ||||||
|         status.getIn(['reblog', 'id'], status.get('id')) |  | ||||||
|       }`;
 |  | ||||||
|     } |  | ||||||
|     if (e.button === 0) { |  | ||||||
|       if (isExpanded === false) this.setExpansion(null); |  | ||||||
|       else if (e.shiftKey) { |  | ||||||
|         this.setExpansion(false); |  | ||||||
|         document.getSelection().removeAllRanges(); |  | ||||||
|       } else router.history.push(destination); |  | ||||||
|       e.preventDefault(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| ####  `render()`. |  | ||||||
| 
 |  | ||||||
| `render()` actually puts our element on the screen. The particulars of |  | ||||||
| this operation are further explained in the code below. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { |  | ||||||
|       parseClick, |  | ||||||
|       setExpansion, |  | ||||||
|       saveHeight, |  | ||||||
|       handleRef, |  | ||||||
|     } = this; |  | ||||||
|     const { router } = this.context; |  | ||||||
|     const { |  | ||||||
|       status, |  | ||||||
|       account, |  | ||||||
|       settings, |  | ||||||
|       collapsed, |  | ||||||
|       muted, |  | ||||||
|       prepend, |  | ||||||
|       intersectionObserverWrapper, |  | ||||||
|       onOpenVideo, |  | ||||||
|       onOpenMedia, |  | ||||||
|       notification, |  | ||||||
|       ...other |  | ||||||
|     } = this.props; |  | ||||||
|     const { isExpanded, isIntersecting, isHidden } = this.state; |  | ||||||
|     let background = null; |  | ||||||
|     let attachments = null; |  | ||||||
|     let media = null; |  | ||||||
|     let mediaIcon = null; |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| If we don't have a status, then we don't render anything. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if (status === null) { |  | ||||||
|       return null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| If our status is offscreen and hidden, then we render an empty <div> in |  | ||||||
| its place. We fill it with "content" but note that opacity is set to 0. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if (!isIntersecting && isHidden) { |  | ||||||
|       return ( |  | ||||||
|         <div |  | ||||||
|           ref={this.handleRef} |  | ||||||
|           data-id={status.get('id')} |  | ||||||
|           style={{ |  | ||||||
|             height   : `${this.height}px`, |  | ||||||
|             opacity  : 0, |  | ||||||
|             overflow : 'hidden', |  | ||||||
|           }} |  | ||||||
|         > |  | ||||||
|           { |  | ||||||
|             status.getIn(['account', 'display_name']) || |  | ||||||
|             status.getIn(['account', 'username']) |  | ||||||
|           } |  | ||||||
|           {status.get('content')} |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| If user backgrounds for collapsed statuses are enabled, then we |  | ||||||
| initialize our background accordingly. This will only be rendered if |  | ||||||
| the status is collapsed. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     if ( |  | ||||||
|       settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds']) |  | ||||||
|     ) background = status.getIn(['account', 'header']); |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| This handles our media attachments. Note that we don't show media on |  | ||||||
| muted (notification) statuses. If the media type is unknown, then we |  | ||||||
| simply ignore it. |  | ||||||
| 
 |  | ||||||
| After we have generated our appropriate media element and stored it in |  | ||||||
| `media`, we snatch the thumbnail to use as our `background` if media |  | ||||||
| backgrounds for collapsed statuses are enabled. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     attachments = status.get('media_attachments'); |  | ||||||
|     if (attachments.size && !muted) { |  | ||||||
|       if (attachments.some((item) => item.get('type') === 'unknown')) { |  | ||||||
| 
 |  | ||||||
|       } else if ( |  | ||||||
|         attachments.getIn([0, 'type']) === 'video' |  | ||||||
|       ) { |  | ||||||
|         media = (  //  Media type is 'video'
 |  | ||||||
|           <StatusPlayer |  | ||||||
|             media={attachments.get(0)} |  | ||||||
|             sensitive={status.get('sensitive')} |  | ||||||
|             letterbox={settings.getIn(['media', 'letterbox'])} |  | ||||||
|             fullwidth={settings.getIn(['media', 'fullwidth'])} |  | ||||||
|             height={250} |  | ||||||
|             onOpenVideo={onOpenVideo} |  | ||||||
|           /> |  | ||||||
|         ); |  | ||||||
|         mediaIcon = 'video-camera'; |  | ||||||
|       } else {  //  Media type is 'image' or 'gifv'
 |  | ||||||
|         media = ( |  | ||||||
|           <StatusGallery |  | ||||||
|             media={attachments} |  | ||||||
|             sensitive={status.get('sensitive')} |  | ||||||
|             letterbox={settings.getIn(['media', 'letterbox'])} |  | ||||||
|             fullwidth={settings.getIn(['media', 'fullwidth'])} |  | ||||||
|             height={250} |  | ||||||
|             onOpenMedia={onOpenMedia} |  | ||||||
|             autoPlayGif={autoPlayGif} |  | ||||||
|           /> |  | ||||||
|         ); |  | ||||||
|         mediaIcon = 'picture-o'; |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if ( |  | ||||||
|         !status.get('sensitive') && |  | ||||||
|         !(status.get('spoiler_text').length > 0) && |  | ||||||
|         settings.getIn(['collapsed', 'backgrounds', 'preview_images']) |  | ||||||
|       ) background = attachments.getIn([0, 'preview_url']); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Here we prepare extra data-* attributes for CSS selectors. |  | ||||||
| Users can use those for theming, hiding avatars etc via UserStyle |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     const selectorAttribs = { |  | ||||||
|       'data-status-by': `@${status.getIn(['account', 'acct'])}`, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if (prepend && account) { |  | ||||||
|       const notifKind = { |  | ||||||
|         favourite: 'favourited', |  | ||||||
|         reblog: 'boosted', |  | ||||||
|         reblogged_by: 'boosted', |  | ||||||
|       }[prepend]; |  | ||||||
| 
 |  | ||||||
|       selectorAttribs[`data-${notifKind}-by`] = `@${account.get('acct')}`; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Finally, we can render our status. We just put the pieces together |  | ||||||
| from above. We only render the action bar if the status isn't |  | ||||||
| collapsed. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <article |  | ||||||
|         className={ |  | ||||||
|           `status${ |  | ||||||
|             muted ? ' muted' : '' |  | ||||||
|           } status-${status.get('visibility')}${ |  | ||||||
|             isExpanded === false ? ' collapsed' : '' |  | ||||||
|           }${ |  | ||||||
|             isExpanded === false && background ? ' has-background' : '' |  | ||||||
|           }${ |  | ||||||
|             this.state.markedForDelete ? ' marked-for-delete' : '' |  | ||||||
|           }` |  | ||||||
|         } |  | ||||||
|         style={{ |  | ||||||
|           backgroundImage: ( |  | ||||||
|             isExpanded === false && background ? |  | ||||||
|             `url(${background})` : |  | ||||||
|             'none' |  | ||||||
|           ), |  | ||||||
|         }} |  | ||||||
|         ref={handleRef} |  | ||||||
|         {...selectorAttribs} |  | ||||||
|       > |  | ||||||
|         {prepend && account ? ( |  | ||||||
|           <StatusPrepend |  | ||||||
|             type={prepend} |  | ||||||
|             account={account} |  | ||||||
|             parseClick={parseClick} |  | ||||||
|             notificationId={this.props.notificationId} |  | ||||||
|           /> |  | ||||||
|         ) : null} |  | ||||||
|         <StatusHeader |  | ||||||
|           status={status} |  | ||||||
|           friend={account} |  | ||||||
|           mediaIcon={mediaIcon} |  | ||||||
|           collapsible={settings.getIn(['collapsed', 'enabled'])} |  | ||||||
|           collapsed={isExpanded === false} |  | ||||||
|           parseClick={parseClick} |  | ||||||
|           setExpansion={setExpansion} |  | ||||||
|         /> |  | ||||||
|         <StatusContent |  | ||||||
|           status={status} |  | ||||||
|           media={media} |  | ||||||
|           mediaIcon={mediaIcon} |  | ||||||
|           expanded={isExpanded} |  | ||||||
|           setExpansion={setExpansion} |  | ||||||
|           onHeightUpdate={saveHeight} |  | ||||||
|           parseClick={parseClick} |  | ||||||
|           disabled={!router} |  | ||||||
|         /> |  | ||||||
|         {isExpanded !== false ? ( |  | ||||||
|           <StatusActionBar |  | ||||||
|             {...other} |  | ||||||
|             status={status} |  | ||||||
|             account={status.get('account')} |  | ||||||
|           /> |  | ||||||
|         ) : null} |  | ||||||
|         {notification ? ( |  | ||||||
|           <NotificationOverlayContainer |  | ||||||
|             notification={notification} |  | ||||||
|           /> |  | ||||||
|         ) : null} |  | ||||||
|       </article> |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,203 +0,0 @@ | ||||||
| //  Package imports  //
 |  | ||||||
| import React from 'react'; |  | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| import PropTypes from 'prop-types'; |  | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import IconButton from '../../../mastodon/components/icon_button'; |  | ||||||
| import { isIOS } from '../../../mastodon/is_mobile'; |  | ||||||
| 
 |  | ||||||
| const messages = defineMessages({ |  | ||||||
|   toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' }, |  | ||||||
|   toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' }, |  | ||||||
|   expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' }, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| @injectIntl |  | ||||||
| export default class StatusPlayer extends React.PureComponent { |  | ||||||
| 
 |  | ||||||
|   static contextTypes = { |  | ||||||
|     router: PropTypes.object, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   static propTypes = { |  | ||||||
|     media: ImmutablePropTypes.map.isRequired, |  | ||||||
|     letterbox: PropTypes.bool, |  | ||||||
|     fullwidth: PropTypes.bool, |  | ||||||
|     height: PropTypes.number, |  | ||||||
|     sensitive: PropTypes.bool, |  | ||||||
|     intl: PropTypes.object.isRequired, |  | ||||||
|     autoplay: PropTypes.bool, |  | ||||||
|     onOpenVideo: PropTypes.func.isRequired, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   static defaultProps = { |  | ||||||
|     height: 110, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   state = { |  | ||||||
|     visible: !this.props.sensitive, |  | ||||||
|     preview: true, |  | ||||||
|     muted: true, |  | ||||||
|     hasAudio: true, |  | ||||||
|     videoError: false, |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   handleClick = () => { |  | ||||||
|     this.setState({ muted: !this.state.muted }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleVideoClick = (e) => { |  | ||||||
|     e.stopPropagation(); |  | ||||||
| 
 |  | ||||||
|     const node = this.video; |  | ||||||
| 
 |  | ||||||
|     if (node.paused) { |  | ||||||
|       node.play(); |  | ||||||
|     } else { |  | ||||||
|       node.pause(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleOpen = () => { |  | ||||||
|     this.setState({ preview: !this.state.preview }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleVisibility = () => { |  | ||||||
|     this.setState({ |  | ||||||
|       visible: !this.state.visible, |  | ||||||
|       preview: true, |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleExpand = () => { |  | ||||||
|     this.video.pause(); |  | ||||||
|     this.props.onOpenVideo(this.props.media, this.video.currentTime); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setRef = (c) => { |  | ||||||
|     this.video = c; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleLoadedData = () => { |  | ||||||
|     if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) { |  | ||||||
|       this.setState({ hasAudio: false }); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleVideoError = () => { |  | ||||||
|     this.setState({ videoError: true }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentDidMount () { |  | ||||||
|     if (!this.video) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.video.addEventListener('loadeddata', this.handleLoadedData); |  | ||||||
|     this.video.addEventListener('error', this.handleVideoError); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentDidUpdate () { |  | ||||||
|     if (!this.video) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.video.addEventListener('loadeddata', this.handleLoadedData); |  | ||||||
|     this.video.addEventListener('error', this.handleVideoError); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   componentWillUnmount () { |  | ||||||
|     if (!this.video) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this.video.removeEventListener('loadeddata', this.handleLoadedData); |  | ||||||
|     this.video.removeEventListener('error', this.handleVideoError); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render () { |  | ||||||
|     const { media, intl, letterbox, fullwidth, height, sensitive, autoplay } = this.props; |  | ||||||
| 
 |  | ||||||
|     let spoilerButton = ( |  | ||||||
|       <div className={`status__video-player-spoiler ${this.state.visible ? 'status__video-player-spoiler--visible' : ''}`}> |  | ||||||
|         <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} /> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     let expandButton = !this.context.router ? '' : ( |  | ||||||
|       <div className='status__video-player-expand'> |  | ||||||
|         <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} /> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     let muteButton = ''; |  | ||||||
| 
 |  | ||||||
|     if (this.state.hasAudio) { |  | ||||||
|       muteButton = ( |  | ||||||
|         <div className='status__video-player-mute'> |  | ||||||
|           <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} /> |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (!this.state.visible) { |  | ||||||
|       if (sensitive) { |  | ||||||
|         return ( |  | ||||||
|           <div role='button' tabIndex='0' style={{ height: `${height}px` }} className={`media-spoiler ${fullwidth ? 'full-width' : ''}`} onClick={this.handleVisibility}> |  | ||||||
|             {spoilerButton} |  | ||||||
|             <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span> |  | ||||||
|             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       } else { |  | ||||||
|         return ( |  | ||||||
|           <div role='button' tabIndex='0' style={{ height: `${height}px` }} className={`media-spoiler ${fullwidth ? 'full-width' : ''}`} onClick={this.handleVisibility}> |  | ||||||
|             {spoilerButton} |  | ||||||
|             <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span> |  | ||||||
|             <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (this.state.preview && !autoplay) { |  | ||||||
|       return ( |  | ||||||
|         <div role='button' tabIndex='0' className={`media-spoiler-video ${fullwidth ? 'full-width' : ''}`} style={{ height: `${height}px`, backgroundImage: `url(${media.get('preview_url')})` }} onClick={this.handleOpen}> |  | ||||||
|           {spoilerButton} |  | ||||||
|           <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div> |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     if (this.state.videoError) { |  | ||||||
|       return ( |  | ||||||
|         <div style={{ height: `${height}px` }} className='video-error-cover' > |  | ||||||
|           <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span> |  | ||||||
|         </div> |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return ( |  | ||||||
|       <div className={`status__video-player ${fullwidth ? 'full-width' : ''}`} style={{ height: `${height}px` }}> |  | ||||||
|         {spoilerButton} |  | ||||||
|         {muteButton} |  | ||||||
|         {expandButton} |  | ||||||
| 
 |  | ||||||
|         <video |  | ||||||
|           className={`status__video-player-video${letterbox ? ' letterbox' : ''}`} |  | ||||||
|           role='button' |  | ||||||
|           tabIndex='0' |  | ||||||
|           ref={this.setRef} |  | ||||||
|           src={media.get('url')} |  | ||||||
|           autoPlay={!isIOS()} |  | ||||||
|           loop |  | ||||||
|           muted={this.state.muted} |  | ||||||
|           onClick={this.handleVideoClick} |  | ||||||
|         /> |  | ||||||
|       </div> |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
| } |  | ||||||
|  | @ -1,126 +0,0 @@ | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `reducers/local_settings` |  | ||||||
| ======================== |  | ||||||
| 
 |  | ||||||
| >   For more information on the contents of this file, please contact: |  | ||||||
| > |  | ||||||
| >   - kibigo! [@kibi@glitch.social] |  | ||||||
| 
 |  | ||||||
| This file provides our Redux reducers related to local settings. The |  | ||||||
| associated actions are: |  | ||||||
| 
 |  | ||||||
|  -  __`STORE_HYDRATE` :__ |  | ||||||
|     Used to hydrate the store with its initial values. |  | ||||||
| 
 |  | ||||||
|  -  __`LOCAL_SETTING_CHANGE` :__ |  | ||||||
|     Used to change the value of a local setting in the store. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Imports: |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| //  Package imports  //
 |  | ||||||
| import { Map as ImmutableMap } from 'immutable'; |  | ||||||
| 
 |  | ||||||
| //  Mastodon imports  //
 |  | ||||||
| import { STORE_HYDRATE } from '../../mastodon/actions/store'; |  | ||||||
| 
 |  | ||||||
| //  Our imports  //
 |  | ||||||
| import { LOCAL_SETTING_CHANGE } from '../actions/local_settings'; |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| initialState: |  | ||||||
| ------------- |  | ||||||
| 
 |  | ||||||
| You can see the default values for all of our local settings here. |  | ||||||
| These are only used if no previously-saved values exist. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const initialState = ImmutableMap({ |  | ||||||
|   layout    : 'auto', |  | ||||||
|   stretch   : true, |  | ||||||
|   navbar_under : false, |  | ||||||
|   side_arm  : 'none', |  | ||||||
|   collapsed : ImmutableMap({ |  | ||||||
|     enabled     : true, |  | ||||||
|     auto        : ImmutableMap({ |  | ||||||
|       all              : false, |  | ||||||
|       notifications    : true, |  | ||||||
|       lengthy          : true, |  | ||||||
|       reblogs          : false, |  | ||||||
|       replies          : false, |  | ||||||
|       media            : false, |  | ||||||
|     }), |  | ||||||
|     backgrounds : ImmutableMap({ |  | ||||||
|       user_backgrounds : false, |  | ||||||
|       preview_images   : false, |  | ||||||
|     }), |  | ||||||
|   }), |  | ||||||
|   media     : ImmutableMap({ |  | ||||||
|     letterbox   : true, |  | ||||||
|     fullwidth   : true, |  | ||||||
|   }), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| Helper functions: |  | ||||||
| ----------------- |  | ||||||
| 
 |  | ||||||
| ###  `hydrate(state, localSettings)` |  | ||||||
| 
 |  | ||||||
| `hydrate()` is used to hydrate the `local_settings` part of our store |  | ||||||
| with its initial values. The `state` will probably just be the |  | ||||||
| `initialState`, and the `localSettings` should be whatever we pulled |  | ||||||
| from `localStorage`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| const hydrate = (state, localSettings) => state.mergeDeep(localSettings); |  | ||||||
| 
 |  | ||||||
| //  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
| 
 |  | ||||||
| `localSettings(state = initialState, action)`: |  | ||||||
| ---------------------------------------------- |  | ||||||
| 
 |  | ||||||
| This function holds our actual reducer. |  | ||||||
| 
 |  | ||||||
| If our action is `STORE_HYDRATE`, then we call `hydrate()` with the |  | ||||||
| `local_settings` property of the provided `action.state`. |  | ||||||
| 
 |  | ||||||
| If our action is `LOCAL_SETTING_CHANGE`, then we set `action.key` in |  | ||||||
| our state to the provided `action.value`. Note that `action.key` MUST |  | ||||||
| be an array, since we use `setIn()`. |  | ||||||
| 
 |  | ||||||
| >   __Note :__ |  | ||||||
| >   We call this function `localSettings`, but its associated object |  | ||||||
| >   in the store is `local_settings`. |  | ||||||
| 
 |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| export default function localSettings(state = initialState, action) { |  | ||||||
|   switch(action.type) { |  | ||||||
|   case STORE_HYDRATE: |  | ||||||
|     return hydrate(state, action.state.get('local_settings')); |  | ||||||
|   case LOCAL_SETTING_CHANGE: |  | ||||||
|     return state.setIn(action.key, action.value); |  | ||||||
|   default: |  | ||||||
|     return state; |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
|  | @ -105,13 +105,12 @@ export function fetchAccountFail(id, error) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function followAccount(id, reblogs = true) { | export function followAccount(id) { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     const alreadyFollowing = getState().getIn(['relationships', id, 'following']); |  | ||||||
|     dispatch(followAccountRequest(id)); |     dispatch(followAccountRequest(id)); | ||||||
| 
 | 
 | ||||||
|     api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { |     api(getState).post(`/api/v1/accounts/${id}/follow`).then(response => { | ||||||
|       dispatch(followAccountSuccess(response.data, alreadyFollowing)); |       dispatch(followAccountSuccess(response.data)); | ||||||
|     }).catch(error => { |     }).catch(error => { | ||||||
|       dispatch(followAccountFail(error)); |       dispatch(followAccountFail(error)); | ||||||
|     }); |     }); | ||||||
|  | @ -137,11 +136,10 @@ export function followAccountRequest(id) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function followAccountSuccess(relationship, alreadyFollowing) { | export function followAccountSuccess(relationship) { | ||||||
|   return { |   return { | ||||||
|     type: ACCOUNT_FOLLOW_SUCCESS, |     type: ACCOUNT_FOLLOW_SUCCESS, | ||||||
|     relationship, |     relationship, | ||||||
|     alreadyFollowing, |  | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ import { | ||||||
|   refreshHomeTimeline, |   refreshHomeTimeline, | ||||||
|   refreshCommunityTimeline, |   refreshCommunityTimeline, | ||||||
|   refreshPublicTimeline, |   refreshPublicTimeline, | ||||||
|   refreshDirectTimeline, |  | ||||||
| } from './timelines'; | } from './timelines'; | ||||||
| 
 | 
 | ||||||
| export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE'; | export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE'; | ||||||
|  | @ -32,7 +31,6 @@ export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; | ||||||
| export const COMPOSE_MOUNT   = 'COMPOSE_MOUNT'; | export const COMPOSE_MOUNT   = 'COMPOSE_MOUNT'; | ||||||
| export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; | export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; | ||||||
| 
 | 
 | ||||||
| export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'; |  | ||||||
| export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; | export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; | ||||||
| export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; | export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; | ||||||
| export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; | export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; | ||||||
|  | @ -46,8 +44,6 @@ export const COMPOSE_UPLOAD_CHANGE_REQUEST     = 'COMPOSE_UPLOAD_UPDATE_REQUEST' | ||||||
| export const COMPOSE_UPLOAD_CHANGE_SUCCESS     = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; | export const COMPOSE_UPLOAD_CHANGE_SUCCESS     = 'COMPOSE_UPLOAD_UPDATE_SUCCESS'; | ||||||
| export const COMPOSE_UPLOAD_CHANGE_FAIL        = 'COMPOSE_UPLOAD_UPDATE_FAIL'; | export const COMPOSE_UPLOAD_CHANGE_FAIL        = 'COMPOSE_UPLOAD_UPDATE_FAIL'; | ||||||
| 
 | 
 | ||||||
| export const COMPOSE_DOODLE_SET        = 'COMPOSE_DOODLE_SET'; |  | ||||||
| 
 |  | ||||||
| export function changeCompose(text) { | export function changeCompose(text) { | ||||||
|   return { |   return { | ||||||
|     type: COMPOSE_CHANGE, |     type: COMPOSE_CHANGE, | ||||||
|  | @ -95,16 +91,14 @@ export function mentionCompose(account, router) { | ||||||
| 
 | 
 | ||||||
| export function submitCompose() { | export function submitCompose() { | ||||||
|   return function (dispatch, getState) { |   return function (dispatch, getState) { | ||||||
|     let status = getState().getIn(['compose', 'text'], ''); |     const status = getState().getIn(['compose', 'text'], ''); | ||||||
| 
 | 
 | ||||||
|     if (!status || !status.length) { |     if (!status || !status.length) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     dispatch(submitComposeRequest()); |     dispatch(submitComposeRequest()); | ||||||
|     if (getState().getIn(['compose', 'advanced_options', 'do_not_federate'])) { | 
 | ||||||
|       status = status + ' 👁️'; |  | ||||||
|     } |  | ||||||
|     api(getState).post('/api/v1/statuses', { |     api(getState).post('/api/v1/statuses', { | ||||||
|       status, |       status, | ||||||
|       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), |       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), | ||||||
|  | @ -134,8 +128,6 @@ export function submitCompose() { | ||||||
|       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { |       if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { | ||||||
|         insertOrRefresh('community', refreshCommunityTimeline); |         insertOrRefresh('community', refreshCommunityTimeline); | ||||||
|         insertOrRefresh('public', refreshPublicTimeline); |         insertOrRefresh('public', refreshPublicTimeline); | ||||||
|       } else if (response.data.visibility === 'direct') { |  | ||||||
|         insertOrRefresh('direct', refreshDirectTimeline); |  | ||||||
|       } |       } | ||||||
|     }).catch(function (error) { |     }).catch(function (error) { | ||||||
|       dispatch(submitComposeFail(error)); |       dispatch(submitComposeFail(error)); | ||||||
|  | @ -163,13 +155,6 @@ export function submitComposeFail(error) { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function doodleSet(options) { |  | ||||||
|   return { |  | ||||||
|     type: COMPOSE_DOODLE_SET, |  | ||||||
|     options: options, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function uploadCompose(files) { | export function uploadCompose(files) { | ||||||
|   return function (dispatch, getState) { |   return function (dispatch, getState) { | ||||||
|     if (getState().getIn(['compose', 'media_attachments']).size > 3) { |     if (getState().getIn(['compose', 'media_attachments']).size > 3) { | ||||||
|  | @ -349,13 +334,6 @@ export function unmountCompose() { | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export function toggleComposeAdvancedOption(option) { |  | ||||||
|   return { |  | ||||||
|     type: COMPOSE_ADVANCED_OPTIONS_CHANGE, |  | ||||||
|     option: option, |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function changeComposeSensitivity() { | export function changeComposeSensitivity() { | ||||||
|   return { |   return { | ||||||
|     type: COMPOSE_SENSITIVITY_CHANGE, |     type: COMPOSE_SENSITIVITY_CHANGE, | ||||||
|  |  | ||||||
|  | @ -6,17 +6,6 @@ import { defineMessages } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; | export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; | ||||||
| 
 | 
 | ||||||
| // tracking the notif cleaning request
 |  | ||||||
| export const NOTIFICATIONS_DELETE_MARKED_REQUEST = 'NOTIFICATIONS_DELETE_MARKED_REQUEST'; |  | ||||||
| export const NOTIFICATIONS_DELETE_MARKED_SUCCESS = 'NOTIFICATIONS_DELETE_MARKED_SUCCESS'; |  | ||||||
| export const NOTIFICATIONS_DELETE_MARKED_FAIL = 'NOTIFICATIONS_DELETE_MARKED_FAIL'; |  | ||||||
| export const NOTIFICATIONS_MARK_ALL_FOR_DELETE = 'NOTIFICATIONS_MARK_ALL_FOR_DELETE'; |  | ||||||
| export const NOTIFICATIONS_ENTER_CLEARING_MODE = 'NOTIFICATIONS_ENTER_CLEARING_MODE'; // arg: yes
 |  | ||||||
| // Unmark notifications (when the cleaning mode is left)
 |  | ||||||
| export const NOTIFICATIONS_UNMARK_ALL_FOR_DELETE = 'NOTIFICATIONS_UNMARK_ALL_FOR_DELETE'; |  | ||||||
| // Mark one for delete
 |  | ||||||
| export const NOTIFICATION_MARK_FOR_DELETE = 'NOTIFICATION_MARK_FOR_DELETE'; |  | ||||||
| 
 |  | ||||||
| export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; | export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST'; | ||||||
| export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; | export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS'; | ||||||
| export const NOTIFICATIONS_REFRESH_FAIL    = 'NOTIFICATIONS_REFRESH_FAIL'; | export const NOTIFICATIONS_REFRESH_FAIL    = 'NOTIFICATIONS_REFRESH_FAIL'; | ||||||
|  | @ -199,67 +188,3 @@ export function scrollTopNotifications(top) { | ||||||
|     top, |     top, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
| 
 |  | ||||||
| export function deleteMarkedNotifications() { |  | ||||||
|   return (dispatch, getState) => { |  | ||||||
|     dispatch(deleteMarkedNotificationsRequest()); |  | ||||||
| 
 |  | ||||||
|     let ids = []; |  | ||||||
|     getState().getIn(['notifications', 'items']).forEach((n) => { |  | ||||||
|       if (n.get('markedForDelete')) { |  | ||||||
|         ids.push(n.get('id')); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     if (ids.length === 0) { |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     api(getState).delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => { |  | ||||||
|       dispatch(deleteMarkedNotificationsSuccess()); |  | ||||||
|     }).catch(error => { |  | ||||||
|       console.error(error); |  | ||||||
|       dispatch(deleteMarkedNotificationsFail(error)); |  | ||||||
|     }); |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function enterNotificationClearingMode(yes) { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATIONS_ENTER_CLEARING_MODE, |  | ||||||
|     yes: yes, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function markAllNotifications(yes) { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATIONS_MARK_ALL_FOR_DELETE, |  | ||||||
|     yes: yes, // true, false or null. null = invert
 |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function deleteMarkedNotificationsRequest() { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATIONS_DELETE_MARKED_REQUEST, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function deleteMarkedNotificationsFail() { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATIONS_DELETE_MARKED_FAIL, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function markNotificationForDelete(id, yes) { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATION_MARK_FOR_DELETE, |  | ||||||
|     id: id, |  | ||||||
|     yes: yes, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export function deleteMarkedNotificationsSuccess() { |  | ||||||
|   return { |  | ||||||
|     type: NOTIFICATIONS_DELETE_MARKED_SUCCESS, |  | ||||||
|   }; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  | @ -51,4 +51,3 @@ export const connectCommunityStream = () => connectTimelineStream('community', ' | ||||||
| export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); | export const connectMediaStream = () => connectTimelineStream('community', 'public:local'); | ||||||
| export const connectPublicStream = () => connectTimelineStream('public', 'public'); | export const connectPublicStream = () => connectTimelineStream('public', 'public'); | ||||||
| export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); | export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); | ||||||
| export const connectDirectStream = () => connectTimelineStream('direct', 'direct'); |  | ||||||
|  |  | ||||||
|  | @ -115,7 +115,6 @@ export function refreshTimeline(timelineId, path, params = {}) { | ||||||
| export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home'); | export const refreshHomeTimeline         = () => refreshTimeline('home', '/api/v1/timelines/home'); | ||||||
| export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public'); | export const refreshPublicTimeline       = () => refreshTimeline('public', '/api/v1/timelines/public'); | ||||||
| export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); | export const refreshCommunityTimeline    = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||||
| export const refreshDirectTimeline       = () => refreshTimeline('direct', '/api/v1/timelines/direct'); |  | ||||||
| export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | export const refreshAccountTimeline      = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | ||||||
| export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | ||||||
| export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | export const refreshHashtagTimeline      = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||||
|  | @ -156,7 +155,6 @@ export function expandTimeline(timelineId, path, params = {}) { | ||||||
| export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/timelines/home'); | export const expandHomeTimeline         = () => expandTimeline('home', '/api/v1/timelines/home'); | ||||||
| export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public'); | export const expandPublicTimeline       = () => expandTimeline('public', '/api/v1/timelines/public'); | ||||||
| export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); | export const expandCommunityTimeline    = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); | ||||||
| export const expandDirectTimeline       = () => expandTimeline('direct', '/api/v1/timelines/direct'); |  | ||||||
| export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | export const expandAccountTimeline      = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); | ||||||
| export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); | ||||||
| export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | export const expandHashtagTimeline      = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ | ||||||
| exports[`<Avatar /> Autoplay renders a animated avatar 1`] = ` | exports[`<Avatar /> Autoplay renders a animated avatar 1`] = ` | ||||||
| <div | <div | ||||||
|   className="account__avatar" |   className="account__avatar" | ||||||
|   data-avatar-of="@alice" |  | ||||||
|   onMouseEnter={[Function]} |   onMouseEnter={[Function]} | ||||||
|   onMouseLeave={[Function]} |   onMouseLeave={[Function]} | ||||||
|   style={ |   style={ | ||||||
|  | @ -20,7 +19,6 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = ` | ||||||
| exports[`<Avatar /> Still renders a still avatar 1`] = ` | exports[`<Avatar /> Still renders a still avatar 1`] = ` | ||||||
| <div | <div | ||||||
|   className="account__avatar" |   className="account__avatar" | ||||||
|   data-avatar-of="@alice" |  | ||||||
|   onMouseEnter={[Function]} |   onMouseEnter={[Function]} | ||||||
|   onMouseLeave={[Function]} |   onMouseLeave={[Function]} | ||||||
|   style={ |   style={ | ||||||
|  |  | ||||||
|  | @ -6,7 +6,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = ` | ||||||
| > | > | ||||||
|   <div |   <div | ||||||
|     className="account__avatar-overlay-base" |     className="account__avatar-overlay-base" | ||||||
|     data-avatar-of="@alice" |  | ||||||
|     style={ |     style={ | ||||||
|       Object { |       Object { | ||||||
|         "backgroundImage": "url(/static/alice.jpg)", |         "backgroundImage": "url(/static/alice.jpg)", | ||||||
|  | @ -15,7 +14,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = ` | ||||||
|   /> |   /> | ||||||
|   <div |   <div | ||||||
|     className="account__avatar-overlay-overlay" |     className="account__avatar-overlay-overlay" | ||||||
|     data-avatar-of="@eve@blackhat.lair" |  | ||||||
|     style={ |     style={ | ||||||
|       Object { |       Object { | ||||||
|         "backgroundImage": "url(/static/eve.jpg)", |         "backgroundImage": "url(/static/eve.jpg)", | ||||||
|  |  | ||||||
|  | @ -112,19 +112,3 @@ exports[`<Button /> renders the props.text instead of children 1`] = ` | ||||||
|   foo |   foo | ||||||
| </button> | </button> | ||||||
| `; | `; | ||||||
| 
 |  | ||||||
| exports[`<Button /> renders title if props.title is given 1`] = ` |  | ||||||
| <button |  | ||||||
|   className="button" |  | ||||||
|   disabled={undefined} |  | ||||||
|   onClick={[Function]} |  | ||||||
|   style={ |  | ||||||
|     Object { |  | ||||||
|       "height": "36px", |  | ||||||
|       "lineHeight": "36px", |  | ||||||
|       "padding": "0 16px", |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   title="foo" |  | ||||||
| /> |  | ||||||
| `; |  | ||||||
|  |  | ||||||
|  | @ -72,11 +72,4 @@ describe('<Button />', () => { | ||||||
| 
 | 
 | ||||||
|     expect(tree).toMatchSnapshot(); |     expect(tree).toMatchSnapshot(); | ||||||
|   }); |   }); | ||||||
| 
 |  | ||||||
|   it('renders title if props.title is given', () => { |  | ||||||
|     const component = renderer.create(<Button title='foo' />); |  | ||||||
|     const tree      = component.toJSON(); |  | ||||||
| 
 |  | ||||||
|     expect(tree).toMatchSnapshot(); |  | ||||||
|   }); |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -15,8 +15,8 @@ const messages = defineMessages({ | ||||||
|   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, |   requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, | ||||||
|   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, |   unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, | ||||||
|   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, |   unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, | ||||||
|   mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' }, |   mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'Mute notifications from @{name}' }, | ||||||
|   unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' }, |   unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'Unmute notifications from @{name}' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @injectIntl | @injectIntl | ||||||
|  | @ -93,7 +93,7 @@ export default class Account extends ImmutablePureComponent { | ||||||
|           </div> |           </div> | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following ? true : false} />; |         buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -11,8 +11,8 @@ import classNames from 'classnames'; | ||||||
| const textAtCursorMatchesToken = (str, caretPosition) => { | const textAtCursorMatchesToken = (str, caretPosition) => { | ||||||
|   let word; |   let word; | ||||||
| 
 | 
 | ||||||
|   let left  = str.slice(0, caretPosition).search(/[^\s\u200B]+$/); |   let left  = str.slice(0, caretPosition).search(/\S+$/); | ||||||
|   let right = str.slice(caretPosition).search(/[\s\u200B]/); |   let right = str.slice(caretPosition).search(/\s/); | ||||||
| 
 | 
 | ||||||
|   if (right < 0) { |   if (right < 0) { | ||||||
|     word = str.slice(left); |     word = str.slice(left); | ||||||
|  |  | ||||||
|  | @ -64,7 +64,6 @@ export default class Avatar extends React.PureComponent { | ||||||
|         onMouseEnter={this.handleMouseEnter} |         onMouseEnter={this.handleMouseEnter} | ||||||
|         onMouseLeave={this.handleMouseLeave} |         onMouseLeave={this.handleMouseLeave} | ||||||
|         style={style} |         style={style} | ||||||
|         data-avatar-of={`@${account.get('acct')}`} |  | ||||||
|       /> |       /> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -21,8 +21,8 @@ export default class AvatarOverlay extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div className='account__avatar-overlay'> |       <div className='account__avatar-overlay'> | ||||||
|         <div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} /> |         <div className='account__avatar-overlay-base' style={baseStyle} /> | ||||||
|         <div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} /> |         <div className='account__avatar-overlay-overlay' style={overlayStyle} /> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -14,7 +14,6 @@ export default class Button extends React.PureComponent { | ||||||
|     className: PropTypes.string, |     className: PropTypes.string, | ||||||
|     style: PropTypes.object, |     style: PropTypes.object, | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     title: PropTypes.string, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|  | @ -36,26 +35,26 @@ export default class Button extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     let attrs = { |     const style = { | ||||||
|       className: classNames('button', this.props.className, { |       padding: `0 ${this.props.size / 2.25}px`, | ||||||
|         'button-secondary': this.props.secondary, |       height: `${this.props.size}px`, | ||||||
|         'button--block': this.props.block, |       lineHeight: `${this.props.size}px`, | ||||||
|       }), |       ...this.props.style, | ||||||
|       disabled: this.props.disabled, |  | ||||||
|       onClick: this.handleClick, |  | ||||||
|       ref: this.setRef, |  | ||||||
|       style: { |  | ||||||
|         padding: `0 ${this.props.size / 2.25}px`, |  | ||||||
|         height: `${this.props.size}px`, |  | ||||||
|         lineHeight: `${this.props.size}px`, |  | ||||||
|         ...this.props.style, |  | ||||||
|       }, |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if (this.props.title) attrs.title = this.props.title; |     const className = classNames('button', this.props.className, { | ||||||
|  |       'button-secondary': this.props.secondary, | ||||||
|  |       'button--block': this.props.block, | ||||||
|  |     }); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <button {...attrs}> |       <button | ||||||
|  |         className={className} | ||||||
|  |         disabled={this.props.disabled} | ||||||
|  |         onClick={this.handleClick} | ||||||
|  |         ref={this.setRef} | ||||||
|  |         style={style} | ||||||
|  |       > | ||||||
|         {this.props.text || this.props.children} |         {this.props.text || this.props.children} | ||||||
|       </button> |       </button> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -7,8 +7,6 @@ export default class Column extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     extraClasses: PropTypes.string, |  | ||||||
|     name: PropTypes.string, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   scrollTop () { |   scrollTop () { | ||||||
|  | @ -42,10 +40,10 @@ export default class Column extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { children, extraClasses, name } = this.props; |     const { children } = this.props; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div role='region' data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}> |       <div role='region' className='column' ref={this.setRef}> | ||||||
|         {children} |         {children} | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -9,8 +9,7 @@ export default class ColumnBackButton extends React.PureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleClick = () => { |   handleClick = () => { | ||||||
|     // if history is exhausted, or we would leave mastodon, just go to root.
 |     if (window.history && window.history.length === 1) { | ||||||
|     if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) { |  | ||||||
|       this.context.router.history.push('/'); |       this.context.router.history.push('/'); | ||||||
|     } else { |     } else { | ||||||
|       this.context.router.history.goBack(); |       this.context.router.history.goBack(); | ||||||
|  |  | ||||||
|  | @ -9,12 +9,8 @@ export default class ColumnBackButtonSlim extends React.PureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleClick = () => { |   handleClick = () => { | ||||||
|     // if history is exhausted, or we would leave mastodon, just go to root.
 |     if (window.history && window.history.length === 1) this.context.router.history.push('/'); | ||||||
|     if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) { |     else this.context.router.history.goBack(); | ||||||
|       this.context.router.history.push('/'); |  | ||||||
|     } else { |  | ||||||
|       this.context.router.history.goBack(); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|  |  | ||||||
|  | @ -1,18 +1,13 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; | import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; |  | ||||||
| 
 |  | ||||||
| // Glitch imports
 |  | ||||||
| import NotificationPurgeButtonsContainer from '../../glitch/components/column/notif_cleaning_widget/container'; |  | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, |   show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, | ||||||
|   hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, |   hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, | ||||||
|   moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' }, |   moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' }, | ||||||
|   moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, |   moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, | ||||||
|   enterNotifCleaning : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @injectIntl | @injectIntl | ||||||
|  | @ -27,19 +22,14 @@ export default class ColumnHeader extends React.PureComponent { | ||||||
|     title: PropTypes.node.isRequired, |     title: PropTypes.node.isRequired, | ||||||
|     icon: PropTypes.string.isRequired, |     icon: PropTypes.string.isRequired, | ||||||
|     active: PropTypes.bool, |     active: PropTypes.bool, | ||||||
|     localSettings : ImmutablePropTypes.map, |  | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|     focusable: PropTypes.bool, |     focusable: PropTypes.bool, | ||||||
|     showBackButton: PropTypes.bool, |     showBackButton: PropTypes.bool, | ||||||
|     notifCleaning: PropTypes.bool, // true only for the notification column
 |  | ||||||
|     notifCleaningActive: PropTypes.bool, |  | ||||||
|     onEnterCleaningMode: PropTypes.func, |  | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     pinned: PropTypes.bool, |     pinned: PropTypes.bool, | ||||||
|     onPin: PropTypes.func, |     onPin: PropTypes.func, | ||||||
|     onMove: PropTypes.func, |     onMove: PropTypes.func, | ||||||
|     onClick: PropTypes.func, |     onClick: PropTypes.func, | ||||||
|     intl: PropTypes.object.isRequired, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|  | @ -49,7 +39,6 @@ export default class ColumnHeader extends React.PureComponent { | ||||||
|   state = { |   state = { | ||||||
|     collapsed: true, |     collapsed: true, | ||||||
|     animating: false, |     animating: false, | ||||||
|     animatingNCD: false, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleToggleClick = (e) => { |   handleToggleClick = (e) => { | ||||||
|  | @ -70,32 +59,17 @@ export default class ColumnHeader extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleBackClick = () => { |   handleBackClick = () => { | ||||||
|     // if history is exhausted, or we would leave mastodon, just go to root.
 |     if (window.history && window.history.length === 1) this.context.router.history.push('/'); | ||||||
|     if (window.history && (window.history.length === 1 || window.history.length === window._mastoInitialHistoryLen)) { |     else this.context.router.history.goBack(); | ||||||
|       this.context.router.history.push('/'); |  | ||||||
|     } else { |  | ||||||
|       this.context.router.history.goBack(); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleTransitionEnd = () => { |   handleTransitionEnd = () => { | ||||||
|     this.setState({ animating: false }); |     this.setState({ animating: false }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleTransitionEndNCD = () => { |  | ||||||
|     this.setState({ animatingNCD: false }); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onEnterCleaningMode = () => { |  | ||||||
|     this.setState({ animatingNCD: true }); |  | ||||||
|     this.props.onEnterCleaningMode(!this.props.notifCleaningActive); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render () { |   render () { | ||||||
|     const { intl, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props; |     const { title, icon, active, children, pinned, onPin, multiColumn, focusable, showBackButton, intl: { formatMessage } } = this.props; | ||||||
|     const { collapsed, animating, animatingNCD } = this.state; |     const { collapsed, animating } = this.state; | ||||||
| 
 |  | ||||||
|     let title = this.props.title; |  | ||||||
| 
 | 
 | ||||||
|     const wrapperClassName = classNames('column-header__wrapper', { |     const wrapperClassName = classNames('column-header__wrapper', { | ||||||
|       'active': active, |       'active': active, | ||||||
|  | @ -114,20 +88,8 @@ export default class ColumnHeader extends React.PureComponent { | ||||||
|       'active': !collapsed, |       'active': !collapsed, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const notifCleaningButtonClassName = classNames('column-header__button', { |  | ||||||
|       'active': notifCleaningActive, |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     const notifCleaningDrawerClassName = classNames('ncd column-header__collapsible', { |  | ||||||
|       'collapsed': !notifCleaningActive, |  | ||||||
|       'animating': animatingNCD, |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
|     let extraContent, pinButton, moveButtons, backButton, collapseButton; |     let extraContent, pinButton, moveButtons, backButton, collapseButton; | ||||||
| 
 | 
 | ||||||
|     //*glitch
 |  | ||||||
|     const msgEnterNotifCleaning = intl.formatMessage(messages.enterNotifCleaning); |  | ||||||
| 
 |  | ||||||
|     if (children) { |     if (children) { | ||||||
|       extraContent = ( |       extraContent = ( | ||||||
|         <div key='extra-content' className='column-header__collapsible__extra'> |         <div key='extra-content' className='column-header__collapsible__extra'> | ||||||
|  | @ -178,30 +140,13 @@ export default class ColumnHeader extends React.PureComponent { | ||||||
|           <span className='column-header__title'> |           <span className='column-header__title'> | ||||||
|             {title} |             {title} | ||||||
|           </span> |           </span> | ||||||
|  | 
 | ||||||
|           <div className='column-header__buttons'> |           <div className='column-header__buttons'> | ||||||
|             {backButton} |             {backButton} | ||||||
|             { notifCleaning ? ( |  | ||||||
|               <button |  | ||||||
|                 aria-label={msgEnterNotifCleaning} |  | ||||||
|                 title={msgEnterNotifCleaning} |  | ||||||
|                 onClick={this.onEnterCleaningMode} |  | ||||||
|                 className={notifCleaningButtonClassName} |  | ||||||
|               > |  | ||||||
|                 <i className='fa fa-eraser' /> |  | ||||||
|               </button> |  | ||||||
|             ) : null} |  | ||||||
|             {collapseButton} |             {collapseButton} | ||||||
|           </div> |           </div> | ||||||
|         </h1> |         </h1> | ||||||
| 
 | 
 | ||||||
|         { notifCleaning ? ( |  | ||||||
|           <div className={notifCleaningDrawerClassName} onTransitionEnd={this.handleTransitionEndNCD}> |  | ||||||
|             <div className='column-header__collapsible-inner nopad-drawer'> |  | ||||||
|               {(notifCleaningActive || animatingNCD) ? (<NotificationPurgeButtonsContainer />) : null } |  | ||||||
|             </div> |  | ||||||
|           </div> |  | ||||||
|         ) : null} |  | ||||||
| 
 |  | ||||||
|         <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}> |         <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}> | ||||||
|           <div className='column-header__collapsible-inner'> |           <div className='column-header__collapsible-inner'> | ||||||
|             {(!collapsed || animating) && collapsedContent} |             {(!collapsed || animating) && collapsedContent} | ||||||
|  |  | ||||||
|  | @ -20,10 +20,8 @@ export default class IconButton extends React.PureComponent { | ||||||
|     disabled: PropTypes.bool, |     disabled: PropTypes.bool, | ||||||
|     inverted: PropTypes.bool, |     inverted: PropTypes.bool, | ||||||
|     animate: PropTypes.bool, |     animate: PropTypes.bool, | ||||||
|     flip: PropTypes.bool, |  | ||||||
|     overlay: PropTypes.bool, |     overlay: PropTypes.bool, | ||||||
|     tabIndex: PropTypes.string, |     tabIndex: PropTypes.string, | ||||||
|     label: PropTypes.string, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|  | @ -44,18 +42,14 @@ export default class IconButton extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     let style = { |     const style = { | ||||||
|       fontSize: `${this.props.size}px`, |       fontSize: `${this.props.size}px`, | ||||||
|  |       width: `${this.props.size * 1.28571429}px`, | ||||||
|       height: `${this.props.size * 1.28571429}px`, |       height: `${this.props.size * 1.28571429}px`, | ||||||
|       lineHeight: `${this.props.size}px`, |       lineHeight: `${this.props.size}px`, | ||||||
|       ...this.props.style, |       ...this.props.style, | ||||||
|       ...(this.props.active ? this.props.activeStyle : {}), |       ...(this.props.active ? this.props.activeStyle : {}), | ||||||
|     }; |     }; | ||||||
|     if (!this.props.label) { |  | ||||||
|       style.width = `${this.props.size * 1.28571429}px`; |  | ||||||
|     } else { |  | ||||||
|       style.textAlign = 'left'; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     const { |     const { | ||||||
|       active, |       active, | ||||||
|  | @ -65,7 +59,6 @@ export default class IconButton extends React.PureComponent { | ||||||
|       expanded, |       expanded, | ||||||
|       icon, |       icon, | ||||||
|       inverted, |       inverted, | ||||||
|       flip, |  | ||||||
|       overlay, |       overlay, | ||||||
|       pressed, |       pressed, | ||||||
|       tabIndex, |       tabIndex, | ||||||
|  | @ -79,21 +72,6 @@ export default class IconButton extends React.PureComponent { | ||||||
|       overlayed: overlay, |       overlayed: overlay, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const flipDeg = flip ? -180 : -360; |  | ||||||
|     const rotateDeg = active ? flipDeg : 0; |  | ||||||
| 
 |  | ||||||
|     const motionDefaultStyle = { |  | ||||||
|       rotate: rotateDeg, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const springOpts = { |  | ||||||
|       stiffness: this.props.flip ? 60 : 120, |  | ||||||
|       damping: 7, |  | ||||||
|     }; |  | ||||||
|     const motionStyle = { |  | ||||||
|       rotate: animate ? spring(rotateDeg, springOpts) : 0, |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     if (!animate) { |     if (!animate) { | ||||||
|       // Perf optimization: avoid unnecessary <Motion> components unless
 |       // Perf optimization: avoid unnecessary <Motion> components unless
 | ||||||
|       // we actually need to animate.
 |       // we actually need to animate.
 | ||||||
|  | @ -114,7 +92,7 @@ export default class IconButton extends React.PureComponent { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Motion defaultStyle={motionDefaultStyle} style={motionStyle}> |       <Motion defaultStyle={{ rotate: active ? -360 : 0 }} style={{ rotate: animate ? spring(active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}> | ||||||
|         {({ rotate }) => |         {({ rotate }) => | ||||||
|           <button |           <button | ||||||
|             aria-label={title} |             aria-label={title} | ||||||
|  | @ -127,7 +105,6 @@ export default class IconButton extends React.PureComponent { | ||||||
|             tabIndex={tabIndex} |             tabIndex={tabIndex} | ||||||
|           > |           > | ||||||
|             <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' /> |             <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' /> | ||||||
|             {this.props.label} |  | ||||||
|           </button> |           </button> | ||||||
|         } |         } | ||||||
|       </Motion> |       </Motion> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/status/gallery
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/status
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/status/action_bar
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/status/content
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import StatusContainer from '../../glitch/components/status/container'; | import StatusContainer from '../containers/status_container'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import ScrollableList from './scrollable_list'; | import ScrollableList from './scrollable_list'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/status/container
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import Status from '../components/status'; | import Status from '../components/status'; | ||||||
|  |  | ||||||
|  | @ -20,8 +20,6 @@ const messages = defineMessages({ | ||||||
|   media: { id: 'account.media', defaultMessage: 'Media' }, |   media: { id: 'account.media', defaultMessage: 'Media' }, | ||||||
|   blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, |   blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' }, | ||||||
|   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, |   unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, | ||||||
|   hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' }, |  | ||||||
|   showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @injectIntl | @injectIntl | ||||||
|  | @ -32,7 +30,6 @@ export default class ActionBar extends React.PureComponent { | ||||||
|     onFollow: PropTypes.func, |     onFollow: PropTypes.func, | ||||||
|     onBlock: PropTypes.func.isRequired, |     onBlock: PropTypes.func.isRequired, | ||||||
|     onMention: PropTypes.func.isRequired, |     onMention: PropTypes.func.isRequired, | ||||||
|     onReblogToggle: PropTypes.func.isRequired, |  | ||||||
|     onReport: PropTypes.func.isRequired, |     onReport: PropTypes.func.isRequired, | ||||||
|     onMute: PropTypes.func.isRequired, |     onMute: PropTypes.func.isRequired, | ||||||
|     onBlockDomain: PropTypes.func.isRequired, |     onBlockDomain: PropTypes.func.isRequired, | ||||||
|  | @ -63,15 +60,6 @@ export default class ActionBar extends React.PureComponent { | ||||||
|     if (account.get('id') === me) { |     if (account.get('id') === me) { | ||||||
|       menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); |       menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' }); | ||||||
|     } else { |     } else { | ||||||
|       const following = account.getIn(['relationship', 'following']); |  | ||||||
|       if (following) { |  | ||||||
|         if (following.get('reblogs')) { |  | ||||||
|           menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); |  | ||||||
|         } else { |  | ||||||
|           menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (account.getIn(['relationship', 'muting'])) { |       if (account.getIn(['relationship', 'muting'])) { | ||||||
|         menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); |         menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute }); | ||||||
|       } else { |       } else { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/account/header
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import InnerHeader from '../../../../glitch/components/account/header'; | import InnerHeader from '../../account/components/header'; | ||||||
| import ActionBar from '../../account/components/action_bar'; | import ActionBar from '../../account/components/action_bar'; | ||||||
| import MissingIndicator from '../../../components/missing_indicator'; | import MissingIndicator from '../../../components/missing_indicator'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  | @ -13,7 +13,6 @@ export default class Header extends ImmutablePureComponent { | ||||||
|     onFollow: PropTypes.func.isRequired, |     onFollow: PropTypes.func.isRequired, | ||||||
|     onBlock: PropTypes.func.isRequired, |     onBlock: PropTypes.func.isRequired, | ||||||
|     onMention: PropTypes.func.isRequired, |     onMention: PropTypes.func.isRequired, | ||||||
|     onReblogToggle: PropTypes.func.isRequired, |  | ||||||
|     onReport: PropTypes.func.isRequired, |     onReport: PropTypes.func.isRequired, | ||||||
|     onMute: PropTypes.func.isRequired, |     onMute: PropTypes.func.isRequired, | ||||||
|     onBlockDomain: PropTypes.func.isRequired, |     onBlockDomain: PropTypes.func.isRequired, | ||||||
|  | @ -40,10 +39,6 @@ export default class Header extends ImmutablePureComponent { | ||||||
|     this.props.onReport(this.props.account); |     this.props.onReport(this.props.account); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleReblogToggle = () => { |  | ||||||
|     this.props.onReblogToggle(this.props.account); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleMute = () => { |   handleMute = () => { | ||||||
|     this.props.onMute(this.props.account); |     this.props.onMute(this.props.account); | ||||||
|   } |   } | ||||||
|  | @ -82,7 +77,6 @@ export default class Header extends ImmutablePureComponent { | ||||||
|           account={account} |           account={account} | ||||||
|           onBlock={this.handleBlock} |           onBlock={this.handleBlock} | ||||||
|           onMention={this.handleMention} |           onMention={this.handleMention} | ||||||
|           onReblogToggle={this.handleReblogToggle} |  | ||||||
|           onReport={this.handleReport} |           onReport={this.handleReport} | ||||||
|           onMute={this.handleMute} |           onMute={this.handleMute} | ||||||
|           onBlockDomain={this.handleBlockDomain} |           onBlockDomain={this.handleBlockDomain} | ||||||
|  |  | ||||||
|  | @ -67,14 +67,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | ||||||
|     dispatch(mentionCompose(account, router)); |     dispatch(mentionCompose(account, router)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onReblogToggle (account) { |  | ||||||
|     if (account.getIn(['relationship', 'following', 'reblogs'])) { |  | ||||||
|       dispatch(followAccount(account.get('id'), false)); |  | ||||||
|     } else { |  | ||||||
|       dispatch(followAccount(account.get('id'), true)); |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onReport (account) { |   onReport (account) { | ||||||
|     dispatch(initReport(account)); |     dispatch(initReport(account)); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -59,7 +59,7 @@ export default class AccountTimeline extends ImmutablePureComponent { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column name='account'> |       <Column> | ||||||
|         <ColumnBackButton /> |         <ColumnBackButton /> | ||||||
| 
 | 
 | ||||||
|         <StatusList |         <StatusList | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ export default class Blocks extends ImmutablePureComponent { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column name='blocks' icon='ban' heading={intl.formatMessage(messages.heading)}> |       <Column icon='ban' heading={intl.formatMessage(messages.heading)}> | ||||||
|         <ColumnBackButtonSlim /> |         <ColumnBackButtonSlim /> | ||||||
|         <ScrollContainer scrollKey='blocks'> |         <ScrollContainer scrollKey='blocks'> | ||||||
|           <div className='scrollable' onScroll={this.handleScroll}> |           <div className='scrollable' onScroll={this.handleScroll}> | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ export default class CommunityTimeline extends React.PureComponent { | ||||||
|     const pinned = !!columnId; |     const pinned = !!columnId; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column ref={this.setRef} name='local'> |       <Column ref={this.setRef}> | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='users' |           icon='users' | ||||||
|           active={hasUnread} |           active={hasUnread} | ||||||
|  |  | ||||||
|  | @ -5,11 +5,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import ReplyIndicatorContainer from '../containers/reply_indicator_container'; | import ReplyIndicatorContainer from '../containers/reply_indicator_container'; | ||||||
| import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | import AutosuggestTextarea from '../../../components/autosuggest_textarea'; | ||||||
|  | import UploadButtonContainer from '../containers/upload_button_container'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import Collapsable from '../../../components/collapsable'; | import Collapsable from '../../../components/collapsable'; | ||||||
| import SpoilerButtonContainer from '../containers/spoiler_button_container'; | import SpoilerButtonContainer from '../containers/spoiler_button_container'; | ||||||
| import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; | import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; | ||||||
| import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container'; |  | ||||||
| import SensitiveButtonContainer from '../containers/sensitive_button_container'; | import SensitiveButtonContainer from '../containers/sensitive_button_container'; | ||||||
| import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; | import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; | ||||||
| import UploadFormContainer from '../containers/upload_form_container'; | import UploadFormContainer from '../containers/upload_form_container'; | ||||||
|  | @ -18,10 +18,6 @@ import { isMobile } from '../../../is_mobile'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { length } from 'stringz'; | import { length } from 'stringz'; | ||||||
| import { countableText } from '../util/counter'; | import { countableText } from '../util/counter'; | ||||||
| import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index'; |  | ||||||
| import initialState from '../../../initial_state'; |  | ||||||
| 
 |  | ||||||
| const maxChars = initialState.max_toot_chars; |  | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, |   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, | ||||||
|  | @ -40,9 +36,6 @@ export default class ComposeForm extends ImmutablePureComponent { | ||||||
|     suggestions: ImmutablePropTypes.list, |     suggestions: ImmutablePropTypes.list, | ||||||
|     spoiler: PropTypes.bool, |     spoiler: PropTypes.bool, | ||||||
|     privacy: PropTypes.string, |     privacy: PropTypes.string, | ||||||
|     advanced_options: ImmutablePropTypes.contains({ |  | ||||||
|       do_not_federate: PropTypes.bool, |  | ||||||
|     }), |  | ||||||
|     spoiler_text: PropTypes.string, |     spoiler_text: PropTypes.string, | ||||||
|     focusDate: PropTypes.instanceOf(Date), |     focusDate: PropTypes.instanceOf(Date), | ||||||
|     preselectDate: PropTypes.instanceOf(Date), |     preselectDate: PropTypes.instanceOf(Date), | ||||||
|  | @ -52,13 +45,11 @@ export default class ComposeForm extends ImmutablePureComponent { | ||||||
|     onSubmit: PropTypes.func.isRequired, |     onSubmit: PropTypes.func.isRequired, | ||||||
|     onClearSuggestions: PropTypes.func.isRequired, |     onClearSuggestions: PropTypes.func.isRequired, | ||||||
|     onFetchSuggestions: PropTypes.func.isRequired, |     onFetchSuggestions: PropTypes.func.isRequired, | ||||||
|     onPrivacyChange: PropTypes.func.isRequired, |  | ||||||
|     onSuggestionSelected: PropTypes.func.isRequired, |     onSuggestionSelected: PropTypes.func.isRequired, | ||||||
|     onChangeSpoilerText: PropTypes.func.isRequired, |     onChangeSpoilerText: PropTypes.func.isRequired, | ||||||
|     onPaste: PropTypes.func.isRequired, |     onPaste: PropTypes.func.isRequired, | ||||||
|     onPickEmoji: PropTypes.func.isRequired, |     onPickEmoji: PropTypes.func.isRequired, | ||||||
|     showSearch: PropTypes.bool, |     showSearch: PropTypes.bool, | ||||||
|     settings : ImmutablePropTypes.map.isRequired, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|  | @ -75,11 +66,6 @@ export default class ComposeForm extends ImmutablePureComponent { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleSubmit2 = () => { |  | ||||||
|     this.props.onPrivacyChange(this.props.settings.get('side_arm')); |  | ||||||
|     this.handleSubmit(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleSubmit = () => { |   handleSubmit = () => { | ||||||
|     if (this.props.text !== this.autosuggestTextarea.textarea.value) { |     if (this.props.text !== this.autosuggestTextarea.textarea.value) { | ||||||
|       // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
 |       // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
 | ||||||
|  | @ -158,58 +144,16 @@ export default class ComposeForm extends ImmutablePureComponent { | ||||||
|   render () { |   render () { | ||||||
|     const { intl, onPaste, showSearch } = this.props; |     const { intl, onPaste, showSearch } = this.props; | ||||||
|     const disabled = this.props.is_submitting; |     const disabled = this.props.is_submitting; | ||||||
|     const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : ''; |     const text     = [this.props.spoiler_text, countableText(this.props.text)].join(''); | ||||||
|     const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join(''); |  | ||||||
| 
 |  | ||||||
|     const secondaryVisibility = this.props.settings.get('side_arm'); |  | ||||||
|     let showSideArm = secondaryVisibility !== 'none'; |  | ||||||
| 
 | 
 | ||||||
|     let publishText = ''; |     let publishText = ''; | ||||||
|     let publishText2 = ''; |  | ||||||
|     let title = ''; |  | ||||||
|     let title2 = ''; |  | ||||||
| 
 | 
 | ||||||
|     const privacyIcons = { |     if (this.props.privacy === 'private' || this.props.privacy === 'direct') { | ||||||
|       none: '', |       publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; | ||||||
|       public: 'globe', |  | ||||||
|       unlisted: 'unlock-alt', |  | ||||||
|       private: 'lock', |  | ||||||
|       direct: 'envelope', |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     title = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${this.props.privacy}.short` })}`; |  | ||||||
| 
 |  | ||||||
|     if (showSideArm) { |  | ||||||
|       // Enhanced behavior with dual toot buttons
 |  | ||||||
|       publishText = ( |  | ||||||
|         <span> |  | ||||||
|           { |  | ||||||
|             <i |  | ||||||
|               className={`fa fa-${privacyIcons[this.props.privacy]}`} |  | ||||||
|               style={{ paddingRight: '5px' }} |  | ||||||
|             /> |  | ||||||
|           }{intl.formatMessage(messages.publish)} |  | ||||||
|         </span> |  | ||||||
|       ); |  | ||||||
| 
 |  | ||||||
|       title2 = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`; |  | ||||||
|       publishText2 = ( |  | ||||||
|         <i |  | ||||||
|           className={`fa fa-${privacyIcons[secondaryVisibility]}`} |  | ||||||
|           aria-label={title2} |  | ||||||
|         /> |  | ||||||
|       ); |  | ||||||
|     } else { |     } else { | ||||||
|       // Original vanilla behavior - no icon if public or unlisted
 |       publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); | ||||||
|       if (this.props.privacy === 'private' || this.props.privacy === 'direct') { |  | ||||||
|         publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; |  | ||||||
|       } else { |  | ||||||
|         publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const submitDisabled = disabled || this.props.is_uploading || length(text) > maxChars || (text.length !== 0 && text.trim().length === 0); |  | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|       <div className='compose-form'> |       <div className='compose-form'> | ||||||
|         <Collapsable isVisible={this.props.spoiler} fullHeight={50}> |         <Collapsable isVisible={this.props.spoiler} fullHeight={50}> | ||||||
|  | @ -248,35 +192,17 @@ export default class ComposeForm extends ImmutablePureComponent { | ||||||
|           <UploadFormContainer /> |           <UploadFormContainer /> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div className='compose-form__buttons'> |         <div className='compose-form__buttons-wrapper'> | ||||||
|           <ComposeAttachOptions /> |           <div className='compose-form__buttons'> | ||||||
|           <SensitiveButtonContainer /> |             <UploadButtonContainer /> | ||||||
|           <div className='compose-form__buttons-separator' /> |             <PrivacyDropdownContainer /> | ||||||
|           <PrivacyDropdownContainer /> |             <SensitiveButtonContainer /> | ||||||
|           <SpoilerButtonContainer /> |             <SpoilerButtonContainer /> | ||||||
|           <ComposeAdvancedOptionsContainer /> |           </div> | ||||||
|         </div> |  | ||||||
| 
 | 
 | ||||||
|         <div className='compose-form__publish'> |           <div className='compose-form__publish'> | ||||||
|           <div className='character-counter__wrapper'><CharacterCounter max={maxChars} text={text} /></div> |             <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> | ||||||
|           <div className='compose-form__publish-button-wrapper'> |             <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> | ||||||
|             { |  | ||||||
|               showSideArm ? |  | ||||||
|                 <Button |  | ||||||
|                   className='compose-form__publish__side-arm' |  | ||||||
|                   text={publishText2} |  | ||||||
|                   title={title2} |  | ||||||
|                   onClick={this.handleSubmit2} |  | ||||||
|                   disabled={submitDisabled} |  | ||||||
|                 /> : '' |  | ||||||
|             } |  | ||||||
|             <Button |  | ||||||
|               className='compose-form__publish__primary' |  | ||||||
|               text={publishText} |  | ||||||
|               title={title} |  | ||||||
|               onClick={this.handleSubmit} |  | ||||||
|               disabled={submitDisabled} |  | ||||||
|             /> |  | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||||
| import AccountContainer from '../../../containers/account_container'; | import AccountContainer from '../../../containers/account_container'; | ||||||
| import StatusContainer from '../../../../glitch/components/status/container'; | import StatusContainer from '../../../containers/status_container'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import ComposeForm from '../components/compose_form'; | import ComposeForm from '../components/compose_form'; | ||||||
| import { changeComposeVisibility, uploadCompose } from '../../../actions/compose'; | import { uploadCompose } from '../../../actions/compose'; | ||||||
| import { | import { | ||||||
|   changeCompose, |   changeCompose, | ||||||
|   submitCompose, |   submitCompose, | ||||||
|  | @ -15,7 +15,6 @@ const mapStateToProps = state => ({ | ||||||
|   text: state.getIn(['compose', 'text']), |   text: state.getIn(['compose', 'text']), | ||||||
|   suggestion_token: state.getIn(['compose', 'suggestion_token']), |   suggestion_token: state.getIn(['compose', 'suggestion_token']), | ||||||
|   suggestions: state.getIn(['compose', 'suggestions']), |   suggestions: state.getIn(['compose', 'suggestions']), | ||||||
|   advanced_options: state.getIn(['compose', 'advanced_options']), |  | ||||||
|   spoiler: state.getIn(['compose', 'spoiler']), |   spoiler: state.getIn(['compose', 'spoiler']), | ||||||
|   spoiler_text: state.getIn(['compose', 'spoiler_text']), |   spoiler_text: state.getIn(['compose', 'spoiler_text']), | ||||||
|   privacy: state.getIn(['compose', 'privacy']), |   privacy: state.getIn(['compose', 'privacy']), | ||||||
|  | @ -24,8 +23,6 @@ const mapStateToProps = state => ({ | ||||||
|   is_submitting: state.getIn(['compose', 'is_submitting']), |   is_submitting: state.getIn(['compose', 'is_submitting']), | ||||||
|   is_uploading: state.getIn(['compose', 'is_uploading']), |   is_uploading: state.getIn(['compose', 'is_uploading']), | ||||||
|   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), |   showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), | ||||||
|   settings: state.get('local_settings'), |  | ||||||
|   filesAttached: state.getIn(['compose', 'media_attachments']).size > 0, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapDispatchToProps = (dispatch) => ({ | const mapDispatchToProps = (dispatch) => ({ | ||||||
|  | @ -34,10 +31,6 @@ const mapDispatchToProps = (dispatch) => ({ | ||||||
|     dispatch(changeCompose(text)); |     dispatch(changeCompose(text)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onPrivacyChange (value) { |  | ||||||
|     dispatch(changeComposeVisibility(value)); |  | ||||||
|   }, |  | ||||||
| 
 |  | ||||||
|   onSubmit () { |   onSubmit () { | ||||||
|     dispatch(submitCompose()); |     dispatch(submitCompose()); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  | @ -5,8 +5,6 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { mountCompose, unmountCompose } from '../../actions/compose'; | import { mountCompose, unmountCompose } from '../../actions/compose'; | ||||||
| import { openModal } from '../../actions/modal'; |  | ||||||
| import { changeLocalSetting } from '../../../glitch/actions/local_settings'; |  | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import { injectIntl, defineMessages } from 'react-intl'; | import { injectIntl, defineMessages } from 'react-intl'; | ||||||
| import SearchContainer from './containers/search_container'; | import SearchContainer from './containers/search_container'; | ||||||
|  | @ -21,7 +19,7 @@ const messages = defineMessages({ | ||||||
|   notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, |   notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, | ||||||
|   public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, |   public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, | ||||||
|   community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, |   community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, | ||||||
|   settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, |   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | ||||||
|   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, |   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @ -50,16 +48,6 @@ export default class Compose extends React.PureComponent { | ||||||
|     this.props.dispatch(unmountCompose()); |     this.props.dispatch(unmountCompose()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onLayoutClick = (e) => { |  | ||||||
|     const layout = e.currentTarget.getAttribute('data-mastodon-layout'); |  | ||||||
|     this.props.dispatch(changeLocalSetting(['layout'], layout)); |  | ||||||
|     e.preventDefault(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   openSettings = () => { |  | ||||||
|     this.props.dispatch(openModal('SETTINGS', {})); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onFocus = () => { |   onFocus = () => { | ||||||
|     this.props.dispatch(changeComposing(true)); |     this.props.dispatch(changeComposing(true)); | ||||||
|   } |   } | ||||||
|  | @ -90,14 +78,12 @@ export default class Compose extends React.PureComponent { | ||||||
|           {!columns.some(column => column.get('id') === 'PUBLIC') && ( |           {!columns.some(column => column.get('id') === 'PUBLIC') && ( | ||||||
|             <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link> |             <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link> | ||||||
|           )} |           )} | ||||||
|           <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a> |           <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a> | ||||||
|           <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a> |           <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a> | ||||||
|         </nav> |         </nav> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|       <div className='drawer'> |       <div className='drawer'> | ||||||
|         {header} |         {header} | ||||||
|  | @ -105,7 +91,7 @@ export default class Compose extends React.PureComponent { | ||||||
|         <SearchContainer /> |         <SearchContainer /> | ||||||
| 
 | 
 | ||||||
|         <div className='drawer__pager'> |         <div className='drawer__pager'> | ||||||
|           <div className='drawer__inner scrollable optionally-scrollable' onFocus={this.onFocus}> |           <div className='drawer__inner' onFocus={this.onFocus}> | ||||||
|             <NavigationContainer onClose={this.onBlur} /> |             <NavigationContainer onClose={this.onBlur} /> | ||||||
|             <ComposeFormContainer /> |             <ComposeFormContainer /> | ||||||
|           </div> |           </div> | ||||||
|  | @ -118,7 +104,6 @@ export default class Compose extends React.PureComponent { | ||||||
|             } |             } | ||||||
|           </Motion> |           </Motion> | ||||||
|         </div> |         </div> | ||||||
| 
 |  | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -68,7 +68,7 @@ export default class Favourites extends ImmutablePureComponent { | ||||||
|     const pinned = !!columnId; |     const pinned = !!columnId; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column ref={this.setRef} name='favourites'> |       <Column ref={this.setRef}> | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='star' |           icon='star' | ||||||
|           title={intl.formatMessage(messages.heading)} |           title={intl.formatMessage(messages.heading)} | ||||||
|  |  | ||||||
|  | @ -47,14 +47,14 @@ export default class FollowRequests extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|     if (!accountIds) { |     if (!accountIds) { | ||||||
|       return ( |       return ( | ||||||
|         <Column name='follow-requests'> |         <Column> | ||||||
|           <LoadingIndicator /> |           <LoadingIndicator /> | ||||||
|         </Column> |         </Column> | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column name='follow-requests' icon='users' heading={intl.formatMessage(messages.heading)}> |       <Column icon='users' heading={intl.formatMessage(messages.heading)}> | ||||||
|         <ColumnBackButtonSlim /> |         <ColumnBackButtonSlim /> | ||||||
| 
 | 
 | ||||||
|         <ScrollContainer scrollKey='follow_requests'> |         <ScrollContainer scrollKey='follow_requests'> | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ import ColumnLink from '../ui/components/column_link'; | ||||||
| import ColumnSubheading from '../ui/components/column_subheading'; | import ColumnSubheading from '../ui/components/column_subheading'; | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { openModal } from '../../actions/modal'; |  | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  | @ -18,16 +17,13 @@ const messages = defineMessages({ | ||||||
|   navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, |   navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, | ||||||
|   settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, |   settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, | ||||||
|   community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, |   community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, | ||||||
|   direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, |  | ||||||
|   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, |   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, | ||||||
|   settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, |  | ||||||
|   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, |   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, | ||||||
|   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, |   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, | ||||||
|   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, |   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, | ||||||
|   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, |   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, | ||||||
|   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, |   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' }, | ||||||
|   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, |   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }, | ||||||
|   show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' }, |  | ||||||
|   pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, |   pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @ -45,18 +41,8 @@ export default class GettingStarted extends ImmutablePureComponent { | ||||||
|     myAccount: ImmutablePropTypes.map.isRequired, |     myAccount: ImmutablePropTypes.map.isRequired, | ||||||
|     columns: ImmutablePropTypes.list, |     columns: ImmutablePropTypes.list, | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|     dispatch: PropTypes.func.isRequired, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   openSettings = () => { |  | ||||||
|     this.props.dispatch(openModal('SETTINGS', {})); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   openOnboardingModal = (e) => { |  | ||||||
|     e.preventDefault(); |  | ||||||
|     this.props.dispatch(openModal('ONBOARDING')); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   render () { |   render () { | ||||||
|     const { intl, myAccount, columns, multiColumn } = this.props; |     const { intl, myAccount, columns, multiColumn } = this.props; | ||||||
| 
 | 
 | ||||||
|  | @ -80,62 +66,43 @@ export default class GettingStarted extends ImmutablePureComponent { | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { |  | ||||||
|       navItems.push(<ColumnLink key='4' icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     navItems = navItems.concat([ |     navItems = navItems.concat([ | ||||||
|       <ColumnLink key='5' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, |       <ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />, | ||||||
|       <ColumnLink key='6' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />, |       <ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />, | ||||||
|     ]); |     ]); | ||||||
| 
 | 
 | ||||||
|     if (myAccount.get('locked')) { |     if (myAccount.get('locked')) { | ||||||
|       navItems.push(<ColumnLink key='7' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); |       navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     navItems = navItems.concat([ |     navItems = navItems.concat([ | ||||||
|       <ColumnLink key='8' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />, |       <ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />, | ||||||
|       <ColumnLink key='9' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />, |       <ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />, | ||||||
|     ]); |     ]); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile> |       <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile> | ||||||
|         <div className='scrollable optionally-scrollable'> |         <div className='getting-started__wrapper'> | ||||||
|           <div className='getting-started__wrapper'> |           <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} /> | ||||||
|             <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} /> |           {navItems} | ||||||
|             {navItems} |           <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> | ||||||
|             <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} /> |           <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> | ||||||
|             <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' /> |           <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' /> | ||||||
|             <ColumnLink icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} /> |           <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> | ||||||
|             <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' /> |         </div> | ||||||
|             <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={this.openSettings} /> |  | ||||||
|             <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> |  | ||||||
|           </div> |  | ||||||
| 
 | 
 | ||||||
|           <div className='getting-started__footer'> |         <div className='getting-started__footer scrollable optionally-scrollable'> | ||||||
|             <div className='static-content getting-started'> |           <div className='static-content getting-started'> | ||||||
|               <p> |             <p> | ||||||
|                 <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'> |               <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
 | ||||||
|                   <FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /> |             </p> | ||||||
|                 </a> •  |             <p> | ||||||
|                 <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'> |               <FormattedMessage | ||||||
|                   <FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /> |                 id='getting_started.open_source_notice' | ||||||
|                 </a> •  |                 defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.' | ||||||
|                 <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'> |                 values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }} | ||||||
|                   <FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /> |               /> | ||||||
|                 </a> |             </p> | ||||||
|               </p> |  | ||||||
|               <p> |  | ||||||
|                 <FormattedMessage |  | ||||||
|                   id='getting_started.open_source_notice' |  | ||||||
|                   defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.' |  | ||||||
|                   values={{ |  | ||||||
|                     github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>, |  | ||||||
|                     Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a>, |  | ||||||
|                   }} |  | ||||||
|                 /> |  | ||||||
|               </p> |  | ||||||
|             </div> |  | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </Column> |       </Column> | ||||||
|  |  | ||||||
|  | @ -91,7 +91,7 @@ export default class HashtagTimeline extends React.PureComponent { | ||||||
|     const pinned = !!columnId; |     const pinned = !!columnId; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column ref={this.setRef} name='hashtag'> |       <Column ref={this.setRef}> | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='hashtag' |           icon='hashtag' | ||||||
|           active={hasUnread} |           active={hasUnread} | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ export default class HomeTimeline extends React.PureComponent { | ||||||
|     const pinned = !!columnId; |     const pinned = !!columnId; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column ref={this.setRef} name='home'> |       <Column ref={this.setRef}> | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='home' |           icon='home' | ||||||
|           active={hasUnread} |           active={hasUnread} | ||||||
|  |  | ||||||
|  | @ -54,7 +54,7 @@ export default class Mutes extends ImmutablePureComponent { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column name='mutes' icon='volume-off' heading={intl.formatMessage(messages.heading)}> |       <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}> | ||||||
|         <ColumnBackButtonSlim /> |         <ColumnBackButtonSlim /> | ||||||
|         <ScrollContainer scrollKey='mutes'> |         <ScrollContainer scrollKey='mutes'> | ||||||
|           <div className='scrollable mutes' onScroll={this.handleScroll}> |           <div className='scrollable mutes' onScroll={this.handleScroll}> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/notification
 |  | ||||||
| 
 |  | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
|  |  | ||||||
|  | @ -1,6 +1,3 @@ | ||||||
| //  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
 |  | ||||||
| //  SEE INSTEAD : glitch/components/notification/container
 |  | ||||||
| 
 |  | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { makeGetNotification } from '../../../selectors'; | import { makeGetNotification } from '../../../selectors'; | ||||||
| import Notification from '../components/notification'; | import Notification from '../components/notification'; | ||||||
|  |  | ||||||
|  | @ -4,13 +4,9 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Column from '../../components/column'; | import Column from '../../components/column'; | ||||||
| import ColumnHeader from '../../components/column_header'; | import ColumnHeader from '../../components/column_header'; | ||||||
| import { | import { expandNotifications, scrollTopNotifications } from '../../actions/notifications'; | ||||||
|   enterNotificationClearingMode, |  | ||||||
|   expandNotifications, |  | ||||||
|   scrollTopNotifications, |  | ||||||
| } from '../../actions/notifications'; |  | ||||||
| import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | ||||||
| import NotificationContainer from '../../../glitch/components/notification/container'; | import NotificationContainer from './containers/notification_container'; | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||||
| import ColumnSettingsContainer from './containers/column_settings_container'; | import ColumnSettingsContainer from './containers/column_settings_container'; | ||||||
| import { createSelector } from 'reselect'; | import { createSelector } from 'reselect'; | ||||||
|  | @ -29,22 +25,12 @@ const getNotifications = createSelector([ | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   notifications: getNotifications(state), |   notifications: getNotifications(state), | ||||||
|   localSettings:  state.get('local_settings'), |  | ||||||
|   isLoading: state.getIn(['notifications', 'isLoading'], true), |   isLoading: state.getIn(['notifications', 'isLoading'], true), | ||||||
|   isUnread: state.getIn(['notifications', 'unread']) > 0, |   isUnread: state.getIn(['notifications', 'unread']) > 0, | ||||||
|   hasMore: !!state.getIn(['notifications', 'next']), |   hasMore: !!state.getIn(['notifications', 'next']), | ||||||
|   notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| /* glitch */ | @connect(mapStateToProps) | ||||||
| const mapDispatchToProps = dispatch => ({ |  | ||||||
|   onEnterCleaningMode(yes) { |  | ||||||
|     dispatch(enterNotificationClearingMode(yes)); |  | ||||||
|   }, |  | ||||||
|   dispatch, |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| @connect(mapStateToProps, mapDispatchToProps) |  | ||||||
| @injectIntl | @injectIntl | ||||||
| export default class Notifications extends React.PureComponent { | export default class Notifications extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|  | @ -58,9 +44,6 @@ export default class Notifications extends React.PureComponent { | ||||||
|     isUnread: PropTypes.bool, |     isUnread: PropTypes.bool, | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|     hasMore: PropTypes.bool, |     hasMore: PropTypes.bool, | ||||||
|     localSettings: ImmutablePropTypes.map, |  | ||||||
|     notifCleaningActive: PropTypes.bool, |  | ||||||
|     onEnterCleaningMode: PropTypes.func, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|  | @ -163,11 +146,7 @@ export default class Notifications extends React.PureComponent { | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column |       <Column ref={this.setColumnRef}> | ||||||
|         ref={this.setColumnRef} |  | ||||||
|         name='notifications' |  | ||||||
|         extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null} |  | ||||||
|       > |  | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='bell' |           icon='bell' | ||||||
|           active={isUnread} |           active={isUnread} | ||||||
|  | @ -177,10 +156,6 @@ export default class Notifications extends React.PureComponent { | ||||||
|           onClick={this.handleHeaderClick} |           onClick={this.handleHeaderClick} | ||||||
|           pinned={pinned} |           pinned={pinned} | ||||||
|           multiColumn={multiColumn} |           multiColumn={multiColumn} | ||||||
|           localSettings={this.props.localSettings} |  | ||||||
|           notifCleaning |  | ||||||
|           notifCleaningActive={this.props.notifCleaningActive} // this is used to toggle the header text
 |  | ||||||
|           onEnterCleaningMode={this.props.onEnterCleaningMode} |  | ||||||
|         > |         > | ||||||
|           <ColumnSettingsContainer /> |           <ColumnSettingsContainer /> | ||||||
|         </ColumnHeader> |         </ColumnHeader> | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ export default class PublicTimeline extends React.PureComponent { | ||||||
|     const pinned = !!columnId; |     const pinned = !!columnId; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <Column ref={this.setRef} name='federated'> |       <Column ref={this.setRef}> | ||||||
|         <ColumnHeader |         <ColumnHeader | ||||||
|           icon='globe' |           icon='globe' | ||||||
|           active={hasUnread} |           active={hasUnread} | ||||||
|  |  | ||||||
|  | @ -107,8 +107,8 @@ export default class ActionBar extends React.PureComponent { | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     let reblogIcon = 'retweet'; |     let reblogIcon = 'retweet'; | ||||||
|     //if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
 |     if (status.get('visibility') === 'direct') reblogIcon = 'envelope'; | ||||||
|     // else if (status.get('visibility') === 'private') reblogIcon = 'lock';
 |     else if (status.get('visibility') === 'private') reblogIcon = 'lock'; | ||||||
| 
 | 
 | ||||||
|     let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private'); |     let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,16 +3,14 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Avatar from '../../../components/avatar'; | import Avatar from '../../../components/avatar'; | ||||||
| import DisplayName from '../../../components/display_name'; | import DisplayName from '../../../components/display_name'; | ||||||
| import StatusContent from '../../../../glitch/components/status/content'; | import StatusContent from '../../../components/status_content'; | ||||||
| import StatusGallery from '../../../../glitch/components/status/gallery'; | import MediaGallery from '../../../components/media_gallery'; | ||||||
| import StatusPlayer from '../../../../glitch/components/status/player'; |  | ||||||
| import AttachmentList from '../../../components/attachment_list'; | import AttachmentList from '../../../components/attachment_list'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import { FormattedDate, FormattedNumber } from 'react-intl'; | import { FormattedDate, FormattedNumber } from 'react-intl'; | ||||||
| import CardContainer from '../containers/card_container'; | import CardContainer from '../containers/card_container'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| // import Video from '../../video';
 | import Video from '../../video'; | ||||||
| import VisibilityIcon from '../../../../glitch/components/status/visibility_icon'; |  | ||||||
| 
 | 
 | ||||||
| export default class DetailedStatus extends ImmutablePureComponent { | export default class DetailedStatus extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|  | @ -22,7 +20,6 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     status: ImmutablePropTypes.map.isRequired, |     status: ImmutablePropTypes.map.isRequired, | ||||||
|     settings: ImmutablePropTypes.map.isRequired, |  | ||||||
|     onOpenMedia: PropTypes.func.isRequired, |     onOpenMedia: PropTypes.func.isRequired, | ||||||
|     onOpenVideo: PropTypes.func.isRequired, |     onOpenVideo: PropTypes.func.isRequired, | ||||||
|   }; |   }; | ||||||
|  | @ -36,16 +33,14 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
|     e.stopPropagation(); |     e.stopPropagation(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // handleOpenVideo = startTime => {
 |   handleOpenVideo = startTime => { | ||||||
|   //   this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime);
 |     this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), startTime); | ||||||
|   // }
 |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; |     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; | ||||||
|     const { settings } = this.props; |  | ||||||
| 
 | 
 | ||||||
|     let media           = ''; |     let media           = ''; | ||||||
|     let mediaIcon       = null; |  | ||||||
|     let applicationLink = ''; |     let applicationLink = ''; | ||||||
|     let reblogLink = ''; |     let reblogLink = ''; | ||||||
|     let reblogIcon = 'retweet'; |     let reblogIcon = 'retweet'; | ||||||
|  | @ -54,32 +49,32 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
|       if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { |       if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { | ||||||
|         media = <AttachmentList media={status.get('media_attachments')} />; |         media = <AttachmentList media={status.get('media_attachments')} />; | ||||||
|       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { |       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { | ||||||
|  |         const video = status.getIn(['media_attachments', 0]); | ||||||
|  | 
 | ||||||
|         media = ( |         media = ( | ||||||
|           <StatusPlayer |           <Video | ||||||
|  |             preview={video.get('preview_url')} | ||||||
|  |             src={video.get('url')} | ||||||
|  |             width={300} | ||||||
|  |             height={150} | ||||||
|  |             onOpenVideo={this.handleOpenVideo} | ||||||
|             sensitive={status.get('sensitive')} |             sensitive={status.get('sensitive')} | ||||||
|             media={status.getIn(['media_attachments', 0])} |  | ||||||
|             letterbox={settings.getIn(['media', 'letterbox'])} |  | ||||||
|             fullwidth={settings.getIn(['media', 'fullwidth'])} |  | ||||||
|             height={250} |  | ||||||
|             onOpenVideo={this.props.onOpenVideo} |  | ||||||
|             autoplay |  | ||||||
|           /> |           /> | ||||||
|         ); |         ); | ||||||
|         mediaIcon = 'video-camera'; |  | ||||||
|       } else { |       } else { | ||||||
|         media = ( |         media = ( | ||||||
|           <StatusGallery |           <MediaGallery | ||||||
|  |             standalone | ||||||
|             sensitive={status.get('sensitive')} |             sensitive={status.get('sensitive')} | ||||||
|             media={status.get('media_attachments')} |             media={status.get('media_attachments')} | ||||||
|             letterbox={settings.getIn(['media', 'letterbox'])} |             height={300} | ||||||
|             fullwidth={settings.getIn(['media', 'fullwidth'])} |  | ||||||
|             height={250} |  | ||||||
|             onOpenMedia={this.props.onOpenMedia} |             onOpenMedia={this.props.onOpenMedia} | ||||||
|           /> |           /> | ||||||
|         ); |         ); | ||||||
|         mediaIcon = 'picture-o'; |  | ||||||
|       } |       } | ||||||
|     } else media = <CardContainer statusId={status.get('id')} />; |     } else if (status.get('spoiler_text').length === 0) { | ||||||
|  |       media = <CardContainer statusId={status.get('id')} />; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (status.get('application')) { |     if (status.get('application')) { | ||||||
|       applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>; |       applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>; | ||||||
|  | @ -109,11 +104,9 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
|           <DisplayName account={status.get('account')} /> |           <DisplayName account={status.get('account')} /> | ||||||
|         </a> |         </a> | ||||||
| 
 | 
 | ||||||
|         <StatusContent |         <StatusContent status={status} /> | ||||||
|           status={status} | 
 | ||||||
|           media={media} |         {media} | ||||||
|           mediaIcon={mediaIcon} |  | ||||||
|         /> |  | ||||||
| 
 | 
 | ||||||
|         <div className='detailed-status__meta'> |         <div className='detailed-status__meta'> | ||||||
|           <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'> |           <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'> | ||||||
|  | @ -123,7 +116,7 @@ export default class DetailedStatus extends ImmutablePureComponent { | ||||||
|             <span className='detailed-status__favorites'> |             <span className='detailed-status__favorites'> | ||||||
|               <FormattedNumber value={status.get('favourites_count')} /> |               <FormattedNumber value={status.get('favourites_count')} /> | ||||||
|             </span> |             </span> | ||||||
|           </Link> · <VisibilityIcon visibility={status.get('visibility')} /> |           </Link> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ import { initReport } from '../../actions/reports'; | ||||||
| import { makeGetStatus } from '../../selectors'; | import { makeGetStatus } from '../../selectors'; | ||||||
| import { ScrollContainer } from 'react-router-scroll-4'; | import { ScrollContainer } from 'react-router-scroll-4'; | ||||||
| import ColumnBackButton from '../../components/column_back_button'; | import ColumnBackButton from '../../components/column_back_button'; | ||||||
| import StatusContainer from '../../../glitch/components/status/container'; | import StatusContainer from '../../containers/status_container'; | ||||||
| import { openModal } from '../../actions/modal'; | import { openModal } from '../../actions/modal'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  | @ -43,7 +43,6 @@ const makeMapStateToProps = () => { | ||||||
| 
 | 
 | ||||||
|   const mapStateToProps = (state, props) => ({ |   const mapStateToProps = (state, props) => ({ | ||||||
|     status: getStatus(state, props.params.statusId), |     status: getStatus(state, props.params.statusId), | ||||||
|     settings: state.get('local_settings'), |  | ||||||
|     ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), |     ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]), | ||||||
|     descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), |     descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]), | ||||||
|   }); |   }); | ||||||
|  | @ -63,7 +62,6 @@ export default class Status extends ImmutablePureComponent { | ||||||
|     params: PropTypes.object.isRequired, |     params: PropTypes.object.isRequired, | ||||||
|     dispatch: PropTypes.func.isRequired, |     dispatch: PropTypes.func.isRequired, | ||||||
|     status: ImmutablePropTypes.map, |     status: ImmutablePropTypes.map, | ||||||
|     settings: ImmutablePropTypes.map.isRequired, |  | ||||||
|     ancestorsIds: ImmutablePropTypes.list, |     ancestorsIds: ImmutablePropTypes.list, | ||||||
|     descendantsIds: ImmutablePropTypes.list, |     descendantsIds: ImmutablePropTypes.list, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|  | @ -255,10 +253,8 @@ export default class Status extends ImmutablePureComponent { | ||||||
|     if (status && ancestorsIds && ancestorsIds.size > 0) { |     if (status && ancestorsIds && ancestorsIds.size > 0) { | ||||||
|       const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1]; |       const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1]; | ||||||
| 
 | 
 | ||||||
|       if (element) { |       element.scrollIntoView(true); | ||||||
|         element.scrollIntoView(true); |       this._scrolledIntoView = true; | ||||||
|         this._scrolledIntoView = true; |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -272,7 +268,7 @@ export default class Status extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     let ancestors, descendants; |     let ancestors, descendants; | ||||||
|     const { status, settings, ancestorsIds, descendantsIds } = this.props; |     const { status, ancestorsIds, descendantsIds } = this.props; | ||||||
|     const { fullscreen } = this.state; |     const { fullscreen } = this.state; | ||||||
| 
 | 
 | ||||||
|     if (status === null) { |     if (status === null) { | ||||||
|  | @ -314,7 +310,6 @@ export default class Status extends ImmutablePureComponent { | ||||||
|               <div className='focusable' tabIndex='0'> |               <div className='focusable' tabIndex='0'> | ||||||
|                 <DetailedStatus |                 <DetailedStatus | ||||||
|                   status={status} |                   status={status} | ||||||
|                   settings={settings} |  | ||||||
|                   onOpenVideo={this.handleOpenVideo} |                   onOpenVideo={this.handleOpenVideo} | ||||||
|                   onOpenMedia={this.handleOpenMedia} |                   onOpenMedia={this.handleOpenMedia} | ||||||
|                 /> |                 /> | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||||
| import Button from '../../../components/button'; | import Button from '../../../components/button'; | ||||||
| import StatusContent from '../../../../glitch/components/status/content'; | import StatusContent from '../../../components/status_content'; | ||||||
| import Avatar from '../../../components/avatar'; | import Avatar from '../../../components/avatar'; | ||||||
| import RelativeTimestamp from '../../../components/relative_timestamp'; | import RelativeTimestamp from '../../../components/relative_timestamp'; | ||||||
| import DisplayName from '../../../components/display_name'; | import DisplayName from '../../../components/display_name'; | ||||||
|  |  | ||||||
|  | @ -13,7 +13,6 @@ export default class Column extends React.PureComponent { | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     active: PropTypes.bool, |     active: PropTypes.bool, | ||||||
|     hideHeadingOnMobile: PropTypes.bool, |     hideHeadingOnMobile: PropTypes.bool, | ||||||
|     name: PropTypes.string, |  | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleHeaderClick = () => { |   handleHeaderClick = () => { | ||||||
|  | @ -48,7 +47,7 @@ export default class Column extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { heading, icon, children, active, hideHeadingOnMobile, name } = this.props; |     const { heading, icon, children, active, hideHeadingOnMobile } = this.props; | ||||||
| 
 | 
 | ||||||
|     const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); |     const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); | ||||||
| 
 | 
 | ||||||
|  | @ -60,7 +59,6 @@ export default class Column extends React.PureComponent { | ||||||
|       <div |       <div | ||||||
|         ref={this.setRef} |         ref={this.setRef} | ||||||
|         role='region' |         role='region' | ||||||
|         data-column={name} |  | ||||||
|         aria-labelledby={columnHeaderId} |         aria-labelledby={columnHeaderId} | ||||||
|         className='column' |         className='column' | ||||||
|         onScroll={this.handleScroll} |         onScroll={this.handleScroll} | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import React from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| const ColumnLink = ({ icon, text, to, onClick, href, method }) => { | const ColumnLink = ({ icon, text, to, href, method }) => { | ||||||
|   if (href) { |   if (href) { | ||||||
|     return ( |     return ( | ||||||
|       <a href={href} className='column-link' data-method={method}> |       <a href={href} className='column-link' data-method={method}> | ||||||
|  | @ -10,20 +10,13 @@ const ColumnLink = ({ icon, text, to, onClick, href, method }) => { | ||||||
|         {text} |         {text} | ||||||
|       </a> |       </a> | ||||||
|     ); |     ); | ||||||
|   } else if (to) { |   } else { | ||||||
|     return ( |     return ( | ||||||
|       <Link to={to} className='column-link'> |       <Link to={to} className='column-link'> | ||||||
|         <i className={`fa fa-fw fa-${icon} column-link__icon`} /> |         <i className={`fa fa-fw fa-${icon} column-link__icon`} /> | ||||||
|         {text} |         {text} | ||||||
|       </Link> |       </Link> | ||||||
|     ); |     ); | ||||||
|   } else { |  | ||||||
|     return ( |  | ||||||
|       <a onClick={onClick} className='column-link' role='button' tabIndex='0' data-method={method}> |  | ||||||
|         <i className={`fa fa-fw fa-${icon} column-link__icon`} /> |  | ||||||
|         {text} |  | ||||||
|       </a> |  | ||||||
|     ); |  | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -31,9 +24,9 @@ ColumnLink.propTypes = { | ||||||
|   icon: PropTypes.string.isRequired, |   icon: PropTypes.string.isRequired, | ||||||
|   text: PropTypes.string.isRequired, |   text: PropTypes.string.isRequired, | ||||||
|   to: PropTypes.string, |   to: PropTypes.string, | ||||||
|   onClick: PropTypes.func, |  | ||||||
|   href: PropTypes.string, |   href: PropTypes.string, | ||||||
|   method: PropTypes.string, |   method: PropTypes.string, | ||||||
|  |   hideOnMobile: PropTypes.bool, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default ColumnLink; | export default ColumnLink; | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ import BundleContainer from '../containers/bundle_container'; | ||||||
| import ColumnLoading from './column_loading'; | import ColumnLoading from './column_loading'; | ||||||
| import DrawerLoading from './drawer_loading'; | import DrawerLoading from './drawer_loading'; | ||||||
| import BundleColumnError from './bundle_column_error'; | import BundleColumnError from './bundle_column_error'; | ||||||
| import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, DirectTimeline, FavouritedStatuses } from '../../ui/util/async-components'; | import { Compose, Notifications, HomeTimeline, CommunityTimeline, PublicTimeline, HashtagTimeline, FavouritedStatuses } from '../../ui/util/async-components'; | ||||||
| 
 | 
 | ||||||
| import detectPassiveEvents from 'detect-passive-events'; | import detectPassiveEvents from 'detect-passive-events'; | ||||||
| import { scrollRight } from '../../../scroll'; | import { scrollRight } from '../../../scroll'; | ||||||
|  | @ -23,7 +23,6 @@ const componentMap = { | ||||||
|   'PUBLIC': PublicTimeline, |   'PUBLIC': PublicTimeline, | ||||||
|   'COMMUNITY': CommunityTimeline, |   'COMMUNITY': CommunityTimeline, | ||||||
|   'HASHTAG': HashtagTimeline, |   'HASHTAG': HashtagTimeline, | ||||||
|   'DIRECT': DirectTimeline, |  | ||||||
|   'FAVOURITES': FavouritedStatuses, |   'FAVOURITES': FavouritedStatuses, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,13 +7,11 @@ import ActionsModal from './actions_modal'; | ||||||
| import MediaModal from './media_modal'; | import MediaModal from './media_modal'; | ||||||
| import VideoModal from './video_modal'; | import VideoModal from './video_modal'; | ||||||
| import BoostModal from './boost_modal'; | import BoostModal from './boost_modal'; | ||||||
| import DoodleModal from './doodle_modal'; |  | ||||||
| import ConfirmationModal from './confirmation_modal'; | import ConfirmationModal from './confirmation_modal'; | ||||||
| import { | import { | ||||||
|   OnboardingModal, |   OnboardingModal, | ||||||
|   MuteModal, |   MuteModal, | ||||||
|   ReportModal, |   ReportModal, | ||||||
|   SettingsModal, |  | ||||||
|   EmbedModal, |   EmbedModal, | ||||||
| } from '../../../features/ui/util/async-components'; | } from '../../../features/ui/util/async-components'; | ||||||
| 
 | 
 | ||||||
|  | @ -22,11 +20,9 @@ const MODAL_COMPONENTS = { | ||||||
|   'ONBOARDING': OnboardingModal, |   'ONBOARDING': OnboardingModal, | ||||||
|   'VIDEO': () => Promise.resolve({ default: VideoModal }), |   'VIDEO': () => Promise.resolve({ default: VideoModal }), | ||||||
|   'BOOST': () => Promise.resolve({ default: BoostModal }), |   'BOOST': () => Promise.resolve({ default: BoostModal }), | ||||||
|   'DOODLE': () => Promise.resolve({ default: DoodleModal }), |  | ||||||
|   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), |   'CONFIRM': () => Promise.resolve({ default: ConfirmationModal }), | ||||||
|   'MUTE': MuteModal, |   'MUTE': MuteModal, | ||||||
|   'REPORT': ReportModal, |   'REPORT': ReportModal, | ||||||
|   'SETTINGS': SettingsModal, |  | ||||||
|   'ACTIONS': () => Promise.resolve({ default: ActionsModal }), |   'ACTIONS': () => Promise.resolve({ default: ActionsModal }), | ||||||
|   'EMBED': EmbedModal, |   'EMBED': EmbedModal, | ||||||
| }; | }; | ||||||
|  | @ -45,7 +41,7 @@ export default class ModalRoot extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   handleKeyUp = (e) => { |   handleKeyUp = (e) => { | ||||||
|     if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) |     if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27) | ||||||
|          && !!this.props.type && !this.props.props.noEsc) { |          && !!this.props.type) { | ||||||
|       this.props.onClose(); |       this.props.onClose(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | @ -90,7 +86,7 @@ export default class ModalRoot extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderLoading = modalId => () => { |   renderLoading = modalId => () => { | ||||||
|     return ['MEDIA', 'VIDEO', 'BOOST', 'DOODLE', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null; |     return ['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].indexOf(modalId) === -1 ? <ModalLoading /> : null; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderError = (props) => { |   renderError = (props) => { | ||||||
|  |  | ||||||
|  | @ -10,10 +10,7 @@ import ComposeForm from '../../compose/components/compose_form'; | ||||||
| import Search from '../../compose/components/search'; | import Search from '../../compose/components/search'; | ||||||
| import NavigationBar from '../../compose/components/navigation_bar'; | import NavigationBar from '../../compose/components/navigation_bar'; | ||||||
| import ColumnHeader from './column_header'; | import ColumnHeader from './column_header'; | ||||||
| import { | import { List as ImmutableList } from 'immutable'; | ||||||
|   List as ImmutableList, |  | ||||||
|   Map as ImmutableMap, |  | ||||||
| } from 'immutable'; |  | ||||||
| import { me } from '../../../initial_state'; | import { me } from '../../../initial_state'; | ||||||
| 
 | 
 | ||||||
| const noop = () => { }; | const noop = () => { }; | ||||||
|  | @ -32,8 +29,8 @@ const PageOne = ({ acct, domain }) => ( | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|     <div> |     <div> | ||||||
|       <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to {domain}!' values={{ domain }} /></h1> |       <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1> | ||||||
|       <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='{domain} is an "instance" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' values={{ domain }} /></p> |       <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p> | ||||||
|       <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p> |       <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p> | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  | @ -48,7 +45,7 @@ const PageTwo = ({ myAccount }) => ( | ||||||
|   <div className='onboarding-modal__page onboarding-modal__page-two'> |   <div className='onboarding-modal__page onboarding-modal__page-two'> | ||||||
|     <div className='figure non-interactive'> |     <div className='figure non-interactive'> | ||||||
|       <div className='pseudo-drawer'> |       <div className='pseudo-drawer'> | ||||||
|         <NavigationBar onClose={noop} account={myAccount} /> |         <NavigationBar account={myAccount} /> | ||||||
|       </div> |       </div> | ||||||
|       <ComposeForm |       <ComposeForm | ||||||
|         text='Awoo! #introductions' |         text='Awoo! #introductions' | ||||||
|  | @ -63,9 +60,7 @@ const PageTwo = ({ myAccount }) => ( | ||||||
|         onClearSuggestions={noop} |         onClearSuggestions={noop} | ||||||
|         onFetchSuggestions={noop} |         onFetchSuggestions={noop} | ||||||
|         onSuggestionSelected={noop} |         onSuggestionSelected={noop} | ||||||
|         onPrivacyChange={noop} |  | ||||||
|         showSearch |         showSearch | ||||||
|         settings={ImmutableMap.of('side_arm', 'none')} |  | ||||||
|       /> |       /> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|  | @ -89,7 +84,7 @@ const PageThree = ({ myAccount }) => ( | ||||||
|       /> |       /> | ||||||
| 
 | 
 | ||||||
|       <div className='pseudo-drawer'> |       <div className='pseudo-drawer'> | ||||||
|         <NavigationBar onClose={noop} account={myAccount} /> |         <NavigationBar account={myAccount} /> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
| 
 | 
 | ||||||
|  | @ -154,8 +149,8 @@ const PageSix = ({ admin, domain }) => { | ||||||
|     <div className='onboarding-modal__page onboarding-modal__page-six'> |     <div className='onboarding-modal__page onboarding-modal__page-six'> | ||||||
|       <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1> |       <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1> | ||||||
|       {adminSection} |       {adminSection} | ||||||
|       <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ domain, fork: <a href='https://en.wikipedia.org/wiki/Fork_(software_development)' target='_blank' rel='noopener'>fork</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>Mastodon</a>, github: <a href='https://github.com/glitch-soc/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
 |       <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p> | ||||||
|       <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ domain, apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p> |       <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p> | ||||||
|       <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p> |       <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  | @ -15,7 +15,6 @@ import { clearHeight } from '../../actions/height_cache'; | ||||||
| import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; | import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; | ||||||
| import UploadArea from './components/upload_area'; | import UploadArea from './components/upload_area'; | ||||||
| import ColumnsAreaContainer from './containers/columns_area_container'; | import ColumnsAreaContainer from './containers/columns_area_container'; | ||||||
| import classNames from 'classnames'; |  | ||||||
| import { | import { | ||||||
|   Compose, |   Compose, | ||||||
|   Status, |   Status, | ||||||
|  | @ -29,7 +28,6 @@ import { | ||||||
|   Following, |   Following, | ||||||
|   Reblogs, |   Reblogs, | ||||||
|   Favourites, |   Favourites, | ||||||
|   DirectTimeline, |  | ||||||
|   HashtagTimeline, |   HashtagTimeline, | ||||||
|   Notifications, |   Notifications, | ||||||
|   FollowRequests, |   FollowRequests, | ||||||
|  | @ -45,7 +43,7 @@ import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | // Dummy import, to make sure that <Status /> ends up in the application bundle.
 | ||||||
| // Without this it ends up in ~8 very commonly used bundles.
 | // Without this it ends up in ~8 very commonly used bundles.
 | ||||||
| import '../../../glitch/components/status'; | import '../../components/status'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, |   beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, | ||||||
|  | @ -54,9 +52,6 @@ const messages = defineMessages({ | ||||||
| const mapStateToProps = state => ({ | const mapStateToProps = state => ({ | ||||||
|   isComposing: state.getIn(['compose', 'is_composing']), |   isComposing: state.getIn(['compose', 'is_composing']), | ||||||
|   hasComposingText: state.getIn(['compose', 'text']) !== '', |   hasComposingText: state.getIn(['compose', 'text']) !== '', | ||||||
|   layout: state.getIn(['local_settings', 'layout']), |  | ||||||
|   isWide: state.getIn(['local_settings', 'stretch']), |  | ||||||
|   navbarUnder: state.getIn(['local_settings', 'navbar_under']), |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const keyMap = { | const keyMap = { | ||||||
|  | @ -77,7 +72,6 @@ const keyMap = { | ||||||
|   goToNotifications: 'g n', |   goToNotifications: 'g n', | ||||||
|   goToLocal: 'g l', |   goToLocal: 'g l', | ||||||
|   goToFederated: 'g t', |   goToFederated: 'g t', | ||||||
|   goToDirect: 'g d', |  | ||||||
|   goToStart: 'g s', |   goToStart: 'g s', | ||||||
|   goToFavourites: 'g f', |   goToFavourites: 'g f', | ||||||
|   goToPinned: 'g p', |   goToPinned: 'g p', | ||||||
|  | @ -98,10 +92,6 @@ export default class UI extends React.Component { | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     dispatch: PropTypes.func.isRequired, |     dispatch: PropTypes.func.isRequired, | ||||||
|     children: PropTypes.node, |     children: PropTypes.node, | ||||||
|     layout: PropTypes.string, |  | ||||||
|     isWide: PropTypes.bool, |  | ||||||
|     systemFontUi: PropTypes.bool, |  | ||||||
|     navbarUnder: PropTypes.bool, |  | ||||||
|     isComposing: PropTypes.bool, |     isComposing: PropTypes.bool, | ||||||
|     hasComposingText: PropTypes.bool, |     hasComposingText: PropTypes.bool, | ||||||
|     location: PropTypes.object, |     location: PropTypes.object, | ||||||
|  | @ -224,7 +214,6 @@ export default class UI extends React.Component { | ||||||
|     if (nextProps.isComposing !== this.props.isComposing) { |     if (nextProps.isComposing !== this.props.isComposing) { | ||||||
|       // Avoid expensive update just to toggle a class
 |       // Avoid expensive update just to toggle a class
 | ||||||
|       this.node.classList.toggle('is-composing', nextProps.isComposing); |       this.node.classList.toggle('is-composing', nextProps.isComposing); | ||||||
|       this.node.classList.toggle('navbar-under', nextProps.navbarUnder); |  | ||||||
| 
 | 
 | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  | @ -324,10 +313,6 @@ export default class UI extends React.Component { | ||||||
|     this.context.router.history.push('/timelines/public'); |     this.context.router.history.push('/timelines/public'); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleHotkeyGoToDirect = () => { |  | ||||||
|     this.context.router.history.push('/timelines/direct'); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   handleHotkeyGoToStart = () => { |   handleHotkeyGoToStart = () => { | ||||||
|     this.context.router.history.push('/getting-started'); |     this.context.router.history.push('/getting-started'); | ||||||
|   } |   } | ||||||
|  | @ -354,24 +339,7 @@ export default class UI extends React.Component { | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { width, draggingOver } = this.state; |     const { width, draggingOver } = this.state; | ||||||
|     const { children, layout, isWide, navbarUnder } = this.props; |     const { children } = this.props; | ||||||
| 
 |  | ||||||
|     const columnsClass = layout => { |  | ||||||
|       switch (layout) { |  | ||||||
|       case 'single': |  | ||||||
|         return 'single-column'; |  | ||||||
|       case 'multiple': |  | ||||||
|         return 'multi-columns'; |  | ||||||
|       default: |  | ||||||
|         return 'auto-columns'; |  | ||||||
|       } |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     const className = classNames('ui', columnsClass(layout), { |  | ||||||
|       'wide': isWide, |  | ||||||
|       'system-font': this.props.systemFontUi, |  | ||||||
|       'navbar-under': navbarUnder, |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     const handlers = { |     const handlers = { | ||||||
|       new: this.handleHotkeyNew, |       new: this.handleHotkeyNew, | ||||||
|  | @ -383,7 +351,6 @@ export default class UI extends React.Component { | ||||||
|       goToNotifications: this.handleHotkeyGoToNotifications, |       goToNotifications: this.handleHotkeyGoToNotifications, | ||||||
|       goToLocal: this.handleHotkeyGoToLocal, |       goToLocal: this.handleHotkeyGoToLocal, | ||||||
|       goToFederated: this.handleHotkeyGoToFederated, |       goToFederated: this.handleHotkeyGoToFederated, | ||||||
|       goToDirect: this.handleHotkeyGoToDirect, |  | ||||||
|       goToStart: this.handleHotkeyGoToStart, |       goToStart: this.handleHotkeyGoToStart, | ||||||
|       goToFavourites: this.handleHotkeyGoToFavourites, |       goToFavourites: this.handleHotkeyGoToFavourites, | ||||||
|       goToPinned: this.handleHotkeyGoToPinned, |       goToPinned: this.handleHotkeyGoToPinned, | ||||||
|  | @ -394,17 +361,16 @@ export default class UI extends React.Component { | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}> |       <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}> | ||||||
|         <div className={className} ref={this.setRef}> |         <div className='ui' ref={this.setRef}> | ||||||
|           {navbarUnder ? null : (<TabsBar />)} |           <TabsBar /> | ||||||
| 
 | 
 | ||||||
|           <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}> |           <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}> | ||||||
|             <WrappedSwitch> |             <WrappedSwitch> | ||||||
|               <Redirect from='/' to='/getting-started' exact /> |               <Redirect from='/' to='/getting-started' exact /> | ||||||
|               <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> |               <WrappedRoute path='/getting-started' component={GettingStarted} content={children} /> | ||||||
|               <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} /> |               <WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} /> | ||||||
|               <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} /> |               <WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} /> | ||||||
|               <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} /> |               <WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} /> | ||||||
|               <WrappedRoute path='/timelines/direct' component={DirectTimeline} content={children} /> |  | ||||||
|               <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> |               <WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} /> | ||||||
| 
 | 
 | ||||||
|               <WrappedRoute path='/notifications' component={Notifications} content={children} /> |               <WrappedRoute path='/notifications' component={Notifications} content={children} /> | ||||||
|  | @ -430,7 +396,6 @@ export default class UI extends React.Component { | ||||||
|           </ColumnsAreaContainer> |           </ColumnsAreaContainer> | ||||||
| 
 | 
 | ||||||
|           <NotificationsContainer /> |           <NotificationsContainer /> | ||||||
|           {navbarUnder ? (<TabsBar />) : null} |  | ||||||
|           <LoadingBarContainer className='loading-bar' /> |           <LoadingBarContainer className='loading-bar' /> | ||||||
|           <ModalContainer /> |           <ModalContainer /> | ||||||
|           <UploadArea active={draggingOver} onClose={this.closeUploadModal} /> |           <UploadArea active={draggingOver} onClose={this.closeUploadModal} /> | ||||||
|  |  | ||||||
|  | @ -26,10 +26,6 @@ export function HashtagTimeline () { | ||||||
|   return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); |   return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function DirectTimeline() { |  | ||||||
|   return import(/* webpackChunkName: "features/direct_timeline" */'../../direct_timeline'); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export function Status () { | export function Status () { | ||||||
|   return import(/* webpackChunkName: "features/status" */'../../status'); |   return import(/* webpackChunkName: "features/status" */'../../status'); | ||||||
| } | } | ||||||
|  | @ -98,13 +94,6 @@ export function ReportModal () { | ||||||
|   return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); |   return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function SettingsModal () { |  | ||||||
|   return import(/* webpackChunkName: "modals/settings_modal" */'glitch/components/local_settings/container'); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| //  THESE AREN'T USED BY US; SEE `glitch/components/status` AND `mastodon/features/status`.  //
 |  | ||||||
| //  IF MASTODON EVER CHANGES DETAILED STATUSES TO REQUIRE THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL.  //
 |  | ||||||
| 
 |  | ||||||
| export function MediaGallery () { | export function MediaGallery () { | ||||||
|   return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery'); |   return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery'); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,5 @@ | ||||||
| const element = document.getElementById('initial-state'); | const element = document.getElementById('initial-state'); | ||||||
| const initialState = element && function () { | const initialState = element && JSON.parse(element.textContent); | ||||||
|   const result = JSON.parse(element.textContent); |  | ||||||
|   try { |  | ||||||
|     result.local_settings = JSON.parse(localStorage.getItem('mastodon-settings')); |  | ||||||
|   } catch (e) { |  | ||||||
|     result.local_settings = {}; |  | ||||||
|   } |  | ||||||
|   return result; |  | ||||||
| }(); |  | ||||||
| 
 | 
 | ||||||
| const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; | const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,15 +2,8 @@ import detectPassiveEvents from 'detect-passive-events'; | ||||||
| 
 | 
 | ||||||
| const LAYOUT_BREAKPOINT = 630; | const LAYOUT_BREAKPOINT = 630; | ||||||
| 
 | 
 | ||||||
| export function isMobile(width, columns) { | export function isMobile(width) { | ||||||
|   switch (columns) { |   return width <= LAYOUT_BREAKPOINT; | ||||||
|   case 'multiple': |  | ||||||
|     return false; |  | ||||||
|   case 'single': |  | ||||||
|     return true; |  | ||||||
|   default: |  | ||||||
|     return width <= LAYOUT_BREAKPOINT; |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; | ||||||
|  |  | ||||||
|  | @ -755,19 +755,6 @@ | ||||||
|     ], |     ], | ||||||
|     "path": "app/javascript/mastodon/features/compose/index.json" |     "path": "app/javascript/mastodon/features/compose/index.json" | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     "descriptors": [ |  | ||||||
|       { |  | ||||||
|         "defaultMessage": "Direct messages", |  | ||||||
|         "id": "column.direct" |  | ||||||
|       }, |  | ||||||
|       { |  | ||||||
|         "defaultMessage": "You don't have any direct messages yet. When you send or receive one, it will show up here.", |  | ||||||
|         "id": "empty_column.direct" |  | ||||||
|       } |  | ||||||
|     ], |  | ||||||
|     "path": "app/javascript/mastodon/features/direct_timeline/index.json" |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     "descriptors": [ |     "descriptors": [ | ||||||
|       { |       { | ||||||
|  | @ -829,10 +816,6 @@ | ||||||
|         "defaultMessage": "Local timeline", |         "defaultMessage": "Local timeline", | ||||||
|         "id": "navigation_bar.community_timeline" |         "id": "navigation_bar.community_timeline" | ||||||
|       }, |       }, | ||||||
|       { |  | ||||||
|         "defaultMessage": "Direct messages", |  | ||||||
|         "id": "navigation_bar.direct" |  | ||||||
|       }, |  | ||||||
|       { |       { | ||||||
|         "defaultMessage": "Preferences", |         "defaultMessage": "Preferences", | ||||||
|         "id": "navigation_bar.preferences" |         "id": "navigation_bar.preferences" | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ | ||||||
|   "bundle_modal_error.retry": "Try again", |   "bundle_modal_error.retry": "Try again", | ||||||
|   "column.blocks": "Blocked users", |   "column.blocks": "Blocked users", | ||||||
|   "column.community": "Local timeline", |   "column.community": "Local timeline", | ||||||
|   "column.direct": "Direct messages", |  | ||||||
|   "column.favourites": "Favourites", |   "column.favourites": "Favourites", | ||||||
|   "column.follow_requests": "Follow requests", |   "column.follow_requests": "Follow requests", | ||||||
|   "column.home": "Home", |   "column.home": "Home", | ||||||
|  | @ -81,7 +80,6 @@ | ||||||
|   "emoji_button.symbols": "Symbols", |   "emoji_button.symbols": "Symbols", | ||||||
|   "emoji_button.travel": "Travel & Places", |   "emoji_button.travel": "Travel & Places", | ||||||
|   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", |   "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", | ||||||
|   "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", |  | ||||||
|   "empty_column.hashtag": "There is nothing in this hashtag yet.", |   "empty_column.hashtag": "There is nothing in this hashtag yet.", | ||||||
|   "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", |   "empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.", | ||||||
|   "empty_column.home.public_timeline": "the public timeline", |   "empty_column.home.public_timeline": "the public timeline", | ||||||
|  | @ -108,7 +106,6 @@ | ||||||
|   "missing_indicator.label": "Not found", |   "missing_indicator.label": "Not found", | ||||||
|   "navigation_bar.blocks": "Blocked users", |   "navigation_bar.blocks": "Blocked users", | ||||||
|   "navigation_bar.community_timeline": "Local timeline", |   "navigation_bar.community_timeline": "Local timeline", | ||||||
|   "navigation_bar.direct": "Direct messages", |  | ||||||
|   "navigation_bar.edit_profile": "Edit profile", |   "navigation_bar.edit_profile": "Edit profile", | ||||||
|   "navigation_bar.favourites": "Favourites", |   "navigation_bar.favourites": "Favourites", | ||||||
|   "navigation_bar.follow_requests": "Follow requests", |   "navigation_bar.follow_requests": "Follow requests", | ||||||
|  |  | ||||||
|  | @ -28,11 +28,6 @@ function main() { | ||||||
|       WebPushSubscription.register(); |       WebPushSubscription.register(); | ||||||
|     } |     } | ||||||
|     perf.stop('main()'); |     perf.stop('main()'); | ||||||
| 
 |  | ||||||
|     // remember the initial URL
 |  | ||||||
|     if (window.history && typeof window._mastoInitialHistoryLen === 'undefined') { |  | ||||||
|       window._mastoInitialHistoryLen = window.history.length; |  | ||||||
|     } |  | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -126,7 +126,6 @@ export default function accountsCounters(state = initialState, action) { | ||||||
|   case STATUS_FETCH_SUCCESS: |   case STATUS_FETCH_SUCCESS: | ||||||
|     return normalizeAccountFromStatus(state, action.status); |     return normalizeAccountFromStatus(state, action.status); | ||||||
|   case ACCOUNT_FOLLOW_SUCCESS: |   case ACCOUNT_FOLLOW_SUCCESS: | ||||||
|     if (action.alreadyFollowing) { return state; } |  | ||||||
|     return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); |     return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); | ||||||
|   case ACCOUNT_UNFOLLOW_SUCCESS: |   case ACCOUNT_UNFOLLOW_SUCCESS: | ||||||
|     return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1)); |     return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1)); | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ import { | ||||||
|   COMPOSE_SUGGESTIONS_CLEAR, |   COMPOSE_SUGGESTIONS_CLEAR, | ||||||
|   COMPOSE_SUGGESTIONS_READY, |   COMPOSE_SUGGESTIONS_READY, | ||||||
|   COMPOSE_SUGGESTION_SELECT, |   COMPOSE_SUGGESTION_SELECT, | ||||||
|   COMPOSE_ADVANCED_OPTIONS_CHANGE, |  | ||||||
|   COMPOSE_SENSITIVITY_CHANGE, |   COMPOSE_SENSITIVITY_CHANGE, | ||||||
|   COMPOSE_SPOILERNESS_CHANGE, |   COMPOSE_SPOILERNESS_CHANGE, | ||||||
|   COMPOSE_SPOILER_TEXT_CHANGE, |   COMPOSE_SPOILER_TEXT_CHANGE, | ||||||
|  | @ -26,7 +25,6 @@ import { | ||||||
|   COMPOSE_UPLOAD_CHANGE_REQUEST, |   COMPOSE_UPLOAD_CHANGE_REQUEST, | ||||||
|   COMPOSE_UPLOAD_CHANGE_SUCCESS, |   COMPOSE_UPLOAD_CHANGE_SUCCESS, | ||||||
|   COMPOSE_UPLOAD_CHANGE_FAIL, |   COMPOSE_UPLOAD_CHANGE_FAIL, | ||||||
|   COMPOSE_DOODLE_SET, |  | ||||||
|   COMPOSE_RESET, |   COMPOSE_RESET, | ||||||
| } from '../actions/compose'; | } from '../actions/compose'; | ||||||
| import { TIMELINE_DELETE } from '../actions/timelines'; | import { TIMELINE_DELETE } from '../actions/timelines'; | ||||||
|  | @ -37,9 +35,6 @@ import { me } from '../initial_state'; | ||||||
| 
 | 
 | ||||||
| const initialState = ImmutableMap({ | const initialState = ImmutableMap({ | ||||||
|   mounted: false, |   mounted: false, | ||||||
|   advanced_options: ImmutableMap({ |  | ||||||
|     do_not_federate: false, |  | ||||||
|   }), |  | ||||||
|   sensitive: false, |   sensitive: false, | ||||||
|   spoiler: false, |   spoiler: false, | ||||||
|   spoiler_text: '', |   spoiler_text: '', | ||||||
|  | @ -55,24 +50,10 @@ const initialState = ImmutableMap({ | ||||||
|   media_attachments: ImmutableList(), |   media_attachments: ImmutableList(), | ||||||
|   suggestion_token: null, |   suggestion_token: null, | ||||||
|   suggestions: ImmutableList(), |   suggestions: ImmutableList(), | ||||||
|   default_advanced_options: ImmutableMap({ |  | ||||||
|     do_not_federate: false, |  | ||||||
|   }), |  | ||||||
|   default_privacy: 'public', |   default_privacy: 'public', | ||||||
|   default_sensitive: false, |   default_sensitive: false, | ||||||
|   resetFileKey: Math.floor((Math.random() * 0x10000)), |   resetFileKey: Math.floor((Math.random() * 0x10000)), | ||||||
|   idempotencyKey: null, |   idempotencyKey: null, | ||||||
|   doodle: ImmutableMap({ |  | ||||||
|     fg: 'rgb(  0,    0,    0)', |  | ||||||
|     bg: 'rgb(255,  255,  255)', |  | ||||||
|     swapped: false, |  | ||||||
|     mode: 'draw', |  | ||||||
|     size: 'normal', |  | ||||||
|     weight: 2, |  | ||||||
|     opacity: 1, |  | ||||||
|     adaptiveStroke: true, |  | ||||||
|     smoothing: false, |  | ||||||
|   }), |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| function statusToTextMentions(state, status) { | function statusToTextMentions(state, status) { | ||||||
|  | @ -92,7 +73,6 @@ function clearAll(state) { | ||||||
|     map.set('spoiler_text', ''); |     map.set('spoiler_text', ''); | ||||||
|     map.set('is_submitting', false); |     map.set('is_submitting', false); | ||||||
|     map.set('in_reply_to', null); |     map.set('in_reply_to', null); | ||||||
|     map.set('advanced_options', state.get('default_advanced_options')); |  | ||||||
|     map.set('privacy', state.get('default_privacy')); |     map.set('privacy', state.get('default_privacy')); | ||||||
|     map.set('sensitive', false); |     map.set('sensitive', false); | ||||||
|     map.update('media_attachments', list => list.clear()); |     map.update('media_attachments', list => list.clear()); | ||||||
|  | @ -134,7 +114,7 @@ function removeMedia(state, mediaId) { | ||||||
| 
 | 
 | ||||||
| const insertSuggestion = (state, position, token, completion) => { | const insertSuggestion = (state, position, token, completion) => { | ||||||
|   return state.withMutations(map => { |   return state.withMutations(map => { | ||||||
|     map.update('text', oldText => `${oldText.slice(0, position)}${completion}\u200B${oldText.slice(position + token.length)}`); |     map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`); | ||||||
|     map.set('suggestion_token', null); |     map.set('suggestion_token', null); | ||||||
|     map.update('suggestions', ImmutableList(), list => list.clear()); |     map.update('suggestions', ImmutableList(), list => list.clear()); | ||||||
|     map.set('focusDate', new Date()); |     map.set('focusDate', new Date()); | ||||||
|  | @ -146,7 +126,7 @@ const insertEmoji = (state, position, emojiData) => { | ||||||
|   const emoji = emojiData.native; |   const emoji = emojiData.native; | ||||||
| 
 | 
 | ||||||
|   return state.withMutations(map => { |   return state.withMutations(map => { | ||||||
|     map.update('text', oldText => `${oldText.slice(0, position)}${emoji}\u200B${oldText.slice(position)}`); |     map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`); | ||||||
|     map.set('focusDate', new Date()); |     map.set('focusDate', new Date()); | ||||||
|     map.set('idempotencyKey', uuid()); |     map.set('idempotencyKey', uuid()); | ||||||
|   }); |   }); | ||||||
|  | @ -184,11 +164,6 @@ export default function compose(state = initialState, action) { | ||||||
|     return state |     return state | ||||||
|       .set('mounted', false) |       .set('mounted', false) | ||||||
|       .set('is_composing', false); |       .set('is_composing', false); | ||||||
|   case COMPOSE_ADVANCED_OPTIONS_CHANGE: |  | ||||||
|     return state |  | ||||||
|       .set('advanced_options', |  | ||||||
|         state.get('advanced_options').set(action.option, !state.getIn(['advanced_options', action.option]))) |  | ||||||
|       .set('idempotencyKey', uuid()); |  | ||||||
|   case COMPOSE_SENSITIVITY_CHANGE: |   case COMPOSE_SENSITIVITY_CHANGE: | ||||||
|     return state.withMutations(map => { |     return state.withMutations(map => { | ||||||
|       if (!state.get('spoiler')) { |       if (!state.get('spoiler')) { | ||||||
|  | @ -226,9 +201,6 @@ export default function compose(state = initialState, action) { | ||||||
|       map.set('in_reply_to', action.status.get('id')); |       map.set('in_reply_to', action.status.get('id')); | ||||||
|       map.set('text', statusToTextMentions(state, action.status)); |       map.set('text', statusToTextMentions(state, action.status)); | ||||||
|       map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); |       map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); | ||||||
|       map.set('advanced_options', new ImmutableMap({ |  | ||||||
|         do_not_federate: /👁\ufe0f?<\/p>$/.test(action.status.get('content')), |  | ||||||
|       })); |  | ||||||
|       map.set('focusDate', new Date()); |       map.set('focusDate', new Date()); | ||||||
|       map.set('preselectDate', new Date()); |       map.set('preselectDate', new Date()); | ||||||
|       map.set('idempotencyKey', uuid()); |       map.set('idempotencyKey', uuid()); | ||||||
|  | @ -249,7 +221,6 @@ export default function compose(state = initialState, action) { | ||||||
|       map.set('spoiler', false); |       map.set('spoiler', false); | ||||||
|       map.set('spoiler_text', ''); |       map.set('spoiler_text', ''); | ||||||
|       map.set('privacy', state.get('default_privacy')); |       map.set('privacy', state.get('default_privacy')); | ||||||
|       map.set('advanced_options', state.get('default_advanced_options')); |  | ||||||
|       map.set('idempotencyKey', uuid()); |       map.set('idempotencyKey', uuid()); | ||||||
|     }); |     }); | ||||||
|   case COMPOSE_SUBMIT_REQUEST: |   case COMPOSE_SUBMIT_REQUEST: | ||||||
|  | @ -299,8 +270,6 @@ export default function compose(state = initialState, action) { | ||||||
| 
 | 
 | ||||||
|         return item; |         return item; | ||||||
|       })); |       })); | ||||||
|   case COMPOSE_DOODLE_SET: |  | ||||||
|     return state.mergeIn(['doodle'], action.options); |  | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ import accounts_counters from './accounts_counters'; | ||||||
| import statuses from './statuses'; | import statuses from './statuses'; | ||||||
| import relationships from './relationships'; | import relationships from './relationships'; | ||||||
| import settings from './settings'; | import settings from './settings'; | ||||||
| import local_settings from '../../glitch/reducers/local_settings'; |  | ||||||
| import push_notifications from './push_notifications'; | import push_notifications from './push_notifications'; | ||||||
| import status_lists from './status_lists'; | import status_lists from './status_lists'; | ||||||
| import cards from './cards'; | import cards from './cards'; | ||||||
|  | @ -37,7 +36,6 @@ const reducers = { | ||||||
|   statuses, |   statuses, | ||||||
|   relationships, |   relationships, | ||||||
|   settings, |   settings, | ||||||
|   local_settings, |  | ||||||
|   push_notifications, |   push_notifications, | ||||||
|   cards, |   cards, | ||||||
|   mutes, |   mutes, | ||||||
|  |  | ||||||
|  | @ -8,12 +8,6 @@ import { | ||||||
|   NOTIFICATIONS_EXPAND_FAIL, |   NOTIFICATIONS_EXPAND_FAIL, | ||||||
|   NOTIFICATIONS_CLEAR, |   NOTIFICATIONS_CLEAR, | ||||||
|   NOTIFICATIONS_SCROLL_TOP, |   NOTIFICATIONS_SCROLL_TOP, | ||||||
|   NOTIFICATIONS_DELETE_MARKED_REQUEST, |  | ||||||
|   NOTIFICATIONS_DELETE_MARKED_SUCCESS, |  | ||||||
|   NOTIFICATION_MARK_FOR_DELETE, |  | ||||||
|   NOTIFICATIONS_DELETE_MARKED_FAIL, |  | ||||||
|   NOTIFICATIONS_ENTER_CLEARING_MODE, |  | ||||||
|   NOTIFICATIONS_MARK_ALL_FOR_DELETE, |  | ||||||
| } from '../actions/notifications'; | } from '../actions/notifications'; | ||||||
| import { | import { | ||||||
|   ACCOUNT_BLOCK_SUCCESS, |   ACCOUNT_BLOCK_SUCCESS, | ||||||
|  | @ -29,16 +23,12 @@ const initialState = ImmutableMap({ | ||||||
|   unread: 0, |   unread: 0, | ||||||
|   loaded: false, |   loaded: false, | ||||||
|   isLoading: true, |   isLoading: true, | ||||||
|   cleaningMode: false, |  | ||||||
|   // notification removal mark of new notifs loaded whilst cleaningMode is true.
 |  | ||||||
|   markNewForDelete: false, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const notificationToMap = (state, notification) => ImmutableMap({ | const notificationToMap = notification => ImmutableMap({ | ||||||
|   id: notification.id, |   id: notification.id, | ||||||
|   type: notification.type, |   type: notification.type, | ||||||
|   account: notification.account.id, |   account: notification.account.id, | ||||||
|   markedForDelete: state.get('markNewForDelete'), |  | ||||||
|   status: notification.status ? notification.status.id : null, |   status: notification.status ? notification.status.id : null, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @ -54,7 +44,7 @@ const normalizeNotification = (state, notification) => { | ||||||
|       list = list.take(20); |       list = list.take(20); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return list.unshift(notificationToMap(state, notification)); |     return list.unshift(notificationToMap(notification)); | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -63,7 +53,7 @@ const normalizeNotifications = (state, notifications, next) => { | ||||||
|   const loaded = state.get('loaded'); |   const loaded = state.get('loaded'); | ||||||
| 
 | 
 | ||||||
|   notifications.forEach((n, i) => { |   notifications.forEach((n, i) => { | ||||||
|     items = items.set(i, notificationToMap(state, n)); |     items = items.set(i, notificationToMap(n)); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   if (state.get('next') === null) { |   if (state.get('next') === null) { | ||||||
|  | @ -80,7 +70,7 @@ const appendNormalizedNotifications = (state, notifications, next) => { | ||||||
|   let items = ImmutableList(); |   let items = ImmutableList(); | ||||||
| 
 | 
 | ||||||
|   notifications.forEach((n, i) => { |   notifications.forEach((n, i) => { | ||||||
|     items = items.set(i, notificationToMap(state, n)); |     items = items.set(i, notificationToMap(n)); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   return state |   return state | ||||||
|  | @ -105,43 +95,11 @@ const deleteByStatus = (state, statusId) => { | ||||||
|   return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); |   return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const markForDelete = (state, notificationId, yes) => { |  | ||||||
|   return state.update('items', list => list.map(item => { |  | ||||||
|     if(item.get('id') === notificationId) { |  | ||||||
|       return item.set('markedForDelete', yes); |  | ||||||
|     } else { |  | ||||||
|       return item; |  | ||||||
|     } |  | ||||||
|   })); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const markAllForDelete = (state, yes) => { |  | ||||||
|   return state.update('items', list => list.map(item => { |  | ||||||
|     if(yes !== null) { |  | ||||||
|       return item.set('markedForDelete', yes); |  | ||||||
|     } else { |  | ||||||
|       return item.set('markedForDelete', !item.get('markedForDelete')); |  | ||||||
|     } |  | ||||||
|   })); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const unmarkAllForDelete = (state) => { |  | ||||||
|   return state.update('items', list => list.map(item => item.set('markedForDelete', false))); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| const deleteMarkedNotifs = (state) => { |  | ||||||
|   return state.update('items', list => list.filterNot(item => item.get('markedForDelete'))); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| export default function notifications(state = initialState, action) { | export default function notifications(state = initialState, action) { | ||||||
|   let st; |  | ||||||
| 
 |  | ||||||
|   switch(action.type) { |   switch(action.type) { | ||||||
|   case NOTIFICATIONS_REFRESH_REQUEST: |   case NOTIFICATIONS_REFRESH_REQUEST: | ||||||
|   case NOTIFICATIONS_EXPAND_REQUEST: |   case NOTIFICATIONS_EXPAND_REQUEST: | ||||||
|   case NOTIFICATIONS_DELETE_MARKED_REQUEST: |  | ||||||
|     return state.set('isLoading', true); |     return state.set('isLoading', true); | ||||||
|   case NOTIFICATIONS_DELETE_MARKED_FAIL: |  | ||||||
|   case NOTIFICATIONS_REFRESH_FAIL: |   case NOTIFICATIONS_REFRESH_FAIL: | ||||||
|   case NOTIFICATIONS_EXPAND_FAIL: |   case NOTIFICATIONS_EXPAND_FAIL: | ||||||
|     return state.set('isLoading', false); |     return state.set('isLoading', false); | ||||||
|  | @ -160,31 +118,6 @@ export default function notifications(state = initialState, action) { | ||||||
|     return state.set('items', ImmutableList()).set('next', null); |     return state.set('items', ImmutableList()).set('next', null); | ||||||
|   case TIMELINE_DELETE: |   case TIMELINE_DELETE: | ||||||
|     return deleteByStatus(state, action.id); |     return deleteByStatus(state, action.id); | ||||||
| 
 |  | ||||||
|   case NOTIFICATION_MARK_FOR_DELETE: |  | ||||||
|     return markForDelete(state, action.id, action.yes); |  | ||||||
| 
 |  | ||||||
|   case NOTIFICATIONS_DELETE_MARKED_SUCCESS: |  | ||||||
|     return deleteMarkedNotifs(state).set('isLoading', false); |  | ||||||
| 
 |  | ||||||
|   case NOTIFICATIONS_ENTER_CLEARING_MODE: |  | ||||||
|     st = state.set('cleaningMode', action.yes); |  | ||||||
|     if (!action.yes) { |  | ||||||
|       return unmarkAllForDelete(st).set('markNewForDelete', false); |  | ||||||
|     } else { |  | ||||||
|       return st; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|   case NOTIFICATIONS_MARK_ALL_FOR_DELETE: |  | ||||||
|     st = state; |  | ||||||
|     if (action.yes === null) { |  | ||||||
|       // Toggle - this is a bit confusing, as it toggles the all-none mode
 |  | ||||||
|       //st = st.set('markNewForDelete', !st.get('markNewForDelete'));
 |  | ||||||
|     } else { |  | ||||||
|       st = st.set('markNewForDelete', action.yes); |  | ||||||
|     } |  | ||||||
|     return markAllForDelete(st, action.yes); |  | ||||||
| 
 |  | ||||||
|   default: |   default: | ||||||
|     return state; |     return state; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -9,7 +9,6 @@ const initialState = ImmutableMap({ | ||||||
|   saved: true, |   saved: true, | ||||||
| 
 | 
 | ||||||
|   onboarded: false, |   onboarded: false, | ||||||
|   layout: 'auto', |  | ||||||
| 
 | 
 | ||||||
|   skinTone: 1, |   skinTone: 1, | ||||||
| 
 | 
 | ||||||
|  | @ -58,12 +57,6 @@ const initialState = ImmutableMap({ | ||||||
|       body: '', |       body: '', | ||||||
|     }), |     }), | ||||||
|   }), |   }), | ||||||
| 
 |  | ||||||
|   direct: ImmutableMap({ |  | ||||||
|     regex: ImmutableMap({ |  | ||||||
|       body: '', |  | ||||||
|     }), |  | ||||||
|   }), |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const defaultColumns = fromJS([ | const defaultColumns = fromJS([ | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import loadPolyfills from '../mastodon/load_polyfills'; | import loadPolyfills from 'themes/glitch/util/load_polyfills'; | ||||||
| 
 | 
 | ||||||
| require.context('../images/', true); | require.context('../images/', true); | ||||||
| 
 | 
 | ||||||
| function loaded() { | function loaded() { | ||||||
|   const TimelineContainer = require('../mastodon/containers/timeline_container').default; |   const TimelineContainer = require('themes/glitch/containers/timeline_container').default; | ||||||
|   const React             = require('react'); |   const React             = require('react'); | ||||||
|   const ReactDOM          = require('react-dom'); |   const ReactDOM          = require('react-dom'); | ||||||
|   const mountNode         = document.getElementById('mastodon-timeline'); |   const mountNode         = document.getElementById('mastodon-timeline'); | ||||||
|  | @ -15,7 +15,7 @@ function loaded() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function main() { | function main() { | ||||||
|   const ready = require('../mastodon/ready').default; |   const ready = require('themes/glitch/util/ready').default; | ||||||
|   ready(loaded); |   ready(loaded); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,7 @@ | ||||||
|  | //  THIS IS THE `vanilla` THEME PACK FILE!!
 | ||||||
|  | //  IT'S HERE FOR UPSTREAM COMPATIBILITY!!
 | ||||||
|  | //  THE `glitch` PACK FILE IS IN `themes/glitch/index.js`!!
 | ||||||
|  | 
 | ||||||
| import loadPolyfills from '../mastodon/load_polyfills'; | import loadPolyfills from '../mastodon/load_polyfills'; | ||||||
| 
 | 
 | ||||||
| // import default stylesheet with variables
 | // import default stylesheet with variables
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,6 @@ | ||||||
| import { start } from 'rails-ujs'; | import { start } from 'rails-ujs'; | ||||||
| import 'font-awesome/css/font-awesome.css'; | import 'font-awesome/css/font-awesome.css'; | ||||||
| 
 | 
 | ||||||
| // import common styling
 |  | ||||||
| require('../styles/common.scss'); |  | ||||||
| 
 |  | ||||||
| require.context('../images/', true); | require.context('../images/', true); | ||||||
| 
 | 
 | ||||||
| start(); | start(); | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import loadPolyfills from '../mastodon/load_polyfills'; | import loadPolyfills from 'themes/glitch/util/load_polyfills'; | ||||||
| import { processBio } from '../glitch/util/bio_metadata'; | import { processBio } from 'themes/glitch/util/bio_metadata'; | ||||||
| import ready from '../mastodon/ready'; | import ready from 'themes/glitch/util/ready'; | ||||||
| 
 | 
 | ||||||
| window.addEventListener('message', e => { | window.addEventListener('message', e => { | ||||||
|   const data = e.data || {}; |   const data = e.data || {}; | ||||||
|  | @ -22,12 +22,12 @@ function main() { | ||||||
|   const { length } = require('stringz'); |   const { length } = require('stringz'); | ||||||
|   const IntlRelativeFormat = require('intl-relativeformat').default; |   const IntlRelativeFormat = require('intl-relativeformat').default; | ||||||
|   const { delegate } = require('rails-ujs'); |   const { delegate } = require('rails-ujs'); | ||||||
|   const emojify = require('../mastodon/features/emoji/emoji').default; |   const emojify = require('../themes/glitch/util/emoji').default; | ||||||
|   const { getLocale } = require('../mastodon/locales'); |   const { getLocale } = require('mastodon/locales'); | ||||||
|   const { localeData } = getLocale(); |   const { localeData } = getLocale(); | ||||||
|   const VideoContainer = require('../mastodon/containers/video_container').default; |   const VideoContainer = require('../themes/glitch/containers/video_container').default; | ||||||
|   const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default; |   const MediaGalleryContainer = require('../themes/glitch/containers/media_gallery_container').default; | ||||||
|   const CardContainer = require('../mastodon/containers/card_container').default; |   const CardContainer = require('../themes/glitch/containers/card_container').default; | ||||||
|   const React = require('react'); |   const React = require('react'); | ||||||
|   const ReactDOM = require('react-dom'); |   const ReactDOM = require('react-dom'); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| import loadPolyfills from '../mastodon/load_polyfills'; | import loadPolyfills from 'themes/glitch/util/load_polyfills'; | ||||||
| 
 | 
 | ||||||
| require.context('../images/', true); | require.context('../images/', true); | ||||||
| 
 | 
 | ||||||
| function loaded() { | function loaded() { | ||||||
|   const ComposeContainer = require('../mastodon/containers/compose_container').default; |   const ComposeContainer = require('themes/glitch/containers/compose_container').default; | ||||||
|   const React = require('react'); |   const React = require('react'); | ||||||
|   const ReactDOM = require('react-dom'); |   const ReactDOM = require('react-dom'); | ||||||
|   const mountNode = document.getElementById('mastodon-compose'); |   const mountNode = document.getElementById('mastodon-compose'); | ||||||
|  | @ -15,7 +15,7 @@ function loaded() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function main() { | function main() { | ||||||
|   const ready = require('../mastodon/ready').default; |   const ready = require('themes/glitch/util/ready').default; | ||||||
|   ready(loaded); |   ready(loaded); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| @import 'mastodon/mixins'; | @import 'mastodon/mixins'; | ||||||
| @import 'mastodon/variables'; | @import 'mastodon/variables'; | ||||||
| @import 'variables-glitch'; |  | ||||||
| @import 'fonts/roboto'; | @import 'fonts/roboto'; | ||||||
| @import 'fonts/roboto-mono'; | @import 'fonts/roboto-mono'; | ||||||
| @import 'fonts/montserrat'; | @import 'fonts/montserrat'; | ||||||
|  |  | ||||||
|  | @ -1,9 +1,9 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-display'; |   font-family: 'mastodon-font-display'; | ||||||
|   src: local('Montserrat'), |   src: local('Montserrat'), | ||||||
|     url('../fonts/montserrat/Montserrat-Regular.woff2') format('woff2'), |     url('~fonts/montserrat/Montserrat-Regular.woff2') format('woff2'), | ||||||
|     url('../fonts/montserrat/Montserrat-Regular.woff') format('woff'), |     url('~fonts/montserrat/Montserrat-Regular.woff') format('woff'), | ||||||
|     url('../fonts/montserrat/Montserrat-Regular.ttf') format('truetype'); |     url('~fonts/montserrat/Montserrat-Regular.ttf') format('truetype'); | ||||||
|   font-weight: 400; |   font-weight: 400; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  | @ -11,7 +11,7 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-display'; |   font-family: 'mastodon-font-display'; | ||||||
|   src: local('Montserrat'), |   src: local('Montserrat'), | ||||||
|     url('../fonts/montserrat/Montserrat-Medium.ttf') format('truetype'); |     url('~fonts/montserrat/Montserrat-Medium.ttf') format('truetype'); | ||||||
|   font-weight: 500; |   font-weight: 500; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-monospace'; |   font-family: 'mastodon-font-monospace'; | ||||||
|   src: local('Roboto Mono'), |   src: local('Roboto Mono'), | ||||||
|     url('../fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'), |     url('~fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'), | ||||||
|     url('../fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'), |     url('~fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'), | ||||||
|     url('../fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'), |     url('~fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'), | ||||||
|     url('../fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg'); |     url('~fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg'); | ||||||
|   font-weight: 400; |   font-weight: 400; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,10 +1,10 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-sans-serif'; |   font-family: 'mastodon-font-sans-serif'; | ||||||
|   src: local('Roboto'), |   src: local('Roboto'), | ||||||
|     url('../fonts/roboto/roboto-italic-webfont.woff2') format('woff2'), |     url('~fonts/roboto/roboto-italic-webfont.woff2') format('woff2'), | ||||||
|     url('../fonts/roboto/roboto-italic-webfont.woff') format('woff'), |     url('~fonts/roboto/roboto-italic-webfont.woff') format('woff'), | ||||||
|     url('../fonts/roboto/roboto-italic-webfont.ttf') format('truetype'), |     url('~fonts/roboto/roboto-italic-webfont.ttf') format('truetype'), | ||||||
|     url('../fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg'); |     url('~fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg'); | ||||||
|   font-weight: normal; |   font-weight: normal; | ||||||
|   font-style: italic; |   font-style: italic; | ||||||
| } | } | ||||||
|  | @ -12,10 +12,10 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-sans-serif'; |   font-family: 'mastodon-font-sans-serif'; | ||||||
|   src: local('Roboto'), |   src: local('Roboto'), | ||||||
|     url('../fonts/roboto/roboto-bold-webfont.woff2') format('woff2'), |     url('~fonts/roboto/roboto-bold-webfont.woff2') format('woff2'), | ||||||
|     url('../fonts/roboto/roboto-bold-webfont.woff') format('woff'), |     url('~fonts/roboto/roboto-bold-webfont.woff') format('woff'), | ||||||
|     url('../fonts/roboto/roboto-bold-webfont.ttf') format('truetype'), |     url('~fonts/roboto/roboto-bold-webfont.ttf') format('truetype'), | ||||||
|     url('../fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg'); |     url('~fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg'); | ||||||
|   font-weight: bold; |   font-weight: bold; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  | @ -23,10 +23,10 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-sans-serif'; |   font-family: 'mastodon-font-sans-serif'; | ||||||
|   src: local('Roboto'), |   src: local('Roboto'), | ||||||
|     url('../fonts/roboto/roboto-medium-webfont.woff2') format('woff2'), |     url('~fonts/roboto/roboto-medium-webfont.woff2') format('woff2'), | ||||||
|     url('../fonts/roboto/roboto-medium-webfont.woff') format('woff'), |     url('~fonts/roboto/roboto-medium-webfont.woff') format('woff'), | ||||||
|     url('../fonts/roboto/roboto-medium-webfont.ttf') format('truetype'), |     url('~fonts/roboto/roboto-medium-webfont.ttf') format('truetype'), | ||||||
|     url('../fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg'); |     url('~fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg'); | ||||||
|   font-weight: 500; |   font-weight: 500; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  | @ -34,10 +34,10 @@ | ||||||
| @font-face { | @font-face { | ||||||
|   font-family: 'mastodon-font-sans-serif'; |   font-family: 'mastodon-font-sans-serif'; | ||||||
|   src: local('Roboto'), |   src: local('Roboto'), | ||||||
|     url('../fonts/roboto/roboto-regular-webfont.woff2') format('woff2'), |     url('~fonts/roboto/roboto-regular-webfont.woff2') format('woff2'), | ||||||
|     url('../fonts/roboto/roboto-regular-webfont.woff') format('woff'), |     url('~fonts/roboto/roboto-regular-webfont.woff') format('woff'), | ||||||
|     url('../fonts/roboto/roboto-regular-webfont.ttf') format('truetype'), |     url('~fonts/roboto/roboto-regular-webfont.ttf') format('truetype'), | ||||||
|     url('../fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg'); |     url('~fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg'); | ||||||
|   font-weight: normal; |   font-weight: normal; | ||||||
|   font-style: normal; |   font-style: normal; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| @mixin avatar-radius() { | @mixin avatar-radius() { | ||||||
|   border-radius: $ui-avatar-border-size; |   border-radius: 4px; | ||||||
|   background: transparent no-repeat; |   background: transparent no-repeat; | ||||||
|   background-position: 50%; |   background-position: 50%; | ||||||
|   background-clip: padding-box; |   background-clip: padding-box; | ||||||
|  | @ -10,33 +10,3 @@ | ||||||
|   height: $size; |   height: $size; | ||||||
|   background-size: $size $size; |   background-size: $size $size; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| @mixin single-column($media, $parent: '&') { |  | ||||||
|   .auto-columns #{$parent} { |  | ||||||
|     @media #{$media} { |  | ||||||
|       @content; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   .single-column #{$parent} { |  | ||||||
|     @content; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @mixin limited-single-column($media, $parent: '&') { |  | ||||||
|   .auto-columns #{$parent}, .single-column #{$parent} { |  | ||||||
|     @media #{$media} { |  | ||||||
|       @content; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| @mixin multi-columns($media, $parent: '&') { |  | ||||||
|   .auto-columns #{$parent} { |  | ||||||
|     @media #{$media} { |  | ||||||
|       @content; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   .multi-columns #{$parent} { |  | ||||||
|     @content; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -424,14 +424,16 @@ | ||||||
|       text-align: center; |       text-align: center; | ||||||
| 
 | 
 | ||||||
|       .avatar { |       .avatar { | ||||||
|         @include avatar-size(80px); |         width: 80px; | ||||||
|  |         height: 80px; | ||||||
|         margin: 0 auto; |         margin: 0 auto; | ||||||
|         margin-bottom: 15px; |         margin-bottom: 15px; | ||||||
| 
 | 
 | ||||||
|         img { |         img { | ||||||
|           @include avatar-radius(); |  | ||||||
|           @include avatar-size(80px); |  | ||||||
|           display: block; |           display: block; | ||||||
|  |           width: 80px; | ||||||
|  |           height: 80px; | ||||||
|  |           border-radius: 48px; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -83,15 +83,16 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .avatar { |   .avatar { | ||||||
|     @include avatar-size(120px); |     width: 120px; | ||||||
|     margin: 0 auto; |     margin: 0 auto; | ||||||
|     position: relative; |     position: relative; | ||||||
|     z-index: 2; |     z-index: 2; | ||||||
| 
 | 
 | ||||||
|     img { |     img { | ||||||
|       @include avatar-radius(); |       width: 120px; | ||||||
|       @include avatar-size(120px); |       height: 120px; | ||||||
|       display: block; |       display: block; | ||||||
|  |       border-radius: 120px; | ||||||
|       box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); |       box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | @ -206,50 +207,6 @@ | ||||||
|     color: $ui-secondary-color; |     color: $ui-secondary-color; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .metadata { |  | ||||||
|     $meta-table-border: darken($classic-highlight-color, 20%);//#174f77; |  | ||||||
| 
 |  | ||||||
|     border-collapse: collapse; |  | ||||||
|     padding: 0; |  | ||||||
|     margin: 15px -15px -10px -15px; |  | ||||||
|     border: 0 none; |  | ||||||
|     border-top: 1px solid $meta-table-border; |  | ||||||
|     border-bottom: 1px solid $meta-table-border; |  | ||||||
| 
 |  | ||||||
|     td, th { |  | ||||||
|       padding: 10px; |  | ||||||
|       border: 0 none; |  | ||||||
|       border-bottom: 1px solid $meta-table-border; |  | ||||||
|       vertical-align: middle; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     tr:last-child { |  | ||||||
|       td, th { |  | ||||||
|         border-bottom: 0 none; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     td { |  | ||||||
|       color: $ui-primary-color; |  | ||||||
|       width:100%; // makes it stretch |  | ||||||
|       padding-left: 0; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     th { |  | ||||||
|       padding-left: 15px; |  | ||||||
|       font-weight: bold; |  | ||||||
|       text-align: left; |  | ||||||
|       width: 94px; |  | ||||||
|       color: $ui-secondary-color; |  | ||||||
|       background: darken($ui-base-color, 8%); |  | ||||||
|       //background: #131415; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     a { |  | ||||||
|       color: $classic-highlight-color; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   @media screen and (max-width: 480px) { |   @media screen and (max-width: 480px) { | ||||||
|     display: block; |     display: block; | ||||||
| 
 | 
 | ||||||
|  | @ -407,12 +364,14 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .avatar { |     .avatar { | ||||||
|       @include avatar-size(80px); |       width: 80px; | ||||||
|  |       height: 80px; | ||||||
| 
 | 
 | ||||||
|       img { |       img { | ||||||
|         display: block; |         display: block; | ||||||
|         @include avatar-radius(); |         width: 80px; | ||||||
|         @include avatar-size(80px); |         height: 80px; | ||||||
|  |         border-radius: 80px; | ||||||
|         border: 2px solid $simple-background-color; |         border: 2px solid $simple-background-color; | ||||||
|         background: $simple-background-color; |         background: $simple-background-color; | ||||||
|       } |       } | ||||||
|  | @ -492,14 +451,15 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     & > div { |     & > div { | ||||||
|       @include avatar-size(48px); |  | ||||||
|       float: left; |       float: left; | ||||||
|       margin-right: 10px; |       margin-right: 10px; | ||||||
|  |       width: 48px; | ||||||
|  |       height: 48px; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .avatar { |     .avatar { | ||||||
|       @include avatar-radius(); |  | ||||||
|       display: block; |       display: block; | ||||||
|  |       border-radius: 4px; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .display-name { |     .display-name { | ||||||
|  |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							|  | @ -93,25 +93,28 @@ | ||||||
| 
 | 
 | ||||||
|     .status__avatar { |     .status__avatar { | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       @include avatar-size(48px); |       left: 14px; | ||||||
|       margin-left: -62px; |       top: 14px; | ||||||
|  |       width: 48px; | ||||||
|  |       height: 48px; | ||||||
| 
 | 
 | ||||||
|       & > div { |       & > div { | ||||||
|         @include avatar-size(48px); |         width: 48px; | ||||||
|  |         height: 48px; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       img { |       img { | ||||||
|         @include avatar-radius(); |  | ||||||
|         display: block; |         display: block; | ||||||
|  |         border-radius: 4px; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .display-name { |     .display-name { | ||||||
|       display: block; |       display: block; | ||||||
|       max-width: 100%; |       max-width: 100%; | ||||||
|       //overflow: hidden; |       overflow: hidden; | ||||||
|       //white-space: nowrap; |       white-space: nowrap; | ||||||
|       //text-overflow: ellipsis; |       text-overflow: ellipsis; | ||||||
| 
 | 
 | ||||||
|       strong { |       strong { | ||||||
|         font-weight: 500; |         font-weight: 500; | ||||||
|  | @ -177,11 +180,12 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .avatar { |     .avatar { | ||||||
|       @include avatar-size(48px); |       width: 48px; | ||||||
|  |       height: 48px; | ||||||
| 
 | 
 | ||||||
|       img { |       img { | ||||||
|         @include avatar-radius(); |  | ||||||
|         display: block; |         display: block; | ||||||
|  |         border-radius: 4px; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -27,6 +27,3 @@ $ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkes | ||||||
| $ui-primary-color: $classic-primary-color !default;            // Lighter | $ui-primary-color: $classic-primary-color !default;            // Lighter | ||||||
| $ui-secondary-color: $classic-secondary-color !default;        // Lightest | $ui-secondary-color: $classic-secondary-color !default;        // Lightest | ||||||
| $ui-highlight-color: $classic-highlight-color !default;        // Vibrant | $ui-highlight-color: $classic-highlight-color !default;        // Vibrant | ||||||
| 
 |  | ||||||
| // Avatar border size (8% default, 100% for rounded avatars) |  | ||||||
| $ui-avatar-border-size: 8%; |  | ||||||
|  |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| // glitch-soc added variables |  | ||||||
| 
 |  | ||||||
| $dismiss-overlay-width: 4rem; |  | ||||||
							
								
								
									
										661
									
								
								app/javascript/themes/glitch/actions/accounts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										661
									
								
								app/javascript/themes/glitch/actions/accounts.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,661 @@ | ||||||
|  | import api, { getLinks } from 'themes/glitch/util/api'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | ||||||
|  | export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | ||||||
|  | export const ACCOUNT_FETCH_FAIL    = 'ACCOUNT_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; | ||||||
|  | export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; | ||||||
|  | export const ACCOUNT_FOLLOW_FAIL    = 'ACCOUNT_FOLLOW_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST'; | ||||||
|  | export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS'; | ||||||
|  | export const ACCOUNT_UNFOLLOW_FAIL    = 'ACCOUNT_UNFOLLOW_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; | ||||||
|  | export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; | ||||||
|  | export const ACCOUNT_BLOCK_FAIL    = 'ACCOUNT_BLOCK_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST'; | ||||||
|  | export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; | ||||||
|  | export const ACCOUNT_UNBLOCK_FAIL    = 'ACCOUNT_UNBLOCK_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST'; | ||||||
|  | export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; | ||||||
|  | export const ACCOUNT_MUTE_FAIL    = 'ACCOUNT_MUTE_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; | ||||||
|  | export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; | ||||||
|  | export const ACCOUNT_UNMUTE_FAIL    = 'ACCOUNT_UNMUTE_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; | ||||||
|  | export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; | ||||||
|  | export const FOLLOWERS_FETCH_FAIL    = 'FOLLOWERS_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST'; | ||||||
|  | export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS'; | ||||||
|  | export const FOLLOWERS_EXPAND_FAIL    = 'FOLLOWERS_EXPAND_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; | ||||||
|  | export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; | ||||||
|  | export const FOLLOWING_FETCH_FAIL    = 'FOLLOWING_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST'; | ||||||
|  | export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; | ||||||
|  | export const FOLLOWING_EXPAND_FAIL    = 'FOLLOWING_EXPAND_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; | ||||||
|  | export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; | ||||||
|  | export const RELATIONSHIPS_FETCH_FAIL    = 'RELATIONSHIPS_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; | ||||||
|  | export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; | ||||||
|  | export const FOLLOW_REQUESTS_FETCH_FAIL    = 'FOLLOW_REQUESTS_FETCH_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; | ||||||
|  | export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; | ||||||
|  | export const FOLLOW_REQUESTS_EXPAND_FAIL    = 'FOLLOW_REQUESTS_EXPAND_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; | ||||||
|  | export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; | ||||||
|  | export const FOLLOW_REQUEST_AUTHORIZE_FAIL    = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; | ||||||
|  | 
 | ||||||
|  | export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; | ||||||
|  | export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; | ||||||
|  | export const FOLLOW_REQUEST_REJECT_FAIL    = 'FOLLOW_REQUEST_REJECT_FAIL'; | ||||||
|  | 
 | ||||||
|  | export function fetchAccount(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchRelationships([id])); | ||||||
|  | 
 | ||||||
|  |     if (getState().getIn(['accounts', id], null) !== null) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dispatch(fetchAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/${id}`).then(response => { | ||||||
|  |       dispatch(fetchAccountSuccess(response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchAccountFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FETCH_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchAccountSuccess(account) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FETCH_SUCCESS, | ||||||
|  |     account, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchAccountFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FETCH_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |     skipAlert: true, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function followAccount(id, reblogs = true) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     const alreadyFollowing = getState().getIn(['relationships', id, 'following']); | ||||||
|  |     dispatch(followAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { | ||||||
|  |       dispatch(followAccountSuccess(response.data, alreadyFollowing)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(followAccountFail(error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unfollowAccount(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(unfollowAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { | ||||||
|  |       dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(unfollowAccountFail(error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function followAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FOLLOW_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function followAccountSuccess(relationship, alreadyFollowing) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FOLLOW_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |     alreadyFollowing, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function followAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_FOLLOW_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unfollowAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNFOLLOW_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unfollowAccountSuccess(relationship, statuses) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNFOLLOW_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |     statuses, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unfollowAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNFOLLOW_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function blockAccount(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(blockAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { | ||||||
|  |       // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
 | ||||||
|  |       dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(blockAccountFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unblockAccount(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(unblockAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { | ||||||
|  |       dispatch(unblockAccountSuccess(response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(unblockAccountFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function blockAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_BLOCK_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function blockAccountSuccess(relationship, statuses) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_BLOCK_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |     statuses, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function blockAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_BLOCK_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unblockAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNBLOCK_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unblockAccountSuccess(relationship) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNBLOCK_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unblockAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNBLOCK_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function muteAccount(id, notifications) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(muteAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { | ||||||
|  |       // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
 | ||||||
|  |       dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(muteAccountFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unmuteAccount(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(unmuteAccountRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { | ||||||
|  |       dispatch(unmuteAccountSuccess(response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(unmuteAccountFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function muteAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_MUTE_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function muteAccountSuccess(relationship, statuses) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_MUTE_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |     statuses, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function muteAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_MUTE_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unmuteAccountRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNMUTE_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unmuteAccountSuccess(relationship) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNMUTE_SUCCESS, | ||||||
|  |     relationship, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function unmuteAccountFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: ACCOUNT_UNMUTE_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function fetchFollowers(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchFollowersRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  | 
 | ||||||
|  |       dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchFollowersFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowersFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_FETCH_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowers(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     const url = getState().getIn(['user_lists', 'followers', id, 'next']); | ||||||
|  | 
 | ||||||
|  |     if (url === null) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dispatch(expandFollowersRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(url).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  | 
 | ||||||
|  |       dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(expandFollowersFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowersRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_EXPAND_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowersSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_EXPAND_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowersFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWERS_EXPAND_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowing(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchFollowingRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  | 
 | ||||||
|  |       dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchFollowingFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowingFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_FETCH_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowing(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     const url = getState().getIn(['user_lists', 'following', id, 'next']); | ||||||
|  | 
 | ||||||
|  |     if (url === null) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dispatch(expandFollowingRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(url).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  | 
 | ||||||
|  |       dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); | ||||||
|  |       dispatch(fetchRelationships(response.data.map(item => item.id))); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(expandFollowingFail(id, error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowingRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_EXPAND_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowingSuccess(id, accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_EXPAND_SUCCESS, | ||||||
|  |     id, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowingFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOWING_EXPAND_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchRelationships(accountIds) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     const loadedRelationships = getState().get('relationships'); | ||||||
|  |     const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null); | ||||||
|  | 
 | ||||||
|  |     if (newAccountIds.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dispatch(fetchRelationshipsRequest(newAccountIds)); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { | ||||||
|  |       dispatch(fetchRelationshipsSuccess(response.data)); | ||||||
|  |     }).catch(error => { | ||||||
|  |       dispatch(fetchRelationshipsFail(error)); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchRelationshipsRequest(ids) { | ||||||
|  |   return { | ||||||
|  |     type: RELATIONSHIPS_FETCH_REQUEST, | ||||||
|  |     ids, | ||||||
|  |     skipLoading: true, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchRelationshipsSuccess(relationships) { | ||||||
|  |   return { | ||||||
|  |     type: RELATIONSHIPS_FETCH_SUCCESS, | ||||||
|  |     relationships, | ||||||
|  |     skipLoading: true, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchRelationshipsFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: RELATIONSHIPS_FETCH_FAIL, | ||||||
|  |     error, | ||||||
|  |     skipLoading: true, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowRequests() { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(fetchFollowRequestsRequest()); | ||||||
|  | 
 | ||||||
|  |     api(getState).get('/api/v1/follow_requests').then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  |       dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); | ||||||
|  |     }).catch(error => dispatch(fetchFollowRequestsFail(error))); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowRequestsRequest() { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_FETCH_REQUEST, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowRequestsSuccess(accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_FETCH_SUCCESS, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function fetchFollowRequestsFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_FETCH_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowRequests() { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     const url = getState().getIn(['user_lists', 'follow_requests', 'next']); | ||||||
|  | 
 | ||||||
|  |     if (url === null) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     dispatch(expandFollowRequestsRequest()); | ||||||
|  | 
 | ||||||
|  |     api(getState).get(url).then(response => { | ||||||
|  |       const next = getLinks(response).refs.find(link => link.rel === 'next'); | ||||||
|  |       dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); | ||||||
|  |     }).catch(error => dispatch(expandFollowRequestsFail(error))); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowRequestsRequest() { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_EXPAND_REQUEST, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowRequestsSuccess(accounts, next) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_EXPAND_SUCCESS, | ||||||
|  |     accounts, | ||||||
|  |     next, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function expandFollowRequestsFail(error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUESTS_EXPAND_FAIL, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function authorizeFollowRequest(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(authorizeFollowRequestRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState) | ||||||
|  |       .post(`/api/v1/follow_requests/${id}/authorize`) | ||||||
|  |       .then(() => dispatch(authorizeFollowRequestSuccess(id))) | ||||||
|  |       .catch(error => dispatch(authorizeFollowRequestFail(id, error))); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function authorizeFollowRequestRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function authorizeFollowRequestSuccess(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function authorizeFollowRequestFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_AUTHORIZE_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export function rejectFollowRequest(id) { | ||||||
|  |   return (dispatch, getState) => { | ||||||
|  |     dispatch(rejectFollowRequestRequest(id)); | ||||||
|  | 
 | ||||||
|  |     api(getState) | ||||||
|  |       .post(`/api/v1/follow_requests/${id}/reject`) | ||||||
|  |       .then(() => dispatch(rejectFollowRequestSuccess(id))) | ||||||
|  |       .catch(error => dispatch(rejectFollowRequestFail(id, error))); | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function rejectFollowRequestRequest(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_REJECT_REQUEST, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function rejectFollowRequestSuccess(id) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_REJECT_SUCCESS, | ||||||
|  |     id, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export function rejectFollowRequestFail(id, error) { | ||||||
|  |   return { | ||||||
|  |     type: FOLLOW_REQUEST_REJECT_FAIL, | ||||||
|  |     id, | ||||||
|  |     error, | ||||||
|  |   }; | ||||||
|  | }; | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
		Reference in a new issue