Merge pull request #1229 from ThibG/glitch-soc/features/upstream-scroll-behavior
Merge upstream changes to scroll behavior in single column
This commit is contained in:
commit
10238768b4
41 changed files with 303 additions and 87 deletions
|
@ -10,10 +10,11 @@ export default class Column extends React.PureComponent {
|
||||||
extraClasses: PropTypes.string,
|
extraClasses: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
|
bindToDocument: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
scrollTop () {
|
scrollTop () {
|
||||||
const scrollable = this.node.querySelector('.scrollable');
|
const scrollable = this.props.bindToDocument ? document.scrollingElement : this.node.querySelector('.scrollable');
|
||||||
|
|
||||||
if (!scrollable) {
|
if (!scrollable) {
|
||||||
return;
|
return;
|
||||||
|
@ -35,11 +36,19 @@ export default class Column extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
if (this.props.bindToDocument) {
|
||||||
|
document.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
} else {
|
||||||
|
this.node.addEventListener('wheel', this.handleWheel, detectPassiveEvents.hasSupport ? { passive: true } : false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.node.removeEventListener('wheel', this.handleWheel);
|
if (this.props.bindToDocument) {
|
||||||
|
document.removeEventListener('wheel', this.handleWheel);
|
||||||
|
} else {
|
||||||
|
this.node.removeEventListener('wheel', this.handleWheel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
export default class ColumnBackButton extends React.PureComponent {
|
export default class ColumnBackButton extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -9,6 +10,10 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
handleClick = (event) => {
|
handleClick = (event) => {
|
||||||
// if history is exhausted, or we would leave mastodon, just go to root.
|
// if history is exhausted, or we would leave mastodon, just go to root.
|
||||||
if (window.history.state) {
|
if (window.history.state) {
|
||||||
|
@ -24,12 +29,20 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
const { multiColumn } = this.props;
|
||||||
|
|
||||||
|
const component = (
|
||||||
<button onClick={this.handleClick} className='column-back-button'>
|
<button onClick={this.handleClick} className='column-back-button'>
|
||||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (multiColumn) {
|
||||||
|
return component;
|
||||||
|
} else {
|
||||||
|
return createPortal(component, document.getElementById('tabs-bar__portal'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
@ -36,6 +37,7 @@ class ColumnHeader extends React.PureComponent {
|
||||||
onEnterCleaningMode: PropTypes.func,
|
onEnterCleaningMode: PropTypes.func,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
pinned: PropTypes.bool,
|
pinned: PropTypes.bool,
|
||||||
|
placeholder: PropTypes.bool,
|
||||||
onPin: PropTypes.func,
|
onPin: PropTypes.func,
|
||||||
onMove: PropTypes.func,
|
onMove: PropTypes.func,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
@ -104,7 +106,7 @@ class ColumnHeader extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive } = this.props;
|
const { intl, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, notifCleaning, notifCleaningActive, placeholder } = this.props;
|
||||||
const { collapsed, animating, animatingNCD } = this.state;
|
const { collapsed, animating, animatingNCD } = this.state;
|
||||||
|
|
||||||
let title = this.props.title;
|
let title = this.props.title;
|
||||||
|
@ -157,7 +159,7 @@ class ColumnHeader extends React.PureComponent {
|
||||||
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
|
<button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (multiColumn) {
|
} else if (multiColumn && this.props.onPin) {
|
||||||
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,13 +181,13 @@ class ColumnHeader extends React.PureComponent {
|
||||||
collapsedContent.push(pinButton);
|
collapsedContent.push(pinButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children || multiColumn) {
|
if (children || (multiColumn && this.props.onPin)) {
|
||||||
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
|
collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasTitle = icon && title;
|
const hasTitle = icon && title;
|
||||||
|
|
||||||
return (
|
const component = (
|
||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<h1 className={buttonClassName}>
|
<h1 className={buttonClassName}>
|
||||||
{hasTitle && (
|
{hasTitle && (
|
||||||
|
@ -229,6 +231,12 @@ class ColumnHeader extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (multiColumn || placeholder) {
|
||||||
|
return component;
|
||||||
|
} else {
|
||||||
|
return createPortal(component, document.getElementById('tabs-bar__portal'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -49,6 +49,10 @@ export default class Mastodon extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldUpdateScroll (_, { location }) {
|
||||||
|
return !(location.state && location.state.mastodonModalOpen);
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { locale } = this.props;
|
const { locale } = this.props;
|
||||||
|
|
||||||
|
@ -57,7 +61,7 @@ export default class Mastodon extends React.PureComponent {
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<BrowserRouter basename='/web'>
|
<BrowserRouter basename='/web'>
|
||||||
<ScrollContext>
|
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||||
<Route path='/' component={UI} />
|
<Route path='/' component={UI} />
|
||||||
</ScrollContext>
|
</ScrollContext>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
|
|
@ -2,16 +2,17 @@ import React, { PureComponent, Fragment } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
|
import { List as ImmutableList, fromJS } from 'immutable';
|
||||||
import { getLocale } from 'mastodon/locales';
|
import { getLocale } from 'mastodon/locales';
|
||||||
|
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
|
||||||
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
import MediaGallery from 'flavours/glitch/components/media_gallery';
|
||||||
import Video from 'flavours/glitch/features/video';
|
|
||||||
import Card from 'flavours/glitch/features/status/components/card';
|
|
||||||
import Poll from 'flavours/glitch/components/poll';
|
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 ModalRoot from 'flavours/glitch/components/modal_root';
|
import ModalRoot from 'flavours/glitch/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 Video from 'flavours/glitch/features/video';
|
||||||
|
import Card from 'flavours/glitch/features/status/components/card';
|
||||||
|
import Audio from 'flavours/glitch/features/audio';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,12 @@ class ProfileColumnHeader extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { onClick, intl } = this.props;
|
const { onClick, intl, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
|
@ -24,6 +25,7 @@ class ProfileColumnHeader extends React.PureComponent {
|
||||||
title={intl.formatMessage(messages.profile)}
|
title={intl.formatMessage(messages.profile)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
showBackButton
|
showBackButton
|
||||||
|
multiColumn={multiColumn}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
isAccount: PropTypes.bool,
|
isAccount: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -130,7 +131,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { attachments, isLoading, hasMore, isAccount } = this.props;
|
const { attachments, isLoading, hasMore, isAccount, multiColumn } = this.props;
|
||||||
const { width } = this.state;
|
const { width } = this.state;
|
||||||
|
|
||||||
if (!isAccount) {
|
if (!isAccount) {
|
||||||
|
@ -157,7 +158,7 @@ class AccountGallery extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setColumnRef}>
|
<Column ref={this.setColumnRef}>
|
||||||
<ProfileColumnHeader onClick={this.handleHeaderClick} />
|
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}>
|
<ScrollContainer scrollKey='account_gallery' shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||||
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
<div className='scrollable scrollable--flex' onScroll={this.handleScroll}>
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -96,7 +97,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='account'>
|
<Column ref={this.setRef} name='account'>
|
||||||
<ProfileColumnHeader onClick={this.handleHeaderClick} />
|
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||||
|
|
||||||
<StatusList
|
<StatusList
|
||||||
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
prepend={<HeaderContainer accountId={this.props.params.accountId} />}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -55,13 +56,14 @@ class Blocks extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column name='blocks' icon='ban' heading={intl.formatMessage(messages.heading)}>
|
<Column name='blocks' bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='blocks'
|
scrollKey='blocks'
|
||||||
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} />
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Bookmarks extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='bookmarks'>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} name='bookmarks'>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='bookmark'
|
icon='bookmark'
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
@ -93,6 +93,7 @@ class Bookmarks extends ImmutablePureComponent {
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,7 +105,7 @@ class CommunityTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}>
|
<Column ref={this.setRef} name='local' bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='users'
|
icon='users'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -135,7 +135,7 @@ class DirectTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='envelope'
|
icon='envelope'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -56,13 +57,14 @@ class Blocks extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='domain_blocks'
|
scrollKey='domain_blocks'
|
||||||
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} />
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite toots yet. When you favourite one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='favourites' label={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} name='favourites' label={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='star'
|
icon='star'
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
||||||
import { fetchFavourites } from 'flavours/glitch/actions/interactions';
|
import { fetchFavourites } from 'flavours/glitch/actions/interactions';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
@ -13,6 +14,7 @@ import ScrollableList from '../../components/scrollable_list';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.favourited_by', defaultMessage: 'Favourited by' },
|
heading: { id: 'column.favourited_by', defaultMessage: 'Favourited by' },
|
||||||
|
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
|
@ -27,6 +29,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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,8 +53,12 @@ class Favourites extends ImmutablePureComponent {
|
||||||
this.column = c;
|
this.column = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRefresh = () => {
|
||||||
|
this.props.dispatch(fetchFavourites(this.props.params.statusId));
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, accountIds } = this.props;
|
const { intl, accountIds, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -70,10 +77,15 @@ class Favourites extends ImmutablePureComponent {
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
onClick={this.handleHeaderClick}
|
onClick={this.handleHeaderClick}
|
||||||
showBackButton
|
showBackButton
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
extraButton={(
|
||||||
|
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<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} />
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -55,7 +56,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column name='follow-requests' icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} name='follow-requests' icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
|
@ -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} />
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -92,7 +93,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef}>
|
||||||
<ProfileColumnHeader onClick={this.handleHeaderClick} />
|
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='followers'
|
scrollKey='followers'
|
||||||
|
@ -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} />
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -92,7 +93,7 @@ class Following extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Column ref={this.setRef}>
|
||||||
<ProfileColumnHeader onClick={this.handleHeaderClick} />
|
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||||
|
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='following'
|
scrollKey='following'
|
||||||
|
@ -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} />
|
||||||
|
|
|
@ -166,7 +166,7 @@ const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2);
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
<Column bindToDocument={!multiColumn} name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||||
<div className='scrollable optionally-scrollable'>
|
<div className='scrollable optionally-scrollable'>
|
||||||
<div className='getting-started__wrapper'>
|
<div className='getting-started__wrapper'>
|
||||||
{!multiColumn && <NavigationBar account={myAccount} />}
|
{!multiColumn && <NavigationBar account={myAccount} />}
|
||||||
|
|
|
@ -145,6 +145,7 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
pinned={pinned}
|
pinned={pinned}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
showBackButton
|
showBackButton
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{columnId && <ColumnSettingsContainer columnId={columnId} />}
|
{columnId && <ColumnSettingsContainer columnId={columnId} />}
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
@ -155,6 +156,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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -97,7 +97,7 @@ class HomeTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='home'
|
icon='home'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -25,10 +25,10 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, collapseEnabled } = this.props;
|
const { intl, collapseEnabled, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='question' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -174,6 +174,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
onClick={this.handleHeaderClick}
|
onClick={this.handleHeaderClick}
|
||||||
pinned={pinned}
|
pinned={pinned}
|
||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
<div className='column-header__links'>
|
<div className='column-header__links'>
|
||||||
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
|
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
|
||||||
|
@ -212,6 +213,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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -60,7 +61,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='bars' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} icon='bars' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
|
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
@ -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')} />
|
||||||
|
|
|
@ -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 (
|
||||||
|
@ -55,13 +56,14 @@ class Mutes extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column name='mutes' icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} name='mutes' icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='mutes'
|
scrollKey='mutes'
|
||||||
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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
@ -233,6 +234,7 @@ class Notifications extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column
|
<Column
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
ref={this.setColumnRef}
|
ref={this.setColumnRef}
|
||||||
name='notifications'
|
name='notifications'
|
||||||
extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
|
extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
|
||||||
|
|
|
@ -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,15 +43,16 @@ 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 bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}>
|
||||||
<ColumnBackButtonSlim />
|
<ColumnBackButtonSlim />
|
||||||
<StatusList
|
<StatusList
|
||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
scrollKey='pinned_statuses'
|
scrollKey='pinned_statuses'
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
|
bindToDocument={!multiColumn}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -104,7 +104,7 @@ class PublicTimeline extends React.PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='globe'
|
icon='globe'
|
||||||
active={hasUnread}
|
active={hasUnread}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
||||||
import { fetchReblogs } from 'flavours/glitch/actions/interactions';
|
import { fetchReblogs } from 'flavours/glitch/actions/interactions';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
@ -13,6 +14,7 @@ import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.reblogged_by', defaultMessage: 'Boosted by' },
|
heading: { id: 'column.reblogged_by', defaultMessage: 'Boosted by' },
|
||||||
|
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
|
@ -27,6 +29,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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,8 +53,12 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
this.column = c;
|
this.column = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleRefresh = () => {
|
||||||
|
this.props.dispatch(fetchReblogs(this.props.params.statusId));
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, accountIds } = this.props;
|
const { intl, accountIds, multiColumn } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -70,11 +77,16 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={intl.formatMessage(messages.heading)}
|
||||||
onClick={this.handleHeaderClick}
|
onClick={this.handleHeaderClick}
|
||||||
showBackButton
|
showBackButton
|
||||||
|
multiColumn={multiColumn}
|
||||||
|
extraButton={(
|
||||||
|
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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} />
|
||||||
|
|
|
@ -155,6 +155,7 @@ class Status extends ImmutablePureComponent {
|
||||||
descendantsIds: ImmutablePropTypes.list,
|
descendantsIds: ImmutablePropTypes.list,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
askReplyConfirmation: PropTypes.bool,
|
askReplyConfirmation: PropTypes.bool,
|
||||||
|
multiColumn: PropTypes.bool,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -497,13 +498,13 @@ class Status extends ImmutablePureComponent {
|
||||||
render () {
|
render () {
|
||||||
let ancestors, descendants;
|
let ancestors, descendants;
|
||||||
const { setExpansion } = this;
|
const { setExpansion } = this;
|
||||||
const { status, settings, ancestorsIds, descendantsIds, intl, domain } = this.props;
|
const { status, settings, ancestorsIds, descendantsIds, intl, domain, multiColumn } = this.props;
|
||||||
const { fullscreen, isExpanded } = this.state;
|
const { fullscreen, isExpanded } = this.state;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton multiColumn={multiColumn} />
|
||||||
<MissingIndicator />
|
<MissingIndicator />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
@ -531,12 +532,13 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setColumnRef} label={intl.formatMessage(messages.detailedStatus)}>
|
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.detailedStatus)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='comment'
|
icon='comment'
|
||||||
title={intl.formatMessage(messages.tootHeading)}
|
title={intl.formatMessage(messages.tootHeading)}
|
||||||
onClick={this.handleHeaderClick}
|
onClick={this.handleHeaderClick}
|
||||||
showBackButton
|
showBackButton
|
||||||
|
multiColumn={multiColumn}
|
||||||
extraButton={(
|
extraButton={(
|
||||||
<button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
<button className='column-header__button' title={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(!isExpanded ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={!isExpanded ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default class ColumnLoading extends ImmutablePureComponent {
|
||||||
let { title, icon } = this.props;
|
let { title, icon } = this.props;
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
<ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} />
|
<ColumnHeader icon={icon} title={title} multiColumn={false} focusable={false} placeholder />
|
||||||
<div className='scrollable' />
|
<div className='scrollable' />
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Base from '../../../components/modal_root';
|
import { getScrollbarWidth } from 'flavours/glitch/util/scrollbar';
|
||||||
|
import Base from 'flavours/glitch/components/modal_root';
|
||||||
import BundleContainer from '../containers/bundle_container';
|
import BundleContainer from '../containers/bundle_container';
|
||||||
import BundleModalError from './bundle_modal_error';
|
import BundleModalError from './bundle_modal_error';
|
||||||
import ModalLoading from './modal_loading';
|
import ModalLoading from './modal_loading';
|
||||||
|
@ -61,8 +62,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,9 +73,13 @@ class TabsBar extends React.PureComponent {
|
||||||
const { intl: { formatMessage } } = this.props;
|
const { intl: { formatMessage } } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className='tabs-bar' ref={this.setRef}>
|
<div className='tabs-bar__wrapper'>
|
||||||
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
|
<nav className='tabs-bar' ref={this.setRef}>
|
||||||
</nav>
|
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div id='tabs-bar__portal' />
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
||||||
|
|
|
@ -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: 100vh;
|
||||||
|
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;
|
||||||
|
|
|
@ -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%;
|
||||||
|
@ -24,12 +25,14 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
min-width: 285px;
|
||||||
|
|
||||||
&--start {
|
&--start {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__inner {
|
&__inner {
|
||||||
|
position: fixed;
|
||||||
width: 285px;
|
width: 285px;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -40,6 +43,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
@ -50,6 +54,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs-bar__wrapper {
|
||||||
|
background: darken($ui-base-color, 8%);
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-bar {
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.react-swipeable-view-container {
|
.react-swipeable-view-container {
|
||||||
&,
|
&,
|
||||||
.columns-area,
|
.columns-area,
|
||||||
|
@ -83,7 +107,6 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: darken($ui-base-color, 7%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
@ -91,6 +114,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-back-button {
|
.column-back-button {
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
background: lighten($ui-base-color, 4%);
|
background: lighten($ui-base-color, 4%);
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
|
@ -1415,6 +1415,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-badge-wrapper {
|
.icon-badge-wrapper {
|
||||||
|
|
|
@ -127,8 +127,17 @@
|
||||||
top: 15px;
|
top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scrollable {
|
||||||
|
overflow: visible;
|
||||||
|
|
||||||
|
@supports(display: grid) {
|
||||||
|
contain: content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: $no-gap-breakpoint) {
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 630px) {
|
@media screen and (min-width: 630px) {
|
||||||
|
@ -217,13 +226,11 @@
|
||||||
|
|
||||||
@media screen and (min-width: $no-gap-breakpoint) {
|
@media screen and (min-width: $no-gap-breakpoint) {
|
||||||
.tabs-bar {
|
.tabs-bar {
|
||||||
margin: 10px auto;
|
|
||||||
margin-bottom: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.react-swipeable-view-container .columns-area--mobile {
|
.react-swipeable-view-container .columns-area--mobile {
|
||||||
height: calc(100% - 20px) !important;
|
height: calc(100% - 10px) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.getting-started__wrapper,
|
.getting-started__wrapper,
|
||||||
|
|
34
app/javascript/flavours/glitch/util/scrollbar.js
Normal file
34
app/javascript/flavours/glitch/util/scrollbar.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
/** @type {number | null} */
|
||||||
|
let cachedScrollbarWidth = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
const getActualScrollbarWidth = () => {
|
||||||
|
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;
|
||||||
|
outer.parentNode.removeChild(outer);
|
||||||
|
|
||||||
|
return scrollbarWidth;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {number}
|
||||||
|
*/
|
||||||
|
export const getScrollbarWidth = () => {
|
||||||
|
if (cachedScrollbarWidth !== null) {
|
||||||
|
return cachedScrollbarWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollbarWidth = getActualScrollbarWidth();
|
||||||
|
cachedScrollbarWidth = scrollbarWidth;
|
||||||
|
|
||||||
|
return scrollbarWidth;
|
||||||
|
};
|
Loading…
Reference in a new issue