forked from treehouse/mastodon
parent
4fa6472523
commit
aa22b38fdb
|
@ -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,10 +83,15 @@ 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;
|
||||||
|
|
||||||
|
if (this.props.bindToDocument) {
|
||||||
|
document.scrollingElement.scrollTop = newScrollTop;
|
||||||
|
} else {
|
||||||
this.node.scrollTop = newScrollTop;
|
this.node.scrollTop = newScrollTop;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
clearMouseIdleTimer = () => {
|
clearMouseIdleTimer = () => {
|
||||||
|
@ -100,7 +108,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
this.clearMouseIdleTimer();
|
this.clearMouseIdleTimer();
|
||||||
this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY);
|
this.mouseIdleTimer = 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;
|
||||||
}
|
}
|
||||||
|
@ -135,15 +143,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);
|
||||||
}
|
}
|
||||||
|
@ -153,8 +173,8 @@ export default class ScrollableList extends PureComponent {
|
||||||
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
|
||||||
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
|
||||||
|
|
||||||
if (someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) {
|
if (someItemInserted && (this.getScrollTop() > 0 || this.mouseMovedRecently)) {
|
||||||
return this.node.scrollHeight - this.node.scrollTop;
|
return this.getScrollHeight() - this.getScrollTop();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -164,7 +184,7 @@ export default class ScrollableList extends PureComponent {
|
||||||
// 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) {
|
if (snapshot !== null) {
|
||||||
this.setScrollTop(this.node.scrollHeight - snapshot);
|
this.setScrollTop(this.getScrollHeight() - snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,14 +217,24 @@ export default class ScrollableList extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachScrollListener () {
|
attachScrollListener () {
|
||||||
|
if (this.props.bindToDocument) {
|
||||||
|
document.addEventListener('scroll', this.handleScroll);
|
||||||
|
document.addEventListener('wheel', this.handleWheel);
|
||||||
|
} else {
|
||||||
this.node.addEventListener('scroll', this.handleScroll);
|
this.node.addEventListener('scroll', this.handleScroll);
|
||||||
this.node.addEventListener('wheel', this.handleWheel);
|
this.node.addEventListener('wheel', this.handleWheel);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
detachScrollListener () {
|
detachScrollListener () {
|
||||||
|
if (this.props.bindToDocument) {
|
||||||
|
document.removeEventListener('scroll', this.handleScroll);
|
||||||
|
document.removeEventListener('wheel', this.handleWheel);
|
||||||
|
} else {
|
||||||
this.node.removeEventListener('scroll', this.handleScroll);
|
this.node.removeEventListener('scroll', this.handleScroll);
|
||||||
this.node.removeEventListener('wheel', this.handleWheel);
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getFirstChildKey (props) {
|
getFirstChildKey (props) {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import Video from '../features/video';
|
||||||
import Card from '../features/status/components/card';
|
import Card from '../features/status/components/card';
|
||||||
import Poll from 'mastodon/components/poll';
|
import Poll from 'mastodon/components/poll';
|
||||||
import ModalRoot from '../components/modal_root';
|
import ModalRoot from '../components/modal_root';
|
||||||
|
import { getScrollbarWidth } from '../features/ui/components/modal_root';
|
||||||
import MediaModal from '../features/ui/components/media_modal';
|
import MediaModal from '../features/ui/components/media_modal';
|
||||||
import { List as ImmutableList, fromJS } from 'immutable';
|
import { List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
@ -31,6 +32,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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +41,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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
withReplies: PropTypes.bool,
|
withReplies: PropTypes.bool,
|
||||||
blockedBy: PropTypes.bool,
|
blockedBy: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -77,7 +78,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount } = this.props;
|
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, isAccount, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
|
@ -112,6 +113,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,6 +32,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 () {
|
||||||
|
@ -43,7 +44,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, accountIds, shouldUpdateScroll, hasMore } = this.props;
|
const { intl, accountIds, shouldUpdateScroll, hasMore, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -64,6 +65,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />
|
<AccountContainer key={id} id={id} />
|
||||||
|
|
|
@ -126,6 +126,7 @@ class CommunityTimeline extends React.PureComponent {
|
||||||
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!' />}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -33,6 +33,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
domains: ImmutablePropTypes.orderedSet,
|
domains: ImmutablePropTypes.orderedSet,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -44,7 +45,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, domains, shouldUpdateScroll, hasMore } = this.props;
|
const { intl, domains, shouldUpdateScroll, hasMore, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!domains) {
|
if (!domains) {
|
||||||
return (
|
return (
|
||||||
|
@ -65,6 +66,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{domains.map(domain =>
|
{domains.map(domain =>
|
||||||
<DomainContainer key={domain} domain={domain} />
|
<DomainContainer key={domain} domain={domain} />
|
||||||
|
|
|
@ -95,6 +95,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
shouldUpdateScroll: PropTypes.func,
|
shouldUpdateScroll: PropTypes.func,
|
||||||
accountIds: ImmutablePropTypes.list,
|
accountIds: ImmutablePropTypes.list,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -36,7 +37,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { shouldUpdateScroll, accountIds } = this.props;
|
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -56,6 +57,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
scrollKey='favourites'
|
scrollKey='favourites'
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
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} />
|
||||||
|
|
|
@ -32,6 +32,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 () {
|
||||||
|
@ -43,7 +44,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, shouldUpdateScroll, accountIds, hasMore } = this.props;
|
const { intl, shouldUpdateScroll, accountIds, hasMore, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -64,6 +65,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountAuthorizeContainer key={id} id={id} />
|
<AccountAuthorizeContainer key={id} id={id} />
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
blockedBy: PropTypes.bool,
|
blockedBy: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -55,7 +56,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
|
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
|
@ -87,6 +88,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}
|
||||||
>
|
>
|
||||||
{blockedBy ? [] : accountIds.map(id =>
|
{blockedBy ? [] : accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Following extends ImmutablePureComponent {
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
blockedBy: PropTypes.bool,
|
blockedBy: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -55,7 +56,7 @@ class Following extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount } = this.props;
|
const { shouldUpdateScroll, accountIds, hasMore, blockedBy, isAccount, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
return (
|
return (
|
||||||
|
@ -87,6 +88,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}
|
||||||
>
|
>
|
||||||
{blockedBy ? [] : accountIds.map(id =>
|
{blockedBy ? [] : accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />
|
||||||
|
|
|
@ -157,6 +157,7 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
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.' />}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -119,6 +119,7 @@ class HomeTimeline extends React.PureComponent {
|
||||||
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> }} />}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -184,6 +184,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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, shouldUpdateScroll, lists } = this.props;
|
const { intl, shouldUpdateScroll, lists, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!lists) {
|
if (!lists) {
|
||||||
return (
|
return (
|
||||||
|
@ -70,6 +71,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
|
prepend={<ColumnSubheading text={intl.formatMessage(messages.subheading)} />}
|
||||||
|
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')} />
|
||||||
|
|
|
@ -32,6 +32,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 () {
|
||||||
|
@ -43,7 +44,7 @@ class Mutes extends ImmutablePureComponent {
|
||||||
}, 300, { leading: true });
|
}, 300, { leading: true });
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, shouldUpdateScroll, hasMore, accountIds } = this.props;
|
const { intl, shouldUpdateScroll, hasMore, accountIds, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -64,6 +65,7 @@ class Mutes extends ImmutablePureComponent {
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />
|
<AccountContainer key={id} id={id} />
|
||||||
|
|
|
@ -191,6 +191,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>
|
||||||
|
|
|
@ -28,6 +28,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 () {
|
||||||
|
@ -43,7 +44,7 @@ class PinnedStatuses extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, shouldUpdateScroll, statusIds, hasMore } = this.props;
|
const { intl, shouldUpdateScroll, 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}>
|
||||||
|
@ -53,6 +54,7 @@ class PinnedStatuses extends ImmutablePureComponent {
|
||||||
scrollKey='pinned_statuses'
|
scrollKey='pinned_statuses'
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -126,6 +126,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
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' />}
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
shouldUpdateScroll: PropTypes.func,
|
shouldUpdateScroll: PropTypes.func,
|
||||||
accountIds: ImmutablePropTypes.list,
|
accountIds: ImmutablePropTypes.list,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
|
@ -36,7 +37,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { shouldUpdateScroll, accountIds } = this.props;
|
const { shouldUpdateScroll, accountIds, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -56,6 +57,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
scrollKey='reblogs'
|
scrollKey='reblogs'
|
||||||
shouldUpdateScroll={shouldUpdateScroll}
|
shouldUpdateScroll={shouldUpdateScroll}
|
||||||
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} />
|
||||||
|
|
|
@ -32,6 +32,28 @@ const MODAL_COMPONENTS = {
|
||||||
'LIST_ADDER':ListAdder,
|
'LIST_ADDER':ListAdder,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
@ -47,8 +69,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,12 +110,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 || forceSingleColumn) {
|
||||||
|
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 && !forceSingleColumn) {
|
||||||
|
document.body.classList.toggle('layout-single-column', this.state.mobile);
|
||||||
|
document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
|
|
@ -108,14 +108,6 @@ function main() {
|
||||||
if (parallaxComponents.length > 0 ) {
|
if (parallaxComponents.length > 0 ) {
|
||||||
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, '.webapp-btn', 'click', ({ target, button }) => {
|
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
|
||||||
|
|
|
@ -8,7 +8,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;
|
||||||
|
@ -35,11 +35,19 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.app-body {
|
&.app-body {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&.layout-single-column {
|
||||||
|
height: auto;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.layout-multiple-columns {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0;
|
}
|
||||||
background: $ui-base-color;
|
|
||||||
|
|
||||||
&.with-modals--active {
|
&.with-modals--active {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
|
@ -56,7 +64,6 @@ body {
|
||||||
|
|
||||||
&--active {
|
&--active {
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
margin-right: 13px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,9 +141,22 @@ 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.layout-single-column .app-holder {
|
||||||
|
&,
|
||||||
|
& > div {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-multiple-columns .app-holder {
|
||||||
|
&,
|
||||||
|
& > div {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1804,6 +1804,7 @@ a.account__display-name {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
&__pane {
|
&__pane {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1817,6 +1818,7 @@ a.account__display-name {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
|
position: fixed;
|
||||||
width: 285px;
|
width: 285px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1871,7 +1873,6 @@ a.account__display-name {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: darken($ui-base-color, 7%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer {
|
.drawer {
|
||||||
|
@ -2012,6 +2013,10 @@ a.account__display-name {
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue