about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch')
-rw-r--r--app/javascript/flavours/glitch/components/media_gallery.js4
-rw-r--r--app/javascript/flavours/glitch/features/account/components/header.js11
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/components/media_item.js4
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js8
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.js4
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js32
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js3
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js12
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/options_container.js10
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js23
-rw-r--r--app/javascript/flavours/glitch/features/emoji/emoji.js23
-rw-r--r--app/javascript/flavours/glitch/features/explore/index.js20
-rw-r--r--app/javascript/flavours/glitch/features/getting_started/components/trends.js7
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/header.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/accounts_map.js3
-rw-r--r--app/javascript/flavours/glitch/reducers/compose.js4
-rw-r--r--app/javascript/flavours/glitch/styles/components/columns.scss35
-rw-r--r--app/javascript/flavours/glitch/styles/components/index.scss10
-rw-r--r--app/javascript/flavours/glitch/styles/mastodon-light/diff.scss20
21 files changed, 150 insertions, 91 deletions
diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js
index 5414b4858..ac0d05926 100644
--- a/app/javascript/flavours/glitch/components/media_gallery.js
+++ b/app/javascript/flavours/glitch/components/media_gallery.js
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
 import { is } from 'immutable';
 import IconButton from './icon_button';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
 import classNames from 'classnames';
 import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
 import { debounce } from 'lodash';
@@ -202,7 +201,7 @@ class Item extends React.PureComponent {
         </a>
       );
     } else if (attachment.get('type') === 'gifv') {
-      const autoPlay = !isIOS() && this.getAutoPlay();
+      const autoPlay = this.getAutoPlay();
 
       thumbnail = (
         <div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
@@ -216,6 +215,7 @@ class Item extends React.PureComponent {
             onMouseEnter={this.handleMouseEnter}
             onMouseLeave={this.handleMouseLeave}
             autoPlay={autoPlay}
+            playsInline
             loop
             muted
           />
diff --git a/app/javascript/flavours/glitch/features/account/components/header.js b/app/javascript/flavours/glitch/features/account/components/header.js
index 47c074ec3..9a5f2fd62 100644
--- a/app/javascript/flavours/glitch/features/account/components/header.js
+++ b/app/javascript/flavours/glitch/features/account/components/header.js
@@ -141,6 +141,17 @@ class Header extends ImmutablePureComponent {
     }
   }
 
+  handleShare = () => {
+    const { account } = this.props;
+
+    navigator.share({
+      text: `${titleFromAccount(account)}\n${account.get('note_plain')}`,
+      url: account.get('url'),
+    }).catch((e) => {
+      if (e.name !== 'AbortError') console.error(e);
+    });
+  }
+
   render () {
     const { account, hidden, intl, domain } = this.props;
     const { signedIn } = this.context.identity;
diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
index a16ee4806..f7a7fd467 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js
@@ -2,7 +2,6 @@ import Blurhash from 'flavours/glitch/components/blurhash';
 import classNames from 'classnames';
 import Icon from 'flavours/glitch/components/icon';
 import { autoPlayGif, displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
-import { isIOS } from 'flavours/glitch/is_mobile';
 import PropTypes from 'prop-types';
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -109,7 +108,8 @@ export default class MediaItem extends ImmutablePureComponent {
             src={attachment.get('url')}
             onMouseEnter={this.handleMouseEnter}
             onMouseLeave={this.handleMouseLeave}
-            autoPlay={!isIOS() && autoPlayGif}
+            autoPlay={autoPlayGif}
+            playsInline
             loop
             muted
           />
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index b735af0ac..b79082f00 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -25,7 +25,13 @@ const emptyList = ImmutableList();
 const mapStateToProps = (state, { params: { acct, id, tagged }, withReplies = false }) => {
   const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
 
-  if (!accountId) {
+  if (accountId === null) {
+    return {
+      isLoading: false,
+      isAccount: false,
+      statusIds: emptyList,
+    };
+  } else if (!accountId) {
     return {
       isLoading: true,
       statusIds: emptyList,
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 516648f4b..abdd247a0 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -356,10 +356,8 @@ class ComposeForm extends ImmutablePureComponent {
           <OptionsContainer
             advancedOptions={advancedOptions}
             disabled={isSubmitting}
-            onChangeVisibility={onChangeVisibility}
             onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
             onUpload={onPaste}
-            privacy={privacy}
             isEditing={isEditing}
             sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
             spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
index 6b6d3de94..3de198c45 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
@@ -9,13 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button';
 import DropdownMenu from './dropdown_menu';
 
 //  Utils.
-import { isUserTouching } from 'flavours/glitch/is_mobile';
 import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
 
 //  The component.
 export default class ComposerOptionsDropdown extends React.PureComponent {
 
   static propTypes = {
+    isUserTouching: PropTypes.func,
     disabled: PropTypes.bool,
     icon: PropTypes.string,
     items: PropTypes.arrayOf(PropTypes.shape({
@@ -49,7 +49,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
     const { onModalOpen } = this.props;
     const { open } = this.state;
 
-    if (isUserTouching()) {
+    if (this.props.isUserTouching && this.props.isUserTouching()) {
       if (this.state.open) {
         this.props.onModalClose();
       } else {
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index c6278f4cb..b5276c371 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -10,8 +10,8 @@ import { connect } from 'react-redux';
 //  Components.
 import IconButton from 'flavours/glitch/components/icon_button';
 import TextIconButton from './text_icon_button';
-import Dropdown from './dropdown';
-import PrivacyDropdown from './privacy_dropdown';
+import DropdownContainer from '../containers/dropdown_container';
+import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
 import LanguageDropdown from '../containers/language_dropdown_container';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
@@ -126,15 +126,11 @@ class ComposerOptions extends ImmutablePureComponent {
     hasPoll: PropTypes.bool,
     intl: PropTypes.object.isRequired,
     onChangeAdvancedOption: PropTypes.func,
-    onChangeVisibility: PropTypes.func,
     onChangeContentType: PropTypes.func,
     onTogglePoll: PropTypes.func,
     onDoodleOpen: PropTypes.func,
-    onModalClose: PropTypes.func,
-    onModalOpen: PropTypes.func,
     onToggleSpoiler: PropTypes.func,
     onUpload: PropTypes.func,
-    privacy: PropTypes.string,
     contentType: PropTypes.string,
     resetFileKey: PropTypes.number,
     spoiler: PropTypes.bool,
@@ -195,12 +191,8 @@ class ComposerOptions extends ImmutablePureComponent {
       hasPoll,
       onChangeAdvancedOption,
       onChangeContentType,
-      onChangeVisibility,
       onTogglePoll,
-      onModalClose,
-      onModalOpen,
       onToggleSpoiler,
-      privacy,
       resetFileKey,
       spoiler,
       showContentTypeChoice,
@@ -239,7 +231,7 @@ class ComposerOptions extends ImmutablePureComponent {
           multiple
           style={{ display: 'none' }}
         />
-        <Dropdown
+        <DropdownContainer
           disabled={disabled || !allowMedia}
           icon='paperclip'
           items={[
@@ -255,8 +247,6 @@ class ComposerOptions extends ImmutablePureComponent {
             },
           ]}
           onChange={this.handleClickAttach}
-          onModalClose={onModalClose}
-          onModalOpen={onModalOpen}
           title={formatMessage(messages.attach)}
         />
         {!!pollLimits && (
@@ -275,15 +265,9 @@ class ComposerOptions extends ImmutablePureComponent {
           />
         )}
         <hr />
-        <PrivacyDropdown
-          disabled={disabled || isEditing}
-          onChange={onChangeVisibility}
-          onModalClose={onModalClose}
-          onModalOpen={onModalOpen}
-          value={privacy}
-        />
+        <PrivacyDropdownContainer disabled={disabled || isEditing} />
         {showContentTypeChoice && (
-          <Dropdown
+          <DropdownContainer
             disabled={disabled}
             icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
             items={[
@@ -292,8 +276,6 @@ class ComposerOptions extends ImmutablePureComponent {
               contentTypeItems.markdown,
             ]}
             onChange={onChangeContentType}
-            onModalClose={onModalClose}
-            onModalOpen={onModalOpen}
             title={formatMessage(messages.content_type)}
             value={contentType}
           />
@@ -308,7 +290,7 @@ class ComposerOptions extends ImmutablePureComponent {
           />
         )}
         <LanguageDropdown />
-        <Dropdown
+        <DropdownContainer
           disabled={disabled || isEditing}
           icon='ellipsis-h'
           items={advancedOptions ? [
@@ -325,8 +307,6 @@ class ComposerOptions extends ImmutablePureComponent {
           ] : null}
           onChange={onChangeAdvancedOption}
           renderItemContents={this.renderToggleItemContents}
-          onModalClose={onModalClose}
-          onModalOpen={onModalOpen}
           title={formatMessage(messages.advanced_options_icon_title)}
           closeOnChange={false}
         />
diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
index 14364b0a0..02cf72289 100644
--- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
@@ -32,7 +32,7 @@ class PrivacyDropdown extends React.PureComponent {
   };
 
   render () {
-    const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props;
+    const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, isUserTouching, intl: { formatMessage } } = this.props;
 
     //  We predefine our privacy items so that we can easily pick the
     //  dropdown icon later.
@@ -75,6 +75,7 @@ class PrivacyDropdown extends React.PureComponent {
         icon={(privacyItems[value] || {}).icon}
         items={items}
         onChange={onChange}
+        isUserTouching={isUserTouching}
         onModalClose={onModalClose}
         onModalOpen={onModalOpen}
         title={formatMessage(messages.change_privacy)}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js
new file mode 100644
index 000000000..3ac16505f
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux';
+import { isUserTouching } from 'flavours/glitch/is_mobile';
+import { openModal, closeModal } from 'flavours/glitch/actions/modal';
+import Dropdown from '../components/dropdown';
+
+const mapDispatchToProps = dispatch => ({
+  isUserTouching,
+  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
+  onModalClose: () => dispatch(closeModal()),
+});
+
+export default connect(null, mapDispatchToProps)(Dropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
index c792aa582..6c3db8173 100644
--- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js
+++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js
@@ -6,7 +6,7 @@ import {
   addPoll,
   removePoll,
 } from 'flavours/glitch/actions/compose';
-import { closeModal, openModal } from 'flavours/glitch/actions/modal';
+import { openModal } from 'flavours/glitch/actions/modal';
 
 function mapStateToProps (state) {
   const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
@@ -48,14 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
   onDoodleOpen() {
     dispatch(openModal('DOODLE', { noEsc: true }));
   },
-
-  onModalClose() {
-    dispatch(closeModal());
-  },
-
-  onModalOpen(props) {
-    dispatch(openModal('ACTIONS', props));
-  },
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(Options);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js
new file mode 100644
index 000000000..5591d89c4
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js
@@ -0,0 +1,23 @@
+import { connect } from 'react-redux';
+import PrivacyDropdown from '../components/privacy_dropdown';
+import { changeComposeVisibility } from 'flavours/glitch/actions/compose';
+import { openModal, closeModal } from 'flavours/glitch/actions/modal';
+import { isUserTouching } from 'flavours/glitch/is_mobile';
+
+const mapStateToProps = state => ({
+  value: state.getIn(['compose', 'privacy']),
+});
+
+const mapDispatchToProps = dispatch => ({
+
+  onChange (value) {
+    dispatch(changeComposeVisibility(value));
+  },
+
+  isUserTouching,
+  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
+  onModalClose: () => dispatch(closeModal()),
+
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
diff --git a/app/javascript/flavours/glitch/features/emoji/emoji.js b/app/javascript/flavours/glitch/features/emoji/emoji.js
index 50a399114..4f33200b6 100644
--- a/app/javascript/flavours/glitch/features/emoji/emoji.js
+++ b/app/javascript/flavours/glitch/features/emoji/emoji.js
@@ -19,8 +19,6 @@ const emojiFilename = (filename) => {
   return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
 };
 
-const domParser = new DOMParser();
-
 const emojifyTextNode = (node, customEmojis) => {
   let str = node.textContent;
 
@@ -39,7 +37,7 @@ const emojifyTextNode = (node, customEmojis) => {
       }
     }
 
-    let rend, replacement = '';
+    let rend, replacement = null;
     if (i === str.length) {
       break;
     } else if (str[i] === ':') {
@@ -51,7 +49,14 @@ const emojifyTextNode = (node, customEmojis) => {
         // if you want additional emoji handler, add statements below which set replacement and return true.
         if (shortname in customEmojis) {
           const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
-          replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
+          replacement = document.createElement('img');
+          replacement.setAttribute('draggable', false);
+          replacement.setAttribute('class', 'emojione custom-emoji');
+          replacement.setAttribute('alt', shortname);
+          replacement.setAttribute('title', shortname);
+          replacement.setAttribute('src', filename);
+          replacement.setAttribute('data-original', customEmojis[shortname].url);
+          replacement.setAttribute('data-static', customEmojis[shortname].static_url);
           return true;
         }
         return false;
@@ -59,7 +64,12 @@ const emojifyTextNode = (node, customEmojis) => {
     } else if (!useSystemEmojiFont) { // matched to unicode emoji
       const { filename, shortCode } = unicodeMapping[match];
       const title = shortCode ? `:${shortCode}:` : '';
-      replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
+      replacement = document.createElement('img');
+      replacement.setAttribute('draggable', false);
+      replacement.setAttribute('class', 'emojione');
+      replacement.setAttribute('alt', match);
+      replacement.setAttribute('title', title);
+      replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
       rend = i + match.length;
       // If the matched character was followed by VS15 (for selecting text presentation), skip it.
       if (str.codePointAt(rend) === 65038) {
@@ -69,9 +79,8 @@ const emojifyTextNode = (node, customEmojis) => {
 
     fragment.append(document.createTextNode(str.slice(0, i)));
     if (replacement) {
-      fragment.append(domParser.parseFromString(replacement, 'text/html').documentElement.getElementsByTagName('img')[0]);
+      fragment.append(replacement);
     }
-    node.textContent = str.slice(0, i);
     str = str.slice(rend);
   }
 
diff --git a/app/javascript/flavours/glitch/features/explore/index.js b/app/javascript/flavours/glitch/features/explore/index.js
index 24fa26eec..ba435d7e3 100644
--- a/app/javascript/flavours/glitch/features/explore/index.js
+++ b/app/javascript/flavours/glitch/features/explore/index.js
@@ -24,6 +24,16 @@ const mapStateToProps = state => ({
   isSearching: state.getIn(['search', 'submitted']) || !showTrends,
 });
 
+// Fix strange bug on Safari where <span> (rendered by FormattedMessage) disappears
+// after clicking around Explore top bar (issue #20885).
+// Removing width=100% from <a> also fixes it, as well as replacing <span> with <div>
+// We're choosing to wrap span with div to keep the changes local only to this tool bar.
+const WrapFormattedMessage = ({ children, ...props }) => <div><FormattedMessage {...props}>{children}</FormattedMessage></div>;
+WrapFormattedMessage.propTypes = {
+  children: PropTypes.any,
+};
+
+
 export default @connect(mapStateToProps)
 @injectIntl
 class Explore extends React.PureComponent {
@@ -47,7 +57,7 @@ class Explore extends React.PureComponent {
     this.column = c;
   }
 
-  render () {
+  render() {
     const { intl, multiColumn, isSearching } = this.props;
     const { signedIn } = this.context.identity;
 
@@ -70,10 +80,10 @@ class Explore extends React.PureComponent {
           ) : (
             <React.Fragment>
               <div className='account__section-headline'>
-                <NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
-                <NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
-                <NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
-                {signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
+                <NavLink exact to='/explore'><WrapFormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
+                <NavLink exact to='/explore/tags'><WrapFormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
+                <NavLink exact to='/explore/links'><WrapFormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
+                {signedIn && <NavLink exact to='/explore/suggestions'><WrapFormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
               </div>
 
               <Switch>
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.js
index 5158f6689..d45934d6e 100644
--- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js
+++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.js
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
 import { FormattedMessage } from 'react-intl';
+import { Link } from 'react-router-dom';
 
 export default class Trends extends ImmutablePureComponent {
 
@@ -36,7 +37,11 @@ export default class Trends extends ImmutablePureComponent {
 
     return (
       <div className='getting-started__trends'>
-        <h4><FormattedMessage id='trends.trending_now' defaultMessage='Trending now' /></h4>
+        <h4>
+          <Link to={'/explore/tags'}>
+            <FormattedMessage id='trends.trending_now' defaultMessage='Trending now' />
+          </Link>
+        </h4>
 
         {trends.take(1).map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />)}
       </div>
diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
index 4dec7d154..739c5ebae 100644
--- a/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/navigation/item/index.js
@@ -50,6 +50,8 @@ export default class LocalSettingsPage extends React.PureComponent {
       <a
         href={href}
         className={finalClassName}
+        title={title}
+        aria-label={title}
       >
         {iconElem} <span>{title}</span>
       </a>
@@ -60,6 +62,8 @@ export default class LocalSettingsPage extends React.PureComponent {
         role='button'
         tabIndex='0'
         className={finalClassName}
+        title={title}
+        aria-label={title}
       >
         {iconElem} <span>{title}</span>
       </a>
diff --git a/app/javascript/flavours/glitch/features/ui/components/header.js b/app/javascript/flavours/glitch/features/ui/components/header.js
index 6c2fb40ba..891f7fc07 100644
--- a/app/javascript/flavours/glitch/features/ui/components/header.js
+++ b/app/javascript/flavours/glitch/features/ui/components/header.js
@@ -36,7 +36,7 @@ class Header extends React.PureComponent {
     if (signedIn) {
       content = (
         <>
-          {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish' defaultMessage='Publish' /></Link>}
+          {location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
           <Account />
         </>
       );
diff --git a/app/javascript/flavours/glitch/reducers/accounts_map.js b/app/javascript/flavours/glitch/reducers/accounts_map.js
index 53e08c8fb..444bbda19 100644
--- a/app/javascript/flavours/glitch/reducers/accounts_map.js
+++ b/app/javascript/flavours/glitch/reducers/accounts_map.js
@@ -1,4 +1,5 @@
 import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
+import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts';
 import { Map as ImmutableMap } from 'immutable';
 
 export const normalizeForLookup = str => str.toLowerCase();
@@ -7,6 +8,8 @@ const initialState = ImmutableMap();
 
 export default function accountsMap(state = initialState, action) {
   switch(action.type) {
+  case ACCOUNT_LOOKUP_FAIL:
+    return action.error?.response?.status === 404 ? state.set(normalizeForLookup(action.acct), null) : state;
   case ACCOUNT_IMPORT:
     return state.set(normalizeForLookup(action.account.acct), action.account.id);
   case ACCOUNTS_IMPORT:
diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js
index 1edc70add..b1c792406 100644
--- a/app/javascript/flavours/glitch/reducers/compose.js
+++ b/app/javascript/flavours/glitch/reducers/compose.js
@@ -578,6 +578,10 @@ export default function compose(state = initialState, action) {
       if (action.status.get('spoiler_text').length > 0) {
         map.set('spoiler', true);
         map.set('spoiler_text', action.status.get('spoiler_text'));
+
+        if (map.get('media_attachments').size >= 1) {
+          map.set('sensitive', true);
+        }
       } else {
         map.set('spoiler', false);
         map.set('spoiler_text', '');
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index c61815e07..71edf7fb3 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -558,6 +558,7 @@ $ui-header-height: 55px;
   border-radius: 4px;
   margin-bottom: 10px;
   align-items: stretch;
+  gap: 2px;
 }
 
 .pillbar-button {
@@ -565,7 +566,6 @@ $ui-header-height: 55px;
   color: #fafafa;
   padding: 2px;
   margin: 0;
-  margin-left: 2px;
   font-size: inherit;
   flex: auto;
   background-color: $ui-base-color;
@@ -578,43 +578,20 @@ $ui-header-height: 55px;
   }
 
   &:not([disabled]) {
-    &:hover {
-      background-color: darken($ui-base-color, 10%);
-    }
-
+    &:hover,
     &:focus {
-      background-color: darken($ui-base-color, 15%);
-    }
-
-    &:active {
-      background-color: darken($ui-base-color, 20%);
+      background-color: darken($ui-base-color, 10%);
     }
 
     &.active {
-      background-color: $ui-highlight-color;
-      box-shadow: inset 0 5px darken($ui-highlight-color, 20%);
-
-      &:hover {
-        background-color: lighten($ui-highlight-color, 10%);
-        box-shadow: inset 0 5px darken($ui-highlight-color, 10%);
-      }
+      background-color: darken($ui-highlight-color, 2%);
 
+      &:hover,
       &:focus {
-        background-color: lighten($ui-highlight-color, 15%);
-        box-shadow: inset 0 5px darken($ui-highlight-color, 5%);
-      }
-
-      &:active {
-        background-color: lighten($ui-highlight-color, 20%);
-        box-shadow: inset 0 5px $ui-highlight-color;
+        background-color: $ui-highlight-color;
       }
     }
   }
-
-  /* TODO: check RTL? */
-  &:first-child {
-    margin-left: 0;
-  }
 }
 
 .limited-account-hint {
diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss
index b00038afd..80b0598a5 100644
--- a/app/javascript/flavours/glitch/styles/components/index.scss
+++ b/app/javascript/flavours/glitch/styles/components/index.scss
@@ -1051,12 +1051,16 @@
     margin-top: 10px;
 
     h4 {
+      border-bottom: 1px solid lighten($ui-base-color, 8%);
+      padding: 10px;
       font-size: 12px;
       text-transform: uppercase;
-      color: $darker-text-color;
-      padding: 10px;
       font-weight: 500;
-      border-bottom: 1px solid lighten($ui-base-color, 8%);
+
+      a {
+        color: $darker-text-color;
+        text-decoration: none;
+      }
     }
 
     @media screen and (max-height: 810px) {
diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
index 6489c2f80..9fc1aed2a 100644
--- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
+++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss
@@ -712,6 +712,26 @@ html {
 
 // Glitch-soc-specific changes
 
+.pillbar-button {
+  background: $ui-secondary-color;
+
+  &:not([disabled]) {
+    &:hover,
+    &:focus {
+      background: darken($ui-secondary-color, 10%);
+    }
+
+    &.active {
+      background-color: darken($ui-highlight-color, 2%);
+
+      &:hover,
+      &:focus {
+        background: lighten($ui-highlight-color, 10%);
+      }
+    }
+  }
+}
+
 .glitch.local-settings {
   background: $ui-base-color;
   border: 1px solid lighten($ui-base-color, 8%);