about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js2
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/composer/publisher/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/composer/spoiler/index.js1
-rw-r--r--app/javascript/flavours/glitch/features/composer/upload_form/item/index.js3
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js55
-rw-r--r--app/javascript/flavours/glitch/features/hashtag_timeline/index.js17
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notification.js12
-rw-r--r--app/javascript/flavours/glitch/features/public_timeline/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js21
-rw-r--r--app/javascript/flavours/glitch/features/status/components/card.js7
-rw-r--r--app/javascript/flavours/glitch/features/status/components/detailed_status.js9
-rw-r--r--app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js8
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/doodle_modal.js6
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/embed_modal.js3
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/report_modal.js2
-rw-r--r--app/javascript/flavours/glitch/features/ui/containers/notifications_container.js19
-rw-r--r--app/javascript/flavours/glitch/features/ui/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/video/index.js6
22 files changed, 128 insertions, 60 deletions
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index dc5b1447b..96696c2a5 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -88,7 +88,7 @@ export default class Header extends ImmutablePureComponent {
       } else if (account.getIn(['relationship', 'blocking'])) {
         actionBtn = (
           <div className='account--action-button'>
-            <IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
+            <IconButton size={26} icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
           </div>
         );
       }
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/composer/options/index.js
index 05cbe24c9..5b4a7444c 100644
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ b/app/javascript/flavours/glitch/features/composer/options/index.js
@@ -197,7 +197,7 @@ export default class ComposerOptions extends React.PureComponent {
         text: <FormattedMessage {...messages.public_short} />,
       },
       unlisted: {
-        icon: 'unlock-alt',
+        icon: 'unlock',
         meta: <FormattedMessage {...messages.unlisted_long} />,
         name: 'unlisted',
         text: <FormattedMessage {...messages.unlisted_short} />,
@@ -214,6 +214,7 @@ export default class ComposerOptions extends React.PureComponent {
           onChange={handleChangeFiles}
           ref={handleRefFileElement}
           type='file'
+          multiple
           {...hiddenComponent}
         />
         <Dropdown
diff --git a/app/javascript/flavours/glitch/features/composer/publisher/index.js b/app/javascript/flavours/glitch/features/composer/publisher/index.js
index 5ded26f80..dc9c8f8eb 100644
--- a/app/javascript/flavours/glitch/features/composer/publisher/index.js
+++ b/app/javascript/flavours/glitch/features/composer/publisher/index.js
@@ -58,7 +58,7 @@ export default function ComposerPublisher ({
               <Icon
                 icon={{
                   public: 'globe',
-                  unlisted: 'unlock-alt',
+                  unlisted: 'unlock',
                   private: 'lock',
                   direct: 'envelope',
                 }[sideArm]}
@@ -82,7 +82,7 @@ export default function ComposerPublisher ({
                     direct: 'envelope',
                     private: 'lock',
                     public: 'globe',
-                    unlisted: 'unlock-alt',
+                    unlisted: 'unlock',
                   }[privacy]}
                 />
                 {' '}
diff --git a/app/javascript/flavours/glitch/features/composer/spoiler/index.js b/app/javascript/flavours/glitch/features/composer/spoiler/index.js
index 1c3c962f0..e2f9c7021 100644
--- a/app/javascript/flavours/glitch/features/composer/spoiler/index.js
+++ b/app/javascript/flavours/glitch/features/composer/spoiler/index.js
@@ -87,6 +87,7 @@ export default class ComposerSpoiler extends React.PureComponent {
             type='text'
             value={text}
             ref={handleRefSpoilerText}
+            disabled={hidden}
           />
         </label>
       </div>
diff --git a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
index 93fa4e39e..4f5f66f04 100644
--- a/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
+++ b/app/javascript/flavours/glitch/features/composer/upload_form/item/index.js
@@ -169,13 +169,12 @@ export default class ComposerUploadFormItem extends React.PureComponent {
               </div>
               <label>
                 <span style={{ display: 'none' }}><FormattedMessage {...messages.description} /></span>
-                <input
+                <textarea
                   maxLength={420}
                   onBlur={handleBlur}
                   onChange={handleChange}
                   onFocus={handleFocus}
                   placeholder={intl.formatMessage(messages.description)}
-                  type='text'
                   value={description}
                 />
               </label>
diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js
index 126350813..d0c72c087 100644
--- a/app/javascript/flavours/glitch/features/getting_started/index.js
+++ b/app/javascript/flavours/glitch/features/getting_started/index.js
@@ -166,7 +166,7 @@ export default class GettingStarted extends ImmutablePureComponent {
           <div className='getting-started__footer'>
             <ul>
               {invitesEnabled && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>}
-              <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this instance' /></a> · </li>
+              <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
               <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
               <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
               <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a></li>
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
index 82936c838..dc0ffee85 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.js
@@ -1,10 +1,15 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import Toggle from 'react-toggle';
 import AsyncSelect from 'react-select/lib/Async';
 
+const messages = defineMessages({
+  placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
+  noOptions: { id: 'hashtag.column_settings.select.no_options_message', defaultMessage: 'No suggestions found' },
+});
+
 @injectIntl
 export default class ColumnSettings extends React.PureComponent {
 
@@ -25,6 +30,7 @@ export default class ColumnSettings extends React.PureComponent {
 
   tags (mode) {
     let tags = this.props.settings.getIn(['tags', mode]) || [];
+
     if (tags.toJSON) {
       return tags.toJSON();
     } else {
@@ -32,33 +38,36 @@ export default class ColumnSettings extends React.PureComponent {
     }
   };
 
-  onSelect = (mode) => {
-    return (value) => {
-      this.props.onChange(['tags', mode], value);
-    };
-  };
+  onSelect = mode => value => this.props.onChange(['tags', mode], value);
 
   onToggle = () => {
     if (this.state.open && this.hasTags()) {
       this.props.onChange('tags', {});
     }
+
     this.setState({ open: !this.state.open });
   };
 
+  noOptionsMessage = () => this.props.intl.formatMessage(messages.noOptions);
+
   modeSelect (mode) {
     return (
-      <div className='column-settings__section'>
-        {this.modeLabel(mode)}
+      <div className='column-settings__row'>
+        <span className='column-settings__section'>
+          {this.modeLabel(mode)}
+        </span>
+
         <AsyncSelect
           isMulti
           autoFocus
           value={this.tags(mode)}
-          settings={this.props.settings}
-          settingPath={['tags', mode]}
           onChange={this.onSelect(mode)}
           loadOptions={this.props.onLoad}
-          classNamePrefix='column-settings__hashtag-select'
+          className='column-select__container'
+          classNamePrefix='column-select'
           name='tags'
+          placeholder={this.props.intl.formatMessage(messages.placeholder)}
+          noOptionsMessage={this.noOptionsMessage}
         />
       </div>
     );
@@ -66,11 +75,15 @@ export default class ColumnSettings extends React.PureComponent {
 
   modeLabel (mode) {
     switch(mode) {
-    case 'any':  return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
-    case 'all':  return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
-    case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+    case 'any':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
+    case 'all':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
+    case 'none':
+      return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+    default:
+      return '';
     }
-    return '';
   };
 
   render () {
@@ -78,23 +91,21 @@ export default class ColumnSettings extends React.PureComponent {
       <div>
         <div className='column-settings__row'>
           <div className='setting-toggle'>
-            <Toggle
-              id='hashtag.column_settings.tag_toggle'
-              onChange={this.onToggle}
-              checked={this.state.open}
-            />
+            <Toggle id='hashtag.column_settings.tag_toggle' onChange={this.onToggle} checked={this.state.open} />
+
             <span className='setting-toggle__label'>
               <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
             </span>
           </div>
         </div>
-        {this.state.open &&
+
+        {this.state.open && (
           <div className='column-settings__hashtags'>
             {this.modeSelect('any')}
             {this.modeSelect('all')}
             {this.modeSelect('none')}
           </div>
-        }
+        )}
       </div>
     );
   }
diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
index d04e9cafa..21efaceea 100644
--- a/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/hashtag_timeline/index.js
@@ -40,15 +40,19 @@ export default class HashtagTimeline extends React.PureComponent {
 
   title = () => {
     let title = [this.props.params.id];
+
     if (this.additionalFor('any')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.any'  values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
+      title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any'  values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
     }
+
     if (this.additionalFor('all')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.all'  values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
+      title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all'  values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
     }
+
     if (this.additionalFor('none')) {
-      title.push(' ', <FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
+      title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
     }
+
     return title;
   }
 
@@ -76,9 +80,10 @@ export default class HashtagTimeline extends React.PureComponent {
     let all  = (tags.all || []).map(tag => tag.value);
     let none = (tags.none || []).map(tag => tag.value);
 
-    [id, ...any].map((tag) => {
-      this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
+    [id, ...any].map(tag => {
+      this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
         let tags = status.tags.map(tag => tag.name);
+
         return all.filter(tag => tags.includes(tag)).length === all.length &&
                none.filter(tag => tags.includes(tag)).length === 0;
       })));
@@ -94,12 +99,14 @@ export default class HashtagTimeline extends React.PureComponent {
     const { dispatch } = this.props;
     const { id, tags } = this.props.params;
 
+    this._subscribe(dispatch, id, tags);
     dispatch(expandHashtagTimeline(id, { tags }));
   }
 
   componentWillReceiveProps (nextProps) {
     const { dispatch, params } = this.props;
     const { id, tags } = nextProps.params;
+
     if (id !== params.id || !isEqual(tags, params.tags)) {
       this._unsubscribe();
       this._subscribe(dispatch, id, tags);
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
index ce10e3f51..c583c4863 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.js
@@ -11,7 +11,7 @@ import { preferencesLink } from 'flavours/glitch/util/backend_links';
 
 const messages = defineMessages({
   general: {  id: 'settings.general', defaultMessage: 'General' },
-  compose: {  id: 'settings.compose_box_opts', defaultMessage: 'Compose box options' },
+  compose: {  id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
   content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' },
   collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' },
   media: { id: 'settings.media', defaultMessage: 'Media' },
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 16c64ced6..4535d9849 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -108,7 +108,7 @@ export default class LocalSettingsPage extends React.PureComponent {
     ),
     ({ intl, onChange, settings }) => (
       <div className='glitch local-settings__page compose_box_opts'>
-        <h1><FormattedMessage id='settings.compose_box_opts' defaultMessage='Compose box options' /></h1>
+        <h1><FormattedMessage id='settings.compose_box_opts' defaultMessage='Compose box' /></h1>
         <LocalSettingsPageItem
           settings={settings}
           item={['always_show_spoilers_field']}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js
index 21c55accc..daafe3507 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notification.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js
@@ -18,6 +18,9 @@ export default class Notification extends ImmutablePureComponent {
     onMention: PropTypes.func.isRequired,
     getScrollPosition: PropTypes.func,
     updateScrollBottom: PropTypes.func,
+    cacheMediaWidth: PropTypes.func,
+    cachedMediaWidth: PropTypes.number,
+    onUnmount: PropTypes.func,
   };
 
   render () {
@@ -57,6 +60,9 @@ export default class Notification extends ImmutablePureComponent {
           contextType='notifications'
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
+          cachedMediaWidth={this.props.cachedMediaWidth}
+          cacheMediaWidth={this.props.cacheMediaWidth}
+          onUnmount={this.props.onUnmount}
           withDismiss
         />
       );
@@ -75,6 +81,9 @@ export default class Notification extends ImmutablePureComponent {
           onMention={onMention}
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
+          cachedMediaWidth={this.props.cachedMediaWidth}
+          cacheMediaWidth={this.props.cacheMediaWidth}
+          onUnmount={this.props.onUnmount}
           withDismiss
         />
       );
@@ -93,6 +102,9 @@ export default class Notification extends ImmutablePureComponent {
           onMention={onMention}
           getScrollPosition={getScrollPosition}
           updateScrollBottom={updateScrollBottom}
+          cachedMediaWidth={this.props.cachedMediaWidth}
+          cacheMediaWidth={this.props.cacheMediaWidth}
+          onUnmount={this.props.onUnmount}
           withDismiss
         />
       );
diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.js b/app/javascript/flavours/glitch/features/public_timeline/index.js
index 477d3b8c7..7fe472202 100644
--- a/app/javascript/flavours/glitch/features/public_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/public_timeline/index.js
@@ -127,7 +127,7 @@ export default class PublicTimeline extends React.PureComponent {
           onLoadMore={this.handleLoadMore}
           trackScroll={!pinned}
           scrollKey={`public_timeline-${columnId}`}
-          emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}
+          emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
         />
       </Column>
     );
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index be82bca5b..66cc10d78 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -29,6 +29,7 @@ const messages = defineMessages({
   embed: { id: 'status.embed', defaultMessage: 'Embed' },
   admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
   admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
+  copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
 });
 
 @injectIntl
@@ -119,6 +120,25 @@ export default class ActionBar extends React.PureComponent {
     this.props.onEmbed(this.props.status);
   }
 
+  handleCopy = () => {
+    const url      = this.props.status.get('url');
+    const textarea = document.createElement('textarea');
+
+    textarea.textContent    = url;
+    textarea.style.position = 'fixed';
+
+    document.body.appendChild(textarea);
+
+    try {
+      textarea.select();
+      document.execCommand('copy');
+    } catch (e) {
+
+    } finally {
+      document.body.removeChild(textarea);
+    }
+  }
+
   render () {
     const { status, intl } = this.props;
 
@@ -128,6 +148,7 @@ export default class ActionBar extends React.PureComponent {
     let menu = [];
 
     if (publicStatus) {
+      menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
       menu.push(null);
     }
diff --git a/app/javascript/flavours/glitch/features/status/components/card.js b/app/javascript/flavours/glitch/features/status/components/card.js
index 1e1604d5c..e405a5ef0 100644
--- a/app/javascript/flavours/glitch/features/status/components/card.js
+++ b/app/javascript/flavours/glitch/features/status/components/card.js
@@ -60,6 +60,8 @@ export default class Card extends React.PureComponent {
     maxDescription: PropTypes.number,
     onOpenMedia: PropTypes.func.isRequired,
     compact: PropTypes.bool,
+    defaultWidth: PropTypes.number,
+    cacheWidth: PropTypes.func,
   };
 
   static defaultProps = {
@@ -68,7 +70,7 @@ export default class Card extends React.PureComponent {
   };
 
   state = {
-    width: 280,
+    width: this.props.defaultWidth || 280,
     embedded: false,
   };
 
@@ -111,6 +113,7 @@ export default class Card extends React.PureComponent {
 
   setRef = c => {
     if (c) {
+      if (this.props.cacheWidth) this.props.cacheWidth(c.offsetWidth);
       this.setState({ width: c.offsetWidth });
     }
   }
@@ -133,7 +136,7 @@ export default class Card extends React.PureComponent {
   }
 
   render () {
-    const { card, maxDescription, compact } = this.props;
+    const { card, maxDescription, compact, defaultWidth } = this.props;
     const { width, embedded } = this.state;
 
     if (card === null) {
diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.js b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
index 02f02efea..120ae6817 100644
--- a/app/javascript/flavours/glitch/features/status/components/detailed_status.js
+++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.js
@@ -26,7 +26,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
     settings: ImmutablePropTypes.map.isRequired,
     onOpenMedia: PropTypes.func.isRequired,
     onOpenVideo: PropTypes.func.isRequired,
-    onToggleHidden: PropTypes.func.isRequired,
+    onToggleHidden: PropTypes.func,
     expanded: PropTypes.bool,
     measureHeight: PropTypes.bool,
     onHeightChange: PropTypes.func,
@@ -79,6 +79,10 @@ export default class DetailedStatus extends ImmutablePureComponent {
     this._measureHeight(prevState.height !== this.state.height);
   }
 
+  handleChildUpdate = () => {
+    this._measureHeight();
+  }
+
   handleModalLink = e => {
     e.preventDefault();
 
@@ -94,7 +98,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
   }
 
   render () {
-    const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
+    const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
     const { expanded, onToggleHidden, settings } = this.props;
     const outerStyle = { boxSizing: 'border-box' };
     const { compact } = this.props;
@@ -218,6 +222,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
             collapsed={false}
             onExpandedToggle={onToggleHidden}
             parseClick={this.parseClick}
+            onUpdate={this.handleChildUpdate}
           />
 
           <div className='detailed-status__meta'>
diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
index e41b1dc88..6d3909ea7 100644
--- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
+++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
@@ -160,14 +160,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     }
   },
 
-  onToggleHidden (status) {
-    if (status.get('hidden')) {
-      dispatch(revealStatus(status.get('id')));
-    } else {
-      dispatch(hideStatus(status.get('id')));
-    }
-  },
-
 });
 
 export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
diff --git a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
index 9c74451b3..72f7f30b9 100644
--- a/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/doodle_modal.js
@@ -110,7 +110,7 @@ function dataURLtoFile(dataurl, filename) {
   }
   return new File([u8arr], filename, { type: mime });
 }
-
+/** Doodle canvas size options */
 const DOODLE_SIZES = {
   normal: [500, 500, 'Square 500'],
   tootbanner: [702, 330, 'Tootbanner'],
@@ -520,7 +520,7 @@ export default class DoodleModal extends ImmutablePureComponent {
     let newSize = e.target.value;
     if (newSize === this.oldSize) return;
 
-    if (this.undos.length > 1 && !confirm('Change size? This will erase your drawing!')) {
+    if (this.undos.length > 1 && !confirm('Change canvas size? This will erase your current drawing!')) {
       return;
     }
 
@@ -528,7 +528,7 @@ export default class DoodleModal extends ImmutablePureComponent {
   };
 
   handleClearBtn = () => {
-    if (this.undos.length > 1 && !confirm('Clear screen? This will erase your drawing!')) {
+    if (this.undos.length > 1 && !confirm('Clear canvas? This will erase your current drawing!')) {
       return;
     }
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
index bf29b0da5..b1643df1c 100644
--- a/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/embed_modal.js
@@ -10,6 +10,7 @@ export default class EmbedModal extends ImmutablePureComponent {
   static propTypes = {
     url: PropTypes.string.isRequired,
     onClose: PropTypes.func.isRequired,
+    onError: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   }
 
@@ -35,6 +36,8 @@ export default class EmbedModal extends ImmutablePureComponent {
       iframeDocument.body.style.margin = 0;
       this.iframe.width  = iframeDocument.body.scrollWidth;
       this.iframe.height = iframeDocument.body.scrollHeight;
+    }).catch(error => {
+      this.props.onError(error);
     });
   }
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.js b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
index a139394ac..8be1d5856 100644
--- a/app/javascript/flavours/glitch/features/ui/components/report_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.js
@@ -97,7 +97,7 @@ export default class ReportModal extends ImmutablePureComponent {
 
         <div className='report-modal__container'>
           <div className='report-modal__comment'>
-            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
+            <p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your server moderators. You can provide an explanation of why you are reporting this account below:' /></p>
 
             <textarea
               className='setting-text light'
diff --git a/app/javascript/flavours/glitch/features/ui/containers/notifications_container.js b/app/javascript/flavours/glitch/features/ui/containers/notifications_container.js
index 88d482bcf..283aa2373 100644
--- a/app/javascript/flavours/glitch/features/ui/containers/notifications_container.js
+++ b/app/javascript/flavours/glitch/features/ui/containers/notifications_container.js
@@ -1,11 +1,22 @@
+import { injectIntl } from 'react-intl';
 import { connect } from 'react-redux';
 import { NotificationStack } from 'react-notification';
 import { dismissAlert } from 'flavours/glitch/actions/alerts';
 import { getAlerts } from 'flavours/glitch/selectors';
 
-const mapStateToProps = state => ({
-  notifications: getAlerts(state),
-});
+const mapStateToProps = (state, { intl }) => {
+  const notifications = getAlerts(state);
+
+  notifications.forEach(notification => ['title', 'message'].forEach(key => {
+    const value = notification[key];
+
+    if (typeof value === 'object') {
+      notification[key] = intl.formatMessage(value);
+    }
+  }));
+
+  return { notifications };
+};
 
 const mapDispatchToProps = (dispatch) => {
   return {
@@ -15,4 +26,4 @@ const mapDispatchToProps = (dispatch) => {
   };
 };
 
-export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack);
+export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(NotificationStack));
diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js
index 602d93832..a19b3abf1 100644
--- a/app/javascript/flavours/glitch/features/ui/index.js
+++ b/app/javascript/flavours/glitch/features/ui/index.js
@@ -186,7 +186,7 @@ export default class UI extends React.Component {
     this.setState({ draggingOver: false });
     this.dragTargets = [];
 
-    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
+    if (e.dataTransfer && e.dataTransfer.files.length >= 1) {
       this.props.dispatch(uploadCompose(e.dataTransfer.files));
     }
   }
diff --git a/app/javascript/flavours/glitch/features/video/index.js b/app/javascript/flavours/glitch/features/video/index.js
index d8e8791a7..cf66536c4 100644
--- a/app/javascript/flavours/glitch/features/video/index.js
+++ b/app/javascript/flavours/glitch/features/video/index.js
@@ -103,6 +103,7 @@ export default class Video extends React.PureComponent {
     inline: PropTypes.bool,
     preventPlayback: PropTypes.bool,
     intl: PropTypes.object.isRequired,
+    cacheWidth: PropTypes.func,
   };
 
   state = {
@@ -111,7 +112,7 @@ export default class Video extends React.PureComponent {
     volume: 0.5,
     paused: true,
     dragging: false,
-    containerWidth: false,
+    containerWidth: this.props.width,
     fullscreen: false,
     hovered: false,
     muted: false,
@@ -131,6 +132,7 @@ export default class Video extends React.PureComponent {
     this.player = c;
 
     if (c && c.offsetWidth && c.offsetWidth != this.state.containerWidth) {
+      if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
       this.setState({
         containerWidth: c.offsetWidth,
       });
@@ -275,6 +277,7 @@ export default class Video extends React.PureComponent {
 
   componentDidUpdate (prevProps) {
     if (this.player && this.player.offsetWidth && this.player.offsetWidth != this.state.containerWidth && !this.state.fullscreen) {
+      if (this.props.cacheWidth) this.props.cacheWidth(this.player.offsetWidth);
       this.setState({
         containerWidth: this.player.offsetWidth,
       });
@@ -363,7 +366,6 @@ export default class Video extends React.PureComponent {
       width  = containerWidth;
       height = containerWidth / (16/9);
 
-      playerStyle.width  = width;
       playerStyle.height = height;
     } else if (inline) {
       return (<div className={computedClass} ref={this.setPlayerRef} tabindex={0}></div>);