about summary refs log tree commit diff
path: root/app/javascript/mastodon/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/mastodon/features')
-rw-r--r--app/javascript/mastodon/features/about/index.jsx2
-rw-r--r--app/javascript/mastodon/features/audio/index.jsx8
-rw-r--r--app/javascript/mastodon/features/compose/components/language_dropdown.jsx2
-rw-r--r--app/javascript/mastodon/features/compose/components/poll_form.jsx2
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx2
-rw-r--r--app/javascript/mastodon/features/compose/components/search.jsx2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload.jsx2
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.jsx2
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx4
-rw-r--r--app/javascript/mastodon/features/direct_timeline/index.jsx2
-rw-r--r--app/javascript/mastodon/features/emoji/emoji.js4
-rw-r--r--app/javascript/mastodon/features/filters/select_filter.jsx4
-rw-r--r--app/javascript/mastodon/features/getting_started/components/announcements.jsx2
-rw-r--r--app/javascript/mastodon/features/list_editor/components/search.jsx2
-rw-r--r--app/javascript/mastodon/features/list_editor/index.jsx2
-rw-r--r--app/javascript/mastodon/features/list_timeline/index.jsx4
-rw-r--r--app/javascript/mastodon/features/notifications/components/clear_column_button.jsx2
-rw-r--r--app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx2
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.jsx18
-rw-r--r--app/javascript/mastodon/features/report/components/option.jsx2
-rw-r--r--app/javascript/mastodon/features/status/index.jsx2
-rw-r--r--app/javascript/mastodon/features/ui/components/actions_modal.jsx2
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.jsx4
-rw-r--r--app/javascript/mastodon/features/ui/components/modal_root.jsx2
-rw-r--r--app/javascript/mastodon/features/video/index.jsx6
25 files changed, 44 insertions, 42 deletions
diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx
index 2804c4a21..bb35f3203 100644
--- a/app/javascript/mastodon/features/about/index.jsx
+++ b/app/javascript/mastodon/features/about/index.jsx
@@ -67,7 +67,7 @@ class Section extends React.PureComponent {
 
     return (
       <div className={classNames('about__section', { active: !collapsed })}>
-        <div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}>
+        <div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}>
           <Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title}
         </div>
 
diff --git a/app/javascript/mastodon/features/audio/index.jsx b/app/javascript/mastodon/features/audio/index.jsx
index b0c6eeee6..df40b48f0 100644
--- a/app/javascript/mastodon/features/audio/index.jsx
+++ b/app/javascript/mastodon/features/audio/index.jsx
@@ -470,7 +470,7 @@ class Audio extends React.PureComponent {
     }
 
     return (
-      <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}>
+      <div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
 
         <Blurhash
           hash={blurhash}
@@ -493,7 +493,7 @@ class Audio extends React.PureComponent {
 
         <canvas
           role='button'
-          tabIndex='0'
+          tabIndex={0}
           className='audio-player__canvas'
           width={this.state.width}
           height={this.state.height}
@@ -526,7 +526,7 @@ class Audio extends React.PureComponent {
 
           <span
             className={classNames('video-player__seek__handle', { active: dragging })}
-            tabIndex='0'
+            tabIndex={0}
             style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
             onKeyDown={this.handleAudioKeyDown}
           />
@@ -543,7 +543,7 @@ class Audio extends React.PureComponent {
 
                 <span
                   className='video-player__volume__handle'
-                  tabIndex='0'
+                  tabIndex={0}
                   style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
                 />
               </div>
diff --git a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx
index 908cb61a7..08542e3a9 100644
--- a/app/javascript/mastodon/features/compose/components/language_dropdown.jsx
+++ b/app/javascript/mastodon/features/compose/components/language_dropdown.jsx
@@ -209,7 +209,7 @@ class LanguageDropdownMenu extends React.PureComponent {
     const { value } = this.props;
 
     return (
-      <div key={lang[0]} role='option' tabIndex='0' data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
+      <div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
         <span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
       </div>
     );
diff --git a/app/javascript/mastodon/features/compose/components/poll_form.jsx b/app/javascript/mastodon/features/compose/components/poll_form.jsx
index 967d9321b..f81d7355a 100644
--- a/app/javascript/mastodon/features/compose/components/poll_form.jsx
+++ b/app/javascript/mastodon/features/compose/components/poll_form.jsx
@@ -82,7 +82,7 @@ class OptionIntl extends React.PureComponent {
             onClick={this.handleToggleMultiple}
             onKeyPress={this.handleCheckboxKeypress}
             role='button'
-            tabIndex='0'
+            tabIndex={0}
             title={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
             aria-label={intl.formatMessage(isPollMultiple ? messages.switchToSingle : messages.switchToMultiple)}
           />
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
index 6c4b1064e..e65c9cb72 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.jsx
@@ -115,7 +115,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
     return (
       <div style={{ ...style }} role='listbox' ref={this.setRef}>
         {items.map(item => (
-          <div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
+          <div role='option' tabIndex={0} key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}>
             <div className='privacy-dropdown__option__icon'>
               <Icon id={item.icon} fixedWidth />
             </div>
diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx
index 717ecea37..46723f5cc 100644
--- a/app/javascript/mastodon/features/compose/components/search.jsx
+++ b/app/javascript/mastodon/features/compose/components/search.jsx
@@ -287,7 +287,7 @@ class Search extends React.PureComponent {
           onBlur={this.handleBlur}
         />
 
-        <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
           <Icon id='search' className={hasValue ? '' : 'active'} />
           <Icon id='times-circle' className={hasValue ? 'active' : ''} aria-label={intl.formatMessage(messages.placeholder)} />
         </div>
diff --git a/app/javascript/mastodon/features/compose/components/upload.jsx b/app/javascript/mastodon/features/compose/components/upload.jsx
index f114680b9..e3651c229 100644
--- a/app/javascript/mastodon/features/compose/components/upload.jsx
+++ b/app/javascript/mastodon/features/compose/components/upload.jsx
@@ -42,7 +42,7 @@ export default class Upload extends ImmutablePureComponent {
     const y = ((focusY / -2) + .5) * 100;
 
     return (
-      <div className='compose-form__upload' tabIndex='0' role='button'>
+      <div className='compose-form__upload' tabIndex={0} role='button'>
         <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
           {({ scale }) => (
             <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
index 5996dd666..d0dbffe65 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.jsx
@@ -144,7 +144,7 @@ class Conversation extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex='0'>
+        <div className={classNames('conversation focusable muted', { 'conversation--unread': unread })} tabIndex={0}>
           <div className='conversation__avatar' onClick={this.handleClick} role='presentation'>
             <AvatarComposite accounts={accounts} size={48} />
           </div>
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx
index 27e9a593f..04c404e1d 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.jsx
@@ -55,10 +55,10 @@ export default class ConversationsList extends ImmutablePureComponent {
   }, 300, { leading: true });
 
   render () {
-    const { conversations, onLoadMore, ...other } = this.props;
+    const { conversations, isLoading, onLoadMore, ...other } = this.props;
 
     return (
-      <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
+      <ScrollableList {...other} isLoading={isLoading} showLoading={isLoading && conversations.isEmpty()} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
         {conversations.map(item => (
           <ConversationContainer
             key={item.get('id')}
diff --git a/app/javascript/mastodon/features/direct_timeline/index.jsx b/app/javascript/mastodon/features/direct_timeline/index.jsx
index 5d4a7c49a..fc45ea69a 100644
--- a/app/javascript/mastodon/features/direct_timeline/index.jsx
+++ b/app/javascript/mastodon/features/direct_timeline/index.jsx
@@ -89,8 +89,10 @@ class DirectTimeline extends React.PureComponent {
           trackScroll={!pinned}
           scrollKey={`direct_timeline-${columnId}`}
           timelineId='direct'
+          bindToDocument={!multiColumn}
           onLoadMore={this.handleLoadMore}
           prepend={<div className='follow_requests-unlocked_explanation'><span><FormattedMessage id='compose_form.encryption_warning' defaultMessage='Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.' /> <a href='/terms' target='_blank'><FormattedMessage id='compose_form.direct_message_warning_learn_more' defaultMessage='Learn more' /></a></span></div>}
+          alwaysPrepend
           emptyMessage={<FormattedMessage id='empty_column.direct' defaultMessage="You don't have any private mentions yet. When you send or receive one, it will show up here." />}
         />
 
diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js
index bc3dd8c60..6ae406624 100644
--- a/app/javascript/mastodon/features/emoji/emoji.js
+++ b/app/javascript/mastodon/features/emoji/emoji.js
@@ -50,7 +50,7 @@ const emojifyTextNode = (node, customEmojis) => {
         if (shortname in customEmojis) {
           const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
           replacement = document.createElement('img');
-          replacement.setAttribute('draggable', false);
+          replacement.setAttribute('draggable', 'false');
           replacement.setAttribute('class', 'emojione custom-emoji');
           replacement.setAttribute('alt', shortname);
           replacement.setAttribute('title', shortname);
@@ -65,7 +65,7 @@ const emojifyTextNode = (node, customEmojis) => {
       const { filename, shortCode } = unicodeMapping[match];
       const title = shortCode ? `:${shortCode}:` : '';
       replacement = document.createElement('img');
-      replacement.setAttribute('draggable', false);
+      replacement.setAttribute('draggable', 'false');
       replacement.setAttribute('class', 'emojione');
       replacement.setAttribute('alt', match);
       replacement.setAttribute('title', title);
diff --git a/app/javascript/mastodon/features/filters/select_filter.jsx b/app/javascript/mastodon/features/filters/select_filter.jsx
index b0aede187..5778eec8e 100644
--- a/app/javascript/mastodon/features/filters/select_filter.jsx
+++ b/app/javascript/mastodon/features/filters/select_filter.jsx
@@ -65,7 +65,7 @@ class SelectFilter extends React.PureComponent {
     }
 
     return (
-      <div key={filter[0]} role='button' tabIndex='0' data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}>
+      <div key={filter[0]} role='button' tabIndex={0} data-index={filter[0]} className='language-dropdown__dropdown__results__item' onClick={this.handleItemClick} onKeyDown={this.handleKeyDown}>
         <span className='language-dropdown__dropdown__results__item__native-name'>{filter[1]}</span> {warning}
       </div>
     );
@@ -73,7 +73,7 @@ class SelectFilter extends React.PureComponent {
 
   renderCreateNew (name) {
     return (
-      <div key='add-new-filter' role='button' tabIndex='0' className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}>
+      <div key='add-new-filter' role='button' tabIndex={0} className='language-dropdown__dropdown__results__item' onClick={this.handleNewFilterClick} onKeyDown={this.handleKeyDown}>
         <Icon id='plus' fixedWidth /> <FormattedMessage id='filter_modal.select_filter.prompt_new' defaultMessage='New category: {name}' values={{ name }} />
       </div>
     );
diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx
index 954f407f5..5f993d2c5 100644
--- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx
+++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx
@@ -418,7 +418,7 @@ class Announcements extends ImmutablePureComponent {
         <img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
 
         <div className='announcements__container'>
-          <ReactSwipeableViews animateHeight={!reduceMotion} adjustHeight={reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
+          <ReactSwipeableViews animateHeight animateTransitions={!reduceMotion} index={index} onChangeIndex={this.handleChangeIndex}>
             {announcements.map((announcement, idx) => (
               <Announcement
                 key={announcement.get('id')}
diff --git a/app/javascript/mastodon/features/list_editor/components/search.jsx b/app/javascript/mastodon/features/list_editor/components/search.jsx
index b9745e0a8..f70e272f7 100644
--- a/app/javascript/mastodon/features/list_editor/components/search.jsx
+++ b/app/javascript/mastodon/features/list_editor/components/search.jsx
@@ -63,7 +63,7 @@ class Search extends React.PureComponent {
           />
         </label>
 
-        <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
+        <div role='button' tabIndex={0} className='search__icon' onClick={this.handleClear}>
           <Icon id='search' className={classNames({ active: !hasValue })} />
           <Icon id='times-circle' aria-label={intl.formatMessage(messages.search)} className={classNames({ active: hasValue })} />
         </div>
diff --git a/app/javascript/mastodon/features/list_editor/index.jsx b/app/javascript/mastodon/features/list_editor/index.jsx
index 65cebbdc8..ed9d09132 100644
--- a/app/javascript/mastodon/features/list_editor/index.jsx
+++ b/app/javascript/mastodon/features/list_editor/index.jsx
@@ -60,7 +60,7 @@ class ListEditor extends ImmutablePureComponent {
             {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
           </div>
 
-          {showSearch && <div role='button' tabIndex='-1' className='drawer__backdrop' onClick={onClear} />}
+          {showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
 
           <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
             {({ x }) => (
diff --git a/app/javascript/mastodon/features/list_timeline/index.jsx b/app/javascript/mastodon/features/list_timeline/index.jsx
index d0ba2a186..c0b9a62ff 100644
--- a/app/javascript/mastodon/features/list_timeline/index.jsx
+++ b/app/javascript/mastodon/features/list_timeline/index.jsx
@@ -176,11 +176,11 @@ class ListTimeline extends React.PureComponent {
           multiColumn={multiColumn}
         >
           <div className='column-settings__row column-header__links'>
-            <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
+            <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleEditClick}>
               <Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
             </button>
 
-            <button type='button' className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
+            <button type='button' className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.handleDeleteClick}>
               <Icon id='trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
             </button>
           </div>
diff --git a/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx
index b82fd092f..9a076ce5e 100644
--- a/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx
+++ b/app/javascript/mastodon/features/notifications/components/clear_column_button.jsx
@@ -11,7 +11,7 @@ export default class ClearColumnButton extends React.PureComponent {
 
   render () {
     return (
-      <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
+      <button className='text-btn column-header__setting-btn' tabIndex={0} onClick={this.props.onClick}><Icon id='eraser' /> <FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' /></button>
     );
   }
 
diff --git a/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx b/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx
index 798e4c787..5b2db48fd 100644
--- a/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx
+++ b/app/javascript/mastodon/features/notifications/components/grant_permission_button.jsx
@@ -10,7 +10,7 @@ export default class GrantPermissionButton extends React.PureComponent {
 
   render () {
     return (
-      <button className='text-btn column-header__permission-btn' tabIndex='0' onClick={this.props.onClick}>
+      <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/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx
index cf9d9f72c..f3104cee0 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.jsx
+++ b/app/javascript/mastodon/features/notifications/components/notification.jsx
@@ -123,7 +123,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-follow focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-follow focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='user-plus' fixedWidth />
@@ -145,7 +145,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='user' fixedWidth />
@@ -185,7 +185,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-favourite focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-favourite focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='star' className='star-icon' fixedWidth />
@@ -217,7 +217,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-reblog focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-reblog focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='retweet' fixedWidth />
@@ -253,7 +253,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-status focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-status focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='home' fixedWidth />
@@ -290,7 +290,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-update focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-update focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.update, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='pencil' fixedWidth />
@@ -329,7 +329,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-poll focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
+        <div className={classNames('notification notification-poll focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='tasks' fixedWidth />
@@ -366,7 +366,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-admin-sign-up focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminSignUp, { name: account.get('acct') }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='user-plus' fixedWidth />
@@ -396,7 +396,7 @@ class Notification extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={this.getHandlers()}>
-        <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}>
+        <div className={classNames('notification notification-admin-report focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.adminReport, { name: account.get('acct'), target: notification.getIn(['report', 'target_account', 'acct']) }), notification.get('created_at'))}>
           <div className='notification__message'>
             <div className='notification__favourite-icon-wrapper'>
               <Icon id='flag' fixedWidth />
diff --git a/app/javascript/mastodon/features/report/components/option.jsx b/app/javascript/mastodon/features/report/components/option.jsx
index 42c04b018..342204e22 100644
--- a/app/javascript/mastodon/features/report/components/option.jsx
+++ b/app/javascript/mastodon/features/report/components/option.jsx
@@ -40,7 +40,7 @@ export default class Option extends React.PureComponent {
 
         <span
           className={classNames('poll__input', { active: checked, checkbox: multiple })}
-          tabIndex='0'
+          tabIndex={0}
           role='radio'
           onKeyPress={this.handleKeyPress}
           aria-checked={checked}
diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx
index 1d7cb685f..2d18c6e9d 100644
--- a/app/javascript/mastodon/features/status/index.jsx
+++ b/app/javascript/mastodon/features/status/index.jsx
@@ -630,7 +630,7 @@ class Status extends ImmutablePureComponent {
             {ancestors}
 
             <HotKeys handlers={handlers}>
-              <div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
+              <div className={classNames('focusable', 'detailed-status__wrapper', `detailed-status__wrapper-${status.get('visibility')}`)} tabIndex={0} aria-label={textForScreenReader(intl, status, false)}>
                 <DetailedStatus
                   key={`details-${status.get('id')}`}
                   status={status}
diff --git a/app/javascript/mastodon/features/ui/components/actions_modal.jsx b/app/javascript/mastodon/features/ui/components/actions_modal.jsx
index fd59c1e20..35090e242 100644
--- a/app/javascript/mastodon/features/ui/components/actions_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/actions_modal.jsx
@@ -23,7 +23,7 @@ export default class ActionsModal extends ImmutablePureComponent {
     return (
       <li key={`${text}-${i}`}>
         <a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames({ active })}>
-          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
+          {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex={-1} inverted />}
           <div>
             <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
             <div>{meta}</div>
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.jsx b/app/javascript/mastodon/features/ui/components/media_modal.jsx
index aa179a965..52bd75453 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.jsx
+++ b/app/javascript/mastodon/features/ui/components/media_modal.jsx
@@ -138,8 +138,8 @@ class MediaModal extends ImmutablePureComponent {
 
     const index = this.getIndex();
 
-    const leftNav  = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
-    const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
+    const leftNav  = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
+    const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav  media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
 
     const content = media.map((image) => {
       const width  = image.getIn(['meta', 'original', 'width']) || null;
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.jsx b/app/javascript/mastodon/features/ui/components/modal_root.jsx
index 5a1734977..c252e6caa 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.jsx
+++ b/app/javascript/mastodon/features/ui/components/modal_root.jsx
@@ -73,7 +73,7 @@ export default class ModalRoot extends React.PureComponent {
       document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
     } else {
       document.body.classList.remove('with-modals--active');
-      document.documentElement.style.marginRight = 0;
+      document.documentElement.style.marginRight = '0';
     }
   }
 
diff --git a/app/javascript/mastodon/features/video/index.jsx b/app/javascript/mastodon/features/video/index.jsx
index 58bb45908..e2637e0be 100644
--- a/app/javascript/mastodon/features/video/index.jsx
+++ b/app/javascript/mastodon/features/video/index.jsx
@@ -582,7 +582,7 @@ class Video extends React.PureComponent {
           poster={preview}
           preload={preload}
           role='button'
-          tabIndex='0'
+          tabIndex={0}
           aria-label={alt}
           title={alt}
           lang={lang}
@@ -611,7 +611,7 @@ class Video extends React.PureComponent {
 
             <span
               className={classNames('video-player__seek__handle', { active: dragging })}
-              tabIndex='0'
+              tabIndex={0}
               style={{ left: `${progress}%` }}
               onKeyDown={this.handleVideoKeyDown}
             />
@@ -627,7 +627,7 @@ class Video extends React.PureComponent {
 
                 <span
                   className={classNames('video-player__volume__handle')}
-                  tabIndex='0'
+                  tabIndex={0}
                   style={{ left: `${volume * 100}%` }}
                 />
               </div>