From fdc1a051d3ee1f19f53570d3de474d232d7c58ec Mon Sep 17 00:00:00 2001 From: Curtis ROck Date: Mon, 14 Sep 2020 22:07:33 -0500 Subject: [PATCH 01/43] Rough out import follows page and actions --- app/soapbox/actions/import_follows.js | 18 + app/soapbox/components/sidebar_menu.js | 5 + .../features/compose/components/action_bar.js | 2 + app/soapbox/features/import_follows/index.js | 352 ++++++++++++++++++ app/soapbox/features/ui/index.js | 2 + .../features/ui/util/async-components.js | 4 + 6 files changed, 383 insertions(+) create mode 100644 app/soapbox/actions/import_follows.js create mode 100644 app/soapbox/features/import_follows/index.js diff --git a/app/soapbox/actions/import_follows.js b/app/soapbox/actions/import_follows.js new file mode 100644 index 000000000..7d794dfd0 --- /dev/null +++ b/app/soapbox/actions/import_follows.js @@ -0,0 +1,18 @@ +import api from '../api'; + +export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; +export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; +export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; + +export function importFollows(params) { + return (dispatch, getState) => { + dispatch({ type: IMPORT_FOLLOWS_REQUEST }); + return api(getState) + .post('/api/pleroma/follow_import', params) + .then(response => { + dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data }); + }).catch(error => { + dispatch({ type: IMPORT_FOLLOWS_FAIL, error }); + }); + }; +} diff --git a/app/soapbox/components/sidebar_menu.js b/app/soapbox/components/sidebar_menu.js index 6cc615c65..f81bc298f 100644 --- a/app/soapbox/components/sidebar_menu.js +++ b/app/soapbox/components/sidebar_menu.js @@ -29,6 +29,7 @@ const messages = defineMessages({ filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' }, soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' }, + import_follows: { id: 'navigation_bar.import_follows', defaultMessage: 'Import follows' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, lists: { id: 'column.lists', defaultMessage: 'Lists' }, @@ -184,6 +185,10 @@ class SidebarMenu extends ImmutablePureComponent { {intl.formatMessage(messages.security)} + + + {intl.formatMessage(messages.import_follows)} +
diff --git a/app/soapbox/features/compose/components/action_bar.js b/app/soapbox/features/compose/components/action_bar.js index 8bc6ef4c4..32e8562d7 100644 --- a/app/soapbox/features/compose/components/action_bar.js +++ b/app/soapbox/features/compose/components/action_bar.js @@ -20,6 +20,7 @@ const messages = defineMessages({ filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' }, soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' }, + import_follows: { id: 'navigation_bar.import_follows', defaultMessage: 'Import follows' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' }, @@ -85,6 +86,7 @@ class ActionBar extends React.PureComponent { } menu.push({ text: intl.formatMessage(messages.preferences), to: '/settings/preferences' }); menu.push({ text: intl.formatMessage(messages.security), to: '/auth/edit' }); + menu.push({ text: intl.formatMessage(messages.import_follows), to: '/settings/import' }); menu.push({ text: intl.formatMessage(messages.logout), to: '/auth/sign_out', action: onClickLogOut }); return ( diff --git a/app/soapbox/features/import_follows/index.js b/app/soapbox/features/import_follows/index.js new file mode 100644 index 000000000..1ca095ed5 --- /dev/null +++ b/app/soapbox/features/import_follows/index.js @@ -0,0 +1,352 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +// import ImmutablePropTypes from 'react-immutable-proptypes'; +import Column from '../ui/components/column'; +import { + SimpleForm, + FieldsGroup, + // TextInput, + // Checkbox, + FileChooser, + // SimpleTextarea, + // ColorWithPicker, + // FileChooserLogo, +} from 'soapbox/features/forms'; +// import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import { importFollows } from 'soapbox/actions/import_follows'; +// import Icon from 'soapbox/components/icon'; +// import { defaultConfig } from 'soapbox/actions/soapbox'; +import { uploadMedia } from 'soapbox/actions/media'; + +const messages = defineMessages({ + heading: { id: 'column.import_follows', defaultMessage: 'Import follows' }, + // copyrightFooterLabel: { id: 'soapbox_config.copyright_footer.meta_fields.label_placeholder', defaultMessage: 'Copyright footer' }, + // promoItemIcon: { id: 'soapbox_config.promo_panel.meta_fields.icon_placeholder', defaultMessage: 'Icon' }, + // promoItemLabel: { id: 'soapbox_config.promo_panel.meta_fields.label_placeholder', defaultMessage: 'Label' }, + // promoItemURL: { id: 'soapbox_config.promo_panel.meta_fields.url_placeholder', defaultMessage: 'URL' }, + // homeFooterItemLabel: { id: 'soapbox_config.home_footer.meta_fields.label_placeholder', defaultMessage: 'Label' }, + // homeFooterItemURL: { id: 'soapbox_config.home_footer.meta_fields.url_placeholder', defaultMessage: 'URL' }, + // customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' }, + // rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Raw JSON data' }, + // rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Advanced: Edit the settings data directly.' }, +}); + +// const templates = { +// promoPanelItem: ImmutableMap({ icon: '', text: '', url: '' }), +// footerItem: ImmutableMap({ title: '', url: '' }), +// }; + +const mapStateToProps = state => ({ + follows: state.get('follows'), +}); + +export default @connect(mapStateToProps) +@injectIntl +class ImportFollows extends ImmutablePureComponent { + + static propTypes = { + follows: PropTypes.string, + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + isLoading: false, + follows: this.props.follows, + // rawJSON: JSON.stringify(this.props.soapbox, null, 2), + // jsonValid: true, + } + + setConfig = (value) => { + // const { follows } = this.state; + // const config = soapbox.setIn(path, value); + this.setState({ follows: value }); + }; + + putConfig = config => { + this.setState({ soapbox: config, jsonValid: true }); + }; + + getParams = () => { + const { soapbox } = this.state; + return { + configs: [{ + group: ':pleroma', + key: ':frontend_configurations', + value: [{ + tuple: [':soapbox_fe', soapbox.toJS()], + }], + }], + }; + } + + handleSubmit = (event) => { + const { dispatch } = this.props; + dispatch(importFollows(this.getParams())).then(() => { + this.setState({ isLoading: false }); + }).catch((error) => { + this.setState({ isLoading: false }); + }); + this.setState({ isLoading: true }); + event.preventDefault(); + } + + handleChange = (path, getValue) => { + return e => { + this.setConfig(path, getValue(e)); + }; + }; + + handleFileChange = path => { + return e => { + const data = new FormData(); + data.append('file', e.target.files[0]); + this.props.dispatch(uploadMedia(data)).then(({ data }) => { + this.handleChange(path, e => data.url)(e); + }).catch(() => {}); + }; + }; + + handleAddItem = (path, template) => { + return e => { + this.setConfig( + path, + this.getSoapboxConfig().getIn(path, ImmutableList()).push(template), + ); + }; + }; + + handleDeleteItem = path => { + return e => { + const soapbox = this.state.soapbox.deleteIn(path); + this.setState({ soapbox }); + }; + }; + + handleItemChange = (path, key, field, template) => { + return this.handleChange( + path, (e) => + template + .merge(field) + .set(key, e.target.value) + ); + }; + + handlePromoItemChange = (index, key, field) => { + return this.handleItemChange( + ['promoPanel', 'items', index], key, field, templates.promoPanelItem + ); + }; + + handleHomeFooterItemChange = (index, key, field) => { + return this.handleItemChange( + ['navlinks', 'homeFooter', index], key, field, templates.footerItem + ); + }; + + handleEditJSON = e => { + this.setState({ rawJSON: e.target.value }); + } + + getSoapboxConfig = () => { + return defaultConfig.mergeDeep(this.state.soapbox); + } + + componentDidUpdate(prevProps, prevState) { + if (prevProps.soapbox !== this.props.soapbox) { + this.putConfig(this.props.soapbox); + } + + if (prevState.soapbox !== this.state.soapbox) { + this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) }); + } + + if (prevState.rawJSON !== this.state.rawJSON) { + try { + const data = fromJS(JSON.parse(this.state.rawJSON)); + this.putConfig(data); + } catch { + this.setState({ jsonValid: false }); + } + } + } + + render() { + const { intl } = this.props; + const soapbox = this.getSoapboxConfig(); + + return ( + + +
+ +
+
+ +
+
+ } + name='banner' + hint={} + onChange={this.handleFileChange(['banner'])} + /> +
+
+
+ +
+ } + value={soapbox.get('brandColor')} + onChange={this.handleChange(['brandColor'], (e) => e.hex)} + /> +
+
+ + } + hint={} + name='patron' + checked={soapbox.getIn(['extensions', 'patron', 'enabled'])} + onChange={this.handleChange( + ['extensions', 'patron', 'enabled'], (e) => e.checked, + )} + /> + + + e.target.value)} + /> + + +
+
+ + + + + + Soapbox Icons List }} /> + + { + soapbox.getIn(['promoPanel', 'items']).map((field, i) => ( +
+ + + + +
+ )) + } +
+
+ + +
+
+
+
+ + + + + { + soapbox.getIn(['navlinks', 'homeFooter']).map((field, i) => ( +
+ + + +
+ )) + } +
+
+ + +
+
+
+
+
+ + + + + { + soapbox.get('customCss').map((field, i) => ( +
+ e.target.value)} + /> + +
+ )) + } +
+
+ + +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+ ); + } + +} diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 3f37ffe40..f7c00a442 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -78,6 +78,7 @@ import { Preferences, EditProfile, SoapboxConfig, + ImportFollows, PasswordReset, SecurityForm, MfaForm, @@ -264,6 +265,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 73a8a5540..3da609a38 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -186,6 +186,10 @@ export function SoapboxConfig() { return import(/* webpackChunkName: "features/soapbox_config" */'../../soapbox_config'); } +export function ImportFollows() { + return import(/* webpackChunkName: "features/import_follows" */'../../import_follows'); +} + export function PasswordReset() { return import(/* webpackChunkName: "features/auth_login" */'../../auth_login/components/password_reset'); } From 1b8afd22486fbdd07c031009556ed700ebb77c3a Mon Sep 17 00:00:00 2001 From: crockwave Date: Thu, 17 Sep 2020 17:33:18 -0500 Subject: [PATCH 02/43] Convert newline delimited data to space delimited data --- app/soapbox/actions/import_follows.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/soapbox/actions/import_follows.js b/app/soapbox/actions/import_follows.js index 7d794dfd0..c2b256e4c 100644 --- a/app/soapbox/actions/import_follows.js +++ b/app/soapbox/actions/import_follows.js @@ -4,11 +4,16 @@ export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; +function whiteSpace(params) { + const follows = params.replace(/\n/g, ' '); + return follows; +}; + export function importFollows(params) { return (dispatch, getState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); return api(getState) - .post('/api/pleroma/follow_import', params) + .post('/api/pleroma/follow_import', whiteSpace(params)) .then(response => { dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data }); }).catch(error => { From 3c26d4ed73ce5b97cddececf4aa0e50c307b5abe Mon Sep 17 00:00:00 2001 From: crockwave Date: Fri, 18 Sep 2020 18:23:58 -0500 Subject: [PATCH 03/43] Refined import follows. Breaks on Save event --- app/soapbox/features/forms/index.js | 8 + app/soapbox/features/import_follows/index.js | 295 ++----------------- 2 files changed, 39 insertions(+), 264 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index e6e296818..090914ef8 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -362,3 +362,11 @@ export const FileChooserLogo = props => ( FileChooserLogo.defaultProps = { accept: ['image/svg', 'image/png'], }; + +export const FileChooserCSV = props => ( + +); + +FileChooserCSV.defaultProps = { + accept: ['.csv'], +}; diff --git a/app/soapbox/features/import_follows/index.js b/app/soapbox/features/import_follows/index.js index 1ca095ed5..c16d60c7b 100644 --- a/app/soapbox/features/import_follows/index.js +++ b/app/soapbox/features/import_follows/index.js @@ -1,50 +1,27 @@ import React from 'react'; -import { connect } from 'react-redux'; +// import { connect } from 'react-redux'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; -// import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; import { SimpleForm, FieldsGroup, - // TextInput, - // Checkbox, - FileChooser, - // SimpleTextarea, - // ColorWithPicker, - // FileChooserLogo, + FileChooserCSV, } from 'soapbox/features/forms'; -// import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { importFollows } from 'soapbox/actions/import_follows'; -// import Icon from 'soapbox/components/icon'; -// import { defaultConfig } from 'soapbox/actions/soapbox'; import { uploadMedia } from 'soapbox/actions/media'; const messages = defineMessages({ heading: { id: 'column.import_follows', defaultMessage: 'Import follows' }, - // copyrightFooterLabel: { id: 'soapbox_config.copyright_footer.meta_fields.label_placeholder', defaultMessage: 'Copyright footer' }, - // promoItemIcon: { id: 'soapbox_config.promo_panel.meta_fields.icon_placeholder', defaultMessage: 'Icon' }, - // promoItemLabel: { id: 'soapbox_config.promo_panel.meta_fields.label_placeholder', defaultMessage: 'Label' }, - // promoItemURL: { id: 'soapbox_config.promo_panel.meta_fields.url_placeholder', defaultMessage: 'URL' }, - // homeFooterItemLabel: { id: 'soapbox_config.home_footer.meta_fields.label_placeholder', defaultMessage: 'Label' }, - // homeFooterItemURL: { id: 'soapbox_config.home_footer.meta_fields.url_placeholder', defaultMessage: 'URL' }, - // customCssLabel: { id: 'soapbox_config.custom_css.meta_fields.url_placeholder', defaultMessage: 'URL' }, - // rawJSONLabel: { id: 'soapbox_config.raw_json_label', defaultMessage: 'Raw JSON data' }, - // rawJSONHint: { id: 'soapbox_config.raw_json_hint', defaultMessage: 'Advanced: Edit the settings data directly.' }, }); -// const templates = { -// promoPanelItem: ImmutableMap({ icon: '', text: '', url: '' }), -// footerItem: ImmutableMap({ title: '', url: '' }), -// }; +// const mapStateToProps = state => ({ +// follows: state.get('follows'), +// }); -const mapStateToProps = state => ({ - follows: state.get('follows'), -}); - -export default @connect(mapStateToProps) -@injectIntl +// export default @connect(mapStateToProps) +export default @injectIntl class ImportFollows extends ImmutablePureComponent { static propTypes = { @@ -56,31 +33,19 @@ class ImportFollows extends ImmutablePureComponent { state = { isLoading: false, follows: this.props.follows, - // rawJSON: JSON.stringify(this.props.soapbox, null, 2), - // jsonValid: true, } setConfig = (value) => { - // const { follows } = this.state; - // const config = soapbox.setIn(path, value); this.setState({ follows: value }); }; - putConfig = config => { - this.setState({ soapbox: config, jsonValid: true }); - }; + // putConfig = config => { + // this.setState({ soapbox: config, jsonValid: true }); + // }; getParams = () => { - const { soapbox } = this.state; - return { - configs: [{ - group: ':pleroma', - key: ':frontend_configurations', - value: [{ - tuple: [':soapbox_fe', soapbox.toJS()], - }], - }], - }; + const { follows } = this.state; + return { follows: follows.toJS() }; } handleSubmit = (event) => { @@ -94,89 +59,35 @@ class ImportFollows extends ImmutablePureComponent { event.preventDefault(); } - handleChange = (path, getValue) => { + handleChange = (getValue) => { return e => { - this.setConfig(path, getValue(e)); + this.setConfig(getValue(e)); }; }; + // handleUpload = (event) => { + // const { dispatch } = this.props; + // dispatch(importFollows(event.target.files[0])).then(() => { + // this.setState({ isLoading: false }); + // }).catch((error) => { + // this.setState({ isLoading: false }); + // }); + // this.setState({ isLoading: true }); + // event.preventDefault(); + // } + handleFileChange = path => { return e => { const data = new FormData(); data.append('file', e.target.files[0]); this.props.dispatch(uploadMedia(data)).then(({ data }) => { - this.handleChange(path, e => data.url)(e); + this.handleChange(e => data.url)(e); }).catch(() => {}); }; }; - handleAddItem = (path, template) => { - return e => { - this.setConfig( - path, - this.getSoapboxConfig().getIn(path, ImmutableList()).push(template), - ); - }; - }; - - handleDeleteItem = path => { - return e => { - const soapbox = this.state.soapbox.deleteIn(path); - this.setState({ soapbox }); - }; - }; - - handleItemChange = (path, key, field, template) => { - return this.handleChange( - path, (e) => - template - .merge(field) - .set(key, e.target.value) - ); - }; - - handlePromoItemChange = (index, key, field) => { - return this.handleItemChange( - ['promoPanel', 'items', index], key, field, templates.promoPanelItem - ); - }; - - handleHomeFooterItemChange = (index, key, field) => { - return this.handleItemChange( - ['navlinks', 'homeFooter', index], key, field, templates.footerItem - ); - }; - - handleEditJSON = e => { - this.setState({ rawJSON: e.target.value }); - } - - getSoapboxConfig = () => { - return defaultConfig.mergeDeep(this.state.soapbox); - } - - componentDidUpdate(prevProps, prevState) { - if (prevProps.soapbox !== this.props.soapbox) { - this.putConfig(this.props.soapbox); - } - - if (prevState.soapbox !== this.state.soapbox) { - this.setState({ rawJSON: JSON.stringify(this.state.soapbox, null, 2) }); - } - - if (prevState.rawJSON !== this.state.rawJSON) { - try { - const data = fromJS(JSON.parse(this.state.rawJSON)); - this.putConfig(data); - } catch { - this.setState({ jsonValid: false }); - } - } - } - render() { const { intl } = this.props; - const soapbox = this.getSoapboxConfig(); return ( @@ -184,160 +95,16 @@ class ImportFollows extends ImmutablePureComponent {
-
- -
- } - name='banner' - hint={} - onChange={this.handleFileChange(['banner'])} + } + name='follows' + hint={} + onChange={this.handleFileChange('follows')} />
- -
- } - value={soapbox.get('brandColor')} - onChange={this.handleChange(['brandColor'], (e) => e.hex)} - /> -
-
- - } - hint={} - name='patron' - checked={soapbox.getIn(['extensions', 'patron', 'enabled'])} - onChange={this.handleChange( - ['extensions', 'patron', 'enabled'], (e) => e.checked, - )} - /> - - - e.target.value)} - /> - - -
-
- - - - - - Soapbox Icons List }} /> - - { - soapbox.getIn(['promoPanel', 'items']).map((field, i) => ( -
- - - - -
- )) - } -
-
- - -
-
-
-
- - - - - { - soapbox.getIn(['navlinks', 'homeFooter']).map((field, i) => ( -
- - - -
- )) - } -
-
- - -
-
-
-
-
- - - - - { - soapbox.get('customCss').map((field, i) => ( -
- e.target.value)} - /> - -
- )) - } -
-
- - -
-
-
-
- -
- -
-
diff --git a/app/soapbox/features/compose/components/action_bar.js b/app/soapbox/features/compose/components/action_bar.js index 32e8562d7..863d49f22 100644 --- a/app/soapbox/features/compose/components/action_bar.js +++ b/app/soapbox/features/compose/components/action_bar.js @@ -20,7 +20,7 @@ const messages = defineMessages({ filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' }, admin_settings: { id: 'navigation_bar.admin_settings', defaultMessage: 'Admin settings' }, soapbox_config: { id: 'navigation_bar.soapbox_config', defaultMessage: 'Soapbox config' }, - import_follows: { id: 'navigation_bar.import_follows', defaultMessage: 'Import follows' }, + import_data: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' }, security: { id: 'navigation_bar.security', defaultMessage: 'Security' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' }, @@ -85,8 +85,8 @@ class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.soapbox_config), to: '/soapbox/config' }); } menu.push({ text: intl.formatMessage(messages.preferences), to: '/settings/preferences' }); + menu.push({ text: intl.formatMessage(messages.import_data), to: '/settings/import' }); menu.push({ text: intl.formatMessage(messages.security), to: '/auth/edit' }); - menu.push({ text: intl.formatMessage(messages.import_follows), to: '/settings/import' }); menu.push({ text: intl.formatMessage(messages.logout), to: '/auth/sign_out', action: onClickLogOut }); return ( diff --git a/app/soapbox/features/import_follows/index.js b/app/soapbox/features/import_data/index.js similarity index 77% rename from app/soapbox/features/import_follows/index.js rename to app/soapbox/features/import_data/index.js index c612a2cb0..39e305c64 100644 --- a/app/soapbox/features/import_follows/index.js +++ b/app/soapbox/features/import_data/index.js @@ -9,19 +9,14 @@ import { FieldsGroup, FileChooserCSV, } from 'soapbox/features/forms'; -import { importFollows } from 'soapbox/actions/import_follows'; +import { importFollows } from 'soapbox/actions/import_data'; const messages = defineMessages({ - heading: { id: 'column.import_follows', defaultMessage: 'Import follows' }, + heading: { id: 'column.import_data', defaultMessage: 'Import data' }, }); -const mapStateToProps = state => ({ - follows: state.get('follows'), -}); - -export default @connect(mapStateToProps) -@injectIntl -class ImportFollows extends ImmutablePureComponent { +export default @injectIntl +class ImportData extends ImmutablePureComponent { constructor(props) { super(props); @@ -71,16 +66,16 @@ class ImportFollows extends ImmutablePureComponent { const { intl } = this.props; return ( - +
} + label={} name='follows' - hint={} + hint={} onChange={this.handleFileChange('follows')} />
diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index f7c00a442..70b891ce8 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -78,7 +78,7 @@ import { Preferences, EditProfile, SoapboxConfig, - ImportFollows, + ImportData, PasswordReset, SecurityForm, MfaForm, @@ -264,8 +264,8 @@ class SwitchingColumnsArea extends React.PureComponent { + - diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 3da609a38..724a33e39 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -186,8 +186,8 @@ export function SoapboxConfig() { return import(/* webpackChunkName: "features/soapbox_config" */'../../soapbox_config'); } -export function ImportFollows() { - return import(/* webpackChunkName: "features/import_follows" */'../../import_follows'); +export function ImportData() { + return import(/* webpackChunkName: "features/import_follows" */'../../import_data'); } export function PasswordReset() { From 1ebc679b8bbb49e0c81141354653d05db4c5ee70 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 11:41:46 -0500 Subject: [PATCH 08/43] ImportData: POST CSV directly instead of reading it --- app/soapbox/features/forms/index.js | 2 +- app/soapbox/features/import_data/index.js | 38 ++++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index 82e778e31..bd1b93252 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -270,5 +270,5 @@ export const FileChooserCSV = props => ( ); FileChooserCSV.defaultProps = { - accept: ['.csv'], + accept: ['text/csv'], }; diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index 39e305c64..e414b26e2 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -15,52 +15,40 @@ const messages = defineMessages({ heading: { id: 'column.import_data', defaultMessage: 'Import data' }, }); -export default @injectIntl +export default @connect() +@injectIntl class ImportData extends ImmutablePureComponent { - constructor(props) { - super(props); - this.state = { - list: null, - }; - } - static propTypes = { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; state = { + followsCSV: null, isLoading: false, } handleSubmit = (event) => { const { dispatch } = this.props; + let params = new FormData(); - params.append('list', this.state.list); + params.append('list', this.state.followsCSV); + + this.setState({ isLoading: true }); dispatch(importFollows(params)).then(() => { this.setState({ isLoading: false }); }).catch((error) => { this.setState({ isLoading: false }); }); - this.setState({ isLoading: true }); + event.preventDefault(); } - handleChange = (e) => { - const content = e.target.result; - this.setState({ - list: content, - }); - }; - - handleFileChange = path => { - return e => { - let fileData = new FileReader(); - fileData.onloadend = this.handleChange; - fileData.readAsText(e.target.files[0]); - }; - }; + handleFileChange = e => { + const [followsCSV] = e.target.files || []; + this.setState({ followsCSV }); + } render() { const { intl } = this.props; @@ -76,7 +64,7 @@ class ImportData extends ImmutablePureComponent { label={} name='follows' hint={} - onChange={this.handleFileChange('follows')} + onChange={this.handleFileChange} />
From a2500d933230b5a2bb5daf3a10ac983120188669 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 11:43:26 -0500 Subject: [PATCH 09/43] ImportData: form validation, Save --> Import --- app/soapbox/features/import_data/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index e414b26e2..8621d7acb 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -65,6 +65,7 @@ class ImportData extends ImmutablePureComponent { name='follows' hint={} onChange={this.handleFileChange} + required />
@@ -72,7 +73,7 @@ class ImportData extends ImmutablePureComponent {
From e6e4a5c447f81aa06dbe95892582d5982963dcb4 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 12:24:38 -0500 Subject: [PATCH 10/43] ImportData: allow importing Blocks --- app/soapbox/actions/import_data.js | 36 ++++++++ .../import_data/components/csv_importer.js | 77 ++++++++++++++++ app/soapbox/features/import_data/index.js | 88 +++++++------------ 3 files changed, 143 insertions(+), 58 deletions(-) create mode 100644 app/soapbox/features/import_data/components/csv_importer.js diff --git a/app/soapbox/actions/import_data.js b/app/soapbox/actions/import_data.js index e80ffa2d2..251d2972d 100644 --- a/app/soapbox/actions/import_data.js +++ b/app/soapbox/actions/import_data.js @@ -5,6 +5,14 @@ export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; +export const IMPORT_BLOCKS_REQUEST = 'IMPORT_BLOCKS_REQUEST'; +export const IMPORT_BLOCKS_SUCCESS = 'IMPORT_BLOCKS_SUCCESS'; +export const IMPORT_BLOCKS_FAIL = 'IMPORT_BLOCKS_FAIL'; + +export const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST'; +export const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS'; +export const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL'; + export function importFollows(params) { return (dispatch, getState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); @@ -18,3 +26,31 @@ export function importFollows(params) { }); }; } + +export function importBlocks(params) { + return (dispatch, getState) => { + dispatch({ type: IMPORT_BLOCKS_REQUEST }); + return api(getState) + .post('/api/pleroma/blocks_import', params) + .then(response => { + dispatch(showAlert('', 'Blocks imported successfully')); + dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data }); + }).catch(error => { + dispatch({ type: IMPORT_BLOCKS_FAIL, error }); + }); + }; +} + +export function importMutes(params) { + return (dispatch, getState) => { + dispatch({ type: IMPORT_MUTES_REQUEST }); + return api(getState) + .post('/api/pleroma/mutes_import', params) + .then(response => { + dispatch(showAlert('', 'Mutes imported successfully')); + dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data }); + }).catch(error => { + dispatch({ type: IMPORT_MUTES_FAIL, error }); + }); + }; +} diff --git a/app/soapbox/features/import_data/components/csv_importer.js b/app/soapbox/features/import_data/components/csv_importer.js new file mode 100644 index 000000000..7571273b3 --- /dev/null +++ b/app/soapbox/features/import_data/components/csv_importer.js @@ -0,0 +1,77 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import { + SimpleForm, + FieldsGroup, + FileChooserCSV, +} from 'soapbox/features/forms'; + +export default @connect() +@injectIntl +class CSVImporter extends ImmutablePureComponent { + + static propTypes = { + action: PropTypes.func.isRequired, + messages: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + file: null, + isLoading: false, + } + + handleSubmit = (event) => { + const { dispatch, action } = this.props; + + let params = new FormData(); + params.append('list', this.state.file); + + this.setState({ isLoading: true }); + dispatch(action(params)).then(() => { + this.setState({ isLoading: false }); + }).catch((error) => { + this.setState({ isLoading: false }); + }); + + event.preventDefault(); + } + + handleFileChange = e => { + const [file] = e.target.files || []; + this.setState({ file }); + } + + render() { + const { intl, messages } = this.props; + + return ( + +
+ +
+
+ +
+
+
+
+
+ +
+
+ ); + } + +} diff --git a/app/soapbox/features/import_data/index.js b/app/soapbox/features/import_data/index.js index 8621d7acb..c1d788ff7 100644 --- a/app/soapbox/features/import_data/index.js +++ b/app/soapbox/features/import_data/index.js @@ -1,82 +1,54 @@ import React from 'react'; -import { connect } from 'react-redux'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import Column from '../ui/components/column'; import { - SimpleForm, - FieldsGroup, - FileChooserCSV, -} from 'soapbox/features/forms'; -import { importFollows } from 'soapbox/actions/import_data'; + importFollows, + importBlocks, + // importMutes, +} from 'soapbox/actions/import_data'; +import CSVImporter from './components/csv_importer'; const messages = defineMessages({ heading: { id: 'column.import_data', defaultMessage: 'Import data' }, + submit: { id: 'import_data.actions.import', defaultMessage: 'Import' }, }); -export default @connect() -@injectIntl +const followMessages = defineMessages({ + input_label: { id: 'import_data.follows_label', defaultMessage: 'Follows' }, + input_hint: { id: 'import_data.hints.follows', defaultMessage: 'CSV file containing a list of followed accounts' }, + submit: { id: 'import_data.actions.import_follows', defaultMessage: 'Import follows' }, +}); + +const blockMessages = defineMessages({ + input_label: { id: 'import_data.blocks_label', defaultMessage: 'Blocks' }, + input_hint: { id: 'import_data.hints.blocks', defaultMessage: 'CSV file containing a list of blocked accounts' }, + submit: { id: 'import_data.actions.import_blocks', defaultMessage: 'Import blocks' }, +}); + +// Not yet supported by Pleroma stable, in develop branch +// const muteMessages = defineMessages({ +// input_label: { id: 'import_data.mutes_label', defaultMessage: 'Mutes' }, +// input_hint: { id: 'import_data.hints.mutes', defaultMessage: 'CSV file containing a list of muted accounts' }, +// submit: { id: 'import_data.actions.import_mutes', defaultMessage: 'Import mutes' }, +// }); + +export default @injectIntl class ImportData extends ImmutablePureComponent { static propTypes = { - dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, }; - state = { - followsCSV: null, - isLoading: false, - } - - handleSubmit = (event) => { - const { dispatch } = this.props; - - let params = new FormData(); - params.append('list', this.state.followsCSV); - - this.setState({ isLoading: true }); - dispatch(importFollows(params)).then(() => { - this.setState({ isLoading: false }); - }).catch((error) => { - this.setState({ isLoading: false }); - }); - - event.preventDefault(); - } - - handleFileChange = e => { - const [followsCSV] = e.target.files || []; - this.setState({ followsCSV }); - } - render() { const { intl } = this.props; return ( - -
- -
-
- } - name='follows' - hint={} - onChange={this.handleFileChange} - required - /> -
-
-
-
-
- -
-
+ + + {/* */}
); } From 066655728299ddd696abb53f00d6aa9dfd7037de Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 12:34:35 -0500 Subject: [PATCH 11/43] ImportData: move FileChooserCSV params into CSVImporter --- app/soapbox/features/forms/index.js | 8 -------- .../features/import_data/components/csv_importer.js | 6 ++++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index bd1b93252..4330919b3 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -264,11 +264,3 @@ export const FileChooserLogo = props => ( FileChooserLogo.defaultProps = { accept: ['image/svg', 'image/png'], }; - -export const FileChooserCSV = props => ( - -); - -FileChooserCSV.defaultProps = { - accept: ['text/csv'], -}; diff --git a/app/soapbox/features/import_data/components/csv_importer.js b/app/soapbox/features/import_data/components/csv_importer.js index 7571273b3..7f837b067 100644 --- a/app/soapbox/features/import_data/components/csv_importer.js +++ b/app/soapbox/features/import_data/components/csv_importer.js @@ -4,9 +4,9 @@ import { injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import PropTypes from 'prop-types'; import { + SimpleInput, SimpleForm, FieldsGroup, - FileChooserCSV, } from 'soapbox/features/forms'; export default @connect() @@ -55,7 +55,9 @@ class CSVImporter extends ImmutablePureComponent {
- Date: Sun, 27 Sep 2020 12:36:24 -0500 Subject: [PATCH 12/43] ImportData: fix webpackChunkName --- app/soapbox/features/ui/util/async-components.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/util/async-components.js b/app/soapbox/features/ui/util/async-components.js index 724a33e39..83f26c270 100644 --- a/app/soapbox/features/ui/util/async-components.js +++ b/app/soapbox/features/ui/util/async-components.js @@ -187,7 +187,7 @@ export function SoapboxConfig() { } export function ImportData() { - return import(/* webpackChunkName: "features/import_follows" */'../../import_data'); + return import(/* webpackChunkName: "features/import_data" */'../../import_data'); } export function PasswordReset() { From 3091f20fc97403e5b2b3a590843c13c71a45a81e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 13:33:03 -0500 Subject: [PATCH 13/43] Halloween: transparent vignette for light theme --- app/styles/holiday/halloween.scss | 4 ++-- app/styles/themes.scss | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/styles/holiday/halloween.scss b/app/styles/holiday/halloween.scss index 8f04e74c1..b63a1ed0f 100644 --- a/app/styles/holiday/halloween.scss +++ b/app/styles/holiday/halloween.scss @@ -43,13 +43,13 @@ body.halloween { } .app-holder { - // Black vignette + // Vignette &::before { background-image: radial-gradient( circle, transparent 0%, transparent 60%, - #000 100% + var(--vignette-color) 100% ); } diff --git a/app/styles/themes.scss b/app/styles/themes.scss index 13d952b17..a670b3086 100644 --- a/app/styles/themes.scss +++ b/app/styles/themes.scss @@ -64,6 +64,7 @@ body.theme-mode-light { var(--brand-color_s), calc(var(--brand-color_l) - 8%) ); + --vignette-color: transparent; // Meta-variables --primary-text-color_h: 0; @@ -92,6 +93,7 @@ body.theme-mode-dark { var(--brand-color_s), calc(var(--brand-color_l) + 8%) ); + --vignette-color: #000; // Meta-variables --primary-text-color_h: 0; From 83f31b8a00613a576ea17c5867f2840788b5f1dd Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 14:33:21 -0500 Subject: [PATCH 14/43] Notifications: parseInt ID when sorting --- app/soapbox/reducers/notifications.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/soapbox/reducers/notifications.js b/app/soapbox/reducers/notifications.js index 7dbda2c64..3d0d14dfb 100644 --- a/app/soapbox/reducers/notifications.js +++ b/app/soapbox/reducers/notifications.js @@ -32,8 +32,9 @@ const initialState = ImmutableMap({ // For sorting the notifications const comparator = (a, b) => { - if (a.get('id') < b.get('id')) return 1; - if (a.get('id') > b.get('id')) return -1; + const parse = m => parseInt(m.get('id'), 10); + if (parse(a) < parse(b)) return 1; + if (parse(a) > parse(b)) return -1; return 0; }; From afe23f0028783bb80e52078a4f03a550dca9a132 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 15:27:39 -0500 Subject: [PATCH 15/43] Composer: push into timelines even if it's a reply --- app/soapbox/actions/compose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/actions/compose.js b/app/soapbox/actions/compose.js index ef6b9995d..b0e35f531 100644 --- a/app/soapbox/actions/compose.js +++ b/app/soapbox/actions/compose.js @@ -149,7 +149,7 @@ export function handleComposeSubmit(dispatch, getState, response, status) { if (response.data.visibility !== 'direct') { insertIfOnline('home'); - } else if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { + } else if (response.data.visibility === 'public') { insertIfOnline('community'); insertIfOnline('public'); } From 0b7763e7267108b7ef70713b6ecbf9c85c952d48 Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Sun, 27 Sep 2020 15:43:23 -0500 Subject: [PATCH 16/43] fix composer jump by reseting lastToken on click and arrow up and down in autosuggesttextarea --- .../components/autosuggest_textarea.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index d9a044022..e60661f6b 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -50,6 +50,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { autoFocus: PropTypes.bool, onFocus: PropTypes.func, onBlur: PropTypes.func, + clickableAreaRef: PropTypes.object, }; static defaultProps = { @@ -107,6 +108,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { if (suggestions.size > 0 && !suggestionsHidden) { e.preventDefault(); this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); + } else { + this.setState({ lastToken: null }); } break; @@ -114,6 +117,8 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { if (suggestions.size > 0 && !suggestionsHidden) { e.preventDefault(); this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); + } else { + this.setState({ lastToken: null }); } break; @@ -159,6 +164,28 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); } + getClickableArea = () => { + const { clickableAreaRef } = this.props; + return clickableAreaRef ? clickableAreaRef.current : this.form; + } + + isClickInside = (e) => { + return [ + this.getClickableArea(), + document.querySelector('.autosuggest-textarea__textarea'), + ].some(element => element && element.contains(e.target)); + } + + handleClick = (e) => { + if (this.isClickInside(e)) { + this.setState({ lastToken: null }); + } + } + + componentDidMount() { + document.addEventListener('click', this.handleClick, true); + } + componentDidUpdate(prevProps, prevState) { const { suggestions } = this.props; if (suggestions !== prevProps.suggestions && suggestions.size > 0 && prevState.suggestionsHidden && prevState.focused) { From a67ab78c361ac689aeab567f2544f3854086f124 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 16:17:21 -0500 Subject: [PATCH 17/43] Timelines: refactor timelineQueue to use IDs instead of full objects, fixes #156 --- app/soapbox/actions/compose.js | 2 +- app/soapbox/actions/timelines.js | 20 ++++++++++---------- app/soapbox/reducers/contexts.js | 9 +++++++-- app/soapbox/reducers/timelines.js | 18 +++++++++--------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/soapbox/actions/compose.js b/app/soapbox/actions/compose.js index b0e35f531..8c947655a 100644 --- a/app/soapbox/actions/compose.js +++ b/app/soapbox/actions/compose.js @@ -143,7 +143,7 @@ export function handleComposeSubmit(dispatch, getState, response, status) { let dequeueArgs = {}; if (timelineId === 'community') dequeueArgs.onlyMedia = getSettings(getState()).getIn(['community', 'other', 'onlyMedia']); dispatch(dequeueTimeline(timelineId, null, dequeueArgs)); - dispatch(updateTimeline(timelineId, { ...response.data })); + dispatch(updateTimeline(timelineId, response.data.id)); } }; diff --git a/app/soapbox/actions/timelines.js b/app/soapbox/actions/timelines.js index 498c89dfb..67f6b19f8 100644 --- a/app/soapbox/actions/timelines.js +++ b/app/soapbox/actions/timelines.js @@ -25,31 +25,31 @@ export function processTimelineUpdate(timeline, status, accept) { const columnSettings = getSettings(getState()).get(timeline, ImmutableMap()); const shouldSkipQueue = shouldFilter(fromJS(status), columnSettings); + dispatch(importFetchedStatus(status)); + if (shouldSkipQueue) { - return dispatch(updateTimeline(timeline, status, accept)); + return dispatch(updateTimeline(timeline, status.id, accept)); } else { - return dispatch(updateTimelineQueue(timeline, status, accept)); + return dispatch(updateTimelineQueue(timeline, status.id, accept)); } }; } -export function updateTimeline(timeline, status, accept) { +export function updateTimeline(timeline, statusId, accept) { return dispatch => { if (typeof accept === 'function' && !accept(status)) { return; } - dispatch(importFetchedStatus(status)); - dispatch({ type: TIMELINE_UPDATE, timeline, - status, + statusId, }); }; }; -export function updateTimelineQueue(timeline, status, accept) { +export function updateTimelineQueue(timeline, statusId, accept) { return dispatch => { if (typeof accept === 'function' && !accept(status)) { return; @@ -58,7 +58,7 @@ export function updateTimelineQueue(timeline, status, accept) { dispatch({ type: TIMELINE_UPDATE_QUEUE, timeline, - status, + statusId, }); }; }; @@ -73,8 +73,8 @@ export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) { if (totalQueuedItemsCount === 0) { return; } else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) { - queuedItems.forEach(status => { - dispatch(updateTimeline(timeline, status.toJS(), null)); + queuedItems.forEach(statusId => { + dispatch(updateTimeline(timeline, statusId, null)); }); } else { if (typeof expandFunc === 'function') { diff --git a/app/soapbox/reducers/contexts.js b/app/soapbox/reducers/contexts.js index 8df462f81..844f9f78f 100644 --- a/app/soapbox/reducers/contexts.js +++ b/app/soapbox/reducers/contexts.js @@ -3,7 +3,8 @@ import { ACCOUNT_MUTE_SUCCESS, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; +import { TIMELINE_DELETE } from '../actions/timelines'; +import { STATUS_IMPORT, STATUSES_IMPORT } from 'soapbox/actions/importer'; import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; const initialState = ImmutableMap({ @@ -87,8 +88,12 @@ export default function replies(state = initialState, action) { return normalizeContext(state, action.id, action.ancestors, action.descendants); case TIMELINE_DELETE: return deleteFromContexts(state, [action.id]); - case TIMELINE_UPDATE: + case STATUS_IMPORT: return updateContext(state, action.status); + case STATUSES_IMPORT: + return state.withMutations(mutable => + action.statuses.forEach(status => updateContext(mutable, status))); + default: return state; } diff --git a/app/soapbox/reducers/timelines.js b/app/soapbox/reducers/timelines.js index b134742ba..3c514849a 100644 --- a/app/soapbox/reducers/timelines.js +++ b/app/soapbox/reducers/timelines.js @@ -65,10 +65,10 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is })); }; -const updateTimeline = (state, timeline, status) => { +const updateTimeline = (state, timeline, statusId) => { const top = state.getIn([timeline, 'top']); const ids = state.getIn([timeline, 'items'], ImmutableList()); - const includesId = ids.includes(status.get('id')); + const includesId = ids.includes(statusId); const unread = state.getIn([timeline, 'unread'], 0); if (includesId) { @@ -80,17 +80,17 @@ const updateTimeline = (state, timeline, status) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { if (!top) mMap.set('unread', unread + 1); if (top && ids.size > 40) newIds = newIds.take(20); - mMap.set('items', newIds.unshift(status.get('id'))); + mMap.set('items', newIds.unshift(statusId)); })); }; -const updateTimelineQueue = (state, timeline, status) => { +const updateTimelineQueue = (state, timeline, statusId) => { const queuedStatuses = state.getIn([timeline, 'queuedItems'], ImmutableList()); const listedStatuses = state.getIn([timeline, 'items'], ImmutableList()); const totalQueuedItemsCount = state.getIn([timeline, 'totalQueuedItemsCount'], 0); - let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === status.get('id')); - if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === status.get('id')); + let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === statusId); + if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === statusId); if (alreadyExists) { return state; @@ -100,7 +100,7 @@ const updateTimelineQueue = (state, timeline, status) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { if (totalQueuedItemsCount <= MAX_QUEUED_ITEMS) { - mMap.set('queuedItems', newQueuedStatuses.push(status)); + mMap.set('queuedItems', newQueuedStatuses.push(statusId)); } mMap.set('totalQueuedItemsCount', totalQueuedItemsCount + 1); })); @@ -165,9 +165,9 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_SUCCESS: return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent); case TIMELINE_UPDATE: - return updateTimeline(state, action.timeline, fromJS(action.status)); + return updateTimeline(state, action.timeline, action.statusId); case TIMELINE_UPDATE_QUEUE: - return updateTimelineQueue(state, action.timeline, fromJS(action.status)); + return updateTimelineQueue(state, action.timeline, action.statusId); case TIMELINE_DEQUEUE: return state.update(action.timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('queuedItems', ImmutableList()); From 99f0a069dd6092e7d9c835a5f2ffc2db42bf14bf Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Sun, 27 Sep 2020 16:32:07 -0500 Subject: [PATCH 18/43] pass getClickableArea into autosuggesttextarea as prop --- app/soapbox/components/autosuggest_textarea.js | 8 ++------ app/soapbox/features/compose/components/compose_form.js | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index e60661f6b..703aa9797 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -51,6 +51,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { onFocus: PropTypes.func, onBlur: PropTypes.func, clickableAreaRef: PropTypes.object, + getClickableArea: PropTypes.func.isRequired, }; static defaultProps = { @@ -164,14 +165,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); } - getClickableArea = () => { - const { clickableAreaRef } = this.props; - return clickableAreaRef ? clickableAreaRef.current : this.form; - } - isClickInside = (e) => { return [ - this.getClickableArea(), + this.props.getClickableArea(), document.querySelector('.autosuggest-textarea__textarea'), ].some(element => element && element.contains(e.target)); } diff --git a/app/soapbox/features/compose/components/compose_form.js b/app/soapbox/features/compose/components/compose_form.js index af8ce4263..39647c923 100644 --- a/app/soapbox/features/compose/components/compose_form.js +++ b/app/soapbox/features/compose/components/compose_form.js @@ -286,6 +286,7 @@ class ComposeForm extends ImmutablePureComponent { onSuggestionSelected={this.onSuggestionSelected} onPaste={onPaste} autoFocus={shouldAutoFocus} + getClickableArea={this.getClickableArea} > { !condensed && From c77c89ea08ee87a6163ec62b6a3072039c99d07e Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 16:48:23 -0500 Subject: [PATCH 19/43] UI: fix PropType warning on `page` --- app/soapbox/features/ui/util/react_router_helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/ui/util/react_router_helpers.js b/app/soapbox/features/ui/util/react_router_helpers.js index ceffd0daa..133750f91 100644 --- a/app/soapbox/features/ui/util/react_router_helpers.js +++ b/app/soapbox/features/ui/util/react_router_helpers.js @@ -18,7 +18,7 @@ class WrappedRoute extends React.Component { static propTypes = { component: PropTypes.func.isRequired, - page: PropTypes.func, + page: PropTypes.object, content: PropTypes.node, componentParams: PropTypes.object, layout: PropTypes.object, From 5ad39921f9a516105e6fb54e8888ba38e7f09b7f Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Sun, 27 Sep 2020 17:02:10 -0500 Subject: [PATCH 20/43] re-open compose modal after changing privacy, fixes #200 --- .../features/compose/containers/privacy_dropdown_container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/compose/containers/privacy_dropdown_container.js b/app/soapbox/features/compose/containers/privacy_dropdown_container.js index c112bc553..0c3bbf83b 100644 --- a/app/soapbox/features/compose/containers/privacy_dropdown_container.js +++ b/app/soapbox/features/compose/containers/privacy_dropdown_container.js @@ -17,7 +17,7 @@ const mapDispatchToProps = dispatch => ({ isUserTouching, onModalOpen: props => dispatch(openModal('ACTIONS', props)), - onModalClose: () => dispatch(closeModal()), + onModalClose: () => (dispatch(closeModal()), dispatch(openModal('COMPOSE'))), }); From d5d8c4877e2ee51ee619f04350c9616b9fd3276b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 17:24:55 -0500 Subject: [PATCH 21/43] Interactions: optimistic Favourite and EmojiReact actions, fixes #104 --- app/soapbox/reducers/statuses.js | 28 +++++++++++++++---- .../utils/__tests__/emoji_reacts-test.js | 26 +++++++++++++++++ app/soapbox/utils/emoji_reacts.js | 17 +++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) diff --git a/app/soapbox/reducers/statuses.js b/app/soapbox/reducers/statuses.js index 8cccd4a09..b7f09ef22 100644 --- a/app/soapbox/reducers/statuses.js +++ b/app/soapbox/reducers/statuses.js @@ -2,6 +2,7 @@ import { REBLOG_REQUEST, REBLOG_FAIL, FAVOURITE_REQUEST, + UNFAVOURITE_REQUEST, FAVOURITE_FAIL, } from '../actions/interactions'; import { @@ -12,11 +13,12 @@ import { } from '../actions/statuses'; import { EMOJI_REACT_REQUEST, + UNEMOJI_REACT_REQUEST, } from '../actions/emoji_reacts'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { Map as ImmutableMap, fromJS } from 'immutable'; -import { simulateEmojiReact } from 'soapbox/utils/emoji_reacts'; +import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -40,11 +42,27 @@ export default function statuses(state = initialState, action) { case STATUSES_IMPORT: return importStatuses(state, action.statuses); case FAVOURITE_REQUEST: - return state.setIn([action.status.get('id'), 'favourited'], true); + return state.update(action.status.get('id'), status => + status + .set('favourited', true) + .update('favourites_count', count => count + 1)); + case UNFAVOURITE_REQUEST: + return state.update(action.status.get('id'), status => + status + .set('favourited', false) + .update('favourites_count', count => Math.max(0, count - 1))); case EMOJI_REACT_REQUEST: - const path = [action.status.get('id'), 'pleroma', 'emoji_reactions']; - const emojiReacts = state.getIn(path); - return state.setIn(path, simulateEmojiReact(emojiReacts, action.emoji)); + return state + .updateIn( + [action.status.get('id'), 'pleroma', 'emoji_reactions'], + emojiReacts => simulateEmojiReact(emojiReacts, action.emoji) + ); + case UNEMOJI_REACT_REQUEST: + return state + .updateIn( + [action.status.get('id'), 'pleroma', 'emoji_reactions'], + emojiReacts => simulateUnEmojiReact(emojiReacts, action.emoji) + ); case FAVOURITE_FAIL: return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false); case REBLOG_REQUEST: diff --git a/app/soapbox/utils/__tests__/emoji_reacts-test.js b/app/soapbox/utils/__tests__/emoji_reacts-test.js index fcce0a11b..4663304b2 100644 --- a/app/soapbox/utils/__tests__/emoji_reacts-test.js +++ b/app/soapbox/utils/__tests__/emoji_reacts-test.js @@ -6,6 +6,7 @@ import { reduceEmoji, getReactForStatus, simulateEmojiReact, + simulateUnEmojiReact, } from '../emoji_reacts'; import { fromJS } from 'immutable'; @@ -205,3 +206,28 @@ describe('simulateEmojiReact', () => { ])); }); }); + +describe('simulateUnEmojiReact', () => { + it('removes the emoji from the list', () => { + const emojiReacts = fromJS([ + { 'count': 2, 'me': false, 'name': '👍' }, + { 'count': 3, 'me': true, 'name': '❤' }, + ]); + expect(simulateUnEmojiReact(emojiReacts, '❤')).toEqual(fromJS([ + { 'count': 2, 'me': false, 'name': '👍' }, + { 'count': 2, 'me': false, 'name': '❤' }, + ])); + }); + + it('removes the emoji if it\'s the last one in the list', () => { + const emojiReacts = fromJS([ + { 'count': 2, 'me': false, 'name': '👍' }, + { 'count': 2, 'me': false, 'name': '❤' }, + { 'count': 1, 'me': true, 'name': '😯' }, + ]); + expect(simulateUnEmojiReact(emojiReacts, '😯')).toEqual(fromJS([ + { 'count': 2, 'me': false, 'name': '👍' }, + { 'count': 2, 'me': false, 'name': '❤' }, + ])); + }); +}); diff --git a/app/soapbox/utils/emoji_reacts.js b/app/soapbox/utils/emoji_reacts.js index 2cb1b3fc3..39c194101 100644 --- a/app/soapbox/utils/emoji_reacts.js +++ b/app/soapbox/utils/emoji_reacts.js @@ -100,3 +100,20 @@ export const simulateEmojiReact = (emojiReacts, emoji) => { })); } }; + +export const simulateUnEmojiReact = (emojiReacts, emoji) => { + const idx = emojiReacts.findIndex(e => + e.get('name') === emoji && e.get('me') === true); + + if (idx > -1) { + const emojiReact = emojiReacts.get(idx); + const newCount = emojiReact.get('count') - 1; + if (newCount < 1) return emojiReacts.delete(idx); + return emojiReacts.set(idx, emojiReact.merge({ + count: emojiReact.get('count') - 1, + me: false, + })); + } else { + return emojiReacts; + } +}; From 136965acdd1197bbeeb4123831ca63f8d3c3c65a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 17:55:04 -0500 Subject: [PATCH 22/43] Composer: remove click listener on unmount --- app/soapbox/components/autosuggest_textarea.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index 703aa9797..060e817e8 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -182,6 +182,10 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { document.addEventListener('click', this.handleClick, true); } + componentWillUnmount() { + document.removeEventListener('click', this.handleClick, true); + } + componentDidUpdate(prevProps, prevState) { const { suggestions } = this.props; if (suggestions !== prevProps.suggestions && suggestions.size > 0 && prevState.suggestionsHidden && prevState.focused) { From d243b534866be06a529de4bfdfafedd93704fc0c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 18:02:57 -0500 Subject: [PATCH 23/43] PrivacyDropdown: clean up dispatch calls --- .../compose/containers/privacy_dropdown_container.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/soapbox/features/compose/containers/privacy_dropdown_container.js b/app/soapbox/features/compose/containers/privacy_dropdown_container.js index 0c3bbf83b..a90d54fa6 100644 --- a/app/soapbox/features/compose/containers/privacy_dropdown_container.js +++ b/app/soapbox/features/compose/containers/privacy_dropdown_container.js @@ -17,7 +17,10 @@ const mapDispatchToProps = dispatch => ({ isUserTouching, onModalOpen: props => dispatch(openModal('ACTIONS', props)), - onModalClose: () => (dispatch(closeModal()), dispatch(openModal('COMPOSE'))), + onModalClose: () => { + dispatch(closeModal()); + dispatch(openModal('COMPOSE')); + }, }); From c05eb0120afb08acc8e0bf6bd92af1f5ff786132 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 19:09:35 -0500 Subject: [PATCH 24/43] Purge state on logout, fixes #264 --- app/soapbox/reducers/index.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/app/soapbox/reducers/index.js b/app/soapbox/reducers/index.js index 6f4abdafe..5ec2b581c 100644 --- a/app/soapbox/reducers/index.js +++ b/app/soapbox/reducers/index.js @@ -1,4 +1,6 @@ import { combineReducers } from 'redux-immutable'; +import { Map as ImmutableMap } from 'immutable'; +import { AUTH_LOGGED_OUT } from 'soapbox/actions/auth'; import dropdown_menu from './dropdown_menu'; import timelines from './timelines'; import meta from './meta'; @@ -48,7 +50,7 @@ import chat_messages from './chat_messages'; import chat_message_lists from './chat_message_lists'; import profile_hover_card from './profile_hover_card'; -const reducers = { +const appReducer = combineReducers({ dropdown_menu, timelines, meta, @@ -97,6 +99,27 @@ const reducers = { chat_messages, chat_message_lists, profile_hover_card, +}); + +// Clear the state (mostly) when the user logs out +const logOut = (state = ImmutableMap()) => { + const whitelist = ['instance', 'soapbox', 'custom_emojis']; + + return ImmutableMap( + whitelist.reduce((acc, curr) => { + acc[curr] = state.get(curr); + return acc; + }, {}) + ); }; -export default combineReducers(reducers); +const rootReducer = (state, action) => { + switch(action.type) { + case AUTH_LOGGED_OUT: + return appReducer(logOut(state), action); + default: + return appReducer(state, action); + } +}; + +export default rootReducer; From ce1e04c224e7a580497bd5fa8c6f91ee221f59df Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 19:43:42 -0500 Subject: [PATCH 25/43] Obliterate hydrateStore() --- app/soapbox/actions/compose.js | 11 -------- app/soapbox/actions/settings.js | 7 ++++++ app/soapbox/actions/store.js | 23 ----------------- app/soapbox/containers/soapbox.js | 5 ---- app/soapbox/initial_state.js | 6 ----- .../reducers/__tests__/compose-test.js | 25 +++---------------- app/soapbox/reducers/compose.js | 18 +++---------- app/soapbox/reducers/media_attachments.js | 3 --- app/soapbox/reducers/meta.js | 3 --- app/soapbox/reducers/push_notifications.js | 16 ------------ app/soapbox/reducers/settings.js | 12 --------- 11 files changed, 14 insertions(+), 115 deletions(-) delete mode 100644 app/soapbox/actions/store.js delete mode 100644 app/soapbox/initial_state.js diff --git a/app/soapbox/actions/compose.js b/app/soapbox/actions/compose.js index 8c947655a..f4a644e87 100644 --- a/app/soapbox/actions/compose.js +++ b/app/soapbox/actions/compose.js @@ -440,17 +440,6 @@ export function updateTagHistory(tags) { }; } -export function hydrateCompose() { - return (dispatch, getState) => { - const me = getState().get('me'); - const history = tagHistory.get(me); - - if (history !== null) { - dispatch(updateTagHistory(history)); - } - }; -} - function insertIntoTagHistory(recognizedTags, text) { return (dispatch, getState) => { const state = getState(); diff --git a/app/soapbox/actions/settings.js b/app/soapbox/actions/settings.js index 097265814..205250c3b 100644 --- a/app/soapbox/actions/settings.js +++ b/app/soapbox/actions/settings.js @@ -2,6 +2,7 @@ import { debounce } from 'lodash'; import { showAlertForError } from './alerts'; import { patchMe } from 'soapbox/actions/me'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import uuid from '../uuid'; export const SETTING_CHANGE = 'SETTING_CHANGE'; export const SETTING_SAVE = 'SETTING_SAVE'; @@ -114,6 +115,12 @@ const defaultSettings = ImmutableMap({ trends: ImmutableMap({ show: true, }), + + columns: ImmutableList([ + ImmutableMap({ id: 'COMPOSE', uuid: uuid(), params: {} }), + ImmutableMap({ id: 'HOME', uuid: uuid(), params: {} }), + ImmutableMap({ id: 'NOTIFICATIONS', uuid: uuid(), params: {} }), + ]), }); export function getSettings(state) { diff --git a/app/soapbox/actions/store.js b/app/soapbox/actions/store.js deleted file mode 100644 index 87e495b99..000000000 --- a/app/soapbox/actions/store.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Iterable, fromJS } from 'immutable'; -import { hydrateCompose } from './compose'; - -export const STORE_HYDRATE = 'STORE_HYDRATE'; -export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; - -const convertState = rawState => - fromJS(rawState, (k, v) => - Iterable.isIndexed(v) ? v.toList() : v.toMap()); - -export function hydrateStore(rawState) { - return dispatch => { - const state = convertState(rawState); - - dispatch({ - type: STORE_HYDRATE, - state, - }); - - dispatch(hydrateCompose()); - // dispatch(importFetchedAccounts(Object.values(rawState.accounts))); - }; -}; diff --git a/app/soapbox/containers/soapbox.js b/app/soapbox/containers/soapbox.js index ed4549d27..138d12701 100644 --- a/app/soapbox/containers/soapbox.js +++ b/app/soapbox/containers/soapbox.js @@ -14,8 +14,6 @@ import { ScrollContext } from 'react-router-scroll-4'; import UI from '../features/ui'; // import Introduction from '../features/introduction'; import { fetchCustomEmojis } from '../actions/custom_emojis'; -import { hydrateStore } from '../actions/store'; -import initialState from '../initial_state'; import { preload } from '../actions/preload'; import { IntlProvider } from 'react-intl'; import ErrorBoundary from '../components/error_boundary'; @@ -32,9 +30,6 @@ const validLocale = locale => Object.keys(messages).includes(locale); export const store = configureStore(); -const hydrateAction = hydrateStore(initialState); - -store.dispatch(hydrateAction); store.dispatch(preload()); store.dispatch(fetchMe()); store.dispatch(fetchInstance()); diff --git a/app/soapbox/initial_state.js b/app/soapbox/initial_state.js deleted file mode 100644 index dd47e4e8b..000000000 --- a/app/soapbox/initial_state.js +++ /dev/null @@ -1,6 +0,0 @@ -'use strict'; - -const element = document.getElementById('initial-state'); -const initialState = element ? JSON.parse(element.textContent) : {}; - -export default initialState; diff --git a/app/soapbox/reducers/__tests__/compose-test.js b/app/soapbox/reducers/__tests__/compose-test.js index 230036fe8..d8122bab2 100644 --- a/app/soapbox/reducers/__tests__/compose-test.js +++ b/app/soapbox/reducers/__tests__/compose-test.js @@ -3,18 +3,18 @@ import { Map as ImmutableMap } from 'immutable'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'soapbox/actions/me'; import { SETTING_CHANGE } from 'soapbox/actions/settings'; import * as actions from 'soapbox/actions/compose'; -//import { STORE_HYDRATE } from 'soapbox/actions/store'; //import { REDRAFT } from 'soapbox/actions/statuses'; import { TIMELINE_DELETE } from 'soapbox/actions/timelines'; describe('compose reducer', () => { it('returns the initial state by default', () => { - expect(reducer(undefined, {}).toJS()).toMatchObject({ + const state = reducer(undefined, {}); + expect(state.toJS()).toMatchObject({ mounted: 0, sensitive: false, spoiler: false, spoiler_text: '', - privacy: null, + privacy: 'public', text: '', focusDate: null, caretPosition: null, @@ -30,10 +30,10 @@ describe('compose reducer', () => { suggestions: [], default_privacy: 'public', default_sensitive: false, - idempotencyKey: null, tagHistory: [], content_type: 'text/markdown', }); + expect(state.get('idempotencyKey').length === 36); }); it('uses \'public\' scope as default', () => { @@ -132,23 +132,6 @@ describe('compose reducer', () => { }); }); - // it('should handle STORE_HYDRATE', () => { - // const state = ImmutableMap({ }); - // const action = { - // type: STORE_HYDRATE, - // state: ImmutableMap({ - // compose: true, - // text: 'newtext', - // }), - // }; - // expect(reducer(state, action)).toEqual(ImmutableMap({ - // state: ImmutableMap({ - // compose: true, - // text: 'newtext', - // }), - // })); - // }); - it('should handle COMPOSE_MOUNT', () => { const state = ImmutableMap({ mounted: 1 }); const action = { diff --git a/app/soapbox/reducers/compose.js b/app/soapbox/reducers/compose.js index ad5ea9b64..1e0b6d614 100644 --- a/app/soapbox/reducers/compose.js +++ b/app/soapbox/reducers/compose.js @@ -38,7 +38,6 @@ import { COMPOSE_POLL_SETTINGS_CHANGE, } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; -import { STORE_HYDRATE } from '../actions/store'; import { REDRAFT } from '../actions/statuses'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me'; import { SETTING_CHANGE, FE_NAME } from '../actions/settings'; @@ -47,12 +46,13 @@ import uuid from '../uuid'; import { unescapeHTML } from '../utils/html'; const initialState = ImmutableMap({ + id: null, mounted: 0, sensitive: false, spoiler: false, spoiler_text: '', content_type: 'text/markdown', - privacy: null, + privacy: 'public', text: '', focusDate: null, caretPosition: null, @@ -69,7 +69,7 @@ const initialState = ImmutableMap({ default_privacy: 'public', default_sensitive: false, resetFileKey: Math.floor((Math.random() * 0x10000)), - idempotencyKey: null, + idempotencyKey: uuid(), tagHistory: ImmutableList(), }); @@ -178,16 +178,6 @@ const privacyPreference = (a, b) => { return order[Math.max(order.indexOf(a), order.indexOf(b), 0)]; }; -const hydrate = (state, hydratedState = ImmutableMap()) => { - state = clearAll(state.merge(hydratedState)); - - if (hydratedState.has('text')) { - state = state.set('text', hydratedState.get('text')); - } - - return state; -}; - const domParser = new DOMParser(); const expandMentions = status => { @@ -204,8 +194,6 @@ const expandMentions = status => { export default function compose(state = initialState, action) { let me, defaultPrivacy; switch(action.type) { - case STORE_HYDRATE: - return hydrate(state, action.state.get('compose')); case COMPOSE_MOUNT: return state.set('mounted', state.get('mounted') + 1); case COMPOSE_UNMOUNT: diff --git a/app/soapbox/reducers/media_attachments.js b/app/soapbox/reducers/media_attachments.js index 1f7fe8cff..7e67805dd 100644 --- a/app/soapbox/reducers/media_attachments.js +++ b/app/soapbox/reducers/media_attachments.js @@ -1,4 +1,3 @@ -import { STORE_HYDRATE } from '../actions/store'; import { Map as ImmutableMap, List as ImmutableList, @@ -35,8 +34,6 @@ const initialState = ImmutableMap({ export default function meta(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: - return state.merge(action.state.get('media_attachments')); default: return state; } diff --git a/app/soapbox/reducers/meta.js b/app/soapbox/reducers/meta.js index 6bd397c2c..c3f5062df 100644 --- a/app/soapbox/reducers/meta.js +++ b/app/soapbox/reducers/meta.js @@ -1,6 +1,5 @@ 'use strict'; -import { STORE_HYDRATE } from '../actions/store'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from 'soapbox/actions/me'; import { Map as ImmutableMap, fromJS } from 'immutable'; @@ -8,8 +7,6 @@ const initialState = ImmutableMap(); export default function meta(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: - return state.merge(action.state.get('meta')); case ME_FETCH_SUCCESS: case ME_PATCH_SUCCESS: const me = fromJS(action.me); diff --git a/app/soapbox/reducers/push_notifications.js b/app/soapbox/reducers/push_notifications.js index 317352b79..6b9001684 100644 --- a/app/soapbox/reducers/push_notifications.js +++ b/app/soapbox/reducers/push_notifications.js @@ -1,4 +1,3 @@ -import { STORE_HYDRATE } from '../actions/store'; import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push_notifications'; import Immutable from 'immutable'; @@ -17,21 +16,6 @@ const initialState = Immutable.Map({ export default function push_subscriptions(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: { - const push_subscription = action.state.get('push_subscription'); - - if (push_subscription) { - return state - .set('subscription', new Immutable.Map({ - id: push_subscription.get('id'), - endpoint: push_subscription.get('endpoint'), - })) - .set('alerts', push_subscription.get('alerts') || initialState.get('alerts')) - .set('isSubscribed', true); - } - - return state; - } case SET_SUBSCRIPTION: return state .set('subscription', new Immutable.Map({ diff --git a/app/soapbox/reducers/settings.js b/app/soapbox/reducers/settings.js index 1066716d7..be4f6b88d 100644 --- a/app/soapbox/reducers/settings.js +++ b/app/soapbox/reducers/settings.js @@ -1,11 +1,9 @@ import { SETTING_CHANGE, SETTING_SAVE, FE_NAME } from '../actions/settings'; import { NOTIFICATIONS_FILTER_SET } from '../actions/notifications'; -import { STORE_HYDRATE } from '../actions/store'; import { EMOJI_USE } from '../actions/emojis'; import { LIST_DELETE_SUCCESS, LIST_FETCH_FAIL } from '../actions/lists'; import { ME_FETCH_SUCCESS } from 'soapbox/actions/me'; import { Map as ImmutableMap, fromJS } from 'immutable'; -import uuid from '../uuid'; // Default settings are in action/settings.js // @@ -15,22 +13,12 @@ const initialState = ImmutableMap({ saved: true, }); -const defaultColumns = fromJS([ - { id: 'COMPOSE', uuid: uuid(), params: {} }, - { id: 'HOME', uuid: uuid(), params: {} }, - { id: 'NOTIFICATIONS', uuid: uuid(), params: {} }, -]); - -const hydrate = (state, settings) => state.mergeDeep(settings).update('columns', (val = defaultColumns) => val); - const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false); const filterDeadListColumns = (state, listId) => state.update('columns', columns => columns.filterNot(column => column.get('id') === 'LIST' && column.get('params').get('id') === listId)); export default function settings(state = initialState, action) { switch(action.type) { - case STORE_HYDRATE: - return hydrate(state, action.state.get('settings')); case ME_FETCH_SUCCESS: const me = fromJS(action.me); let fePrefs = me.getIn(['pleroma', 'settings_store', FE_NAME], ImmutableMap()); From 1420f580857501ac3d155983ca6d28f7873bff01 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 20:27:27 -0500 Subject: [PATCH 26/43] Remove rails-ujs, #439 --- app/application.js | 5 +++-- app/soapbox/common.js | 14 -------------- package.json | 1 - yarn.lock | 5 ----- 4 files changed, 3 insertions(+), 22 deletions(-) delete mode 100644 app/soapbox/common.js diff --git a/app/application.js b/app/application.js index 906247ea3..c0f54ff1d 100644 --- a/app/application.js +++ b/app/application.js @@ -1,7 +1,8 @@ import loadPolyfills from './soapbox/load_polyfills'; -import { start } from './soapbox/common'; -start(); +require('fork-awesome/css/fork-awesome.css'); + +require.context('./images/', true); loadPolyfills().then(() => { require('./soapbox/main').default(); diff --git a/app/soapbox/common.js b/app/soapbox/common.js deleted file mode 100644 index 6947fd07c..000000000 --- a/app/soapbox/common.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -import Rails from 'rails-ujs'; - -export function start() { - require('fork-awesome/css/fork-awesome.css'); - require.context('../images/', true); - - try { - Rails.start(); - } catch (e) { - // If called twice - } -}; diff --git a/package.json b/package.json index e407f8788..308f37eea 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "prop-types": "^15.5.10", "punycode": "^2.1.0", "qrcode.react": "^1.0.0", - "rails-ujs": "^5.2.3", "react": "^16.13.1", "react-color": "^2.18.1", "react-dom": "^16.13.1", diff --git a/yarn.lock b/yarn.lock index 3129a175f..f14e28b41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9406,11 +9406,6 @@ railroad-diagrams@^1.0.0: resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= -rails-ujs@^5.2.3: - version "5.2.3" - resolved "https://registry.yarnpkg.com/rails-ujs/-/rails-ujs-5.2.3.tgz#4b65ea781a6befe62e96da6362165286a1fe4099" - integrity sha512-rYgj185MowWFBJI1wdac2FkX4yFYe4+3jJPlB+CTY7a4rmIyg0TqE4vYZmSBBesp7blPUa57oqKzwQjN7eVbEQ== - randexp@0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" From a9fef0ca14875626bc0b2991bbfef57ff525aa9b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 20:51:46 -0500 Subject: [PATCH 27/43] Don't import unused react-sparklines, #439 --- app/soapbox/components/hashtag.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/hashtag.js b/app/soapbox/components/hashtag.js index 2a0017c9c..2872a236a 100644 --- a/app/soapbox/components/hashtag.js +++ b/app/soapbox/components/hashtag.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Sparklines, SparklinesCurve } from 'react-sparklines'; +// import { Sparklines, SparklinesCurve } from 'react-sparklines'; import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from './permalink'; @@ -17,11 +17,12 @@ const Hashtag = ({ hashtag }) => (
}
- {hashtag.get('history') &&
+ {/* Pleroma doesn't support tag history yet */} + {/* hashtag.get('history') &&
day.get('uses')).toArray()}> -
} +
*/} ); From c8f839f483f5428478aeee1bf31622a2ac626a15 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 21:25:48 -0500 Subject: [PATCH 28/43] Remove chromatism, #439 --- app/soapbox/utils/theme.js | 63 ++++++++++++++++++++++++++++++++++++-- package.json | 1 - yarn.lock | 5 --- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/app/soapbox/utils/theme.js b/app/soapbox/utils/theme.js index a4fb01c8c..6e847b5a4 100644 --- a/app/soapbox/utils/theme.js +++ b/app/soapbox/utils/theme.js @@ -1,13 +1,72 @@ import { Map as ImmutableMap } from 'immutable'; -import { convert } from 'chromatism'; export const generateThemeCss = brandColor => { if (!brandColor) return null; return themeDataToCss(brandColorToThemeData(brandColor)); }; +// https://stackoverflow.com/a/5624139 +function hexToRgb(hex) { + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, (m, r, g, b) => ( + r + r + g + g + b + b + )); + + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16), + } : { + // fall back to Azure + r: 4, + g: 130, + b: 216, + }; +} + +// Taken from chromatism.js +// https://github.com/graypegg/chromatism/blob/master/src/conversions/rgb.js +const rgbToHsl = value => { + var r = value.r / 255; + var g = value.g / 255; + var b = value.b / 255; + var rgbOrdered = [ r, g, b ].sort(); + var l = ((rgbOrdered[0] + rgbOrdered[2]) / 2) * 100; + var s, h; + if (rgbOrdered[0] === rgbOrdered[2]) { + s = 0; + h = 0; + } else { + if (l >= 50) { + s = ((rgbOrdered[2] - rgbOrdered[0]) / ((2.0 - rgbOrdered[2]) - rgbOrdered[0])) * 100; + } else { + s = ((rgbOrdered[2] - rgbOrdered[0]) / (rgbOrdered[2] + rgbOrdered[0])) * 100; + } + if (rgbOrdered[2] === r) { + h = ((g - b) / (rgbOrdered[2] - rgbOrdered[0])) * 60; + } else if (rgbOrdered[2] === g) { + h = (2 + ((b - r) / (rgbOrdered[2] - rgbOrdered[0]))) * 60; + } else { + h = (4 + ((r - g) / (rgbOrdered[2] - rgbOrdered[0]))) * 60; + } + if (h < 0) { + h += 360; + } else if (h > 360) { + h = h % 360; + } + } + + return { + h: h, + s: s, + l: l, + }; +}; + export const brandColorToThemeData = brandColor => { - const { h, s, l } = convert(brandColor).hsl; + const { h, s, l } = rgbToHsl(hexToRgb(brandColor)); return ImmutableMap({ 'brand-color_h': h, 'brand-color_s': `${s}%`, diff --git a/package.json b/package.json index 308f37eea..df7042161 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "babel-runtime": "^6.26.0", "blurhash": "^1.0.0", - "chromatism": "^3.0.0", "classnames": "^2.2.5", "compression-webpack-plugin": "^3.0.0", "cross-env": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index f14e28b41..bc1614f09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3072,11 +3072,6 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== -chromatism@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chromatism/-/chromatism-3.0.0.tgz#a7249d353c1e4f3577e444ac41171c4e2e624b12" - integrity sha1-pySdNTweTzV35ESsQRccTi5iSxI= - chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" From b266813b4b23e33f60694e477a1b0fa8a3274dce Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 21:41:11 -0500 Subject: [PATCH 29/43] Reduce semver bundle size, #439 --- app/soapbox/utils/features.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/soapbox/utils/features.js b/app/soapbox/utils/features.js index 98266e3ca..213b4cad9 100644 --- a/app/soapbox/utils/features.js +++ b/app/soapbox/utils/features.js @@ -1,14 +1,14 @@ // Detect backend features to conditionally render elements -import semver from 'semver'; +import gte from 'semver/functions/gte'; export const getFeatures = instance => { const v = parseVersion(instance.get('version')); return { - suggestions: v.software === 'Mastodon' && semver.gte(v.compatVersion, '2.4.3'), - trends: v.software === 'Mastodon' && semver.gte(v.compatVersion, '3.0.0'), - emojiReacts: v.software === 'Pleroma' && semver.gte(v.version, '2.0.0'), + suggestions: v.software === 'Mastodon' && gte(v.compatVersion, '2.4.3'), + trends: v.software === 'Mastodon' && gte(v.compatVersion, '3.0.0'), + emojiReacts: v.software === 'Pleroma' && gte(v.version, '2.0.0'), attachmentLimit: v.software === 'Pleroma' ? Infinity : 4, - focalPoint: v.software === 'Mastodon' && semver.gte(v.compatVersion, '2.3.0'), + focalPoint: v.software === 'Mastodon' && gte(v.compatVersion, '2.3.0'), }; }; From d5517aad79ae2fc2dac601c90ebda04d7737e982 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 22:48:08 -0500 Subject: [PATCH 30/43] Remove standalone components --- .../features/standalone/compose/index.js | 20 ---- .../standalone/hashtag_timeline/index.js | 84 ---------------- .../standalone/public_timeline/index.js | 99 ------------------- 3 files changed, 203 deletions(-) delete mode 100644 app/soapbox/features/standalone/compose/index.js delete mode 100644 app/soapbox/features/standalone/hashtag_timeline/index.js delete mode 100644 app/soapbox/features/standalone/public_timeline/index.js diff --git a/app/soapbox/features/standalone/compose/index.js b/app/soapbox/features/standalone/compose/index.js deleted file mode 100644 index c9ac782e7..000000000 --- a/app/soapbox/features/standalone/compose/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import ComposeFormContainer from '../../compose/containers/compose_form_container'; -import NotificationsContainer from '../../ui/containers/notifications_container'; -import LoadingBarContainer from '../../ui/containers/loading_bar_container'; -import ModalContainer from '../../ui/containers/modal_container'; - -export default class Compose extends React.PureComponent { - - render() { - return ( -
- - - - -
- ); - } - -} diff --git a/app/soapbox/features/standalone/hashtag_timeline/index.js b/app/soapbox/features/standalone/hashtag_timeline/index.js deleted file mode 100644 index 6bb162b6c..000000000 --- a/app/soapbox/features/standalone/hashtag_timeline/index.js +++ /dev/null @@ -1,84 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { expandHashtagTimeline } from 'soapbox/actions/timelines'; -import Masonry from 'react-masonry-infinite'; -import { List as ImmutableList } from 'immutable'; -import DetailedStatusContainer from 'soapbox/features/status/containers/detailed_status_container'; -import { debounce } from 'lodash'; -import LoadingIndicator from 'soapbox/components/loading_indicator'; - -const mapStateToProps = (state, { hashtag }) => ({ - statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()), - isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false), - hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false), -}); - -export default @connect(mapStateToProps) -class HashtagTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - isLoading: PropTypes.bool.isRequired, - hasMore: PropTypes.bool.isRequired, - hashtag: PropTypes.string.isRequired, - }; - - componentDidMount() { - const { dispatch, hashtag } = this.props; - - dispatch(expandHashtagTimeline(hashtag)); - } - - handleLoadMore = () => { - const maxId = this.props.statusIds.last(); - - if (maxId) { - this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId })); - } - } - - setRef = c => { - this.masonry = c; - } - - handleHeightChange = debounce(() => { - if (!this.masonry) { - return; - } - - this.masonry.forcePack(); - }, 50) - - render() { - const { statusIds, hasMore, isLoading } = this.props; - - const sizes = [ - { columns: 1, gutter: 0 }, - { mq: '415px', columns: 1, gutter: 10 }, - { mq: '640px', columns: 2, gutter: 10 }, - { mq: '960px', columns: 3, gutter: 10 }, - { mq: '1255px', columns: 3, gutter: 10 }, - ]; - - const loader = (isLoading && statusIds.isEmpty()) ? : undefined; - - return ( - - {statusIds.map(statusId => ( -
- -
- )).toArray()} -
- ); - } - -} diff --git a/app/soapbox/features/standalone/public_timeline/index.js b/app/soapbox/features/standalone/public_timeline/index.js deleted file mode 100644 index a509c2e04..000000000 --- a/app/soapbox/features/standalone/public_timeline/index.js +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { expandPublicTimeline, expandCommunityTimeline } from 'soapbox/actions/timelines'; -import Masonry from 'react-masonry-infinite'; -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; -import DetailedStatusContainer from 'soapbox/features/status/containers/detailed_status_container'; -import { debounce } from 'lodash'; -import LoadingIndicator from 'soapbox/components/loading_indicator'; - -const mapStateToProps = (state, { local }) => { - const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap()); - - return { - statusIds: timeline.get('items', ImmutableList()), - isLoading: timeline.get('isLoading', false), - hasMore: timeline.get('hasMore', false), - }; -}; - -export default @connect(mapStateToProps) -class PublicTimeline extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - isLoading: PropTypes.bool.isRequired, - hasMore: PropTypes.bool.isRequired, - local: PropTypes.bool, - }; - - componentDidMount() { - this._connect(); - } - - componentDidUpdate(prevProps) { - if (prevProps.local !== this.props.local) { - this._connect(); - } - } - - _connect() { - const { dispatch, local } = this.props; - - dispatch(local ? expandCommunityTimeline() : expandPublicTimeline()); - } - - handleLoadMore = () => { - const { dispatch, statusIds, local } = this.props; - const maxId = statusIds.last(); - - if (maxId) { - dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId })); - } - } - - setRef = c => { - this.masonry = c; - } - - handleHeightChange = debounce(() => { - if (!this.masonry) { - return; - } - - this.masonry.forcePack(); - }, 50) - - render() { - const { statusIds, hasMore, isLoading } = this.props; - - const sizes = [ - { columns: 1, gutter: 0 }, - { mq: '415px', columns: 1, gutter: 10 }, - { mq: '640px', columns: 2, gutter: 10 }, - { mq: '960px', columns: 3, gutter: 10 }, - { mq: '1255px', columns: 3, gutter: 10 }, - ]; - - const loader = (isLoading && statusIds.isEmpty()) ? : undefined; - - return ( - - {statusIds.map(statusId => ( -
- -
- )).toArray()} -
- ); - } - -} From 57b72840e577f429269c8d9bda6a8b631d414893 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 22:48:21 -0500 Subject: [PATCH 31/43] Remove unused CSS, #439 --- app/styles/about.scss | 144 --------- app/styles/accounts.scss | 123 -------- app/styles/application.scss | 5 +- app/styles/basics.scss | 7 - app/styles/compact_header.scss | 34 --- app/styles/containers.scss | 88 ------ app/styles/forms.scss | 346 +-------------------- app/styles/lists.scss | 19 -- app/styles/tables.scss | 16 - app/styles/ui.scss | 52 ---- app/styles/widgets.scss | 534 --------------------------------- 11 files changed, 2 insertions(+), 1366 deletions(-) delete mode 100644 app/styles/compact_header.scss delete mode 100644 app/styles/lists.scss delete mode 100644 app/styles/widgets.scss diff --git a/app/styles/about.scss b/app/styles/about.scss index 77d93917e..915cfd1f8 100644 --- a/app/styles/about.scss +++ b/app/styles/about.scss @@ -532,10 +532,6 @@ $small-breakpoint: 960px; background: transparent; margin: 0 -5px; - .account__header__fields { - border-top: 1px solid var(--brand-color--med); - } - .roles { display: none; } @@ -592,23 +588,6 @@ $small-breakpoint: 960px; border-radius: 0; } - .account__header__fields { - margin: 0; - border-top: 0; - - a { - color: var(--brand-color); - } - - dl:first-child .verified { - border-radius: 0 4px 0 0; - } - - .verified a { - color: $valid-value-color; - } - } - .account__header__content { padding: 20px; padding-bottom: 0; @@ -856,11 +835,6 @@ $small-breakpoint: 960px; background: var(--brand-color--med); padding: 20px 0; - .container-alt { - position: relative; - padding-right: 280px + 15px; - } - &__sections { display: flex; justify-content: space-between; @@ -1300,10 +1274,6 @@ $small-breakpoint: 960px; @media screen and (max-width: 840px) { .information-board { - .container-alt { - padding-right: 20px; - } - .panel { position: static; margin-top: 20px; @@ -1329,11 +1299,6 @@ $small-breakpoint: 960px; text-align: initial; } } - - .header .container-alt, - .features .container-alt { - display: block; - } } .cta { @@ -1491,67 +1456,6 @@ $small-breakpoint: 960px; border-radius: 0; } - .hero-widget { - margin-top: 30px; - margin-bottom: 0; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: var(--primary-text-color--faint); - } - - &__text { - border-radius: 0; - padding-bottom: 0; - } - - &__footer { - background: var(--brand-color--med); - padding: 10px; - border-radius: 0 0 4px 4px; - display: flex; - - &__column { - flex: 1 1 50%; - } - } - - .account { - padding: 10px 0; - border-bottom: 0; - - .account__display-name { - display: flex; - align-items: center; - } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } - } - - &__counter { - padding: 10px; - - strong { - font-family: var(--font-display), sans-serif; - font-size: 15px; - font-weight: 700; - display: block; - } - - span { - font-size: 14px; - color: var(--primary-text-color--faint); - } - } - } - .simple_form .user_agreement .label_input > label { font-weight: 400; color: var(--primary-text-color--faint); @@ -1580,18 +1484,6 @@ $small-breakpoint: 960px; grid-row: 1; display: flex; flex-direction: column; - - .box-widget { - order: 2; - flex: 0 0 auto; - } - - .hero-widget { - margin-top: 0; - margin-bottom: 10px; - order: 1; - flex: 0 0 auto; - } } &__column-registration { @@ -1605,42 +1497,6 @@ $small-breakpoint: 960px; @media screen and (max-width: $no-gap-breakpoint) { grid-gap: 0; - - .hero-widget { - display: block; - margin-bottom: 0; - box-shadow: none; - - &__img, - &__img img, - &__footer { - border-radius: 0; - } - } - - .hero-widget, - .box-widget, - .directory__tag { - border-bottom: 1px solid var(--brand-color--med); - } - - .directory { - margin-top: 0; - - &__tag { - margin-bottom: 0; - - & > a, - & > div { - border-radius: 0; - box-shadow: none; - } - - &:last-child { - border-bottom: 0; - } - } - } } } } diff --git a/app/styles/accounts.scss b/app/styles/accounts.scss index d9bbe2283..5c70a64d3 100644 --- a/app/styles/accounts.scss +++ b/app/styles/accounts.scss @@ -177,31 +177,6 @@ } } -.nothing-here { - background: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - color: var(--primary-text-color--faint); - font-size: 14px; - font-weight: 500; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - cursor: default; - border-radius: 4px; - padding: 20px; - min-height: 30vh; - - &--under-tabs { - border-radius: 0 0 4px 4px; - } - - &--flexible { - box-sizing: border-box; - min-height: 100%; - } -} - .account-role { display: inline-block; padding: 4px 6px; @@ -227,104 +202,6 @@ } } -.account__header__fields { - padding: 0; - margin: 15px -15px -15px; - border: 0 none; - border-top: 1px solid var(--brand-color--med); - border-bottom: 1px solid var(--brand-color--med); - font-size: 14px; - line-height: 20px; - - dl { - display: flex; - border-bottom: 1px solid var(--brand-color--med); - } - - dt, - dd { - box-sizing: border-box; - padding: 14px; - text-align: center; - max-height: 48px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - dt { - font-weight: 500; - width: 120px; - flex: 0 0 auto; - color: var(--primary-text-color--faint); - background: hsla(var(--background-color_hsl), 0.5); - } - - dd { - flex: 1 1 auto; - color: var(--primary-text-color--faint); - } - - a { - color: var(--highlight-text-color); - text-decoration: none; - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } - - .verified { - border: 1px solid rgba($valid-value-color, 0.5); - background: rgba($valid-value-color, 0.25); - - a { - color: $valid-value-color; - font-weight: 500; - } - - &__mark { - color: $valid-value-color; - } - } - - dl:last-child { - border-bottom: 0; - } -} - -.directory__tag .trends__item__current { - width: auto; -} - -.pending-account { - &__header { - color: var(--primary-text-color--faint); - - a { - color: var(--background-color); - text-decoration: none; - - &:hover, - &:active, - &:focus { - text-decoration: underline; - } - } - - strong { - color: var(--primary-text-color); - font-weight: 700; - } - } - - &__body { - margin-top: 10px; - } -} - .account { padding: 10px; position: relative; diff --git a/app/styles/application.scss b/app/styles/application.scss index cb171dffd..dfa13b9ae 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -5,10 +5,7 @@ @import 'reset'; @import 'basics'; @import 'containers'; -@import 'lists'; @import 'footer'; -@import 'compact_header'; -@import 'widgets'; @import 'forms'; @import 'accounts'; @import 'stream_entries'; @@ -16,7 +13,7 @@ @import 'loading'; @import 'ui'; @import 'polls'; -@import 'introduction'; +// @import 'introduction'; @import 'emoji_picker'; @import 'about'; @import 'tables'; diff --git a/app/styles/basics.scss b/app/styles/basics.scss index 092f06202..4a5c8916b 100644 --- a/app/styles/basics.scss +++ b/app/styles/basics.scss @@ -176,13 +176,6 @@ body { margin-top: 1em; } - &__dismiss { - display: inline-block; - text-transform: uppercase; - margin-left: 5px; - font-size: 13px; - } - a { color: var(--brand-color--hicontrast); text-decoration: underline; diff --git a/app/styles/compact_header.scss b/app/styles/compact_header.scss deleted file mode 100644 index 3f6fc003e..000000000 --- a/app/styles/compact_header.scss +++ /dev/null @@ -1,34 +0,0 @@ -.compact-header { - h1 { - font-size: 24px; - line-height: 28px; - color: var(--primary-text-color--faint); - font-weight: 500; - margin-bottom: 20px; - padding: 0 10px; - word-wrap: break-word; - - @media screen and (max-width: 740px) { - text-align: center; - padding: 20px 10px 0; - } - - a { - color: inherit; - text-decoration: none; - } - - small { - font-weight: 400; - color: var(--primary-text-color--faint); - } - - img { - display: inline-block; - margin-bottom: -5px; - margin-right: 15px; - width: 36px; - height: 36px; - } - } -} diff --git a/app/styles/containers.scss b/app/styles/containers.scss index 53bd272c2..74eebe61e 100644 --- a/app/styles/containers.scss +++ b/app/styles/containers.scss @@ -1,14 +1,3 @@ -.container-alt { - width: 700px; - margin: 0 auto; - margin-top: 40px; - - @media screen and (max-width: 740px) { - width: 100%; - margin: 0; - } -} - .logo-container { margin: 100px auto 50px; @@ -111,80 +100,3 @@ margin-left: 8px; } } - -.grid-3 { - display: grid; - grid-gap: 10px; - grid-template-columns: 3fr 1fr; - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-column: 1 / 3; - grid-row: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 2; - } - - .column-2 { - grid-column: 2; - grid-row: 2; - } - - .column-3 { - grid-column: 1 / 3; - grid-row: 3; - } - - .landing-page__call-to-action { - min-height: 100%; - } - - @media screen and (max-width: 738px) { - grid-template-columns: minmax(0, 50%) minmax(0, 50%); - - .landing-page__call-to-action { - padding: 20px; - display: flex; - align-items: center; - justify-content: center; - } - - .row__information-board { - width: 100%; - justify-content: center; - align-items: center; - } - - .row__mascot { - display: none; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - grid-gap: 0; - grid-template-columns: minmax(0, 100%); - - .column-0 { - grid-column: 1; - } - - .column-1 { - grid-column: 1; - grid-row: 3; - } - - .column-2 { - grid-column: 1; - grid-row: 2; - } - - .column-3 { - grid-column: 1; - grid-row: 4; - } - } -} diff --git a/app/styles/forms.scss b/app/styles/forms.scss index 5471f9778..147199870 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -542,155 +542,6 @@ code { font-size: 24px; } -.flash-message { - background: var(--brand-color--med); - color: var(--primary-text-color--faint); - border-radius: 4px; - padding: 15px 10px; - margin-bottom: 30px; - text-align: center; - - &.notice { - border: 1px solid rgba($valid-value-color, 0.5); - background: rgba($valid-value-color, 0.25); - color: $valid-value-color; - } - - &.alert { - border: 1px solid rgba($error-value-color, 0.5); - background: rgba($error-value-color, 0.25); - color: $error-value-color; - } - - a { - display: inline-block; - color: var(--primary-text-color--faint); - text-decoration: none; - - &:hover { - color: var(--primary-text-color); - text-decoration: underline; - } - } - - p { - margin-bottom: 15px; - } - - .oauth-code { - outline: 0; - box-sizing: border-box; - display: block; - width: 100%; - border: 0; - padding: 10px; - font-family: var(--font-monospace), monospace; - background: var(--brand-color--med); - color: var(--primary-text-color); - font-size: 14px; - margin: 0; - - &::-moz-focus-inner { - border: 0; - } - - &::-moz-focus-inner, - &:focus, - &:active { - outline: 0 !important; - } - - &:focus { - background: var(--brand-color--faint); - } - } - - strong { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - @media screen and (max-width: 740px) and (min-width: 441px) { - margin-top: 40px; - } -} - -.form-footer { - margin-top: 30px; - text-align: center; - - a { - color: var(--primary-text-color--faint); - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } -} - -.quick-nav { - list-style: none; - margin-bottom: 25px; - font-size: 14px; - - li { - display: inline-block; - margin-right: 10px; - } - - a { - color: var(--highlight-text-color); - text-transform: uppercase; - text-decoration: none; - font-weight: 700; - - &:hover, - &:focus, - &:active { - color: var(--highlight-text-color); - } - } -} - -.oauth-prompt, -.follow-prompt { - margin-bottom: 30px; - color: var(--primary-text-color--faint); - - h2 { - font-size: 16px; - margin-bottom: 30px; - text-align: center; - } - - strong { - color: var(--primary-text-color--faint); - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - @media screen and (max-width: 740px) and (min-width: 441px) { - margin-top: 40px; - } -} - -.qr-wrapper { - display: flex; - flex-wrap: wrap; - align-items: flex-start; -} - .qr-code { flex: 0 0 auto; background: var(--foreground-color); @@ -705,35 +556,7 @@ code { } } -.qr-alternative { - margin-bottom: 20px; - color: var(--primary-text-color--faint); - flex: 150px; - - samp { - display: block; - font-size: 14px; - } -} - -.table-form { - p { - margin-bottom: 15px; - - strong { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - } -} - -.simple_form, -.table-form { +.simple_form { .warning { box-sizing: border-box; background: rgba($error-value-color, 0.5); @@ -773,173 +596,6 @@ code { } } -.action-pagination { - display: flex; - flex-wrap: wrap; - align-items: center; - - .actions, - .pagination { - flex: 1 1 auto; - } - - .actions { - padding: 30px 0; - padding-right: 20px; - flex: 0 0 auto; - } -} - -.post-follow-actions { - text-align: center; - color: var(--primary-text-color--faint); - - div { - margin-bottom: 4px; - } -} - -.alternative-login { - margin-top: 20px; - margin-bottom: 20px; - - h4 { - font-size: 16px; - color: var(--primary-text-color); - text-align: center; - margin-bottom: 20px; - border: 0; - padding: 0; - } - - .button { - display: block; - } -} - -.scope-danger { - color: $warning-red; -} - -.form_admin_settings_site_short_description, -.form_admin_settings_site_description, -.form_admin_settings_site_extended_description, -.form_admin_settings_site_terms, -.form_admin_settings_custom_css, -.form_admin_settings_closed_registrations_message { - textarea { - font-family: var(--font-monospace), monospace; - } -} - -.input-copy { - background: var(--background-color); - border: 1px solid var(--background-color); - border-radius: 4px; - display: flex; - align-items: center; - padding-right: 4px; - position: relative; - top: 1px; - transition: border-color 300ms linear; - - &__wrapper { - flex: 1 1 auto; - } - - input[type=text] { - background: transparent; - border: 0; - padding: 10px; - font-size: 14px; - font-family: var(--font-monospace), monospace; - } - - button { - flex: 0 0 auto; - margin: 4px; - text-transform: none; - font-weight: 400; - font-size: 14px; - padding: 7px 18px; - padding-bottom: 6px; - width: auto; - transition: background 300ms linear; - } - - &.copied { - border-color: $valid-value-color; - transition: none; - - button { - background: $valid-value-color; - transition: none; - } - } -} - -.connection-prompt { - margin-bottom: 25px; - - .fa-link { - background-color: var(--brand-color--med); - border-radius: 100%; - font-size: 24px; - padding: 10px; - } - - &__column { - align-items: center; - display: flex; - flex: 1; - flex-direction: column; - flex-shrink: 1; - max-width: 50%; - - &-sep { - align-self: center; - flex-grow: 0; - overflow: visible; - position: relative; - z-index: 1; - } - - p { - word-break: break-word; - } - } - - .account__avatar { - margin-bottom: 20px; - } - - &__connection { - background-color: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - padding: 25px 10px; - position: relative; - text-align: center; - - &::after { - background-color: var(--brand-color--med); - content: ''; - display: block; - height: 100%; - left: 50%; - position: absolute; - top: 0; - width: 1px; - } - } - - &__row { - align-items: flex-start; - display: flex; - flex-direction: row; - } -} - .columns-area { form.simple_form { padding: 15px; diff --git a/app/styles/lists.scss b/app/styles/lists.scss deleted file mode 100644 index 6019cd800..000000000 --- a/app/styles/lists.scss +++ /dev/null @@ -1,19 +0,0 @@ -.no-list { - list-style: none; - - li { - display: inline-block; - margin: 0 5px; - } -} - -.recovery-codes { - list-style: none; - margin: 0 auto; - - li { - font-size: 125%; - line-height: 1.5; - letter-spacing: 1px; - } -} diff --git a/app/styles/tables.scss b/app/styles/tables.scss index 8fbe8cca6..bb7c07a3a 100644 --- a/app/styles/tables.scss +++ b/app/styles/tables.scss @@ -224,20 +224,4 @@ a.table-action-link { font-weight: 700; } } - - .nothing-here { - border: 1px solid var(--background-color); - border-top: 0; - box-shadow: none; - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 1px solid var(--background-color); - } - } - - @media screen and (max-width: 870px) { - .accounts-table tbody td.optional { - display: none; - } - } } diff --git a/app/styles/ui.scss b/app/styles/ui.scss index a8f09663a..2d4d54f71 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -219,12 +219,6 @@ display: flex; } -.domain_buttons { - height: 18px; - padding: 10px; - white-space: nowrap; -} - .muted { .status__content p, .status__content a { @@ -670,52 +664,6 @@ 100% { opacity: 1; } } -.layout-toggle { - display: flex; - padding: 5px; - - button { - box-sizing: border-box; - flex: 0 0 50%; - background: transparent; - padding: 5px; - border: 0; - position: relative; - - &:hover, - &:focus, - &:active { - svg path:first-child { - fill: var(--background-color); - } - } - } - - svg { - width: 100%; - height: auto; - - path:first-child { - fill: var(--brand-color--med); - } - - path:last-child { - fill: var(--background-color); - } - } - - &__active { - color: var(--brand-color); - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: var(--brand-color--med); - border-radius: 50%; - padding: 0.35rem; - } -} - .verified-icon { display: inline-block; margin: 0 4px 0 1px; diff --git a/app/styles/widgets.scss b/app/styles/widgets.scss deleted file mode 100644 index 92e8c7121..000000000 --- a/app/styles/widgets.scss +++ /dev/null @@ -1,534 +0,0 @@ -.hero-widget { - margin-bottom: 10px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - &__img { - width: 100%; - position: relative; - overflow: hidden; - border-radius: 4px 4px 0 0; - background: $base-shadow-color; - - img { - object-fit: cover; - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 4px 4px 0 0; - } - } - - &__text { - background: var(--brand-color--med); - padding: 20px; - border-radius: 0 0 4px 4px; - font-size: 15px; - color: var(--primary-text-color--faint); - line-height: 20px; - word-wrap: break-word; - font-weight: 400; - - .emojione { - width: 20px; - height: 20px; - margin: -3px 0 0; - } - - p { - margin-bottom: 20px; - - &:last-child { - margin-bottom: 0; - } - } - - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: var(--primary-text-color); - } - - a { - color: var(--primary-text-color--faint); - text-decoration: none; - - &:hover { - text-decoration: underline; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } -} - -.endorsements-widget { - margin-bottom: 10px; - padding-bottom: 10px; - - h4 { - padding: 10px; - text-transform: uppercase; - font-weight: 700; - font-size: 13px; - color: var(--primary-text-color--faint); - } - - .account { - padding: 10px 0; - - &:last-child { - border-bottom: 0; - } - - .account__display-name { - display: flex; - align-items: center; - } - - .account__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } - } -} - -.box-widget { - padding: 20px; - border-radius: 4px; - background: var(--background-color); - box-shadow: 0 0 1px 1px rgba($base-shadow-color, 0.2); -} - -.contact-widget, -.landing-page__information.contact-widget { - box-sizing: border-box; - padding: 20px; - min-height: 100%; - border-radius: 4px; - background: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); -} - -.contact-widget { - font-size: 15px; - color: var(--primary-text-color--faint); - line-height: 20px; - word-wrap: break-word; - font-weight: 400; - - strong { - font-weight: 500; - } - - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - } - - &__mail { - margin-top: 10px; - - a { - color: var(--primary-text-color); - text-decoration: none; - } - } -} - -.moved-account-widget { - padding: 15px; - padding-bottom: 20px; - border-radius: 4px; - background: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - color: var(--primary-text-color--faint); - font-weight: 400; - margin-bottom: 10px; - - strong, - a { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - a { - color: inherit; - text-decoration: underline; - - &.mention { - text-decoration: none; - - span { - text-decoration: none; - } - - &:focus, - &:hover, - &:active { - text-decoration: none; - - span { - text-decoration: underline; - } - } - } - } - - &__message { - margin-bottom: 15px; - - .fa { - margin-right: 5px; - color: var(--primary-text-color--faint); - } - } - - &__card { - .detailed-status__display-avatar { - position: relative; - cursor: pointer; - } - - .detailed-status__display-name { - margin-bottom: 0; - text-decoration: none; - - span { - font-weight: 400; - } - } - } -} - -.memoriam-widget { - padding: 20px; - border-radius: 4px; - background: $base-shadow-color; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - font-size: 14px; - color: var(--primary-text-color--faint); - margin-bottom: 10px; -} - -.page-header { - background: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - padding: 60px 15px; - text-align: center; - margin: 10px 0; - - h1 { - color: var(--primary-text-color); - font-size: 36px; - line-height: 1.1; - font-weight: 700; - margin-bottom: 10px; - } - - p { - font-size: 15px; - color: var(--primary-text-color--faint); - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-top: 0; - background: var(--brand-color--faint); - - h1 { - font-size: 24px; - } - } -} - -.directory { - background: var(--brand-color--med); - border-radius: 4px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - &__tag { - box-sizing: border-box; - margin-bottom: 10px; - - & > a, - & > div { - display: flex; - align-items: center; - justify-content: space-between; - background: var(--brand-color--med); - border-radius: 4px; - padding: 15px; - text-decoration: none; - color: inherit; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - } - - & > a { - &:hover, - &:active, - &:focus { - background: var(--brand-color--med); - } - } - - &.active > a { - background: var(--brand-color); - cursor: default; - } - - &.disabled > div { - opacity: 0.5; - cursor: default; - } - - h4 { - flex: 1 1 auto; - font-size: 18px; - font-weight: 700; - color: var(--primary-text-color); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - .fa { - color: var(--primary-text-color--faint); - } - - small { - display: block; - font-weight: 400; - font-size: 15px; - margin-top: 8px; - color: var(--primary-text-color--faint); - } - } - - &.active h4 { - &, - .fa, - small { - color: var(--primary-text-color); - } - } - - .avatar-stack { - flex: 0 0 auto; - width: (36px + 4px) * 3; - } - - &.active .avatar-stack .account__avatar { - border-color: var(--brand-color); - } - } -} - -.avatar-stack { - display: flex; - justify-content: flex-end; - - .account__avatar { - flex: 0 0 auto; - width: 36px; - height: 36px; - border-radius: 50%; - position: relative; - margin-left: -10px; - background: var(--background-color); - border: 2px solid var(--brand-color--med); - - &:nth-child(1) { - z-index: 1; - } - - &:nth-child(2) { - z-index: 2; - } - - &:nth-child(3) { - z-index: 3; - } - } -} - -.accounts-table { - width: 100%; - - .account { - padding: 0; - border: 0; - } - - strong { - font-weight: 700; - } - - thead th { - text-align: center; - text-transform: uppercase; - color: var(--primary-text-color--faint); - font-weight: 700; - padding: 10px; - - &:first-child { - text-align: left; - } - } - - tbody td { - padding: 15px 0; - vertical-align: middle; - border-bottom: 1px solid var(--brand-color--med); - } - - tbody tr:last-child td { - border-bottom: 0; - } - - &__count { - width: 120px; - text-align: center; - font-size: 15px; - font-weight: 500; - color: var(--primary-text-color); - - small { - display: block; - color: var(--primary-text-color--faint); - font-weight: 400; - font-size: 14px; - } - } - - &__comment { - width: 50%; - vertical-align: initial !important; - } - - @media screen and (max-width: $no-gap-breakpoint) { - tbody td.optional { - display: none; - } - } -} - -.moved-account-widget, -.memoriam-widget, -.box-widget, -.contact-widget, -.landing-page__information.contact-widget, -.directory, -.page-header { - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - border-radius: 0; - } -} - -$maximum-width: 1235px; -$fluid-breakpoint: $maximum-width + 20px; - -.statuses-grid { - min-height: 600px; - - @media screen and (max-width: 640px) { - width: 100% !important; // Masonry layout is unnecessary at this width - } - - &__item { - width: (960px - 20px) / 3; - - @media screen and (max-width: $fluid-breakpoint) { - width: (940px - 20px) / 3; - } - - @media screen and (max-width: 640px) { - width: 100%; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100vw; - } - } - - .detailed-status { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-top: 1px solid var(--background-color); - } - - &.compact { - .detailed-status__meta { - margin-top: 15px; - } - - .status__content { - font-size: 15px; - line-height: 20px; - - .emojione { - width: 20px; - height: 20px; - margin: -3px 0 0; - } - - .status__content__spoiler-link { - line-height: 20px; - margin: 0; - } - } - - .media-gallery, - .status-card, - .video-player { - margin-top: 15px; - } - } - } -} - -.notice-widget { - margin-bottom: 10px; - color: var(--primary-text-color--faint); - - p { - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - } - - a { - font-size: 14px; - line-height: 20px; - text-decoration: none; - font-weight: 500; - color: var(--brand-color); - - &:hover, - &:focus, - &:active { - text-decoration: underline; - } - } -} From c597200798a5bb2485c681be2767df1149d30037 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 23:20:15 -0500 Subject: [PATCH 32/43] Remove unused CSS, #439 --- app/styles/about.scss | 952 --------------------------------- app/styles/application.scss | 11 +- app/styles/dashboard.scss | 76 --- app/styles/rtl.scss | 79 +-- app/styles/stream_entries.scss | 159 ------ app/styles/tables.scss | 227 -------- 6 files changed, 7 insertions(+), 1497 deletions(-) delete mode 100644 app/styles/dashboard.scss delete mode 100644 app/styles/stream_entries.scss delete mode 100644 app/styles/tables.scss diff --git a/app/styles/about.scss b/app/styles/about.scss index 915cfd1f8..6a09753a6 100644 --- a/app/styles/about.scss +++ b/app/styles/about.scss @@ -1,7 +1,5 @@ $maximum-width: 1235px; $fluid-breakpoint: $maximum-width + 20px; -$column-breakpoint: 700px; -$small-breakpoint: 960px; .public-layout { .container { @@ -194,477 +192,6 @@ $small-breakpoint: 960px; } } } - - $no-columns-breakpoint: 600px; - - .grid { - display: grid; - grid-gap: 10px; - grid-template-columns: minmax(300px, 3fr) minmax(298px, 1fr); - grid-auto-columns: 25%; - grid-auto-rows: max-content; - - .column-0 { - grid-row: 1; - grid-column: 1; - } - - .column-1 { - grid-row: 1; - grid-column: 2; - } - - @media screen and (max-width: $no-columns-breakpoint) { - grid-template-columns: 100%; - grid-gap: 0; - - .column-1 { - display: none; - } - } - } - - .public-account-header { - overflow: hidden; - margin-bottom: 10px; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - - &.inactive { - opacity: 0.5; - - .public-account-header__image, - .avatar { - filter: grayscale(100%); - } - - .logo-button { - background-color: var(--primary-text-color--faint); - } - } - - &__image { - border-radius: 4px 4px 0 0; - overflow: hidden; - height: 300px; - position: relative; - background: var(--background-color); - - &::after { - content: ""; - display: block; - position: absolute; - width: 100%; - height: 100%; - box-shadow: inset 0 -1px 1px 1px rgba($base-shadow-color, 0.15); - top: 0; - left: 0; - } - - img { - object-fit: cover; - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 4px 4px 0 0; - } - - @media screen and (max-width: 600px) { - height: 200px; - } - } - - &--no-bar { - margin-bottom: 0; - - .public-account-header__image, - .public-account-header__image img { - border-radius: 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - box-shadow: none; - - &__image::after { - display: none; - } - - &__image, - &__image img { - border-radius: 0; - } - } - - &__bar { - position: relative; - margin-top: -80px; - display: flex; - justify-content: flex-start; - - &::before { - content: ""; - display: block; - background: var(--brand-color--faint); - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 60px; - border-radius: 0 0 4px 4px; - z-index: -1; - } - - .avatar { - display: block; - width: 120px; - height: 120px; - padding-left: 20px - 4px; - flex: 0 0 auto; - - img { - display: block; - width: 100%; - height: 100%; - margin: 0; - border-radius: 50%; - border: 4px solid var(--brand-color--faint); - background: var(--background-color); - } - } - - @media screen and (max-width: 600px) { - margin-top: 0; - background: var(--brand-color--faint); - border-radius: 0 0 4px 4px; - padding: 5px; - - &::before { - display: none; - } - - .avatar { - width: 48px; - height: 48px; - padding: 7px 0; - padding-left: 10px; - - img { - border: 0; - border-radius: 4px; - } - - @media screen and (max-width: 360px) { - display: none; - } - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - - @media screen and (max-width: $no-columns-breakpoint) { - flex-wrap: wrap; - } - } - - &__tabs { - flex: 1 1 auto; - margin-left: 20px; - - &__name { - padding-top: 20px; - padding-bottom: 8px; - - h1 { - font-size: 20px; - line-height: 18px * 1.5; - color: var(--primary-text-color); - font-weight: 500; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - text-shadow: 1px 1px 1px $base-shadow-color; - - small { - display: block; - font-size: 14px; - color: var(--primary-text-color); - font-weight: 400; - overflow: hidden; - text-overflow: ellipsis; - } - } - } - - @media screen and (max-width: 600px) { - margin-left: 15px; - display: flex; - justify-content: space-between; - align-items: center; - - &__name { - padding-top: 0; - padding-bottom: 0; - - h1 { - font-size: 16px; - line-height: 24px; - text-shadow: none; - - small { - color: var(--primary-text-color--faint); - } - } - } - } - - &__tabs { - display: flex; - justify-content: flex-start; - align-items: stretch; - height: 58px; - - .details-counters { - display: flex; - flex-direction: row; - min-width: 300px; - } - - @media screen and (max-width: $no-columns-breakpoint) { - .details-counters { - display: none; - } - } - - .counter { - width: 33.3%; - box-sizing: border-box; - flex: 0 0 auto; - color: var(--primary-text-color--faint); - padding: 10px; - border-right: 1px solid var(--brand-color--faint); - cursor: default; - text-align: center; - position: relative; - - a { - display: block; - } - - &:last-child { - border-right: 0; - } - - &::after { - display: block; - content: ""; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - border-bottom: 4px solid var(--brand-color); - opacity: 0.5; - transition: all 400ms ease; - } - - &.active { - &::after { - border-bottom: 4px solid var(--highlight-text-color); - opacity: 1; - } - - &.inactive::after { - border-bottom-color: var(--primary-text-color--faint); - } - } - - &:hover { - &::after { - opacity: 1; - transition-duration: 100ms; - } - } - - a { - text-decoration: none; - color: inherit; - } - - .counter-label { - font-size: 12px; - display: block; - } - - .counter-number { - font-weight: 500; - font-size: 18px; - margin-bottom: 5px; - color: var(--primary-text-color); - font-family: var(--font-display), sans-serif; - } - } - - .spacer { - flex: 1 1 auto; - height: 1px; - } - - &__buttons { - padding: 7px 8px; - } - } - } - - &__extra { - display: none; - margin-top: 4px; - - .public-account-bio { - border-radius: 0; - box-shadow: none; - background: transparent; - margin: 0 -5px; - - .roles { - display: none; - } - } - - &__links { - margin-top: -15px; - font-size: 14px; - color: var(--primary-text-color--faint); - - a { - display: inline-block; - color: var(--primary-text-color--faint); - text-decoration: none; - padding: 15px; - font-weight: 500; - - strong { - font-weight: 700; - color: var(--primary-text-color); - } - } - } - - @media screen and (max-width: $no-columns-breakpoint) { - display: block; - flex: 100%; - } - } - } - - .account__section-headline { - border-radius: 4px 4px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } - } - - .detailed-status__meta { - margin-top: 25px; - } - - .public-account-bio { - background: var(--brand-color--med); - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; - - @media screen and (max-width: $no-gap-breakpoint) { - box-shadow: none; - margin-bottom: 0; - border-radius: 0; - } - - .account__header__content { - padding: 20px; - padding-bottom: 0; - color: var(--primary-text-color); - } - - &__extra, - .roles { - padding: 20px; - font-size: 14px; - color: var(--primary-text-color--faint); - } - - .roles { - padding-bottom: 0; - } - } - - .static-icon-button { - color: var(--brand-color); - font-size: 18px; - - & > span { - font-size: 14px; - font-weight: 500; - } - } - - .card-grid { - display: flex; - flex-wrap: wrap; - min-width: 100%; - margin: 0 -5px; - - & > div { - box-sizing: border-box; - flex: 1 0 auto; - width: 300px; - padding: 0 5px; - margin-bottom: 10px; - max-width: 33.333%; - - @media screen and (max-width: 900px) { - max-width: 50%; - } - - @media screen and (max-width: 600px) { - max-width: 100%; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - margin: 0; - border-top: 1px solid var(--brand-color--med); - - & > div { - width: 100%; - padding: 0; - margin-bottom: 0; - border-bottom: 1px solid var(--brand-color--med); - - &:last-child { - border-bottom: 0; - } - - .card__bar { - background: var(--brand-color--med); - - &:hover, - &:active, - &:focus { - background: var(--brand-color--faint); - } - } - } - } - } } .container { @@ -831,481 +358,6 @@ $small-breakpoint: 960px; } } -.information-board { - background: var(--brand-color--med); - padding: 20px 0; - - &__sections { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - } - - &__section { - flex: 1 0 0; - font-family: var(--font-sans-serif), sans-serif; - font-size: 16px; - line-height: 28px; - color: var(--primary-text-color); - text-align: right; - padding: 10px 15px; - - span, - strong { - display: block; - } - - span { - &:last-child { - color: var(--primary-text-color--faint); - } - } - - strong { - font-family: var(--font-display), sans-serif; - font-weight: 500; - font-size: 32px; - line-height: 48px; - } - - @media screen and (max-width: $column-breakpoint) { - text-align: center; - } - } - - .panel { - position: absolute; - width: 280px; - box-sizing: border-box; - background: var(--background-color); - padding: 20px; - padding-top: 10px; - border-radius: 4px 4px 0 0; - right: 0; - bottom: -40px; - - .panel-header { - font-family: var(--font-display), sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - color: var(--primary-text-color--faint); - padding-bottom: 5px; - margin-bottom: 15px; - border-bottom: 1px solid var(--brand-color--faint); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - - a, - span { - font-weight: 400; - color: var(--primary-text-color); - } - - a { - text-decoration: none; - } - } - } - - .owner { - text-align: center; - - .avatar { - width: 80px; - height: 80px; - margin: 0 auto; - margin-bottom: 15px; - - img { - display: block; - width: 80px; - height: 80px; - border-radius: 48px; - } - } - - .name { - font-size: 14px; - - a { - display: block; - color: var(--primary-text-color); - text-decoration: none; - - &:hover { - .display_name { - text-decoration: underline; - } - } - } - - .username { - display: block; - color: var(--primary-text-color--faint); - } - } - } -} - -.landing-page { - p, - li { - font-family: var(--font-sans-serif), sans-serif; - font-size: 16px; - font-weight: 400; - font-size: 16px; - line-height: 30px; - margin-bottom: 12px; - color: var(--primary-text-color--faint); - - a { - color: var(--highlight-text-color); - text-decoration: underline; - } - } - - em { - display: inline; - margin: 0; - padding: 0; - font-weight: 700; - background: transparent; - font-family: inherit; - font-size: inherit; - line-height: inherit; - color: var(--primary-text-color); - } - - h1 { - font-family: var(--font-display), sans-serif; - font-size: 26px; - line-height: 30px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - - small { - font-family: var(--font-sans-serif), sans-serif; - display: block; - font-size: 18px; - font-weight: 400; - color: var(--primary-text-color); - } - } - - h2 { - font-family: var(--font-display), sans-serif; - font-size: 22px; - line-height: 26px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - } - - h3 { - font-family: var(--font-display), sans-serif; - font-size: 18px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - } - - h4 { - font-family: var(--font-display), sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - } - - h5 { - font-family: var(--font-display), sans-serif; - font-size: 14px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - } - - h6 { - font-family: var(--font-display), sans-serif; - font-size: 12px; - line-height: 24px; - font-weight: 500; - margin-bottom: 20px; - color: var(--primary-text-color--faint); - } - - ul, - ol { - margin-left: 20px; - - &[type='a'] { - list-style-type: lower-alpha; - } - - &[type='i'] { - list-style-type: lower-roman; - } - } - - ul { - list-style: disc; - } - - ol { - list-style: decimal; - } - - li > ol, - li > ul { - margin-top: 6px; - } - - hr { - width: 100%; - height: 0; - border: 0; - border-bottom: 1px solid hsla(var(--background-color_hsl), .6); - margin: 20px 0; - - &.spacer { - height: 1px; - border: 0; - } - } - - &__information, - &__forms { - padding: 20px; - } - - &__call-to-action { - background: var(--brand-color--med); - border-radius: 4px; - padding: 25px 40px; - overflow: hidden; - box-sizing: border-box; - - .row { - width: 100%; - display: flex; - flex-direction: row-reverse; - flex-wrap: nowrap; - justify-content: space-between; - align-items: center; - } - - .row__information-board { - display: flex; - justify-content: flex-end; - align-items: flex-end; - - .information-board__section { - flex: 1 0 auto; - padding: 0 10px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - width: 100%; - justify-content: space-between; - } - } - - .row__mascot { - flex: 1; - margin: 10px -50px 0 0; - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } - } - } - - &__logo { - margin-right: 20px; - - img { - height: 50px; - width: auto; - mix-blend-mode: lighten; - } - } - - &__information { - padding: 45px 40px; - margin-bottom: 10px; - - &:last-child { - margin-bottom: 0; - } - - strong { - font-weight: 500; - color: var(--primary-text-color); - } - - .account { - border-bottom: 0; - padding: 0; - - &__display-name { - align-items: center; - display: flex; - margin-right: 5px; - } - - div.account__display-name { - &:hover { - .display-name strong { - text-decoration: none; - } - } - - .account__avatar { - cursor: default; - } - } - - &__avatar-wrapper { - margin-left: 0; - flex: 0 0 auto; - } - - &__avatar { - width: 44px; - height: 44px; - background-size: 44px 44px; - } - - .display-name { - font-size: 15px; - - &__account { - font-size: 14px; - } - } - } - - @media screen and (max-width: $small-breakpoint) { - .contact { - margin-top: 30px; - } - } - - @media screen and (max-width: $column-breakpoint) { - padding: 25px 20px; - } - } - - &__information, - &__forms, - #soapbox-timeline { - box-sizing: border-box; - background: var(--brand-color--med); - border-radius: 4px; - box-shadow: 0 0 6px rgba(#000000, 0.1); - } - - &__mascot { - height: 104px; - position: relative; - left: -40px; - bottom: 25px; - - img { - height: 190px; - width: auto; - } - } - - &__short-description { - .row { - display: flex; - flex-wrap: wrap; - align-items: center; - margin-bottom: 40px; - } - - @media screen and (max-width: $column-breakpoint) { - .row { - margin-bottom: 20px; - } - } - - p a { - color: var(--primary-text-color--faint); - } - - h1 { - font-weight: 500; - color: var(--primary-text-color); - margin-bottom: 0; - - small { - color: var(--primary-text-color--faint); - - span { - color: var(--primary-text-color--faint); - } - } - } - - p:last-child { - margin-bottom: 0; - } - } - - &__hero { - margin-bottom: 10px; - - img { - display: block; - margin: 0; - max-width: 100%; - height: auto; - border-radius: 4px; - } - } - - @media screen and (max-width: 840px) { - .information-board { - .panel { - position: static; - margin-top: 20px; - width: 100%; - border-radius: 4px; - - .panel-header { - text-align: center; - } - } - } - } - - @media screen and (max-width: 675px) { - .header-wrapper { - padding-top: 0; - - &.compact { - padding-bottom: 0; - } - - &.compact .hero .heading { - text-align: initial; - } - } - } - - .cta { - margin: 20px; - } -} - .public-layout { position: relative; background-color: var(--brand-color); @@ -1553,10 +605,6 @@ $small-breakpoint: 960px; } } -.public-layout pre.canary { - white-space: pre-wrap; -} - .about-page { background: var(--brand-color--faint); border-radius: inherit; diff --git a/app/styles/application.scss b/app/styles/application.scss index dfa13b9ae..06e2921d7 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -8,7 +8,6 @@ @import 'footer'; @import 'forms'; @import 'accounts'; -@import 'stream_entries'; @import 'boost'; @import 'loading'; @import 'ui'; @@ -16,8 +15,6 @@ // @import 'introduction'; @import 'emoji_picker'; @import 'about'; -@import 'tables'; -@import 'dashboard'; @import 'rtl'; @import 'accessibility'; @import 'donations'; @@ -35,10 +32,10 @@ @import 'components/account-header'; @import 'components/user-panel'; @import 'components/compose-form'; -@import 'components/group-card'; -@import 'components/group-detail'; -@import 'components/group-form'; -@import 'components/group-sidebar-panel'; +// @import 'components/group-card'; +// @import 'components/group-detail'; +// @import 'components/group-form'; +// @import 'components/group-sidebar-panel'; @import 'components/sidebar-menu'; @import 'components/hotkeys-modal'; @import 'components/emoji-reacts'; diff --git a/app/styles/dashboard.scss b/app/styles/dashboard.scss deleted file mode 100644 index 2099c5312..000000000 --- a/app/styles/dashboard.scss +++ /dev/null @@ -1,76 +0,0 @@ -.dashboard__counters { - display: flex; - flex-wrap: wrap; - margin: 0 -5px; - margin-bottom: 20px; - - & > div { - box-sizing: border-box; - flex: 0 0 33.333%; - padding: 0 5px; - margin-bottom: 10px; - - & > div, - & > a { - padding: 20px; - background: var(--brand-color--faint); - border-radius: 4px; - } - - & > a { - text-decoration: none; - color: inherit; - display: block; - - &:hover, - &:focus, - &:active { - background: var(--brand-color--med); - } - } - } - - &__num, - &__text { - text-align: center; - font-weight: 500; - font-size: 24px; - line-height: 21px; - color: var(--primary-text-color); - font-family: var(--font-display), sans-serif; - margin-bottom: 20px; - line-height: 30px; - } - - &__text { - font-size: 18px; - } - - &__label { - font-size: 14px; - color: var(--primary-text-color--faint); - text-align: center; - font-weight: 500; - } -} - -.dashboard__widgets { - display: flex; - flex-wrap: wrap; - margin: 0 -5px; - - & > div { - flex: 0 0 33.333%; - margin-bottom: 20px; - - & > div { - padding: 0 5px; - } - } - - a:not(.name-tag) { - color: var(--background-color); - font-weight: 500; - text-decoration: none; - } -} diff --git a/app/styles/rtl.scss b/app/styles/rtl.scss index 848547ee2..e8e59d734 100644 --- a/app/styles/rtl.scss +++ b/app/styles/rtl.scss @@ -7,16 +7,6 @@ body.rtl { padding-right: 15px; } - .landing-page__logo { - margin-right: 0; - margin-left: 20px; - } - - .landing-page .features-list .features-list__row .visual { - margin-left: 0; - margin-right: 15px; - } - .column-link__icon, .column-header__icon { margin-right: 0; @@ -83,23 +73,16 @@ body.rtl { right: 10px; } - .status, - .activity-stream .status.light { + .status { padding-left: 10px; padding-right: 68px; } - .status__info .status__display-name, - .activity-stream .status.light .status__display-name { + .status__info .status__display-name { padding-left: 25px; padding-right: 0; } - .activity-stream .pre-header { - padding-right: 68px; - padding-left: 0; - } - .status__prepend { margin-left: 0; margin-right: 68px; @@ -110,11 +93,6 @@ body.rtl { right: -26px; } - .activity-stream .pre-header .pre-header__icon { - left: auto; - right: 42px; - } - .account__avatar-overlay-overlay { right: auto; left: 0; @@ -125,8 +103,7 @@ body.rtl { left: 0; } - .status__relative-time, - .activity-stream .status.light .status__header .status__meta { + .status__relative-time { float: left; } @@ -256,44 +233,6 @@ body.rtl { margin-left: 45px; } - .landing-page .header-wrapper .mascot { - right: 60px; - left: auto; - } - - .landing-page__call-to-action .row__information-board { - direction: rtl; - } - - .landing-page .header .hero .floats .float-1 { - left: -120px; - right: auto; - } - - .landing-page .header .hero .floats .float-2 { - left: 210px; - right: auto; - } - - .landing-page .header .hero .floats .float-3 { - left: 110px; - right: auto; - } - - .landing-page .header .links .brand img { - left: 0; - } - - .landing-page .fa-external-link { - padding-right: 5px; - padding-left: 0 !important; - } - - .landing-page .features #soapbox-timeline { - margin-right: 0; - margin-left: 30px; - } - @media screen and (min-width: 631px) { .column, .drawer { @@ -329,18 +268,6 @@ body.rtl { } } - .landing-page__information { - .account__display-name { - margin-right: 0; - margin-left: 5px; - } - - .account__avatar-wrapper { - margin-left: 12px; - margin-right: 0; - } - } - .card__bar .display-name { margin-left: 0; margin-right: 15px; diff --git a/app/styles/stream_entries.scss b/app/styles/stream_entries.scss deleted file mode 100644 index 653454ae2..000000000 --- a/app/styles/stream_entries.scss +++ /dev/null @@ -1,159 +0,0 @@ -.activity-stream { - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; - - @media screen and (max-width: $no-gap-breakpoint) { - margin-bottom: 0; - border-radius: 0; - box-shadow: none; - } - - &--headless { - border-radius: 0; - margin: 0; - box-shadow: none; - - .detailed-status, - .status { - border-radius: 0 !important; - } - } - - div[data-component] { - width: 100%; - } - - .entry { - background: var(--brand-color--med); - - .detailed-status, - .status, - .load-more { - animation: none; - } - - &:last-child { - .detailed-status, - .status, - .load-more { - border-bottom: 0; - border-radius: 0 0 4px 4px; - } - } - - &:first-child { - .detailed-status, - .status, - .load-more { - border-radius: 4px 4px 0 0; - } - - &:last-child { - .detailed-status, - .status, - .load-more { - border-radius: 4px; - } - } - } - - @media screen and (max-width: 740px) { - .detailed-status, - .status, - .load-more { - border-radius: 0 !important; - } - } - } - - &--highlighted .entry { - background: var(--brand-color--med); - } -} - -.button.logo-button { - flex: 0 auto; - font-size: 14px; - background: var(--brand-color); - color: #fff; - text-transform: none; - line-height: 36px; - height: auto; - padding: 3px 15px; - border: 0; - - svg { - width: 20px; - height: auto; - vertical-align: middle; - margin-right: 5px; - fill: var(--primary-text-color); - } - - &:active, - &:focus, - &:hover { - background: var(--brand-color--hicontrast); - } - - &:disabled, - &.disabled { - &:active, - &:focus, - &:hover { - background: var(--brand-color--med); - } - } - - &.button--destructive { - &:active, - &:focus, - &:hover { - background: $error-red; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - svg { - display: none; - } - } -} - -.embed, -.public-layout { - .detailed-status { - padding: 15px; - } - - .status { - padding: 15px 15px 15px (48px + 15px * 2); - min-height: 48px + 2px; - - &__avatar { - left: 15px; - top: 17px; - } - - &__content { - padding-top: 5px; - } - - &__prepend { - margin-left: 48px + 15px * 2; - padding-top: 15px; - } - - &__prepend-icon-wrapper { - left: -32px; - } - - .media-gallery, - &__action-bar, - .video-player { - margin-top: 10px; - } - } -} diff --git a/app/styles/tables.scss b/app/styles/tables.scss deleted file mode 100644 index bb7c07a3a..000000000 --- a/app/styles/tables.scss +++ /dev/null @@ -1,227 +0,0 @@ -.table { - width: 100%; - max-width: 100%; - border-spacing: 0; - border-collapse: collapse; - - th, - td { - padding: 8px; - line-height: 18px; - vertical-align: top; - border-top: 1px solid var(--brand-color--med); - text-align: left; - background: var(--brand-color--med); - } - - & > thead > tr > th { - vertical-align: bottom; - border-bottom: 2px solid var(--brand-color--med); - border-top: 0; - font-weight: 500; - } - - & > tbody > tr > th { - font-weight: 500; - } - - & > tbody > tr:nth-child(odd) > td, - & > tbody > tr:nth-child(odd) > th { - background: var(--brand-color--med); - } - - a { - color: var(--highlight-text-color); - text-decoration: underline; - - &:hover { - text-decoration: none; - } - } - - strong { - font-weight: 500; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } - - &.inline-table { - & > tbody > tr:nth-child(odd) { - & > td, - & > th { - background: transparent; - } - } - - & > tbody > tr:first-child { - & > td, - & > th { - border-top: 0; - } - } - } - - &.batch-table { - & > thead > tr > th { - background: var(--brand-color--med); - border-top: 1px solid var(--background-color); - border-bottom: 1px solid var(--background-color); - - &:first-child { - border-radius: 4px 0 0; - border-left: 1px solid var(--background-color); - } - - &:last-child { - border-radius: 0 4px 0 0; - border-right: 1px solid var(--background-color); - } - } - } - - &--invites tbody td { - vertical-align: middle; - } -} - -.table-wrapper { - overflow: auto; - margin-bottom: 20px; -} - -samp { - font-family: var(--font-monospace), monospace; -} - -button.table-action-link { - background: transparent; - border: 0; - font: inherit; -} - -button.table-action-link, -a.table-action-link { - text-decoration: none; - display: inline-block; - margin-right: 5px; - padding: 0 10px; - color: var(--primary-text-color--faint); - font-weight: 500; - - &:hover { - color: var(--primary-text-color); - } - - i.fa { - font-weight: 400; - margin-right: 5px; - } - - &:first-child { - padding-left: 0; - } -} - -.batch-table { - &__toolbar, - &__row { - display: flex; - - &__select { - box-sizing: border-box; - padding: 8px 16px; - cursor: pointer; - min-height: 100%; - - input { - margin-top: 8px; - } - - &--aligned { - display: flex; - align-items: center; - - input { - margin-top: 0; - } - } - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } - } - - &__actions, - &__content { - padding: 8px 0; - padding-right: 16px; - flex: 1 1 auto; - } - } - - &__toolbar { - border: 1px solid var(--background-color); - background: var(--brand-color--med); - border-radius: 4px 0 0; - height: 47px; - align-items: center; - - &__actions { - text-align: right; - padding-right: 16px - 5px; - } - - @media screen and (max-width: $no-gap-breakpoint) { - display: none; - } - } - - &__row { - border: 1px solid var(--background-color); - border-top: 0; - background: var(--brand-color--med); - - @media screen and (max-width: $no-gap-breakpoint) { - &:first-child { - border-top: 1px solid var(--background-color); - } - } - - &:hover { - background: var(--background-color); - } - - &:nth-child(even) { - background: var(--brand-color--med); - - &:hover { - background: var(--brand-color--faint); - } - } - - &__content { - padding-top: 12px; - padding-bottom: 16px; - - &--unpadded { - padding: 0; - } - } - } - - .status__content { - padding-top: 0; - - summary { - display: list-item; - } - - strong { - font-weight: 700; - } - } -} From 3d81dd2892f67236878f54228da8f2e0981946ed Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 27 Sep 2020 23:53:43 -0500 Subject: [PATCH 33/43] DetailedStatus: fix link on display name --- app/soapbox/features/status/components/detailed_status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/features/status/components/detailed_status.js b/app/soapbox/features/status/components/detailed_status.js index 131de49ec..ee162810e 100644 --- a/app/soapbox/features/status/components/detailed_status.js +++ b/app/soapbox/features/status/components/detailed_status.js @@ -175,7 +175,7 @@ export default class DetailedStatus extends ImmutablePureComponent { - + From 5b6833060eaedf740e919e5b50952a9c13914724 Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Mon, 28 Sep 2020 00:01:36 -0500 Subject: [PATCH 34/43] fix spacing on preferences options --- app/styles/dyslexic.scss | 1 + app/styles/forms.scss | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/styles/dyslexic.scss b/app/styles/dyslexic.scss index 9dd348fec..6db2cadc2 100644 --- a/app/styles/dyslexic.scss +++ b/app/styles/dyslexic.scss @@ -1,5 +1,6 @@ .dyslexic { font-family: 'OpenDyslexic' !important; + margin-bottom: 8px; } body.dyslexic { diff --git a/app/styles/forms.scss b/app/styles/forms.scss index 147199870..da97a6006 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -13,7 +13,7 @@ code { .simple_form { .input { - margin-bottom: 15px; + margin-bottom: 8px; overflow: hidden; &.hidden { @@ -43,7 +43,6 @@ code { &.boolean { position: relative; - margin-bottom: 0; .label_input > label { font-family: inherit; @@ -111,7 +110,6 @@ code { span.hint { display: block; font-size: 12px; - margin-top: 4px; } p.hint { @@ -172,15 +170,10 @@ code { font-size: 14px; color: var(--primary-text-color); display: block; - margin-bottom: 8px; word-wrap: break-word; font-weight: 500; } - .hint { - margin-top: 6px; - } - ul { flex: 390px; } From 57be8b0e2c6ce16320f21ce06567b7f856dd50c0 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 10:25:12 -0500 Subject: [PATCH 35/43] DetailedStatus: display name spacing hotfix --- app/styles/components/detailed-status.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/styles/components/detailed-status.scss b/app/styles/components/detailed-status.scss index ceab5899c..5bf63db28 100644 --- a/app/styles/components/detailed-status.scss +++ b/app/styles/components/detailed-status.scss @@ -114,6 +114,10 @@ color: var(--primary-text-color); } + span.hover-ref-wrapper { + display: inline; + } + .display-name__account { display: block; margin-top: -10px; From 061a820ce5b186ac920ebaa48b3effb1d51bc82c Mon Sep 17 00:00:00 2001 From: Mary Kate Date: Mon, 28 Sep 2020 00:57:31 -0500 Subject: [PATCH 36/43] fix language picker dropdown arrow --- .../__snapshots__/forms-test.js.snap | 38 ++++++++++--------- app/soapbox/features/forms/index.js | 2 +- app/styles/forms.scss | 18 +++++++++ 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/app/soapbox/features/forms/__tests__/__snapshots__/forms-test.js.snap b/app/soapbox/features/forms/__tests__/__snapshots__/forms-test.js.snap index 0f1e2df50..1bd05c5fd 100644 --- a/app/soapbox/features/forms/__tests__/__snapshots__/forms-test.js.snap +++ b/app/soapbox/features/forms/__tests__/__snapshots__/forms-test.js.snap @@ -55,23 +55,27 @@ exports[` renders correctly 1`] = ` `; exports[` renders correctly 1`] = ` - +
+ +
`; exports[` renders correctly 1`] = ` diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js index 4330919b3..5c7951ddb 100644 --- a/app/soapbox/features/forms/index.js +++ b/app/soapbox/features/forms/index.js @@ -236,7 +236,7 @@ export class SelectDropdown extends ImmutablePureComponent { )); - const selectElem = ; + const selectElem =
; return label ? ( {selectElem} diff --git a/app/styles/forms.scss b/app/styles/forms.scss index da97a6006..5025a5a43 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -429,6 +429,7 @@ code { } select { + appearance: none; box-sizing: border-box; font-size: 16px; color: var(--primary-text-color); @@ -444,6 +445,23 @@ code { padding-right: 30px; height: 41px; position: relative; + margin-top: 8px; + cursor: pointer; + } + + .select-wrapper::after { + display: block; + font-family: 'ForkAwesome'; + content: ''; + width: 10px; + position: absolute; + right: 12px; + top: 1px; + border-left: 1px solid var(--highlight-text-color); + height: 39px; + padding: 12px; + box-sizing: border-box; + pointer-events: none; } .label_input { From 76b7b32e2331846f3c1526b10cf6a34a9e7bff65 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 13:05:20 -0500 Subject: [PATCH 37/43] Revoke OAuth token on logout, fixes #445 --- app/soapbox/actions/auth.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/soapbox/actions/auth.js b/app/soapbox/actions/auth.js index 50a26cd3c..09a225bf5 100644 --- a/app/soapbox/actions/auth.js +++ b/app/soapbox/actions/auth.js @@ -145,7 +145,17 @@ export function logIn(username, password) { export function logOut() { return (dispatch, getState) => { + const state = getState(); + dispatch({ type: AUTH_LOGGED_OUT }); + + // Attempt to destroy OAuth token on logout + api(getState).post('/oauth/revoke', { + client_id: state.getIn(['auth', 'app', 'client_id']), + client_secret: state.getIn(['auth', 'app', 'client_secret']), + token: state.getIn(['auth', 'user', 'access_token']), + }); + dispatch(showAlert('Successfully logged out.', '')); }; } From edf3612f93b1b91729b5694d384a2145f1338cb5 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 13:52:13 -0500 Subject: [PATCH 38/43] SoapboxConfig: hide CustomCSS for now, #447 --- app/soapbox/features/soapbox_config/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js index a3e775d7c..9b8b1942d 100644 --- a/app/soapbox/features/soapbox_config/index.js +++ b/app/soapbox/features/soapbox_config/index.js @@ -320,7 +320,7 @@ class SoapboxConfig extends ImmutablePureComponent { -
+ {/*
@@ -344,7 +344,7 @@ class SoapboxConfig extends ImmutablePureComponent {
- + */}
From 7d44e8411198f7823c910ca1be7c4092ad1bb898 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 14:57:15 -0500 Subject: [PATCH 39/43] Timelines: fix existingQueuedStatus bug --- app/soapbox/reducers/timelines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/reducers/timelines.js b/app/soapbox/reducers/timelines.js index 3c514849a..c86376406 100644 --- a/app/soapbox/reducers/timelines.js +++ b/app/soapbox/reducers/timelines.js @@ -89,7 +89,7 @@ const updateTimelineQueue = (state, timeline, statusId) => { const listedStatuses = state.getIn([timeline, 'items'], ImmutableList()); const totalQueuedItemsCount = state.getIn([timeline, 'totalQueuedItemsCount'], 0); - let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus.get('id') === statusId); + let alreadyExists = queuedStatuses.find(existingQueuedStatus => existingQueuedStatus === statusId); if (!alreadyExists) alreadyExists = listedStatuses.find(existingListedStatusId => existingListedStatusId === statusId); if (alreadyExists) { From fb94664c08d8c75f127042e1ef04dd4c16a67e66 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 15:24:17 -0500 Subject: [PATCH 40/43] Composer: fix #449 hydrate tagHistory --- app/soapbox/reducers/compose.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/soapbox/reducers/compose.js b/app/soapbox/reducers/compose.js index 1e0b6d614..c72ea6d87 100644 --- a/app/soapbox/reducers/compose.js +++ b/app/soapbox/reducers/compose.js @@ -42,6 +42,7 @@ import { REDRAFT } from '../actions/statuses'; import { ME_FETCH_SUCCESS, ME_PATCH_SUCCESS } from '../actions/me'; import { SETTING_CHANGE, FE_NAME } from '../actions/settings'; import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { tagHistory } from 'soapbox/settings'; import uuid from '../uuid'; import { unescapeHTML } from '../utils/html'; @@ -362,9 +363,12 @@ export default function compose(state = initialState, action) { return state.update('poll', poll => poll.set('expires_in', action.expiresIn).set('multiple', action.isMultiple)); case ME_FETCH_SUCCESS: me = fromJS(action.me); - defaultPrivacy = me.getIn(['pleroma', 'settings_store', FE_NAME, 'defaultPrivacy']); - if (!defaultPrivacy) return state; - return state.set('default_privacy', defaultPrivacy).set('privacy', defaultPrivacy); + defaultPrivacy = me.getIn(['pleroma', 'settings_store', FE_NAME, 'defaultPrivacy'], 'public'); + return state.merge({ + default_privacy: defaultPrivacy, + privacy: defaultPrivacy, + tagHistory: ImmutableList(tagHistory.get(action.me.id)), + }); case ME_PATCH_SUCCESS: me = fromJS(action.me); defaultPrivacy = me.getIn(['pleroma', 'settings_store', FE_NAME, 'defaultPrivacy']); From 18d08a3630eb2a3282918fbb394a8120b3000ee2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 19:05:45 -0500 Subject: [PATCH 41/43] Revert "Composer: remove click listener on unmount" This reverts commit 136965acdd1197bbeeb4123831ca63f8d3c3c65a. --- app/soapbox/components/autosuggest_textarea.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index 060e817e8..703aa9797 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -182,10 +182,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { document.addEventListener('click', this.handleClick, true); } - componentWillUnmount() { - document.removeEventListener('click', this.handleClick, true); - } - componentDidUpdate(prevProps, prevState) { const { suggestions } = this.props; if (suggestions !== prevProps.suggestions && suggestions.size > 0 && prevState.suggestionsHidden && prevState.focused) { From b78a00a306b4d91c29103fac128d7cacb5cefef6 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 19:05:56 -0500 Subject: [PATCH 42/43] Revert "pass getClickableArea into autosuggesttextarea as prop" This reverts commit 99f0a069dd6092e7d9c835a5f2ffc2db42bf14bf. --- app/soapbox/components/autosuggest_textarea.js | 8 ++++++-- app/soapbox/features/compose/components/compose_form.js | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index 703aa9797..e60661f6b 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -51,7 +51,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { onFocus: PropTypes.func, onBlur: PropTypes.func, clickableAreaRef: PropTypes.object, - getClickableArea: PropTypes.func.isRequired, }; static defaultProps = { @@ -165,9 +164,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); } + getClickableArea = () => { + const { clickableAreaRef } = this.props; + return clickableAreaRef ? clickableAreaRef.current : this.form; + } + isClickInside = (e) => { return [ - this.props.getClickableArea(), + this.getClickableArea(), document.querySelector('.autosuggest-textarea__textarea'), ].some(element => element && element.contains(e.target)); } diff --git a/app/soapbox/features/compose/components/compose_form.js b/app/soapbox/features/compose/components/compose_form.js index 39647c923..af8ce4263 100644 --- a/app/soapbox/features/compose/components/compose_form.js +++ b/app/soapbox/features/compose/components/compose_form.js @@ -286,7 +286,6 @@ class ComposeForm extends ImmutablePureComponent { onSuggestionSelected={this.onSuggestionSelected} onPaste={onPaste} autoFocus={shouldAutoFocus} - getClickableArea={this.getClickableArea} > { !condensed && From f71a1e4ce1ed0c8a81d69177040d382c7a2a2d4b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 28 Sep 2020 19:06:06 -0500 Subject: [PATCH 43/43] Revert "fix composer jump by reseting lastToken on click and arrow up and down in autosuggesttextarea" This reverts commit 0b7763e7267108b7ef70713b6ecbf9c85c952d48. --- .../components/autosuggest_textarea.js | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/app/soapbox/components/autosuggest_textarea.js b/app/soapbox/components/autosuggest_textarea.js index e60661f6b..d9a044022 100644 --- a/app/soapbox/components/autosuggest_textarea.js +++ b/app/soapbox/components/autosuggest_textarea.js @@ -50,7 +50,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { autoFocus: PropTypes.bool, onFocus: PropTypes.func, onBlur: PropTypes.func, - clickableAreaRef: PropTypes.object, }; static defaultProps = { @@ -108,8 +107,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { if (suggestions.size > 0 && !suggestionsHidden) { e.preventDefault(); this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); - } else { - this.setState({ lastToken: null }); } break; @@ -117,8 +114,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { if (suggestions.size > 0 && !suggestionsHidden) { e.preventDefault(); this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); - } else { - this.setState({ lastToken: null }); } break; @@ -164,28 +159,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { this.textarea.focus(); } - getClickableArea = () => { - const { clickableAreaRef } = this.props; - return clickableAreaRef ? clickableAreaRef.current : this.form; - } - - isClickInside = (e) => { - return [ - this.getClickableArea(), - document.querySelector('.autosuggest-textarea__textarea'), - ].some(element => element && element.contains(e.target)); - } - - handleClick = (e) => { - if (this.isClickInside(e)) { - this.setState({ lastToken: null }); - } - } - - componentDidMount() { - document.addEventListener('click', this.handleClick, true); - } - componentDidUpdate(prevProps, prevState) { const { suggestions } = this.props; if (suggestions !== prevProps.suggestions && suggestions.size > 0 && prevState.suggestionsHidden && prevState.focused) {