about summary refs log tree commit diff
diff options
context:
space:
mode:
authorThibG <thib@sitedethib.com>2020-07-09 15:09:19 +0200
committerGitHub <noreply@github.com>2020-07-09 15:09:19 +0200
commit6fda3cbbebfdc7b050f4437b996b2ad36c1db64c (patch)
tree7a3831f14548361e2515d0e9ced6a3f3b1c812e3
parent61c07c37317f01c1ab4981826704750fe9937fe7 (diff)
Freeze scroll position when a dropdown menu is open in the TL (#14271)
* Freeze scroll position when a dropdown menu is open in the TL

* Apply this to direct TL as well

* Fix case when mouse leaves the menu
-rw-r--r--app/javascript/mastodon/actions/dropdown_menu.js4
-rw-r--r--app/javascript/mastodon/components/scrollable_list.js15
-rw-r--r--app/javascript/mastodon/components/status.js5
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js14
-rw-r--r--app/javascript/mastodon/components/status_list.js1
-rw-r--r--app/javascript/mastodon/containers/dropdown_menu_container.js4
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js13
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversations_list.js4
-rw-r--r--app/javascript/mastodon/reducers/dropdown_menu.js6
9 files changed, 49 insertions, 17 deletions
diff --git a/app/javascript/mastodon/actions/dropdown_menu.js b/app/javascript/mastodon/actions/dropdown_menu.js
index 14f2939c7..fb6e55612 100644
--- a/app/javascript/mastodon/actions/dropdown_menu.js
+++ b/app/javascript/mastodon/actions/dropdown_menu.js
@@ -1,8 +1,8 @@
 export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
 export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
 
-export function openDropdownMenu(id, placement, keyboard) {
-  return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard };
+export function openDropdownMenu(id, placement, keyboard, scroll_key) {
+  return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
 }
 
 export function closeDropdownMenu(id) {
diff --git a/app/javascript/mastodon/components/scrollable_list.js b/app/javascript/mastodon/components/scrollable_list.js
index 7eb0910c9..35740f226 100644
--- a/app/javascript/mastodon/components/scrollable_list.js
+++ b/app/javascript/mastodon/components/scrollable_list.js
@@ -10,10 +10,18 @@ import { List as ImmutableList } from 'immutable';
 import classNames from 'classnames';
 import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
 import LoadingIndicator from './loading_indicator';
+import { connect } from 'react-redux';
 
 const MOUSE_IDLE_DELAY = 300;
 
-export default class ScrollableList extends PureComponent {
+const mapStateToProps = (state, { scrollKey }) => {
+  return {
+    preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']),
+  };
+};
+
+export default @connect(mapStateToProps)
+class ScrollableList extends PureComponent {
 
   static contextTypes = {
     router: PropTypes.object,
@@ -37,6 +45,7 @@ export default class ScrollableList extends PureComponent {
     emptyMessage: PropTypes.node,
     children: PropTypes.node,
     bindToDocument: PropTypes.bool,
+    preventScroll: PropTypes.bool,
   };
 
   static defaultProps = {
@@ -129,7 +138,7 @@ export default class ScrollableList extends PureComponent {
   });
 
   handleMouseIdle = () => {
-    if (this.scrollToTopOnMouseIdle) {
+    if (this.scrollToTopOnMouseIdle && !this.props.preventScroll) {
       this.setScrollTop(0);
     }
 
@@ -179,7 +188,7 @@ export default class ScrollableList extends PureComponent {
       this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
     const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0);
 
-    if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
+    if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently || this.props.preventScroll)) {
       return this.getScrollHeight() - this.getScrollTop();
     } else {
       return null;
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index f9f6736e6..174e401b7 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -94,6 +94,7 @@ class Status extends ImmutablePureComponent {
     updateScrollBottom: PropTypes.func,
     cacheMediaWidth: PropTypes.func,
     cachedMediaWidth: PropTypes.number,
+    scrollKey: PropTypes.string,
   };
 
   // Avoid checking props that are functions (and whose equality will always
@@ -264,7 +265,7 @@ class Status extends ImmutablePureComponent {
     let media = null;
     let statusAvatar, prepend, rebloggedByText;
 
-    const { intl, hidden, featured, otherAccounts, unread, showThread } = this.props;
+    const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey } = this.props;
 
     let { status, account, ...other } = this.props;
 
@@ -459,7 +460,7 @@ class Status extends ImmutablePureComponent {
 
             {media}
 
-            <StatusActionBar status={status} account={account} {...other} />
+            <StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
           </div>
         </div>
       </HotKeys>
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index a4aa27088..231c517e9 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -85,6 +85,7 @@ class StatusActionBar extends ImmutablePureComponent {
     onPin: PropTypes.func,
     onBookmark: PropTypes.func,
     withDismiss: PropTypes.bool,
+    scrollKey: PropTypes.string,
     intl: PropTypes.object.isRequired,
   };
 
@@ -229,7 +230,7 @@ class StatusActionBar extends ImmutablePureComponent {
   }
 
   render () {
-    const { status, relationship, intl, withDismiss } = this.props;
+    const { status, relationship, intl, withDismiss, scrollKey } = this.props;
 
     const mutingConversation = status.get('muted');
     const anonymousAccess    = !me;
@@ -333,7 +334,16 @@ class StatusActionBar extends ImmutablePureComponent {
         {shareButton}
 
         <div className='status__action-bar-dropdown'>
-          <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
+          <DropdownMenuContainer
+            scrollKey={scrollKey}
+            disabled={anonymousAccess}
+            status={status}
+            items={menu}
+            icon='ellipsis-h'
+            size={18}
+            direction='right'
+            title={intl.formatMessage(messages.more)}
+          />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index e1b370c91..25411c127 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -99,6 +99,7 @@ export default class StatusList extends ImmutablePureComponent {
           onMoveUp={this.handleMoveUp}
           onMoveDown={this.handleMoveDown}
           contextType={timelineId}
+          scrollKey={this.props.scrollKey}
           showThread
         />
       ))
diff --git a/app/javascript/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index ab1823194..6ec9bbffd 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -12,7 +12,7 @@ const mapStateToProps = state => ({
   openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
 });
 
-const mapDispatchToProps = (dispatch, { status, items }) => ({
+const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
   onOpen(id, onItemClick, dropdownPlacement, keyboard) {
     if (status) {
       dispatch(fetchRelationships([status.getIn(['account', 'id'])]));
@@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
       status,
       actions: items,
       onClick: onItemClick,
-    }) : openDropdownMenu(id, dropdownPlacement, keyboard));
+    }) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
   },
 
   onClose(id) {
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index f9e45067f..6ecc27fac 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -36,6 +36,7 @@ class Conversation extends ImmutablePureComponent {
     accounts: ImmutablePropTypes.list.isRequired,
     lastStatus: ImmutablePropTypes.map,
     unread:PropTypes.bool.isRequired,
+    scrollKey: PropTypes.string,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
     markRead: PropTypes.func.isRequired,
@@ -127,7 +128,7 @@ class Conversation extends ImmutablePureComponent {
   }
 
   render () {
-    const { accounts, lastStatus, unread, intl } = this.props;
+    const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
 
     if (lastStatus === null) {
       return null;
@@ -194,7 +195,15 @@ class Conversation extends ImmutablePureComponent {
               <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
 
               <div className='status__action-bar-dropdown'>
-                <DropdownMenuContainer status={lastStatus} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
+                <DropdownMenuContainer
+                  scrollKey={scrollKey}
+                  status={lastStatus}
+                  items={menu}
+                  icon='ellipsis-h'
+                  size={18}
+                  direction='right'
+                  title={intl.formatMessage(messages.more)}
+                />
               </div>
             </div>
           </div>
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
index 8867bbd73..4ee8e5212 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversations_list.js
@@ -10,6 +10,7 @@ export default class ConversationsList extends ImmutablePureComponent {
 
   static propTypes = {
     conversations: ImmutablePropTypes.list.isRequired,
+    scrollKey: PropTypes.string.isRequired,
     hasMore: PropTypes.bool,
     isLoading: PropTypes.bool,
     onLoadMore: PropTypes.func,
@@ -58,13 +59,14 @@ export default class ConversationsList extends ImmutablePureComponent {
     const { conversations, onLoadMore, ...other } = this.props;
 
     return (
-      <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
+      <ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
         {conversations.map(item => (
           <ConversationContainer
             key={item.get('id')}
             conversationId={item.get('id')}
             onMoveUp={this.handleMoveUp}
             onMoveDown={this.handleMoveDown}
+            scrollKey={this.props.scrollKey}
           />
         ))}
       </ScrollableList>
diff --git a/app/javascript/mastodon/reducers/dropdown_menu.js b/app/javascript/mastodon/reducers/dropdown_menu.js
index 36fd4f132..a78a11acc 100644
--- a/app/javascript/mastodon/reducers/dropdown_menu.js
+++ b/app/javascript/mastodon/reducers/dropdown_menu.js
@@ -4,14 +4,14 @@ import {
   DROPDOWN_MENU_CLOSE,
 } from '../actions/dropdown_menu';
 
-const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false });
+const initialState = Immutable.Map({ openId: null, placement: null, keyboard: false, scroll_key: null });
 
 export default function dropdownMenu(state = initialState, action) {
   switch (action.type) {
   case DROPDOWN_MENU_OPEN:
-    return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard });
+    return state.merge({ openId: action.id, placement: action.placement, keyboard: action.keyboard, scroll_key: action.scroll_key });
   case DROPDOWN_MENU_CLOSE:
-    return state.get('openId') === action.id ? state.set('openId', null) : state;
+    return state.get('openId') === action.id ? state.set('openId', null).set('scroll_key', null) : state;
   default:
     return state;
   }