about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/components')
-rw-r--r--app/javascript/flavours/glitch/components/error_boundary.js66
-rw-r--r--app/javascript/flavours/glitch/components/intersection_observer_article.js29
-rw-r--r--app/javascript/flavours/glitch/components/status.js33
-rw-r--r--app/javascript/flavours/glitch/components/status_action_bar.js8
4 files changed, 72 insertions, 64 deletions
diff --git a/app/javascript/flavours/glitch/components/error_boundary.js b/app/javascript/flavours/glitch/components/error_boundary.js
index 142a0c21a..dd21f2930 100644
--- a/app/javascript/flavours/glitch/components/error_boundary.js
+++ b/app/javascript/flavours/glitch/components/error_boundary.js
@@ -50,43 +50,43 @@ export default class ErrorBoundary extends React.PureComponent {
           <h1><FormattedMessage id='web_app_crash.title' defaultMessage="We're sorry, but something went wrong with the Mastodon app." /></h1>
           <p>
             <FormattedMessage id='web_app_crash.content' defaultMessage='You could try any of the following:' />
-            <ul>
-              <li>
-                <FormattedMessage
-                  id='web_app_crash.report_issue'
-                  defaultMessage='Report a bug in the {issuetracker}'
-                  values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
-                />
-                { debugInfo !== '' && (
-                  <details>
-                    <summary><FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' /></summary>
-                    <textarea
-                      className='web_app_crash-stacktrace'
-                      value={debugInfo}
-                      rows='10'
-                      readOnly
-                    />
-                  </details>
-                )}
-              </li>
+          </p>
+          <ul>
+            <li>
+              <FormattedMessage
+                id='web_app_crash.report_issue'
+                defaultMessage='Report a bug in the {issuetracker}'
+                values={{ issuetracker: <a href='https://github.com/glitch-soc/mastodon/issues' rel='noopener' target='_blank'><FormattedMessage id='web_app_crash.issue_tracker' defaultMessage='issue tracker' /></a> }}
+              />
+              { debugInfo !== '' && (
+                <details>
+                  <summary><FormattedMessage id='web_app_crash.debug_info' defaultMessage='Debug information' /></summary>
+                  <textarea
+                    className='web_app_crash-stacktrace'
+                    value={debugInfo}
+                    rows='10'
+                    readOnly
+                  />
+                </details>
+              )}
+            </li>
+            <li>
+              <FormattedMessage
+                id='web_app_crash.reload_page'
+                defaultMessage='{reload} the current page'
+                values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }}
+              />
+            </li>
+            { preferencesLink !== undefined && (
               <li>
                 <FormattedMessage
-                  id='web_app_crash.reload_page'
-                  defaultMessage='{reload} the current page'
-                  values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }}
+                  id='web_app_crash.change_your_settings'
+                  defaultMessage='Change your {settings}'
+                  values={{ settings: <a href={preferencesLink}><FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /></a> }}
                 />
               </li>
-              { preferencesLink !== undefined && (
-                <li>
-                  <FormattedMessage
-                    id='web_app_crash.change_your_settings'
-                    defaultMessage='Change your {settings}'
-                    values={{ settings: <a href={preferencesLink}><FormattedMessage id='web_app_crash.settings' defaultMessage='settings' /></a> }}
-                  />
-                </li>
-              )}
-            </ul>
-          </p>
+            )}
+          </ul>
         </div>
       </div>
     );
diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js
index 900c98638..03b3700df 100644
--- a/app/javascript/flavours/glitch/components/intersection_observer_article.js
+++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js
@@ -1,10 +1,12 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import ImmutablePureComponent from 'react-immutable-pure-component';
 import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
 import getRectFromEntry from 'flavours/glitch/util/get_rect_from_entry';
 
-export default class IntersectionObserverArticle extends ImmutablePureComponent {
+// Diff these props in the "unrendered" state
+const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
+
+export default class IntersectionObserverArticle extends React.Component {
 
   static propTypes = {
     intersectionObserverWrapper: PropTypes.object.isRequired,
@@ -22,20 +24,21 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
   }
 
   shouldComponentUpdate (nextProps, nextState) {
-    if (!nextState.isIntersecting && nextState.isHidden) {
-      // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
-      // that either "isIntersecting" or "isHidden" matter, and then they're
-      // the only things that matter (and updated ARIA attributes).
-      return this.state.isIntersecting || !this.state.isHidden || nextProps.listLength !== this.props.listLength;
-    } else if (nextState.isIntersecting && !this.state.isIntersecting) {
-      // If we're going from a non-intersecting state to an intersecting state,
-      // (i.e. offscreen to onscreen), then we definitely need to re-render
+    const isUnrendered = !this.state.isIntersecting && (this.state.isHidden || this.props.cachedHeight);
+    const willBeUnrendered = !nextState.isIntersecting && (nextState.isHidden || nextProps.cachedHeight);
+    if (!!isUnrendered !== !!willBeUnrendered) {
+      // If we're going from rendered to unrendered (or vice versa) then update
       return true;
     }
-    // Otherwise, diff based on "updateOnProps" and "updateOnStates"
-    return super.shouldComponentUpdate(nextProps, nextState);
+    // If we are and remain hidden, diff based on props
+    if (isUnrendered) {
+      return !updateOnPropsForUnrendered.every(prop => nextProps[prop] === this.props[prop]);
+    }
+    // Else, assume the children have changed
+    return true;
   }
 
+
   componentDidMount () {
     const { intersectionObserverWrapper, id } = this.props;
 
@@ -119,7 +122,7 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent
         data-id={id}
         tabIndex='0'
         style={style}>
-          {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || cachedHeight) })}
+          {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || !!cachedHeight) })}
       </article>
     );
   }
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index ed2623ebb..022ae6de8 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -55,8 +55,8 @@ export const defaultMediaVisibility = (status, settings) => {
   return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
 }
 
-@injectIntl
-export default class Status extends ImmutablePureComponent {
+export default @injectIntl
+class Status extends ImmutablePureComponent {
 
   static contextTypes = {
     router: PropTypes.object,
@@ -105,6 +105,7 @@ export default class Status extends ImmutablePureComponent {
     showMedia: undefined,
     statusId: undefined,
     revealBehindCW: undefined,
+    showCard: false,
   }
 
   // Avoid checking props that are functions (and whose equality will always
@@ -255,28 +256,32 @@ export default class Status extends ImmutablePureComponent {
       this.setState({ autoCollapsed: true });
     }
 
-    this.didShowCard  = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards');
+    // Hack to fix timeline jumps when a preview card is fetched
+    this.setState({
+      showCard: !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'),
+    });
   }
 
+  //  Hack to fix timeline jumps on second rendering when auto-collapsing
+  //  or on subsequent rendering when a preview card has been fetched
   getSnapshotBeforeUpdate (prevProps, prevState) {
-    if (this.props.getScrollPosition) {
+    if (!this.props.getScrollPosition) return null;
+
+    const { muted, hidden, status, settings } = this.props;
+
+    const doShowCard = !muted && !hidden && status && status.get('card') && settings.get('inline_preview_cards');
+    if (this.state.autoCollapsed || (doShowCard && !this.state.showCard)) {
+      if (doShowCard) this.setState({ showCard: true });
+      if (this.state.autoCollapsed) this.setState({ autoCollapsed: false });
       return this.props.getScrollPosition();
     } else {
       return null;
     }
   }
 
-  //  Hack to fix timeline jumps on second rendering when auto-collapsing
   componentDidUpdate (prevProps, prevState, snapshot) {
-    const doShowCard  = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards');
-    if (this.state.autoCollapsed || (doShowCard && !this.didShowCard)) {
-      if (doShowCard) this.didShowCard = true;
-      if (this.state.autoCollapsed) this.setState({ autoCollapsed: false });
-      if (snapshot !== null && this.props.updateScrollBottom) {
-        if (this.node.offsetTop < snapshot.top) {
-          this.props.updateScrollBottom(snapshot.height - snapshot.top);
-        }
-      }
+    if (snapshot !== null && this.props.updateScrollBottom && this.node.offsetTop < snapshot.top) {
+      this.props.updateScrollBottom(snapshot.height - snapshot.top);
     }
   }
 
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index 85bc4a976..c424fbde1 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -284,11 +284,11 @@ export default class StatusActionBar extends ImmutablePureComponent {
       <div className='status__action-bar'>
         {replyButton}
         {!directMessage && [
-          <IconButton className='status__action-bar-button' disabled={reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblogMessage)} icon={reblogIcon} onClick={this.handleReblogClick} />,
-          <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />,
+          <IconButton key='reblog-button' className='status__action-bar-button' disabled={reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblogMessage)} icon={reblogIcon} onClick={this.handleReblogClick} />,
+          <IconButton key='favourite-button' className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />,
           shareButton,
-          <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />,
-          <div className='status__action-bar-dropdown'>
+          <IconButton key='bookmark-button' className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />,
+          <div key='dropdown-button' className='status__action-bar-dropdown'>
             <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} />
           </div>,
         ]}