forked from treehouse/mastodon
[Glitch] Freeze scroll position when a dropdown menu is open in the TL
Port 6fda3cbbeb
to glitch-soc
Signed-off-by: Thibaut Girka <thib@sitedethib.com>
rebase/4.0.0rc2
parent
042c32ea3b
commit
e248399220
|
@ -1,8 +1,8 @@
|
||||||
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
|
||||||
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
|
||||||
|
|
||||||
export function openDropdownMenu(id, placement, keyboard) {
|
export function openDropdownMenu(id, placement, keyboard, scroll_key) {
|
||||||
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard };
|
return { type: DROPDOWN_MENU_OPEN, id, placement, keyboard, scroll_key };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeDropdownMenu(id) {
|
export function closeDropdownMenu(id) {
|
||||||
|
|
|
@ -10,10 +10,18 @@ import { List as ImmutableList } from 'immutable';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from 'flavours/glitch/util/fullscreen';
|
||||||
import LoadingIndicator from './loading_indicator';
|
import LoadingIndicator from './loading_indicator';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
const MOUSE_IDLE_DELAY = 300;
|
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 = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
@ -37,6 +45,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
emptyMessage: PropTypes.node,
|
emptyMessage: PropTypes.node,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
bindToDocument: PropTypes.bool,
|
bindToDocument: PropTypes.bool,
|
||||||
|
preventScroll: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -124,7 +133,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
});
|
});
|
||||||
|
|
||||||
handleMouseIdle = () => {
|
handleMouseIdle = () => {
|
||||||
if (this.scrollToTopOnMouseIdle) {
|
if (this.scrollToTopOnMouseIdle && !this.props.preventScroll) {
|
||||||
this.setScrollTop(0);
|
this.setScrollTop(0);
|
||||||
}
|
}
|
||||||
this.mouseMovedRecently = false;
|
this.mouseMovedRecently = false;
|
||||||
|
@ -176,7 +185,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
||||||
const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0);
|
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();
|
return this.getScrollHeight() - this.getScrollTop();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -96,6 +96,7 @@ class Status extends ImmutablePureComponent {
|
||||||
cacheMediaWidth: PropTypes.func,
|
cacheMediaWidth: PropTypes.func,
|
||||||
cachedMediaWidth: PropTypes.number,
|
cachedMediaWidth: PropTypes.number,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
scrollKey: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
|
|
@ -74,6 +74,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
showReplyCount: PropTypes.bool,
|
showReplyCount: PropTypes.bool,
|
||||||
directMessage: PropTypes.bool,
|
directMessage: PropTypes.bool,
|
||||||
|
scrollKey: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -198,7 +199,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, intl, withDismiss, showReplyCount, directMessage } = this.props;
|
const { status, intl, withDismiss, showReplyCount, directMessage, scrollKey } = this.props;
|
||||||
|
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const anonymousAccess = !me;
|
const anonymousAccess = !me;
|
||||||
|
@ -300,7 +301,16 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
<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} />,
|
<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} />,
|
||||||
filterButton,
|
filterButton,
|
||||||
<div key='dropdown-button' className='status__action-bar-dropdown'>
|
<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)} />
|
<DropdownMenuContainer
|
||||||
|
scrollKey={scrollKey}
|
||||||
|
disabled={anonymousAccess}
|
||||||
|
status={status}
|
||||||
|
items={menu}
|
||||||
|
icon='ellipsis-h'
|
||||||
|
size={18}
|
||||||
|
direction='right'
|
||||||
|
ariaLabel={intl.formatMessage(messages.more)}
|
||||||
|
/>
|
||||||
</div>,
|
</div>,
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
onMoveDown={this.handleMoveDown}
|
onMoveDown={this.handleMoveDown}
|
||||||
contextType={timelineId}
|
contextType={timelineId}
|
||||||
|
scrollKey={this.props.scrollKey}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
) : null;
|
) : null;
|
||||||
|
@ -112,6 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
onMoveDown={this.handleMoveDown}
|
onMoveDown={this.handleMoveDown}
|
||||||
contextType={timelineId}
|
contextType={timelineId}
|
||||||
|
scrollKey={this.props.scrollKey}
|
||||||
/>
|
/>
|
||||||
)).concat(scrollableContent);
|
)).concat(scrollableContent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const mapStateToProps = state => ({
|
||||||
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { status, items }) => ({
|
const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({
|
||||||
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
onOpen(id, onItemClick, dropdownPlacement, keyboard) {
|
||||||
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
dispatch(isUserTouching() ? openModal('ACTIONS', {
|
||||||
status,
|
status,
|
||||||
|
@ -22,7 +22,7 @@ const mapDispatchToProps = (dispatch, { status, items }) => ({
|
||||||
onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null,
|
onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null,
|
||||||
} : null
|
} : null
|
||||||
),
|
),
|
||||||
}) : openDropdownMenu(id, dropdownPlacement, keyboard));
|
}) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey));
|
||||||
},
|
},
|
||||||
onClose(id) {
|
onClose(id) {
|
||||||
dispatch(closeModal('ACTIONS'));
|
dispatch(closeModal('ACTIONS'));
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Conversation extends ImmutablePureComponent {
|
||||||
accounts: ImmutablePropTypes.list.isRequired,
|
accounts: ImmutablePropTypes.list.isRequired,
|
||||||
lastStatus: ImmutablePropTypes.map,
|
lastStatus: ImmutablePropTypes.map,
|
||||||
unread:PropTypes.bool.isRequired,
|
unread:PropTypes.bool.isRequired,
|
||||||
|
scrollKey: PropTypes.string,
|
||||||
onMoveUp: PropTypes.func,
|
onMoveUp: PropTypes.func,
|
||||||
onMoveDown: PropTypes.func,
|
onMoveDown: PropTypes.func,
|
||||||
markRead: PropTypes.func.isRequired,
|
markRead: PropTypes.func.isRequired,
|
||||||
|
@ -156,7 +157,7 @@ class Conversation extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { accounts, lastStatus, unread, intl } = this.props;
|
const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
|
||||||
const { isExpanded } = this.state;
|
const { isExpanded } = this.state;
|
||||||
|
|
||||||
if (lastStatus === null) {
|
if (lastStatus === null) {
|
||||||
|
@ -223,7 +224,15 @@ class Conversation extends ImmutablePureComponent {
|
||||||
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
|
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReply} />
|
||||||
|
|
||||||
<div className='status__action-bar-dropdown'>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class ConversationsList extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
conversations: ImmutablePropTypes.list.isRequired,
|
conversations: ImmutablePropTypes.list.isRequired,
|
||||||
|
scrollKey: PropTypes.string.isRequired,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
onLoadMore: PropTypes.func,
|
onLoadMore: PropTypes.func,
|
||||||
|
@ -57,13 +58,14 @@ export default class ConversationsList extends ImmutablePureComponent {
|
||||||
const { conversations, onLoadMore, ...other } = this.props;
|
const { conversations, onLoadMore, ...other } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} scrollKey='direct' ref={this.setRef}>
|
<ScrollableList {...other} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
|
||||||
{conversations.map(item => (
|
{conversations.map(item => (
|
||||||
<ConversationContainer
|
<ConversationContainer
|
||||||
key={item.get('id')}
|
key={item.get('id')}
|
||||||
conversationId={item.get('id')}
|
conversationId={item.get('id')}
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
onMoveDown={this.handleMoveDown}
|
onMoveDown={this.handleMoveDown}
|
||||||
|
scrollKey={this.props.scrollKey}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
|
|
|
@ -4,14 +4,14 @@ import {
|
||||||
DROPDOWN_MENU_CLOSE,
|
DROPDOWN_MENU_CLOSE,
|
||||||
} from '../actions/dropdown_menu';
|
} 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) {
|
export default function dropdownMenu(state = initialState, action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case DROPDOWN_MENU_OPEN:
|
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:
|
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:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue