about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorReverite <github@reverite.sh>2019-06-09 16:54:21 -0700
committerReverite <github@reverite.sh>2019-06-09 16:54:21 -0700
commit3614718bc91f90a6dc19dd80ecf3bc191283c24e (patch)
treead35f4dbe92fdbc3f95881d6be3d4f9b29d4a704 /app/javascript
parent846a09a7435fb9eb435e9950175ee0e696ed4909 (diff)
parente16c8fbc7a2b5a866960a87bc8c950ad0d38f61b (diff)
Merge branch 'glitch' into production
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_textarea.js5
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.js8
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js12
-rw-r--r--app/javascript/flavours/glitch/features/list_timeline/index.js17
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/timelines.js4
-rw-r--r--app/javascript/flavours/glitch/styles/accounts.scss1
-rw-r--r--app/javascript/flavours/glitch/styles/components/media.scss1
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js61
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js51
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js2
-rw-r--r--app/javascript/mastodon/features/list_timeline/index.js17
-rw-r--r--app/javascript/mastodon/features/ui/components/compose_panel.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/navigation_panel.js3
-rw-r--r--app/javascript/mastodon/locales/ko.json4
-rw-r--r--app/javascript/mastodon/reducers/compose.js1
-rw-r--r--app/javascript/mastodon/reducers/timelines.js8
-rw-r--r--app/javascript/styles/contrast/diff.scss2
-rw-r--r--app/javascript/styles/contrast/variables.scss2
-rw-r--r--app/javascript/styles/mailer.scss4
-rw-r--r--app/javascript/styles/mastodon/_mixins.scss10
-rw-r--r--app/javascript/styles/mastodon/accounts.scss1
-rw-r--r--app/javascript/styles/mastodon/admin.scss2
-rw-r--r--app/javascript/styles/mastodon/basics.scss5
-rw-r--r--app/javascript/styles/mastodon/components.scss124
-rw-r--r--app/javascript/styles/mastodon/containers.scss4
-rw-r--r--app/javascript/styles/mastodon/emoji_picker.scss8
-rw-r--r--app/javascript/styles/mastodon/forms.scss2
-rw-r--r--app/javascript/styles/mastodon/polls.scss1
-rw-r--r--app/javascript/styles/mastodon/rtl.scss1
30 files changed, 241 insertions, 124 deletions
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
index e1ded2b3a..cf3907fbf 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -138,8 +138,11 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     this.setState({ suggestionsHidden: true, focused: false });
   }
 
-  onFocus = () => {
+  onFocus = (e) => {
     this.setState({ focused: true });
+    if (this.props.onFocus) {
+      this.props.onFocus(e);
+    }
   }
 
   onSuggestionClick = (e) => {
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index 6d1f54c60..4c398fd19 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -151,8 +151,12 @@ export default class StatusActionBar extends ImmutablePureComponent {
 
   handleOpen = () => {
     let state = {...this.context.router.history.location.state};
-    state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
-    this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state);
+    if (state.mastodonModalOpen) {
+      this.context.router.history.replace(`/statuses/${this.props.status.get('id')}`, { mastodonBackSteps: (state.mastodonBackSteps || 0) + 1 });
+    } else {
+      state.mastodonBackSteps = (state.mastodonBackSteps || 0) + 1;
+      this.context.router.history.push(`/statuses/${this.props.status.get('id')}`, state);
+    }
   }
 
   handleEmbed = () => {
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index e8f000b1e..0120be28f 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -28,6 +28,10 @@ const messages = defineMessages({
 export default @injectIntl
 class ComposeForm extends ImmutablePureComponent {
 
+  setRef = c => {
+    this.composeForm = c;
+  };
+
   static contextTypes = {
     router: PropTypes.object,
   };
@@ -208,6 +212,10 @@ class ComposeForm extends ImmutablePureComponent {
     }
   }
 
+  handleFocus = () => {
+    this.composeForm.scrollIntoView();
+  }
+
   //  This statement does several things:
   //  - If we're beginning a reply, and,
   //      - Replying to zero or one users, places the cursor at the end
@@ -302,7 +310,7 @@ class ComposeForm extends ImmutablePureComponent {
     let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia);
 
     return (
-      <div className='composer'>
+      <div className='composer' ref={this.setRef}>
         <WarningContainer />
 
         <ReplyIndicatorContainer />
@@ -323,6 +331,7 @@ class ComposeForm extends ImmutablePureComponent {
             searchTokens={[':']}
             id='glitch.composer.spoiler.input'
             className='spoiler-input__input'
+            autoFocus={false}
           />
         </div>
 
@@ -336,6 +345,7 @@ class ComposeForm extends ImmutablePureComponent {
             value={this.props.text}
             onChange={this.handleChange}
             suggestions={this.props.suggestions}
+            onFocus={this.handleFocus}
             onKeyDown={this.handleKeyDown}
             onSuggestionsFetchRequested={onFetchSuggestions}
             onSuggestionsClearRequested={onClearSuggestions}
diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js
index ef829b937..0405073c5 100644
--- a/app/javascript/flavours/glitch/features/list_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/list_timeline/index.js
@@ -75,6 +75,23 @@ export default class ListTimeline extends React.PureComponent {
     this.disconnect = dispatch(connectListStream(id));
   }
 
+  componentWillReceiveProps (nextProps) {
+    const { dispatch } = this.props;
+    const { id } = nextProps.params;
+
+    if (id !== this.props.params.id) {
+      if (this.disconnect) {
+        this.disconnect();
+        this.disconnect = null;
+      }
+
+      dispatch(fetchList(id));
+      dispatch(expandListTimeline(id));
+
+      this.disconnect = dispatch(connectListStream(id));
+    }
+  }
+
   componentWillUnmount () {
     if (this.disconnect) {
       this.disconnect();
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 51a341c42..36dfb8f15 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -442,6 +442,7 @@ export default function compose(state = initialState, action) {
       map.set('focusDate', new Date());
       map.set('caretPosition', null);
       map.set('idempotencyKey', uuid());
+      map.set('sensitive', action.status.get('sensitive'));
 
       if (action.status.get('spoiler_text').length > 0) {
         map.set('spoiler', true);
diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js
index cb233de1c..440b370e6 100644
--- a/app/javascript/flavours/glitch/reducers/timelines.js
+++ b/app/javascript/flavours/glitch/reducers/timelines.js
@@ -35,7 +35,9 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
 
     if (!next && !isLoadingRecent) mMap.set('hasMore', false);
 
-    if (!statuses.isEmpty()) {
+    if (timeline.endsWith(':pinned')) {
+      mMap.set('items', statuses.map(status => status.get('id')));
+    } else if (!statuses.isEmpty()) {
       mMap.update('items', ImmutableList(), oldIds => {
         const newIds = statuses.map(status => status.get('id'));
         const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss
index 57451c3a1..8b8c615ee 100644
--- a/app/javascript/flavours/glitch/styles/accounts.scss
+++ b/app/javascript/flavours/glitch/styles/accounts.scss
@@ -70,6 +70,7 @@
         border-radius: 4px;
         @include avatar-radius();
         background: darken($ui-base-color, 8%);
+        object-fit: cover;
       }
     }
 
diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss
index e5927057e..8b5d0486d 100644
--- a/app/javascript/flavours/glitch/styles/components/media.scss
+++ b/app/javascript/flavours/glitch/styles/components/media.scss
@@ -117,7 +117,6 @@
   display: block;
   text-decoration: none;
   color: $secondary-text-color;
-  line-height: 0;
   position: relative;
   z-index: 1;
 
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index f3fb7fa8b..4c50294ba 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -138,8 +138,11 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
     this.setState({ suggestionsHidden: true, focused: false });
   }
 
-  onFocus = () => {
+  onFocus = (e) => {
     this.setState({ focused: true });
+    if (this.props.onFocus) {
+      this.props.onFocus(e);
+    }
   }
 
   onSuggestionClick = (e) => {
@@ -189,7 +192,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
   }
 
   render () {
-    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
+    const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
     const { suggestionsHidden } = this.state;
     const style = { direction: 'ltr' };
 
@@ -197,34 +200,38 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
       style.direction = 'rtl';
     }
 
-    return (
-      <div className='autosuggest-textarea'>
-        <label>
-          <span style={{ display: 'none' }}>{placeholder}</span>
-
-          <Textarea
-            inputRef={this.setTextarea}
-            className='autosuggest-textarea__textarea'
-            disabled={disabled}
-            placeholder={placeholder}
-            autoFocus={autoFocus}
-            value={value}
-            onChange={this.onChange}
-            onKeyDown={this.onKeyDown}
-            onKeyUp={onKeyUp}
-            onFocus={this.onFocus}
-            onBlur={this.onBlur}
-            onPaste={this.onPaste}
-            style={style}
-            aria-autocomplete='list'
-          />
-        </label>
-
+    return [
+      <div className='compose-form__autosuggest-wrapper'>
+        <div className='autosuggest-textarea'>
+          <label>
+            <span style={{ display: 'none' }}>{placeholder}</span>
+
+            <Textarea
+              inputRef={this.setTextarea}
+              className='autosuggest-textarea__textarea'
+              disabled={disabled}
+              placeholder={placeholder}
+              autoFocus={autoFocus}
+              value={value}
+              onChange={this.onChange}
+              onKeyDown={this.onKeyDown}
+              onKeyUp={onKeyUp}
+              onFocus={this.onFocus}
+              onBlur={this.onBlur}
+              onPaste={this.onPaste}
+              style={style}
+              aria-autocomplete='list'
+            />
+          </label>
+        </div>
+        {children}
+      </div>,
+      <div className='autosuggest-textarea__suggestions-wrapper'>
         <div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}>
           {suggestions.map(this.renderSuggestion)}
         </div>
-      </div>
-    );
+      </div>,
+    ];
   }
 
 }
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 7e8b38580..ff22a2953 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -34,6 +34,10 @@ const messages = defineMessages({
 export default @injectIntl
 class ComposeForm extends ImmutablePureComponent {
 
+  setRef = c => {
+    this.composeForm = c;
+  };
+
   static contextTypes = {
     router: PropTypes.object,
   };
@@ -115,6 +119,10 @@ class ComposeForm extends ImmutablePureComponent {
     this.props.onChangeSpoilerText(e.target.value);
   }
 
+  handleFocus = () => {
+    this.composeForm.scrollIntoView();
+  }
+
   componentDidUpdate (prevProps) {
     // This statement does several things:
     // - If we're beginning a reply, and,
@@ -178,7 +186,7 @@ class ComposeForm extends ImmutablePureComponent {
     }
 
     return (
-      <div className='compose-form'>
+      <div className='compose-form' ref={this.setRef}>
         <WarningContainer />
 
         <ReplyIndicatorContainer />
@@ -201,29 +209,30 @@ class ComposeForm extends ImmutablePureComponent {
           />
         </div>
 
-        <div className='compose-form__autosuggest-wrapper'>
-          <AutosuggestTextarea
-            ref={this.setAutosuggestTextarea}
-            placeholder={intl.formatMessage(messages.placeholder)}
-            disabled={disabled}
-            value={this.props.text}
-            onChange={this.handleChange}
-            suggestions={this.props.suggestions}
-            onKeyDown={this.handleKeyDown}
-            onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
-            onSuggestionsClearRequested={this.onSuggestionsClearRequested}
-            onSuggestionSelected={this.onSuggestionSelected}
-            onPaste={onPaste}
-            autoFocus={!showSearch && !isMobile(window.innerWidth)}
-          />
-
+        <div className={`emoji-picker-wrapper ${this.props.showSearch ? 'emoji-picker-wrapper--hidden' : ''}`}>
           <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
         </div>
 
-        <div className='compose-form__modifiers'>
-          <UploadFormContainer />
-          <PollFormContainer />
-        </div>
+        <AutosuggestTextarea
+          ref={this.setAutosuggestTextarea}
+          placeholder={intl.formatMessage(messages.placeholder)}
+          disabled={disabled}
+          value={this.props.text}
+          onChange={this.handleChange}
+          suggestions={this.props.suggestions}
+          onFocus={this.handleFocus}
+          onKeyDown={this.handleKeyDown}
+          onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
+          onSuggestionsClearRequested={this.onSuggestionsClearRequested}
+          onSuggestionSelected={this.onSuggestionSelected}
+          onPaste={onPaste}
+          autoFocus={!showSearch && !isMobile(window.innerWidth)}
+        >
+          <div className='compose-form__modifiers'>
+            <UploadFormContainer />
+            <PollFormContainer />
+          </div>
+        </AutosuggestTextarea>
 
         <div className='compose-form__buttons-wrapper'>
           <div className='compose-form__buttons'>
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 6833c43ef..7f9edfeee 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -21,7 +21,7 @@ class SearchPopout extends React.PureComponent {
     const { style } = this.props;
     const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
     return (
-      <div style={{ ...style, position: 'absolute', width: 285 }}>
+      <div style={{ ...style, position: 'absolute', width: 285, zIndex: 2 }}>
         <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
           {({ opacity, scaleX, scaleY }) => (
             <div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js
index b6722e91a..ad7d16f95 100644
--- a/app/javascript/mastodon/features/list_timeline/index.js
+++ b/app/javascript/mastodon/features/list_timeline/index.js
@@ -75,6 +75,23 @@ class ListTimeline extends React.PureComponent {
     this.disconnect = dispatch(connectListStream(id));
   }
 
+  componentWillReceiveProps (nextProps) {
+    const { dispatch } = this.props;
+    const { id } = nextProps.params;
+
+    if (id !== this.props.params.id) {
+      if (this.disconnect) {
+        this.disconnect();
+        this.disconnect = null;
+      }
+
+      dispatch(fetchList(id));
+      dispatch(expandListTimeline(id));
+
+      this.disconnect = dispatch(connectListStream(id));
+    }
+  }
+
   componentWillUnmount () {
     if (this.disconnect) {
       this.disconnect();
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js
index c456a6400..a05fbbe39 100644
--- a/app/javascript/mastodon/features/ui/components/compose_panel.js
+++ b/app/javascript/mastodon/features/ui/components/compose_panel.js
@@ -9,9 +9,6 @@ const ComposePanel = () => (
     <SearchContainer openInRoute />
     <NavigationContainer />
     <ComposeFormContainer />
-
-    <div className='flex-spacer' />
-
     <LinkFooter withHotkeys />
   </div>
 );
diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js
index 613be7391..1d783ba1b 100644
--- a/app/javascript/mastodon/features/ui/components/navigation_panel.js
+++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js
@@ -22,7 +22,8 @@ const NavigationPanel = () => (
     <hr />
 
     <a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a>
-    <a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
+    <a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a>
+    <a className='column-link column-link--transparent' href='/explore'><Icon className='column-link__icon' id='address-book-o' fixedWidth /><FormattedMessage id='navigation_bar.profile_directory' defaultMessage='Profile directory' /></a>
   </div>
 );
 
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 9a49ce4aa..bd7b9764d 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -204,6 +204,7 @@
   "keyboard_shortcuts.search": "검색창에 포커스",
   "keyboard_shortcuts.start": "\"시작하기\" 컬럼 열기",
   "keyboard_shortcuts.toggle_hidden": "CW로 가려진 텍스트를 표시/비표시",
+  "keyboard_shortcuts.toggle_sensitivity": "이미지 보이기/숨기기",
   "keyboard_shortcuts.toot": "새 툿 작성",
   "keyboard_shortcuts.unfocus": "작성창에서 포커스 해제",
   "keyboard_shortcuts.up": "리스트에서 위로 이동",
@@ -236,6 +237,7 @@
   "navigation_bar.favourites": "즐겨찾기",
   "navigation_bar.filters": "뮤트",
   "navigation_bar.follow_requests": "팔로우 요청",
+  "navigation_bar.follows_and_followers": "팔로우와 팔로워",
   "navigation_bar.info": "이 서버에 대해서",
   "navigation_bar.keyboard_shortcuts": "단축키",
   "navigation_bar.lists": "리스트",
@@ -348,7 +350,7 @@
   "status.show_less_all": "모두 접기",
   "status.show_more": "더 보기",
   "status.show_more_all": "모두 펼치기",
-  "status.show_thread": "스레드 보기",
+  "status.show_thread": "글타래 보기",
   "status.unmute_conversation": "이 대화의 뮤트 해제하기",
   "status.unpin": "고정 해제",
   "suggestions.dismiss": "추천 지우기",
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 708272591..29c691144 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -338,6 +338,7 @@ export default function compose(state = initialState, action) {
       map.set('focusDate', new Date());
       map.set('caretPosition', null);
       map.set('idempotencyKey', uuid());
+      map.set('sensitive', action.status.get('sensitive'));
 
       if (action.status.get('spoiler_text').length > 0) {
         map.set('spoiler', true);
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
index 6a972f967..309a95a19 100644
--- a/app/javascript/mastodon/reducers/timelines.js
+++ b/app/javascript/mastodon/reducers/timelines.js
@@ -35,14 +35,12 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
 
     if (!next && !isLoadingRecent) mMap.set('hasMore', false);
 
-    if (!statuses.isEmpty()) {
+    if (timeline.endsWith(':pinned')) {
+      mMap.set('items', statuses.map(status => status.get('id')));
+    } else if (!statuses.isEmpty()) {
       mMap.update('items', ImmutableList(), oldIds => {
         const newIds = statuses.map(status => status.get('id'));
 
-        if (timeline.indexOf(':pinned') !== -1) {
-          return newIds;
-        }
-
         const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1;
         const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0);
 
diff --git a/app/javascript/styles/contrast/diff.scss b/app/javascript/styles/contrast/diff.scss
index f78e60597..5a40e7d79 100644
--- a/app/javascript/styles/contrast/diff.scss
+++ b/app/javascript/styles/contrast/diff.scss
@@ -5,7 +5,7 @@
       &-description {
         input {
           &::placeholder {
-            opacity: 1.0;
+            opacity: 1;
           }
         }
       }
diff --git a/app/javascript/styles/contrast/variables.scss b/app/javascript/styles/contrast/variables.scss
index f6cadf029..cfe3b21db 100644
--- a/app/javascript/styles/contrast/variables.scss
+++ b/app/javascript/styles/contrast/variables.scss
@@ -20,5 +20,5 @@ $highlight-text-color: $classic-highlight-color !default;
 $action-button-color: #8d9ac2;
 
 $inverted-text-color: $black !default;
-$lighter-text-color: darken($ui-base-color,6%) !default;
+$lighter-text-color: darken($ui-base-color, 6%) !default;
 $light-text-color: darken($ui-primary-color, 40%) !default;
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index 74d1df8ed..b4fb1d709 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -279,6 +279,8 @@ h5 {
 }
 
 .hero-with-button {
+  padding-bottom: 16px;
+
   h1 {
     margin-bottom: 4px;
   }
@@ -286,8 +288,6 @@ h5 {
   p.lead {
     margin-bottom: 32px;
   }
-
-  padding-bottom: 16px;
 }
 
 .header {
diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss
index 08806599e..faaffb30f 100644
--- a/app/javascript/styles/mastodon/_mixins.scss
+++ b/app/javascript/styles/mastodon/_mixins.scss
@@ -1,21 +1,21 @@
-@mixin avatar-radius() {
+@mixin avatar-radius {
   border-radius: 4px;
   background: transparent no-repeat;
   background-position: 50%;
   background-clip: padding-box;
 }
 
-@mixin avatar-size($size:48px) {
+@mixin avatar-size($size: 48px) {
   width: $size;
   height: $size;
   background-size: $size $size;
 }
 
-@mixin search-input() {
+@mixin search-input {
   outline: 0;
   box-sizing: border-box;
   width: 100%;
-  border: none;
+  border: 0;
   box-shadow: none;
   font-family: inherit;
   background: $ui-base-color;
@@ -42,7 +42,7 @@
   }
 }
 
-@mixin search-popout() {
+@mixin search-popout {
   background: $simple-background-color;
   border-radius: 4px;
   padding: 10px 14px;
diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss
index a790251f4..a93e1ea12 100644
--- a/app/javascript/styles/mastodon/accounts.scss
+++ b/app/javascript/styles/mastodon/accounts.scss
@@ -68,6 +68,7 @@
         margin: 0;
         border-radius: 4px;
         background: darken($ui-base-color, 8%);
+        object-fit: cover;
       }
     }
 
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 74f91599a..692d86852 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -171,7 +171,7 @@ $content-width: 840px;
       text-transform: none;
       padding-bottom: 0;
       margin-bottom: 0;
-      border-bottom: none;
+      border-bottom: 0;
     }
 
     & > p {
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 4411ca0b4..b5a77ce94 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -2,7 +2,8 @@
   @if type-of($color) == 'color' {
     $color: str-slice(ie-hex-str($color), 4);
   }
-  @return '%23' + unquote($color)
+
+  @return '%23' + unquote($color);
 }
 
 body {
@@ -15,7 +16,7 @@ body {
   text-rendering: optimizelegibility;
   font-feature-settings: "kern";
   text-size-adjust: none;
-  -webkit-tap-highlight-color: rgba(0,0,0,0);
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
   -webkit-tap-highlight-color: transparent;
 
   &.system-font {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 959b601e6..63c38ff42 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -128,7 +128,7 @@
   display: inline-block;
   padding: 0;
   color: $action-button-color;
-  border: none;
+  border: 0;
   background: transparent;
   cursor: pointer;
   transition: color 100ms ease-in;
@@ -196,7 +196,7 @@
 
 .text-icon-button {
   color: $lighter-text-color;
-  border: none;
+  border: 0;
   background: transparent;
   cursor: pointer;
   font-weight: 600;
@@ -333,14 +333,15 @@
     }
   }
 
+  .emoji-picker-dropdown {
+    position: absolute;
+    top: 5px;
+    right: 5px;
+    z-index: 1;
+  }
+
   .compose-form__autosuggest-wrapper {
     position: relative;
-
-    .emoji-picker-dropdown {
-      position: absolute;
-      right: 5px;
-      top: 5px;
-    }
   }
 
   .autosuggest-textarea,
@@ -352,11 +353,12 @@
   .spoiler-input {
     height: 0;
     transform-origin: bottom;
-    opacity: 0.0;
+    opacity: 0;
 
     &.spoiler-input--visible {
-      height: 47px;
-      opacity: 1.0;
+      height: 36px;
+      margin-bottom: 11px;
+      opacity: 1;
     }
   }
 
@@ -406,6 +408,20 @@
     }
   }
 
+  .autosuggest-textarea__suggestions-wrapper {
+    position: relative;
+    height: 0;
+  }
+
+  .emoji-picker-wrapper {
+    position: relative;
+    height: 0;
+
+    &.emoji-picker-wrapper--hidden {
+      display: none;
+    }
+  }
+
   .autosuggest-textarea__suggestions {
     box-sizing: border-box;
     display: none;
@@ -566,6 +582,7 @@
     border-radius: 0 0 4px 4px;
     display: flex;
     justify-content: space-between;
+    flex: 0 0 auto;
 
     .compose-form__buttons {
       display: flex;
@@ -614,6 +631,7 @@
     display: flex;
     justify-content: flex-end;
     min-width: 0;
+    flex: 0 0 auto;
 
     .compose-form__publish-button-wrapper {
       overflow: hidden;
@@ -644,6 +662,9 @@
   margin-bottom: 10px;
   background: $ui-primary-color;
   padding: 10px;
+  min-height: 23px;
+  overflow-y: auto;
+  flex: 0 2 auto;
 }
 
 .reply-indicator__header {
@@ -1172,7 +1193,7 @@
 }
 
 .account__avatar {
-  @include avatar-radius();
+  @include avatar-radius;
   position: relative;
 
   &-inline {
@@ -1182,11 +1203,11 @@
   }
 
   &-composite {
-    @include avatar-radius();
+    @include avatar-radius;
     overflow: hidden;
 
     & > div {
-      @include avatar-radius();
+      @include avatar-radius;
       float: left;
       position: relative;
       box-sizing: border-box;
@@ -1202,12 +1223,12 @@ a .account__avatar {
   @include avatar-size(48px);
 
   &-base {
-    @include avatar-radius();
+    @include avatar-radius;
     @include avatar-size(36px);
   }
 
   &-overlay {
-    @include avatar-radius();
+    @include avatar-radius;
     @include avatar-size(24px);
 
     position: absolute;
@@ -1585,13 +1606,13 @@ a.account__display-name {
     .icon-button.close {
       position: absolute;
       pointer-events: none;
-      transform: scale(0.0, 1.0) translate(-100%, 0);
+      transform: scale(0, 1) translate(-100%, 0);
       opacity: 0;
     }
 
     .compose__action-bar .icon-button {
       pointer-events: auto;
-      transform: scale(1.0, 1.0) translate(0, 0);
+      transform: scale(1, 1) translate(0, 0);
       opacity: 1;
     }
   }
@@ -1794,7 +1815,6 @@ a.account__display-name {
     height: 100%;
 
     &__pane {
-      flex: 1 1 auto;
       height: 100%;
       overflow: hidden;
       pointer-events: none;
@@ -2059,6 +2079,10 @@ a.account__display-name {
 
     .account {
       padding: 15px 10px;
+
+      &__header__bio {
+        margin: 0 -10px;
+      }
     }
 
     .notification {
@@ -2185,7 +2209,8 @@ a.account__display-name {
   margin-top: 10px;
   display: flex;
   flex-direction: column;
-  height: 100%;
+  height: calc(100% - 10px);
+  overflow-y: hidden;
 
   .search__input {
     line-height: 18px;
@@ -2201,14 +2226,33 @@ a.account__display-name {
   .navigation-bar {
     padding-top: 20px;
     padding-bottom: 20px;
+    flex: 0 1 48px;
+    min-height: 20px;
   }
 
   .flex-spacer {
     background: transparent;
   }
 
+  .compose-form {
+    flex: 1;
+    overflow-y: hidden;
+    display: flex;
+    flex-direction: column;
+    min-height: 310px;
+    padding-bottom: 71px;
+    margin-bottom: -71px;
+  }
+
+  .compose-form__autosuggest-wrapper {
+    overflow-y: auto;
+    background-color: $white;
+    border-radius: 4px 4px 0 0;
+    flex: 0 1 auto;
+  }
+
   .autosuggest-textarea__textarea {
-    max-height: 200px;
+    overflow-y: hidden;
   }
 
   .compose-form__upload-thumbnail {
@@ -2218,6 +2262,9 @@ a.account__display-name {
 
 .navigation-panel {
   margin-top: 10px;
+  margin-bottom: 10px;
+  height: calc(100% - 20px);
+  overflow-y: auto;
 
   hr {
     border: 0;
@@ -2664,7 +2711,7 @@ a.account__display-name {
 .setting-text {
   color: $darker-text-color;
   background: transparent;
-  border: none;
+  border: 0;
   border-bottom: 2px solid $ui-primary-color;
   box-sizing: border-box;
   display: block;
@@ -3002,7 +3049,7 @@ a.status-card.compact:hover {
 
   & > button {
     margin: 0;
-    border: none;
+    border: 0;
     padding: 15px 0 15px 15px;
     color: inherit;
     background: transparent;
@@ -3167,11 +3214,11 @@ a.status-card.compact:hover {
 }
 
 .no-reduce-motion .loading-indicator span {
-  animation: loader-label 1.15s infinite cubic-bezier(0.215, 0.610, 0.355, 1.000);
+  animation: loader-label 1.15s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
 }
 
 .no-reduce-motion .loading-indicator__figure {
-  animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.610, 0.355, 1.000);
+  animation: loader-figure 1.15s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
 }
 
 @keyframes loader-figure {
@@ -3338,7 +3385,7 @@ a.status-card.compact:hover {
 
   .column-select {
     &__control {
-      @include search-input();
+      @include search-input;
     }
 
     &__placeholder {
@@ -3389,7 +3436,7 @@ a.status-card.compact:hover {
     }
 
     &__menu {
-      @include search-popout();
+      @include search-popout;
       padding: 0;
       background: $ui-secondary-color;
     }
@@ -3550,7 +3597,7 @@ a.status-card.compact:hover {
 
 .no-reduce-motion .shake-bottom {
   transform-origin: 50% 100%;
-  animation: shake-bottom 0.8s cubic-bezier(0.455, 0.030, 0.515, 0.955) 2s 2 both;
+  animation: shake-bottom 0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955) 2s 2 both;
 }
 
 .emoji-picker-dropdown__menu {
@@ -3845,10 +3892,11 @@ a.status-card.compact:hover {
 }
 
 .search__input {
+  @include search-input;
+
   display: block;
   padding: 10px;
   padding-right: 30px;
-  @include search-input();
 }
 
 .search__icon {
@@ -4456,14 +4504,14 @@ a.status-card.compact:hover {
 }
 
 .actions-modal {
+  max-height: 80vh;
+  max-width: 80vw;
+
   .status {
     overflow-y: auto;
     max-height: 300px;
   }
 
-  max-height: 80vh;
-  max-width: 80vw;
-
   .actions-modal__item-label {
     font-weight: 500;
   }
@@ -4678,7 +4726,7 @@ a.status-card.compact:hover {
 }
 
 .media-gallery__item {
-  border: none;
+  border: 0;
   box-sizing: border-box;
   display: block;
   float: left;
@@ -5138,7 +5186,7 @@ a.status-card.compact:hover {
 }
 
 .account-gallery__item {
-  border: none;
+  border: 0;
   box-sizing: border-box;
   display: block;
   position: relative;
@@ -5212,7 +5260,7 @@ a.status-card.compact:hover {
 }
 
 .search-popout {
-  @include search-popout();
+  @include search-popout;
 }
 
 noscript {
@@ -5314,14 +5362,14 @@ noscript {
         .icon-button.close {
           pointer-events: auto;
           opacity: 1;
-          transform: scale(1.0, 1.0) translate(0, 0);
+          transform: scale(1, 1) translate(0, 0);
           bottom: 5px;
         }
 
         .compose__action-bar .icon-button {
           pointer-events: none;
           opacity: 0;
-          transform: scale(0.0, 1.0) translate(100%, 0);
+          transform: scale(0, 1) translate(100%, 0);
         }
       }
     }
@@ -5351,7 +5399,7 @@ noscript {
       box-sizing: border-box;
       display: block;
       width: 100%;
-      border: none;
+      border: 0;
       padding: 10px;
       font-family: $font-monospace, monospace;
       background: $ui-base-color;
diff --git a/app/javascript/styles/mastodon/containers.scss b/app/javascript/styles/mastodon/containers.scss
index 0eae4939f..2d68d2b70 100644
--- a/app/javascript/styles/mastodon/containers.scss
+++ b/app/javascript/styles/mastodon/containers.scss
@@ -121,7 +121,7 @@
   grid-auto-rows: max-content;
 
   .column-0 {
-    grid-column: 1/3;
+    grid-column: 1 / 3;
     grid-row: 1;
   }
 
@@ -136,7 +136,7 @@
   }
 
   .column-3 {
-    grid-column: 1/3;
+    grid-column: 1 / 3;
     grid-row: 3;
   }
 
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index e49084b5f..4bfd66504 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -1,14 +1,14 @@
 .emoji-mart {
+  font-size: 13px;
+  display: inline-block;
+  color: $inverted-text-color;
+
   &,
   * {
     box-sizing: border-box;
     line-height: 1.15;
   }
 
-  font-size: 13px;
-  display: inline-block;
-  color: $inverted-text-color;
-
   .emoji-mart-emoji {
     padding: 6px;
   }
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 2b8d7a682..f3de87791 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -553,7 +553,7 @@ code {
     box-sizing: border-box;
     display: block;
     width: 100%;
-    border: none;
+    border: 0;
     padding: 10px;
     font-family: $font-monospace, monospace;
     background: $ui-base-color;
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index 0d55afda4..12f57b7a9 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -47,7 +47,6 @@
       width: 100%;
       font-size: 14px;
       color: $inverted-text-color;
-      display: block;
       outline: 0;
       font-family: inherit;
       background: $simple-background-color;
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 940dc8af2..a59f59f59 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -180,7 +180,6 @@ body.rtl {
   }
 
   .fa-ul {
-    margin-left: 0;
     margin-left: 2.14285714em;
   }