[Glitch] Change single-column mode to scroll the whole page

Port de4ead8fc6 to glitch-soc

Signed-off-by: Thibaut Girka <thib@sitedethib.com>
This commit is contained in:
Eugen Rochko 2019-07-19 09:25:22 +02:00 committed by Thibaut Girka
parent 7633d16627
commit 4fdae72eeb
26 changed files with 150 additions and 42 deletions

View file

@ -35,6 +35,7 @@ export default class ScrollableList extends PureComponent {
alwaysPrepend: PropTypes.bool, alwaysPrepend: PropTypes.bool,
emptyMessage: PropTypes.node, emptyMessage: PropTypes.node,
children: PropTypes.node, children: PropTypes.node,
bindToDocument: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@ -50,7 +51,9 @@ export default class ScrollableList extends PureComponent {
handleScroll = throttle(() => { handleScroll = throttle(() => {
if (this.node) { if (this.node) {
const { scrollTop, scrollHeight, clientHeight } = this.node; const scrollTop = this.getScrollTop();
const scrollHeight = this.getScrollHeight();
const clientHeight = this.getClientHeight();
const offset = scrollHeight - scrollTop - clientHeight; const offset = scrollHeight - scrollTop - clientHeight;
if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) { if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
@ -80,9 +83,14 @@ export default class ScrollableList extends PureComponent {
scrollToTopOnMouseIdle = false; scrollToTopOnMouseIdle = false;
setScrollTop = newScrollTop => { setScrollTop = newScrollTop => {
if (this.node.scrollTop !== newScrollTop) { if (this.getScrollTop() !== newScrollTop) {
this.lastScrollWasSynthetic = true; this.lastScrollWasSynthetic = true;
this.node.scrollTop = newScrollTop;
if (this.props.bindToDocument) {
document.scrollingElement.scrollTop = newScrollTop;
} else {
this.node.scrollTop = newScrollTop;
}
} }
}; };
@ -100,7 +108,7 @@ export default class ScrollableList extends PureComponent {
this.mouseIdleTimer = this.mouseIdleTimer =
setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY); setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
if (!this.mouseMovedRecently && this.node.scrollTop === 0) { if (!this.mouseMovedRecently && this.getScrollTop() === 0) {
// Only set if we just started moving and are scrolled to the top. // Only set if we just started moving and are scrolled to the top.
this.scrollToTopOnMouseIdle = true; this.scrollToTopOnMouseIdle = true;
} }
@ -132,15 +140,27 @@ export default class ScrollableList extends PureComponent {
} }
getScrollPosition = () => { getScrollPosition = () => {
if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { if (this.node && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
return {height: this.node.scrollHeight, top: this.node.scrollTop}; return { height: this.getScrollHeight(), top: this.getScrollTop() };
} else { } else {
return null; return null;
} }
} }
getScrollTop = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
}
getScrollHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
}
getClientHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
}
updateScrollBottom = (snapshot) => { updateScrollBottom = (snapshot) => {
const newScrollTop = this.node.scrollHeight - snapshot; const newScrollTop = this.getScrollHeight() - snapshot;
this.setScrollTop(newScrollTop); this.setScrollTop(newScrollTop);
} }
@ -155,8 +175,8 @@ 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.node.scrollTop > 0 || this.mouseMovedRecently)) { if (pendingChanged || someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
return this.node.scrollHeight - this.node.scrollTop; return this.getScrollHeight() - this.getScrollTop();
} else { } else {
return null; return null;
} }
@ -165,7 +185,9 @@ export default class ScrollableList extends PureComponent {
componentDidUpdate (prevProps, prevState, snapshot) { componentDidUpdate (prevProps, prevState, snapshot) {
// Reset the scroll position when a new child comes in in order not to // Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page. // jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null) this.updateScrollBottom(snapshot); if (snapshot !== null) {
this.updateScrollBottom(snapshot);
}
} }
componentWillUnmount () { componentWillUnmount () {
@ -191,13 +213,23 @@ export default class ScrollableList extends PureComponent {
} }
attachScrollListener () { attachScrollListener () {
this.node.addEventListener('scroll', this.handleScroll); if (this.props.bindToDocument) {
this.node.addEventListener('wheel', this.handleWheel); document.addEventListener('scroll', this.handleScroll);
document.addEventListener('wheel', this.handleWheel);
} else {
this.node.addEventListener('scroll', this.handleScroll);
this.node.addEventListener('wheel', this.handleWheel);
}
} }
detachScrollListener () { detachScrollListener () {
this.node.removeEventListener('scroll', this.handleScroll); if (this.props.bindToDocument) {
this.node.removeEventListener('wheel', this.handleWheel); document.removeEventListener('scroll', this.handleScroll);
document.removeEventListener('wheel', this.handleWheel);
} else {
this.node.removeEventListener('scroll', this.handleScroll);
this.node.removeEventListener('wheel', this.handleWheel);
}
} }
getFirstChildKey (props) { getFirstChildKey (props) {

View file

@ -10,6 +10,7 @@ import Poll from 'flavours/glitch/components/poll';
import Hashtag from 'flavours/glitch/components/hashtag'; import Hashtag from 'flavours/glitch/components/hashtag';
import Audio from 'flavours/glitch/features/audio'; import Audio from 'flavours/glitch/features/audio';
import ModalRoot from 'flavours/glitch/components/modal_root'; import ModalRoot from 'flavours/glitch/components/modal_root';
import { getScrollbarWidth } from 'flavours/glitch/features/ui/components/modal_root';
import MediaModal from 'flavours/glitch/features/ui/components/media_modal'; import MediaModal from 'flavours/glitch/features/ui/components/media_modal';
import { List as ImmutableList, fromJS } from 'immutable'; import { List as ImmutableList, fromJS } from 'immutable';
@ -33,6 +34,8 @@ export default class MediaContainer extends PureComponent {
handleOpenMedia = (media, index) => { handleOpenMedia = (media, index) => {
document.body.classList.add('with-modals--active'); document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media, index }); this.setState({ media, index });
} }
@ -40,11 +43,15 @@ export default class MediaContainer extends PureComponent {
const media = ImmutableList([video]); const media = ImmutableList([video]);
document.body.classList.add('with-modals--active'); document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
this.setState({ media, time }); this.setState({ media, time });
} }
handleCloseMedia = () => { handleCloseMedia = () => {
document.body.classList.remove('with-modals--active'); document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = 0;
this.setState({ media: null, index: null, time: null }); this.setState({ media: null, index: null, time: null });
} }

View file

@ -39,6 +39,7 @@ class AccountTimeline extends ImmutablePureComponent {
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool, withReplies: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -76,7 +77,7 @@ class AccountTimeline extends ImmutablePureComponent {
} }
render () { render () {
const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount } = this.props; const { statusIds, featuredStatusIds, isLoading, hasMore, isAccount, multiColumn } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -108,6 +109,7 @@ class AccountTimeline extends ImmutablePureComponent {
hasMore={hasMore} hasMore={hasMore}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />} emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -31,6 +31,7 @@ class Blocks extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -42,7 +43,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, accountIds, hasMore } = this.props; const { intl, accountIds, hasMore, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -62,6 +63,7 @@ class Blocks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} /> <AccountContainer key={id} id={id} />

View file

@ -125,6 +125,7 @@ class CommunityTimeline extends React.PureComponent {
timelineId={`community${onlyMedia ? ':media' : ''}`} timelineId={`community${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -32,6 +32,7 @@ class Blocks extends ImmutablePureComponent {
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
domains: ImmutablePropTypes.list, domains: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -43,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, domains, hasMore } = this.props; const { intl, domains, hasMore, multiColumn } = this.props;
if (!domains) { if (!domains) {
return ( return (
@ -63,6 +64,7 @@ class Blocks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{domains.map(domain => {domains.map(domain =>
<DomainContainer key={domain} domain={domain} /> <DomainContainer key={domain} domain={domain} />

View file

@ -93,6 +93,7 @@ class Favourites extends ImmutablePureComponent {
isLoading={isLoading} isLoading={isLoading}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -27,6 +27,7 @@ class Favourites extends ImmutablePureComponent {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -51,7 +52,7 @@ class Favourites extends ImmutablePureComponent {
} }
render () { render () {
const { intl, accountIds } = this.props; const { intl, accountIds, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -74,6 +75,7 @@ class Favourites extends ImmutablePureComponent {
<ScrollableList <ScrollableList
scrollKey='favourites' scrollKey='favourites'
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />

View file

@ -31,6 +31,7 @@ class FollowRequests extends ImmutablePureComponent {
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -42,7 +43,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, accountIds, hasMore } = this.props; const { intl, accountIds, hasMore, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -63,6 +64,7 @@ class FollowRequests extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountAuthorizeContainer key={id} id={id} /> <AccountAuthorizeContainer key={id} id={id} />

View file

@ -33,6 +33,7 @@ class Followers extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -70,7 +71,7 @@ class Followers extends ImmutablePureComponent {
} }
render () { render () {
const { accountIds, hasMore, isAccount } = this.props; const { accountIds, hasMore, isAccount, multiColumn } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -101,6 +102,7 @@ class Followers extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />} prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />

View file

@ -33,6 +33,7 @@ class Following extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -70,7 +71,7 @@ class Following extends ImmutablePureComponent {
} }
render () { render () {
const { accountIds, hasMore, isAccount } = this.props; const { accountIds, hasMore, isAccount, multiColumn } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -101,6 +102,7 @@ class Following extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />} prepend={<HeaderContainer accountId={this.props.params.accountId} hideTabs />}
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />

View file

@ -155,6 +155,7 @@ class HashtagTimeline extends React.PureComponent {
timelineId={`hashtag:${id}`} timelineId={`hashtag:${id}`}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -117,6 +117,7 @@ class HomeTimeline extends React.PureComponent {
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
timelineId='home' timelineId='home'
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Visit {public} or use search to get started and meet other users.' values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -212,6 +212,7 @@ class ListTimeline extends React.PureComponent {
timelineId={`list:${id}`} timelineId={`list:${id}`}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />} emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet.' />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -40,6 +40,7 @@ class Lists extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
lists: ImmutablePropTypes.list, lists: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -47,7 +48,7 @@ class Lists extends ImmutablePureComponent {
} }
render () { render () {
const { intl, lists } = this.props; const { intl, lists, multiColumn } = this.props;
if (!lists) { if (!lists) {
return ( return (
@ -69,6 +70,7 @@ class Lists extends ImmutablePureComponent {
<ScrollableList <ScrollableList
scrollKey='lists' scrollKey='lists'
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{lists.map(list => {lists.map(list =>
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} /> <ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />

View file

@ -31,6 +31,7 @@ class Mutes extends ImmutablePureComponent {
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -42,7 +43,7 @@ class Mutes extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, accountIds, hasMore } = this.props; const { intl, accountIds, hasMore, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -62,6 +63,7 @@ class Mutes extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} /> <AccountContainer key={id} id={id} />

View file

@ -226,6 +226,7 @@ class Notifications extends React.PureComponent {
onScrollToTop={this.handleScrollToTop} onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll} onScroll={this.handleScroll}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
> >
{scrollableContent} {scrollableContent}
</ScrollableList> </ScrollableList>

View file

@ -27,6 +27,7 @@ class PinnedStatuses extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list.isRequired, statusIds: ImmutablePropTypes.list.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool.isRequired, hasMore: PropTypes.bool.isRequired,
multiColumn: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -42,7 +43,7 @@ class PinnedStatuses extends ImmutablePureComponent {
} }
render () { render () {
const { intl, statusIds, hasMore } = this.props; const { intl, statusIds, hasMore, multiColumn } = this.props;
return ( return (
<Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}> <Column icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
@ -51,6 +52,7 @@ class PinnedStatuses extends ImmutablePureComponent {
statusIds={statusIds} statusIds={statusIds}
scrollKey='pinned_statuses' scrollKey='pinned_statuses'
hasMore={hasMore} hasMore={hasMore}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -124,6 +124,7 @@ class PublicTimeline extends React.PureComponent {
trackScroll={!pinned} trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`} scrollKey={`public_timeline-${columnId}`}
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />} emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
bindToDocument={!multiColumn}
/> />
</Column> </Column>
); );

View file

@ -27,6 +27,7 @@ class Reblogs extends ImmutablePureComponent {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -51,7 +52,7 @@ class Reblogs extends ImmutablePureComponent {
} }
render () { render () {
const { intl, accountIds } = this.props; const { intl, accountIds, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -75,6 +76,7 @@ class Reblogs extends ImmutablePureComponent {
<ScrollableList <ScrollableList
scrollKey='reblogs' scrollKey='reblogs'
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />

View file

@ -46,6 +46,28 @@ const MODAL_COMPONENTS = {
'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor, 'PINNED_ACCOUNTS_EDITOR': PinnedAccountsEditor,
}; };
let cachedScrollbarWidth = null;
export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth;
}
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.overflow = 'scroll';
document.body.appendChild(outer);
const inner = document.createElement('div');
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
cachedScrollbarWidth = scrollbarWidth;
outer.parentNode.removeChild(outer);
return scrollbarWidth;
};
export default class ModalRoot extends React.PureComponent { export default class ModalRoot extends React.PureComponent {
static propTypes = { static propTypes = {
@ -61,8 +83,10 @@ export default class ModalRoot extends React.PureComponent {
componentDidUpdate (prevProps, prevState, { visible }) { componentDidUpdate (prevProps, prevState, { visible }) {
if (visible) { if (visible) {
document.body.classList.add('with-modals--active'); document.body.classList.add('with-modals--active');
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
} else { } else {
document.body.classList.remove('with-modals--active'); document.body.classList.remove('with-modals--active');
document.documentElement.style.marginRight = 0;
} }
} }

View file

@ -129,12 +129,25 @@ class SwitchingColumnsArea extends React.PureComponent {
componentWillMount () { componentWillMount () {
window.addEventListener('resize', this.handleResize, { passive: true }); window.addEventListener('resize', this.handleResize, { passive: true });
if (this.state.mobile) {
document.body.classList.toggle('layout-single-column', true);
document.body.classList.toggle('layout-multiple-columns', false);
} else {
document.body.classList.toggle('layout-single-column', false);
document.body.classList.toggle('layout-multiple-columns', true);
}
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps, prevState) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) { if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.node.handleChildrenContentChange(); this.node.handleChildrenContentChange();
} }
if (prevState.mobile !== this.state.mobile) {
document.body.classList.toggle('layout-single-column', this.state.mobile);
document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
}
} }
componentWillUnmount () { componentWillUnmount () {

View file

@ -94,14 +94,6 @@ function main() {
new Rellax('.parallax', { speed: -1 }); new Rellax('.parallax', { speed: -1 });
} }
if (document.body.classList.contains('with-modals')) {
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
const scrollbarWidthStyle = document.createElement('style');
scrollbarWidthStyle.id = 'scrollbar-width';
document.head.appendChild(scrollbarWidthStyle);
scrollbarWidthStyle.sheet.insertRule(`body.with-modals--active { margin-right: ${scrollbarWidth}px; }`, 0);
}
delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original')); delegate(document, '.custom-emoji', 'mouseover', getEmojiAnimationHandler('data-original'));
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));

View file

@ -7,7 +7,7 @@
body { body {
font-family: $font-sans-serif, sans-serif; font-family: $font-sans-serif, sans-serif;
background: darken($ui-base-color, 8%); background: darken($ui-base-color, 7%);
font-size: 13px; font-size: 13px;
line-height: 18px; line-height: 18px;
font-weight: 400; font-weight: 400;
@ -34,11 +34,19 @@ body {
} }
&.app-body { &.app-body {
position: absolute;
width: 100%;
height: 100%;
padding: 0; padding: 0;
background: $ui-base-color;
&.layout-single-column {
height: auto;
min-height: 100%;
overflow-y: scroll;
}
&.layout-multiple-columns {
position: absolute;
width: 100%;
height: 100%;
}
&.with-modals--active { &.with-modals--active {
overflow-y: hidden; overflow-y: hidden;
@ -55,7 +63,6 @@ body {
&--active { &--active {
overflow-y: hidden; overflow-y: hidden;
margin-right: 13px;
} }
} }
@ -124,7 +131,6 @@ button {
& > div { & > div {
display: flex; display: flex;
width: 100%; width: 100%;
height: 100%;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
outline: 0 !important; outline: 0 !important;

View file

@ -17,6 +17,7 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 100vh;
&__pane { &__pane {
height: 100%; height: 100%;
@ -30,6 +31,7 @@
} }
&__inner { &__inner {
position: fixed;
width: 285px; width: 285px;
pointer-events: auto; pointer-events: auto;
height: 100%; height: 100%;
@ -83,7 +85,6 @@
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: darken($ui-base-color, 7%);
} }
.column { .column {

View file

@ -127,6 +127,10 @@
top: 15px; top: 15px;
} }
.scrollable {
overflow: visible;
}
@media screen and (min-width: $no-gap-breakpoint) { @media screen and (min-width: $no-gap-breakpoint) {
padding: 10px 0; padding: 10px 0;
} }