mirror of
https://github.com/mastodon/mastodon.git
synced 2024-08-20 21:08:15 -07:00
Compare commits
5 commits
3f8f95ca45
...
7cc11f420e
Author | SHA1 | Date | |
---|---|---|---|
|
7cc11f420e | ||
|
a50c8e951f | ||
|
2c1e75727d | ||
|
2166f44079 | ||
|
77ec956d92 |
11 changed files with 595 additions and 41 deletions
|
@ -60,7 +60,7 @@ export interface BaseNotificationGroupJSON {
|
||||||
|
|
||||||
interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
|
interface NotificationGroupWithStatusJSON extends BaseNotificationGroupJSON {
|
||||||
type: NotificationWithStatusType;
|
type: NotificationWithStatusType;
|
||||||
status: ApiStatusJSON;
|
status_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationWithStatusJSON extends BaseNotificationJSON {
|
interface NotificationWithStatusJSON extends BaseNotificationJSON {
|
||||||
|
|
|
@ -0,0 +1,258 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<ButtonScrollList /> handles a large number of children correctly 1`] = `
|
||||||
|
<div
|
||||||
|
className="button-scroll-list-container"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll left"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="button-scroll-list"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll right"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ButtonScrollList /> handles a single child correctly 1`] = `
|
||||||
|
<div
|
||||||
|
className="button-scroll-list-container"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll left"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="button-scroll-list"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll right"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<ButtonScrollList /> renders an empty button scroll list element 1`] = `null`;
|
||||||
|
|
||||||
|
exports[`<ButtonScrollList /> renders the children 1`] = `
|
||||||
|
<div
|
||||||
|
className="button-scroll-list-container"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll left"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="button-scroll-list"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
aria-label="Scroll right"
|
||||||
|
className="icon-button column-header__setting-btn"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
MockIcon
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -0,0 +1,119 @@
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
|
||||||
|
import { render, screen } from 'mastodon/test_helpers';
|
||||||
|
|
||||||
|
import ButtonScrollList from '../button_scroll_list';
|
||||||
|
|
||||||
|
jest.mock('mastodon/components/icon', () => {
|
||||||
|
return {
|
||||||
|
Icon: () => <div>MockIcon</div>,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('<ButtonScrollList />', () => {
|
||||||
|
it('renders an empty button scroll list element', () => {
|
||||||
|
const children = [];
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the children', () => {
|
||||||
|
const children = Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<div key={i} ref={jest.fn()} />
|
||||||
|
));
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scrolls left', () => {
|
||||||
|
const children = Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<div key={i} ref={jest.fn()} />
|
||||||
|
));
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const instance = component.getInstance();
|
||||||
|
instance.scrollLeft();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scrolls right', () => {
|
||||||
|
const children = Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<div key={i} ref={jest.fn()} />
|
||||||
|
));
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const instance = component.getInstance();
|
||||||
|
instance.scrollRight();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('scrolls left and right correctly', () => {
|
||||||
|
const children = Array.from({ length: 10 }, (_, i) => (
|
||||||
|
<div key={i}>{i}</div>
|
||||||
|
));
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let instance = component.getInstance();
|
||||||
|
instance.scrollRight();
|
||||||
|
expect(instance.slide).toBe(1);
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
let instance = component.getInstance();
|
||||||
|
instance.scrollLeft();
|
||||||
|
expect(instance.slide).toBe(0);
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a single child correctly', () => {
|
||||||
|
const children = [<div key={0} ref={jest.fn()} />];
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles a large number of children correctly', () => {
|
||||||
|
const children = Array.from({ length: 50 }, (_, i) => (
|
||||||
|
<div key={i} ref={jest.fn()} />
|
||||||
|
));
|
||||||
|
const component = renderer.create(
|
||||||
|
<ButtonScrollList>{children}</ButtonScrollList>,
|
||||||
|
);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if scroll buttons are accessible', () => {
|
||||||
|
const children = Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<div key={i} ref={jest.fn()} />
|
||||||
|
));
|
||||||
|
render(<ButtonScrollList>{children}</ButtonScrollList>);
|
||||||
|
|
||||||
|
const leftButton = screen.getByRole('button', { name: /scroll left/i });
|
||||||
|
const rightButton = screen.getByRole('button', { name: /scroll right/i });
|
||||||
|
|
||||||
|
expect(leftButton).toBeTruthy();
|
||||||
|
expect(rightButton).toBeTruthy();
|
||||||
|
|
||||||
|
leftButton.focus();
|
||||||
|
expect(document.activeElement).toBe(leftButton);
|
||||||
|
|
||||||
|
rightButton.focus();
|
||||||
|
expect(document.activeElement).toBe(rightButton);
|
||||||
|
});
|
||||||
|
});
|
86
app/javascript/mastodon/components/button_scroll_list.jsx
Normal file
86
app/javascript/mastodon/components/button_scroll_list.jsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
|
||||||
|
class ButtonScrollList extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.scrollRef = React.createRef();
|
||||||
|
this.slide = 0;
|
||||||
|
this.childrenLength = React.Children.count(props.children);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.scrollRef && this.scrollRef.current) {
|
||||||
|
const container = this.scrollRef.current;
|
||||||
|
container.scrollTo({ left: 0, behavior: 'auto' });
|
||||||
|
this.slide = 0;
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollLeft = () => {
|
||||||
|
if (this.scrollRef && this.scrollRef.current) {
|
||||||
|
this.scrollRef.current.scrollBy({ left: -200, behavior: 'smooth' });
|
||||||
|
this.slide = Math.max(0, this.slide - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollRight = () => {
|
||||||
|
if (this.scrollRef && this.scrollRef.current) {
|
||||||
|
const { children } = this.props;
|
||||||
|
const container = this.scrollRef.current;
|
||||||
|
const maxScrollLeft = container.scrollWidth - container.clientWidth;
|
||||||
|
|
||||||
|
if (container.scrollLeft < maxScrollLeft) {
|
||||||
|
container.scrollBy({ left: 200, behavior: 'smooth' });
|
||||||
|
this.slide = Math.min(
|
||||||
|
React.Children.count(children) - 1,
|
||||||
|
this.slide + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
if (React.Children.count(children) === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='button-scroll-list-container'>
|
||||||
|
<button
|
||||||
|
className='icon-button column-header__setting-btn'
|
||||||
|
aria-label='Scroll left'
|
||||||
|
onClick={this.scrollLeft}
|
||||||
|
>
|
||||||
|
<Icon id='chevron-left' icon={ChevronLeftIcon} />
|
||||||
|
</button>
|
||||||
|
<div className='button-scroll-list' ref={this.scrollRef}>
|
||||||
|
{React.Children.map(children, (child, index) => (
|
||||||
|
<div key={index}>{child}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className='icon-button column-header__setting-btn'
|
||||||
|
aria-label='Scroll right'
|
||||||
|
onClick={this.scrollRight}
|
||||||
|
>
|
||||||
|
<Icon id='chevron-right' icon={ChevronRightIcon} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ButtonScrollList;
|
|
@ -10,6 +10,7 @@ import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||||
|
import FollowedTagsList from 'mastodon/components/followed_tags_list';
|
||||||
import type { IconProp } from 'mastodon/components/icon';
|
import type { IconProp } from 'mastodon/components/icon';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
|
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
|
||||||
|
@ -255,7 +256,11 @@ export const ColumnHeader: React.FC<Props> = ({
|
||||||
<>
|
<>
|
||||||
{backButton}
|
{backButton}
|
||||||
|
|
||||||
<button onClick={handleTitleClick} className='column-header__title'>
|
<button
|
||||||
|
onClick={handleTitleClick}
|
||||||
|
className='column-header__title'
|
||||||
|
style={{ overflow: 'visible', paddingRight: '15px' }}
|
||||||
|
>
|
||||||
{!backButton && (
|
{!backButton && (
|
||||||
<Icon
|
<Icon
|
||||||
id={icon}
|
id={icon}
|
||||||
|
@ -268,6 +273,8 @@ export const ColumnHeader: React.FC<Props> = ({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{icon === 'home' ? <FollowedTagsList /> : null}
|
||||||
|
|
||||||
{!hasTitle && backButton}
|
{!hasTitle && backButton}
|
||||||
|
|
||||||
<div className='column-header__buttons'>
|
<div className='column-header__buttons'>
|
||||||
|
|
67
app/javascript/mastodon/components/followed_tags_list.jsx
Normal file
67
app/javascript/mastodon/components/followed_tags_list.jsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
import {
|
||||||
|
expandFollowedHashtags,
|
||||||
|
fetchFollowedHashtags,
|
||||||
|
} from 'mastodon/actions/tags';
|
||||||
|
import ButtonScrollList from 'mastodon/components/button_scroll_list';
|
||||||
|
import { Hashtag } from 'mastodon/components/hashtag';
|
||||||
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => ({
|
||||||
|
hashtags: state.getIn(['followed_tags', 'items']),
|
||||||
|
isLoading: state.getIn(['followed_tags', 'isLoading'], true),
|
||||||
|
hasMore: !!state.getIn(['followed_tags', 'next']),
|
||||||
|
});
|
||||||
|
|
||||||
|
class FollowedTagsList extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
hashtags: ImmutablePropTypes.list.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
hasMore: PropTypes.bool.isRequired,
|
||||||
|
...WithRouterPropTypes,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.dispatch(fetchFollowedHashtags());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadMore = debounce(
|
||||||
|
() => {
|
||||||
|
this.props.dispatch(expandFollowedHashtags());
|
||||||
|
},
|
||||||
|
300,
|
||||||
|
{ leading: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { hashtags } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='followed-tags-list'>
|
||||||
|
<ButtonScrollList>
|
||||||
|
{hashtags.map((hashtag) => (
|
||||||
|
<div className='hashtag-wrapper' key={hashtag.get('name')}>
|
||||||
|
<Hashtag
|
||||||
|
name={hashtag.get('name')}
|
||||||
|
showSkeleton={false}
|
||||||
|
to={`/tags/${hashtag.get('name')}`}
|
||||||
|
withGraph={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ButtonScrollList>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(injectIntl(FollowedTagsList));
|
|
@ -85,6 +85,7 @@ export interface HashtagProps {
|
||||||
description?: React.ReactNode;
|
description?: React.ReactNode;
|
||||||
history?: number[];
|
history?: number[];
|
||||||
name: string;
|
name: string;
|
||||||
|
showSkeleton?: boolean;
|
||||||
people: number;
|
people: number;
|
||||||
to: string;
|
to: string;
|
||||||
uses?: number;
|
uses?: number;
|
||||||
|
@ -93,6 +94,7 @@ export interface HashtagProps {
|
||||||
|
|
||||||
export const Hashtag: React.FC<HashtagProps> = ({
|
export const Hashtag: React.FC<HashtagProps> = ({
|
||||||
name,
|
name,
|
||||||
|
showSkeleton = true,
|
||||||
to,
|
to,
|
||||||
people,
|
people,
|
||||||
uses,
|
uses,
|
||||||
|
@ -113,13 +115,15 @@ export const Hashtag: React.FC<HashtagProps> = ({
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{description ? (
|
{showSkeleton ? (
|
||||||
<span>{description}</span>
|
description ? (
|
||||||
) : typeof people !== 'undefined' ? (
|
<span>{description}</span>
|
||||||
<ShortNumber value={people} renderer={accountsCountRenderer} />
|
) : typeof people !== 'undefined' ? (
|
||||||
) : (
|
<ShortNumber value={people} renderer={accountsCountRenderer} />
|
||||||
<Skeleton width={100} />
|
) : (
|
||||||
)}
|
<Skeleton width={100} />
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{typeof uses !== 'undefined' && (
|
{typeof uses !== 'undefined' && (
|
||||||
|
|
|
@ -49,21 +49,14 @@ export const FilteredNotificationsBanner: React.FC = () => {
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='filtered_notifications_banner.pending_requests'
|
id='filtered_notifications_banner.pending_requests'
|
||||||
defaultMessage='Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
defaultMessage='From {count, plural, =0 {no one} one {one person} other {# people}} you may know'
|
||||||
values={{ count: policy.summary.pending_requests_count }}
|
values={{ count: policy.summary.pending_requests_count }}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='filtered-notifications-banner__badge'>
|
<div className='filtered-notifications-banner__badge'>
|
||||||
<div className='filtered-notifications-banner__badge__badge'>
|
{toCappedNumber(policy.summary.pending_notifications_count)}
|
||||||
{toCappedNumber(policy.summary.pending_notifications_count)}
|
|
||||||
</div>
|
|
||||||
<FormattedMessage
|
|
||||||
id='filtered_notifications_banner.mentions'
|
|
||||||
defaultMessage='{count, plural, one {mention} other {mentions}}'
|
|
||||||
values={{ count: policy.summary.pending_notifications_count }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
|
@ -300,8 +300,7 @@
|
||||||
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
||||||
"filter_modal.select_filter.title": "Filter this post",
|
"filter_modal.select_filter.title": "Filter this post",
|
||||||
"filter_modal.title.status": "Filter a post",
|
"filter_modal.title.status": "Filter a post",
|
||||||
"filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentions}}",
|
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
||||||
"filtered_notifications_banner.pending_requests": "Notifications from {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
|
||||||
"filtered_notifications_banner.title": "Filtered notifications",
|
"filtered_notifications_banner.title": "Filtered notifications",
|
||||||
"firehose.all": "All",
|
"firehose.all": "All",
|
||||||
"firehose.local": "This server",
|
"firehose.local": "This server",
|
||||||
|
|
|
@ -124,9 +124,9 @@ export function createNotificationGroupFromJSON(
|
||||||
case 'mention':
|
case 'mention':
|
||||||
case 'poll':
|
case 'poll':
|
||||||
case 'update': {
|
case 'update': {
|
||||||
const { status, ...groupWithoutStatus } = group;
|
const { status_id: statusId, ...groupWithoutStatus } = group;
|
||||||
return {
|
return {
|
||||||
statusId: status.id,
|
statusId,
|
||||||
sampleAccountIds,
|
sampleAccountIds,
|
||||||
...groupWithoutStatus,
|
...groupWithoutStatus,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3561,6 +3561,42 @@ $ui-header-logo-wordmark-width: 99px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-scroll-list-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-scroll-list {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
flex-grow: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-scroll-list::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.followed-tags-list {
|
||||||
|
overflow: hidden;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hashtag-wrapper {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
.column-back-button {
|
.column-back-button {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -10171,25 +10207,10 @@ noscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
&__badge {
|
&__badge {
|
||||||
display: flex;
|
background: $ui-button-background-color;
|
||||||
align-items: center;
|
color: $white;
|
||||||
border-radius: 999px;
|
border-radius: 100px;
|
||||||
background: var(--background-border-color);
|
padding: 2px 8px;
|
||||||
color: $darker-text-color;
|
|
||||||
padding: 4px;
|
|
||||||
padding-inline-end: 8px;
|
|
||||||
gap: 6px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 16px;
|
|
||||||
word-break: keep-all;
|
|
||||||
|
|
||||||
&__badge {
|
|
||||||
background: $ui-button-background-color;
|
|
||||||
color: $white;
|
|
||||||
border-radius: 100px;
|
|
||||||
padding: 2px 8px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue