Progress
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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
|
||||
|
||||
35
app/javascript/gabsocial/components/comment.js
Normal file
35
app/javascript/gabsocial/components/comment.js
Normal 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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
{ /* */ }
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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')}`;
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
70
app/javascript/gabsocial/features/favorites/favorites.js
Normal file
70
app/javascript/gabsocial/features/favorites/favorites.js
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
1
app/javascript/gabsocial/features/favorites/index.js
Normal file
1
app/javascript/gabsocial/features/favorites/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './favorites'
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
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'>
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './compose'
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './hashtag_timeline'
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './public_timeline'
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './detailed_status'
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './detailed_status_action_bar'
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
32
app/javascript/gabsocial/pages/basic_page.js
Normal file
32
app/javascript/gabsocial/pages/basic_page.js
Normal 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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user