about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
authorStarfall <us@starfall.systems>2020-12-24 13:36:25 -0600
committerStarfall <us@starfall.systems>2020-12-24 13:36:25 -0600
commit6ed4e874c5ace36344f77b3f096c4089d9b11e01 (patch)
tree83b2675d297f56a75b5e5dec33c644bc19f6cf1b /app/javascript/flavours
parentab127fd7941b7c84e6d6fe3071d41f52affb143c (diff)
parent225c934a1b66e2fcbedbda7936666c1ca3c9a04b (diff)
Merge branch 'glitch' into main
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/actions/notifications.js9
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_input.js8
-rw-r--r--app/javascript/flavours/glitch/components/autosuggest_textarea.js8
-rw-r--r--app/javascript/flavours/glitch/components/status_content.js9
-rw-r--r--app/javascript/flavours/glitch/features/account_gallery/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/account_timeline/index.js2
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/reply_indicator.js4
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/column_settings.js25
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js19
-rw-r--r--app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js5
-rw-r--r--app/javascript/flavours/glitch/features/notifications/index.js2
-rw-r--r--app/javascript/flavours/glitch/reducers/notifications.js3
-rw-r--r--app/javascript/flavours/glitch/reducers/settings.js2
-rw-r--r--app/javascript/flavours/glitch/styles/about.scss1
-rw-r--r--app/javascript/flavours/glitch/styles/components/columns.scss8
-rw-r--r--app/javascript/flavours/glitch/styles/components/status.scss2
-rw-r--r--app/javascript/flavours/glitch/styles/forms.scss15
-rw-r--r--app/javascript/flavours/glitch/util/rtl.js32
18 files changed, 68 insertions, 88 deletions
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js
index 9f12df773..bd3a34e5d 100644
--- a/app/javascript/flavours/glitch/actions/notifications.js
+++ b/app/javascript/flavours/glitch/actions/notifications.js
@@ -50,9 +50,8 @@ export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY';
 
 export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
 
-export const NOTIFICATIONS_SET_BROWSER_SUPPORT        = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
-export const NOTIFICATIONS_SET_BROWSER_PERMISSION     = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
-export const NOTIFICATIONS_DISMISS_BROWSER_PERMISSION = 'NOTIFICATIONS_DISMISS_BROWSER_PERMISSION';
+export const NOTIFICATIONS_SET_BROWSER_SUPPORT    = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
+export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
 
 defineMessages({
   mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
@@ -372,7 +371,3 @@ export function setBrowserPermission (value) {
     value,
   };
 }
-
-export const dismissBrowserPermission = () => ({
-  type: NOTIFICATIONS_DISMISS_BROWSER_PERMISSION,
-});
diff --git a/app/javascript/flavours/glitch/components/autosuggest_input.js b/app/javascript/flavours/glitch/components/autosuggest_input.js
index 1ef7ee216..cc0ff7dea 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_input.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_input.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
 import AutosuggestHashtag from './autosuggest_hashtag';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import { isRtl } from 'flavours/glitch/util/rtl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import classNames from 'classnames';
 import { List as ImmutableList } from 'immutable';
@@ -189,11 +188,6 @@ export default class AutosuggestInput extends ImmutablePureComponent {
   render () {
     const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
     const { suggestionsHidden } = this.state;
-    const style = { direction: 'ltr' };
-
-    if (isRtl(value)) {
-      style.direction = 'rtl';
-    }
 
     return (
       <div className='autosuggest-input'>
@@ -212,7 +206,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
             onKeyUp={onKeyUp}
             onFocus={this.onFocus}
             onBlur={this.onBlur}
-            style={style}
+            dir='auto'
             aria-autocomplete='list'
             id={id}
             className={className}
diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
index 1ce2f42b4..967c593af 100644
--- a/app/javascript/flavours/glitch/components/autosuggest_textarea.js
+++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
 import AutosuggestHashtag from './autosuggest_hashtag';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import { isRtl } from 'flavours/glitch/util/rtl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import Textarea from 'react-textarea-autosize';
 import classNames from 'classnames';
@@ -195,11 +194,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
   render () {
     const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
     const { suggestionsHidden } = this.state;
-    const style = { direction: 'ltr' };
-
-    if (isRtl(value)) {
-      style.direction = 'rtl';
-    }
 
     return [
       <div className='compose-form__autosuggest-wrapper' key='autosuggest-wrapper'>
@@ -220,7 +214,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
               onFocus={this.onFocus}
               onBlur={this.onBlur}
               onPaste={this.onPaste}
-              style={style}
+              dir='auto'
               aria-autocomplete='list'
             />
           </label>
diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js
index a39f747b8..76e2d79a5 100644
--- a/app/javascript/flavours/glitch/components/status_content.js
+++ b/app/javascript/flavours/glitch/components/status_content.js
@@ -1,7 +1,6 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
-import { isRtl } from 'flavours/glitch/util/rtl';
 import { FormattedMessage } from 'react-intl';
 import Permalink from './permalink';
 import classnames from 'classnames';
@@ -277,16 +276,11 @@ export default class StatusContent extends React.PureComponent {
 
     const content = { __html: status.get('contentHtml') };
     const spoilerContent = { __html: status.get('spoilerHtml') };
-    const directionStyle = { direction: 'ltr' };
     const classNames = classnames('status__content', {
       'status__content--with-action': parseClick && !disabled,
       'status__content--with-spoiler': status.get('spoiler_text').length > 0,
     });
 
-    if (isRtl(status.get('search_index'))) {
-      directionStyle.direction = 'rtl';
-    }
-
     if (status.get('spoiler_text').length > 0) {
       let mentionsPlaceholder = '';
 
@@ -346,7 +340,6 @@ export default class StatusContent extends React.PureComponent {
             <div
               ref={this.setContentsRef}
               key={`contents-${tagLinks}`}
-              style={directionStyle}
               tabIndex={!hidden ? 0 : null}
               dangerouslySetInnerHTML={content}
               className='status__content__text'
@@ -360,7 +353,6 @@ export default class StatusContent extends React.PureComponent {
       return (
         <div
           className={classNames}
-          style={directionStyle}
           onMouseDown={this.handleMouseDown}
           onMouseUp={this.handleMouseUp}
           tabIndex='0'
@@ -380,7 +372,6 @@ export default class StatusContent extends React.PureComponent {
       return (
         <div
           className='status__content'
-          style={directionStyle}
           tabIndex='0'
           ref={this.setRef}
         >
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js
index fda8082cc..81203e3f8 100644
--- a/app/javascript/flavours/glitch/features/account_gallery/index.js
+++ b/app/javascript/flavours/glitch/features/account_gallery/index.js
@@ -168,7 +168,7 @@ class AccountGallery extends ImmutablePureComponent {
 
             {suspended ? (
               <div className='empty-column-indicator'>
-                <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />
+                <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />
               </div>
             ) : (
               <div role='feed' className='account-gallery__container' ref={this.handleRef}>
diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js
index c56cc9b8e..0d24980a9 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/index.js
+++ b/app/javascript/flavours/glitch/features/account_timeline/index.js
@@ -117,7 +117,7 @@ class AccountTimeline extends ImmutablePureComponent {
     let emptyMessage;
 
     if (suspended) {
-      emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
+      emptyMessage = <FormattedMessage id='empty_column.account_suspended' defaultMessage='Account suspended' />;
     } else if (remote && statusIds.isEmpty()) {
       emptyMessage = <RemoteHint url={remoteUrl} />;
     } else {
diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
index 9d5b65a40..0fd07c282 100644
--- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
+++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
@@ -10,9 +10,6 @@ import AccountContainer from 'flavours/glitch/containers/account_container';
 import IconButton from 'flavours/glitch/components/icon_button';
 import AttachmentList from 'flavours/glitch/components/attachment_list';
 
-//  Utils.
-import { isRtl } from 'flavours/glitch/util/rtl';
-
 //  Messages.
 const messages = defineMessages({
   cancel: {
@@ -71,7 +68,6 @@ class ReplyIndicator extends ImmutablePureComponent {
         <div
           className='content'
           dangerouslySetInnerHTML={{ __html: content || '' }}
-          style={{ direction: isRtl(content) ? 'rtl' : 'ltr' }}
         />
         {attachments.size > 0 && (
           <AttachmentList
diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
index 9748219dd..e502c3173 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { FormattedMessage } from 'react-intl';
 import ClearColumnButton from './clear_column_button';
+import GrantPermissionButton from './grant_permission_button';
 import SettingToggle from './setting_toggle';
 
 export default class ColumnSettings extends React.PureComponent {
@@ -23,7 +24,7 @@ export default class ColumnSettings extends React.PureComponent {
   }
 
   render () {
-    const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission } = this.props;
+    const { settings, pushSettings, onChange, onClear, alertsEnabled, browserSupport, browserPermission, onRequestNotificationPermission } = this.props;
 
     const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
     const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
@@ -43,6 +44,14 @@ export default class ColumnSettings extends React.PureComponent {
           </div>
         )}
 
+        {alertsEnabled && browserSupport && browserPermission === 'default' && (
+          <div className='column-settings__row column-settings__row--with-margin'>
+            <span className='warning-hint'>
+              <FormattedMessage id='notifications.permission_required' defaultMessage='Desktop notifications are unavailable because the required permission has not been granted.' /> <GrantPermissionButton onClick={onRequestNotificationPermission} />
+            </span>
+          </div>
+        )}
+
         <div className='column-settings__row'>
           <ClearColumnButton onClick={onClear} />
         </div>
@@ -62,7 +71,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
@@ -73,7 +82,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-follow-request' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow_request' defaultMessage='New follow requests:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow_request']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow_request']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow_request']} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow_request']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow_request']} onChange={onChange} label={soundStr} />
@@ -84,7 +93,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
@@ -95,7 +104,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
@@ -106,7 +115,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
@@ -117,7 +126,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-poll' className='column-settings__section'><FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
@@ -128,7 +137,7 @@ export default class ColumnSettings extends React.PureComponent {
           <span id='notifications-status' className='column-settings__section'><FormattedMessage id='notifications.column_settings.status' defaultMessage='New toots:' /></span>
 
           <div className='column-settings__row'>
-            <SettingToggle disabled={browserPermission !== 'granted'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status']} onChange={onChange} label={alertStr} />
+            <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status']} onChange={onChange} label={alertStr} />
             {showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'status']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
             <SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'status']} onChange={onChange} label={showStr} />
             <SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'status']} onChange={onChange} label={soundStr} />
diff --git a/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js b/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js
new file mode 100644
index 000000000..798e4c787
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/notifications/components/grant_permission_button.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { FormattedMessage } from 'react-intl';
+
+export default class GrantPermissionButton extends React.PureComponent {
+
+  static propTypes = {
+    onClick: PropTypes.func.isRequired,
+  };
+
+  render () {
+    return (
+      <button className='text-btn column-header__permission-btn' tabIndex='0' onClick={this.props.onClick}>
+        <FormattedMessage id='notifications.grant_permission' defaultMessage='Grant permission.' />
+      </button>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
index 73fc05dea..dd163225e 100644
--- a/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
+++ b/app/javascript/flavours/glitch/features/notifications/components/notifications_permission_banner.js
@@ -2,7 +2,8 @@ import React from 'react';
 import Icon from 'flavours/glitch/components/icon';
 import Button from 'flavours/glitch/components/button';
 import IconButton from 'flavours/glitch/components/icon_button';
-import { requestBrowserPermission, dismissBrowserPermission } from 'flavours/glitch/actions/notifications';
+import { requestBrowserPermission } from 'flavours/glitch/actions/notifications';
+import { changeSetting } from 'flavours/glitch/actions/settings';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -25,7 +26,7 @@ class NotificationsPermissionBanner extends React.PureComponent {
   }
 
   handleClose = () => {
-    this.props.dispatch(dismissBrowserPermission());
+    this.props.dispatch(changeSetting(['notifications', 'dismissPermissionBanner'], true));
   }
 
   render () {
diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js
index 73d969517..5ceda9a91 100644
--- a/app/javascript/flavours/glitch/features/notifications/index.js
+++ b/app/javascript/flavours/glitch/features/notifications/index.js
@@ -69,7 +69,7 @@ const mapStateToProps = state => ({
   notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
   lastReadId: state.getIn(['local_settings', 'notifications', 'show_unread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
   canMarkAsRead: state.getIn(['local_settings', 'notifications', 'show_unread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
-  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default',
+  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
 });
 
 /* glitch */
diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js
index c630cc7e3..b4c5ef71a 100644
--- a/app/javascript/flavours/glitch/reducers/notifications.js
+++ b/app/javascript/flavours/glitch/reducers/notifications.js
@@ -19,7 +19,6 @@ import {
   NOTIFICATIONS_MARK_AS_READ,
   NOTIFICATIONS_SET_BROWSER_SUPPORT,
   NOTIFICATIONS_SET_BROWSER_PERMISSION,
-  NOTIFICATIONS_DISMISS_BROWSER_PERMISSION,
 } from 'flavours/glitch/actions/notifications';
 import {
   ACCOUNT_BLOCK_SUCCESS,
@@ -284,8 +283,6 @@ export default function notifications(state = initialState, action) {
     return state.set('browserSupport', action.value);
   case NOTIFICATIONS_SET_BROWSER_PERMISSION:
     return state.set('browserPermission', action.value);
-  case NOTIFICATIONS_DISMISS_BROWSER_PERMISSION:
-    return state.set('browserPermission', 'denied');
 
   case NOTIFICATION_MARK_FOR_DELETE:
     return markForDelete(state, action.id, action.yes);
diff --git a/app/javascript/flavours/glitch/reducers/settings.js b/app/javascript/flavours/glitch/reducers/settings.js
index bf0545c48..091b8feec 100644
--- a/app/javascript/flavours/glitch/reducers/settings.js
+++ b/app/javascript/flavours/glitch/reducers/settings.js
@@ -48,6 +48,8 @@ const initialState = ImmutableMap({
       advanced: false,
     }),
 
+    dismissPermissionBanner: false,
+
     shows: ImmutableMap({
       follow: true,
       follow_request: false,
diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss
index ac5f3ebb0..de821fbe4 100644
--- a/app/javascript/flavours/glitch/styles/about.scss
+++ b/app/javascript/flavours/glitch/styles/about.scss
@@ -734,6 +734,7 @@ $small-breakpoint: 960px;
 
       &__column {
         flex: 1 1 50%;
+        overflow-x: hidden;
       }
     }
 
diff --git a/app/javascript/flavours/glitch/styles/components/columns.scss b/app/javascript/flavours/glitch/styles/components/columns.scss
index 2550f50f4..2d080d3b9 100644
--- a/app/javascript/flavours/glitch/styles/components/columns.scss
+++ b/app/javascript/flavours/glitch/styles/components/columns.scss
@@ -443,6 +443,12 @@
   }
 }
 
+.column-header__permission-btn {
+  display: inline;
+  font-weight: inherit;
+  text-decoration: underline;
+}
+
 .column-header__setting-arrows {
   float: right;
 
@@ -690,7 +696,7 @@
 }
 
 .column-settings__row {
-  .text-btn {
+  .text-btn:not(.column-header__permission-btn) {
     margin-bottom: 15px;
   }
 }
diff --git a/app/javascript/flavours/glitch/styles/components/status.scss b/app/javascript/flavours/glitch/styles/components/status.scss
index 7ca1ae423..e067c9335 100644
--- a/app/javascript/flavours/glitch/styles/components/status.scss
+++ b/app/javascript/flavours/glitch/styles/components/status.scss
@@ -70,6 +70,7 @@
   p, pre, blockquote {
     margin-bottom: 20px;
     white-space: pre-wrap;
+    unicode-bidi: plaintext;
 
     &:last-child {
       margin-bottom: 0;
@@ -152,6 +153,7 @@
   a {
     color: $secondary-text-color;
     text-decoration: none;
+    unicode-bidi: isolate;
 
     &:hover {
       text-decoration: underline;
diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss
index f973cad22..0c7cb443b 100644
--- a/app/javascript/flavours/glitch/styles/forms.scss
+++ b/app/javascript/flavours/glitch/styles/forms.scss
@@ -362,11 +362,6 @@ code {
       box-shadow: none;
     }
 
-    &:focus:invalid:not(:placeholder-shown),
-    &:required:invalid:not(:placeholder-shown) {
-      border-color: lighten($error-red, 12%);
-    }
-
     &:required:valid {
       border-color: $valid-value-color;
     }
@@ -382,6 +377,16 @@ code {
     }
   }
 
+  input[type=text],
+  input[type=number],
+  input[type=email],
+  input[type=password] {
+    &:focus:invalid:not(:placeholder-shown),
+    &:required:invalid:not(:placeholder-shown) {
+      border-color: lighten($error-red, 12%);
+    }
+  }
+
   .input.field_with_errors {
     label {
       color: lighten($error-red, 12%);
diff --git a/app/javascript/flavours/glitch/util/rtl.js b/app/javascript/flavours/glitch/util/rtl.js
deleted file mode 100644
index 89bed6de8..000000000
--- a/app/javascript/flavours/glitch/util/rtl.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// U+0590  to U+05FF  - Hebrew
-// U+0600  to U+06FF  - Arabic
-// U+0700  to U+074F  - Syriac
-// U+0750  to U+077F  - Arabic Supplement
-// U+0780  to U+07BF  - Thaana
-// U+07C0  to U+07FF  - N'Ko
-// U+0800  to U+083F  - Samaritan
-// U+08A0  to U+08FF  - Arabic Extended-A
-// U+FB1D  to U+FB4F  - Hebrew presentation forms
-// U+FB50  to U+FDFF  - Arabic presentation forms A
-// U+FE70  to U+FEFF  - Arabic presentation forms B
-
-const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg;
-
-export function isRtl(text) {
-  if (text.length === 0) {
-    return false;
-  }
-
-  text = text.replace(/(?:^|[^\/\w])@([a-z0-9_]+(@[a-z0-9\.\-]+)?)/ig, '');
-  text = text.replace(/(?:^|[^\/\w])#([\S]+)/ig, '');
-  text = text.replace(/\s+/g, '');
-  text = text.replace(/(\w\S+\.\w{2,}\S*)/g, '');
-
-  const matches = text.match(rtlChars);
-
-  if (!matches) {
-    return false;
-  }
-
-  return matches.length / text.length > 0.3;
-};