This commit is contained in:
mgabdev
2020-03-03 22:45:16 -05:00
parent 0df3c073a5
commit 33952e424f
127 changed files with 765 additions and 3588 deletions

View File

@@ -30,7 +30,7 @@ export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
export const NOTIFICATIONS_MARK_READ = 'NOTIFICATIONS_MARK_READ';
export const MAX_QUEUED_NOTIFICATIONS = 40;
export const MAX_QUEUED_NOTIFICATIONS = 40
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
@@ -43,12 +43,12 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
if (accountIds.length > 0) {
dispatch(fetchRelationships(accountIds));
}
};
}
export function initializeNotifications() {
return {
type: NOTIFICATIONS_INITIALIZE,
};
}
}
export function updateNotifications(notification, intlMessages, intlLocale) {

View File

@@ -40,7 +40,7 @@ class Avatar extends ImmutablePureComponent {
const shouldAnimate = animate || !sameImg
const options = {
className: [_s.default, _s.circle].join(' '),
className: [_s.default, _s.circle, _s.overflowHidden].join(' '),
onMouseEnter: shouldAnimate ? this.handleMouseEnter : undefined,
onMouseLeave: shouldAnimate ? this.handleMouseLeave : undefined,
src: account.get((hovering || animate) ? 'avatar' : 'avatar_static'),

View File

@@ -20,7 +20,7 @@ export default class Badge extends PureComponent {
render() {
const { children, description } = this.props
const { hovering } = this.state
const { hovering } = this.state // : todo : tooltip
return (
<Text

View File

@@ -0,0 +1,35 @@
import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { me } from '../initial_state'
import Avatar from './avatar'
import DisplayName from './display_name'
import Button from './button'
import Text from './text'
const messages = defineMessages({
follow: { id: 'follow', defaultMessage: 'Follow' },
})
export default
@injectIntl
class Comment extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
}
render() {
//
return (
<div>
</div>
)
}
}

View File

@@ -28,6 +28,7 @@ class DisplayName extends ImmutablePureComponent {
multiline: PropTypes.bool,
large: PropTypes.bool,
noHover: PropTypes.bool,
noUsername: PropTypes.bool,
}
handleMouseEnter = debounce(() => {
@@ -41,7 +42,13 @@ class DisplayName extends ImmutablePureComponent {
}
render() {
const { account, multiline, large, noHover } = this.props
const {
account,
multiline,
large,
noHover,
noUsername
} = this.props
if (!account) return null
@@ -114,9 +121,12 @@ class DisplayName extends ImmutablePureComponent {
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Investor' />
*/ }
</div>
<span className={usernameClasses}>
@{account.get('acct')}
</span>
{
!noUsername &&
<span className={usernameClasses}>
@{account.get('acct')}
</span>
}
</span>
)
}

View File

@@ -0,0 +1,40 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { shortNumberFormat } from '../../utils/numbers'
import PanelLayout from './panel_layout'
import Button from '../button'
import Divider from '../divider'
import Heading from '../heading'
import Icon from '../icon'
import Text from '../text'
const messages = defineMessages({
title: { id: 'notification_filters', defaultMessage: 'Notification Filters' },
})
export default
@injectIntl
class NotificationFilterPanel extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
// date
// verfied or not
// specific user
// specific status
// only people i do/not follow
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
</PanelLayout>
)
}
}

View File

@@ -1,5 +1,39 @@
export default class StatusOptionsPopover extends PureComponent {
render() {
// if (publicStatus) {
// menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
// menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
// menu.push(null);
// }
// if (me === status.getIn(['account', 'id'])) {
// if (publicStatus) {
// menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
// } else {
// if (status.get('visibility') === 'private') {
// menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
// }
// }
// menu.push(null);
// menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
// menu.push(null);
// menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
// menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
// } else {
// menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
// menu.push(null);
// menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
// menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
// menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
// if (isStaff) {
// menu.push(null);
// menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
// menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
// }
// }
return (
<div>
{ /* */ }

View File

@@ -1,17 +1,17 @@
import { throttle } from 'lodash';
import { List as ImmutableList } from 'immutable';
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import ColumnIndicator from './column_indicator';
import LoadMore from './load_more';
import { throttle } from 'lodash'
import { List as ImmutableList } from 'immutable'
import IntersectionObserverArticle from './intersection_observer_article'
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'
import ColumnIndicator from './column_indicator'
import LoadMore from './load_more'
const MOUSE_IDLE_DELAY = 300;
const MOUSE_IDLE_DELAY = 300
export default class ScrollableList extends PureComponent {
static contextTypes = {
router: PropTypes.object,
};
}
static propTypes = {
scrollKey: PropTypes.string.isRequired,
@@ -23,11 +23,11 @@ export default class ScrollableList extends PureComponent {
children: PropTypes.node,
onScrollToTop: PropTypes.func,
onScroll: PropTypes.func,
};
}
state = {
cachedMediaWidth: 250, // Default media/card width using default Gab Social theme
};
}
intersectionObserverWrapper = new IntersectionObserverWrapper();
@@ -213,7 +213,7 @@ export default class ScrollableList extends PureComponent {
<div className='scrollable-list' onMouseMove={this.handleMouseMove}>
<div role='feed'>
{React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticleContainer
<IntersectionObserverArticle
key={child.key}
id={child.key}
index={index}
@@ -227,7 +227,7 @@ export default class ScrollableList extends PureComponent {
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})}
</IntersectionObserverArticleContainer>
</IntersectionObserverArticle>
))}
{loadMore}

View File

@@ -272,9 +272,9 @@ class Status extends ImmutablePureComponent {
render() {
let media = null;
let statusAvatar, prepend, rebloggedByText, reblogContent;
let prepend, rebloggedByText, reblogContent;
const { intl, hidden, featured, otherAccounts, unread, showThread, group, promoted } = this.props;
const { intl, hidden, featured, unread, showThread, group, promoted } = this.props;
// console.log("replies:", this.props.replies)
@@ -321,7 +321,7 @@ class Status extends ImmutablePureComponent {
prepend = (
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}>
<Icon
id='pin'
id='star'
width='10px'
height='10px'
className={_s.fillColorSecondary}
@@ -366,7 +366,7 @@ class Status extends ImmutablePureComponent {
}
if (status.get('poll')) {
media = <Poll pollId={status.get('poll')} />;
media = <Poll pollId={status.get('poll')} />
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
@@ -375,6 +375,7 @@ class Status extends ImmutablePureComponent {
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer}>
{Component => (
<Component
inline
preview={video.get('preview_url')}
blurhash={video.get('blurhash')}
src={video.get('url')}
@@ -382,7 +383,6 @@ class Status extends ImmutablePureComponent {
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
width={this.props.cachedMediaWidth}
height={110}
inline
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
cacheWidth={this.props.cacheMediaWidth}
@@ -391,7 +391,7 @@ class Status extends ImmutablePureComponent {
/>
)}
</Bundle>
);
)
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
@@ -408,10 +408,10 @@ class Status extends ImmutablePureComponent {
/>
)}
</Bundle>
);
)
}
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
console.log("card:", status.get('card'))
// console.log("card:", status.get('card'))
media = (
<Card
onOpenMedia={this.props.onOpenMedia}
@@ -419,23 +419,21 @@ class Status extends ImmutablePureComponent {
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
/>
);
)
}
const handlers = this.props.muted
? {}
: {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,
openProfile: this.handleHotkeyOpenProfile,
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
};
const handlers = this.props.muted ? {} : {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,
openProfile: this.handleHotkeyOpenProfile,
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
}
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;

View File

@@ -1,39 +0,0 @@
import { Provider } from 'react-redux';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import configureStore from '../store/configureStore';
import { hydrateStore } from '../actions/store';
import { fetchCustomEmojis } from '../actions/custom_emojis';
import initialState from '../initial_state';
import Compose from '../features/standalone/compose';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
const store = configureStore();
if (initialState) {
store.dispatch(hydrateStore(initialState));
}
store.dispatch(fetchCustomEmojis());
export default class TimelineContainer extends PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
};
render () {
const { locale } = this.props;
return (
<IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<Compose />
</Provider>
</IntlProvider>
);
}
}

View File

@@ -1,16 +0,0 @@
import { setHeight } from '../actions/height_cache';
import IntersectionObserverArticle from '../components/intersection_observer_article';
const makeMapStateToProps = (state, props) => ({
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
});
const mapDispatchToProps = (dispatch) => ({
onHeightChange (key, id, height) {
dispatch(setHeight(key, id, height));
},
});
export default connect(makeMapStateToProps, mapDispatchToProps)(IntersectionObserverArticle);

View File

@@ -1,55 +0,0 @@
import { Fragment } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import configureStore from '../store/configureStore';
import { hydrateStore } from '../actions/store';
import initialState from '../initial_state';
import PublicTimeline from '../features/standalone/public_timeline';
import HashtagTimeline from '../features/standalone/hashtag_timeline';
import ModalRoot from '../components/modal/modal_root'
const { localeData, messages } = getLocale();
addLocaleData(localeData);
const store = configureStore();
if (initialState) {
store.dispatch(hydrateStore(initialState));
}
export default class TimelineContainer extends PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
hashtag: PropTypes.string,
local: PropTypes.bool,
};
static defaultProps = {
local: !initialState.settings.known_fediverse,
};
render () {
const { locale, hashtag, local } = this.props;
const timeline = hashtag ? <HashtagTimeline hashtag={hashtag} /> : <PublicTimeline local={local} />;
return (
<IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<Fragment>
{timeline}
{ReactDOM.createPortal(
<ModalContainer />,
document.getElementById('modal-container'),
)}
</Fragment>
</Provider>
</IntlProvider>
);
}
}

View File

@@ -20,7 +20,7 @@ const messages = defineMessages({
error: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
});
const mapStateToProps = (state, { params: { username } }) => {
const mapStateToProps = (state, { mediaType, params: { username } }) => {
const accounts = state.getIn(['accounts']);
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase());
@@ -67,7 +67,7 @@ class LoadMoreMedia extends ImmutablePureComponent {
disabled={this.props.disabled}
onClick={this.handleLoadMore}
/>
);
)
}
}
@@ -86,11 +86,11 @@ class AccountGallery extends ImmutablePureComponent {
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
}
state = {
width: 323,
};
}
componentDidMount() {
const { params: { username }, accountId } = this.props;
@@ -158,27 +158,29 @@ class AccountGallery extends ImmutablePureComponent {
const { width } = this.state;
if (!isAccount && accountId !== -1) {
return (<ColumnIndicator type='missing' />);
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!attachments && isLoading)) {
return (<ColumnIndicator type='loading' />);
return <ColumnIndicator type='loading' />
} else if (unavailable) {
return (<ColumnIndicator type='error' message={intl.formatMessage(messages.error)} />);
return <ColumnIndicator type='error' message={intl.formatMessage(messages.error)} />
}
let loadOlder = null;
let loadOlder = null
if (hasMore && !(isLoading && attachments.size === 0)) {
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />;
loadOlder = <LoadMore visible={!isLoading} onClick={this.handleLoadOlder} />
}
return (
<div className='scrollable-list scrollable-list--flex' onScroll={this.handleScroll}>
<div role='feed' className='account-gallery__container' ref={this.handleRef}>
{attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : (
{
attachments.map((attachment, index) => attachment === null ? (
<LoadMoreMedia key={'more:' + attachments.getIn(index + 1, 'id')} maxId={index > 0 ? attachments.getIn(index - 1, 'id') : null} onLoadMore={this.handleLoadMore} />
) : (
<MediaItem key={attachment.get('id')} attachment={attachment} displayWidth={width} onOpenMedia={this.handleOpenMedia} />
))}
))
}
{
attachments.size == 0 &&
@@ -190,11 +192,12 @@ class AccountGallery extends ImmutablePureComponent {
{loadOlder}
</div>
{isLoading && attachments.size === 0 && (
{
isLoading && attachments.size === 0 &&
<div className='slist__append'>
<ColumnIndicator type='loading' />
</div>
)}
}
</div>
);
}

View File

@@ -308,7 +308,7 @@ class Header extends ImmutablePureComponent {
}} />
</Button>
}
<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />
{/*<DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />*/}
</div>
}

View File

@@ -23,7 +23,6 @@ const mapStateToProps = state => {
timelineId,
allFediverse,
onlyMedia,
// hasUnread: state.getIn(['timelines', `${timelineId}${onlyMedia ? ':media' : ''}`, 'unread']) > 0,
}
}
@@ -39,7 +38,6 @@ class CommunityTimeline extends PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
// hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool,
allFediverse: PropTypes.bool,
timelineId: PropTypes.string,

View File

@@ -52,7 +52,7 @@ class ActionBar extends PureComponent {
return (
<div style={{'marginTop':'-6px'}}>
<div>
<DropdownMenuContainer items={menu} icon='chevron-down' size={size} direction='right' />
{ /* <DropdownMenuContainer items={menu} icon='chevron-down' size={size} direction='right' /> */ }
</div>
</div>
);

View File

@@ -0,0 +1,70 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import { fetchReblogs } from '../../actions/interactions';
import { fetchStatus } from '../../actions/statuses';
import { makeGetStatus } from '../../selectors';
import AccountContainer from '../../containers/account_container';
import ColumnIndicator from '../../components/column_indicator';
import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, props) => {
const getStatus = makeGetStatus();
const status = getStatus(state, {
id: props.params.statusId,
username: props.params.username,
});
return {
status,
accountIds: state.getIn(['user_lists', 'favorites', props.params.statusId]),
};
};
export default
@connect(mapStateToProps)
class Favorites extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
status: ImmutablePropTypes.map,
};
componentWillMount() {
this.props.dispatch(fetchReblogs(this.props.params.statusId));
this.props.dispatch(fetchStatus(this.props.params.statusId));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchReblogs(nextProps.params.statusId));
this.props.dispatch(fetchStatus(nextProps.params.statusId));
}
}
render() {
const { accountIds, status } = this.props;
if (!accountIds) {
return <ColumnIndicator type='loading' />
} else if (!status) {
return <ColumnIndicator type='missing' />
}
return (
<ScrollableList
scrollKey='reblogs'
emptyMessage={<FormattedMessage id='status.reblogs.empty' defaultMessage='No one has reposted this gab yet. When someone does, they will show up here.' />}
>
{
accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
)
}
</ScrollableList>
);
}
}

View File

@@ -0,0 +1 @@
export { default } from './favorites'

View File

@@ -72,7 +72,10 @@ class GroupMembers extends ImmutablePureComponent {
return (
<div className="group-account-wrapper" key={id}>
<AccountContainer id={id} withNote={false} actionIcon="none" onActionClick={() => true} />
{menu.length > 0 && <DropdownMenuContainer items={menu} icon='ellipsis-h' size={18} direction='right' />}
{ /*
menu.length > 0 && <DropdownMenuContainer items={menu} icon='ellipsis-h' size={18} direction='right' />
*/
}
</div>
);
})}

View File

@@ -45,7 +45,8 @@ getActionButton() {
{ text: intl.formatMessage(messages.removed_accounts), to: `/groups/${group.get('id')}/removed_accounts` },
];
return <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />;
// <DropdownMenuContainer items={menu} icon='ellipsis-v' size={24} direction='right' />;
return <div></div>
}
render () {

View File

@@ -1,12 +1,12 @@
import { FormattedMessage } from 'react-intl';
import { isEqual } from 'lodash';
import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
import { connectHashtagStream } from '../../actions/streaming';
import StatusListContainer from '../../containers/status_list_container';
import { FormattedMessage } from 'react-intl'
import { isEqual } from 'lodash'
import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines'
import { connectHashtagStream } from '../../actions/streaming'
import StatusListContainer from '../../containers/status_list_container'
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
});
})
export default
@connect(mapStateToProps)
@@ -18,88 +18,114 @@ class HashtagTimeline extends PureComponent {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
hasUnread: PropTypes.bool,
};
}
title = () => {
let title = [this.props.params.id];
let title = [this.props.params.id]
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}'
/>
)
}
if (this.additionalFor('all')) {
title.push(' ', <FormattedMessage key='all' id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage='and {additional}' />);
title.push(' ',
<FormattedMessage
key='all'
id='hashtag.column_header.tag_mode.all'
values={{
additional: this.additionalFor('all')
}}
defaultMessage='and {additional}'
/>
)
}
if (this.additionalFor('none')) {
title.push(' ', <FormattedMessage key='none' id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage='without {additional}' />);
title.push(' ',
<FormattedMessage
key='none'
id='hashtag.column_header.tag_mode.none'
values={{
additional: this.additionalFor('none')
}}
defaultMessage='without {additional}'
/>
)
}
return title;
return title
}
additionalFor = (mode) => {
const { tags } = this.props.params;
const { tags } = this.props.params
try {
return tags[mode].map(tag => tag.value).join('/');
return tags[mode].map(tag => tag.value).join('/')
} catch (error) {
return '';
return ''
}
}
_subscribe (dispatch, id, tags = {}) {
let any = (tags.any || []).map(tag => tag.value);
let all = (tags.all || []).map(tag => tag.value);
let any = (tags.any || []).map(tag => tag.value)
let all = (tags.all || []).map(tag => tag.value)
let none = (tags.none || []).map(tag => tag.value);
[id, ...any].map(tag => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
let tags = status.tags.map(tag => tag.name);
let tags = status.tags.map(tag => tag.name)
return all.filter(tag => tags.includes(tag)).length === all.length &&
none.filter(tag => tags.includes(tag)).length === 0;
})));
});
none.filter(tag => tags.includes(tag)).length === 0
})))
})
}
_unsubscribe () {
this.disconnects.map(disconnect => disconnect());
this.disconnects = [];
this.disconnects.map(disconnect => disconnect())
this.disconnects = []
}
componentDidMount () {
const { dispatch } = this.props;
const { id, tags } = this.props.params;
const { dispatch } = this.props
const { id, tags } = this.props.params
this._subscribe(dispatch, id, tags);
dispatch(expandHashtagTimeline(id, { tags }));
this._subscribe(dispatch, id, tags)
dispatch(expandHashtagTimeline(id, { tags }))
}
componentWillReceiveProps (nextProps) {
const { dispatch, params } = this.props;
const { id, tags } = nextProps.params;
const { dispatch, params } = this.props
const { id, tags } = nextProps.params
if (id !== params.id || !isEqual(tags, params.tags)) {
this._unsubscribe();
this._subscribe(dispatch, id, tags);
this.props.dispatch(clearTimeline(`hashtag:${id}`));
this.props.dispatch(expandHashtagTimeline(id, { tags }));
this._unsubscribe()
this._subscribe(dispatch, id, tags)
this.props.dispatch(clearTimeline(`hashtag:${id}`))
this.props.dispatch(expandHashtagTimeline(id, { tags }))
}
}
componentWillUnmount () {
this._unsubscribe();
this._unsubscribe()
}
handleLoadMore = maxId => {
const { id, tags } = this.props.params;
this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
const { id, tags } = this.props.params
this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }))
}
render () {
const { hasUnread } = this.props;
const { id } = this.props.params;
const { id } = this.props.params
return (
<StatusListContainer

View File

@@ -1,11 +1,18 @@
import { injectIntl, FormattedMessage } from 'react-intl'
import { NavLink } from 'react-router-dom'
import { injectIntl, defineMessages } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { HotKeys } from 'react-hotkeys'
import ImmutablePropTypes from 'react-immutable-proptypes'
import StatusContainer from '../../../../containers/status_container'
import AccountContainer from '../../../../containers/account_container'
import Button from '../../../../components/button'
import Avatar from '../../../../components/avatar'
import Icon from '../../../../components/icon'
import Text from '../../../../components/text'
import DisplayName from '../../../../components/display_name'
const messages = defineMessages({
})
const notificationForScreenReader = (intl, message, timestamp) => {
const output = [message]
@@ -33,18 +40,115 @@ class Notification extends ImmutablePureComponent {
renderFavorite = () => {
const { status, notificationType, accounts } = this.props
return (
<div className={[_s.default, _s.paddingHorizontal10PX].join(' ')}>
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.marginVertical10PX, _s.paddingVertical10PX, _s.paddingHorizontal10PX].join(' ')}>
<Icon id='apps' height='20px' width='20px' className={_s.marginTop5PX} />
<div className={[_s.default, _s.marginLeft15PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
{
accounts.slice(0, 6).map((account, i) => (
<NavLink
to={`/${account.get('acct')}`}
key={`fav-avatar-${i}`}
className={_s.marginRight5PX}
>
<Avatar size='30' account={account} />
</NavLink>
))
}
</div>
<div className={[_s.default, _s.paddingTop10PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={_s.text}>
{
accounts.slice(0, 1).map((account, i) => (
<DisplayName key={i} account={account} noUsername />
))
}
</div>
<Text size='medium'>
&nbsp;and 3 others favorited your gab
</Text>
</div>
</div>
<div className={[_s.default, _s.paddingTop10PX].join(' ')}>
<Text color='secondary' size='medium'>
post this at 1-14-2020 12:15pm (edited)
</Text>
</div>
</div>
</div>
</div>
</div>
)
}
render() {
const { notification } = this.props
const account = notification.get('account')
const {
status,
notificationType,
accounts,
intl
} = this.props
switch (notification.get('type')) {
case 'favourite':
return this.renderFavorite()
}
// const linkTo = '/admin/posts/123/reblogs' // etc.
return null
return (
<NavLink
to={`/`}
className={[_s.default, _s.paddingHorizontal10PX, _s.backgroundSubtle_onHover].join(' ')}
>
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.marginVertical10PX, _s.paddingVertical10PX, _s.paddingHorizontal10PX].join(' ')}>
<Icon id='apps' height='20px' width='20px' className={_s.marginTop5PX} />
<div className={[_s.default, _s.marginLeft15PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
{
accounts.slice(0, 6).map((account, i) => (
<NavLink
to={`/${account.get('acct')}`}
key={`fav-avatar-${i}`}
className={_s.marginRight5PX}
>
<Avatar size='30' account={account} />
</NavLink>
))
}
</div>
<div className={[_s.default, _s.paddingTop10PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<div className={_s.text}>
{
accounts.slice(0, 1).map((account, i) => (
<DisplayName key={i} account={account} noUsername />
))
}
</div>
<Text size='medium'>
&nbsp;and 3 others favorited your gab
</Text>
</div>
</div>
<div className={[_s.default, _s.paddingTop10PX].join(' ')}>
<Text color='secondary' size='medium'>
post this at 1-14-2020 12:15pm (edited)
</Text>
</div>
</div>
</div>
</div>
</NavLink>
)
}
}

View File

@@ -1,70 +1,74 @@
import { openModal } from '../../../actions/modal';
import { mentionCompose } from '../../../actions/compose';
import { openModal } from '../../../actions/modal'
import { mentionCompose } from '../../../actions/compose'
import {
reblog,
favourite,
unreblog,
unfavourite,
} from '../../../actions/interactions';
} from '../../../actions/interactions'
import {
hideStatus,
revealStatus,
} from '../../../actions/statuses';
import { boostModal } from '../../../initial_state';
import { makeGetNotification, makeGetStatus } from '../../../selectors';
import Notification from '../components/notification/notification';
} from '../../../actions/statuses'
import { boostModal, me } from '../../../initial_state'
import { makeGetNotification, makeGetStatus } from '../../../selectors'
import Notification from '../components/notification/notification-alt'
const makeMapStateToProps = () => {
const getNotification = makeGetNotification();
const getStatus = makeGetStatus();
const getNotification = makeGetNotification()
const getStatus = makeGetStatus()
const mapStateToProps = (state, props) => {
const notification = getNotification(state, props.notification, props.accountId);
const notification = getNotification(state, props.notification, props.accountId)
const account = state.getIn(['accounts', me])
return {
accounts: [account, account, account],
notification: notification,
status: notification.get('status') ? getStatus(state, { id: notification.get('status') }) : null,
};
};
}
}
return mapStateToProps;
};
return mapStateToProps
}
const mapDispatchToProps = dispatch => ({
onMention: (account, router) => {
dispatch(mentionCompose(account, router));
dispatch(mentionCompose(account, router))
},
onModalReblog (status) {
dispatch(reblog(status));
dispatch(reblog(status))
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog(status))
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
this.onModalReblog(status)
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }))
}
}
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
dispatch(unfavourite(status))
} else {
dispatch(favourite(status));
dispatch(favourite(status))
}
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
dispatch(revealStatus(status.get('id')))
} else {
dispatch(hideStatus(status.get('id')));
dispatch(hideStatus(status.get('id')))
}
},
});
})
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification)

View File

@@ -1,19 +1,20 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
import { createSelector } from 'reselect'
import { List as ImmutableList } from 'immutable'
import { debounce } from 'lodash'
import {
expandNotifications,
scrollTopNotifications,
dequeueNotifications,
} from '../../actions/notifications';
import NotificationContainer from './containers/notification_container';
// import ColumnSettingsContainer from './containers/column_settings_container';
import ScrollableList from '../../components/scrollable_list';
import LoadMore from '../../components/load_more';
import TimelineQueueButtonHeader from '../../components/timeline_queue_button_header';
} from '../../actions/notifications'
import NotificationContainer from './containers/notification_container'
// import ColumnSettingsContainer from './containers/column_settings_container'
import ScrollableList from '../../components/scrollable_list'
import LoadMore from '../../components/load_more'
import TimelineQueueButtonHeader from '../../components/timeline_queue_button_header'
import Block from '../../components/block'
const getNotifications = createSelector([
state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
@@ -25,10 +26,10 @@ const getNotifications = createSelector([
// used if user changed the notification settings after loading the notifications from the server
// otherwise a list of notifications will come pre-filtered from the backend
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')))
}
return notifications.filter(item => item !== null && allowedType === item.get('type'));
});
return notifications.filter(item => item !== null && allowedType === item.get('type'))
})
const mapStateToProps = state => ({
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
@@ -37,7 +38,7 @@ const mapStateToProps = state => ({
isUnread: state.getIn(['notifications', 'unread']) > 0,
hasMore: state.getIn(['notifications', 'hasMore']),
totalQueuedNotificationsCount: state.getIn(['notifications', 'totalQueuedNotificationsCount'], 0),
});
})
export default
@connect(mapStateToProps)
@@ -54,73 +55,81 @@ class Notifications extends ImmutablePureComponent {
hasMore: PropTypes.bool,
dequeueNotifications: PropTypes.func,
totalQueuedNotificationsCount: PropTypes.number,
};
}
componentWillUnmount () {
this.handleLoadOlder.cancel();
this.handleScrollToTop.cancel();
this.handleScroll.cancel();
this.props.dispatch(scrollTopNotifications(false));
this.handleLoadOlder.cancel()
this.handleScrollToTop.cancel()
this.handleScroll.cancel()
this.props.dispatch(scrollTopNotifications(false))
}
componentDidMount() {
this.handleDequeueNotifications();
this.props.dispatch(scrollTopNotifications(true));
this.handleDequeueNotifications()
this.props.dispatch(scrollTopNotifications(true))
}
handleLoadGap = (maxId) => {
this.props.dispatch(expandNotifications({ maxId }));
};
this.props.dispatch(expandNotifications({ maxId }))
}
handleLoadOlder = debounce(() => {
const last = this.props.notifications.last();
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
}, 300, { leading: true });
const last = this.props.notifications.last()
this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }))
}, 300, { leading: true })
handleScrollToTop = debounce(() => {
this.props.dispatch(scrollTopNotifications(true));
}, 100);
this.props.dispatch(scrollTopNotifications(true))
}, 100)
handleScroll = debounce(() => {
this.props.dispatch(scrollTopNotifications(false));
}, 100);
this.props.dispatch(scrollTopNotifications(false))
}, 100)
setColumnRef = c => {
this.column = c;
this.column = c
}
handleMoveUp = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
this._selectChild(elementIndex, true);
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1
this._selectChild(elementIndex, true)
}
handleMoveDown = id => {
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
this._selectChild(elementIndex, false);
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1
this._selectChild(elementIndex, false)
}
_selectChild (index, align_top) {
const container = this.column.node;
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
const container = this.column.node
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`)
if (element) {
if (align_top && container.scrollTop > element.offsetTop) {
element.scrollIntoView(true);
element.scrollIntoView(true)
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
element.scrollIntoView(false);
element.scrollIntoView(false)
}
element.focus();
element.focus()
}
}
handleDequeueNotifications = () => {
this.props.dispatch(dequeueNotifications());
};
this.props.dispatch(dequeueNotifications())
}
render () {
const { intl, notifications, isLoading, isUnread, hasMore, showFilterBar, totalQueuedNotificationsCount } = this.props;
const {
intl,
notifications,
isLoading,
isUnread,
hasMore,
showFilterBar,
totalQueuedNotificationsCount
} = this.props
let scrollableContent = null;
let scrollableContent = null
// : todo : include follow requests
@@ -157,7 +166,7 @@ class Notifications extends ImmutablePureComponent {
console.log('filteredNotifications:', filteredNotifications)
if (isLoading && this.scrollableContent) {
scrollableContent = this.scrollableContent;
scrollableContent = this.scrollableContent
} else if (notifications.size > 0 || hasMore) {
scrollableContent = notifications.map((item, index) => item === null ? (
<LoadMore
@@ -169,18 +178,17 @@ class Notifications extends ImmutablePureComponent {
/>
) : (
<NotificationContainer
key={item.get('id')}
key={`notification-${index}`}
notification={item}
accountId={item.get('account')}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
));
))
} else {
scrollableContent = null;
scrollableContent = null
}
this.scrollableContent = scrollableContent;
this.scrollableContent = scrollableContent
return (
<div ref={this.setColumnRef}>
@@ -191,18 +199,20 @@ class Notifications extends ImmutablePureComponent {
itemType='notification'
/>
<ScrollableList
scrollKey='notifications'
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
emptyMessage={<FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />}
onLoadMore={this.handleLoadOlder}
onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll}
>
{ scrollableContent }
</ScrollableList>
<Block>
<ScrollableList
scrollKey='notifications'
isLoading={isLoading}
showLoading={isLoading && notifications.size === 0}
hasMore={hasMore}
emptyMessage={<FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />}
onLoadMore={this.handleLoadOlder}
onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll}
>
{ scrollableContent }
</ScrollableList>
</Block>
</div>
)
}

View File

@@ -1,19 +0,0 @@
import ComposeFormContainer from '../../compose/containers/compose_form_container';
import NotificationsContainer from '../../../containers/notifications_container';
import LoadingBarContainer from '../../../containers/loading_bar_container';
import ModalRoot from '../../../components/modal/modal_root'
export default class Compose extends PureComponent {
render() {
return (
<div>
<ComposeFormContainer />
<NotificationsContainer />
<ModalRoot />
<LoadingBarContainer className='loading-bar' />
</div>
);
}
}

View File

@@ -1 +0,0 @@
export { default } from './compose'

View File

@@ -1,83 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Masonry from 'react-masonry-infinite';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import { expandHashtagTimeline } from '../../../actions/timelines';
import DetailedStatusContainer from '../../../features/status/containers/detailed_status_container';
import ColumnIndicator from '../../../components/column_indicator';
const mapStateToProps = (state, { hashtag }) => ({
statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false),
hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false),
});
export default
@connect(mapStateToProps)
class HashtagTimeline extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool.isRequired,
hasMore: PropTypes.bool.isRequired,
hashtag: PropTypes.string.isRequired,
};
componentDidMount () {
const { dispatch, hashtag } = this.props;
dispatch(expandHashtagTimeline(hashtag));
}
handleLoadMore = () => {
const maxId = this.props.statusIds.last();
if (maxId) {
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
}
}
setRef = c => {
this.masonry = c;
}
handleHeightChange = debounce(() => {
if (!this.masonry) {
return;
}
this.masonry.forcePack();
}, 50)
render () {
const { statusIds, hasMore, isLoading } = this.props;
const sizes = [
{ columns: 1, gutter: 0 },
{ mq: '415px', columns: 1, gutter: 10 },
{ mq: '640px', columns: 2, gutter: 10 },
{ mq: '960px', columns: 3, gutter: 10 },
{ mq: '1255px', columns: 3, gutter: 10 },
];
const loader = (isLoading && statusIds.isEmpty()) ? <ColumnIndicator type='loading' key={0} /> : undefined;
return (
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
{statusIds.map(statusId => (
<div className='statuses-grid__item' key={statusId}>
<DetailedStatusContainer
id={statusId}
compact
measureHeight
onHeightChange={this.handleHeightChange}
/>
</div>
)).toArray()}
</Masonry>
);
}
}

View File

@@ -1 +0,0 @@
export { default } from './hashtag_timeline'

View File

@@ -1 +0,0 @@
export { default } from './public_timeline'

View File

@@ -1,98 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Masonry from 'react-masonry-infinite';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { debounce } from 'lodash';
import { expandPublicTimeline, expandCommunityTimeline } from '../../../actions/timelines';
import DetailedStatusContainer from '../../../features/status/containers/detailed_status_container';
import ColumnIndicator from '../../../components/column_indicator';
const mapStateToProps = (state, { local }) => {
const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
return {
statusIds: timeline.get('items', ImmutableList()),
isLoading: timeline.get('isLoading', false),
hasMore: timeline.get('hasMore', false),
};
};
export default
@connect(mapStateToProps)
class PublicTimeline extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool.isRequired,
hasMore: PropTypes.bool.isRequired,
local: PropTypes.bool,
};
componentDidMount () {
this._connect();
}
componentDidUpdate (prevProps) {
if (prevProps.local !== this.props.local) {
this._connect();
}
}
_connect () {
const { dispatch, local } = this.props;
dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
}
handleLoadMore = () => {
const { dispatch, statusIds, local } = this.props;
const maxId = statusIds.last();
if (maxId) {
dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
}
}
setRef = c => {
this.masonry = c;
}
handleHeightChange = debounce(() => {
if (!this.masonry) {
return;
}
this.masonry.forcePack();
}, 50)
render () {
const { statusIds, hasMore, isLoading } = this.props;
const sizes = [
{ columns: 1, gutter: 0 },
{ mq: '415px', columns: 1, gutter: 10 },
{ mq: '640px', columns: 2, gutter: 10 },
{ mq: '960px', columns: 3, gutter: 10 },
{ mq: '1255px', columns: 3, gutter: 10 },
];
const loader = (isLoading && statusIds.isEmpty()) ? <ColumnIndicator type='loading' key={0} /> : undefined;
return (
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
{statusIds.map(statusId => (
<div className='statuses-grid__item' key={statusId}>
<DetailedStatusContainer
id={statusId}
compact
measureHeight
onHeightChange={this.handleHeightChange}
/>
</div>
)).toArray()}
</Masonry>
);
}
}

View File

@@ -124,7 +124,7 @@ export default class Card extends ImmutablePureComponent {
return (
<div
ref={this.setRef}
className={[_s.default, _s.backgroundcolorSecondary3, _s.positionAbsolute, _s.top0, _s.right0, _s.bottom0, _s.left0, _s.statusCardVideo].join(' ')}
className={[_s.default, _s.backgroundColorSecondary3, _s.positionAbsolute, _s.top0, _s.right0, _s.bottom0, _s.left0, _s.statusCardVideo].join(' ')}
dangerouslySetInnerHTML={content}
/>
)

View File

@@ -1,218 +0,0 @@
import { Link, NavLink } from 'react-router-dom';
import { FormattedDate, FormattedNumber } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import classNames from 'classnames';
import scheduleIdleTask from '../../../../utils/schedule_idle_task';
import StatusQuote from '../../../../components/status_quote';
import Avatar from '../../../../components/avatar';
import DisplayName from '../../../../components/display_name';
import StatusContent from '../../../../components/status_content';
import MediaGallery from '../../../../components/media_gallery';
import Icon from '../../../../components/icon';
import Poll from '../../../../components/poll';
import Card from '../card';
import Video from '../../../video';
export default class DetailedStatus extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
status: ImmutablePropTypes.map,
onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
measureHeight: PropTypes.bool,
onHeightChange: PropTypes.func,
domain: PropTypes.string.isRequired,
compact: PropTypes.bool,
showMedia: PropTypes.bool,
onToggleMediaVisibility: PropTypes.func,
onShowRevisions: PropTypes.func,
};
state = {
height: null,
};
handleShowRevisions = () => {
this.props.onShowRevisions(this.props.status);
}
handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime);
}
handleExpandedToggle = () => {
this.props.onToggleHidden(this.props.status);
}
_measureHeight (heightJustChanged) {
if (this.props.measureHeight && this.node) {
scheduleIdleTask(() => this.node && this.setState({ height: Math.ceil(this.node.scrollHeight) + 1 }));
if (this.props.onHeightChange && heightJustChanged) {
this.props.onHeightChange();
}
}
}
setRef = c => {
this.node = c;
this._measureHeight();
}
componentDidUpdate (prevProps, prevState) {
this._measureHeight(prevState.height !== this.state.height);
}
handleModalLink = e => {
e.preventDefault();
let href;
if (e.target.nodeName !== 'A') {
href = e.target.parentNode.href;
} else {
href = e.target.href;
}
window.open(href, 'gabsocial-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const outerStyle = { boxSizing: 'border-box' };
const { compact } = this.props;
if (!status) {
return null;
}
let media = '';
let applicationLink = '';
let reblogLink = '';
let reblogIcon = 'retweet';
let favouriteLink = '';
if (this.props.measureHeight) {
outerStyle.height = `${this.state.height}px`;
}
if (status.get('poll')) {
media = <Poll pollId={status.get('poll')} />;
} else if (status.get('media_attachments').size > 0) {
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
media = (
<Video
preview={video.get('preview_url')}
blurhash={video.get('blurhash')}
src={video.get('url')}
alt={video.get('description')}
aspectRatio={video.getIn(['meta', 'small', 'aspect'])}
width={300}
height={150}
inline
onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
} else {
media = (
<MediaGallery
standalone
sensitive={status.get('sensitive')}
media={status.get('media_attachments')}
height={300}
onOpenMedia={this.props.onOpenMedia}
visible={this.props.showMedia}
onToggleVisibility={this.props.onToggleMediaVisibility}
/>
);
}
} else if (status.get('spoiler_text').length === 0) {
media = <Card onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} />;
}
if (status.get('application')) {
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
}
if (status.get('visibility') === 'direct') {
reblogIcon = 'envelope';
} else if (status.get('visibility') === 'private') {
reblogIcon = 'lock';
}
if (status.get('visibility') === 'private') {
reblogLink = <Icon id={reblogIcon} />;
} else if (this.context.router) {
reblogLink = (
<Link to={`/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}/reblogs`} className='detailed-status__link'>
<Icon id={reblogIcon} />
<span className='detailed-status__reblogs'>
<FormattedNumber value={status.get('reblogs_count')} />
</span>
</Link>
);
} else {
reblogLink = (
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
<Icon id={reblogIcon} />
<span className='detailed-status__reblogs'>
<FormattedNumber value={status.get('reblogs_count')} />
</span>
</a>
);
}
favouriteLink = (
<span className='detailed-status__link'>
<Icon id='star' />
<span className='detailed-status__favorites'>
<FormattedNumber value={status.get('favourites_count')} />
</span>
</span>
);
return (
<div style={outerStyle}>
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
</NavLink>
{(status.get('group') || status.get('revised_at') !== null) && (
<div className='status__meta'>
{status.get('group') && <React.Fragment>Posted in <NavLink to={`/groups/${status.getIn(['group', 'id'])}`}>{status.getIn(['group', 'title'])}</NavLink></React.Fragment>}
{status.get('revised_at') !== null && <a onClick={this.handleShowRevisions}> Edited</a>}
</div>
)}
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
{media}
{ /* status.get('quote') && <StatusQuote
id={status.get('quote')}
/> */ }
<div className='detailed-status__meta'>
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
</a>{applicationLink} · {reblogLink} · {favouriteLink}
</div>
</div>
</div>
);
}
}

View File

@@ -1,96 +0,0 @@
.detailed-status {
background: lighten($ui-base-color, 4%);
padding: 14px 10px;
&--flex {
@include flex(space-between, flex-start, row, wrap);
.status__content,
.detailed-status__meta {
flex: 100%;
}
}
.status__content {
@include text-sizing(19px, 400, 24px);
.emojione {
margin: -1px 0 0;
@include size(24px);
}
.status__content__spoiler-link {
line-height: 24px;
margin: -1px 0 0;
}
}
.video-player {
margin-top: 8px;
}
&__meta {
margin-top: 15px;
color: $dark-text-color;
@include text-sizing(14px, 400, 18px);
}
&__datetime {
&:hover {
text-decoration: underline;
}
}
&__favorites,
&__reblogs {
display: inline-block;
margin-left: 6px;
@include text-sizing(12px, 500);
}
&__display-name,
&__datetime,
&__application {
text-decoration: none;
}
&__display-name {
&:hover strong {
text-decoration: underline;
}
}
&__application,
&__datetime {
color: inherit;
}
&__display-name {
color: $secondary-text-color;
display: block;
line-height: 24px;
margin-bottom: 15px;
overflow: hidden;
strong,
span {
display: block;
@include text-overflow;
}
strong {
font-size: 16px;
color: $primary-text-color;
}
}
&__display-avatar {
float: left;
margin-right: 10px;
}
}

View File

@@ -1 +0,0 @@
export { default } from './detailed_status'

View File

@@ -1,235 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import { openModal } from '../../../../actions/modal';
import { me, isStaff } from '../../../../initial_state';
import IconButton from '../../../../components/icon_button';
const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' },
edit: { id: 'status.edit', defaultMessage: 'Edit' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Repost' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Repost to original audience' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' },
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
block: { id: 'status.block', defaultMessage: 'Block @{name}' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
share: { id: 'status.share', defaultMessage: 'Share' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
});
const mapDispatchToProps = (dispatch) => ({
onOpenUnauthorizedModal() {
dispatch(openModal('UNAUTHORIZED'));
},
});
export default
@connect(null, mapDispatchToProps)
@injectIntl
class ActionBar extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onQuote: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onMute: PropTypes.func,
onMuteConversation: PropTypes.func,
onBlock: PropTypes.func,
onReport: PropTypes.func,
onPin: PropTypes.func,
onEmbed: PropTypes.func,
intl: PropTypes.object.isRequired,
onOpenUnauthorizedModal: PropTypes.func.isRequired,
};
handleReplyClick = () => {
if (me) {
this.props.onReply(this.props.status);
} else {
this.props.onOpenUnauthorizedModal();
}
}
handleReblogClick = (e) => {
if (me) {
this.props.onReblog(this.props.status, e);
} else {
this.props.onOpenUnauthorizedModal();
}
}
handleQuoteClick = (e) => {
if (me) {
this.props.onQuote(this.props.status, e);
} else {
this.props.onOpenUnauthorizedModal();
}
}
handleFavouriteClick = () => {
if (me) {
this.props.onFavourite(this.props.status);
} else {
this.props.onOpenUnauthorizedModal();
}
}
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
handleEditClick = () => {
this.props.onEdit(this.props.status);
}
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
handleBlockClick = () => {
this.props.onBlock(this.props.status);
}
handleReport = () => {
this.props.onReport(this.props.status);
}
handlePinClick = () => {
this.props.onPin(this.props.status);
}
handleShare = () => {
navigator.share({
text: this.props.status.get('search_index'),
url: this.props.status.get('url'),
});
}
handleEmbed = () => {
this.props.onEmbed(this.props.status);
}
handleCopy = () => {
const url = this.props.status.get('url');
const textarea = document.createElement('textarea');
textarea.textContent = url;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
} catch (e) {
} finally {
document.body.removeChild(textarea);
}
}
render () {
const { status, intl } = this.props;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const mutingConversation = status.get('muted');
let menu = [];
if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
menu.push(null);
}
if (me === status.getIn(['account', 'id'])) {
if (publicStatus) {
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
}
}
menu.push(null);
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: intl.formatMessage(messages.edit), action: this.handleEditClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
if (isStaff) {
menu.push(null);
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
}
}
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<div className='detailed-status-action-bar__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
);
let replyIcon;
if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply';
} else {
replyIcon = 'reply-all';
}
let reblogIcon = 'retweet';
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
return (
<div className='detailed-status__action-bar'>
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} active={status.get('reblogged')} title={reblog_disabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton disabled={reblog_disabled} title={reblog_disabled ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-left' onClick={this.handleQuoteClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
{shareButton}
<div className='detailed-status-action-bar__dropdown'>
<DropdownMenuContainer size={18} icon='ellipsis-h' items={menu} direction='left' title='More' />
</div>
</div>
);
}
}

View File

@@ -1,20 +0,0 @@
.detailed-status-action-bar {
display: flex;
flex-direction: row;
padding: 10px 0;
background: lighten($ui-base-color, 4%);
border-top: 1px solid lighten($ui-base-color, 8%);
border-bottom: 1px solid lighten($ui-base-color, 8%);
&__button {
flex: 1 1 auto;
text-align: center;
}
&__dropdown {
flex: 1 1 auto;
position: relative;
@include flex(center, center);
}
}

View File

@@ -1 +0,0 @@
export { default } from './detailed_status_action_bar'

View File

@@ -467,7 +467,7 @@ class Status extends ImmutablePureComponent {
<HotKeys handlers={handlers}>
<div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<DetailedStatus
{ /* <DetailedStatus
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
@@ -475,6 +475,12 @@ class Status extends ImmutablePureComponent {
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
/> */ }
<StatusContainer
id={status.get('id')}
contextType={'timelineId'}
showThread
/>
<ActionBar

View File

@@ -30,38 +30,39 @@ import HomePage from '../../pages/home_page'
import NotificationsPage from '../../pages/notifications_page'
import ListPage from '../../pages/list_page'
import ListsPage from '../../pages/lists_page'
import BasicPage from '../../pages/basic_page'
import SettingsPage from '../../pages/settings_page'
// import GroupSidebarPanel from '../groups/sidebar_panel'
import {
Status,
// GettingStarted,
CommunityTimeline,
AccountGallery,
AccountTimeline,
// AccountGallery,
HomeTimeline,
Blocks,
CommunityTimeline,
DomainBlocks,
Explore,
Favorites,
FavoritedStatuses,
Followers,
Following,
// Reblogs,
// DirectTimeline,
// HashtagTimeline,
Notifications,
// FollowRequests,
FollowRequests,
GenericNotFound,
FavoritedStatuses,
// Blocks,
// DomainBlocks,
// Mutes,
Search,
// Explore,
GettingStarted,
GroupsCollection,
GroupTimeline,
ListTimeline,
ListsDirectory,
GroupMembers,
GroupRemovedAccounts,
GroupCreate,
GroupEdit,
GroupMembers,
GroupRemovedAccounts,
GroupTimeline,
HashtagTimeline,
HomeTimeline,
ListsDirectory,
ListTimeline,
Mutes,
Notifications,
Reblogs,
Search,
Status,
} from './util/async-components'
import { me, meUsername } from '../../initial_state'
@@ -86,7 +87,6 @@ const keyMap = {
new: 'n',
search: 's',
forceNew: 'option+n',
focusColumn: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
reply: 'r',
Favorite: 'f',
boost: 'b',
@@ -100,13 +100,10 @@ const keyMap = {
goToNotifications: 'g n',
goToStart: 'g s',
goToFavorites: 'g f',
goToPinned: 'g p',
goToProfile: 'g u',
goToBlocked: 'g b',
goToMuted: 'g m',
goToRequests: 'g r',
toggleHidden: 'x',
toggleSensitive: 'h',
}
class SwitchingArea extends PureComponent {
@@ -146,7 +143,7 @@ class SwitchingArea extends PureComponent {
<Redirect from='/' to='/home' exact />
<WrappedRoute path='/home' exact page={HomePage} component={HomeTimeline} content={children} />
<WrappedRoute path='/timeline/all' exact page={HomePage} component={CommunityTimeline} content={children} />
<WrappedRoute path='/timeline/all' exact page={BasicPage} component={CommunityTimeline} content={children} componentParams={{ title: 'Hashtag Timeline' }} />
<WrappedRoute path='/groups' exact page={GroupsPage} component={GroupsCollection} content={children} componentParams={{ activeTab: 'featured' }} />
<WrappedRoute path='/groups/browse/member' exact page={GroupsPage} component={GroupsCollection} content={children} componentParams={{ activeTab: 'member' }} />
@@ -158,7 +155,7 @@ class SwitchingArea extends PureComponent {
<WrappedRoute path='/groups/:id/edit' page={GroupPage} component={GroupEdit} content={children} />
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
{ /* <WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} /> */}
<WrappedRoute path='/tags/:id' publicRoute page={BasicPage} component={HashtagTimeline} content={children} componentParams={{ title: 'Hashtag' }} />
<WrappedRoute path='/lists' exact page={ListsPage} component={ListsDirectory} content={children} />
<WrappedRoute path='/list/:id' page={ListPage} component={ListTimeline} content={children} />
@@ -171,15 +168,15 @@ class SwitchingArea extends PureComponent {
<WrappedRoute path='/search/groups' exact page={SearchPage} component={Search} content={children} />
{/*
<WrappedRoute path='/settings/account' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/profile' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/account' exact page={SettingsPage} component={AccountSettings} content={children} />
<WrappedRoute path='/settings/profile' exact page={SettingsPage} component={ProfileSettings} content={children} />
<WrappedRoute path='/settings/domain_blocks' exact page={SettingsPage} component={DomainBlocks} content={children} />
<WrappedRoute path='/settings/relationships' exact page={SettingsPage} component={DomainBlocks} content={children} />
<WrappedRoute path='/settings/filters' exact page={SettingsPage} component={DomainBlocks} content={children} />
<WrappedRoute path='/settings/relationships' exact page={SettingsPage} component={RelationshipSettings} content={children} />
<WrappedRoute path='/settings/filters' exact page={SettingsPage} component={Filters} content={children} />
<WrappedRoute path='/settings/blocks' exact page={SettingsPage} component={Blocks} content={children} />
<WrappedRoute path='/settings/mutes' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/development' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/billing' exact page={SettingsPage} component={Mutes} content={children} />
<WrappedRoute path='/settings/development' exact page={SettingsPage} component={Development} content={children} />
<WrappedRoute path='/settings/billing' exact page={SettingsPage} component={Billing} content={children} />
*/ }
<Redirect from='/@:username' to='/:username' exact />
@@ -193,22 +190,25 @@ class SwitchingArea extends PureComponent {
<Redirect from='/@:username/following' to='/:username/following' />
<WrappedRoute path='/:username/following' page={ProfilePage} component={Following} content={children} />
{ /*
<Redirect from='/@:username/media' to='/:username/media' />
<WrappedRoute path='/:username/media' component={AccountGallery} page={ProfilePage} content={children} />
*/ }
<Redirect from='/@:username/photos' to='/:username/photos' />
<WrappedRoute path='/:username/photos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ mediaType: 'photo' }} />
<Redirect from='/@:username/videos' to='/:username/videos' />
<WrappedRoute path='/:username/videos' page={ProfilePage} component={AccountGallery} content={children} componentParams={{ mediaType: 'video' }} />
<Redirect from='/@:username/favorites' to='/:username/favorites' />
<WrappedRoute path='/:username/favorites' page={ProfilePage} component={FavoritedStatuses} content={children} />
{ /*
<Redirect from='/@:username/posts/:statusId' to='/:username/posts/:statusId' exact />
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact component={Status} content={children} />
*/ }
<WrappedRoute path='/:username/posts/:statusId' publicRoute exact page={BasicPage} component={Status} content={children} componentParams={{ title: 'Status' }} />
{ /*
<Redirect from='/@:username/posts/:statusId/reblogs' to='/:username/posts/:statusId/reblogs' />
<WrappedRoute path='/:username/posts/:statusId/reblogs' component={Reblogs} content={children} />
*/}
<WrappedRoute path='/:username/posts/:statusId/reblogs' page={BasicPage} component={Reblogs} content={children} componentParams={{ title: 'Reblogs' }} />
<Redirect from='/@:username/posts/:statusId/favorites' to='/:username/posts/:statusId/favorites' />
<WrappedRoute path='/:username/posts/:statusId/favorites' page={BasicPage} component={Favorites} content={children} componentParams={{ title: 'Favorites' }} />
<WrappedRoute page={ErrorPage} component={GenericNotFound} content={children} />
</Switch>
)
@@ -273,7 +273,7 @@ class UI extends PureComponent {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files')) {
this.setState({
draggingOver: true
draggingOver: true,
})
}
}
@@ -417,24 +417,6 @@ class UI extends PureComponent {
this.props.dispatch(resetCompose())
}
handleHotkeyFocusColumn = e => {
const index = (e.key * 1) + 1 // First child is drawer, skip that
const column = this.node.querySelector(`.column:nth-child(${index})`)
if (!column) return
const container = column.querySelector('.scrollable')
if (container) {
const status = container.querySelector('.focusable')
if (status) {
if (container.scrollTop > status.offsetTop) {
status.scrollIntoView(true)
}
status.focus()
}
}
}
handleHotkeyBack = () => {
if (window.history && window.history.length === 1) {
this.context.router.history.push('/home') // homehack
@@ -467,10 +449,6 @@ class UI extends PureComponent {
this.context.router.history.push(`/${meUsername}/favorites`)
}
handleHotkeyGoToPinned = () => {
this.context.router.history.push(`/${meUsername}/pins`)
}
handleHotkeyGoToProfile = () => {
this.context.router.history.push(`/${meUsername}`)
}
@@ -500,13 +478,11 @@ class UI extends PureComponent {
new: this.handleHotkeyNew,
search: this.handleHotkeySearch,
forceNew: this.handleHotkeyForceNew,
focusColumn: this.handleHotkeyFocusColumn,
back: this.handleHotkeyBack,
goToHome: this.handleHotkeyGoToHome,
goToNotifications: this.handleHotkeyGoToNotifications,
goToStart: this.handleHotkeyGoToStart,
goToFavorites: this.handleHotkeyGoToFavorites,
goToPinned: this.handleHotkeyGoToPinned,
goToProfile: this.handleHotkeyGoToProfile,
goToBlocked: this.handleHotkeyGoToBlocked,
goToMuted: this.handleHotkeyGoToMuted,

View File

@@ -1,63 +1,3 @@
export function EmojiPicker() {
return import(/* webpackChunkName: "emoji_picker" */'../../../components/emoji/emoji_picker')
}
export function Compose() {
return import(/* webpackChunkName: "features/compose" */'../../compose')
}
export function Notifications() {
return import(/* webpackChunkName: "features/notifications" */'../../notifications')
}
export function HomeTimeline() {
return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline')
}
export function CommunityTimeline() {
return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline')
}
export function HashtagTimeline() {
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline')
}
export function ListTimeline() {
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline')
}
export function GroupTimeline() {
return import(/* webpackChunkName: "features/group_timeline" */'../../group_timeline')
}
export function GroupMembers() {
return import(/* webpackChunkName: "features/group_members" */'../../group_members')
}
export function GroupRemovedAccounts() {
return import(/* webpackChunkName: "features/group_removed_accounts" */'../../group_removed_accounts')
}
export function GroupCreate() {
return import(/* webpackChunkName: "features/groups_create" */'../../group_create')
}
export function GroupEdit() {
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/edit')
}
export function GroupsCollection() {
return import(/* webpackChunkName: "features/groups_collection" */'../../g../../groups_collection')
}
export function ListsDirectory() {
return import(/* webpackChunkName: "features/lists_directory" */'../../lists_directory')
}
export function Status() {
return import(/* webpackChunkName: "features/status" */'../../status')
}
export function AccountTimeline() {
return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline')
}
@@ -66,6 +6,30 @@ export function AccountGallery() {
return import(/* webpackChunkName: "features/account_gallery" */'../../account_gallery')
}
export function Blocks() {
return import(/* webpackChunkName: "features/blocks" */'../../blocks')
}
export function CommunityTimeline() {
return import(/* webpackChunkName: "features/community_timeline" */'../../community_timeline')
}
export function Compose() {
return import(/* webpackChunkName: "features/compose" */'../../compose')
}
export function DomainBlocks() {
return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks')
}
export function EmbedModal() {
return import(/* webpackChunkName: "modals/embed_modal" */'../../../components/modal/embed_modal')
}
export function EmojiPicker() {
return import(/* webpackChunkName: "emoji_picker" */'../../../components/emoji/emoji_picker')
}
export function Followers() {
return import(/* webpackChunkName: "features/followers" */'../../followers')
}
@@ -74,28 +38,68 @@ export function Following() {
return import(/* webpackChunkName: "features/following" */'../../following')
}
export function Reblogs() {
return import(/* webpackChunkName: "features/reblogs" */'../../reblogs')
}
export function FollowRequests() {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests')
}
export function GenericNotFound() {
return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found')
}
export function FavoritedStatuses() {
return import(/* webpackChunkName: "features/favorited_statuses" */'../../favorited_statuses')
}
export function Blocks() {
return import(/* webpackChunkName: "features/blocks" */'../../blocks')
export function GenericNotFound() {
return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found')
}
export function DomainBlocks() {
return import(/* webpackChunkName: "features/domain_blocks" */'../../domain_blocks')
export function GroupsCollection() {
return import(/* webpackChunkName: "features/groups_collection" */'../../g../../groups_collection')
}
export function GroupCreate() {
return import(/* webpackChunkName: "features/groups_create" */'../../group_create')
}
export function GroupEdit() {
return import(/* webpackChunkName: "features/groups/timeline" */'../../groups/edit')
}
export function GroupMembers() {
return import(/* webpackChunkName: "features/group_members" */'../../group_members')
}
export function GroupRemovedAccounts() {
return import(/* webpackChunkName: "features/group_removed_accounts" */'../../group_removed_accounts')
}
export function GroupTimeline() {
return import(/* webpackChunkName: "features/group_timeline" */'../../group_timeline')
}
export function HashtagTimeline() {
return import(/* webpackChunkName: "features/hashtag_timeline" */'../../hashtag_timeline')
}
export function HomeTimeline() {
return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline')
}
export function ListAdder() {
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder')
}
export function ListsDirectory() {
return import(/* webpackChunkName: "features/lists_directory" */'../../lists_directory')
}
export function ListEditor() {
return import(/* webpackChunkName: "features/list_editor" */'../../list_editor')
}
export function ListTimeline() {
return import(/* webpackChunkName: "features/list_timeline" */'../../list_timeline')
}
export function MediaGallery() {
return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery')
}
export function Mutes() {
@@ -106,34 +110,30 @@ export function MuteModal() {
return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/mute_modal')
}
export function StatusRevisionModal() {
return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/status_revision_modal')
export function Notifications() {
return import(/* webpackChunkName: "features/notifications" */'../../notifications')
}
export function Reblogs() {
return import(/* webpackChunkName: "features/reblogs" */'../../reblogs')
}
export function ReportModal() {
return import(/* webpackChunkName: "modals/report_modal" */'../../../components/modal/report_modal')
}
export function MediaGallery() {
return import(/* webpackChunkName: "status/media_gallery" */'../../../components/media_gallery')
export function Search() {
return import(/*webpackChunkName: "features/search" */'../../search')
}
export function Status() {
return import(/* webpackChunkName: "features/status" */'../../status')
}
export function StatusRevisionModal() {
return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/status_revision_modal')
}
export function Video() {
return import(/* webpackChunkName: "features/video" */'../../video')
}
export function EmbedModal() {
return import(/* webpackChunkName: "modals/embed_modal" */'../../../components/modal/embed_modal')
}
export function ListEditor() {
return import(/* webpackChunkName: "features/list_editor" */'../../list_editor')
}
export function ListAdder() {
return import(/*webpackChunkName: "features/list_adder" */'../../list_adder')
}
export function Search() {
return import(/*webpackChunkName: "features/search" */'../../search')
}

View File

@@ -19,13 +19,13 @@ export default class DefaultLayout extends PureComponent {
// const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <button key='floating-action-button' onClick={this.handleOpenComposeModal} className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}></button>;
return (
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundColorSecondary3].join(' ')}>
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundColorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.paddingLeft15PX, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
<div className={[_s.default, _s.width645PX].join(' ')}>
<ColumnHeader

View File

@@ -44,13 +44,13 @@ export default class GroupLayout extends ImmutablePureComponent {
// const !!relationships && relationships.get('member')
return (
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundColorSecondary3].join(' ')}>
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundColorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.paddingLeft15PX, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
<div className={[_s.default, _s.width645PX].join(' ')}>
<ColumnHeader

View File

@@ -43,7 +43,7 @@ export default class ProfileLayout extends ImmutablePureComponent {
]
return (
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundColorSecondary3].join(' ')}>
<Sidebar />

View File

@@ -18,13 +18,13 @@ export default class SearchLayout extends PureComponent {
// const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <button key='floating-action-button' onClick={this.handleOpenComposeModal} className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}></button>;
return (
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundColorSecondary3].join(' ')}>
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundColorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.paddingLeft15PX, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
<div className={[_s.default, _s.width645PX].join(' ')}>
<ColumnHeader

View File

@@ -10,6 +10,7 @@ const perf = require('./performance');
function main() {
perf.start('main()');
// : todo :
// if (window.history && history.replaceState) {
// const { pathname, search, hash } = window.location;
// const path = pathname + search + hash;

View File

@@ -0,0 +1,32 @@
import { Fragment } from 'react'
import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout'
export default class BasicPage extends PureComponent {
static propTypes = {
title: PropTypes.string,
children: PropTypes.node,
}
render() {
const { children, title } = this.props
return (
<DefaultLayout
title={title}
layout={(
<Fragment>
<TrendsPanel />
<WhoToFollowPanel />
<LinkFooter />
</Fragment>
)}
showBackBtn
>
{children}
</DefaultLayout>
)
}
}

View File

@@ -1,6 +1,7 @@
import { Fragment } from 'react'
import LinkFooter from '../components/link_footer'
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
import NotificationFilterPanel from '../components/panel/notification_filter_panel'
import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout'
@@ -13,6 +14,7 @@ export default class NotificationsPage extends PureComponent {
title='Notifications'
layout={(
<Fragment>
<NotificationFilterPanel />
<TrendsPanel />
<WhoToFollowPanel />
<LinkFooter />

View File

@@ -59,10 +59,14 @@ const normalizeNotification = (state, notification) => {
const expandNormalizedNotifications = (state, notifications, next) => {
let items = ImmutableList();
// : todo filter notiications here:
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(n));
});
console.log("reducer notifications:", notifications)
return state.withMutations(mutable => {
if (!items.isEmpty()) {
mutable.update('items', list => {
@@ -74,7 +78,9 @@ const expandNormalizedNotifications = (state, notifications, next) => {
item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0
);
return list.take(firstIndex).concat(items, list.skip(lastIndex));
const pop = list.take(firstIndex).concat(items, list.skip(lastIndex));
console.log("pop:", pop)
return pop
});
}