about summary refs log tree commit diff
path: root/app/assets/javascripts/components/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/components/features')
-rw-r--r--app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx68
-rw-r--r--app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx41
-rw-r--r--app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx21
-rw-r--r--app/assets/javascripts/components/features/home_timeline/index.jsx5
-rw-r--r--app/assets/javascripts/components/features/notifications/components/column_settings.jsx145
-rw-r--r--app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx32
-rw-r--r--app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx6
-rw-r--r--app/assets/javascripts/components/features/ui/containers/status_list_container.jsx59
8 files changed, 251 insertions, 126 deletions
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 (
+      <ColumnCollapsable icon='sliders' fullHeight={209} onCollapse={onSave}>
+        <div style={outerStyle}>
+          <span style={sectionStyle}><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show reblogs' />} />
+          </div>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
+          </div>
+
+          <span style={sectionStyle}><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
+
+          <div style={rowStyle}>
+            <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
+          </div>
+        </div>
+      </ColumnCollapsable>
+    );
+  }
+
+});
+
+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 (
+      <input
+        style={style}
+        className='setting-text'
+        value={settings.getIn(settingKey)}
+        onChange={this.handleChange}
+        placeholder={label}
+      />
+    );
+  }
+
+});
+
+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 (
       <Column icon='home' heading={intl.formatMessage(messages.title)}>
+        <ColumnSettingsContainer />
         <StatusListContainer {...this.props} type='home' />
       </Column>
     );
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 = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
     const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
 
     return (
-      <div style={{ position: 'relative' }}>
-        <div style={{...iconStyle, color: collapsed ? '#9baec8' : '#fff', background: collapsed ? '#2f3441' : '#373b4a' }} onClick={this.handleToggleCollapsed}><i className='fa fa-sliders' /></div>
-
-        <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : 458) }}>
-          {({ opacity, height }) =>
-            <div style={{ overflow: 'hidden', height: `${height}px`, opacity: opacity / 100 }}>
-              <div style={outerStyle}>
-                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
-
-                <div style={rowStyle}>
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['alerts', 'follow'])} onChange={this.handleChange.bind(this, ['alerts', 'follow'])} />
-                    <span style={labelSpanStyle}>{alertStr}</span>
-                  </label>
-
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['shows', 'follow'])} onChange={this.handleChange.bind(this, ['shows', 'follow'])} />
-                    <span style={labelSpanStyle}>{showStr}</span>
-                  </label>
-                </div>
-
-                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
-
-                <div style={rowStyle}>
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['alerts', 'favourite'])} onChange={this.handleChange.bind(this, ['alerts', 'favourite'])} />
-                    <span style={labelSpanStyle}>{alertStr}</span>
-                  </label>
-
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['shows', 'favourite'])} onChange={this.handleChange.bind(this, ['shows', 'favourite'])} />
-                    <span style={labelSpanStyle}>{showStr}</span>
-                  </label>
-                </div>
-
-                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
-
-                <div style={rowStyle}>
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['alerts', 'mention'])} onChange={this.handleChange.bind(this, ['alerts', 'mention'])} />
-                    <span style={labelSpanStyle}>{alertStr}</span>
-                  </label>
-
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['shows', 'mention'])} onChange={this.handleChange.bind(this, ['shows', 'mention'])} />
-                    <span style={labelSpanStyle}>{showStr}</span>
-                  </label>
-                </div>
-
-                <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
-
-                <div style={rowStyle}>
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['alerts', 'reblog'])} onChange={this.handleChange.bind(this, ['alerts', 'reblog'])} />
-                    <span style={labelSpanStyle}>{alertStr}</span>
-                  </label>
-
-                  <label style={labelStyle}>
-                    <Toggle checked={settings.getIn(['shows', 'reblog'])} onChange={this.handleChange.bind(this, ['shows', 'reblog'])} />
-                    <span style={labelSpanStyle}>{showStr}</span>
-                  </label>
-                </div>
-              </div>
-            </div>
-          }
-        </Motion>
-      </div>
+      <ColumnCollapsable icon='sliders' fullHeight={458} onCollapse={onSave}>
+        <div style={outerStyle}>
+          <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+            <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
+          </div>
+
+          <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+            <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
+          </div>
+
+          <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+            <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
+          </div>
+
+          <span style={sectionStyle}><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
+
+          <div style={rowStyle}>
+            <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+            <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
+          </div>
+        </div>
+      </ColumnCollapsable>
     );
   }
 
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 }) => (
+  <label style={labelStyle}>
+    <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} />
+    <span style={labelSpanStyle}>{label}</span>
+  </label>
+);
+
+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);