From 3807b0b171d588ccccfc6210c823e5ce87b9b90f Mon Sep 17 00:00:00 2001 From: Jessica Stokes Date: Wed, 4 Jan 2017 17:25:48 -0800 Subject: Improve quality of life for 4-inch phones Removes extra UI margins < 360px, and allows the tab bar to scroll. Also slightly improves horizontal scrolling behaviour on desktop. --- app/assets/javascripts/components/features/ui/components/tabs_bar.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx index 219979522..499c9e287 100644 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx @@ -3,9 +3,8 @@ import { FormattedMessage } from 'react-intl'; const outerStyle = { background: '#373b4a', - margin: '10px', flex: '0 0 auto', - marginBottom: '0' + overflowY: 'auto' }; const tabStyle = { -- cgit From 5b75f6d0f36940f40db9f1064f37b0069068b691 Mon Sep 17 00:00:00 2001 From: Jessica Stokes Date: Wed, 4 Jan 2017 17:32:11 -0800 Subject: Make tabs bar take up less room on 4-inch phones --- app/assets/javascripts/components/features/ui/components/tabs_bar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx index 499c9e287..dbfdc3f85 100644 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx @@ -10,7 +10,7 @@ const outerStyle = { const tabStyle = { display: 'block', flex: '1 1 auto', - padding: '10px', + padding: '10px 5px', color: '#fff', textDecoration: 'none', textAlign: 'center', -- cgit From 9c493b1ea28ac3d36be367c019236394b6bd341d Mon Sep 17 00:00:00 2001 From: Jessica Stokes Date: Wed, 4 Jan 2017 19:30:22 -0800 Subject: Replace "Public" in tab bar with "More" hamburger --- app/assets/javascripts/components/features/ui/components/tabs_bar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx index dbfdc3f85..aa40a488f 100644 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx @@ -30,7 +30,7 @@ const TabsBar = () => { - + ); }; -- cgit From 989c3f40022bc65d69915be597acda3c4d58de60 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Fri, 6 Jan 2017 22:09:55 +0100 Subject: Add tab bar alternative to desktop UI, upgrade react & react-redux --- .../javascripts/components/containers/mastodon.jsx | 7 +-- .../features/compose/components/drawer.jsx | 73 ++++++++++++++++++---- .../features/compose/components/navigation_bar.jsx | 2 +- .../components/features/compose/index.jsx | 5 +- .../components/features/ui/components/tabs_bar.jsx | 2 +- .../javascripts/components/features/ui/index.jsx | 9 ++- app/assets/stylesheets/components.scss | 22 +++++++ package.json | 9 +-- yarn.lock | 6 +- 9 files changed, 103 insertions(+), 32 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx index 026daeb06..6c0d28053 100644 --- a/app/assets/javascripts/components/containers/mastodon.jsx +++ b/app/assets/javascripts/components/containers/mastodon.jsx @@ -9,7 +9,6 @@ import { import { updateNotifications } from '../actions/notifications'; import { setAccessToken } from '../actions/meta'; import { setAccountSelf } from '../actions/accounts'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import createBrowserHistory from 'history/lib/createBrowserHistory'; import { applyRouterMiddleware, @@ -63,8 +62,6 @@ const Mastodon = React.createClass({ locale: React.PropTypes.string.isRequired }, - mixins: [PureRenderMixin], - componentWillMount() { const { token, account, locale } = this.props; @@ -108,9 +105,9 @@ const Mastodon = React.createClass({ - + - + diff --git a/app/assets/javascripts/components/features/compose/components/drawer.jsx b/app/assets/javascripts/components/features/compose/components/drawer.jsx index d31d0e453..b694cdd2a 100644 --- a/app/assets/javascripts/components/features/compose/components/drawer.jsx +++ b/app/assets/javascripts/components/features/compose/components/drawer.jsx @@ -1,26 +1,75 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { Link } from 'react-router'; +import { injectIntl, defineMessages } from 'react-intl'; -const style = { +const messages = defineMessages({ + start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, + public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Public timeline' }, + preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, + logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' } +}); + +const outerStyle = { + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + overflowY: 'hidden' +}; + +const innerStyle = { boxSizing: 'border-box', - background: '#454b5e', padding: '0', display: 'flex', flexDirection: 'column', - overflowY: 'auto' + overflowY: 'auto', + flexGrow: '1' +}; + +const tabStyle = { + display: 'block', + flex: '1 1 auto', + padding: '15px', + paddingBottom: '13px', + color: '#9baec8', + textDecoration: 'none', + textAlign: 'center', + fontSize: '16px', + borderBottom: '2px solid transparent' }; -const Drawer = React.createClass({ +const tabActiveStyle = { + color: '#2b90d9', + borderBottom: '2px solid #2b90d9' +}; - mixins: [PureRenderMixin], +const Drawer = ({ children, withHeader, intl }) => { + let header = ''; - render () { - return ( -
- {this.props.children} + if (withHeader) { + header = ( +
+ + + +
); } -}); + return ( +
+ {header} + +
+ {children} +
+
+ ); +}; + +Drawer.propTypes = { + withHeader: React.PropTypes.bool, + children: React.PropTypes.node, + intl: React.PropTypes.object +}; -export default Drawer; +export default injectIntl(Drawer); diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx index 71b50fc3a..289e2dddf 100644 --- a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx +++ b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx @@ -21,7 +21,7 @@ const NavigationBar = React.createClass({
{this.props.account.get('acct')} - · +
); diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx index 4017c8949..f6095c0c6 100644 --- a/app/assets/javascripts/components/features/compose/index.jsx +++ b/app/assets/javascripts/components/features/compose/index.jsx @@ -10,7 +10,8 @@ import { mountCompose, unmountCompose } from '../../actions/compose'; const Compose = React.createClass({ propTypes: { - dispatch: React.PropTypes.func.isRequired + dispatch: React.PropTypes.func.isRequired, + withHeader: React.PropTypes.bool }, mixins: [PureRenderMixin], @@ -25,7 +26,7 @@ const Compose = React.createClass({ render () { return ( - + diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx index aa40a488f..2f8a28fad 100644 --- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx +++ b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx @@ -30,7 +30,7 @@ const TabsBar = () => { - + ); }; diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index 76e3dd940..db793f945 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -14,6 +14,11 @@ import { connect } from 'react-redux'; const UI = React.createClass({ + propTypes: { + dispatch: React.PropTypes.func.isRequired, + children: React.PropTypes.node + }, + getInitialState () { return { width: window.innerWidth @@ -41,7 +46,7 @@ const UI = React.createClass({ handleDrop (e) { e.preventDefault(); - if (e.dataTransfer) { + if (e.dataTransfer && e.dataTransfer.files.length === 1) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } }, @@ -72,7 +77,7 @@ const UI = React.createClass({ } else { mountedColumns = ( - + {this.props.children} diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index c2fcd76b3..69595995c 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -349,6 +349,28 @@ width: 280px; } +.drawer__inner { + background: linear-gradient(rgba(69, 75, 94, 1), rgba(69, 75, 94, 0.65)); +} + +.drawer__header { + flex: 0 0 auto; + font-size: 16px; + background: darken(#454b5e, 5%); + margin-bottom: 10px; + display: flex; + flex-direction: row; + + a { + transition: all 100ms ease-in; + + &:hover { + background: darken(#454b5e, 10%); + transition: all 200ms ease-out; + } + } +} + .column, .drawer { margin-left: 5px; margin-right: 5px; diff --git a/package.json b/package.json index 3a9365d3e..6a072ca06 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "chai": "^3.5.0", "chai-enzyme": "^0.5.2", "css-loader": "^0.26.1", - "emojione": "^2.2.6", + "emojione": "latest", "enzyme": "^2.4.1", "es6-promise": "^3.2.1", "http-link-header": "^0.5.0", @@ -39,22 +39,19 @@ "react-motion": "^0.4.5", "react-notification": "^6.4.0", "react-proxy": "^1.1.8", - "react-redux": "^5.0.0-beta.3", + "react-redux": "^5.0.1", "react-redux-loading-bar": "^2.4.1", "react-router": "^2.8.0", "react-router-scroll": "^0.3.2", "react-simple-dropdown": "^1.1.4", "react-storybook-addon-intl": "^0.1.0", "react-toggle": "^2.1.1", - "redux": "^3.5.2", + "redux": "^3.6.0", "redux-immutable": "^3.0.8", "redux-thunk": "^2.1.0", "reselect": "^2.5.4", "sass-loader": "^4.0.2", "sinon": "^1.17.6", "style-loader": "^0.13.1" - }, - "dependencies": { - "emojione": "latest" } } diff --git a/yarn.lock b/yarn.lock index db5f7d408..948de9ba8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4301,9 +4301,9 @@ react-redux@^4.4.5: lodash "^4.2.0" loose-envify "^1.1.0" -react-redux@^5.0.0-beta.3: - version "5.0.0-beta.3" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.0-beta.3.tgz#d50bfb00799cf7d2a9fd55fe34d6b3ecc24d3072" +react-redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.1.tgz#84a41bd4cdd180452bb6922bc79ad25bd5abb7c4" dependencies: hoist-non-react-statics "^1.0.3" invariant "^2.0.0" -- cgit From e1ca3549561b133a5c878f5f8ace2c8d2dec4eee Mon Sep 17 00:00:00 2001 From: blackle Date: Sat, 7 Jan 2017 18:41:57 -0500 Subject: Show logout button on getting started so it's available on mobile --- app/assets/javascripts/components/features/getting_started/index.jsx | 4 +++- .../javascripts/components/features/ui/components/column_link.jsx | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index 8f56ba39f..8b4096b34 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -9,7 +9,8 @@ const messages = defineMessages({ heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Public timeline' }, preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' } + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Sign out' } }); const mapStateToProps = state => ({ @@ -28,6 +29,7 @@ const GettingStarted = ({ intl, me }) => {
+ {followRequests}
diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx index a2f7c13a6..901a29f5c 100644 --- a/app/assets/javascripts/components/features/ui/components/column_link.jsx +++ b/app/assets/javascripts/components/features/ui/components/column_link.jsx @@ -13,10 +13,10 @@ const iconStyle = { marginRight: '5px' }; -const ColumnLink = ({ icon, text, to, href }) => { +const ColumnLink = ({ icon, text, to, href, method }) => { if (href) { return ( - + {text} -- cgit From d64c454cfe0db2e0f8205e37be4b0161309c5c2c Mon Sep 17 00:00:00 2001 From: blackle Date: Sun, 8 Jan 2017 05:04:01 -0500 Subject: Switch to compose view when tapping 'mention' in dropdown on mobile --- .../javascripts/components/components/status_action_bar.jsx | 2 +- app/assets/javascripts/components/containers/status_container.jsx | 6 +++++- app/assets/javascripts/components/features/account/index.jsx | 8 ++++++++ app/assets/javascripts/components/features/status/index.jsx | 4 ++++ app/assets/javascripts/components/features/ui/index.jsx | 5 ++--- app/assets/javascripts/components/is_mobile.jsx | 5 +++++ 6 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 app/assets/javascripts/components/is_mobile.jsx (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx index afaf82561..c037bc573 100644 --- a/app/assets/javascripts/components/components/status_action_bar.jsx +++ b/app/assets/javascripts/components/components/status_action_bar.jsx @@ -49,7 +49,7 @@ const StatusActionBar = React.createClass({ }, handleMentionClick () { - this.props.onMention(this.props.status.get('account')); + this.props.onMention(this.props.status.get('account'), this.context.router); }, handleBlockClick () { diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx index 6a882eab4..ad2be03d1 100644 --- a/app/assets/javascripts/components/containers/status_container.jsx +++ b/app/assets/javascripts/components/containers/status_container.jsx @@ -15,6 +15,7 @@ import { blockAccount } from '../actions/accounts'; import { deleteStatus } from '../actions/statuses'; import { openMedia } from '../actions/modal'; import { createSelector } from 'reselect' +import { isMobile } from '../is_mobile' const mapStateToProps = (state, props) => ({ statusBase: state.getIn(['statuses', props.id]), @@ -86,8 +87,11 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(deleteStatus(status.get('id'))); }, - onMention (account) { + onMention (account, router) { dispatch(mentionCompose(account)); + if (isMobile(window.innerWidth)) { + router.push('/statuses/new'); + } }, onOpenMedia (url) { diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx index c2cc58bb2..2a9eba28a 100644 --- a/app/assets/javascripts/components/features/account/index.jsx +++ b/app/assets/javascripts/components/features/account/index.jsx @@ -20,6 +20,7 @@ import LoadingIndicator from '../../components/loading_indicator'; import ActionBar from './components/action_bar'; import Column from '../ui/components/column'; import ColumnBackButton from '../../components/column_back_button'; +import { isMobile } from '../../is_mobile' const makeMapStateToProps = () => { const getAccount = makeGetAccount(); @@ -34,6 +35,10 @@ const makeMapStateToProps = () => { const Account = React.createClass({ + contextTypes: { + router: React.PropTypes.object + }, + propTypes: { params: React.PropTypes.object.isRequired, dispatch: React.PropTypes.func.isRequired, @@ -71,6 +76,9 @@ const Account = React.createClass({ handleMention () { this.props.dispatch(mentionCompose(this.props.account)); + if (isMobile(window.innerWidth)) { + this.context.router.push('/statuses/new'); + } }, render () { diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx index 0a1528fe9..27a252759 100644 --- a/app/assets/javascripts/components/features/status/index.jsx +++ b/app/assets/javascripts/components/features/status/index.jsx @@ -23,6 +23,7 @@ import { ScrollContainer } from 'react-router-scroll'; import ColumnBackButton from '../../components/column_back_button'; import StatusContainer from '../../containers/status_container'; import { openMedia } from '../../actions/modal'; +import { isMobile } from '../../is_mobile' const makeMapStateToProps = () => { const getStatus = makeGetStatus(); @@ -80,6 +81,9 @@ const Status = React.createClass({ handleMentionClick (account) { this.props.dispatch(mentionCompose(account)); + if (isMobile(window.innerWidth)) { + this.context.router.push('/statuses/new'); + } }, handleOpenMedia (url) { diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index db793f945..ee2e29d6f 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -11,6 +11,7 @@ import Notifications from '../notifications'; import { debounce } from 'react-decoration'; import { uploadCompose } from '../../actions/compose'; import { connect } from 'react-redux'; +import { isMobile } from '../../is_mobile' const UI = React.createClass({ @@ -64,11 +65,9 @@ const UI = React.createClass({ }, render () { - const layoutBreakpoint = 1024; - let mountedColumns; - if (this.state.width <= layoutBreakpoint) { + if (isMobile(this.state.width)) { mountedColumns = ( {this.props.children} diff --git a/app/assets/javascripts/components/is_mobile.jsx b/app/assets/javascripts/components/is_mobile.jsx new file mode 100644 index 000000000..eaa6221e4 --- /dev/null +++ b/app/assets/javascripts/components/is_mobile.jsx @@ -0,0 +1,5 @@ +const LAYOUT_BREAKPOINT = 1024; + +export function isMobile(width) { + return width <= LAYOUT_BREAKPOINT; +}; -- cgit From 312c51b5c87e23c62d163770d550dc94df32627f Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Jan 2017 17:25:10 +0100 Subject: Home column filters --- .../javascripts/components/actions/settings.jsx | 14 +- .../components/components/column_collapsable.jsx | 60 +++++++++ .../home_timeline/components/column_settings.jsx | 68 ++++++++++ .../home_timeline/components/setting_text.jsx | 41 ++++++ .../containers/column_settings_container.jsx | 21 +++ .../components/features/home_timeline/index.jsx | 5 +- .../notifications/components/column_settings.jsx | 145 +++++---------------- .../notifications/components/setting_toggle.jsx | 32 +++++ .../containers/column_settings_container.jsx | 6 +- .../ui/containers/status_list_container.jsx | 59 ++++++--- app/assets/javascripts/components/locales/de.jsx | 8 +- .../javascripts/components/reducers/settings.jsx | 7 + app/assets/stylesheets/components.scss | 14 +- app/controllers/api/web/settings_controller.rb | 2 +- package.json | 3 + yarn.lock | 127 +++++++++++++++--- 16 files changed, 462 insertions(+), 150 deletions(-) create mode 100644 app/assets/javascripts/components/components/column_collapsable.jsx create mode 100644 app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx create mode 100644 app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx create mode 100644 app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx create mode 100644 app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/actions/settings.jsx b/app/assets/javascripts/components/actions/settings.jsx index 0a6fb7cdb..c754b30ca 100644 --- a/app/assets/javascripts/components/actions/settings.jsx +++ b/app/assets/javascripts/components/actions/settings.jsx @@ -3,13 +3,15 @@ import axios from 'axios'; export const SETTING_CHANGE = 'SETTING_CHANGE'; export function changeSetting(key, value) { - return (dispatch, getState) => { - dispatch({ - type: SETTING_CHANGE, - key, - value - }); + return { + type: SETTING_CHANGE, + key, + value + }; +}; +export function saveSettings() { + return (_, getState) => { axios.put('/api/web/settings', { data: getState().get('settings').toJS() }); diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx new file mode 100644 index 000000000..abd65d633 --- /dev/null +++ b/app/assets/javascripts/components/components/column_collapsable.jsx @@ -0,0 +1,60 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import { Motion, spring } from 'react-motion'; + +const iconStyle = { + fontSize: '16px', + padding: '15px', + position: 'absolute', + right: '0', + top: '-48px', + cursor: 'pointer' +}; + +const ColumnCollapsable = React.createClass({ + + propTypes: { + icon: React.PropTypes.string.isRequired, + fullHeight: React.PropTypes.number.isRequired, + children: React.PropTypes.node, + onCollapse: React.PropTypes.func + }, + + getInitialState () { + return { + collapsed: true + }; + }, + + mixins: [PureRenderMixin], + + handleToggleCollapsed () { + const currentState = this.state.collapsed; + + this.setState({ collapsed: !currentState }); + + if (!currentState && this.props.onCollapse) { + this.props.onCollapse(); + } + }, + + render () { + const { icon, fullHeight, children } = this.props; + const { collapsed } = this.state; + + return ( +
+
+ + + {({ opacity, height }) => +
+ {children} +
+ } +
+
+ ); + } +}); + +export default ColumnCollapsable; diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx new file mode 100644 index 000000000..714be309b --- /dev/null +++ b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx @@ -0,0 +1,68 @@ +import PureRenderMixin from 'react-addons-pure-render-mixin'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ColumnCollapsable from '../../../components/column_collapsable'; +import SettingToggle from '../../notifications/components/setting_toggle'; +import SettingText from './setting_text'; + +const messages = defineMessages({ + filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter by regular expressions' } +}); + +const outerStyle = { + background: '#373b4a', + padding: '15px' +}; + +const sectionStyle = { + cursor: 'default', + display: 'block', + fontWeight: '500', + color: '#9baec8', + marginBottom: '10px' +}; + +const rowStyle = { + +}; + +const ColumnSettings = React.createClass({ + + propTypes: { + settings: ImmutablePropTypes.map.isRequired, + onChange: React.PropTypes.func.isRequired, + onSave: React.PropTypes.func.isRequired, + intl: React.PropTypes.object.isRequired + }, + + mixins: [PureRenderMixin], + + render () { + const { settings, onChange, onSave, intl } = this.props; + + return ( + +
+ + +
+ } /> +
+ +
+ } /> +
+ + + +
+ +
+
+
+ ); + } + +}); + +export default injectIntl(ColumnSettings); diff --git a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx new file mode 100644 index 000000000..79697e869 --- /dev/null +++ b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx @@ -0,0 +1,41 @@ +import ImmutablePropTypes from 'react-immutable-proptypes'; + +const style = { + display: 'block', + fontFamily: 'inherit', + marginBottom: '10px', + padding: '7px 0', + boxSizing: 'border-box', + width: '100%' +}; + +const SettingText = React.createClass({ + + propTypes: { + settings: ImmutablePropTypes.map.isRequired, + settingKey: React.PropTypes.array.isRequired, + label: React.PropTypes.string.isRequired, + onChange: React.PropTypes.func.isRequired + }, + + handleChange (e) { + this.props.onChange(this.props.settingKey, e.target.value) + }, + + render () { + const { settings, settingKey, label } = this.props; + + return ( + + ); + } + +}); + +export default SettingText; diff --git a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx new file mode 100644 index 000000000..3b3ce19bc --- /dev/null +++ b/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx @@ -0,0 +1,21 @@ +import { connect } from 'react-redux'; +import ColumnSettings from '../components/column_settings'; +import { changeSetting, saveSettings } from '../../../actions/settings'; + +const mapStateToProps = state => ({ + settings: state.getIn(['settings', 'home']) +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (key, checked) { + dispatch(changeSetting(['home', ...key], checked)); + }, + + onSave () { + dispatch(saveSettings()); + } + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx index e4f4fa7c7..8703d0b70 100644 --- a/app/assets/javascripts/components/features/home_timeline/index.jsx +++ b/app/assets/javascripts/components/features/home_timeline/index.jsx @@ -4,6 +4,7 @@ import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; import { refreshTimeline } from '../../actions/timelines'; import { defineMessages, injectIntl } from 'react-intl'; +import ColumnSettingsContainer from './containers/column_settings_container'; const messages = defineMessages({ title: { id: 'column.home', defaultMessage: 'Home' } @@ -12,7 +13,8 @@ const messages = defineMessages({ const HomeTimeline = React.createClass({ propTypes: { - dispatch: React.PropTypes.func.isRequired + dispatch: React.PropTypes.func.isRequired, + intl: React.PropTypes.object.isRequired }, mixins: [PureRenderMixin], @@ -26,6 +28,7 @@ const HomeTimeline = React.createClass({ return ( + ); diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx index b4035c20d..dfb59713c 100644 --- a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx +++ b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx @@ -1,37 +1,14 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Toggle from 'react-toggle'; -import { Motion, spring } from 'react-motion'; import { FormattedMessage } from 'react-intl'; +import ColumnCollapsable from '../../../components/column_collapsable'; +import SettingToggle from './setting_toggle'; const outerStyle = { background: '#373b4a', padding: '15px' }; -const iconStyle = { - fontSize: '16px', - padding: '15px', - position: 'absolute', - right: '0', - top: '-48px', - cursor: 'pointer' -}; - -const labelStyle = { - display: 'block', - lineHeight: '24px', - verticalAlign: 'middle' -}; - -const labelSpanStyle = { - display: 'inline-block', - verticalAlign: 'middle', - marginBottom: '14px', - marginLeft: '8px', - color: '#9baec8' -}; - const sectionStyle = { cursor: 'default', display: 'block', @@ -48,100 +25,50 @@ const ColumnSettings = React.createClass({ propTypes: { settings: ImmutablePropTypes.map.isRequired, - onChange: React.PropTypes.func.isRequired - }, - - getInitialState () { - return { - collapsed: true - }; + onChange: React.PropTypes.func.isRequired, + onSave: React.PropTypes.func.isRequired }, mixins: [PureRenderMixin], - handleToggleCollapsed () { - this.setState({ collapsed: !this.state.collapsed }); - }, - - handleChange (key, e) { - this.props.onChange(key, e.target.checked); - }, - render () { - const { settings } = this.props; - const { collapsed } = this.state; + const { settings, onChange, onSave } = this.props; const alertStr = ; const showStr = ; return ( -
-
- - - {({ opacity, height }) => -
-
- - -
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
- - - -
-
-
- } -
-
+ +
+ + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+
+
); } diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx new file mode 100644 index 000000000..c2438f716 --- /dev/null +++ b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx @@ -0,0 +1,32 @@ +import ImmutablePropTypes from 'react-immutable-proptypes'; +import Toggle from 'react-toggle'; + +const labelStyle = { + display: 'block', + lineHeight: '24px', + verticalAlign: 'middle' +}; + +const labelSpanStyle = { + display: 'inline-block', + verticalAlign: 'middle', + marginBottom: '14px', + marginLeft: '8px', + color: '#9baec8' +}; + +const SettingToggle = ({ settings, settingKey, label, onChange }) => ( + +); + +SettingToggle.propTypes = { + settings: ImmutablePropTypes.map.isRequired, + settingKey: React.PropTypes.array.isRequired, + label: React.PropTypes.node.isRequired, + onChange: React.PropTypes.func.isRequired +}; + +export default SettingToggle; diff --git a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx index 5792e97e3..bc24c75e0 100644 --- a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx +++ b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import ColumnSettings from '../components/column_settings'; -import { changeSetting } from '../../../actions/settings'; +import { changeSetting, saveSettings } from '../../../actions/settings'; const mapStateToProps = state => ({ settings: state.getIn(['settings', 'notifications']) @@ -10,6 +10,10 @@ const mapDispatchToProps = dispatch => ({ onChange (key, checked) { dispatch(changeSetting(['notifications', ...key], checked)); + }, + + onSave () { + dispatch(saveSettings()); } }); diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx index 1621cec7b..7b893711c 100644 --- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx @@ -2,26 +2,55 @@ import { connect } from 'react-redux'; import StatusList from '../../../components/status_list'; import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines'; import Immutable from 'immutable'; +import { createSelector } from 'reselect'; + +const getStatusIds = createSelector([ + (state, { type }) => state.getIn(['settings', type]), + (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()), + (state) => state.get('statuses') +], (columnSettings, statusIds, statuses) => statusIds.filter(id => { + const statusForId = statuses.get(id); + let showStatus = true; + + if (columnSettings.getIn(['shows', 'reblog']) === false) { + showStatus = showStatus && statusForId.get('reblog') === null; + } + + if (columnSettings.getIn(['shows', 'reply']) === false) { + showStatus = showStatus && statusForId.get('in_reply_to_id') === null; + } + + if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) { + try { + const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i'); + showStatus = showStatus && !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'content']) : statusForId.get('content')); + } catch(e) { + // Bad regex, don't affect filters + } + } + + return showStatus; +})); const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', props.type, 'items'], Immutable.List()) + statusIds: getStatusIds(state, props) }); -const mapDispatchToProps = function (dispatch, props) { - return { - onScrollToBottom () { - dispatch(scrollTopTimeline(props.type, false)); - dispatch(expandTimeline(props.type, props.id)); - }, +const mapDispatchToProps = (dispatch, { type, id }) => ({ - onScrollToTop () { - dispatch(scrollTopTimeline(props.type, true)); - }, + onScrollToBottom () { + dispatch(scrollTopTimeline(type, false)); + dispatch(expandTimeline(type, id)); + }, - onScroll () { - dispatch(scrollTopTimeline(props.type, false)); - } - }; -}; + onScrollToTop () { + dispatch(scrollTopTimeline(type, true)); + }, + + onScroll () { + dispatch(scrollTopTimeline(type, false)); + } + +}); export default connect(mapStateToProps, mapDispatchToProps)(StatusList); diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx index c37a71c21..7d32824f1 100644 --- a/app/assets/javascripts/components/locales/de.jsx +++ b/app/assets/javascripts/components/locales/de.jsx @@ -65,7 +65,13 @@ const en = { "notifications.column_settings.mention": "Erwähnungen:", "notifications.column_settings.reblog": "Geteilte Beiträge:", "follow_request.authorize": "Erlauben", - "follow_request.reject": "Ablehnen" + "follow_request.reject": "Ablehnen", + "home.column_settings.basic": "Einfach", + "home.column_settings.advanced": "Fortgeschritten", + "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen", + "home.column_settings.show_replies": "Antworten anzeigen", + "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke", + "missing_indicator.label": "Nicht gefunden" }; export default en; diff --git a/app/assets/javascripts/components/reducers/settings.jsx b/app/assets/javascripts/components/reducers/settings.jsx index 2a834d81c..8bd9edae2 100644 --- a/app/assets/javascripts/components/reducers/settings.jsx +++ b/app/assets/javascripts/components/reducers/settings.jsx @@ -3,6 +3,13 @@ import { STORE_HYDRATE } from '../actions/store'; import Immutable from 'immutable'; const initialState = Immutable.Map({ + home: Immutable.Map({ + shows: Immutable.Map({ + reblog: true, + reply: true + }) + }), + notifications: Immutable.Map({ alerts: Immutable.Map({ follow: true, diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 0abe8c808..f1edfce9d 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -649,4 +649,16 @@ right: 8px !important; left: initial !important; } -} \ No newline at end of file +} + +.setting-text { + color: #9baec8; + background: transparent; + border: none; + border-bottom: 2px solid #9baec8; + + &:focus, &:active { + color: #fff; + border-bottom-color: #2b90d9; + } +} diff --git a/app/controllers/api/web/settings_controller.rb b/app/controllers/api/web/settings_controller.rb index e6f690114..c00e016a4 100644 --- a/app/controllers/api/web/settings_controller.rb +++ b/app/controllers/api/web/settings_controller.rb @@ -6,7 +6,7 @@ class Api::Web::SettingsController < ApiController before_action :require_user! def update - setting = Web::Setting.where(user: current_user).first_or_initialize(user: current_user) + setting = ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user) setting.data = params[:data] setting.save! diff --git a/package.json b/package.json index 6a072ca06..8c75d632b 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,8 @@ "sass-loader": "^4.0.2", "sinon": "^1.17.6", "style-loader": "^0.13.1" + }, + "dependencies": { + "webpack": "^1.14.0" } } diff --git a/yarn.lock b/yarn.lock index 948de9ba8..fac04d0b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1124,6 +1124,12 @@ browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" +browserify-aes@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-0.4.0.tgz#067149b668df31c4b58533e02d01e806d8608e2c" + dependencies: + inherits "^2.0.1" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" @@ -1186,7 +1192,7 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@~0.1.2, browserify-zlib@~0.1.4: +browserify-zlib@^0.1.4, browserify-zlib@~0.1.2, browserify-zlib@~0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" dependencies: @@ -1520,7 +1526,7 @@ constants-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-0.0.1.tgz#92577db527ba6c4cf0a4568d84bc031f441e21f2" -constants-browserify@~1.0.0: +constants-browserify@^1.0.0, constants-browserify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -1596,6 +1602,15 @@ cryptiles@2.x.x: dependencies: boom "2.x.x" +crypto-browserify@3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.3.0.tgz#b9fc75bb4a0ed61dcf1cd5dae96eb30c9c3e506c" + dependencies: + browserify-aes "0.4.0" + pbkdf2-compat "2.0.1" + ripemd160 "0.2.0" + sha.js "2.2.6" + crypto-browserify@^3.0.0: version "3.11.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522" @@ -2559,7 +2574,7 @@ https-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.0.tgz#b3ffdfe734b2a3d4a9efd58e8654c91fce86eafd" -https-browserify@~0.0.0: +https-browserify@0.0.1, https-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" @@ -3458,6 +3473,34 @@ node-libs-browser@^0.6.0: util "~0.10.3" vm-browserify "0.0.4" +node-libs-browser@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b" + dependencies: + assert "^1.1.1" + browserify-zlib "^0.1.4" + buffer "^4.9.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "3.3.0" + domain-browser "^1.1.1" + events "^1.0.0" + https-browserify "0.0.1" + os-browserify "^0.2.0" + path-browserify "0.0.0" + process "^0.11.0" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.0.5" + stream-browserify "^2.0.1" + stream-http "^2.3.1" + string_decoder "^0.10.25" + timers-browserify "^2.0.2" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.10.3" + vm-browserify "0.0.4" + node-pre-gyp@^0.6.29: version "0.6.30" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.30.tgz#64d3073a6f573003717ccfe30c89023297babba1" @@ -3663,6 +3706,10 @@ optionator@^0.8.1: type-check "~0.3.2" wordwrap "~1.0.0" +os-browserify@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" + os-browserify@~0.1.1, os-browserify@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54" @@ -4133,7 +4180,7 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -querystring-es3@~0.2.0: +querystring-es3@^0.2.0, querystring-es3@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -4397,7 +4444,7 @@ readable-stream@1.1, readable-stream@^1.0.27-1, readable-stream@^1.1.13: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.0, readable-stream@~2.1.4: +"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@~2.1.4: version "2.1.5" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0" dependencies: @@ -4689,6 +4736,10 @@ set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + setprototypeof@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.1.tgz#52009b27888c4dc48f591949c0a8275834c1ca7e" @@ -4784,6 +4835,10 @@ source-list-map@^0.1.4, source-list-map@~0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.6.tgz#e1e6f94f0b40c4d28dcf8f5b8766e0e45636877f" +source-list-map@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.7.tgz#d4b5ce2a46535c72c7e8527c71a77d250618172e" + source-map-support@^0.4.2: version "0.4.3" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.3.tgz#693c8383d4389a4569486987c219744dfc601685" @@ -4861,7 +4916,7 @@ stream-browserify@^1.0.0: inherits "~2.0.1" readable-stream "^1.0.27-1" -stream-browserify@^2.0.0: +stream-browserify@^2.0.0, stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" dependencies: @@ -4879,7 +4934,7 @@ stream-consume@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" -stream-http@^2.0.0: +stream-http@^2.0.0, stream-http@^2.3.1: version "2.4.0" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.4.0.tgz#9599aa8e263667ce4190e0dc04a1d065d3595a7e" dependencies: @@ -4924,7 +4979,7 @@ string.prototype.padstart@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" -string_decoder@~0.10.0, string_decoder@~0.10.25, string_decoder@~0.10.x: +string_decoder@^0.10.25, string_decoder@~0.10.0, string_decoder@~0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -5051,6 +5106,12 @@ timers-browserify@^1.0.1: dependencies: process "~0.11.0" +timers-browserify@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" + dependencies: + setimmediate "^1.0.4" + to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" @@ -5125,6 +5186,15 @@ uglify-js@~2.6.0: uglify-to-browserify "~1.0.0" yargs "~3.10.0" +uglify-js@~2.7.3: + version "2.7.5" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" + dependencies: + async "~0.2.6" + source-map "~0.5.1" + uglify-to-browserify "~1.0.0" + yargs "~3.10.0" + uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" @@ -5162,16 +5232,16 @@ url-loader@^0.5.7: loader-utils "0.2.x" mime "1.2.x" -url@~0.10.1: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" +url@^0.11.0, url@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" dependencies: punycode "1.3.2" querystring "0.2.0" -url@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" +url@~0.10.1: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" dependencies: punycode "1.3.2" querystring "0.2.0" @@ -5184,7 +5254,7 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, "util@>=0.10.3 <1", util@~0.10.1, util@~0.10.3: +util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3, util@~0.10.1, util@~0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -5260,6 +5330,13 @@ webpack-core@~0.6.0: source-list-map "~0.1.0" source-map "~0.4.1" +webpack-core@~0.6.9: + version "0.6.9" + resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" + dependencies: + source-list-map "~0.1.7" + source-map "~0.4.1" + webpack-dev-middleware@^1.6.0: version "1.8.4" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.8.4.tgz#e8765c9122887ce9e3abd4cc9c3eb31b61e0948d" @@ -5298,6 +5375,26 @@ webpack@^1.13.1: watchpack "^0.2.1" webpack-core "~0.6.0" +webpack@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823" + dependencies: + acorn "^3.0.0" + async "^1.3.0" + clone "^1.0.2" + enhanced-resolve "~0.9.0" + interpret "^0.6.4" + loader-utils "^0.2.11" + memory-fs "~0.3.0" + mkdirp "~0.5.0" + node-libs-browser "^0.7.0" + optimist "~0.6.0" + supports-color "^3.1.0" + tapable "~0.1.8" + uglify-js "~2.7.3" + watchpack "^0.2.1" + webpack-core "~0.6.9" + whatwg-fetch@>=0.10.0: version "1.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-1.0.0.tgz#01c2ac4df40e236aaa18480e3be74bd5c8eb798e" -- cgit From 18b11100e7d66b5e5f5a2a364423582c6b7d75a9 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 10 Jan 2017 17:33:32 +0100 Subject: Fix issue when settings are not defined for column type --- .../components/features/ui/containers/status_list_container.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx index 7b893711c..ffb6f6e79 100644 --- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx @@ -5,7 +5,7 @@ import Immutable from 'immutable'; import { createSelector } from 'reselect'; const getStatusIds = createSelector([ - (state, { type }) => state.getIn(['settings', type]), + (state, { type }) => state.getIn(['settings', type], Immutable.Map()), (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()), (state) => state.get('statuses') ], (columnSettings, statusIds, statuses) => statusIds.filter(id => { -- cgit From da5d3662305b3d28af9e88a7168f3a1702351a9a Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 16 Jan 2017 12:04:02 +0100 Subject: Fix #414 - Improve lightbox, add loading indicator --- .../javascripts/components/components/lightbox.jsx | 22 ++-- .../features/ui/containers/modal_container.jsx | 16 ++- package.json | 1 + yarn.lock | 123 ++------------------- 4 files changed, 35 insertions(+), 127 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/components/lightbox.jsx b/app/assets/javascripts/components/components/lightbox.jsx index b5c2a69d8..1e3a88955 100644 --- a/app/assets/javascripts/components/components/lightbox.jsx +++ b/app/assets/javascripts/components/components/lightbox.jsx @@ -35,7 +35,9 @@ const Lightbox = React.createClass({ propTypes: { isVisible: React.PropTypes.bool, onOverlayClicked: React.PropTypes.func, - onCloseClicked: React.PropTypes.func + onCloseClicked: React.PropTypes.func, + intl: React.PropTypes.object.isRequired, + children: React.PropTypes.node }, mixins: [PureRenderMixin], @@ -57,19 +59,17 @@ const Lightbox = React.createClass({ render () { const { intl, isVisible, onOverlayClicked, onCloseClicked, children } = this.props; - const content = isVisible ? children :
; - return ( -
- - {({ y }) => -
+ + {({ backgroundOpacity, opacity, y }) => +
+
- {content} + {children}
- } - -
+
+ } +
); } diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index cd7d63a4a..eeec55ff5 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -1,6 +1,8 @@ -import { connect } from 'react-redux'; -import { closeModal } from '../../../actions/modal'; -import Lightbox from '../../../components/lightbox'; +import { connect } from 'react-redux'; +import { closeModal } from '../../../actions/modal'; +import Lightbox from '../../../components/lightbox'; +import ImageLoader from 'react-imageloader'; +import LoadingIndicator from '../../../components/loading_indicator'; const mapStateToProps = state => ({ url: state.getIn(['modal', 'url']), @@ -23,6 +25,8 @@ const imageStyle = { maxHeight: '80vh' }; +const preloader = () => ; + const Modal = React.createClass({ propTypes: { @@ -37,7 +41,11 @@ const Modal = React.createClass({ return ( - + ); } diff --git a/package.json b/package.json index 907e4a238..194bcfeba 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "react-autosuggest": "^7.0.1", "react-decoration": "^1.4.0", "react-dom": "^15.3.0", + "react-imageloader": "^2.1.0", "react-immutable-proptypes": "^2.1.0", "react-intl": "^2.1.5", "react-motion": "^0.4.5", diff --git a/yarn.lock b/yarn.lock index fac04d0b3..f681b8f14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,10 +97,6 @@ webpack-dev-middleware "^1.6.0" webpack-hot-middleware "^2.10.0" -Base64@~0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028" - JSONStream@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-0.10.0.tgz#74349d0d89522b71f30f0a03ff9bd20ca6f12ac0" @@ -1192,7 +1188,7 @@ browserify-sign@^4.0.0: inherits "^2.0.1" parse-asn1 "^5.0.0" -browserify-zlib@^0.1.4, browserify-zlib@~0.1.2, browserify-zlib@~0.1.4: +browserify-zlib@^0.1.4, browserify-zlib@~0.1.2: version "0.1.4" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d" dependencies: @@ -1522,10 +1518,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -constants-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-0.0.1.tgz#92577db527ba6c4cf0a4568d84bc031f441e21f2" - constants-browserify@^1.0.0, constants-browserify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -1626,14 +1618,6 @@ crypto-browserify@^3.0.0: public-encrypt "^4.0.0" randombytes "^2.0.0" -crypto-browserify@~3.2.6: - version "3.2.8" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.2.8.tgz#b9b11dbe6d9651dd882a01e6cc467df718ecf189" - dependencies: - pbkdf2-compat "2.0.1" - ripemd160 "0.2.0" - sha.js "2.2.6" - css-color-names@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -2543,13 +2527,6 @@ htmlparser2@~3.8.1: entities "1.0" readable-stream "1.1" -http-browserify@^1.3.2: - version "1.7.0" - resolved "https://registry.yarnpkg.com/http-browserify/-/http-browserify-1.7.0.tgz#33795ade72df88acfbfd36773cefeda764735b20" - dependencies: - Base64 "~0.2.0" - inherits "~2.0.1" - http-errors@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.0.tgz#b1cb3d8260fd8e2386cad3189045943372d48211" @@ -2570,10 +2547,6 @@ http-signature@~1.1.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.0.tgz#b3ffdfe734b2a3d4a9efd58e8654c91fce86eafd" - https-browserify@0.0.1, https-browserify@~0.0.0: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" @@ -3445,34 +3418,6 @@ node-gyp@^3.3.1: tar "^2.0.0" which "1" -node-libs-browser@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.6.0.tgz#244806d44d319e048bc8607b5cc4eaf9a29d2e3c" - dependencies: - assert "^1.1.1" - browserify-zlib "~0.1.4" - buffer "^4.9.0" - console-browserify "^1.1.0" - constants-browserify "0.0.1" - crypto-browserify "~3.2.6" - domain-browser "^1.1.1" - events "^1.0.0" - http-browserify "^1.3.2" - https-browserify "0.0.0" - os-browserify "~0.1.2" - path-browserify "0.0.0" - process "^0.11.0" - punycode "^1.2.4" - querystring-es3 "~0.2.0" - readable-stream "^1.1.13" - stream-browserify "^1.0.0" - string_decoder "~0.10.25" - timers-browserify "^1.0.1" - tty-browserify "0.0.0" - url "~0.10.1" - util "~0.10.3" - vm-browserify "0.0.4" - node-libs-browser@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-0.7.0.tgz#3e272c0819e308935e26674408d7af0e1491b83b" @@ -3710,7 +3655,7 @@ os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" -os-browserify@~0.1.1, os-browserify@~0.1.2: +os-browserify@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.1.2.tgz#49ca0293e0b19590a5f5de10c7f265a617d8fe54" @@ -4280,6 +4225,10 @@ react-fuzzy@^0.3.3: classnames "^2.2.3" fuse.js "^2.2.0" +react-imageloader@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-imageloader/-/react-imageloader-2.1.0.tgz#a58401970b3282386aeb810c43175165634f6308" + react-immutable-proptypes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.1.0.tgz#023d6f39bb15c97c071e9e60d00d136eac5fa0b4" @@ -4435,7 +4384,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -readable-stream@1.1, readable-stream@^1.0.27-1, readable-stream@^1.1.13: +readable-stream@1.1: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" dependencies: @@ -4831,7 +4780,7 @@ sortobject@^1.0.0: dependencies: editions "^1.1.1" -source-list-map@^0.1.4, source-list-map@~0.1.0: +source-list-map@^0.1.4: version "0.1.6" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.6.tgz#e1e6f94f0b40c4d28dcf8f5b8766e0e45636877f" @@ -4909,13 +4858,6 @@ stackframe@^0.3.1: version "1.3.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.0.tgz#8e55758cb20e7682c1f4fce8dcab30bf01d1e07a" -stream-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-1.0.0.tgz#bf9b4abfb42b274d751479e44e0ff2656b6f1193" - dependencies: - inherits "~2.0.1" - readable-stream "^1.0.27-1" - stream-browserify@^2.0.0, stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" @@ -4979,7 +4921,7 @@ string.prototype.padstart@^3.0.0: es-abstract "^1.4.3" function-bind "^1.0.2" -string_decoder@^0.10.25, string_decoder@~0.10.0, string_decoder@~0.10.25, string_decoder@~0.10.x: +string_decoder@^0.10.25, string_decoder@~0.10.0, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -5177,15 +5119,6 @@ ua-parser-js@^0.7.9: version "0.7.10" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f" -uglify-js@~2.6.0: - version "2.6.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.6.4.tgz#65ea2fb3059c9394692f15fed87c2b36c16b9adf" - dependencies: - async "~0.2.6" - source-map "~0.5.1" - uglify-to-browserify "~1.0.0" - yargs "~3.10.0" - uglify-js@~2.7.3: version "2.7.5" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8" @@ -5239,13 +5172,6 @@ url@^0.11.0, url@~0.11.0: punycode "1.3.2" querystring "0.2.0" -url@~0.10.1: - version "0.10.3" - resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - user-home@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" @@ -5254,7 +5180,7 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3, util@~0.10.1, util@~0.10.3: +util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3, util@~0.10.1: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -5323,13 +5249,6 @@ webidl-conversions@^3.0.0, webidl-conversions@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" -webpack-core@~0.6.0: - version "0.6.8" - resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.8.tgz#edf9135de00a6a3c26dd0f14b208af0aa4af8d0a" - dependencies: - source-list-map "~0.1.0" - source-map "~0.4.1" - webpack-core@~0.6.9: version "0.6.9" resolved "https://registry.yarnpkg.com/webpack-core/-/webpack-core-0.6.9.tgz#fc571588c8558da77be9efb6debdc5a3b172bdc2" @@ -5355,27 +5274,7 @@ webpack-hot-middleware@^2.10.0: querystring "^0.2.0" strip-ansi "^3.0.0" -webpack@^1.13.1: - version "1.13.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.13.2.tgz#f11a96f458eb752970a86abe746c0704fabafaf3" - dependencies: - acorn "^3.0.0" - async "^1.3.0" - clone "^1.0.2" - enhanced-resolve "~0.9.0" - interpret "^0.6.4" - loader-utils "^0.2.11" - memory-fs "~0.3.0" - mkdirp "~0.5.0" - node-libs-browser "^0.6.0" - optimist "~0.6.0" - supports-color "^3.1.0" - tapable "~0.1.8" - uglify-js "~2.6.0" - watchpack "^0.2.1" - webpack-core "~0.6.0" - -webpack@^1.14.0: +webpack@^1.13.1, webpack@^1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-1.14.0.tgz#54f1ffb92051a328a5b2057d6ae33c289462c823" dependencies: -- cgit From 6cf44ca92c3b92df5bda32adb59258104f1ac9c5 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 16 Jan 2017 19:36:32 +0100 Subject: Improve how the list entry Account component looks when target is blocked/follow is requested --- .../javascripts/components/components/account.jsx | 26 +++++++++++++++++----- .../components/containers/account_container.jsx | 12 +++++++++- .../features/ui/containers/modal_container.jsx | 12 +++++++++- app/models/account.rb | 2 +- 4 files changed, 44 insertions(+), 8 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx index 814d8a9c8..108401b2f 100644 --- a/app/assets/javascripts/components/components/account.jsx +++ b/app/assets/javascripts/components/components/account.jsx @@ -8,7 +8,9 @@ import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' } + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, + unblock: { id: 'account.unblock', defaultMessage: 'Unblock' } }); const outerStyle = { @@ -42,7 +44,9 @@ const Account = React.createClass({ account: ImmutablePropTypes.map.isRequired, me: React.PropTypes.number.isRequired, onFollow: React.PropTypes.func.isRequired, - withNote: React.PropTypes.bool + onBlock: React.PropTypes.func.isRequired, + withNote: React.PropTypes.bool, + intl: React.PropTypes.object.isRequired }, getDefaultProps () { @@ -57,6 +61,10 @@ const Account = React.createClass({ this.props.onFollow(this.props.account); }, + handleBlock () { + this.props.onBlock(this.props.account); + }, + render () { const { account, me, withNote, intl } = this.props; @@ -70,10 +78,18 @@ const Account = React.createClass({ note =
{account.get('note')}
; } - if (account.get('id') !== me && account.get('relationship', null) != null) { + if (account.get('id') !== me && account.get('relationship', null) !== null) { const following = account.getIn(['relationship', 'following']); - - buttons = ; + const requested = account.getIn(['relationship', 'requested']); + const blocking = account.getIn(['relationship', 'blocking']); + + if (requested) { + buttons = + } else if (blocking) { + buttons = ; + } else { + buttons = ; + } } return ( diff --git a/app/assets/javascripts/components/containers/account_container.jsx b/app/assets/javascripts/components/containers/account_container.jsx index 1f49f9819..889c0ac4c 100644 --- a/app/assets/javascripts/components/containers/account_container.jsx +++ b/app/assets/javascripts/components/containers/account_container.jsx @@ -3,7 +3,9 @@ import { makeGetAccount } from '../selectors'; import Account from '../components/account'; import { followAccount, - unfollowAccount + unfollowAccount, + blockAccount, + unblockAccount } from '../actions/accounts'; const makeMapStateToProps = () => { @@ -24,6 +26,14 @@ const mapDispatchToProps = (dispatch) => ({ } else { dispatch(followAccount(account.get('id'))); } + }, + + onBlock (account) { + if (account.getIn(['relationship', 'blocking'])) { + dispatch(unblockAccount(account.get('id'))); + } else { + dispatch(blockAccount(account.get('id'))); + } } }); diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index eeec55ff5..66dfe915e 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -25,7 +25,17 @@ const imageStyle = { maxHeight: '80vh' }; -const preloader = () => ; +const loadingStyle = { + background: '#373b4a', + width: '400px', + paddingBottom: '120px' +}; + +const preloader = () => ( +
+ +
+); const Modal = React.createClass({ diff --git a/app/models/account.rb b/app/models/account.rb index 5f07615fc..5d6324a7b 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -126,7 +126,7 @@ class Account < ApplicationRecord def save_with_optional_avatar! save! rescue ActiveRecord::RecordInvalid => invalid - if invalid.record.errors[:avatar_file_size] || invalid[:avatar_content_type] + if invalid.record.errors[:avatar_file_size] || invalid.record.errors[:avatar_content_type] self.avatar = nil retry end -- cgit From 46be4631ae046c45edc3cc8e01c8fc4144ff6444 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 19 Jan 2017 10:54:18 +0100 Subject: Fix #222 - Update followers count when following/unfollowing Also, since the root component connects to the stream that updates home/notification columns, there is pretty much no case for refreshing those columns beyond initial load. So, move the loading of those columns into the root component, to prevent unneccessary reloads when switching tabs on mobile or resizing desktop window between mobile/desktop layouts --- .../javascripts/components/features/home_timeline/index.jsx | 9 +-------- .../javascripts/components/features/notifications/index.jsx | 10 +--------- app/assets/javascripts/components/features/ui/index.jsx | 9 +++++++-- app/assets/javascripts/components/reducers/accounts.jsx | 8 +++++++- 4 files changed, 16 insertions(+), 20 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx index 8703d0b70..5d2263f15 100644 --- a/app/assets/javascripts/components/features/home_timeline/index.jsx +++ b/app/assets/javascripts/components/features/home_timeline/index.jsx @@ -1,8 +1,6 @@ -import { connect } from 'react-redux'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; -import { refreshTimeline } from '../../actions/timelines'; import { defineMessages, injectIntl } from 'react-intl'; import ColumnSettingsContainer from './containers/column_settings_container'; @@ -13,16 +11,11 @@ const messages = defineMessages({ const HomeTimeline = React.createClass({ propTypes: { - dispatch: React.PropTypes.func.isRequired, intl: React.PropTypes.object.isRequired }, mixins: [PureRenderMixin], - componentWillMount () { - this.props.dispatch(refreshTimeline('home')); - }, - render () { const { intl } = this.props; @@ -36,4 +29,4 @@ const HomeTimeline = React.createClass({ }); -export default connect()(injectIntl(HomeTimeline)); +export default injectIntl(HomeTimeline); diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx index 29be491eb..d243f178f 100644 --- a/app/assets/javascripts/components/features/notifications/index.jsx +++ b/app/assets/javascripts/components/features/notifications/index.jsx @@ -2,10 +2,7 @@ import { connect } from 'react-redux'; import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; -import { - refreshNotifications, - expandNotifications -} from '../../actions/notifications'; +import { expandNotifications } from '../../actions/notifications'; import NotificationContainer from './containers/notification_container'; import { ScrollContainer } from 'react-router-scroll'; import { defineMessages, injectIntl } from 'react-intl'; @@ -43,11 +40,6 @@ const Notifications = React.createClass({ mixins: [PureRenderMixin], - componentWillMount () { - const { dispatch } = this.props; - dispatch(refreshNotifications()); - }, - handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index ee2e29d6f..003d061ad 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -8,10 +8,12 @@ import Compose from '../compose'; import TabsBar from './components/tabs_bar'; import ModalContainer from './containers/modal_container'; import Notifications from '../notifications'; +import { connect } from 'react-redux'; +import { isMobile } from '../../is_mobile'; import { debounce } from 'react-decoration'; import { uploadCompose } from '../../actions/compose'; -import { connect } from 'react-redux'; -import { isMobile } from '../../is_mobile' +import { refreshTimeline } from '../../actions/timelines'; +import { refreshNotifications } from '../../actions/notifications'; const UI = React.createClass({ @@ -56,6 +58,9 @@ const UI = React.createClass({ window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('dragover', this.handleDragOver); window.addEventListener('drop', this.handleDrop); + + this.props.dispatch(refreshTimeline('home')); + this.props.dispatch(refreshNotifications()); }, componentWillUnmount () { diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx index 73dee9078..409dfd663 100644 --- a/app/assets/javascripts/components/reducers/accounts.jsx +++ b/app/assets/javascripts/components/reducers/accounts.jsx @@ -6,7 +6,9 @@ import { FOLLOWING_EXPAND_SUCCESS, ACCOUNT_TIMELINE_FETCH_SUCCESS, ACCOUNT_TIMELINE_EXPAND_SUCCESS, - FOLLOW_REQUESTS_FETCH_SUCCESS + FOLLOW_REQUESTS_FETCH_SUCCESS, + ACCOUNT_FOLLOW_SUCCESS, + ACCOUNT_UNFOLLOW_SUCCESS } from '../actions/accounts'; import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; import { @@ -105,6 +107,10 @@ export default function accounts(state = initialState, action) { case TIMELINE_UPDATE: case STATUS_FETCH_SUCCESS: return normalizeAccountFromStatus(state, action.status); + case ACCOUNT_FOLLOW_SUCCESS: + return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); + case ACCOUNT_UNFOLLOW_SUCCESS: + return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1)); default: return state; } -- cgit From 8aab6920348f5e9d4738a20c1cf4b1b74824960b Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 23 Jan 2017 21:40:48 +0100 Subject: Less re-rendering --- .../javascripts/components/features/ui/containers/modal_container.jsx | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index 66dfe915e..53d162462 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -3,6 +3,7 @@ import { closeModal } from '../../../actions/modal'; import Lightbox from '../../../components/lightbox'; import ImageLoader from 'react-imageloader'; import LoadingIndicator from '../../../components/loading_indicator'; +import PureRenderMixin from 'react-addons-pure-render-mixin'; const mapStateToProps = state => ({ url: state.getIn(['modal', 'url']), @@ -46,6 +47,8 @@ const Modal = React.createClass({ onOverlayClicked: React.PropTypes.func }, + mixins: [PureRenderMixin], + render () { const { url, ...other } = this.props; -- cgit From d9022884c6c4e066b1e6ee8d9c07c220c031454e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 24 Jan 2017 04:12:10 +0100 Subject: Smarter infinite scroll --- .../javascripts/components/actions/accounts.jsx | 11 +++++-- .../javascripts/components/actions/cards.jsx | 2 +- .../javascripts/components/actions/timelines.jsx | 7 +++- .../components/components/status_list.jsx | 37 ++++++++++++++++------ .../components/features/account_timeline/index.jsx | 8 +++-- .../ui/containers/status_list_container.jsx | 3 +- .../javascripts/components/reducers/timelines.jsx | 30 +++++++++++++++--- 7 files changed, 74 insertions(+), 24 deletions(-) (limited to 'app/assets/javascripts/components/features/ui') diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx index 235f29194..0be05034e 100644 --- a/app/assets/javascripts/components/actions/accounts.jsx +++ b/app/assets/javascripts/components/actions/accounts.jsx @@ -80,7 +80,7 @@ export function fetchAccount(id) { export function fetchAccountTimeline(id, replace = false) { return (dispatch, getState) => { - const ids = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List()); + const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()); const newestId = ids.size > 0 ? ids.first() : null; let params = ''; @@ -103,11 +103,16 @@ export function fetchAccountTimeline(id, replace = false) { export function expandAccountTimeline(id) { return (dispatch, getState) => { - const lastId = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List()).last(); + const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last(); dispatch(expandAccountTimelineRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/statuses?max_id=${lastId}`).then(response => { + api(getState).get(`/api/v1/accounts/${id}/statuses`, { + params: { + limit: 10, + max_id: lastId + } + }).then(response => { dispatch(expandAccountTimelineSuccess(id, response.data)); }).catch(error => { dispatch(expandAccountTimelineFail(id, error)); diff --git a/app/assets/javascripts/components/actions/cards.jsx b/app/assets/javascripts/components/actions/cards.jsx index 845d15e93..503c2bfeb 100644 --- a/app/assets/javascripts/components/actions/cards.jsx +++ b/app/assets/javascripts/components/actions/cards.jsx @@ -9,7 +9,7 @@ export function fetchStatusCard(id) { dispatch(fetchStatusCardRequest(id)); api(getState).get(`/api/v1/statuses/${id}/card`).then(response => { - if (!response.data.url) { + if (!response.data.url || !response.data.title || !response.data.description) { return; } diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx index 72e949e87..1d7e0547c 100644 --- a/app/assets/javascripts/components/actions/timelines.jsx +++ b/app/assets/javascripts/components/actions/timelines.jsx @@ -115,7 +115,12 @@ export function expandTimeline(timeline, id = null) { path = `${path}/${id}` } - api(getState).get(`/api/v1/timelines/${path}?max_id=${lastId}`).then(response => { + api(getState).get(`/api/v1/timelines/${path}`, { + params: { + limit: 10, + max_id: lastId + } + }).then(response => { dispatch(expandTimelineSuccess(timeline, response.data)); }).catch(error => { dispatch(expandTimelineFail(timeline, error)); diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx index e0a73435f..69cc013f2 100644 --- a/app/assets/javascripts/components/components/status_list.jsx +++ b/app/assets/javascripts/components/components/status_list.jsx @@ -11,7 +11,8 @@ const StatusList = React.createClass({ onScrollToBottom: React.PropTypes.func, onScrollToTop: React.PropTypes.func, onScroll: React.PropTypes.func, - trackScroll: React.PropTypes.bool + trackScroll: React.PropTypes.bool, + isLoading: React.PropTypes.bool }, getDefaultProps () { @@ -24,10 +25,10 @@ const StatusList = React.createClass({ handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; - + const offset = scrollHeight - scrollTop - clientHeight; this._oldScrollPosition = scrollHeight - scrollTop; - if (scrollTop === scrollHeight - clientHeight && this.props.onScrollToBottom) { + if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) { this.props.onScrollToBottom(); } else if (scrollTop < 100 && this.props.onScrollToTop) { this.props.onScrollToTop(); @@ -36,21 +37,37 @@ const StatusList = React.createClass({ } }, - componentDidUpdate (prevProps) { - if (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && this._oldScrollPosition) { - const node = ReactDOM.findDOMNode(this); + componentDidMount () { + this.attachScrollListener(); + }, - if (node.scrollTop > 0) { - node.scrollTop = node.scrollHeight - this._oldScrollPosition; - } + componentDidUpdate (prevProps) { + if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) { + this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; } }, + componentWillUnmount () { + this.detachScrollListener(); + }, + + attachScrollListener () { + this.node.addEventListener('scroll', this.handleScroll); + }, + + detachScrollListener () { + this.node.removeEventListener('scroll', this.handleScroll); + }, + + setRef (c) { + this.node = c; + }, + render () { const { statusIds, onScrollToBottom, trackScroll } = this.props; const scrollableArea = ( -
+
{statusIds.map((statusId) => { return ; diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx index 4a66dbbf5..5c09839f7 100644 --- a/app/assets/javascripts/components/features/account_timeline/index.jsx +++ b/app/assets/javascripts/components/features/account_timeline/index.jsx @@ -9,7 +9,8 @@ import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId)]), + statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items']), + isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']), me: state.getIn(['meta', 'me']) }); @@ -19,6 +20,7 @@ const AccountTimeline = React.createClass({ params: React.PropTypes.object.isRequired, dispatch: React.PropTypes.func.isRequired, statusIds: ImmutablePropTypes.list, + isLoading: React.PropTypes.bool, me: React.PropTypes.number.isRequired }, @@ -39,13 +41,13 @@ const AccountTimeline = React.createClass({ }, render () { - const { statusIds, me } = this.props; + const { statusIds, isLoading, me } = this.props; if (!statusIds) { return ; } - return + return } }); diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx index ffb6f6e79..8af7b0c3c 100644 --- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx @@ -33,7 +33,8 @@ const getStatusIds = createSelector([ })); const mapStateToProps = (state, props) => ({ - statusIds: getStatusIds(state, props) + statusIds: getStatusIds(state, props), + isLoading: state.getIn(['timelines', props.type, 'isLoading'], true) }); const mapDispatchToProps = (dispatch, { type, id }) => ({ diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index c5e3a253f..6b1093651 100644 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ b/app/assets/javascripts/components/reducers/timelines.jsx @@ -4,6 +4,7 @@ import { TIMELINE_UPDATE, TIMELINE_DELETE, TIMELINE_EXPAND_SUCCESS, + TIMELINE_EXPAND_REQUEST, TIMELINE_SCROLL_TOP } from '../actions/timelines'; import { @@ -13,37 +14,41 @@ import { UNFAVOURITE_SUCCESS } from '../actions/interactions'; import { - ACCOUNT_FETCH_SUCCESS, + ACCOUNT_TIMELINE_FETCH_REQUEST, ACCOUNT_TIMELINE_FETCH_SUCCESS, + ACCOUNT_TIMELINE_EXPAND_REQUEST, ACCOUNT_TIMELINE_EXPAND_SUCCESS, ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; import { - STATUS_FETCH_SUCCESS, CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; import Immutable from 'immutable'; const initialState = Immutable.Map({ home: Immutable.Map({ + isLoading: false, loaded: false, top: true, items: Immutable.List() }), mentions: Immutable.Map({ + isLoading: false, loaded: false, top: true, items: Immutable.List() }), public: Immutable.Map({ + isLoading: false, loaded: false, top: true, items: Immutable.List() }), tag: Immutable.Map({ + isLoading: false, id: null, loaded: false, top: true, @@ -82,6 +87,7 @@ const normalizeTimeline = (state, timeline, statuses, replace = false) => { }); state = state.setIn([timeline, 'loaded'], true); + state = state.setIn([timeline, 'isLoading'], false); return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids)); }; @@ -94,6 +100,8 @@ const appendNormalizedTimeline = (state, timeline, statuses) => { moreIds = moreIds.set(i, status.get('id')); }); + state = state.setIn([timeline, 'isLoading'], false); + return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds)); }; @@ -105,7 +113,10 @@ const normalizeAccountTimeline = (state, accountId, statuses, replace = false) = ids = ids.set(i, status.get('id')); }); - return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => (replace ? ids : list.unshift(...ids))); + return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map + .set('isLoading', false) + .set('loaded', true) + .update('items', Immutable.List(), list => (replace ? ids : list.unshift(...ids)))); }; const appendNormalizedAccountTimeline = (state, accountId, statuses) => { @@ -116,7 +127,9 @@ const appendNormalizedAccountTimeline = (state, accountId, statuses) => { moreIds = moreIds.set(i, status.get('id')); }); - return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.push(...moreIds)); + return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map + .set('isLoading', false) + .update('items', list => list.push(...moreIds))); }; const updateTimeline = (state, timeline, status, references) => { @@ -157,7 +170,7 @@ const deleteStatus = (state, id, accountId, references, reblogOf) => { }); // Remove references from account timelines - state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.filterNot(item => item === id)); + state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id)); // Remove references from context state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => { @@ -207,8 +220,11 @@ const resetTimeline = (state, timeline, id) => { if (timeline === 'tag' && state.getIn([timeline, 'id']) !== id) { state = state.update(timeline, map => map .set('id', id) + .set('isLoading', true) .set('loaded', false) .update('items', list => list.clear())); + } else { + state = state.setIn([timeline, 'isLoading'], true); } return state; @@ -217,6 +233,7 @@ const resetTimeline = (state, timeline, id) => { export default function timelines(state = initialState, action) { switch(action.type) { case TIMELINE_REFRESH_REQUEST: + case TIMELINE_EXPAND_REQUEST: return resetTimeline(state, action.timeline, action.id); case TIMELINE_REFRESH_SUCCESS: return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses)); @@ -228,6 +245,9 @@ export default function timelines(state = initialState, action) { return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants)); + case ACCOUNT_TIMELINE_FETCH_REQUEST: + case ACCOUNT_TIMELINE_EXPAND_REQUEST: + return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true)); case ACCOUNT_TIMELINE_FETCH_SUCCESS: return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace); case ACCOUNT_TIMELINE_EXPAND_SUCCESS: -- cgit