From 297921fce570bfab413bab4e16a4ae694ecc4f28 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Wed, 12 Jul 2017 01:02:51 -0700 Subject: Moved glitch files to their own location ;) --- .../glitch/components/compose/advanced_options.js | 112 +++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 app/javascript/glitch/components/compose/advanced_options.js (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/components/compose/advanced_options.js b/app/javascript/glitch/components/compose/advanced_options.js new file mode 100644 index 000000000..0e72bd053 --- /dev/null +++ b/app/javascript/glitch/components/compose/advanced_options.js @@ -0,0 +1,112 @@ +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Toggle from 'react-toggle'; +import { injectIntl, defineMessages } from 'react-intl'; + +// Mastodon imports // +import IconButton from '../../../mastodon/components/icon_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' }, +}); + +const iconStyle = { + height: null, + lineHeight: '27px', +}; + +@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, + }; + + onToggleDropdown = () => { + this.setState({ open: !this.state.open }); + }; + + onGlobalClick = (e) => { + if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { + this.setState({ open: false }); + } + } + + componentDidMount () { + window.addEventListener('click', this.onGlobalClick); + window.addEventListener('touchstart', this.onGlobalClick); + } + + componentWillUnmount () { + window.removeEventListener('click', this.onGlobalClick); + window.removeEventListener('touchstart', this.onGlobalClick); + } + + state = { + open: false, + }; + + handleClick = (e) => { + const option = e.currentTarget.getAttribute('data-index'); + e.preventDefault(); + this.props.onChange(option); + } + + toggleHandler(option) { + return () => this.props.onChange(option); + } + + setRef = (c) => { + this.node = c; + } + + render () { + const { open } = this.state; + const { intl, values } = this.props; + + const options = [ + { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, + ]; + + const anyEnabled = values.some((enabled) => enabled); + const optionElems = options.map((option) => { + const active = values.get(option.key); + return ( +
+
+ +
+
+ {intl.formatMessage(option.shortText)} + {intl.formatMessage(option.longText)} +
+
+ ); + }); + + return (
+
+ +
+
+ {optionElems} +
+
); + } + +} -- cgit From e53fbb4a09ef0b53236840352f3a800b76b2f961 Mon Sep 17 00:00:00 2001 From: Surinna Curtis Date: Wed, 12 Jul 2017 00:50:50 -0500 Subject: local-only/compose advanced options tweaks. Squashed commit of the following: commit b9877e37f72fdd8134936e1014033b07cb6c3671 Author: Surinna Curtis Date: Wed Jul 12 00:50:10 2017 -0500 account for the eye in the chars left count for local-only toots commit 56ebfa96542e16daa1986cc45e07974801ee12dc Author: Surinna Curtis Date: Wed Jul 12 00:21:02 2017 -0500 factor out an AdvancedOptionsToggle to avoid unnecessary re-renders commit 04cec44ab8744e4e0f52da488c9ec24b1b1422ef Author: Surinna Curtis Date: Wed Jul 12 00:20:24 2017 -0500 s/changeComposeAdvancedOption/toggleComposeAdvancedOption/g commit af5815dee750d1aa8b797a9305e5ab3ce6774e3f Author: Surinna Curtis Date: Tue Jul 11 23:55:19 2017 -0500 clicking anywhere on the whole advanced option toggles --- .../glitch/components/compose/advanced_options.js | 51 ++++++--- .../glitch/containers/compose/advanced_options.js | 4 +- app/javascript/mastodon/actions/compose.js | 2 +- .../features/compose/components/compose_form.js | 3 +- .../mastodon/features/ui/util/async-components.js | 118 +++++++++++++++++++++ 5 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 app/javascript/mastodon/features/ui/util/async-components.js (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/components/compose/advanced_options.js b/app/javascript/glitch/components/compose/advanced_options.js index 0e72bd053..0d22f2546 100644 --- a/app/javascript/glitch/components/compose/advanced_options.js +++ b/app/javascript/glitch/components/compose/advanced_options.js @@ -19,6 +19,35 @@ const iconStyle = { lineHeight: '27px', }; +class AdvancedOptionToggle extends React.PureComponent { + static PropTypes = { + onChange: PropTypes.func.isRequired, + active: PropTypes.bool.isRequired, + key: PropTypes.string.isRequired, + shortText: PropTypes.string.isRequired, + longText: PropTypes.string.isRequired, + } + + onToggle = () => { + this.props.onChange(this.props.name); + } + + render() { + const { active, shortText, longText } = this.props; + return ( +
+
+ +
+
+ {shortText} + {longText} +
+
+ ); + } +} + @injectIntl export default class ComposeAdvancedOptions extends React.PureComponent { @@ -60,10 +89,6 @@ export default class ComposeAdvancedOptions extends React.PureComponent { this.props.onChange(option); } - toggleHandler(option) { - return () => this.props.onChange(option); - } - setRef = (c) => { this.node = c; } @@ -78,17 +103,15 @@ export default class ComposeAdvancedOptions extends React.PureComponent { const anyEnabled = values.some((enabled) => enabled); const optionElems = options.map((option) => { - const active = values.get(option.key); return ( -
-
- -
-
- {intl.formatMessage(option.shortText)} - {intl.formatMessage(option.longText)} -
-
+ ); }); diff --git a/app/javascript/glitch/containers/compose/advanced_options.js b/app/javascript/glitch/containers/compose/advanced_options.js index f8b7354ff..92b989052 100644 --- a/app/javascript/glitch/containers/compose/advanced_options.js +++ b/app/javascript/glitch/containers/compose/advanced_options.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; // Mastodon imports // -import { changeComposeAdvancedOption } from '../../../mastodon/actions/compose'; +import { toggleComposeAdvancedOption } from '../../../mastodon/actions/compose'; // Our imports // import ComposeAdvancedOptions from '../../components/compose/advanced_options'; @@ -14,7 +14,7 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ onChange (option) { - dispatch(changeComposeAdvancedOption(option)); + dispatch(toggleComposeAdvancedOption(option)); }, }); diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index f00b213f0..bce836b45 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -248,7 +248,7 @@ export function unmountCompose() { }; }; -export function changeComposeAdvancedOption(option) { +export function toggleComposeAdvancedOption(option) { return { type: COMPOSE_ADVANCED_OPTIONS_CHANGE, option: option, diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 313635443..4b14f8c81 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -147,7 +147,8 @@ export default class ComposeForm extends ImmutablePureComponent { render () { const { intl, onPaste, showSearch } = this.props; const disabled = this.props.is_submitting; - const text = [this.props.spoiler_text, this.props.text].join(''); + const maybeEye = this.props.advanced_options.get('do_not_federate') ? ' 👁️' : ''; + const text = [this.props.spoiler_text, this.props.text, maybeEye].join(''); let publishText = ''; diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js new file mode 100644 index 000000000..8c9b1bd97 --- /dev/null +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -0,0 +1,118 @@ +export function EmojiPicker () { + return import(/* webpackChunkName: "emojione_picker" */'emojione-picker'); +} + +export function Compose () { + return import(/* webpackChunkName: "features/compose" */'../../compose'); +} + +export function Notifications () { + return import(/* webpackChunkName: "features/notifications" */'../../notifications'); +} + +export function HomeTimeline () { + return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline'); +} + +export function PublicTimeline () { + return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline'); +} + +export function CommunityTimeline () { + return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline'); +} + +export function HashtagTimeline () { + return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline'); +} + +export function Status () { + return import(/* webpackChunkName: "features/status" */'../../status'); +} + +export function GettingStarted () { + return import(/* webpackChunkName: "features/getting_started" */'../../getting_started'); +} + +export function AccountTimeline () { + return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline'); +} + +export function AccountGallery () { + return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery'); +} + +export function Followers () { + return import(/* webpackChunkName: "features/followers" */'../../followers'); +} + +export function Following () { + return import(/* webpackChunkName: "features/following" */'../../following'); +} + +export function Reblogs () { + return import(/* webpackChunkName: "features/reblogs" */'../../reblogs'); +} + +export function Favourites () { + return import(/* webpackChunkName: "features/favourites" */'../../favourites'); +} + +export function FollowRequests () { + return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests'); +} + +export function GenericNotFound () { + return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found'); +} + +export function FavouritedStatuses () { + return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses'); +} + +export function Blocks () { + return import(/* webpackChunkName: "features/blocks" */'../../blocks'); +} + +export function Mutes () { + return import(/* webpackChunkName: "features/mutes" */'../../mutes'); +} + +export function MediaModal () { + return import(/* webpackChunkName: "modals/media_modal" */'../components/media_modal'); +} + +export function OnboardingModal () { + return import(/* webpackChunkName: "modals/onboarding_modal" */'../components/onboarding_modal'); +} + +export function VideoModal () { + return import(/* webpackChunkName: "modals/video_modal" */'../components/video_modal'); +} + +export function BoostModal () { + return import(/* webpackChunkName: "modals/boost_modal" */'../components/boost_modal'); +} + +export function ConfirmationModal () { + return import(/* webpackChunkName: "modals/confirmation_modal" */'../components/confirmation_modal'); +} + +export function ReportModal () { + return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal'); +} + +export function SettingsModal () { + return import(/* webpackChunkName: "modals/settings_modal" */'../../../../glitch/containers/settings'); +} + +// THESE AREN'T USED BY US; SEE `glitch/components/status` AND `mastodon/features/status`. // +// HOWEVER, IF MASTODON EVER CHANGES DETAILED STATUSES SO THAT THEY NEED THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL. // + +export function MediaGallery () { + return import(/* webpackChunkName: "status/MediaGallery" */'../../../components/media_gallery'); +} + +export function VideoPlayer () { + return import(/* webpackChunkName: "status/VideoPlayer" */'../../../components/video_player'); +} -- cgit From 183f993b018dfa2f4d4886f5c211e6a9968f4eb6 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Wed, 12 Jul 2017 02:36:40 -0700 Subject: Linting fixes --- .../glitch/components/compose/advanced_options.js | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/components/compose/advanced_options.js b/app/javascript/glitch/components/compose/advanced_options.js index 0d22f2546..17fc1d801 100644 --- a/app/javascript/glitch/components/compose/advanced_options.js +++ b/app/javascript/glitch/components/compose/advanced_options.js @@ -20,10 +20,11 @@ const iconStyle = { }; class AdvancedOptionToggle extends React.PureComponent { - static PropTypes = { + + static propTypes = { onChange: PropTypes.func.isRequired, active: PropTypes.bool.isRequired, - key: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, shortText: PropTypes.string.isRequired, longText: PropTypes.string.isRequired, } @@ -34,18 +35,19 @@ class AdvancedOptionToggle extends React.PureComponent { render() { const { active, shortText, longText } = this.props; - return ( -
-
- -
-
- {shortText} - {longText} -
+ return ( +
+
+
- ); +
+ {shortText} + {longText} +
+
+ ); } + } @injectIntl @@ -98,7 +100,7 @@ export default class ComposeAdvancedOptions extends React.PureComponent { const { intl, values } = this.props; const options = [ - { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, + { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, ]; const anyEnabled = values.some((enabled) => enabled); -- cgit From 5770d461b21cf5b6a8adcaa44d19832e11289960 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Thu, 13 Jul 2017 02:40:16 -0700 Subject: Moved glitch containers and commented unused files --- .../glitch/components/compose/advanced_options.js | 137 ----------- .../compose/advanced_options/container.js | 22 ++ .../components/compose/advanced_options/index.js | 137 +++++++++++ .../glitch/components/notification/container.js | 21 ++ .../glitch/components/notification/index.js | 2 +- .../glitch/components/settings/container.js | 27 +++ .../glitch/components/status/container.js | 252 +++++++++++++++++++++ app/javascript/glitch/components/status/index.js | 4 +- app/javascript/glitch/components/status/player.js | 199 ++++++++++++++++ .../glitch/components/status/video_player.js | 199 ---------------- .../glitch/containers/compose/advanced_options.js | 22 -- .../glitch/containers/notification/index.js | 21 -- app/javascript/glitch/containers/settings/index.js | 27 --- app/javascript/glitch/containers/status/index.js | 252 --------------------- .../mastodon/components/media_gallery.js | 3 + app/javascript/mastodon/components/status.js | 3 + .../mastodon/components/status_action_bar.js | 3 + .../mastodon/components/status_content.js | 3 + app/javascript/mastodon/components/status_list.js | 2 +- app/javascript/mastodon/components/video_player.js | 3 + .../mastodon/containers/status_container.js | 3 + .../mastodon/features/account/components/header.js | 3 + .../features/compose/components/compose_form.js | 2 +- .../features/compose/components/search_results.js | 2 +- .../notifications/components/notification.js | 3 + .../containers/notification_container.js | 3 + .../mastodon/features/notifications/index.js | 2 +- .../features/status/components/detailed_status.js | 4 +- app/javascript/mastodon/features/status/index.js | 2 +- .../mastodon/features/ui/util/async-components.js | 4 +- 30 files changed, 697 insertions(+), 670 deletions(-) delete mode 100644 app/javascript/glitch/components/compose/advanced_options.js create mode 100644 app/javascript/glitch/components/compose/advanced_options/container.js create mode 100644 app/javascript/glitch/components/compose/advanced_options/index.js create mode 100644 app/javascript/glitch/components/notification/container.js create mode 100644 app/javascript/glitch/components/settings/container.js create mode 100644 app/javascript/glitch/components/status/container.js create mode 100644 app/javascript/glitch/components/status/player.js delete mode 100644 app/javascript/glitch/components/status/video_player.js delete mode 100644 app/javascript/glitch/containers/compose/advanced_options.js delete mode 100644 app/javascript/glitch/containers/notification/index.js delete mode 100644 app/javascript/glitch/containers/settings/index.js delete mode 100644 app/javascript/glitch/containers/status/index.js (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/components/compose/advanced_options.js b/app/javascript/glitch/components/compose/advanced_options.js deleted file mode 100644 index 17fc1d801..000000000 --- a/app/javascript/glitch/components/compose/advanced_options.js +++ /dev/null @@ -1,137 +0,0 @@ -// Package imports // -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Toggle from 'react-toggle'; -import { injectIntl, defineMessages } from 'react-intl'; - -// Mastodon imports // -import IconButton from '../../../mastodon/components/icon_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' }, -}); - -const iconStyle = { - height: null, - lineHeight: '27px', -}; - -class AdvancedOptionToggle 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 = () => { - this.props.onChange(this.props.name); - } - - render() { - const { active, shortText, longText } = this.props; - return ( -
-
- -
-
- {shortText} - {longText} -
-
- ); - } - -} - -@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, - }; - - onToggleDropdown = () => { - this.setState({ open: !this.state.open }); - }; - - onGlobalClick = (e) => { - if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { - this.setState({ open: false }); - } - } - - componentDidMount () { - window.addEventListener('click', this.onGlobalClick); - window.addEventListener('touchstart', this.onGlobalClick); - } - - componentWillUnmount () { - window.removeEventListener('click', this.onGlobalClick); - window.removeEventListener('touchstart', this.onGlobalClick); - } - - state = { - open: false, - }; - - handleClick = (e) => { - const option = e.currentTarget.getAttribute('data-index'); - e.preventDefault(); - this.props.onChange(option); - } - - setRef = (c) => { - this.node = c; - } - - render () { - const { open } = this.state; - const { intl, values } = this.props; - - const options = [ - { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, - ]; - - const anyEnabled = values.some((enabled) => enabled); - const optionElems = options.map((option) => { - return ( - - ); - }); - - return (
-
- -
-
- {optionElems} -
-
); - } - -} diff --git a/app/javascript/glitch/components/compose/advanced_options/container.js b/app/javascript/glitch/components/compose/advanced_options/container.js new file mode 100644 index 000000000..10804454a --- /dev/null +++ b/app/javascript/glitch/components/compose/advanced_options/container.js @@ -0,0 +1,22 @@ +// Package imports // +import { connect } from 'react-redux'; + +// Mastodon imports // +import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compose'; + +// Our imports // +import ComposeAdvancedOptions from '.'; + +const mapStateToProps = state => ({ + values: state.getIn(['compose', 'advanced_options']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (option) { + dispatch(toggleComposeAdvancedOption(option)); + }, + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions); diff --git a/app/javascript/glitch/components/compose/advanced_options/index.js b/app/javascript/glitch/components/compose/advanced_options/index.js new file mode 100644 index 000000000..dabf66095 --- /dev/null +++ b/app/javascript/glitch/components/compose/advanced_options/index.js @@ -0,0 +1,137 @@ +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Toggle from 'react-toggle'; +import { injectIntl, defineMessages } from 'react-intl'; + +// Mastodon imports // +import IconButton from '../../../../mastodon/components/icon_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' }, +}); + +const iconStyle = { + height: null, + lineHeight: '27px', +}; + +class AdvancedOptionToggle 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 = () => { + this.props.onChange(this.props.name); + } + + render() { + const { active, shortText, longText } = this.props; + return ( +
+
+ +
+
+ {shortText} + {longText} +
+
+ ); + } + +} + +@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, + }; + + onToggleDropdown = () => { + this.setState({ open: !this.state.open }); + }; + + onGlobalClick = (e) => { + if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { + this.setState({ open: false }); + } + } + + componentDidMount () { + window.addEventListener('click', this.onGlobalClick); + window.addEventListener('touchstart', this.onGlobalClick); + } + + componentWillUnmount () { + window.removeEventListener('click', this.onGlobalClick); + window.removeEventListener('touchstart', this.onGlobalClick); + } + + state = { + open: false, + }; + + handleClick = (e) => { + const option = e.currentTarget.getAttribute('data-index'); + e.preventDefault(); + this.props.onChange(option); + } + + setRef = (c) => { + this.node = c; + } + + render () { + const { open } = this.state; + const { intl, values } = this.props; + + const options = [ + { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, + ]; + + const anyEnabled = values.some((enabled) => enabled); + const optionElems = options.map((option) => { + return ( + + ); + }); + + return (
+
+ +
+
+ {optionElems} +
+
); + } + +} diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js new file mode 100644 index 000000000..c58ef4bd2 --- /dev/null +++ b/app/javascript/glitch/components/notification/container.js @@ -0,0 +1,21 @@ +// Package imports // +import { connect } from 'react-redux'; + +// Mastodon imports // +import { makeGetNotification } from '../../../mastodon/selectors'; + +// Our imports // +import Notification from '.'; + +const makeMapStateToProps = () => { + const getNotification = makeGetNotification(); + + const mapStateToProps = (state, props) => ({ + notification: getNotification(state, props.notification, props.accountId), + settings: state.get('local_settings'), + }); + + return mapStateToProps; +}; + +export default connect(makeMapStateToProps)(Notification); diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 3f424d85d..34e03cdca 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -11,7 +11,7 @@ import Permalink from '../../../mastodon/components/permalink'; import emojify from '../../../mastodon/emoji'; // Our imports // -import StatusContainer from '../../containers/status'; +import StatusContainer from '../status'; export default class Notification extends ImmutablePureComponent { diff --git a/app/javascript/glitch/components/settings/container.js b/app/javascript/glitch/components/settings/container.js new file mode 100644 index 000000000..6034935eb --- /dev/null +++ b/app/javascript/glitch/components/settings/container.js @@ -0,0 +1,27 @@ +// Package imports // +import { connect } from 'react-redux'; + +// Mastodon imports // +import { closeModal } from '../../../mastodon/actions/modal'; + +// Our imports // +import { changeLocalSetting } from '../../actions/local_settings'; +import Settings from '../../components/settings'; + +const mapStateToProps = state => ({ + settings: state.get('local_settings'), +}); + +const mapDispatchToProps = dispatch => ({ + toggleSetting (setting, e) { + dispatch(changeLocalSetting(setting, e.target.checked)); + }, + changeSetting (setting, e) { + dispatch(changeLocalSetting(setting, e.target.value)); + }, + onClose () { + dispatch(closeModal()); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Settings); diff --git a/app/javascript/glitch/components/status/container.js b/app/javascript/glitch/components/status/container.js new file mode 100644 index 000000000..a8aa6efe9 --- /dev/null +++ b/app/javascript/glitch/components/status/container.js @@ -0,0 +1,252 @@ +/* + +`` +=================== + +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 . + +*/ + + /* * * * */ + +/* + +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, +} from '../../../mastodon/actions/interactions'; +import { + blockAccount, + muteAccount, +} from '../../../mastodon/actions/accounts'; +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', + }, + muteConfirm : { + id : 'confirmations.mute.confirm', + defaultMessage : 'Mute', + }, +}); + + /* * * * */ + +/* + +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); + 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 `` 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 ``. + +*/ + + return { + status : status, + account : account || ownProps.account, + me : state.getIn(['meta', 'me']), + settings : state.get('local_settings'), + prepend : prepend || ownProps.prepend, + reblogModal : state.getIn(['meta', 'boost_modal']), + deleteModal : state.getIn(['meta', 'delete_modal']), + autoPlayGif : state.getIn(['meta', 'auto_play_gif']), + }; + }; + + 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)); + } + }, + + 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: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.blockConfirm), + onConfirm: () => dispatch(blockAccount(account.get('id'))), + })); + }, + + onReport (status) { + dispatch(initReport(status.get('account'), status)); + }, + + onMute (account) { + dispatch(openModal('CONFIRM', { + message: @{account.get('acct')} }} />, + confirm: intl.formatMessage(messages.muteConfirm), + onConfirm: () => dispatch(muteAccount(account.get('id'))), + })); + }, + + onMuteConversation (status) { + if (status.get('muted')) { + dispatch(unmuteStatus(status.get('id'))); + } else { + dispatch(muteStatus(status.get('id'))); + } + }, + +}); + +export default injectIntl( + connect(makeMapStateToProps, mapDispatchToProps)(Status) +); diff --git a/app/javascript/glitch/components/status/index.js b/app/javascript/glitch/components/status/index.js index 12ea0e9aa..1d135754a 100644 --- a/app/javascript/glitch/components/status/index.js +++ b/app/javascript/glitch/components/status/index.js @@ -46,7 +46,7 @@ import StatusHeader from './header'; import StatusContent from './content'; import StatusActionBar from './action_bar'; import StatusGallery from './gallery'; -import StatusVideoPlayer from './video_player'; +import StatusPlayer from './player'; /* * * * */ @@ -619,7 +619,7 @@ backgrounds for collapsed statuses are enabled. attachments.getIn([0, 'type']) === 'video' ) { media = ( // Media type is 'video' - { + 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 = ( +
+ +
+ ); + + let expandButton = ( +
+ +
+ ); + + let muteButton = ''; + + if (this.state.hasAudio) { + muteButton = ( +
+ +
+ ); + } + + if (!this.state.visible) { + if (sensitive) { + return ( +
+ {spoilerButton} + + +
+ ); + } else { + return ( +
+ {spoilerButton} + + +
+ ); + } + } + + if (this.state.preview && !autoplay) { + return ( +
+ {spoilerButton} +
+
+ ); + } + + if (this.state.videoError) { + return ( +
+ +
+ ); + } + + return ( +
+ {spoilerButton} + {muteButton} + {expandButton} + +
+ ); + } + +} diff --git a/app/javascript/glitch/components/status/video_player.js b/app/javascript/glitch/components/status/video_player.js deleted file mode 100644 index 6583107c8..000000000 --- a/app/javascript/glitch/components/status/video_player.js +++ /dev/null @@ -1,199 +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 StatusVideoPlayer extends React.PureComponent { - - 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 = ( -
- -
- ); - - let expandButton = ( -
- -
- ); - - let muteButton = ''; - - if (this.state.hasAudio) { - muteButton = ( -
- -
- ); - } - - if (!this.state.visible) { - if (sensitive) { - return ( -
- {spoilerButton} - - -
- ); - } else { - return ( -
- {spoilerButton} - - -
- ); - } - } - - if (this.state.preview && !autoplay) { - return ( -
- {spoilerButton} -
-
- ); - } - - if (this.state.videoError) { - return ( -
- -
- ); - } - - return ( -
- {spoilerButton} - {muteButton} - {expandButton} - -
- ); - } - -} diff --git a/app/javascript/glitch/containers/compose/advanced_options.js b/app/javascript/glitch/containers/compose/advanced_options.js deleted file mode 100644 index 92b989052..000000000 --- a/app/javascript/glitch/containers/compose/advanced_options.js +++ /dev/null @@ -1,22 +0,0 @@ -// Package imports // -import { connect } from 'react-redux'; - -// Mastodon imports // -import { toggleComposeAdvancedOption } from '../../../mastodon/actions/compose'; - -// Our imports // -import ComposeAdvancedOptions from '../../components/compose/advanced_options'; - -const mapStateToProps = state => ({ - values: state.getIn(['compose', 'advanced_options']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (option) { - dispatch(toggleComposeAdvancedOption(option)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions); diff --git a/app/javascript/glitch/containers/notification/index.js b/app/javascript/glitch/containers/notification/index.js deleted file mode 100644 index 783c838ae..000000000 --- a/app/javascript/glitch/containers/notification/index.js +++ /dev/null @@ -1,21 +0,0 @@ -// Package imports // -import { connect } from 'react-redux'; - -// Mastodon imports // -import { makeGetNotification } from '../../../mastodon/selectors'; - -// Our imports // -import Notification from '../../components/notification'; - -const makeMapStateToProps = () => { - const getNotification = makeGetNotification(); - - const mapStateToProps = (state, props) => ({ - notification: getNotification(state, props.notification, props.accountId), - settings: state.get('local_settings'), - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(Notification); diff --git a/app/javascript/glitch/containers/settings/index.js b/app/javascript/glitch/containers/settings/index.js deleted file mode 100644 index 6034935eb..000000000 --- a/app/javascript/glitch/containers/settings/index.js +++ /dev/null @@ -1,27 +0,0 @@ -// Package imports // -import { connect } from 'react-redux'; - -// Mastodon imports // -import { closeModal } from '../../../mastodon/actions/modal'; - -// Our imports // -import { changeLocalSetting } from '../../actions/local_settings'; -import Settings from '../../components/settings'; - -const mapStateToProps = state => ({ - settings: state.get('local_settings'), -}); - -const mapDispatchToProps = dispatch => ({ - toggleSetting (setting, e) { - dispatch(changeLocalSetting(setting, e.target.checked)); - }, - changeSetting (setting, e) { - dispatch(changeLocalSetting(setting, e.target.value)); - }, - onClose () { - dispatch(closeModal()); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Settings); diff --git a/app/javascript/glitch/containers/status/index.js b/app/javascript/glitch/containers/status/index.js deleted file mode 100644 index baf898e97..000000000 --- a/app/javascript/glitch/containers/status/index.js +++ /dev/null @@ -1,252 +0,0 @@ -/* - -`` -=================== - -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 . - -*/ - - /* * * * */ - -/* - -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, -} from '../../../mastodon/actions/interactions'; -import { - blockAccount, - muteAccount, -} from '../../../mastodon/actions/accounts'; -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 '../../components/status'; - - /* * * * */ - -/* - -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', - }, - muteConfirm : { - id : 'confirmations.mute.confirm', - defaultMessage : 'Mute', - }, -}); - - /* * * * */ - -/* - -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); - 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 `` 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 ``. - -*/ - - return { - status : status, - account : account || ownProps.account, - me : state.getIn(['meta', 'me']), - settings : state.get('local_settings'), - prepend : prepend || ownProps.prepend, - reblogModal : state.getIn(['meta', 'boost_modal']), - deleteModal : state.getIn(['meta', 'delete_modal']), - autoPlayGif : state.getIn(['meta', 'auto_play_gif']), - }; - }; - - 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)); - } - }, - - 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: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.blockConfirm), - onConfirm: () => dispatch(blockAccount(account.get('id'))), - })); - }, - - onReport (status) { - dispatch(initReport(status.get('account'), status)); - }, - - onMute (account) { - dispatch(openModal('CONFIRM', { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.muteConfirm), - onConfirm: () => dispatch(muteAccount(account.get('id'))), - })); - }, - - onMuteConversation (status) { - if (status.get('muted')) { - dispatch(unmuteStatus(status.get('id'))); - } else { - dispatch(muteStatus(status.get('id'))); - } - }, - -}); - -export default injectIntl( - connect(makeMapStateToProps, mapDispatchToProps)(Status) -); diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js index 89a358e38..680a4e58a 100644 --- a/app/javascript/mastodon/components/media_gallery.js +++ b/app/javascript/mastodon/components/media_gallery.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status/gallery + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 6b9fdd2af..6605457f7 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 7bb394e71..2fad0fa5a 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status/action_bar + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index 1b803a22e..ad925edef 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status/content + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import escapeTextContentForBrowser from 'escape-html'; diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index b5c6c2053..94b348f25 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { ScrollContainer } from 'react-router-scroll'; import PropTypes from 'prop-types'; -import StatusContainer from '../../glitch/containers/status'; +import StatusContainer from '../../glitch/components/status/container'; import LoadMore from './load_more'; import ImmutablePureComponent from 'react-immutable-pure-component'; import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; diff --git a/app/javascript/mastodon/components/video_player.js b/app/javascript/mastodon/components/video_player.js index 999cf42d9..5f2447c6d 100644 --- a/app/javascript/mastodon/components/video_player.js +++ b/app/javascript/mastodon/components/video_player.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status/player + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 438ecfe43..9b7f984e0 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/status/container + import React from 'react'; import { connect } from 'react-redux'; import Status from '../components/status'; diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 3239b1085..1133e8a4e 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/account/header + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 4b14f8c81..de5b09834 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -11,7 +11,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; -import ComposeAdvancedOptionsContainer from '../../../../glitch/containers/compose/advanced_options'; +import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; import EmojiPickerDropdown from './emoji_picker_dropdown'; import UploadFormContainer from '../containers/upload_form_container'; diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 3d1c3ca5e..cae4ca412 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; -import StatusContainer from '../../../../glitch/containers/status'; +import StatusContainer from '../../../../glitch/components/status/container'; import Link from 'react-router-dom/Link'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js index 9d631644a..0771849c2 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.js +++ b/app/javascript/mastodon/features/notifications/components/notification.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/notification + import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContainer from '../../../containers/status_container'; diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js index 786222967..1f98a66d2 100644 --- a/app/javascript/mastodon/features/notifications/containers/notification_container.js +++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js @@ -1,3 +1,6 @@ +// THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !! +// SEE INSTEAD : glitch/components/notification/container + import { connect } from 'react-redux'; import { makeGetNotification } from '../../../selectors'; import Notification from '../components/notification'; diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js index fb8f0d3cc..39fb4b26d 100644 --- a/app/javascript/mastodon/features/notifications/index.js +++ b/app/javascript/mastodon/features/notifications/index.js @@ -6,7 +6,7 @@ import Column from '../../components/column'; import ColumnHeader from '../../components/column_header'; import { expandNotifications, scrollTopNotifications } from '../../actions/notifications'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; -import NotificationContainer from '../../../glitch/containers/notification'; +import NotificationContainer from '../../../glitch/components/notification/container'; import { ScrollContainer } from 'react-router-scroll'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index db0181f23..67d1e822d 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -5,7 +5,7 @@ import Avatar from '../../../components/avatar'; import DisplayName from '../../../components/display_name'; import StatusContent from '../../../../glitch/components/status/content'; import StatusGallery from '../../../../glitch/components/status/gallery'; -import StatusVideoPlayer from '../../../../glitch/components/status/video_player'; +import StatusPlayer from '../../../../glitch/components/status/player'; import AttachmentList from '../../../components/attachment_list'; import Link from 'react-router-dom/Link'; import { FormattedDate, FormattedNumber } from 'react-intl'; @@ -48,7 +48,7 @@ export default class DetailedStatus extends ImmutablePureComponent { media = ; } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { media = ( - Date: Fri, 14 Jul 2017 11:13:02 -0700 Subject: Documentation and cleanup --- app/javascript/glitch/actions/local_settings.js | 18 +- app/javascript/glitch/components/account/header.js | 163 ++++++++++++-- .../compose/advanced_options/container.js | 44 ++++ .../components/compose/advanced_options/index.js | 236 +++++++++++++++------ .../components/compose/advanced_options/toggle.js | 103 +++++++++ .../glitch/components/notification/container.js | 47 +++- .../glitch/components/notification/follow.js | 171 +++++++++++++++ .../components/notification/follow_notification.js | 78 ------- .../glitch/components/notification/index.js | 6 +- app/javascript/glitch/reducers/local_settings.js | 24 +-- app/javascript/glitch/util/bio_metadata.js | 4 +- 11 files changed, 705 insertions(+), 189 deletions(-) create mode 100644 app/javascript/glitch/components/compose/advanced_options/toggle.js create mode 100644 app/javascript/glitch/components/notification/follow.js delete mode 100644 app/javascript/glitch/components/notification/follow_notification.js (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/actions/local_settings.js b/app/javascript/glitch/actions/local_settings.js index 479b5841d..93c5a9a17 100644 --- a/app/javascript/glitch/actions/local_settings.js +++ b/app/javascript/glitch/actions/local_settings.js @@ -21,12 +21,12 @@ consists of the following: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Constants ---------- +Constants: +---------- We provide the following constants: @@ -39,12 +39,12 @@ We provide the following constants: export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`changeLocalSetting(key, value)` --------------------------------- +`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 @@ -67,12 +67,12 @@ export function changeLocalSetting(key, value) { }; }; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`saveLocalSettings()` ---------------------- +`saveLocalSettings()`: +---------------------- Saves the local settings to `localStorage` as a JSON object. `changeLocalSetting()` calls this whenever it changes a setting. We diff --git a/app/javascript/glitch/components/account/header.js b/app/javascript/glitch/components/account/header.js index e2d961240..b79140c02 100644 --- a/app/javascript/glitch/components/account/header.js +++ b/app/javascript/glitch/components/account/header.js @@ -1,3 +1,45 @@ +/* + +`` +================= + +> 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 `` 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'; @@ -14,25 +56,63 @@ import Avatar from '../../../mastodon/components/avatar'; // 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 Header extends ImmutablePureComponent { +export default class AccountHeader extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + account : ImmutablePropTypes.map, + me : PropTypes.number.isRequired, + onFollow : PropTypes.func.isRequired, + intl : PropTypes.object.isRequired, }; +/* + +### `render()` + +The `render()` function is used to render our component. + +*/ + render () { const { account, me, 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; } @@ -40,17 +120,30 @@ export default class Header extends ImmutablePureComponent { let displayName = account.get('display_name'); let info = ''; let actionBtn = ''; - let lockedIcon = ''; + let following = false; if (displayName.length === 0) { displayName = account.get('username'); } - if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { - info = ; - } +/* + +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 = ( + + + + ); + } if (account.getIn(['relationship', 'requested'])) { actionBtn = (
@@ -58,30 +151,64 @@ export default class Header extends ImmutablePureComponent {
); } else if (!account.getIn(['relationship', 'blocking'])) { + following = account.getIn(['relationship', 'following']); actionBtn = (
- +
); } } - if (account.get('locked')) { - lockedIcon = ; - } +/* - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; +`displayNameHTML` processes the `displayName` and prepares it for +insertion into the document. Meanwhile, we extract the `text` and +`metadata` from our account's `note` using `processBio()`. + +*/ + + const displayNameHTML = { + __html : emojify(escapeTextContentForBrowser(displayName)), + }; const { text, metadata } = processBio(account.get('note')); +/* + +Here, we render our component using all the things we've defined above. + +*/ + return (
-
+
- - + + + + - @{account.get('acct')} {lockedIcon} + + @{account.get('acct')} + {account.get('locked') ? : null} +
{info} diff --git a/app/javascript/glitch/components/compose/advanced_options/container.js b/app/javascript/glitch/components/compose/advanced_options/container.js index 10804454a..160f22737 100644 --- a/app/javascript/glitch/components/compose/advanced_options/container.js +++ b/app/javascript/glitch/components/compose/advanced_options/container.js @@ -1,3 +1,21 @@ +/* + +`` +=================================== + +This container connects `` to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import { connect } from 'react-redux'; @@ -7,10 +25,36 @@ import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compos // 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) { diff --git a/app/javascript/glitch/components/compose/advanced_options/index.js b/app/javascript/glitch/components/compose/advanced_options/index.js index dabf66095..b745d1cdf 100644 --- a/app/javascript/glitch/components/compose/advanced_options/index.js +++ b/app/javascript/glitch/components/compose/advanced_options/index.js @@ -1,137 +1,241 @@ +/* + +`` +========================== + +> 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 Toggle from 'react-toggle'; import { injectIntl, defineMessages } from 'react-intl'; // Mastodon imports // import IconButton from '../../../../mastodon/components/icon_button'; +// Our imports // +import ComposeAdvancedOptionsToggle from './toggle'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +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' }, + 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' }, }); const iconStyle = { - height: null, - lineHeight: '27px', + height : null, + lineHeight : '27px', }; -class AdvancedOptionToggle 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 = () => { - this.props.onChange(this.props.name); - } +/* - render() { - const { active, shortText, longText } = this.props; - return ( -
-
- -
-
- {shortText} - {longText} -
-
- ); - } +Implementation: +--------------- -} +*/ @injectIntl export default class ComposeAdvancedOptions extends React.PureComponent { static propTypes = { - values: ImmutablePropTypes.contains({ - do_not_federate: PropTypes.bool.isRequired, + values : ImmutablePropTypes.contains({ + do_not_federate : PropTypes.bool.isRequired, }).isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + onChange : PropTypes.func.isRequired, + intl : PropTypes.object.isRequired, }; + state = { + open: false, + }; + +/* + +### `onToggleDropdown()` + +This function toggles the opening and closing of the advanced options +dropdown. + +*/ + onToggleDropdown = () => { this.setState({ open: !this.state.open }); }; +/* + +### `onGlobalClick(e)` + +This function closes the advanced options dropdown if you click +anywhere else on the screen. + +*/ + onGlobalClick = (e) => { if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { this.setState({ open: false }); } } +/* + +### `componentDidMount()`, `componentWillUnmount()` + +This function closes the advanced options dropdown if you click +anywhere else on the screen. + +*/ + componentDidMount () { window.addEventListener('click', this.onGlobalClick); window.addEventListener('touchstart', this.onGlobalClick); } - componentWillUnmount () { window.removeEventListener('click', this.onGlobalClick); window.removeEventListener('touchstart', this.onGlobalClick); } - state = { - open: false, - }; +/* - handleClick = (e) => { - const option = e.currentTarget.getAttribute('data-index'); - e.preventDefault(); - this.props.onChange(option); - } +### `setRef(c)` + +`setRef()` stores a reference to the dropdown's `
in `this.node`. + +*/ setRef = (c) => { this.node = c; } +/* + +### `render()` + +`render()` actually puts our component on the screen. + +*/ + render () { const { open } = this.state; 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, key: 'do_not_federate' }, + { 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 +``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 ( - ); }); - return (
-
- -
-
- {optionElems} +/* + +Finally, we can render our component. + +*/ + + return ( +
+
+ +
+
+ {optionElems} +
-
); + ); } } diff --git a/app/javascript/glitch/components/compose/advanced_options/toggle.js b/app/javascript/glitch/components/compose/advanced_options/toggle.js new file mode 100644 index 000000000..d6907472a --- /dev/null +++ b/app/javascript/glitch/components/compose/advanced_options/toggle.js @@ -0,0 +1,103 @@ +/* + +`` +================================ + +> For more information on the contents of this file, please contact: +> +> - surinna [@srn@dev.glitch.social] + +This creates the toggle used by ``. + +__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 `` and place next to it our text. + +*/ + + render() { + const { active, shortText, longText } = this.props; + return ( +
+
+ +
+
+ {shortText} + {longText} +
+
+ ); + } + +} diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js index 60303537d..bed086172 100644 --- a/app/javascript/glitch/components/notification/container.js +++ b/app/javascript/glitch/components/notification/container.js @@ -1,3 +1,21 @@ +/* + +`` +========================= + +This container connects ``s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import { connect } from 'react-redux'; @@ -8,6 +26,20 @@ import { makeGetNotification } from '../../../mastodon/selectors'; import Notification from '.'; import { deleteNotification } from '../../../mastodon/actions/notifications'; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +State mapping: +-------------- + +The `mapStateToProps()` function maps various state properties to the +props of our component. We wrap this in `makeMapStateToProps()` so that +we only have to call `makeGetNotification()` once instead of every +time. + +*/ + const makeMapStateToProps = () => { const getNotification = makeGetNotification(); @@ -19,7 +51,20 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch) => ({ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +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 => ({ onDeleteNotification (id) { dispatch(deleteNotification(id)); }, diff --git a/app/javascript/glitch/components/notification/follow.js b/app/javascript/glitch/components/notification/follow.js new file mode 100644 index 000000000..26396478b --- /dev/null +++ b/app/javascript/glitch/components/notification/follow.js @@ -0,0 +1,171 @@ +/* + +`` +====================== + +This component renders a follow notification. + +__Props:__ + + - __`id` (`PropTypes.number.isRequired`) :__ + This is the id of the notification. + + - __`onDeleteNotification` (`PropTypes.func.isRequired`) :__ + The function to call when a notification should be + dismissed/deleted. + + - __`account` (`PropTypes.object.isRequired`) :__ + The account associated with the follow notification, ie the account + which followed the user. + + - __`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, FormattedMessage, injectIntl } from 'react-intl'; +import escapeTextContentForBrowser from 'escape-html'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // +import emojify from '../../../mastodon/emoji'; +import Permalink from '../../../mastodon/components/permalink'; +import AccountContainer from '../../../mastodon/containers/account_container'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Inital setup: +------------- + +The `messages` constant is used to define any messages that we need +from inside props. + +*/ + +const messages = defineMessages({ + deleteNotification : + { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, +}); + +/* + +Implementation: +--------------- + +*/ + +@injectIntl +export default class NotificationFollow extends ImmutablePureComponent { + + static propTypes = { + id : PropTypes.number.isRequired, + onDeleteNotification : PropTypes.func.isRequired, + account : ImmutablePropTypes.map.isRequired, + intl : PropTypes.object.isRequired, + }; + +/* + +### `handleNotificationDeleteClick()` + +This function just calls our `onDeleteNotification()` prop with the +notification's `id`. + +*/ + + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.id); + } + +/* + +### `render()` + +This actually renders the component. + +*/ + + render () { + const { account, intl } = this.props; + +/* + +`dismiss` creates the notification dismissal button. Its title is given +by `dismissTitle`. + +*/ + + const dismissTitle = intl.formatMessage(messages.deleteNotification); + const dismiss = ( + + ); + +/* + +`link` is a container for the account's `displayName`, which links to +the account timeline using a ``. + +*/ + + const displayName = account.get('display_name') || account.get('username'); + const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; + const link = ( + + ); + +/* + +We can now render our component. + +*/ + + return ( +
+
+
+ +
+ + + + {dismiss} +
+ + +
+ ); + } + +} diff --git a/app/javascript/glitch/components/notification/follow_notification.js b/app/javascript/glitch/components/notification/follow_notification.js deleted file mode 100644 index 7cabd91f6..000000000 --- a/app/javascript/glitch/components/notification/follow_notification.js +++ /dev/null @@ -1,78 +0,0 @@ -// Package imports // -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import escapeTextContentForBrowser from 'escape-html'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -// Mastodon imports // -import emojify from '../../../mastodon/emoji'; -import Permalink from '../../../mastodon/components/permalink'; -import AccountContainer from '../../../mastodon/containers/account_container'; - -const messages = defineMessages({ - deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, -}); - - -@injectIntl -export default class FollowNotification extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - notificationId: PropTypes.number.isRequired, - onDeleteNotification: PropTypes.func.isRequired, - account: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - }; - - // Avoid checking props that are functions (and whose equality will always - // evaluate to false. See react-immutable-pure-component for usage. - updateOnProps = [ - 'account', - ] - - handleNotificationDeleteClick = () => { - this.props.onDeleteNotification(this.props.notificationId); - } - - render () { - const { account, intl } = this.props; - - const dismissTitle = intl.formatMessage(messages.deleteNotification); - const dismiss = ( - - ); - - const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - const link = ; - return ( -
-
-
- -
- - - - {dismiss} -
- - -
- ); - } - -} diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 0cdc03cbe..556d5aea8 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; // Our imports // import StatusContainer from '../status/container'; -import FollowNotification from './follow_notification'; +import NotificationFollow from './follow'; export default class Notification extends ImmutablePureComponent { @@ -20,8 +20,8 @@ export default class Notification extends ImmutablePureComponent { renderFollow (notification) { return ( - diff --git a/app/javascript/glitch/reducers/local_settings.js b/app/javascript/glitch/reducers/local_settings.js index 776dcead7..35a8e065b 100644 --- a/app/javascript/glitch/reducers/local_settings.js +++ b/app/javascript/glitch/reducers/local_settings.js @@ -18,12 +18,12 @@ associated actions are: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Imports -------- +Imports: +-------- */ @@ -36,12 +36,12 @@ import { STORE_HYDRATE } from '../../mastodon/actions/store'; // Our imports // import { LOCAL_SETTING_CHANGE } from '../actions/local_settings'; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -initialState ------------- +initialState: +------------- You can see the default values for all of our local settings here. These are only used if no previously-saved values exist. @@ -71,12 +71,12 @@ const initialState = ImmutableMap({ }), }); - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Helper functions ----------------- +Helper functions: +----------------- ### `hydrate(state, localSettings)` @@ -89,12 +89,12 @@ from `localStorage`. const hydrate = (state, localSettings) => state.mergeDeep(localSettings); - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`localSettings(state = initialState, action)` ---------------------------------------------- +`localSettings(state = initialState, action)`: +---------------------------------------------- This function holds our actual reducer. diff --git a/app/javascript/glitch/util/bio_metadata.js b/app/javascript/glitch/util/bio_metadata.js index c5e87f356..0c8195e9d 100644 --- a/app/javascript/glitch/util/bio_metadata.js +++ b/app/javascript/glitch/util/bio_metadata.js @@ -1,7 +1,7 @@ /* `util/bio_metadata` -======================== +=================== > For more information on the contents of this file, please contact: > @@ -26,7 +26,7 @@ functions are: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /*********************************************************************\ -- cgit From d589dd7cd0512b558412a38a935b1a9cdcbf0ce7 Mon Sep 17 00:00:00 2001 From: Ondřej Hruška Date: Sat, 21 Oct 2017 20:24:53 +0200 Subject: Compose buttons bar redesign + generalize dropdown (#194) * Generalize compose dropdown for re-use * wip stuffs * new tootbox look and removed old doodle button files * use the house icon for ... --- .../components/compose/advanced_options/index.js | 94 ++------------- .../components/compose/attach_options/index.js | 133 +++++++++++++++++++++ .../glitch/components/compose/dropdown/index.js | 77 ++++++++++++ .../features/compose/components/compose_form.js | 11 +- .../features/compose/components/doodle_button.js | 41 ------- .../compose/containers/doodle_button_container.js | 15 --- app/javascript/styles/mastodon/components.scss | 5 + 7 files changed, 228 insertions(+), 148 deletions(-) create mode 100644 app/javascript/glitch/components/compose/attach_options/index.js create mode 100644 app/javascript/glitch/components/compose/dropdown/index.js delete mode 100644 app/javascript/mastodon/features/compose/components/doodle_button.js delete mode 100644 app/javascript/mastodon/features/compose/containers/doodle_button_container.js (limited to 'app/javascript/glitch/components/compose') diff --git a/app/javascript/glitch/components/compose/advanced_options/index.js b/app/javascript/glitch/components/compose/advanced_options/index.js index b745d1cdf..8251baf4d 100644 --- a/app/javascript/glitch/components/compose/advanced_options/index.js +++ b/app/javascript/glitch/components/compose/advanced_options/index.js @@ -47,11 +47,9 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { injectIntl, defineMessages } from 'react-intl'; -// Mastodon imports // -import IconButton from '../../../../mastodon/components/icon_button'; - // Our imports // import ComposeAdvancedOptionsToggle from './toggle'; +import ComposeDropdown from '../dropdown/index'; // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -77,11 +75,6 @@ const messages = defineMessages({ { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' }, }); -const iconStyle = { - height : null, - lineHeight : '27px', -}; - /* Implementation: @@ -100,67 +93,6 @@ export default class ComposeAdvancedOptions extends React.PureComponent { intl : PropTypes.object.isRequired, }; - state = { - open: false, - }; - -/* - -### `onToggleDropdown()` - -This function toggles the opening and closing of the advanced options -dropdown. - -*/ - - onToggleDropdown = () => { - this.setState({ open: !this.state.open }); - }; - -/* - -### `onGlobalClick(e)` - -This function closes the advanced options dropdown if you click -anywhere else on the screen. - -*/ - - onGlobalClick = (e) => { - if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { - this.setState({ open: false }); - } - } - -/* - -### `componentDidMount()`, `componentWillUnmount()` - -This function closes the advanced options dropdown if you click -anywhere else on the screen. - -*/ - - componentDidMount () { - window.addEventListener('click', this.onGlobalClick); - window.addEventListener('touchstart', this.onGlobalClick); - } - componentWillUnmount () { - window.removeEventListener('click', this.onGlobalClick); - window.removeEventListener('touchstart', this.onGlobalClick); - } - -/* - -### `setRef(c)` - -`setRef()` stores a reference to the dropdown's `
in `this.node`. - -*/ - - setRef = (c) => { - this.node = c; - } /* @@ -171,7 +103,6 @@ anywhere else on the screen. */ render () { - const { open } = this.state; const { intl, values } = this.props; /* @@ -218,23 +149,14 @@ toggle as its `key` so that React can keep track of it. Finally, we can render our component. */ - return ( -
-
- -
-
- {optionElems} -
-
+ + {optionElems} + ); } diff --git a/app/javascript/glitch/components/compose/attach_options/index.js b/app/javascript/glitch/components/compose/attach_options/index.js new file mode 100644 index 000000000..4340972f0 --- /dev/null +++ b/app/javascript/glitch/components/compose/attach_options/index.js @@ -0,0 +1,133 @@ +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { injectIntl, defineMessages } from 'react-intl'; + +// Our imports // +import ComposeDropdown from '../dropdown/index'; +import { uploadCompose } from '../../../../mastodon/actions/compose'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { openModal } from '../../../../mastodon/actions/modal'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +const messages = defineMessages({ + upload : + { id: 'compose.attach.upload', defaultMessage: 'Upload a file' }, + doodle : + { id: 'compose.attach.doodle', defaultMessage: 'Draw something' }, + attach : + { id: 'compose.attach', defaultMessage: 'Attach...' }, +}); + +const mapStateToProps = state => ({ + // This horrible expression is copied from vanilla upload_button_container + disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')), + resetFileKey: state.getIn(['compose', 'resetFileKey']), + acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), +}); + +const mapDispatchToProps = dispatch => ({ + onSelectFile (files) { + dispatch(uploadCompose(files)); + }, + onOpenDoodle () { + dispatch(openModal('DOODLE', { noEsc: true })); + }, +}); + +@injectIntl +@connect(mapStateToProps, mapDispatchToProps) +export default class ComposeAttachOptions extends ImmutablePureComponent { + + static propTypes = { + intl : PropTypes.object.isRequired, + resetFileKey: PropTypes.number, + acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired, + disabled: PropTypes.bool, + onSelectFile: PropTypes.func.isRequired, + onOpenDoodle: PropTypes.func.isRequired, + }; + + handleItemClick = bt => { + if (bt === 'upload') { + this.fileElement.click(); + } + + if (bt === 'doodle') { + this.props.onOpenDoodle(); + } + + this.dropdown.setState({ open: false }); + }; + + handleFileChange = (e) => { + if (e.target.files.length > 0) { + this.props.onSelectFile(e.target.files); + } + } + + setFileRef = (c) => { + this.fileElement = c; + } + + setDropdownRef = (c) => { + this.dropdown = c; + } + + render () { + const { intl, resetFileKey, disabled, acceptContentTypes } = this.props; + + const options = [ + { icon: 'cloud-upload', text: messages.upload, name: 'upload' }, + { icon: 'paint-brush', text: messages.doodle, name: 'doodle' }, + ]; + + const optionElems = options.map((item) => { + const hdl = () => this.handleItemClick(item.name); + return ( +
+
+ +
+ +
+ {intl.formatMessage(item.text)} +
+
+ ); + }); + + return ( +
+ + {optionElems} + + +
+ ); + } + +} diff --git a/app/javascript/glitch/components/compose/dropdown/index.js b/app/javascript/glitch/components/compose/dropdown/index.js new file mode 100644 index 000000000..5f6467155 --- /dev/null +++ b/app/javascript/glitch/components/compose/dropdown/index.js @@ -0,0 +1,77 @@ +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; + +// Mastodon imports // +import IconButton from '../../../../mastodon/components/icon_button'; + +const iconStyle = { + height : null, + lineHeight : '27px', +}; + +export default class ComposeDropdown extends React.PureComponent { + + static propTypes = { + title: PropTypes.string.isRequired, + icon: PropTypes.string, + highlight: PropTypes.bool, + disabled: PropTypes.bool, + children: PropTypes.arrayOf(PropTypes.node).isRequired, + }; + + state = { + open: false, + }; + + onGlobalClick = (e) => { + if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { + this.setState({ open: false }); + } + }; + + componentDidMount () { + window.addEventListener('click', this.onGlobalClick); + window.addEventListener('touchstart', this.onGlobalClick); + } + componentWillUnmount () { + window.removeEventListener('click', this.onGlobalClick); + window.removeEventListener('touchstart', this.onGlobalClick); + } + + onToggleDropdown = () => { + if (this.props.disabled) return; + this.setState({ open: !this.state.open }); + }; + + setRef = (c) => { + this.node = c; + }; + + render () { + const { open } = this.state; + let { highlight, title, icon, disabled } = this.props; + + if (!icon) icon = 'ellipsis-h'; + + return ( +
+
+ +
+
+ {this.props.children} +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 17f5bde4b..4b393bf8b 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -5,8 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; -import UploadButtonContainer from '../containers/upload_button_container'; -import DoodleButtonContainer from '../containers/doodle_button_container'; import { defineMessages, injectIntl } from 'react-intl'; import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; @@ -20,6 +18,7 @@ import { isMobile } from '../../../is_mobile'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { length } from 'stringz'; import { countableText } from '../util/counter'; +import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, @@ -241,12 +240,12 @@ export default class ComposeForm extends ImmutablePureComponent {
- - - - + +
+ +
diff --git a/app/javascript/mastodon/features/compose/components/doodle_button.js b/app/javascript/mastodon/features/compose/components/doodle_button.js deleted file mode 100644 index 0af02458f..000000000 --- a/app/javascript/mastodon/features/compose/components/doodle_button.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import IconButton from '../../../components/icon_button'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -const messages = defineMessages({ - doodle: { id: 'doodle_button.label', defaultMessage: 'Add a drawing' }, -}); - -const iconStyle = { - height: null, - lineHeight: '27px', -}; - -@injectIntl -export default class UploadButton extends ImmutablePureComponent { - - static propTypes = { - disabled: PropTypes.bool, - onOpenCanvas: PropTypes.func.isRequired, - style: PropTypes.object, - intl: PropTypes.object.isRequired, - }; - - handleClick = () => { - this.props.onOpenCanvas(); - } - - render () { - - const { intl, disabled } = this.props; - - return ( -
- -
- ); - } - -} diff --git a/app/javascript/mastodon/features/compose/containers/doodle_button_container.js b/app/javascript/mastodon/features/compose/containers/doodle_button_container.js deleted file mode 100644 index 5ada4514f..000000000 --- a/app/javascript/mastodon/features/compose/containers/doodle_button_container.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import DoodleButton from '../components/doodle_button'; -import { openModal } from '../../../actions/modal'; - -const mapStateToProps = state => ({ - disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')), -}); - -const mapDispatchToProps = dispatch => ({ - onOpenCanvas () { - dispatch(openModal('DOODLE', { noEsc: true })); - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(DoodleButton); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 6fe179581..306a0457d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -322,6 +322,11 @@ } } +.compose-form__buttons-separator { + border-left: 1px solid #c3c3c3; + margin: 0 3px; +} + .compose-form__upload-button-icon { line-height: 27px; } -- cgit