about summary refs log tree commit diff
path: root/app/javascript/mastodon/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/mastodon/features')
-rw-r--r--app/javascript/mastodon/features/account/components/header.js3
-rw-r--r--app/javascript/mastodon/features/account_timeline/components/header.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js8
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/compose_form_container.js1
-rw-r--r--app/javascript/mastodon/features/compose/index.js19
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js68
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js3
-rw-r--r--app/javascript/mastodon/features/notifications/containers/notification_container.js3
-rw-r--r--app/javascript/mastodon/features/notifications/index.js33
-rw-r--r--app/javascript/mastodon/features/status/components/action_bar.js4
-rw-r--r--app/javascript/mastodon/features/status/components/detailed_status.js48
-rw-r--r--app/javascript/mastodon/features/status/index.js7
-rw-r--r--app/javascript/mastodon/features/ui/components/boost_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/column_link.js13
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js12
-rw-r--r--app/javascript/mastodon/features/ui/index.js37
-rw-r--r--app/javascript/mastodon/features/ui/util/async-components.js7
19 files changed, 214 insertions, 60 deletions
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 6eb51a5c7..7ab492225 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/account/header
+
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js
index 167a2097e..09883d7d6 100644
--- a/app/javascript/mastodon/features/account_timeline/components/header.js
+++ b/app/javascript/mastodon/features/account_timeline/components/header.js
@@ -1,7 +1,7 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import InnerHeader from '../../account/components/header';
+import InnerHeader from '../../../../glitch/components/account/header';
 import ActionBar from '../../account/components/action_bar';
 import MissingIndicator from '../../../components/missing_indicator';
 import ImmutablePureComponent from 'react-immutable-pure-component';
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index f3320a42b..000e414fe 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -11,6 +11,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 import Collapsable from '../../../components/collapsable';
 import SpoilerButtonContainer from '../containers/spoiler_button_container';
 import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
+import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container';
 import SensitiveButtonContainer from '../containers/sensitive_button_container';
 import EmojiPickerDropdown from './emoji_picker_dropdown';
 import UploadFormContainer from '../containers/upload_form_container';
@@ -37,6 +38,9 @@ export default class ComposeForm extends ImmutablePureComponent {
     suggestions: ImmutablePropTypes.list,
     spoiler: PropTypes.bool,
     privacy: PropTypes.string,
+    advanced_options: ImmutablePropTypes.contains({
+      do_not_federate: PropTypes.bool,
+    }),
     spoiler_text: PropTypes.string,
     focusDate: PropTypes.instanceOf(Date),
     preselectDate: PropTypes.instanceOf(Date),
@@ -146,7 +150,8 @@ export default class ComposeForm extends ImmutablePureComponent {
   render () {
     const { intl, onPaste, showSearch } = this.props;
     const disabled = this.props.is_submitting;
-    const text     = [this.props.spoiler_text, countableText(this.props.text)].join('');
+    const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
+    const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
 
     let publishText = '';
 
@@ -198,6 +203,7 @@ export default class ComposeForm extends ImmutablePureComponent {
           <div className='compose-form__buttons'>
             <UploadButtonContainer />
             <PrivacyDropdownContainer />
+            <ComposeAdvancedOptionsContainer />
             <SensitiveButtonContainer />
             <SpoilerButtonContainer />
           </div>
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index ae4d1e86a..cae4ca412 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -2,7 +2,7 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../../containers/account_container';
-import StatusContainer from '../../../containers/status_container';
+import StatusContainer from '../../../../glitch/components/status/container';
 import Link from 'react-router-dom/Link';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
index 12d435ded..1911edbf9 100644
--- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js
@@ -15,6 +15,7 @@ const mapStateToProps = state => ({
   text: state.getIn(['compose', 'text']),
   suggestion_token: state.getIn(['compose', 'suggestion_token']),
   suggestions: state.getIn(['compose', 'suggestions']),
+  advanced_options: state.getIn(['compose', 'advanced_options']),
   spoiler: state.getIn(['compose', 'spoiler']),
   spoiler_text: state.getIn(['compose', 'spoiler_text']),
   privacy: state.getIn(['compose', 'privacy']),
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index 0b686ddb3..f0bce1e40 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { connect } from 'react-redux';
 import { mountCompose, unmountCompose } from '../../actions/compose';
+import { openModal } from '../../actions/modal';
+import { changeLocalSetting } from '../../../glitch/actions/local_settings';
 import Link from 'react-router-dom/Link';
 import { injectIntl, defineMessages } from 'react-intl';
 import SearchContainer from './containers/search_container';
@@ -19,7 +21,7 @@ const messages = defineMessages({
   notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
   public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
   community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
-  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
   logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
 });
 
@@ -48,6 +50,16 @@ export default class Compose extends React.PureComponent {
     this.props.dispatch(unmountCompose());
   }
 
+  onLayoutClick = (e) => {
+    const layout = e.currentTarget.getAttribute('data-mastodon-layout');
+    this.props.dispatch(changeLocalSetting(['layout'], layout));
+    e.preventDefault();
+  }
+
+  openSettings = () => {
+    this.props.dispatch(openModal('SETTINGS', {}));
+  }
+
   onFocus = () => {
     this.props.dispatch(changeComposing(true));
   }
@@ -78,12 +90,14 @@ export default class Compose extends React.PureComponent {
           {!columns.some(column => column.get('id') === 'PUBLIC') && (
             <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link>
           )}
-          <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a>
+          <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a>
           <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a>
         </nav>
       );
     }
 
+
+
     return (
       <div className='drawer'>
         {header}
@@ -104,6 +118,7 @@ export default class Compose extends React.PureComponent {
             }
           </Motion>
         </div>
+
       </div>
     );
   }
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index f8ea01024..2efab66f5 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -4,6 +4,7 @@ import ColumnLink from '../ui/components/column_link';
 import ColumnSubheading from '../ui/components/column_subheading';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { connect } from 'react-redux';
+import { openModal } from '../../actions/modal';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -17,12 +18,14 @@ const messages = defineMessages({
   settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
   community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
   preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+  settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
   follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
   sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
   favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
   blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
   mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
   info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
+  show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
 });
 
 const mapStateToProps = state => ({
@@ -39,8 +42,18 @@ export default class GettingStarted extends ImmutablePureComponent {
     me: ImmutablePropTypes.map.isRequired,
     columns: ImmutablePropTypes.list,
     multiColumn: PropTypes.bool,
+    dispatch: PropTypes.func.isRequired,
   };
 
+  openSettings = () => {
+    this.props.dispatch(openModal('SETTINGS', {}));
+  }
+
+  openOnboardingModal = (e) => {
+    e.preventDefault();
+    this.props.dispatch(openModal('ONBOARDING'));
+  }
+
   render () {
     const { intl, me, columns, multiColumn } = this.props;
 
@@ -79,27 +92,42 @@ export default class GettingStarted extends ImmutablePureComponent {
 
     return (
       <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
-        <div className='getting-started__wrapper'>
-          <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
-          {navItems}
-          <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
-          <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
-          <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
-          <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
-        </div>
+        <div className='scrollable optionally-scrollable'>
+          <div className='getting-started__wrapper'>
+            <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
+            {navItems}
+            <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
+            <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
+            <ColumnLink icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} />
+            <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
+            <ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={this.openSettings} />
+            <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
+          </div>
 
-        <div className='getting-started__footer scrollable optionally-scrollable'>
-          <div className='static-content getting-started'>
-            <p>
-              <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
-            </p>
-            <p>
-              <FormattedMessage
-                id='getting_started.open_source_notice'
-                defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
-                values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }}
-              />
-            </p>
+          <div className='getting-started__footer'>
+            <div className='static-content getting-started'>
+              <p>
+                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'>
+                  <FormattedMessage id='getting_started.faq' defaultMessage='FAQ' />
+                </a>&nbsp;•&nbsp;
+                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'>
+                  <FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' />
+                </a>&nbsp;•&nbsp;
+                <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'>
+                  <FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' />
+                </a>
+              </p>
+              <p>
+                <FormattedMessage
+                  id='getting_started.open_source_notice'
+                  defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
+                  values={{
+                    github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>,
+                    Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a>,
+                  }}
+                />
+              </p>
+            </div>
           </div>
         </div>
       </Column>
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index a608a5223..b52c3c934 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/notification
+
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js
index 786222967..1f98a66d2 100644
--- a/app/javascript/mastodon/features/notifications/containers/notification_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js
@@ -1,3 +1,6 @@
+//  THIS FILE EXISTS FOR UPSTREAM COMPATIBILITY & SHOULDN'T BE USED !!
+//  SEE INSTEAD : glitch/components/notification/container
+
 import { connect } from 'react-redux';
 import { makeGetNotification } from '../../../selectors';
 import Notification from '../components/notification';
diff --git a/app/javascript/mastodon/features/notifications/index.js b/app/javascript/mastodon/features/notifications/index.js
index c23560a43..b644718e3 100644
--- a/app/javascript/mastodon/features/notifications/index.js
+++ b/app/javascript/mastodon/features/notifications/index.js
@@ -4,9 +4,14 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Column from '../../components/column';
 import ColumnHeader from '../../components/column_header';
-import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
+import {
+  enterNotificationClearingMode,
+  expandNotifications,
+  scrollTopNotifications,
+} from '../../actions/notifications';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
-import NotificationContainer from './containers/notification_container';
+import NotificationContainer from '../../../glitch/components/notification/container';
+import { ScrollContainer } from 'react-router-scroll';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import { createSelector } from 'reselect';
@@ -25,12 +30,22 @@ const getNotifications = createSelector([
 
 const mapStateToProps = state => ({
   notifications: getNotifications(state),
+  localSettings:  state.get('local_settings'),
   isLoading: state.getIn(['notifications', 'isLoading'], true),
   isUnread: state.getIn(['notifications', 'unread']) > 0,
   hasMore: !!state.getIn(['notifications', 'next']),
+  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 });
 
-@connect(mapStateToProps)
+/* glitch */
+const mapDispatchToProps = dispatch => ({
+  onEnterCleaningMode(yes) {
+    dispatch(enterNotificationClearingMode(yes));
+  },
+  dispatch,
+});
+
+@connect(mapStateToProps, mapDispatchToProps)
 @injectIntl
 export default class Notifications extends React.PureComponent {
 
@@ -44,6 +59,9 @@ export default class Notifications extends React.PureComponent {
     isUnread: PropTypes.bool,
     multiColumn: PropTypes.bool,
     hasMore: PropTypes.bool,
+    localSettings: ImmutablePropTypes.map,
+    notifCleaningActive: PropTypes.bool,
+    onEnterCleaningMode: PropTypes.func,
   };
 
   static defaultProps = {
@@ -119,7 +137,10 @@ export default class Notifications extends React.PureComponent {
     );
 
     return (
-      <Column ref={this.setColumnRef}>
+      <Column
+        ref={this.setColumnRef}
+        extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
+      >
         <ColumnHeader
           icon='bell'
           active={isUnread}
@@ -129,6 +150,10 @@ export default class Notifications extends React.PureComponent {
           onClick={this.handleHeaderClick}
           pinned={pinned}
           multiColumn={multiColumn}
+          localSettings={this.props.localSettings}
+          notifCleaning
+          notifCleaningActive={this.props.notifCleaningActive} // this is used to toggle the header text
+          onEnterCleaningMode={this.props.onEnterCleaningMode}
         >
           <ColumnSettingsContainer />
         </ColumnHeader>
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index c303caf10..4be013be7 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -107,8 +107,8 @@ export default class ActionBar extends React.PureComponent {
     );
 
     let reblogIcon = 'retweet';
-    if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
-    else if (status.get('visibility') === 'private') reblogIcon = 'lock';
+    //if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
+    // else if (status.get('visibility') === 'private') reblogIcon = 'lock';
 
     let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
 
diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js
index 940a2699b..a098322ba 100644
--- a/app/javascript/mastodon/features/status/components/detailed_status.js
+++ b/app/javascript/mastodon/features/status/components/detailed_status.js
@@ -3,14 +3,15 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import Avatar from '../../../components/avatar';
 import DisplayName from '../../../components/display_name';
-import StatusContent from '../../../components/status_content';
-import MediaGallery from '../../../components/media_gallery';
-import VideoPlayer from '../../../components/video_player';
+import StatusContent from '../../../../glitch/components/status/content';
+import StatusGallery from '../../../../glitch/components/status/gallery';
+import StatusPlayer from '../../../../glitch/components/status/player';
 import AttachmentList from '../../../components/attachment_list';
 import Link from 'react-router-dom/Link';
 import { FormattedDate, FormattedNumber } from 'react-intl';
 import CardContainer from '../containers/card_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import VisibilityIcon from '../../../../glitch/components/status/visibility_icon';
 
 export default class DetailedStatus extends ImmutablePureComponent {
 
@@ -20,6 +21,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
 
   static propTypes = {
     status: ImmutablePropTypes.map.isRequired,
+    settings: ImmutablePropTypes.map.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
     autoPlayGif: PropTypes.bool,
@@ -36,21 +38,41 @@ export default class DetailedStatus extends ImmutablePureComponent {
 
   render () {
     const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
+    const { settings } = this.props;
 
     let media           = '';
+    let mediaIcon       = null;
     let applicationLink = '';
 
     if (status.get('media_attachments').size > 0) {
       if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
         media = <AttachmentList media={status.get('media_attachments')} />;
       } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
-        media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
+        media = (
+          <StatusPlayer
+            sensitive={status.get('sensitive')}
+            media={status.getIn(['media_attachments', 0])}
+            letterbox={settings.getIn(['media', 'letterbox'])}
+            height={250}
+            onOpenVideo={this.props.onOpenVideo}
+            autoplay
+          />
+        );
+        mediaIcon = 'video-camera';
       } else {
-        media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
+        media = (
+          <StatusGallery
+            sensitive={status.get('sensitive')}
+            media={status.get('media_attachments')}
+            letterbox={settings.getIn(['media', 'letterbox'])}
+            height={250}
+            onOpenMedia={this.props.onOpenMedia}
+            autoPlayGif={this.props.autoPlayGif}
+          />
+        );
+        mediaIcon = 'picture-o';
       }
-    } else if (status.get('spoiler_text').length === 0) {
-      media = <CardContainer statusId={status.get('id')} />;
-    }
+    } else media = <CardContainer statusId={status.get('id')} />;
 
     if (status.get('application')) {
       applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
@@ -63,9 +85,11 @@ export default class DetailedStatus extends ImmutablePureComponent {
           <DisplayName account={status.get('account')} />
         </a>
 
-        <StatusContent status={status} />
-
-        {media}
+        <StatusContent
+          status={status}
+          media={media}
+          mediaIcon={mediaIcon}
+        />
 
         <div className='detailed-status__meta'>
           <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
@@ -80,7 +104,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             <span className='detailed-status__favorites'>
               <FormattedNumber value={status.get('favourites_count')} />
             </span>
-          </Link>
+          </Link> · <VisibilityIcon visibility={status.get('visibility')} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index c614f6acb..03010cf0a 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -24,7 +24,7 @@ import { initReport } from '../../actions/reports';
 import { makeGetStatus } from '../../selectors';
 import { ScrollContainer } from 'react-router-scroll';
 import ColumnBackButton from '../../components/column_back_button';
-import StatusContainer from '../../containers/status_container';
+import StatusContainer from '../../../glitch/components/status/container';
 import { openModal } from '../../actions/modal';
 import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -39,6 +39,7 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     status: getStatus(state, Number(props.params.statusId)),
+    settings: state.get('local_settings'),
     ancestorsIds: state.getIn(['contexts', 'ancestors', Number(props.params.statusId)]),
     descendantsIds: state.getIn(['contexts', 'descendants', Number(props.params.statusId)]),
     me: state.getIn(['meta', 'me']),
@@ -62,6 +63,7 @@ export default class Status extends ImmutablePureComponent {
     params: PropTypes.object.isRequired,
     dispatch: PropTypes.func.isRequired,
     status: ImmutablePropTypes.map,
+    settings: ImmutablePropTypes.map.isRequired,
     ancestorsIds: ImmutablePropTypes.list,
     descendantsIds: ImmutablePropTypes.list,
     me: PropTypes.number,
@@ -157,7 +159,7 @@ export default class Status extends ImmutablePureComponent {
 
   render () {
     let ancestors, descendants;
-    const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
+    const { status, settings, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
 
     if (status === null) {
       return (
@@ -186,6 +188,7 @@ export default class Status extends ImmutablePureComponent {
 
             <DetailedStatus
               status={status}
+              settings={settings}
               autoPlayGif={autoPlayGif}
               me={me}
               onOpenVideo={this.handleOpenVideo}
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index 0e9592c97..dfd1284e9 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.js
@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Button from '../../../components/button';
-import StatusContent from '../../../components/status_content';
+import StatusContent from '../../../../glitch/components/status/content';
 import Avatar from '../../../components/avatar';
 import RelativeTimestamp from '../../../components/relative_timestamp';
 import DisplayName from '../../../components/display_name';
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
index ad7ec9318..62aab9a23 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.js
+++ b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -2,7 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import Link from 'react-router-dom/Link';
 
-const ColumnLink = ({ icon, text, to, href, method }) => {
+const ColumnLink = ({ icon, text, to, onClick, href, method }) => {
   if (href) {
     return (
       <a href={href} className='column-link' data-method={method}>
@@ -10,13 +10,20 @@ const ColumnLink = ({ icon, text, to, href, method }) => {
         {text}
       </a>
     );
-  } else {
+  } else if (to) {
     return (
       <Link to={to} className='column-link'>
         <i className={`fa fa-fw fa-${icon} column-link__icon`} />
         {text}
       </Link>
     );
+  } else {
+    return (
+      <a onClick={onClick} className='column-link' role='button' tabIndex='0' data-method={method}>
+        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
+        {text}
+      </a>
+    );
   }
 };
 
@@ -24,9 +31,9 @@ ColumnLink.propTypes = {
   icon: PropTypes.string.isRequired,
   text: PropTypes.string.isRequired,
   to: PropTypes.string,
+  onClick: PropTypes.func,
   href: PropTypes.string,
   method: PropTypes.string,
-  hideOnMobile: PropTypes.bool,
 };
 
 export default ColumnLink;
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 5420ba2bd..cd605d7b2 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -13,6 +13,7 @@ import {
   BoostModal,
   ConfirmationModal,
   ReportModal,
+  SettingsModal,
   EmbedModal,
 } from '../../../features/ui/util/async-components';
 
@@ -23,6 +24,7 @@ const MODAL_COMPONENTS = {
   'BOOST': BoostModal,
   'CONFIRM': ConfirmationModal,
   'REPORT': ReportModal,
+  'SETTINGS': SettingsModal,
   'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
   'EMBED': EmbedModal,
 };
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index 7905bca2e..2facf9c44 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -28,8 +28,8 @@ const PageOne = ({ acct, domain }) => (
     </div>
 
     <div>
-      <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
-      <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
+      <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to {domain}!' values={{ domain }} /></h1>
+      <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='{domain} is an "instance" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' values={{ domain }} /></p>
       <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>@{acct}@{domain}</strong> }} /></p>
     </div>
   </div>
@@ -44,7 +44,7 @@ const PageTwo = ({ me }) => (
   <div className='onboarding-modal__page onboarding-modal__page-two'>
     <div className='figure non-interactive'>
       <div className='pseudo-drawer'>
-        <NavigationBar account={me} />
+        <NavigationBar onClose={noop} account={me} />
       </div>
       <ComposeForm
         text='Awoo! #introductions'
@@ -83,7 +83,7 @@ const PageThree = ({ me }) => (
       />
 
       <div className='pseudo-drawer'>
-        <NavigationBar account={me} />
+        <NavigationBar onClose={noop} account={me} />
       </div>
     </div>
 
@@ -148,8 +148,8 @@ const PageSix = ({ admin, domain }) => {
     <div className='onboarding-modal__page onboarding-modal__page-six'>
       <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
       {adminSection}
-      <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
-      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
+      <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ domain, fork: <a href='https://en.wikipedia.org/wiki/Fork_(software_development)' target='_blank' rel='noopener'>fork</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>Mastodon</a>, github: <a href='https://github.com/glitch-soc/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
+      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ domain, apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
       <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
     </div>
   );
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 8f971ae67..883466602 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -39,9 +39,13 @@ import {
 
 // Dummy import, to make sure that <Status /> ends up in the application bundle.
 // Without this it ends up in ~8 very commonly used bundles.
-import '../../components/status';
+import '../../../glitch/components/status';
 
 const mapStateToProps = state => ({
+  systemFontUi: state.getIn(['meta', 'system_font_ui']),
+  layout: state.getIn(['local_settings', 'layout']),
+  isWide: state.getIn(['local_settings', 'stretch']),
+  navbarUnder: state.getIn(['local_settings', 'navbar_under']),
   isComposing: state.getIn(['compose', 'is_composing']),
 });
 
@@ -56,6 +60,10 @@ export default class UI extends React.PureComponent {
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
     children: PropTypes.node,
+    layout: PropTypes.string,
+    isWide: PropTypes.bool,
+    systemFontUi: PropTypes.bool,
+    navbarUnder: PropTypes.bool,
     isComposing: PropTypes.bool,
     location: PropTypes.object,
   };
@@ -158,6 +166,7 @@ export default class UI extends React.PureComponent {
     if (nextProps.isComposing !== this.props.isComposing) {
       // Avoid expensive update just to toggle a class
       this.node.classList.toggle('is-composing', nextProps.isComposing);
+      this.node.classList.toggle('navbar-under', nextProps.navbarUnder);
 
       return false;
     }
@@ -192,12 +201,29 @@ export default class UI extends React.PureComponent {
 
   render () {
     const { width, draggingOver } = this.state;
-    const { children } = this.props;
+    const { children, layout, isWide, navbarUnder } = this.props;
+
+    const columnsClass = layout => {
+      switch (layout) {
+      case 'single':
+        return 'single-column';
+      case 'multiple':
+        return 'multi-columns';
+      default:
+        return 'auto-columns';
+      }
+    };
+
+    const className = classNames('ui', columnsClass(layout), {
+      'wide': isWide,
+      'system-font': this.props.systemFontUi,
+      'navbar-under': navbarUnder,
+    });
 
     return (
-      <div className='ui' ref={this.setRef}>
-        <TabsBar />
-        <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}>
+      <div className={className} ref={this.setRef}>
+        {navbarUnder ? null : (<TabsBar />)}
+        <ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width, layout)}>
           <WrappedSwitch>
             <Redirect from='/' to='/getting-started' exact />
             <WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
@@ -227,6 +253,7 @@ export default class UI extends React.PureComponent {
           </WrappedSwitch>
         </ColumnsAreaContainer>
         <NotificationsContainer />
+        {navbarUnder ? (<TabsBar />) : null}
         <LoadingBarContainer className='loading-bar' />
         <ModalContainer />
         <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js
index 0882beb7f..108ffc142 100644
--- a/app/javascript/mastodon/features/ui/util/async-components.js
+++ b/app/javascript/mastodon/features/ui/util/async-components.js
@@ -102,6 +102,13 @@ export function ReportModal () {
   return import(/* webpackChunkName: "modals/report_modal" */'../components/report_modal');
 }
 
+export function SettingsModal () {
+  return import(/* webpackChunkName: "modals/settings_modal" */'glitch/components/local_settings/container');
+}
+
+//  THESE AREN'T USED BY US; SEE `glitch/components/status` AND `mastodon/features/status`.  //
+//  IF MASTODON EVER CHANGES DETAILED STATUSES TO REQUIRE THEM, WE'LL NEED TO UPDATE THE URLS OR SOMETHING LOL.  //
+
 export function MediaGallery () {
   return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery');
 }