PLT-6215 Major post list refactor (#6501)
* Major post list refactor * Fix post and thread deletion * Fix preferences not selecting correctly * Fix military time displaying * Fix UP key for editing posts * Fix ESLint error * Various fixes and updates per feedback * Fix for permalink view * Revert to old scrolling method and various fixes * Add floating timestamp, new message indicator, scroll arrows * Update post loading for focus mode and add visibility limit * Fix pinning posts and a react warning * Add loading UI updates from Asaad * Fix refreshing loop * Temporarily bump post visibility limit * Update infinite scrolling * Remove infinite scrolling
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
0231e95f1c
Коммит
ab67f6e257
41
webapp/components/post_view/post_message_view/index.js
Обычный файл
41
webapp/components/post_view/post_message_view/index.js
Обычный файл
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {getCustomEmojisAsMap} from 'mattermost-redux/selectors/entities/emojis';
|
||||
import {getBool} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getCurrentUserMentionKeys, getUsersByUsername} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import {getSiteURL} from 'utils/url.jsx';
|
||||
|
||||
import {EmojiMap} from 'stores/emoji_store.jsx';
|
||||
|
||||
import PostMessageView from './post_message_view.jsx';
|
||||
|
||||
function makeMapStateToProps() {
|
||||
let emojiMap;
|
||||
let oldCustomEmoji;
|
||||
|
||||
return function mapStateToProps(state, ownProps) {
|
||||
const newCustomEmoji = getCustomEmojisAsMap(state);
|
||||
if (newCustomEmoji !== oldCustomEmoji) {
|
||||
emojiMap = new EmojiMap(newCustomEmoji);
|
||||
}
|
||||
oldCustomEmoji = newCustomEmoji;
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
emojis: emojiMap,
|
||||
enableFormatting: getBool(state, Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
|
||||
mentionKeys: getCurrentUserMentionKeys(state),
|
||||
usernameMap: getUsersByUsername(state),
|
||||
team: getCurrentTeam(state),
|
||||
siteUrl: getSiteURL()
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(PostMessageView);
|
||||
139
webapp/components/post_view/post_message_view/post_message_view.jsx
Обычный файл
139
webapp/components/post_view/post_message_view/post_message_view.jsx
Обычный файл
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import * as PostUtils from 'utils/post_utils.jsx';
|
||||
import * as TextFormatting from 'utils/text_formatting.jsx';
|
||||
import * as Utils from 'utils/utils.jsx';
|
||||
|
||||
import {getChannelsNameMapInCurrentTeam} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {Posts} from 'mattermost-redux/constants';
|
||||
import store from 'stores/redux_store.jsx';
|
||||
|
||||
import {renderSystemMessage} from './system_message_helpers.jsx';
|
||||
|
||||
export default class PostMessageView extends React.PureComponent {
|
||||
static propTypes = {
|
||||
|
||||
/*
|
||||
* The post to render the message for
|
||||
*/
|
||||
post: PropTypes.object.isRequired,
|
||||
|
||||
/*
|
||||
* Object using emoji names as keys with custom emojis as the values
|
||||
*/
|
||||
emojis: PropTypes.object.isRequired,
|
||||
|
||||
/*
|
||||
* The team the post was made in
|
||||
*/
|
||||
team: PropTypes.object.isRequired,
|
||||
|
||||
/*
|
||||
* Set to enable Markdown formatting
|
||||
*/
|
||||
enableFormatting: PropTypes.bool,
|
||||
|
||||
/*
|
||||
* An array of words that can be used to mention a user
|
||||
*/
|
||||
mentionKeys: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
/*
|
||||
* Object mapping usernames to users
|
||||
*/
|
||||
usernameMap: PropTypes.object,
|
||||
|
||||
/*
|
||||
* The URL that the app is hosted on
|
||||
*/
|
||||
siteUrl: PropTypes.string,
|
||||
|
||||
/*
|
||||
* Options specific to text formatting
|
||||
*/
|
||||
options: PropTypes.object,
|
||||
|
||||
/*
|
||||
* Post identifiers for selenium tests
|
||||
*/
|
||||
lastPostCount: PropTypes.number
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
options: {},
|
||||
mentionKeys: [],
|
||||
usernameMap: {}
|
||||
};
|
||||
|
||||
renderDeletedPost() {
|
||||
return (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='post_body.deleted'
|
||||
defaultMessage='(message deleted)'
|
||||
/>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditedIndicator() {
|
||||
if (!PostUtils.isEdited(this.props.post)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className='post-edited-indicator'>
|
||||
<FormattedMessage
|
||||
id='post_message_view.edited'
|
||||
defaultMessage='(edited)'
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.post.state === Posts.POST_DELETED) {
|
||||
return this.renderDeletedPost();
|
||||
}
|
||||
|
||||
if (!this.props.enableFormatting) {
|
||||
return <span>{this.props.post.message}</span>;
|
||||
}
|
||||
|
||||
const options = Object.assign({}, this.props.options, {
|
||||
emojis: this.props.emojis,
|
||||
siteURL: this.props.siteUrl,
|
||||
mentionKeys: this.props.mentionKeys,
|
||||
usernameMap: this.props.usernameMap,
|
||||
channelNamesMap: getChannelsNameMapInCurrentTeam(store.getState()),
|
||||
team: this.props.team
|
||||
});
|
||||
|
||||
const renderedSystemMessage = renderSystemMessage(this.props.post, options);
|
||||
if (renderedSystemMessage) {
|
||||
return <div>{renderedSystemMessage}</div>;
|
||||
}
|
||||
|
||||
let postId = null;
|
||||
if (this.props.lastPostCount >= 0) {
|
||||
postId = Utils.createSafeId('lastPostMessageText' + this.props.lastPostCount);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span
|
||||
id={postId}
|
||||
className='post-message__text'
|
||||
onClick={Utils.handleFormattedTextClick}
|
||||
dangerouslySetInnerHTML={{__html: TextFormatting.formatText(this.props.post.message, options)}}
|
||||
/>
|
||||
{this.renderEditedIndicator()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {FormattedMessage} from 'react-intl';
|
||||
|
||||
import {PostTypes} from 'utils/constants.jsx';
|
||||
import {formatText} from 'utils/text_formatting.jsx';
|
||||
|
||||
function renderUsername(value, options) {
|
||||
return renderFormattedText(value, {...options, markdown: false});
|
||||
}
|
||||
|
||||
function renderFormattedText(value, options) {
|
||||
return <span dangerouslySetInnerHTML={{__html: formatText(value, options)}}/>;
|
||||
}
|
||||
|
||||
function renderJoinChannelMessage(post, options) {
|
||||
const username = renderUsername(post.props.username, options);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.join_channel.post_and_forget'
|
||||
defaultMessage='{username} has joined the channel.'
|
||||
values={{username}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderLeaveChannelMessage(post, options) {
|
||||
const username = renderUsername(post.props.username, options);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.leave.left'
|
||||
defaultMessage='{username} has left the channel.'
|
||||
values={{username}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderAddToChannelMessage(post, options) {
|
||||
const username = renderUsername(post.props.username, options);
|
||||
const addedUsername = renderUsername(post.props.addedUsername, options);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.add_member.added'
|
||||
defaultMessage='{addedUsername} added to the channel by {username}'
|
||||
values={{
|
||||
username,
|
||||
addedUsername
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderRemoveFromChannelMessage(post, options) {
|
||||
const removedUsername = renderUsername(post.props.removedUsername, options);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.remove_member.removed'
|
||||
defaultMessage='{removedUsername} was removed from the channel'
|
||||
values={{
|
||||
removedUsername
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderHeaderChangeMessage(post, options) {
|
||||
if (!post.props.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const headerOptions = {
|
||||
...options,
|
||||
singleline: true
|
||||
};
|
||||
|
||||
const username = renderUsername(post.props.username, options);
|
||||
const oldHeader = post.props.old_header ? renderFormattedText(post.props.old_header, headerOptions) : null;
|
||||
const newHeader = post.props.new_header ? renderFormattedText(post.props.new_header, headerOptions) : null;
|
||||
|
||||
if (post.props.new_header) {
|
||||
if (post.props.old_header) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.post_update_channel_header_message_and_forget.updated_from'
|
||||
defaultMessage='{username} updated the channel header from: {old} to: {new}'
|
||||
values={{
|
||||
username,
|
||||
old: oldHeader,
|
||||
new: newHeader
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.post_update_channel_header_message_and_forget.updated_to'
|
||||
defaultMessage='{username} updated the channel header to: {new}'
|
||||
values={{
|
||||
username,
|
||||
new: newHeader
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (post.props.old_header) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.post_update_channel_header_message_and_forget.removed'
|
||||
defaultMessage='{username} removed the channel header (was: {old})'
|
||||
values={{
|
||||
username,
|
||||
old: oldHeader
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderDisplayNameChangeMessage(post, options) {
|
||||
if (!(post.props.username && post.props.old_displayname && post.props.new_displayname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(post.props.username, options);
|
||||
const oldDisplayName = post.props.old_displayname;
|
||||
const newDisplayName = post.props.new_displayname;
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.post_update_channel_displayname_message_and_forget.updated_from'
|
||||
defaultMessage='{username} updated the channel display name from: {old} to: {new}'
|
||||
values={{
|
||||
username,
|
||||
old: oldDisplayName,
|
||||
new: newDisplayName
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function renderPurposeChangeMessage(post, options) {
|
||||
if (!post.props.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(post.props.username, options);
|
||||
const oldPurpose = post.props.old_purpose;
|
||||
const newPurpose = post.props.new_purpose;
|
||||
|
||||
if (post.props.new_purpose) {
|
||||
if (post.props.old_purpose) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='app.channel.post_update_channel_purpose_message.updated_from'
|
||||
defaultMessage='{username} updated the channel purpose from: {old} to: {new}'
|
||||
values={{
|
||||
username,
|
||||
old: oldPurpose,
|
||||
new: newPurpose
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='app.channel.post_update_channel_purpose_message.updated_to'
|
||||
defaultMessage='{username} updated the channel purpose to: {new}'
|
||||
values={{
|
||||
username,
|
||||
new: newPurpose
|
||||
}}
|
||||
/>
|
||||
);
|
||||
} else if (post.props.old_purpose) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='app.channel.post_update_channel_purpose_message.removed'
|
||||
defaultMessage='{username} removed the channel purpose (was: {old})'
|
||||
values={{
|
||||
username,
|
||||
old: oldPurpose
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderChannelDeletedMessage(post, options) {
|
||||
if (!post.props.username) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const username = renderUsername(post.props.username, options);
|
||||
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='api.channel.delete_channel.archived'
|
||||
defaultMessage='{username} has archived the channel.'
|
||||
values={{username}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const systemMessageRenderers = {
|
||||
[PostTypes.JOIN_CHANNEL]: renderJoinChannelMessage,
|
||||
[PostTypes.LEAVE_CHANNEL]: renderLeaveChannelMessage,
|
||||
[PostTypes.ADD_TO_CHANNEL]: renderAddToChannelMessage,
|
||||
[PostTypes.REMOVE_FROM_CHANNEL]: renderRemoveFromChannelMessage,
|
||||
[PostTypes.HEADER_CHANGE]: renderHeaderChangeMessage,
|
||||
[PostTypes.DISPLAYNAME_CHANGE]: renderDisplayNameChangeMessage,
|
||||
[PostTypes.PURPOSE_CHANGE]: renderPurposeChangeMessage,
|
||||
[PostTypes.CHANNEL_DELETED]: renderChannelDeletedMessage
|
||||
};
|
||||
|
||||
export function renderSystemMessage(post, options) {
|
||||
if (!systemMessageRenderers[post.type]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return systemMessageRenderers[post.type](post, options);
|
||||
}
|
||||
Ссылка в новой задаче
Block a user