about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch
diff options
context:
space:
mode:
authorpluralcafe-docker <git@plural.cafe>2019-01-12 04:24:14 +0000
committerpluralcafe-docker <git@plural.cafe>2019-01-12 04:24:14 +0000
commitae24ef877b82466cab026a76ea6ed8598ddaae18 (patch)
tree584c25ada377f2e6da49a9b45b3da947501b1831 /app/javascript/flavours/glitch
parentfa1ab7fbe4166f3d8b481891dcc443df5aa212bb (diff)
parent2cfa55185a5fc7d93a160a4e9a4730aae6725b0f (diff)
Merge branch 'glitch'
Diffstat (limited to 'app/javascript/flavours/glitch')
-rw-r--r--app/javascript/flavours/glitch/components/account.js4
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js4
-rw-r--r--app/javascript/flavours/glitch/components/scrollable_list.js68
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js70
-rw-r--r--app/javascript/flavours/glitch/features/list_editor/index.js7
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/status/components/card.js6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/columns_area.js5
-rw-r--r--app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js1
-rw-r--r--app/javascript/flavours/glitch/packs/public.js48
-rw-r--r--app/javascript/flavours/glitch/reducers/list_editor.js11
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/styles/admin.scss19
-rw-r--r--app/javascript/flavours/glitch/styles/components/columns.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss9
-rw-r--r--app/javascript/flavours/glitch/styles/dashboard.scss1
16 files changed, 234 insertions, 30 deletions
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js
index 80f20b8ad..072c601e0 100644
--- a/app/javascript/flavours/glitch/components/account.js
+++ b/app/javascript/flavours/glitch/components/account.js
@@ -67,10 +67,10 @@ export default class Account extends ImmutablePureComponent {
 
     if (hidden) {
       return (
-        <div>
+        <Fragment>
           {account.get('display_name')}
           {account.get('username')}
-        </div>
+        </Fragment>
       );
     }
 
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 10afeb2eb..d0226bbbb 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -71,6 +71,10 @@ class Item extends React.PureComponent {
     const { index, onClick } = this.props;
 
     if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+      if (this.hoverToPlay()) {
+        e.target.pause();
+        e.target.currentTime = 0;
+      }
       e.preventDefault();
       onClick(index);
     }
diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js
index 21d717b81..7cd0774a9 100644
--- a/app/javascript/flavours/glitch/components/scrollable_list.js
+++ b/app/javascript/flavours/glitch/components/scrollable_list.js
@@ -10,6 +10,8 @@ import classNames from 'classnames';
 import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
 import LoadingIndicator from './loading_indicator';
 
+const MOUSE_IDLE_DELAY = 300;
+
 export default class ScrollableList extends PureComponent {
 
   static contextTypes = {
@@ -56,11 +58,66 @@ export default class ScrollableList extends PureComponent {
       } else if (this.props.onScroll) {
         this.props.onScroll();
       }
+
+      if (!this.lastScrollWasSynthetic) {
+        // If the last scroll wasn't caused by setScrollTop(), assume it was
+        // intentional and cancel any pending scroll reset on mouse idle
+        this.scrollToTopOnMouseIdle = false;
+      }
+      this.lastScrollWasSynthetic = false;
     }
   }, 150, {
     trailing: true,
   });
 
+  mouseIdleTimer = null;
+  mouseMovedRecently = false;
+  lastScrollWasSynthetic = false;
+  scrollToTopOnMouseIdle = false;
+
+  setScrollTop = newScrollTop => {
+    if (this.node.scrollTop !== newScrollTop) {
+      this.lastScrollWasSynthetic = true;
+      this.node.scrollTop = newScrollTop;
+    }
+  };
+
+  clearMouseIdleTimer = () => {
+    if (this.mouseIdleTimer === null) {
+      return;
+    }
+    clearTimeout(this.mouseIdleTimer);
+    this.mouseIdleTimer = null;
+  };
+
+  handleMouseMove = throttle(() => {
+    // As long as the mouse keeps moving, clear and restart the idle timer.
+    this.clearMouseIdleTimer();
+    this.mouseIdleTimer =
+      setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
+
+    if (!this.mouseMovedRecently && this.node.scrollTop === 0) {
+      // Only set if we just started moving and are scrolled to the top.
+      this.scrollToTopOnMouseIdle = true;
+    }
+    // Save setting this flag for last, so we can do the comparison above.
+    this.mouseMovedRecently = true;
+  }, MOUSE_IDLE_DELAY / 2);
+
+  handleWheel = throttle(() => {
+    this.scrollToTopOnMouseIdle = false;
+  }, 150, {
+    trailing: true,
+  });
+
+  handleMouseIdle = () => {
+    if (this.scrollToTopOnMouseIdle) {
+      this.setScrollTop(0);
+    }
+    this.mouseMovedRecently = false;
+    this.scrollToTopOnMouseIdle = false;
+  }
+
   componentDidMount () {
     this.attachScrollListener();
     this.attachIntersectionObserver();
@@ -81,16 +138,14 @@ export default class ScrollableList extends PureComponent {
   updateScrollBottom = (snapshot) => {
     const newScrollTop = this.node.scrollHeight - snapshot;
 
-    if (this.node.scrollTop !== newScrollTop) {
-      this.node.scrollTop = newScrollTop;
-    }
+    this.setScrollTop(newScrollTop);
   }
 
   getSnapshotBeforeUpdate (prevProps, prevState) {
     const someItemInserted = React.Children.count(prevProps.children) > 0 &&
       React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
       this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
-    if (someItemInserted && this.node.scrollTop > 0) {
+    if (someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
       return this.node.scrollHeight - this.node.scrollTop;
     } else {
       return null;
@@ -104,6 +159,7 @@ export default class ScrollableList extends PureComponent {
   }
 
   componentWillUnmount () {
+    this.clearMouseIdleTimer();
     this.detachScrollListener();
     this.detachIntersectionObserver();
     detachFullscreenListener(this.onFullScreenChange);
@@ -126,10 +182,12 @@ export default class ScrollableList extends PureComponent {
 
   attachScrollListener () {
     this.node.addEventListener('scroll', this.handleScroll);
+    this.node.addEventListener('wheel', this.handleWheel);
   }
 
   detachScrollListener () {
     this.node.removeEventListener('scroll', this.handleScroll);
+    this.node.removeEventListener('wheel', this.handleWheel);
   }
 
   getFirstChildKey (props) {
@@ -181,7 +239,7 @@ export default class ScrollableList extends PureComponent {
       );
     } else if (isLoading || childrenCount > 0 || hasMore || !emptyMessage) {
       scrollableArea = (
-        <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
+        <div className={classNames('scrollable', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
           <div role='feed' className='item-list'>
             {prepend}
 
diff --git a/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js
new file mode 100644
index 000000000..24aaf82ac
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/list_editor/components/edit_list_form.js
@@ -0,0 +1,70 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import PropTypes from 'prop-types';
+import { changeListEditorTitle, submitListEditor } from 'flavours/glitch/actions/lists';
+import IconButton from 'flavours/glitch/components/icon_button';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
+});
+
+const mapStateToProps = state => ({
+  value: state.getIn(['listEditor', 'title']),
+  disabled: !state.getIn(['listEditor', 'isChanged']),
+});
+
+const mapDispatchToProps = dispatch => ({
+  onChange: value => dispatch(changeListEditorTitle(value)),
+  onSubmit: () => dispatch(submitListEditor(false)),
+});
+
+@connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
+export default class ListForm extends React.PureComponent {
+
+  static propTypes = {
+    value: PropTypes.string.isRequired,
+    disabled: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChange: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+  };
+
+  handleChange = e => {
+    this.props.onChange(e.target.value);
+  }
+
+  handleSubmit = e => {
+    e.preventDefault();
+    this.props.onSubmit();
+  }
+
+  handleClick = () => {
+    this.props.onSubmit();
+  }
+
+  render () {
+    const { value, disabled, intl } = this.props;
+
+    const title = intl.formatMessage(messages.title);
+
+    return (
+      <form className='column-inline-form' onSubmit={this.handleSubmit}>
+        <input
+          className='setting-text'
+          value={value}
+          onChange={this.handleChange}
+        />
+
+        <IconButton
+          disabled={disabled}
+          icon='check'
+          title={title}
+          onClick={this.handleClick}
+        />
+      </form>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/list_editor/index.js b/app/javascript/flavours/glitch/features/list_editor/index.js
index b3be3070a..5f552b113 100644
--- a/app/javascript/flavours/glitch/features/list_editor/index.js
+++ b/app/javascript/flavours/glitch/features/list_editor/index.js
@@ -7,11 +7,11 @@ import { injectIntl } from 'react-intl';
 import { setupListEditor, clearListSuggestions, resetListEditor } from 'flavours/glitch/actions/lists';
 import AccountContainer from './containers/account_container';
 import SearchContainer from './containers/search_container';
+import EditListForm from './components/edit_list_form';
 import Motion from 'flavours/glitch/util/optional_motion';
 import spring from 'react-motion/lib/spring';
 
 const mapStateToProps = state => ({
-  title: state.getIn(['listEditor', 'title']),
   accountIds: state.getIn(['listEditor', 'accounts', 'items']),
   searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
 });
@@ -33,7 +33,6 @@ export default class ListEditor extends ImmutablePureComponent {
     onInitialize: PropTypes.func.isRequired,
     onClear: PropTypes.func.isRequired,
     onReset: PropTypes.func.isRequired,
-    title: PropTypes.string.isRequired,
     accountIds: ImmutablePropTypes.list.isRequired,
     searchAccountIds: ImmutablePropTypes.list.isRequired,
   };
@@ -49,12 +48,12 @@ export default class ListEditor extends ImmutablePureComponent {
   }
 
   render () {
-    const { title, accountIds, searchAccountIds, onClear } = this.props;
+    const { accountIds, searchAccountIds, onClear } = this.props;
     const showSearch = searchAccountIds.size > 0;
 
     return (
       <div className='modal-root__modal list-editor'>
-        <h4>{title}</h4>
+        <EditListForm />
 
         <SearchContainer />
 
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index 4477a4e80..16c64ced6 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -95,6 +95,14 @@ export default class LocalSettingsPage extends React.PureComponent {
           >
             <FormattedMessage id='settings.navbar_under' defaultMessage='Navbar at the bottom (Mobile only)' />
           </LocalSettingsPageItem>
+          <LocalSettingsPageItem
+            settings={settings}
+            item={['swipe_to_change_columns']}
+            id='mastodon-settings--swipe_to_change_columns'
+            onChange={onChange}
+          >
+            <FormattedMessage id='settings.swipe_to_change_columns' defaultMessage='Allow swiping to change columns (Mobile only)' />
+          </LocalSettingsPageItem>
         </section>
       </div>
     ),
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js
index 743fe779a..1e1604d5c 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.js
@@ -195,6 +195,12 @@ export default class Card extends React.PureComponent {
           {thumbnail}
         </div>
       );
+    } else {
+      embed = (
+        <div className='status-card__image'>
+          <i className='fa fa-file-text' />
+        </div>
+      );
     }
 
     return (
diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.js b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
index 65a63294b..61f6c0fed 100644
--- a/app/javascript/flavours/glitch/features/ui/components/columns_area.js
+++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.js
@@ -46,6 +46,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
   static propTypes = {
     intl: PropTypes.object.isRequired,
     columns: ImmutablePropTypes.list.isRequired,
+    swipeToChangeColumns: PropTypes.bool,
     singleColumn: PropTypes.bool,
     children: PropTypes.node,
   };
@@ -153,7 +154,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
   }
 
   render () {
-    const { columns, children, singleColumn, intl } = this.props;
+    const { columns, children, singleColumn, swipeToChangeColumns, intl } = this.props;
     const { shouldAnimate } = this.state;
 
     const columnIndex = getIndex(this.context.router.history.location.pathname);
@@ -163,7 +164,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
       const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><i className='fa fa-pencil' /></Link>;
 
       return columnIndex !== -1 ? [
-        <ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
+        <ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={!swipeToChangeColumns}>
           {links.map(this.renderView)}
         </ReactSwipeableViews>,
 
diff --git a/app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js b/app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js
index 4e26f3017..ba194a002 100644
--- a/app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js
+++ b/app/javascript/flavours/glitch/features/ui/containers/columns_area_container.js
@@ -3,6 +3,7 @@ import ColumnsArea from '../components/columns_area';
 
 const mapStateToProps = state => ({
   columns: state.getIn(['settings', 'columns']),
+  swipeToChangeColumns: state.getIn(['local_settings', 'swipe_to_change_columns']),
 });
 
 export default connect(mapStateToProps, null, null, { forwardRef: true })(ColumnsArea);
diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js
index 78e8f1053..342c5265e 100644
--- a/app/javascript/flavours/glitch/packs/public.js
+++ b/app/javascript/flavours/glitch/packs/public.js
@@ -2,14 +2,26 @@ import loadPolyfills from 'flavours/glitch/util/load_polyfills';
 import ready from 'flavours/glitch/util/ready';
 
 function main() {
-  const IntlRelativeFormat = require('intl-relativeformat').default;
+  const IntlMessageFormat = require('intl-messageformat').default;
+  const { timeAgoString } = require('flavours/glitch/components/relative_timestamp');
   const emojify = require('flavours/glitch/util/emoji').default;
   const { getLocale } = require('locales');
-  const { localeData } = getLocale();
+  const { messages } = getLocale();
   const React = require('react');
   const ReactDOM = require('react-dom');
+  const Rellax = require('rellax');
+  const createHistory = require('history').createBrowserHistory;
 
-  localeData.forEach(IntlRelativeFormat.__addLocaleData);
+  const scrollToDetailedStatus = () => {
+    const history = createHistory();
+    const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status');
+    const location = history.location;
+
+    if (detailedStatuses.length === 1 && (!location.state || !location.state.scrolledToDetailedStatus)) {
+      detailedStatuses[0].scrollIntoView();
+      history.replace(location.pathname, { ...location.state, scrolledToDetailedStatus: true });
+    }
+  };
 
   ready(() => {
     const locale = document.documentElement.lang;
@@ -22,8 +34,6 @@ function main() {
       minute: 'numeric',
     });
 
-    const relativeFormat = new IntlRelativeFormat(locale);
-
     [].forEach.call(document.querySelectorAll('.emojify'), (content) => {
       content.innerHTML = emojify(content.innerHTML);
     });
@@ -38,16 +48,13 @@ function main() {
 
     [].forEach.call(document.querySelectorAll('time.time-ago'), (content) => {
       const datetime = new Date(content.getAttribute('datetime'));
+      const now      = new Date();
 
       content.title = dateTimeFormat.format(datetime);
-      content.textContent = relativeFormat.format(datetime);
-    });
-
-    [].forEach.call(document.querySelectorAll('.logo-button'), (content) => {
-      content.addEventListener('click', (e) => {
-        e.preventDefault();
-        window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes');
-      });
+      content.textContent = timeAgoString({
+        formatMessage: ({ id, defaultMessage }, values) => (new IntlMessageFormat(messages[id] || defaultMessage, locale)).format(values),
+        formatDate: (date, options) => (new Intl.DateTimeFormat(locale, options)).format(date),
+      }, datetime, now, now.getFullYear());
     });
 
     const reactComponents = document.querySelectorAll('[data-component]');
@@ -55,10 +62,23 @@ function main() {
       import(/* webpackChunkName: "containers/media_container" */ 'flavours/glitch/containers/media_container')
         .then(({ default: MediaContainer }) => {
           const content = document.createElement('div');
+
           ReactDOM.render(<MediaContainer locale={locale} components={reactComponents} />, content);
           document.body.appendChild(content);
+          scrollToDetailedStatus();
         })
-        .catch(error => console.error(error));
+        .catch(error => {
+          console.error(error);
+          scrollToDetailedStatus();
+        });
+    } else {
+      scrollToDetailedStatus();
+    }
+
+    const parallaxComponents = document.querySelectorAll('.parallax');
+
+    if (parallaxComponents.length > 0 ) {
+      new Rellax('.parallax', { speed: -1 });
     }
   });
 }
diff --git a/app/javascript/flavours/glitch/reducers/list_editor.js b/app/javascript/flavours/glitch/reducers/list_editor.js
index 02a0dabb1..5427ac098 100644
--- a/app/javascript/flavours/glitch/reducers/list_editor.js
+++ b/app/javascript/flavours/glitch/reducers/list_editor.js
@@ -22,6 +22,7 @@ import {
 const initialState = ImmutableMap({
   listId: null,
   isSubmitting: false,
+  isChanged: false,
   title: '',
 
   accounts: ImmutableMap({
@@ -47,10 +48,16 @@ export default function listEditorReducer(state = initialState, action) {
       map.set('isSubmitting', false);
     });
   case LIST_EDITOR_TITLE_CHANGE:
-    return state.set('title', action.value);
+    return state.withMutations(map => {
+      map.set('title', action.value);
+      map.set('isChanged', true);
+    });
   case LIST_CREATE_REQUEST:
   case LIST_UPDATE_REQUEST:
-    return state.set('isSubmitting', true);
+      return state.withMutations(map => {
+        map.set('isSubmitting', true);
+        map.set('isChanged', false);
+      });
   case LIST_CREATE_FAIL:
   case LIST_UPDATE_FAIL:
     return state.set('isSubmitting', false);
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 15239f28e..93a404328 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -9,6 +9,7 @@ const initialState = ImmutableMap({
   layout    : 'auto',
   stretch   : true,
   navbar_under : false,
+  swipe_to_change_columns: true,
   side_arm  : 'none',
   side_arm_reply_mode : 'keep',
   show_reply_count : false,
diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss
index 635888a2c..e7124a2c0 100644
--- a/app/javascript/flavours/glitch/styles/admin.scss
+++ b/app/javascript/flavours/glitch/styles/admin.scss
@@ -151,6 +151,20 @@ $no-columns-breakpoint: 600px;
       font-weight: 500;
     }
 
+    .directory__tag a {
+      box-shadow: none;
+    }
+
+    .directory__tag h4 {
+      font-size: 18px;
+      font-weight: 700;
+      color: $primary-text-color;
+      text-transform: none;
+      padding-bottom: 0;
+      margin-bottom: 0;
+      border-bottom: none;
+    }
+
     & > p {
       font-size: 14px;
       line-height: 18px;
@@ -194,6 +208,11 @@ $no-columns-breakpoint: 600px;
       color: $valid-value-color;
       font-weight: 500;
     }
+
+    .negative-hint {
+      color: $error-value-color;
+      font-weight: 500;
+    }
   }
 
   @media screen and (max-width: $no-columns-breakpoint) {
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 86c77f980..7a8accc27 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -500,7 +500,7 @@
 
   .icon-button {
     flex: 0 0 auto;
-    margin-left: 5px;
+    margin: 0 5px;
   }
 }
 
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 38ead06cf..9d2757065 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -740,6 +740,15 @@ a.status-card {
   flex: 0 0 100px;
   background: lighten($ui-base-color, 8%);
   position: relative;
+
+  & > .fa {
+    font-size: 21px;
+    position: absolute;
+    transform-origin: 50% 50%;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+  }
 }
 
 .status-card.horizontal {
diff --git a/app/javascript/flavours/glitch/styles/dashboard.scss b/app/javascript/flavours/glitch/styles/dashboard.scss
index 1f96e7368..e4564f062 100644
--- a/app/javascript/flavours/glitch/styles/dashboard.scss
+++ b/app/javascript/flavours/glitch/styles/dashboard.scss
@@ -39,6 +39,7 @@
     color: $primary-text-color;
     font-family: $font-display, sans-serif;
     margin-bottom: 20px;
+    line-height: 30px;
   }
 
   &__text {