Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
@@ -0,0 +1,77 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/threading/ThreadViewer should match snapshot 1`] = `
|
||||
<Fragment>
|
||||
<div
|
||||
className="ThreadViewer"
|
||||
>
|
||||
<div
|
||||
className="post-right-comments-container"
|
||||
>
|
||||
<FileUploadOverlay
|
||||
overlayType="right"
|
||||
/>
|
||||
<DeferredRenderWrapper
|
||||
channel={
|
||||
Object {
|
||||
"create_at": 0,
|
||||
"creator_id": "",
|
||||
"delete_at": 0,
|
||||
"display_name": "",
|
||||
"group_constrained": false,
|
||||
"header": "",
|
||||
"id": "channel_id",
|
||||
"last_post_at": 0,
|
||||
"last_root_post_at": 0,
|
||||
"name": "",
|
||||
"purpose": "",
|
||||
"scheme_id": "",
|
||||
"status": "",
|
||||
"team_id": "team_id",
|
||||
"teammate_id": "",
|
||||
"type": "O",
|
||||
"update_at": 0,
|
||||
}
|
||||
}
|
||||
isThreadView={false}
|
||||
key="id"
|
||||
onCardClick={[Function]}
|
||||
postIds={
|
||||
Array [
|
||||
"id",
|
||||
]
|
||||
}
|
||||
selected={
|
||||
Object {
|
||||
"channel_id": "channel_id",
|
||||
"create_at": 1502715365009,
|
||||
"delete_at": 0,
|
||||
"edit_at": 0,
|
||||
"hashtags": "",
|
||||
"id": "id",
|
||||
"is_following": true,
|
||||
"is_pinned": false,
|
||||
"message": "post message",
|
||||
"metadata": Object {
|
||||
"embeds": Array [],
|
||||
"emojis": Array [],
|
||||
"files": Array [],
|
||||
"images": Object {},
|
||||
"reactions": Array [],
|
||||
},
|
||||
"original_id": "",
|
||||
"pending_post_id": "",
|
||||
"props": Object {},
|
||||
"reply_count": 3,
|
||||
"root_id": "",
|
||||
"type": "system_add_remove",
|
||||
"update_at": 1502715372443,
|
||||
"user_id": "user_id",
|
||||
}
|
||||
}
|
||||
useRelativeTimestamp={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
`;
|
||||
89
webapp/channels/src/components/threading/thread_viewer/index.ts
Обычный файл
89
webapp/channels/src/components/threading/thread_viewer/index.ts
Обычный файл
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getPost, makeGetPostIdsForThread} from 'mattermost-redux/selectors/entities/posts';
|
||||
import {getThread} from 'mattermost-redux/selectors/entities/threads';
|
||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {appsEnabled} from 'mattermost-redux/selectors/entities/apps';
|
||||
|
||||
import {removePost, getNewestPostThread, getPostThread} from 'mattermost-redux/actions/posts';
|
||||
import {getThread as fetchThread, updateThreadRead} from 'mattermost-redux/actions/threads';
|
||||
|
||||
import {GenericAction} from 'mattermost-redux/types/actions';
|
||||
import {UserThread} from '@mattermost/types/threads';
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {getSocketStatus} from 'selectors/views/websocket';
|
||||
import {selectPostCard} from 'actions/views/rhs';
|
||||
import {getHighlightedPostId, getSelectedPostFocussedAt} from 'selectors/rhs';
|
||||
import {updateThreadLastOpened} from 'actions/views/threads';
|
||||
import {GlobalState} from 'types/store';
|
||||
|
||||
import {fetchRHSAppsBindings} from 'mattermost-redux/actions/apps';
|
||||
|
||||
import ThreadViewer from './thread_viewer';
|
||||
|
||||
type OwnProps = {
|
||||
rootPostId: string;
|
||||
};
|
||||
|
||||
function makeMapStateToProps() {
|
||||
const getPostIdsForThread = makeGetPostIdsForThread();
|
||||
const getChannel = makeGetChannel();
|
||||
|
||||
return function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const currentTeamId = getCurrentTeamId(state);
|
||||
const selected = getPost(state, ownProps.rootPostId);
|
||||
const socketStatus = getSocketStatus(state);
|
||||
const highlightedPostId = getHighlightedPostId(state);
|
||||
const selectedPostFocusedAt = getSelectedPostFocussedAt(state);
|
||||
|
||||
let postIds: string[] = [];
|
||||
let userThread: UserThread | null = null;
|
||||
let channel: Channel | null = null;
|
||||
|
||||
if (selected) {
|
||||
postIds = getPostIdsForThread(state, selected.id);
|
||||
userThread = getThread(state, selected.id);
|
||||
channel = getChannel(state, {id: selected.channel_id});
|
||||
}
|
||||
|
||||
return {
|
||||
isCollapsedThreadsEnabled: isCollapsedThreadsEnabled(state),
|
||||
appsEnabled: appsEnabled(state),
|
||||
currentUserId,
|
||||
currentTeamId,
|
||||
userThread,
|
||||
selected,
|
||||
postIds,
|
||||
socketConnectionStatus: socketStatus.connected,
|
||||
channel,
|
||||
highlightedPostId,
|
||||
selectedPostFocusedAt,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
fetchRHSAppsBindings,
|
||||
getNewestPostThread,
|
||||
getPostThread,
|
||||
getThread: fetchThread,
|
||||
removePost,
|
||||
selectPostCard,
|
||||
updateThreadLastOpened,
|
||||
updateThreadRead,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(ThreadViewer);
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
.ThreadViewer {
|
||||
// negate conflicting css for timestamp
|
||||
.post:not(.same--root.same--user) a.post__permalink {
|
||||
position: initial !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.new-separator {
|
||||
.NotificationSeparator {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
& + .post {
|
||||
padding-top: 1em !important;
|
||||
}
|
||||
}
|
||||
|
||||
.post-list__dynamic--RHS {
|
||||
scrollbar-color: var(--center-channel-color-32) #fff0;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.post-right-comments-container > .new-separator,
|
||||
.post + .new-separator {
|
||||
.NotificationSeparator {
|
||||
padding-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.Separator + .new-separator {
|
||||
.NotificationSeparator {
|
||||
padding-top: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.channel-archived-warning__container {
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.channel-archived-warning__content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
padding-right: 47.5px;
|
||||
padding-left: 46.5px;
|
||||
margin: 0 auto;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
margin-right: 3.4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
import {Post} from '@mattermost/types/posts';
|
||||
import {UserThread} from '@mattermost/types/threads';
|
||||
|
||||
import {TestHelper} from 'utils/test_helper';
|
||||
import {fakeDate} from 'tests/helpers/date';
|
||||
|
||||
import {FakePost} from 'types/store/rhs';
|
||||
|
||||
import ThreadViewer, {Props} from './thread_viewer';
|
||||
|
||||
describe('components/threading/ThreadViewer', () => {
|
||||
const post: Post = TestHelper.getPostMock({
|
||||
channel_id: 'channel_id',
|
||||
create_at: 1502715365009,
|
||||
update_at: 1502715372443,
|
||||
is_following: true,
|
||||
reply_count: 3,
|
||||
});
|
||||
|
||||
const fakePost: FakePost = {
|
||||
id: post.id,
|
||||
exists: true,
|
||||
type: post.type,
|
||||
user_id: post.user_id,
|
||||
channel_id: post.channel_id,
|
||||
message: post.message,
|
||||
};
|
||||
|
||||
const channel: Channel = TestHelper.getChannelMock({
|
||||
display_name: '',
|
||||
name: '',
|
||||
header: '',
|
||||
purpose: '',
|
||||
creator_id: '',
|
||||
scheme_id: '',
|
||||
teammate_id: '',
|
||||
status: '',
|
||||
});
|
||||
|
||||
const actions = {
|
||||
removePost: jest.fn(),
|
||||
selectPostCard: jest.fn(),
|
||||
getNewestPostThread: jest.fn(),
|
||||
getPostThread: jest.fn(),
|
||||
getThread: jest.fn(),
|
||||
updateThreadRead: jest.fn(),
|
||||
updateThreadLastOpened: jest.fn(),
|
||||
fetchRHSAppsBindings: jest.fn(),
|
||||
};
|
||||
|
||||
const baseProps: Props = {
|
||||
selected: post,
|
||||
channel,
|
||||
currentUserId: 'user_id',
|
||||
currentTeamId: 'team_id',
|
||||
socketConnectionStatus: true,
|
||||
actions,
|
||||
isCollapsedThreadsEnabled: false,
|
||||
postIds: [post.id],
|
||||
appsEnabled: true,
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
const reset = fakeDate(new Date(1502715365000));
|
||||
|
||||
const wrapper = shallow(
|
||||
<ThreadViewer {...baseProps}/>,
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve));
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
reset();
|
||||
});
|
||||
|
||||
test('should make api call to get thread posts on socket reconnect', () => {
|
||||
const wrapper = shallow(
|
||||
<ThreadViewer {...baseProps}/>,
|
||||
);
|
||||
|
||||
wrapper.setProps({socketConnectionStatus: false});
|
||||
wrapper.setProps({socketConnectionStatus: true});
|
||||
|
||||
return expect(actions.getPostThread).toHaveBeenCalledWith(post.id, true);
|
||||
});
|
||||
|
||||
test('should not break if root post is a fake post', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
selected: fakePost,
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
shallow(<ThreadViewer {...props}/>);
|
||||
}).not.toThrowError("Cannot read property 'reply_count' of undefined");
|
||||
});
|
||||
|
||||
test('should call fetchThread when no thread on mount', (done) => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
|
||||
process.nextTick(() => {
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
expect(actions.getThread).toHaveBeenCalledWith('user_id', 'team_id', 'id', true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should call updateThreadLastOpened on mount', () => {
|
||||
jest.useFakeTimers('modern').setSystemTime(400);
|
||||
const {actions} = baseProps;
|
||||
const userThread = {
|
||||
id: 'id',
|
||||
last_viewed_at: 42,
|
||||
last_reply_at: 32,
|
||||
} as UserThread;
|
||||
|
||||
shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
userThread={userThread}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateThreadLastOpened and updateThreadRead on mount when unread replies', () => {
|
||||
jest.useFakeTimers('modern').setSystemTime(400);
|
||||
const {actions} = baseProps;
|
||||
const userThread = {
|
||||
id: 'id',
|
||||
last_viewed_at: 42,
|
||||
last_reply_at: 142,
|
||||
} as UserThread;
|
||||
|
||||
shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
userThread={userThread}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(3);
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).toHaveBeenCalledWith('user_id', 'team_id', 'id', 400);
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should call updateThreadLastOpened and updateThreadRead upon thread id change', (done) => {
|
||||
jest.useRealTimers();
|
||||
const dateNowOrig = Date.now;
|
||||
Date.now = () => new Date(400).getMilliseconds();
|
||||
const {actions} = baseProps;
|
||||
|
||||
const userThread = {
|
||||
id: 'id',
|
||||
last_viewed_at: 42,
|
||||
last_reply_at: 142,
|
||||
} as UserThread;
|
||||
|
||||
const wrapper = shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
isCollapsedThreadsEnabled={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect.assertions(6);
|
||||
process.nextTick(() => {
|
||||
expect(actions.updateThreadLastOpened).not.toHaveBeenCalled();
|
||||
expect(actions.updateThreadRead).not.toHaveBeenCalled();
|
||||
expect(actions.getThread).toHaveBeenCalled();
|
||||
|
||||
jest.resetAllMocks();
|
||||
wrapper.setProps({userThread});
|
||||
|
||||
expect(actions.updateThreadLastOpened).toHaveBeenCalledWith('id', 42);
|
||||
expect(actions.updateThreadRead).toHaveBeenCalledWith('user_id', 'team_id', 'id', 400);
|
||||
expect(actions.getThread).not.toHaveBeenCalled();
|
||||
Date.now = dateNowOrig;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('should call fetchRHSAppsBindings on mount if appsEnabled', () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actions.fetchRHSAppsBindings).toHaveBeenCalledWith('channel_id', 'id');
|
||||
});
|
||||
|
||||
test('should not call fetchRHSAppsBindings on mount if not appsEnabled', () => {
|
||||
const {actions} = baseProps;
|
||||
|
||||
shallow(
|
||||
<ThreadViewer
|
||||
{...baseProps}
|
||||
appsEnabled={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(actions.fetchRHSAppsBindings).not.toHaveBeenCalledWith('channel_id', 'id');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,234 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {HTMLAttributes} from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import {ActionFunc} from 'mattermost-redux/types/actions';
|
||||
import {ExtendedPost} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import deferComponentRender from 'components/deferComponentRender';
|
||||
import FileUploadOverlay from 'components/file_upload_overlay';
|
||||
import LoadingScreen from 'components/loading_screen';
|
||||
|
||||
import {FakePost} from 'types/store/rhs';
|
||||
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
import {Post} from '@mattermost/types/posts';
|
||||
import {UserThread} from '@mattermost/types/threads';
|
||||
|
||||
import ThreadViewerVirtualized from '../virtualized_thread_viewer';
|
||||
|
||||
import './thread_viewer.scss';
|
||||
|
||||
const DeferredThreadViewerVirt = deferComponentRender(ThreadViewerVirtualized);
|
||||
|
||||
type Attrs = Pick<HTMLAttributes<HTMLDivElement>, 'className' | 'id'>;
|
||||
|
||||
export type Props = Attrs & {
|
||||
isCollapsedThreadsEnabled: boolean;
|
||||
appsEnabled: boolean;
|
||||
userThread?: UserThread | null;
|
||||
channel: Channel | null;
|
||||
selected: Post | FakePost;
|
||||
previousRhsState?: string;
|
||||
currentUserId: string;
|
||||
currentTeamId: string;
|
||||
socketConnectionStatus: boolean;
|
||||
actions: {
|
||||
fetchRHSAppsBindings: (channelId: string, rootID: string) => unknown;
|
||||
getNewestPostThread: (rootId: string) => Promise<any>|ActionFunc;
|
||||
getPostThread: (rootId: string, fetchThreads: boolean) => Promise<any>|ActionFunc;
|
||||
getThread: (userId: string, teamId: string, threadId: string, extended: boolean) => Promise<any>|ActionFunc;
|
||||
removePost: (post: ExtendedPost) => void;
|
||||
selectPostCard: (post: Post) => void;
|
||||
updateThreadLastOpened: (threadId: string, lastViewedAt: number) => unknown;
|
||||
updateThreadRead: (userId: string, teamId: string, threadId: string, timestamp: number) => unknown;
|
||||
};
|
||||
useRelativeTimestamp?: boolean;
|
||||
postIds: string[];
|
||||
highlightedPostId?: Post['id'];
|
||||
selectedPostFocusedAt?: number;
|
||||
isThreadView?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export default class ThreadViewer extends React.PureComponent<Props, State> {
|
||||
public constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (this.props.isCollapsedThreadsEnabled && this.props.userThread !== null) {
|
||||
this.markThreadRead();
|
||||
}
|
||||
|
||||
this.onInit();
|
||||
|
||||
if (this.props.appsEnabled) {
|
||||
this.props.actions.fetchRHSAppsBindings(this.props.channel?.id || '', this.props.selected.id);
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: Props) {
|
||||
const reconnected = this.props.socketConnectionStatus && !prevProps.socketConnectionStatus;
|
||||
|
||||
if (!this.props.selected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedChanged = this.props.selected.id !== prevProps.selected.id;
|
||||
|
||||
if (reconnected || selectedChanged) {
|
||||
this.onInit(reconnected);
|
||||
}
|
||||
|
||||
if (
|
||||
this.props.isCollapsedThreadsEnabled &&
|
||||
this.props.userThread?.id !== prevProps.userThread?.id
|
||||
) {
|
||||
this.markThreadRead();
|
||||
}
|
||||
|
||||
if (this.props.appsEnabled && (
|
||||
this.props.channel?.id !== prevProps.channel?.id || this.props.selected.id !== prevProps.selected.id
|
||||
)) {
|
||||
this.props.actions.fetchRHSAppsBindings(this.props.channel?.id || '', this.props.selected.id);
|
||||
}
|
||||
}
|
||||
|
||||
public morePostsToFetch(): boolean {
|
||||
const replyCount = this.getReplyCount();
|
||||
return this.props.selected && this.props.postIds.length < (replyCount + 1);
|
||||
}
|
||||
|
||||
public getReplyCount(): number {
|
||||
return (this.props.selected as Post)?.reply_count || this.props.userThread?.reply_count || 0;
|
||||
}
|
||||
|
||||
fetchThread() {
|
||||
const {
|
||||
actions: {
|
||||
getThread,
|
||||
},
|
||||
currentUserId,
|
||||
currentTeamId,
|
||||
selected,
|
||||
} = this.props;
|
||||
|
||||
if (selected && this.getReplyCount() && (this.props.selected as Post)?.is_following) {
|
||||
return getThread(
|
||||
currentUserId,
|
||||
currentTeamId,
|
||||
selected.id,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve({data: true});
|
||||
}
|
||||
|
||||
markThreadRead() {
|
||||
if (this.props.userThread) {
|
||||
// update last viewed at for thread before marking as read.
|
||||
this.props.actions.updateThreadLastOpened(
|
||||
this.props.userThread.id,
|
||||
this.props.userThread.last_viewed_at,
|
||||
);
|
||||
|
||||
if (
|
||||
this.props.userThread.last_viewed_at < this.props.userThread.last_reply_at ||
|
||||
this.props.userThread.unread_mentions ||
|
||||
this.props.userThread.unread_replies
|
||||
) {
|
||||
this.props.actions.updateThreadRead(
|
||||
this.props.currentUserId,
|
||||
this.props.currentTeamId,
|
||||
this.props.selected.id,
|
||||
Date.now(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// called either after mount, socket reconnected, or selected thread changed
|
||||
// fetches the thread/posts if needed and
|
||||
// scrolls to either bottom or new messages line
|
||||
private onInit = async (reconnected = false): Promise<void> => {
|
||||
this.setState({isLoading: !reconnected});
|
||||
if (reconnected || this.morePostsToFetch()) {
|
||||
await this.props.actions.getPostThread(this.props.selected.id, !reconnected);
|
||||
} else {
|
||||
await this.props.actions.getNewestPostThread(this.props.selected.id);
|
||||
}
|
||||
|
||||
if (
|
||||
this.props.isCollapsedThreadsEnabled &&
|
||||
this.props.userThread == null
|
||||
) {
|
||||
await this.fetchThread();
|
||||
}
|
||||
|
||||
this.setState({isLoading: false});
|
||||
}
|
||||
|
||||
private handleCardClick = (post: Post) => {
|
||||
if (!post) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.actions.selectPostCard(post);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (this.props.postIds == null || this.props.selected == null || !this.props.channel) {
|
||||
return (
|
||||
<span/>
|
||||
);
|
||||
}
|
||||
|
||||
if (this.state.isLoading && this.props.postIds.length < 2) {
|
||||
return (
|
||||
<LoadingScreen
|
||||
style={{
|
||||
display: 'grid',
|
||||
placeContent: 'center',
|
||||
flex: '1',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames('ThreadViewer', this.props.className)}>
|
||||
<div className='post-right-comments-container'>
|
||||
<>
|
||||
<FileUploadOverlay overlayType='right'/>
|
||||
{this.props.selected && (
|
||||
<DeferredThreadViewerVirt
|
||||
key={this.props.selected.id}
|
||||
channel={this.props.channel}
|
||||
onCardClick={this.handleCardClick}
|
||||
postIds={this.props.postIds}
|
||||
selected={this.props.selected}
|
||||
useRelativeTimestamp={this.props.useRelativeTimestamp || false}
|
||||
highlightedPostId={this.props.highlightedPostId}
|
||||
selectedPostFocusedAt={this.props.selectedPostFocusedAt}
|
||||
isThreadView={Boolean(this.props.isCollapsedThreadsEnabled && this.props.isThreadView)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user