Merge branch 'next' into 'locked-profile-header'
# Conflicts: # app/soapbox/features/account/components/header.js # app/soapbox/pages/profile_page.js
This commit is contained in:
commit
be5203f406
|
@ -25,7 +25,7 @@ exports[`<TimelineQueueButtonHeader /> renders correctly 1`] = `
|
||||||
|
|
||||||
exports[`<TimelineQueueButtonHeader /> renders correctly 2`] = `
|
exports[`<TimelineQueueButtonHeader /> renders correctly 2`] = `
|
||||||
<div
|
<div
|
||||||
className="timeline-queue-header"
|
className="timeline-queue-header hidden"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
className="timeline-queue-header__btn"
|
className="timeline-queue-header__btn"
|
||||||
|
@ -42,14 +42,18 @@ exports[`<TimelineQueueButtonHeader /> renders correctly 2`] = `
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
Click to see 1 new post
|
<div
|
||||||
|
className="timeline-queue-header__label"
|
||||||
|
>
|
||||||
|
Click to see 1 new post
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`<TimelineQueueButtonHeader /> renders correctly 3`] = `
|
exports[`<TimelineQueueButtonHeader /> renders correctly 3`] = `
|
||||||
<div
|
<div
|
||||||
className="timeline-queue-header"
|
className="timeline-queue-header hidden"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
className="timeline-queue-header__btn"
|
className="timeline-queue-header__btn"
|
||||||
|
@ -66,7 +70,11 @@ exports[`<TimelineQueueButtonHeader /> renders correctly 3`] = `
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
Click to see 9999999 new posts
|
<div
|
||||||
|
className="timeline-queue-header__label"
|
||||||
|
>
|
||||||
|
Click to see 9999999 new posts
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { MediaGallery } from 'soapbox/features/ui/util/async-components';
|
||||||
|
import { openModal } from 'soapbox/actions/modal';
|
||||||
|
import Bundle from 'soapbox/features/ui/components/bundle';
|
||||||
|
|
||||||
|
export default @connect()
|
||||||
|
class AttachmentThumbs extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderLoading() {
|
||||||
|
return <div className='media-gallery--compact' />;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenMedia = (media, index) => {
|
||||||
|
this.props.dispatch(openModal('MEDIA', { media, index }));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { media, onClick } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='attachment-thumbs'>
|
||||||
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoading}>
|
||||||
|
{Component => (
|
||||||
|
<Component
|
||||||
|
media={media}
|
||||||
|
onOpenMedia={this.onOpenMedia}
|
||||||
|
height={50}
|
||||||
|
compact
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Bundle>
|
||||||
|
{onClick && (
|
||||||
|
<div className='attachment-thumbs__clickable-region' onClick={onClick} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,185 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
import { fetchLists } from 'soapbox/actions/lists';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
|
||||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
|
||||||
homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' },
|
|
||||||
allTitle: { id: 'home_column_header.all', defaultMessage: 'All' },
|
|
||||||
fediverseTitle: { id: 'home_column_header.fediverse', defaultMessage: 'Fediverse' },
|
|
||||||
listTitle: { id: 'home_column.lists', defaultMessage: 'Lists' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
|
||||||
if (!lists) {
|
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const instance = state.get('instance');
|
|
||||||
const features = getFeatures(instance);
|
|
||||||
|
|
||||||
return {
|
|
||||||
lists: getOrderedLists(state),
|
|
||||||
siteTitle: state.getIn(['instance', 'title']),
|
|
||||||
federating: features.federating,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ColumnHeader extends React.PureComponent {
|
|
||||||
|
|
||||||
static contextTypes = {
|
|
||||||
router: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
active: PropTypes.bool,
|
|
||||||
children: PropTypes.node,
|
|
||||||
activeItem: PropTypes.string,
|
|
||||||
activeSubItem: PropTypes.string,
|
|
||||||
lists: ImmutablePropTypes.list,
|
|
||||||
siteTitle: PropTypes.string,
|
|
||||||
federating: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
collapsed: true,
|
|
||||||
animating: false,
|
|
||||||
expandedFor: null, //lists, groups, etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatch(fetchLists());
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggleClick = (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
this.setState({ collapsed: !this.state.collapsed, animating: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTransitionEnd = () => {
|
|
||||||
this.setState({ animating: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
expandLists = () => {
|
|
||||||
this.setState({
|
|
||||||
expandedFor: 'lists',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists, siteTitle, federating } = this.props;
|
|
||||||
const { collapsed, animating, expandedFor } = this.state;
|
|
||||||
|
|
||||||
const wrapperClassName = classNames('column-header__wrapper', {
|
|
||||||
'active': active,
|
|
||||||
});
|
|
||||||
|
|
||||||
const buttonClassName = classNames('column-header', {
|
|
||||||
'active': active,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collapsibleClassName = classNames('column-header__collapsible', {
|
|
||||||
'collapsed': collapsed,
|
|
||||||
'animating': animating,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collapsibleButtonClassName = classNames('column-header__button', {
|
|
||||||
'active': !collapsed,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expansionClassName = classNames('column-header column-header__expansion', {
|
|
||||||
'open': expandedFor,
|
|
||||||
});
|
|
||||||
|
|
||||||
let extraContent, collapseButton;
|
|
||||||
|
|
||||||
if (children) {
|
|
||||||
extraContent = (
|
|
||||||
<div key='extra-content' className='column-header__collapsible__extra'>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
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 collapsedContent = [
|
|
||||||
extraContent,
|
|
||||||
];
|
|
||||||
|
|
||||||
let expandedContent = null;
|
|
||||||
if ((expandedFor === 'lists' || activeItem === 'lists') && lists) {
|
|
||||||
expandedContent = lists.map(list =>
|
|
||||||
(<Link
|
|
||||||
key={list.get('id')}
|
|
||||||
to={`/list/${list.get('id')}`}
|
|
||||||
className={
|
|
||||||
classNames('btn btn--sub grouped', {
|
|
||||||
'active': list.get('id') === activeSubItem,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{list.get('title')}
|
|
||||||
</Link>),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={wrapperClassName}>
|
|
||||||
<h1 className={buttonClassName}>
|
|
||||||
<Link to='/' className={classNames('btn grouped', { 'active': 'home' === activeItem })}>
|
|
||||||
<Icon id='home' fixedWidth className='column-header__icon' />
|
|
||||||
{formatMessage(messages.homeTitle)}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to='/timeline/local' className={classNames('btn grouped', { 'active': 'local' === activeItem })}>
|
|
||||||
<Icon id={federating ? 'users' : 'globe-w'} fixedWidth className='column-header__icon' />
|
|
||||||
{federating ? siteTitle : formatMessage(messages.allTitle)}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{federating && <Link to='/timeline/fediverse' className={classNames('btn grouped', { 'active': 'fediverse' === activeItem })}>
|
|
||||||
<Icon id='fediverse' fixedWidth className='column-header__icon' />
|
|
||||||
{formatMessage(messages.fediverseTitle)}
|
|
||||||
</Link>}
|
|
||||||
|
|
||||||
<div className='column-header__buttons'>
|
|
||||||
{collapseButton}
|
|
||||||
</div>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
{
|
|
||||||
expandedContent &&
|
|
||||||
<h1 className={expansionClassName}>
|
|
||||||
{expandedContent}
|
|
||||||
</h1>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
|
|
||||||
<div className='column-header__collapsible-inner'>
|
|
||||||
{(!collapsed || animating) && collapsedContent}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(connect(mapStateToProps)(ColumnHeader));
|
|
|
@ -19,9 +19,9 @@ export default class MaterialStatus extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='material-status'>
|
<div className='material-status' tabIndex={-1}>
|
||||||
<div className='material-status__status'>
|
<div className='material-status__status focusable' tabIndex={0}>
|
||||||
<StatusContainer {...this.props} />
|
<StatusContainer {...this.props} focusable={false} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -279,6 +279,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
visible: PropTypes.bool,
|
visible: PropTypes.bool,
|
||||||
onToggleVisibility: PropTypes.func,
|
onToggleVisibility: PropTypes.func,
|
||||||
displayMedia: PropTypes.string,
|
displayMedia: PropTypes.string,
|
||||||
|
compact: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -560,7 +561,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { media, intl, sensitive } = this.props;
|
const { media, intl, sensitive, compact } = this.props;
|
||||||
const { visible } = this.state;
|
const { visible } = this.state;
|
||||||
const sizeData = this.getSizeData(media.size);
|
const sizeData = this.getSizeData(media.size);
|
||||||
|
|
||||||
|
@ -592,7 +593,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='media-gallery' style={sizeData.get('style')} ref={this.handleRef}>
|
<div className={classNames('media-gallery', { 'media-gallery--compact': compact })} style={sizeData.get('style')} ref={this.handleRef}>
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible })}>
|
||||||
{spoilerButton}
|
{spoilerButton}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import RelativeTimestamp from './relative_timestamp';
|
||||||
import DisplayName from './display_name';
|
import DisplayName from './display_name';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import AttachmentList from './attachment_list';
|
import AttachmentThumbs from './attachment_thumbs';
|
||||||
import Card from '../features/status/components/card';
|
import Card from '../features/status/components/card';
|
||||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
@ -93,6 +93,11 @@ class Status extends ImmutablePureComponent {
|
||||||
group: ImmutablePropTypes.map,
|
group: ImmutablePropTypes.map,
|
||||||
displayMedia: PropTypes.string,
|
displayMedia: PropTypes.string,
|
||||||
allowedEmoji: ImmutablePropTypes.list,
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
|
focusable: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
focusable: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
|
@ -336,7 +341,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={minHandlers}>
|
<HotKeys handlers={minHandlers}>
|
||||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
|
<div className={classNames('status__wrapper', 'status__wrapper--filtered', { focusable: this.props.focusable })} tabIndex={this.props.focusable ? 0 : null} ref={this.handleRef}>
|
||||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
|
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
@ -384,10 +389,7 @@ class Status extends ImmutablePureComponent {
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
if (this.props.muted) {
|
if (this.props.muted) {
|
||||||
media = (
|
media = (
|
||||||
<AttachmentList
|
<AttachmentThumbs media={status.get('media_attachments')} onClick={this.handleClick} />
|
||||||
compact
|
|
||||||
media={status.get('media_attachments')}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
} else if (size === 1 && status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
} else if (size === 1 && status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
const video = status.getIn(['media_attachments', 0]);
|
const video = status.getIn(['media_attachments', 0]);
|
||||||
|
@ -493,7 +495,7 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: this.props.focusable && !this.props.muted })} tabIndex={this.props.focusable && !this.props.muted ? 0 : null} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted, read: unread === false })} data-id={status.get('id')}>
|
||||||
|
|
|
@ -458,7 +458,13 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
emoji={meEmojiReact}
|
emoji={meEmojiReact}
|
||||||
onClick={this.handleLikeButtonClick}
|
onClick={this.handleLikeButtonClick}
|
||||||
/>
|
/>
|
||||||
{emojiReactCount !== 0 && <span className='detailed-status__link'>{emojiReactCount}</span>}
|
{emojiReactCount !== 0 && (
|
||||||
|
(features.exposableReactions && !features.emojiReacts) ? (
|
||||||
|
<Link to={`/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/likes`} className='detailed-status__link'>{emojiReactCount}</Link>
|
||||||
|
) : (
|
||||||
|
<span className='detailed-status__link'>{emojiReactCount}</span>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,6 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDequeueTimeline = () => {
|
handleDequeueTimeline = () => {
|
||||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
||||||
const { onDequeueTimeline, timelineId } = this.props;
|
const { onDequeueTimeline, timelineId } = this.props;
|
||||||
if (!onDequeueTimeline || !timelineId) return;
|
if (!onDequeueTimeline || !timelineId) return;
|
||||||
onDequeueTimeline(timelineId);
|
onDequeueTimeline(timelineId);
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class SubNavigation extends React.PureComponent {
|
export default class SubNavigation extends React.PureComponent {
|
||||||
|
|
||||||
|
@ -13,6 +15,10 @@ export default class SubNavigation extends React.PureComponent {
|
||||||
router: PropTypes.object.isRequired,
|
router: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
scrolled: false,
|
||||||
|
}
|
||||||
|
|
||||||
handleBackClick = () => {
|
handleBackClick = () => {
|
||||||
if (window.history && window.history.length === 1) {
|
if (window.history && window.history.length === 1) {
|
||||||
this.context.router.history.push('/');
|
this.context.router.history.push('/');
|
||||||
|
@ -27,11 +33,44 @@ export default class SubNavigation extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.attachScrollListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.detachScrollListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
attachScrollListener() {
|
||||||
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
detachScrollListener() {
|
||||||
|
window.removeEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll = throttle(() => {
|
||||||
|
if (this.node) {
|
||||||
|
const { top } = this.node.getBoundingClientRect();
|
||||||
|
|
||||||
|
if (top <= 50) {
|
||||||
|
this.setState({ scrolled: true });
|
||||||
|
} else {
|
||||||
|
this.setState({ scrolled: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 150, { trailing: true });
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message } = this.props;
|
const { message } = this.props;
|
||||||
|
const { scrolled } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sub-navigation'>
|
<div className={classNames('sub-navigation', { 'sub-navigation--scrolled': scrolled })} ref={this.setRef}>
|
||||||
<div className='sub-navigation__content'>
|
<div className='sub-navigation__content'>
|
||||||
<button
|
<button
|
||||||
className='sub-navigation__back'
|
className='sub-navigation__back'
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
|
import { throttle } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import SvgIcon from 'soapbox/components/svg_icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
|
|
||||||
export default @injectIntl
|
export default @injectIntl
|
||||||
class TimelineQueueButtonHeader extends React.PureComponent {
|
class TimelineQueueButtonHeader extends React.PureComponent {
|
||||||
|
@ -11,25 +12,87 @@ class TimelineQueueButtonHeader extends React.PureComponent {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
count: PropTypes.number,
|
count: PropTypes.number,
|
||||||
message: PropTypes.object.isRequired,
|
message: PropTypes.object.isRequired,
|
||||||
|
threshold: PropTypes.number,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
autoload: PropTypes.bool,
|
||||||
|
autoloadThreshold: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
count: 0,
|
count: 0,
|
||||||
|
threshold: 400,
|
||||||
|
autoload: true,
|
||||||
|
autoloadThreshold: 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
scrolled: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.attachScrollListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.detachScrollListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
const { scrollTop } = (document.scrollingElement || document.documentElement);
|
||||||
|
const { count, onClick, autoload, autoloadThreshold } = this.props;
|
||||||
|
|
||||||
|
if (autoload && scrollTop <= autoloadThreshold && count !== prevProps.count) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attachScrollListener() {
|
||||||
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
detachScrollListener() {
|
||||||
|
window.removeEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll = throttle(() => {
|
||||||
|
const { scrollTop } = (document.scrollingElement || document.documentElement);
|
||||||
|
const { threshold, onClick, autoload, autoloadThreshold } = this.props;
|
||||||
|
|
||||||
|
if (autoload && scrollTop <= autoloadThreshold) {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scrollTop > threshold) {
|
||||||
|
this.setState({ scrolled: true });
|
||||||
|
} else {
|
||||||
|
this.setState({ scrolled: false });
|
||||||
|
}
|
||||||
|
}, 150, { trailing: true });
|
||||||
|
|
||||||
|
handleClick = e => {
|
||||||
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
|
this.props.onClick(e);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { count, message, onClick, intl } = this.props;
|
const { count, message, intl } = this.props;
|
||||||
|
const { scrolled } = this.state;
|
||||||
|
|
||||||
|
const visible = count > 0 && scrolled;
|
||||||
|
|
||||||
const classes = classNames('timeline-queue-header', {
|
const classes = classNames('timeline-queue-header', {
|
||||||
'hidden': (count <= 0),
|
'hidden': !visible,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<a className='timeline-queue-header__btn' onClick={onClick}>
|
<a className='timeline-queue-header__btn' onClick={this.handleClick}>
|
||||||
<SvgIcon src={require('@tabler/icons/icons/arrow-bar-to-up.svg')} />
|
<Icon src={require('@tabler/icons/icons/arrow-bar-to-up.svg')} />
|
||||||
{(count > 0) && intl.formatMessage(message, { count })}
|
{(count > 0) && (
|
||||||
|
<div className='timeline-queue-header__label'>
|
||||||
|
{intl.formatMessage(message, { count })}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,6 +20,8 @@ import classNames from 'classnames';
|
||||||
import Avatar from 'soapbox/components/avatar';
|
import Avatar from 'soapbox/components/avatar';
|
||||||
import { getAcct } from 'soapbox/utils/accounts';
|
import { getAcct } from 'soapbox/utils/accounts';
|
||||||
import { displayFqn } from 'soapbox/utils/state';
|
import { displayFqn } from 'soapbox/utils/state';
|
||||||
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
|
import { NavLink } from 'react-router-dom';
|
||||||
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container';
|
||||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||||
import { ProfileInfoPanel } from 'soapbox/features/ui/util/async-components';
|
import { ProfileInfoPanel } from 'soapbox/features/ui/util/async-components';
|
||||||
|
@ -346,11 +348,12 @@ class Header extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ownAccount = account.get('id') === me;
|
||||||
const info = this.makeInfo();
|
const info = this.makeInfo();
|
||||||
const menu = this.makeMenu();
|
const menu = this.makeMenu();
|
||||||
|
|
||||||
const header = account.get('header', '');
|
const header = account.get('header', '');
|
||||||
const headerMissing = !header || ['/images/banner.png', '/headers/original/missing.png'].some(path => header.endsWith(path));
|
// const headerMissing = !header || ['/images/banner.png', '/headers/original/missing.png'].some(path => header.endsWith(path));
|
||||||
const avatarSize = isSmallScreen ? 90 : 200;
|
const avatarSize = isSmallScreen ? 90 : 200;
|
||||||
const deactivated = !account.getIn(['pleroma', 'is_active'], true);
|
const deactivated = !account.getIn(['pleroma', 'is_active'], true);
|
||||||
|
|
||||||
|
@ -360,7 +363,7 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('account__header', { inactive: !!account.get('moved'), deactivated: deactivated })}>
|
<div className={classNames('account__header', { inactive: !!account.get('moved'), deactivated: deactivated })}>
|
||||||
<div className={classNames('account__header__image', { 'account__header__image--none': headerMissing || deactivated })}>
|
<div className={classNames('account__header__image', { /* 'account__header__image--none': headerMissing || deactivated */ })}>
|
||||||
<div className='account__header__info'>
|
<div className='account__header__info'>
|
||||||
{info}
|
{info}
|
||||||
</div>
|
</div>
|
||||||
|
@ -392,6 +395,40 @@ class Header extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='account__header__extra__links'>
|
||||||
|
|
||||||
|
<NavLink isActive={this.isStatusesPageActive} activeClassName='active' to={`/@${account.get('acct')}`} title={intl.formatNumber(account.get('statuses_count'))}>
|
||||||
|
<span>{shortNumberFormat(account.get('statuses_count'))}</span>
|
||||||
|
<span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
{(ownAccount || !account.getIn(['pleroma', 'hide_follows'], false)) && <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/following`} title={intl.formatNumber(account.get('following_count'))}>
|
||||||
|
{account.getIn(['pleroma', 'hide_follows_count'], false) ? <span>•</span> : <span>{shortNumberFormat(account.get('following_count'))}</span>}
|
||||||
|
<span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{(ownAccount || !account.getIn(['pleroma', 'hide_followers'], false)) && <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/followers`} title={intl.formatNumber(account.get('followers_count'))}>
|
||||||
|
{account.getIn(['pleroma', 'hide_followers_count'], false) ? <span>•</span> : <span>{shortNumberFormat(account.get('followers_count'))}</span>}
|
||||||
|
<span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{(ownAccount || !account.getIn(['pleroma', 'hide_favorites'], true)) && <NavLink exact activeClassName='active' to={`/@${account.get('acct')}/favorites`}>
|
||||||
|
{ /* : TODO : shortNumberFormat(account.get('favourite_count')) */ }
|
||||||
|
<span>•</span>
|
||||||
|
<span><FormattedMessage id='navigation_bar.favourites' defaultMessage='Likes' /></span>
|
||||||
|
</NavLink>}
|
||||||
|
|
||||||
|
{ownAccount &&
|
||||||
|
<NavLink
|
||||||
|
exact activeClassName='active' to={`/@${account.get('acct')}/pins`}
|
||||||
|
>
|
||||||
|
{ /* : TODO : shortNumberFormat(account.get('pinned_count')) */ }
|
||||||
|
<span>•</span>
|
||||||
|
<span><FormattedMessage id='navigation_bar.pins' defaultMessage='Pins' /></span>
|
||||||
|
</NavLink>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
{isSmallScreen && (
|
{isSmallScreen && (
|
||||||
<div className={classNames('account-mobile-container', { 'deactivated': deactivated })}>
|
<div className={classNames('account-mobile-container', { 'deactivated': deactivated })}>
|
||||||
<BundleContainer fetchComponent={ProfileInfoPanel}>
|
<BundleContainer fetchComponent={ProfileInfoPanel}>
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { expandAccountMediaTimeline } from '../../actions/timelines';
|
||||||
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { getAccountGallery } from 'soapbox/selectors';
|
import { getAccountGallery, findAccountByUsername } from 'soapbox/selectors';
|
||||||
import MediaItem from './components/media_item';
|
import MediaItem from './components/media_item';
|
||||||
import LoadMore from 'soapbox/components/load_more';
|
import LoadMore from 'soapbox/components/load_more';
|
||||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||||
|
@ -21,7 +21,6 @@ import { FormattedMessage } from 'react-intl';
|
||||||
const mapStateToProps = (state, { params, withReplies = false }) => {
|
const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const accounts = state.getIn(['accounts']);
|
|
||||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||||
|
|
||||||
let accountId = -1;
|
let accountId = -1;
|
||||||
|
@ -29,7 +28,7 @@ const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
const account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
accountUsername = account ? account.getIn(['acct'], '') : '';
|
accountUsername = account ? account.getIn(['acct'], '') : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { NavLink } from 'react-router-dom';
|
||||||
import { fetchPatronAccount } from '../../actions/patron';
|
import { fetchPatronAccount } from '../../actions/patron';
|
||||||
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import { makeGetStatusIds } from 'soapbox/selectors';
|
import { makeGetStatusIds, findAccountByUsername } from 'soapbox/selectors';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -27,7 +27,6 @@ const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, { params, withReplies = false }) => {
|
const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const accounts = state.getIn(['accounts']);
|
|
||||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||||
const soapboxConfig = getSoapboxConfig(state);
|
const soapboxConfig = getSoapboxConfig(state);
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ const makeMapStateToProps = () => {
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
const account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
accountUsername = account ? account.getIn(['acct'], '') : '';
|
accountUsername = account ? account.getIn(['acct'], '') : '';
|
||||||
accountApId = account ? account.get('url') : '';
|
accountApId = account ? account.get('url') : '';
|
||||||
|
|
|
@ -103,7 +103,6 @@ class UserIndex extends ImmutablePureComponent {
|
||||||
<Column>
|
<Column>
|
||||||
<SimpleForm style={{ paddingBottom: 0 }}>
|
<SimpleForm style={{ paddingBottom: 0 }}>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={this.state.q}
|
|
||||||
onChange={this.handleQueryChange}
|
onChange={this.handleQueryChange}
|
||||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -171,9 +171,8 @@ class ChatBox extends ImmutablePureComponent {
|
||||||
return this.canSubmit() ? (
|
return this.canSubmit() ? (
|
||||||
<div className='chat-box__send'>
|
<div className='chat-box__send'>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon='send'
|
src={require('@tabler/icons/icons/send.svg')}
|
||||||
title={intl.formatMessage(messages.send)}
|
title={intl.formatMessage(messages.send)}
|
||||||
size={16}
|
|
||||||
onClick={this.sendMessage}
|
onClick={this.sendMessage}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import CharacterCounter from './character_counter';
|
// import TextCharacterCounter from './text_character_counter';
|
||||||
|
import VisualCharacterCounter from './visual_character_counter';
|
||||||
import Button from '../../../components/button';
|
import Button from '../../../components/button';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -351,7 +352,12 @@ export default class ComposeForm extends ImmutablePureComponent {
|
||||||
<SpoilerButtonContainer />
|
<SpoilerButtonContainer />
|
||||||
<MarkdownButtonContainer />
|
<MarkdownButtonContainer />
|
||||||
</div>
|
</div>
|
||||||
{maxTootChars && <div className='character-counter__wrapper'><CharacterCounter max={maxTootChars} text={text} /></div>}
|
{maxTootChars && (
|
||||||
|
<div className='compose-form__counter'>
|
||||||
|
{/* <TextCharacterCounter max={maxTootChars} text={text} /> */}
|
||||||
|
<VisualCharacterCounter max={maxTootChars} text={text} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className='compose-form__publish'>
|
<div className='compose-form__publish'>
|
||||||
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
|
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import DisplayName from '../../../components/display_name';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { isRtl } from '../../../rtl';
|
import { isRtl } from '../../../rtl';
|
||||||
import AttachmentList from 'soapbox/components/attachment_list';
|
import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -57,7 +57,7 @@ class ReplyIndicator extends ImmutablePureComponent {
|
||||||
<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
|
<div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
|
||||||
|
|
||||||
{status.get('media_attachments').size > 0 && (
|
{status.get('media_attachments').size > 0 && (
|
||||||
<AttachmentList
|
<AttachmentThumbs
|
||||||
compact
|
compact
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { length } from 'stringz';
|
||||||
|
|
||||||
|
export default class TextCharacterCounter extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
text: PropTypes.string.isRequired,
|
||||||
|
max: PropTypes.number.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
checkRemainingText(diff) {
|
||||||
|
if (diff < 0) {
|
||||||
|
return <span className='character-counter character-counter--over'>{diff}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <span className='character-counter'>{diff}</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const diff = this.props.max - length(this.props.text);
|
||||||
|
return this.checkRemainingText(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ const messages = defineMessages({
|
||||||
* @param {string} props.text - text to use to measure
|
* @param {string} props.text - text to use to measure
|
||||||
* @param {number} props.max - max text allowed
|
* @param {number} props.max - max text allowed
|
||||||
*/
|
*/
|
||||||
class CharacterCounter extends React.PureComponent {
|
class VisualCharacterCounter extends React.PureComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, text, max } = this.props;
|
const { intl, text, max } = this.props;
|
||||||
|
@ -22,21 +22,23 @@ class CharacterCounter extends React.PureComponent {
|
||||||
const progress = textLength / max;
|
const progress = textLength / max;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProgressCircle
|
<div className='visual-character-counter'>
|
||||||
title={intl.formatMessage(messages.title, { chars: textLength, maxChars: max })}
|
<ProgressCircle
|
||||||
progress={progress}
|
title={intl.formatMessage(messages.title, { chars: textLength, maxChars: max })}
|
||||||
radius={10}
|
progress={progress}
|
||||||
stroke={3}
|
radius={10}
|
||||||
/>
|
stroke={3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CharacterCounter.propTypes = {
|
VisualCharacterCounter.propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
max: PropTypes.number.isRequired,
|
max: PropTypes.number.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default injectIntl(CharacterCounter);
|
export default injectIntl(VisualCharacterCounter);
|
|
@ -6,30 +6,41 @@ import {
|
||||||
showSearch,
|
showSearch,
|
||||||
} from '../../../actions/search';
|
} from '../../../actions/search';
|
||||||
import Search from '../components/search';
|
import Search from '../components/search';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
value: state.getIn(['search', 'value']),
|
value: state.getIn(['search', 'value']),
|
||||||
submitted: state.getIn(['search', 'submitted']),
|
submitted: state.getIn(['search', 'submitted']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = (dispatch, { autoSubmit }) => {
|
||||||
|
|
||||||
onChange(value) {
|
const debouncedSubmit = debounce(() => {
|
||||||
dispatch(changeSearch(value));
|
|
||||||
},
|
|
||||||
|
|
||||||
onClear() {
|
|
||||||
dispatch(clearSearch());
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
dispatch(submitSearch());
|
dispatch(submitSearch());
|
||||||
},
|
}, 900);
|
||||||
|
|
||||||
onShow() {
|
return {
|
||||||
dispatch(showSearch());
|
onChange(value) {
|
||||||
},
|
dispatch(changeSearch(value));
|
||||||
|
|
||||||
});
|
if (autoSubmit) {
|
||||||
|
debouncedSubmit();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClear() {
|
||||||
|
dispatch(clearSearch());
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
dispatch(submitSearch());
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
dispatch(showSearch());
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
export default connect(mapStateToProps, mapDispatchToProps)(Search);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { getAcct, isVerified } from 'soapbox/utils/accounts';
|
import { getAcct, isVerified } from 'soapbox/utils/accounts';
|
||||||
import StillImage from 'soapbox/components/still_image';
|
import StillImage from 'soapbox/components/still_image';
|
||||||
import VerificationBadge from 'soapbox/components/verification_badge';
|
import VerificationBadge from 'soapbox/components/verification_badge';
|
||||||
|
@ -13,7 +14,7 @@ const mapStateToProps = state => ({
|
||||||
|
|
||||||
const ProfilePreview = ({ account, displayFqn }) => (
|
const ProfilePreview = ({ account, displayFqn }) => (
|
||||||
<div className='card h-card'>
|
<div className='card h-card'>
|
||||||
<a target='_blank' rel='noopener' href={account.get('url')}>
|
<Link to={`/@${account.get('acct')}`}>
|
||||||
<div className='card__img'>
|
<div className='card__img'>
|
||||||
<StillImage alt='' src={account.get('header')} />
|
<StillImage alt='' src={account.get('header')} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +33,7 @@ const ProfilePreview = ({ account, displayFqn }) => (
|
||||||
<span>@{getAcct(account, displayFqn)}</span>
|
<span>@{getAcct(account, displayFqn)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { debounce } from 'lodash';
|
||||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||||
import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts';
|
import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
|
import { findAccountByUsername } from 'soapbox/selectors';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params }) => {
|
const mapStateToProps = (state, { params }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
|
@ -28,14 +29,13 @@ const mapStateToProps = (state, { params }) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = state.getIn(['accounts']);
|
|
||||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||||
|
|
||||||
let accountId = -1;
|
let accountId = -1;
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
const account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,19 +5,25 @@ import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import { fetchFavourites } from '../../actions/interactions';
|
import { fetchFavourites } from '../../actions/interactions';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.favourites', defaultMessage: 'Likes' },
|
||||||
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
class Favourites extends ImmutablePureComponent {
|
class Favourites extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
accountIds: ImmutablePropTypes.orderedSet,
|
accountIds: ImmutablePropTypes.orderedSet,
|
||||||
|
@ -37,7 +43,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { accountIds } = this.props;
|
const { intl, accountIds } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -50,7 +56,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has liked this post yet. When someone does, they will show up here.' />;
|
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has liked this post yet. When someone does, they will show up here.' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column heading={intl.formatMessage(messages.heading)}>
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='favourites'
|
scrollKey='favourites'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
|
|
@ -17,18 +17,18 @@ import Column from '../ui/components/column';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||||
import { getFollowDifference } from 'soapbox/utils/accounts';
|
import { getFollowDifference } from 'soapbox/utils/accounts';
|
||||||
|
import { findAccountByUsername } from 'soapbox/selectors';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params, withReplies = false }) => {
|
const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const accounts = state.getIn(['accounts']);
|
|
||||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||||
|
|
||||||
let accountId = -1;
|
let accountId = -1;
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
const account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,18 +17,18 @@ import Column from '../ui/components/column';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import MissingIndicator from 'soapbox/components/missing_indicator';
|
import MissingIndicator from 'soapbox/components/missing_indicator';
|
||||||
import { getFollowDifference } from 'soapbox/utils/accounts';
|
import { getFollowDifference } from 'soapbox/utils/accounts';
|
||||||
|
import { findAccountByUsername } from 'soapbox/selectors';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params, withReplies = false }) => {
|
const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
const me = state.get('me');
|
const me = state.get('me');
|
||||||
const accounts = state.getIn(['accounts']);
|
|
||||||
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase());
|
||||||
|
|
||||||
let accountId = -1;
|
let accountId = -1;
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
const account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connectHashtagStream } from '../../actions/streaming';
|
import { connectHashtagStream } from '../../actions/streaming';
|
||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
|
||||||
|
@ -26,8 +25,10 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
title = () => {
|
title = () => {
|
||||||
const title = [this.props.params.id];
|
const title = [`#${this.props.params.id}`];
|
||||||
|
|
||||||
|
// TODO: wtf is all this?
|
||||||
|
// It exists in Mastodon's codebase, but undocumented
|
||||||
if (this.additionalFor('any')) {
|
if (this.additionalFor('any')) {
|
||||||
title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
|
title.push(' ', <FormattedMessage key='any' id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage='or {additional}' />);
|
||||||
}
|
}
|
||||||
|
@ -43,6 +44,8 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: wtf is this?
|
||||||
|
// It exists in Mastodon's codebase, but undocumented
|
||||||
additionalFor = (mode) => {
|
additionalFor = (mode) => {
|
||||||
const { tags } = this.props.params;
|
const { tags } = this.props.params;
|
||||||
|
|
||||||
|
@ -108,9 +111,8 @@ class HashtagTimeline extends React.PureComponent {
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={`#${id}`}>
|
<Column label={`#${id}`} transparent>
|
||||||
<ColumnBackButton />
|
<ColumnHeader active={hasUnread} title={this.title()} />
|
||||||
<ColumnHeader icon='hashtag' active={hasUnread} title={this.title()} />
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
scrollKey='hashtag_timeline'
|
scrollKey='hashtag_timeline'
|
||||||
timelineId={`hashtag:${id}`}
|
timelineId={`hashtag:${id}`}
|
||||||
|
|
|
@ -96,10 +96,11 @@ class HomeTimeline extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { intl, siteTitle, isLoading, isEmpty, features } = this.props;
|
const { intl, siteTitle, isLoading, isEmpty, features } = this.props;
|
||||||
const { done } = this.state;
|
const { done } = this.state;
|
||||||
|
const showSuggestions = features.suggestions && isEmpty && !isLoading && !done;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.title)} transparent>
|
<Column label={intl.formatMessage(messages.title)} transparent={!showSuggestions}>
|
||||||
{(features.suggestions && isEmpty && !isLoading && !done) ? (
|
{showSuggestions ? (
|
||||||
<BundleContainer fetchComponent={FollowRecommendationsContainer}>
|
<BundleContainer fetchComponent={FollowRecommendationsContainer}>
|
||||||
{Component => <Component onDone={this.handleDone} />}
|
{Component => <Component onDone={this.handleDone} />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import StatusListContainer from '../ui/containers/status_list_container';
|
import StatusListContainer from '../ui/containers/status_list_container';
|
||||||
import Column from '../../components/column';
|
import Column from 'soapbox/features/ui/components/column';
|
||||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
import { connectListStream } from '../../actions/streaming';
|
import { connectListStream } from '../../actions/streaming';
|
||||||
import { expandListTimeline } from '../../actions/timelines';
|
import { expandListTimeline } from '../../actions/timelines';
|
||||||
|
@ -11,9 +11,6 @@ import { fetchList, deleteList } from '../../actions/lists';
|
||||||
import { openModal } from '../../actions/modal';
|
import { openModal } from '../../actions/modal';
|
||||||
import MissingIndicator from '../../components/missing_indicator';
|
import MissingIndicator from '../../components/missing_indicator';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
import HomeColumnHeader from '../../components/home_column_header';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import Button from 'soapbox/components/button';
|
import Button from 'soapbox/components/button';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -23,7 +20,7 @@ const messages = defineMessages({
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
list: state.getIn(['lists', props.params.id]),
|
list: state.getIn(['lists', props.params.id]),
|
||||||
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
// hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -37,7 +34,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
hasUnread: PropTypes.bool,
|
// hasUnread: PropTypes.bool,
|
||||||
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
@ -97,7 +94,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hasUnread, list } = this.props;
|
const { list } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
const title = list ? list.get('title') : id;
|
const title = list ? list.get('title') : id;
|
||||||
|
|
||||||
|
@ -126,8 +123,8 @@ class ListTimeline extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={title}>
|
<Column label={title} heading={title} transparent>
|
||||||
<HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
|
{/* <HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
|
||||||
<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}>
|
||||||
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
<Icon id='pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
|
||||||
|
@ -144,7 +141,7 @@ class ListTimeline extends React.PureComponent {
|
||||||
<Icon id='arrow-right' />
|
<Icon id='arrow-right' />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</HomeColumnHeader>
|
</HomeColumnHeader> */}
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
scrollKey='list_timeline'
|
scrollKey='list_timeline'
|
||||||
|
|
|
@ -7,12 +7,16 @@ import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import MissingIndicator from '../../components/missing_indicator';
|
import MissingIndicator from '../../components/missing_indicator';
|
||||||
import { fetchReblogs } from '../../actions/interactions';
|
import { fetchReblogs } from '../../actions/interactions';
|
||||||
import { fetchStatus } from '../../actions/statuses';
|
import { fetchStatus } from '../../actions/statuses';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import { makeGetStatus } from '../../selectors';
|
import { makeGetStatus } from '../../selectors';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.reblogs', defaultMessage: 'Reposts' },
|
||||||
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const getStatus = makeGetStatus();
|
const getStatus = makeGetStatus();
|
||||||
const status = getStatus(state, {
|
const status = getStatus(state, {
|
||||||
|
@ -27,9 +31,11 @@ const mapStateToProps = (state, props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
class Reblogs extends ImmutablePureComponent {
|
class Reblogs extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
accountIds: ImmutablePropTypes.orderedSet,
|
accountIds: ImmutablePropTypes.orderedSet,
|
||||||
|
@ -50,7 +56,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { accountIds, status } = this.props;
|
const { intl, accountIds, status } = this.props;
|
||||||
|
|
||||||
if (!accountIds) {
|
if (!accountIds) {
|
||||||
return (
|
return (
|
||||||
|
@ -71,7 +77,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has reposted this post yet. When someone does, they will show up here.' />;
|
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has reposted this post yet. When someone does, they will show up here.' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column heading={intl.formatMessage(messages.heading)}>
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='reblogs'
|
scrollKey='reblogs'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
|
|
@ -3,8 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import StatusListContainer from '../ui/containers/status_list_container';
|
import StatusListContainer from '../ui/containers/status_list_container';
|
||||||
import Column from '../../components/column';
|
import Column from 'soapbox/features/ui/components/column';
|
||||||
import HomeColumnHeader from '../../components/home_column_header';
|
|
||||||
import PinnedHostsPicker from './components/pinned_hosts_picker';
|
import PinnedHostsPicker from './components/pinned_hosts_picker';
|
||||||
import IconButton from 'soapbox/components/icon_button';
|
import IconButton from 'soapbox/components/icon_button';
|
||||||
import { expandRemoteTimeline } from '../../actions/timelines';
|
import { expandRemoteTimeline } from '../../actions/timelines';
|
||||||
|
@ -82,11 +81,10 @@ class RemoteTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, hasUnread, onlyMedia, timelineId, instance, pinned } = this.props;
|
const { intl, onlyMedia, timelineId, instance, pinned } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.title)} transparent>
|
<Column label={intl.formatMessage(messages.title)} heading={instance} transparent>
|
||||||
<HomeColumnHeader activeItem='fediverse' active={hasUnread} />
|
|
||||||
<PinnedHostsPicker host={instance} />
|
<PinnedHostsPicker host={instance} />
|
||||||
{!pinned && <div className='timeline-filter-message'>
|
{!pinned && <div className='timeline-filter-message'>
|
||||||
<IconButton icon='times' onClick={this.handleCloseClick} />
|
<IconButton icon='times' onClick={this.handleCloseClick} />
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { Link, NavLink } from 'react-router-dom';
|
||||||
import { getDomain } from 'soapbox/utils/accounts';
|
import { getDomain } from 'soapbox/utils/accounts';
|
||||||
import Avatar from 'soapbox/components/avatar';
|
import Avatar from 'soapbox/components/avatar';
|
||||||
import DisplayName from 'soapbox/components/display_name';
|
import DisplayName from 'soapbox/components/display_name';
|
||||||
import AttachmentList from 'soapbox/components/attachment_list';
|
import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
|
||||||
import PollPreview from './poll_preview';
|
import PollPreview from './poll_preview';
|
||||||
import ScheduledStatusActionBar from './scheduled_status_action_bar';
|
import ScheduledStatusActionBar from './scheduled_status_action_bar';
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ class ScheduledStatus extends ImmutablePureComponent {
|
||||||
collapsable
|
collapsable
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AttachmentList
|
<AttachmentThumbs
|
||||||
compact
|
compact
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -12,12 +12,8 @@ const messages = defineMessages({
|
||||||
const Search = ({ intl }) => (
|
const Search = ({ intl }) => (
|
||||||
<div className='column search-page'>
|
<div className='column search-page'>
|
||||||
<ColumnHeader icon='search' title={intl.formatMessage(messages.heading)} />
|
<ColumnHeader icon='search' title={intl.formatMessage(messages.heading)} />
|
||||||
<SearchContainer />
|
<SearchContainer autoSubmit />
|
||||||
<div className='drawer__pager'>
|
<SearchResultsContainer />
|
||||||
<div className='drawer__inner darker'>
|
|
||||||
<SearchResultsContainer />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,10 @@ import { getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
const mapStateToProps = state => {
|
||||||
const instance = state.get('instance');
|
const instance = state.get('instance');
|
||||||
const features = getFeatures(instance);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'),
|
||||||
reactionList: features.exposableReactions,
|
features: getFeatures(instance),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -29,7 +28,7 @@ class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
me: SoapboxPropTypes.me,
|
me: SoapboxPropTypes.me,
|
||||||
allowedEmoji: ImmutablePropTypes.list,
|
allowedEmoji: ImmutablePropTypes.list,
|
||||||
reactionList: PropTypes.bool,
|
features: PropTypes.object.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
getNormalizedReacts = () => {
|
getNormalizedReacts = () => {
|
||||||
|
@ -42,7 +41,7 @@ class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
).reverse();
|
).reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRepost = () => {
|
getReposts = () => {
|
||||||
const { status } = this.props;
|
const { status } = this.props;
|
||||||
if (status.get('reblogs_count')) {
|
if (status.get('reblogs_count')) {
|
||||||
return (
|
return (
|
||||||
|
@ -58,8 +57,39 @@ class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFavourites = () => {
|
||||||
|
const { features, status } = this.props;
|
||||||
|
|
||||||
|
if (status.get('favourites_count')) {
|
||||||
|
const favourites = (
|
||||||
|
<>
|
||||||
|
<Icon src={require('@tabler/icons/icons/thumb-up.svg')} />
|
||||||
|
<span className='emoji-reacts__count'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (features.exposableReactions) {
|
||||||
|
return (
|
||||||
|
<Link to={`/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/likes`} className='emoji-react emoji-react--favourites'>
|
||||||
|
{favourites}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div className='emoji-react emoji-react--favourites'>
|
||||||
|
{favourites}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
getEmojiReacts = () => {
|
getEmojiReacts = () => {
|
||||||
const { status, reactionList } = this.props;
|
const { status, features } = this.props;
|
||||||
|
|
||||||
const emojiReacts = this.getNormalizedReacts();
|
const emojiReacts = this.getNormalizedReacts();
|
||||||
const count = emojiReacts.reduce((acc, cur) => (
|
const count = emojiReacts.reduce((acc, cur) => (
|
||||||
|
@ -81,7 +111,7 @@ class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (reactionList) {
|
if (features.exposableReactions) {
|
||||||
return <Link to={`/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/reactions/${e.get('name')}`} className='emoji-react' key={i}>{emojiReact}</Link>;
|
return <Link to={`/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/reactions/${e.get('name')}`} className='emoji-react' key={i}>{emojiReact}</Link>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,13 +129,12 @@ class StatusInteractionBar extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const emojiReacts = this.getEmojiReacts();
|
const { features } = this.props;
|
||||||
const repost = this.getRepost();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='status-interaction-bar'>
|
<div className='status-interaction-bar'>
|
||||||
{emojiReacts}
|
{features.emojiReacts ? this.getEmojiReacts() : this.getFavourites()}
|
||||||
{repost}
|
{this.getReposts()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -573,7 +573,7 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column label={intl.formatMessage(messages.detailedStatus)} showBackBtn={false}>
|
<Column label={intl.formatMessage(messages.detailedStatus)} transparent>
|
||||||
<SubNavigation message={intl.formatMessage(messages.title)} />
|
<SubNavigation message={intl.formatMessage(messages.title)} />
|
||||||
{/*
|
{/*
|
||||||
Eye icon to show/hide all CWs in a thread.
|
Eye icon to show/hide all CWs in a thread.
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||||
import ColumnHeader from './column_header';
|
import ColumnHeader from './column_header';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Column from 'soapbox/components/column';
|
import Column from 'soapbox/components/column';
|
||||||
import ColumnBackButton from '../../../components/column_back_button_slim';
|
|
||||||
import DropdownMenu from 'soapbox/containers/dropdown_menu_container';
|
import DropdownMenu from 'soapbox/containers/dropdown_menu_container';
|
||||||
|
|
||||||
// Yes, there are 3 types of columns at this point, but this one is better, I swear
|
// Yes, there are 3 types of columns at this point, but this one is better, I swear
|
||||||
|
@ -29,7 +28,6 @@ export default class BetterColumn extends React.PureComponent {
|
||||||
<DropdownMenu items={menu} icon='ellipsis-v' size={18} direction='right' />
|
<DropdownMenu items={menu} icon='ellipsis-v' size={18} direction='right' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ColumnBackButton />
|
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import RelativeTimestamp from '../../../components/relative_timestamp';
|
||||||
import DisplayName from '../../../components/display_name';
|
import DisplayName from '../../../components/display_name';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Icon from 'soapbox/components/icon';
|
import Icon from 'soapbox/components/icon';
|
||||||
import AttachmentList from 'soapbox/components/attachment_list';
|
import AttachmentThumbs from 'soapbox/components/attachment_thumbs';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
||||||
|
@ -88,7 +88,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
<StatusContent status={status} />
|
<StatusContent status={status} />
|
||||||
|
|
||||||
{status.get('media_attachments').size > 0 && (
|
{status.get('media_attachments').size > 0 && (
|
||||||
<AttachmentList
|
<AttachmentThumbs
|
||||||
compact
|
compact
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const DrawerLoading = () => (
|
|
||||||
<div className='drawer'>
|
|
||||||
<div className='drawer__pager'>
|
|
||||||
<div className='drawer__inner' />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default DrawerLoading;
|
|
|
@ -138,8 +138,17 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
let pagination = [];
|
let pagination = [];
|
||||||
|
|
||||||
const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' fixedWidth /></button>;
|
const leftNav = media.size > 1 && (
|
||||||
const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' fixedWidth /></button>;
|
<button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}>
|
||||||
|
<Icon src={require('@tabler/icons/icons/arrow-left.svg')} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
const rightNav = media.size > 1 && (
|
||||||
|
<button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}>
|
||||||
|
<Icon src={require('@tabler/icons/icons/arrow-right.svg')} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
if (media.size > 1) {
|
if (media.size > 1) {
|
||||||
pagination = media.map((item, i) => {
|
pagination = media.map((item, i) => {
|
||||||
|
|
|
@ -56,7 +56,7 @@ import {
|
||||||
Following,
|
Following,
|
||||||
Reblogs,
|
Reblogs,
|
||||||
Reactions,
|
Reactions,
|
||||||
// Favourites,
|
Favourites,
|
||||||
DirectTimeline,
|
DirectTimeline,
|
||||||
Conversations,
|
Conversations,
|
||||||
HashtagTimeline,
|
HashtagTimeline,
|
||||||
|
@ -259,7 +259,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<Redirect from='/canary' to='/about/canary' />
|
<Redirect from='/canary' to='/about/canary' />
|
||||||
<Redirect from='/canary.txt' to='/about/canary' />
|
<Redirect from='/canary.txt' to='/about/canary' />
|
||||||
|
|
||||||
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/tags/:id' publicRoute page={DefaultPage} component={HashtagTimeline} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/lists' page={DefaultPage} component={Lists} content={children} />
|
<WrappedRoute path='/lists' page={DefaultPage} component={Lists} content={children} />
|
||||||
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
|
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
|
||||||
|
@ -288,6 +288,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
<WrappedRoute path='/@:username/pins' component={PinnedStatuses} page={ProfilePage} content={children} />
|
<WrappedRoute path='/@:username/pins' component={PinnedStatuses} page={ProfilePage} content={children} />
|
||||||
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact page={StatusPage} component={Status} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId' publicRoute exact page={StatusPage} component={Status} content={children} />
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/reblogs' page={DefaultPage} component={Reblogs} content={children} />
|
||||||
|
<WrappedRoute path='/@:username/posts/:statusId/likes' page={DefaultPage} component={Favourites} content={children} />
|
||||||
<WrappedRoute path='/@:username/posts/:statusId/reactions/:reaction?' page={DefaultPage} component={Reactions} content={children} />
|
<WrappedRoute path='/@:username/posts/:statusId/reactions/:reaction?' page={DefaultPage} component={Reactions} content={children} />
|
||||||
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
|
<Redirect from='/@:username/:statusId' to='/@:username/posts/:statusId' />
|
||||||
|
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
"auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło",
|
"auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło",
|
||||||
"auth.logged_out": "Wylogowano.",
|
"auth.logged_out": "Wylogowano.",
|
||||||
"backups.actions.create": "Utwórz kopię zapasową",
|
"backups.actions.create": "Utwórz kopię zapasową",
|
||||||
"backups.empty_message": "Nie znaleziono kopii zapasaowych. {action}",
|
"backups.empty_message": "Nie znaleziono kopii zapasowych. {action}",
|
||||||
"backups.empty_message.action": "Chcesz utworzyć?",
|
"backups.empty_message.action": "Chcesz utworzyć?",
|
||||||
"backups.pending": "Oczekująca",
|
"backups.pending": "Oczekująca",
|
||||||
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
|
"boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem",
|
||||||
|
@ -525,12 +525,18 @@
|
||||||
"morefollows.followers_label": "…i {count} więcej {count, plural, one {obserwujący(-a)} few {obserwujących} many {obserwujących} other {obserwujących}} na zdalnych stronach.",
|
"morefollows.followers_label": "…i {count} więcej {count, plural, one {obserwujący(-a)} few {obserwujących} many {obserwujących} other {obserwujących}} na zdalnych stronach.",
|
||||||
"morefollows.following_label": "…i {count} więcej {count, plural, one {obserwowany(-a)} few {obserwowanych} many {obserwowanych} other {obserwowanych}} na zdalnych stronach.",
|
"morefollows.following_label": "…i {count} więcej {count, plural, one {obserwowany(-a)} few {obserwowanych} many {obserwowanych} other {obserwowanych}} na zdalnych stronach.",
|
||||||
"mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
|
"mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
|
||||||
|
"navigation.chats": "Czaty",
|
||||||
|
"navigation.direct_messages": "Wiadomości",
|
||||||
|
"navigation.home": "Strona główna",
|
||||||
|
"navigation.notifications": "Powiadomienia",
|
||||||
|
"navigation.search": "Szukaj",
|
||||||
"navigation_bar.account_aliases": "Aliasy kont",
|
"navigation_bar.account_aliases": "Aliasy kont",
|
||||||
"navigation_bar.admin_settings": "Ustawienia administracyjne",
|
"navigation_bar.admin_settings": "Ustawienia administracyjne",
|
||||||
"navigation_bar.blocks": "Zablokowani użytkownicy",
|
"navigation_bar.blocks": "Zablokowani użytkownicy",
|
||||||
"navigation_bar.bookmarks": "Zakładki",
|
"navigation_bar.bookmarks": "Zakładki",
|
||||||
"navigation_bar.compose": "Utwórz nowy wpis",
|
"navigation_bar.compose": "Utwórz nowy wpis",
|
||||||
"navigation_bar.domain_blocks": "Ukryte domeny",
|
"navigation_bar.domain_blocks": "Ukryte domeny",
|
||||||
|
"navigation_bar.export_data": "Eksportuj dane",
|
||||||
"navigation_bar.favourites": "Ulubione",
|
"navigation_bar.favourites": "Ulubione",
|
||||||
"navigation_bar.filters": "Wyciszone słowa",
|
"navigation_bar.filters": "Wyciszone słowa",
|
||||||
"navigation_bar.follow_requests": "Prośby o śledzenie",
|
"navigation_bar.follow_requests": "Prośby o śledzenie",
|
||||||
|
@ -809,10 +815,13 @@
|
||||||
"status.unpin": "Odepnij z profilu",
|
"status.unpin": "Odepnij z profilu",
|
||||||
"status_list.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowy wpis} few {nowe wpisy} many {nowych wpisów} other {nowe wpisy}}",
|
"status_list.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowy wpis} few {nowe wpisy} many {nowych wpisów} other {nowe wpisy}}",
|
||||||
"statuses.tombstone": "Jeden lub więcej z wpisów jest już niedostępny.",
|
"statuses.tombstone": "Jeden lub więcej z wpisów jest już niedostępny.",
|
||||||
|
"sub_navigation.back": "Wróć",
|
||||||
"suggestions.dismiss": "Odrzuć sugestię",
|
"suggestions.dismiss": "Odrzuć sugestię",
|
||||||
|
"tabs_bar.all": "Wszystkie",
|
||||||
"tabs_bar.apps": "Aplikacje",
|
"tabs_bar.apps": "Aplikacje",
|
||||||
"tabs_bar.chats": "Rozmowy",
|
"tabs_bar.chats": "Rozmowy",
|
||||||
"tabs_bar.dashboard": "Panel administracyjny",
|
"tabs_bar.dashboard": "Panel administracyjny",
|
||||||
|
"tabs_bar.fediverse": "Fediwersum",
|
||||||
"tabs_bar.header": "Informacje o koncie",
|
"tabs_bar.header": "Informacje o koncie",
|
||||||
"tabs_bar.home": "Strona główna",
|
"tabs_bar.home": "Strona główna",
|
||||||
"tabs_bar.news": "Nowości",
|
"tabs_bar.news": "Nowości",
|
||||||
|
|
|
@ -17,10 +17,9 @@ import LinkFooter from '../features/ui/components/link_footer';
|
||||||
import { getAcct } from 'soapbox/utils/accounts';
|
import { getAcct } from 'soapbox/utils/accounts';
|
||||||
import { displayFqn } from 'soapbox/utils/state';
|
import { displayFqn } from 'soapbox/utils/state';
|
||||||
import { getFeatures } from 'soapbox/utils/features';
|
import { getFeatures } from 'soapbox/utils/features';
|
||||||
import { makeGetAccount } from '../selectors';
|
|
||||||
import { Redirect } from 'react-router-dom';
|
import { Redirect } from 'react-router-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { findAccountByUsername, makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
const mapStateToProps = (state, { params, withReplies = false }) => {
|
const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
const username = params.username || '';
|
const username = params.username || '';
|
||||||
|
@ -34,7 +33,7 @@ const mapStateToProps = (state, { params, withReplies = false }) => {
|
||||||
if (accountFetchError) {
|
if (accountFetchError) {
|
||||||
accountId = null;
|
accountId = null;
|
||||||
} else {
|
} else {
|
||||||
account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase());
|
account = findAccountByUsername(state, username);
|
||||||
accountId = account ? account.getIn(['id'], null) : -1;
|
accountId = account ? account.getIn(['id'], null) : -1;
|
||||||
accountUsername = account ? account.getIn(['acct'], '') : '';
|
accountUsername = account ? account.getIn(['acct'], '') : '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import Sticky from 'react-stickynode';
|
||||||
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
import BundleContainer from 'soapbox/features/ui/containers/bundle_container';
|
||||||
|
import PrimaryNavigation from 'soapbox/components/primary_navigation';
|
||||||
import {
|
import {
|
||||||
PromoPanel,
|
PromoPanel,
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
|
@ -36,14 +38,9 @@ class RemoteInstancePage extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--left'>
|
||||||
<div className='columns-area__panels__pane__inner'>
|
<div className='columns-area__panels__pane__inner'>
|
||||||
<BundleContainer fetchComponent={InstanceInfoPanel}>
|
<Sticky top={65}>
|
||||||
{Component => <Component host={host} />}
|
<PrimaryNavigation />
|
||||||
</BundleContainer>
|
</Sticky>
|
||||||
{(disclosed || isAdmin) && (
|
|
||||||
<BundleContainer fetchComponent={InstanceModerationPanel}>
|
|
||||||
{Component => <Component host={host} />}
|
|
||||||
</BundleContainer>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -55,15 +52,25 @@ class RemoteInstancePage extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
<div className='columns-area__panels__pane columns-area__panels__pane--right'>
|
||||||
<div className='columns-area__panels__pane__inner'>
|
<div className='columns-area__panels__pane__inner'>
|
||||||
{me && (
|
<Sticky top={65}>
|
||||||
<BundleContainer fetchComponent={FeaturesPanel}>
|
{me && (
|
||||||
{Component => <Component key='features-panel' />}
|
<BundleContainer fetchComponent={FeaturesPanel}>
|
||||||
|
{Component => <Component key='features-panel' />}
|
||||||
|
</BundleContainer>
|
||||||
|
)}
|
||||||
|
<BundleContainer fetchComponent={PromoPanel}>
|
||||||
|
{Component => <Component key='promo-panel' />}
|
||||||
</BundleContainer>
|
</BundleContainer>
|
||||||
)}
|
<BundleContainer fetchComponent={InstanceInfoPanel}>
|
||||||
<BundleContainer fetchComponent={PromoPanel}>
|
{Component => <Component host={host} />}
|
||||||
{Component => <Component key='promo-panel' />}
|
</BundleContainer>
|
||||||
</BundleContainer>
|
{(disclosed || isAdmin) && (
|
||||||
<LinkFooter key='link-footer' />
|
<BundleContainer fetchComponent={InstanceModerationPanel}>
|
||||||
|
{Component => <Component host={host} />}
|
||||||
|
</BundleContainer>
|
||||||
|
)}
|
||||||
|
<LinkFooter key='link-footer' />
|
||||||
|
</Sticky>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,6 +47,36 @@ export const makeGetAccount = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findAccountsByUsername = (state, username) => {
|
||||||
|
const accounts = state.get('accounts');
|
||||||
|
|
||||||
|
return accounts.filter(account => {
|
||||||
|
return username.toLowerCase() === account.getIn(['acct'], '').toLowerCase();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findAccountByUsername = (state, username) => {
|
||||||
|
const accounts = findAccountsByUsername(state, username);
|
||||||
|
|
||||||
|
if (accounts.size > 1) {
|
||||||
|
const me = state.get('me');
|
||||||
|
const meURL = state.getIn(['accounts', me, 'url']);
|
||||||
|
|
||||||
|
return accounts.find(account => {
|
||||||
|
try {
|
||||||
|
// If more than one account has the same username, try matching its host
|
||||||
|
const { host } = new URL(account.get('url'));
|
||||||
|
const { host: meHost } = new URL(meURL);
|
||||||
|
return host === meHost;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return accounts.first();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toServerSideType = columnType => {
|
const toServerSideType = columnType => {
|
||||||
switch (columnType) {
|
switch (columnType) {
|
||||||
case 'home':
|
case 'home':
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
display: block;
|
display: block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.3);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -13,7 +15,7 @@
|
||||||
&:active,
|
&:active,
|
||||||
&:focus {
|
&:focus {
|
||||||
.card__bar {
|
.card__bar {
|
||||||
background: var(--foreground-color);
|
background-color: var(--brand-color--faint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +24,6 @@
|
||||||
height: 130px;
|
height: 130px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
|
|
||||||
.still-image {
|
.still-image {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -30,7 +31,6 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 600px) {
|
@media screen and (max-width: 600px) {
|
||||||
|
@ -48,12 +48,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: var(--brand-color--faint);
|
background: var(--background-color);
|
||||||
border-radius: 0 0 4px 4px;
|
transition: 0.2s;
|
||||||
|
|
||||||
@media screen and (max-width: $no-gap-breakpoint) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -58,7 +58,6 @@
|
||||||
@import 'components/react-toggle';
|
@import 'components/react-toggle';
|
||||||
@import 'components/getting-started';
|
@import 'components/getting-started';
|
||||||
@import 'components/promo-panel';
|
@import 'components/promo-panel';
|
||||||
@import 'components/drawer';
|
|
||||||
@import 'components/still-image';
|
@import 'components/still-image';
|
||||||
@import 'components/timeline-queue-header';
|
@import 'components/timeline-queue-header';
|
||||||
@import 'components/badge';
|
@import 'components/badge';
|
||||||
|
|
|
@ -296,20 +296,22 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.icon-button {
|
.icon-button {
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
top: calc(50% - 13px);
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.chat-box__send .icon-button {
|
.svg-icon {
|
||||||
top: calc(50% - 9px);
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
|
@ -332,7 +334,7 @@
|
||||||
.ui--chatroom {
|
.ui--chatroom {
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
|
|
||||||
.columns-area__panels__main .columns-area {
|
.columns-area__panels__main .columns-area .column {
|
||||||
height: calc(100vh - 100px);
|
height: calc(100vh - 100px);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
|
@ -313,9 +313,11 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color--faint);
|
||||||
|
|
||||||
@media screen and (max-width: 895px) {
|
@media screen and (max-width: 895px) {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
|
@ -61,6 +61,7 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
flex: 1 1 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 631px) {
|
@media screen and (min-width: 631px) {
|
||||||
|
@ -68,8 +69,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column,
|
.column {
|
||||||
.drawer {
|
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
@ -85,8 +85,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-area > div {
|
.columns-area > div {
|
||||||
.column,
|
.column {
|
||||||
.drawer {
|
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
@ -98,12 +97,10 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 15px 0;
|
padding-top: 15px;
|
||||||
|
|
||||||
.column,
|
.column {
|
||||||
.drawer {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +122,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 580px) {
|
@media (max-width: 580px) {
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.timeline-compose-block {
|
.timeline-compose-block {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin-top: 10px; // Make less claustrophobic
|
margin-top: -5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,6 +347,10 @@
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
.sub-navigation {
|
||||||
|
box-shadow: 0 -6px 6px -6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 580px) {
|
@media screen and (max-width: 580px) {
|
||||||
|
@ -710,6 +709,7 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 160px;
|
min-height: 160px;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
|
||||||
@supports (display: grid) { // hack to fix Chrome <57
|
@supports (display: grid) { // hack to fix Chrome <57
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
@ -727,21 +727,20 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 580px) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-column {
|
.error-column {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 580px) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-link--transparent .icon-with-badge__badge {
|
.column-link--transparent .icon-with-badge__badge {
|
||||||
|
@ -775,6 +774,11 @@
|
||||||
.column__top {
|
.column__top {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2);
|
||||||
|
|
||||||
|
.sub-navigation {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-header {
|
.column-header {
|
||||||
|
@ -856,6 +860,10 @@
|
||||||
|
|
||||||
.column-list {
|
.column-list {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&__empty-message {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-loading {
|
.column-loading {
|
||||||
|
@ -882,6 +890,10 @@
|
||||||
.column--transparent {
|
.column--transparent {
|
||||||
.slist__append {
|
.slist__append {
|
||||||
@include standard-panel;
|
@include standard-panel;
|
||||||
|
|
||||||
|
@media screen and (max-width: 580px) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-navigation ~ .slist .slist__append {
|
.sub-navigation ~ .slist .slist__append {
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__warning {
|
&__warning {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
background: var(--brand-color--faint);
|
background: var(--brand-color--faint);
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
right: 5px;
|
right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__autosuggest-wrapper {
|
&__autosuggest-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color--faint);
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__modifiers {
|
&__modifiers {
|
||||||
color: var(--primary-text-color);
|
color: var(--primary-text-color);
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
@ -327,7 +327,7 @@
|
||||||
}
|
}
|
||||||
} // end .compose-form .compose-form__modifiers
|
} // end .compose-form .compose-form__modifiers
|
||||||
|
|
||||||
.compose-form__buttons-wrapper {
|
&__buttons-wrapper {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -380,13 +380,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.character-counter__wrapper {
|
.character-counter {
|
||||||
align-self: center;
|
display: block;
|
||||||
margin: 0 10px 0 auto;
|
cursor: default;
|
||||||
|
font-family: var(--font-sans-serif), sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary-text-color--faint);
|
||||||
|
&.character-counter--over { color: $warning-red; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.character-counter,
|
||||||
|
.visual-character-counter {
|
||||||
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-form__publish {
|
&__publish {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
@ -396,6 +406,13 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__counter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
} // end .compose-form
|
} // end .compose-form
|
||||||
|
|
||||||
// Icon tweaks
|
// Icon tweaks
|
||||||
|
|
|
@ -71,7 +71,6 @@
|
||||||
|
|
||||||
.detailed-status__link {
|
.detailed-status__link {
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color--faint);
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
@ -159,8 +158,34 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.thread {
|
.thread {
|
||||||
|
@include standard-panel;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
|
||||||
|
@media screen and (max-width: 580px) {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__status {
|
&__status {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
// Only display line if posts are below
|
||||||
|
&:last-child .detailed-status__action-bar {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__descendants .thread__status:first-child {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status--focused:first-child,
|
||||||
|
&__ancestors &__status:first-child {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__descendants &__status:last-child {
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__connector {
|
&__connector {
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
.drawer {
|
|
||||||
width: 300px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__tab {
|
|
||||||
display: block;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
padding: 15px 5px 13px;
|
|
||||||
color: var(--primary-text-color--faint);
|
|
||||||
text-decoration: none;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
border-bottom: 2px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column,
|
|
||||||
.drawer {
|
|
||||||
flex: 1 1 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__pager {
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__inner {
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: var(--foreground-color);
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pseudo-drawer {
|
|
||||||
background: var(--background-color);
|
|
||||||
font-size: 13px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__header {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
font-size: 16px;
|
|
||||||
background: var(--brand-color--med);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
a {
|
|
||||||
transition: background 100ms ease-in;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--background-color);
|
|
||||||
transition: background 200ms ease-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__backdrop {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba($base-overlay-background, 0.5);
|
|
||||||
}
|
|
|
@ -21,12 +21,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-react--reblogs {
|
.emoji-react--reblogs,
|
||||||
|
.emoji-react--favourites {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
margin-right: 10px;
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
margin-right: 0.2em;
|
margin-right: 0.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-react--reblogs {
|
||||||
|
.svg-icon {
|
||||||
color: var(--highlight-text-color);
|
color: var(--highlight-text-color);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
@ -35,6 +42,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emoji-react--favourites {
|
||||||
|
.svg-icon {
|
||||||
|
color: $gold-star;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.emoji-reacts {
|
.emoji-reacts {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
|
|
|
@ -17,8 +17,7 @@
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding: 10px;
|
padding: 20px 10px 40px;
|
||||||
padding-top: 20px;
|
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
|
@ -14,16 +14,6 @@
|
||||||
border-radius: 8px 8px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__inner {
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
|
|
||||||
&.backdrop {
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
border-radius: 0 0 0 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts {
|
&__accounts {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
|
@ -192,3 +192,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$media-compact-size: 50px;
|
||||||
|
|
||||||
|
.media-gallery--compact {
|
||||||
|
height: $media-compact-size !important;
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.spoiler-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery__item {
|
||||||
|
width: $media-compact-size !important;
|
||||||
|
height: $media-compact-size !important;
|
||||||
|
inset: auto !important;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
&-overflow {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icons {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-gallery__file-extension__label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -128,8 +128,9 @@
|
||||||
|
|
||||||
@media screen and (max-width: 600px) { padding: 30px 2px; }
|
@media screen and (max-width: 600px) { padding: 30px 2px; }
|
||||||
|
|
||||||
.fa {
|
.svg-icon {
|
||||||
margin-right: 0;
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +181,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-modal__button {
|
.media-modal__button {
|
||||||
background-color: var(--primary-text-color);
|
background-color: #fff;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
@ -337,7 +338,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 480px;
|
width: 480px;
|
||||||
max-width: 90vw;
|
max-width: 90vw;
|
||||||
border-radius: 4px;
|
border-radius: 10px;
|
||||||
border: 1px solid var(--primary-text-color--faint);
|
border: 1px solid var(--primary-text-color--faint);
|
||||||
color: var(--primary-text-color--faint);
|
color: var(--primary-text-color--faint);
|
||||||
background: var(--foreground-color);
|
background: var(--foreground-color);
|
||||||
|
|
|
@ -29,9 +29,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.pinned-hosts-picker {
|
.pinned-hosts-picker {
|
||||||
margin-left: 10px;
|
padding: 10px 0 0 10px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
background: var(--foreground-color);
|
||||||
|
border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2);
|
||||||
|
|
||||||
.pinned-host {
|
.pinned-host {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
|
|
@ -166,9 +166,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-page {
|
.search-page {
|
||||||
.drawer__inner:not(:empty) {
|
height: 100%;
|
||||||
min-height: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
|
@ -184,14 +182,6 @@
|
||||||
.search__icon .svg-icon {
|
.search__icon .svg-icon {
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__pager {
|
|
||||||
border-radius: 0 0 10px 10px;
|
|
||||||
|
|
||||||
@media screen and (max-width: 450px) {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-results {
|
.search-results {
|
||||||
|
|
|
@ -143,6 +143,10 @@
|
||||||
.status__prepend-icon-wrapper {
|
.status__prepend-icon-wrapper {
|
||||||
left: -26px;
|
left: -26px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
||||||
|
svg.feather-repeat {
|
||||||
|
color: var(--highlight-text-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
|
@ -710,3 +714,18 @@ a.status-card.compact:hover {
|
||||||
padding: 15px 0 10px;
|
padding: 15px 0 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment-thumbs {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&__clickable-region {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
.timeline-queue-header {
|
.timeline-queue-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
max-height: 30px;
|
height: 30px;
|
||||||
position: sticky;
|
position: fixed;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
margin-bottom: 8px;
|
|
||||||
background-color: var(--brand-color);
|
background-color: var(--brand-color);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
border-bottom: 1px solid;
|
|
||||||
border-top: 1px solid;
|
|
||||||
border-color: var(--brand-color--faint);
|
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
transition: max-height 150ms ease;
|
transition: 150ms ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
opacity: 1;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
z-index: 500;
|
z-index: 500;
|
||||||
|
|
||||||
|
.sub-navigation ~ & {
|
||||||
|
top: calc(60px + 41px);
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
max-height: 0;
|
transform: scaleY(0);
|
||||||
opacity: 0;
|
pointer-events: none;
|
||||||
margin: 0;
|
|
||||||
border: 0;
|
.timeline-queue-header__label {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
|
@ -46,4 +46,8 @@
|
||||||
height: 46px;
|
height: 46px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
transition: 150ms ease;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,7 +297,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
background: var(--brand-color);
|
background: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__handle {
|
&__handle {
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
left: 0;
|
left: 0;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
transform: translate(0, -50%);
|
transform: translate(0, -50%);
|
||||||
background: var(--brand-color);
|
background: var(--accent-color);
|
||||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
|
@ -364,7 +364,7 @@
|
||||||
height: 4px;
|
height: 4px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
top: 14px;
|
top: 14px;
|
||||||
background: var(--brand-color);
|
background: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__buffer {
|
&__buffer {
|
||||||
|
@ -380,7 +380,7 @@
|
||||||
height: 12px;
|
height: 12px;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
background: var(--brand-color);
|
background: var(--accent-color);
|
||||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
||||||
|
|
||||||
.no-reduce-motion & {
|
.no-reduce-motion & {
|
||||||
|
|
|
@ -113,6 +113,10 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
|
&--scrolled {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -228,8 +228,7 @@ body.rtl {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 631px) {
|
@media screen and (min-width: 631px) {
|
||||||
.column,
|
.column {
|
||||||
.drawer {
|
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
|
||||||
|
@ -240,8 +239,7 @@ body.rtl {
|
||||||
}
|
}
|
||||||
|
|
||||||
.columns-area > div {
|
.columns-area > div {
|
||||||
.column,
|
.column {
|
||||||
.drawer {
|
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -318,7 +318,6 @@
|
||||||
.react-swipeable-view-container {
|
.react-swipeable-view-container {
|
||||||
&,
|
&,
|
||||||
.columns-area,
|
.columns-area,
|
||||||
.drawer,
|
|
||||||
.column {
|
.column {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
"qrcode.react": "^1.0.0",
|
"qrcode.react": "^1.0.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-color": "^2.18.1",
|
"react-color": "^2.18.1",
|
||||||
|
"react-content-loader": "^6.0.3",
|
||||||
"react-datepicker": "^4.1.1",
|
"react-datepicker": "^4.1.1",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
"react-helmet": "^6.0.0",
|
"react-helmet": "^6.0.0",
|
||||||
|
|
|
@ -7757,6 +7757,11 @@ react-color@^2.18.1:
|
||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
tinycolor2 "^1.4.1"
|
||||||
|
|
||||||
|
react-content-loader@^6.0.3:
|
||||||
|
version "6.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-content-loader/-/react-content-loader-6.0.3.tgz#32e28ca7120e0a2552fc26655d0d4448cc1fc0c5"
|
||||||
|
integrity sha512-CIRgTHze+ls+jGDIfCitw27YkW2XcaMpsYORTUdBxsMFiKuUYMnlvY76dZE4Lsaa9vFXVw+41ieBEK7SJt0nug==
|
||||||
|
|
||||||
react-datepicker@^4.1.1:
|
react-datepicker@^4.1.1:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.2.1.tgz#72caf5055bc7c4eb0279c1f6d7624ded053edc4c"
|
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.2.1.tgz#72caf5055bc7c4eb0279c1f6d7624ded053edc4c"
|
||||||
|
|
Loading…
Reference in New Issue