Merge branch 'release-3.5'
Этот коммит содержится в:
@@ -1217,11 +1217,24 @@ func (us SqlUserStore) SearchInChannel(channelId string, term string, options ma
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
var specialUserSearchChar = []string{
|
||||
"<",
|
||||
">",
|
||||
"+",
|
||||
"-",
|
||||
"(",
|
||||
")",
|
||||
"~",
|
||||
"@",
|
||||
":",
|
||||
"*",
|
||||
}
|
||||
|
||||
func (us SqlUserStore) performSearch(searchQuery string, term string, options map[string]bool, parameters map[string]interface{}) StoreResult {
|
||||
result := StoreResult{}
|
||||
|
||||
// these chars have special meaning and can be treated as spaces
|
||||
for _, c := range specialSearchChar {
|
||||
for _, c := range specialUserSearchChar {
|
||||
term = strings.Replace(term, c, " ", -1)
|
||||
}
|
||||
|
||||
|
||||
@@ -981,6 +981,32 @@ func TestUserStoreSearch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// * should be treated as a space
|
||||
if r1 := <-store.User().Search(tid, "jimb*", searchOptions); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
profiles := r1.Data.([]*model.User)
|
||||
found1 := false
|
||||
found2 := false
|
||||
for _, profile := range profiles {
|
||||
if profile.Id == u1.Id {
|
||||
found1 = true
|
||||
}
|
||||
|
||||
if profile.Id == u3.Id {
|
||||
found2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found1 {
|
||||
t.Fatal("should have found user")
|
||||
}
|
||||
|
||||
if found2 {
|
||||
t.Fatal("should not have found inactive user")
|
||||
}
|
||||
}
|
||||
|
||||
if r1 := <-store.User().Search(tid, "harol", searchOptions); r1.Err != nil {
|
||||
t.Fatal(r1.Err)
|
||||
} else {
|
||||
|
||||
@@ -135,6 +135,7 @@ function populateDMChannelsWithProfiles(userIds) {
|
||||
const currentUserId = UserStore.getCurrentId();
|
||||
|
||||
for (let i = 0; i < userIds.length; i++) {
|
||||
// TODO There's a race condition here for DM channels if those channels aren't loaded yet
|
||||
const channelName = getDirectChannelName(currentUserId, userIds[i]);
|
||||
const channel = ChannelStore.getByName(channelName);
|
||||
if (channel) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as Utils from 'utils/utils.jsx';
|
||||
import * as AsyncClient from 'utils/async_client.jsx';
|
||||
|
||||
import * as GlobalActions from 'actions/global_actions.jsx';
|
||||
import {handleNewPost, loadPosts} from 'actions/post_actions.jsx';
|
||||
import {handleNewPost, loadPosts, loadProfilesForPosts} from 'actions/post_actions.jsx';
|
||||
import {loadProfilesAndTeamMembersForDMSidebar} from 'actions/user_actions.jsx';
|
||||
import {loadChannelsForCurrentUser} from 'actions/channel_actions.jsx';
|
||||
import * as StatusActions from 'actions/status_actions.jsx';
|
||||
@@ -172,6 +172,10 @@ function handleNewPostEvent(msg) {
|
||||
const post = JSON.parse(msg.data.post);
|
||||
handleNewPost(post, msg);
|
||||
|
||||
const posts = {};
|
||||
posts[post.id] = post;
|
||||
loadProfilesForPosts(posts);
|
||||
|
||||
if (UserStore.getStatus(post.user_id) !== UserStatuses.ONLINE) {
|
||||
StatusActions.loadStatusesByIds([post.user_id]);
|
||||
}
|
||||
@@ -207,6 +211,11 @@ function handleNewUserEvent(msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.data.user_id === UserStore.getCurrentId()) {
|
||||
// We should already have ourselves
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncClient.getUser(msg.data.user_id);
|
||||
AsyncClient.getChannelStats();
|
||||
loadProfilesAndTeamMembersForDMSidebar();
|
||||
|
||||
@@ -68,12 +68,18 @@ export default class ChannelHeader extends React.Component {
|
||||
const stats = ChannelStore.getStats(this.props.channelId);
|
||||
const users = UserStore.getProfileListInChannel(this.props.channelId);
|
||||
|
||||
let otherUserId = null;
|
||||
if (channel && channel.type === 'D') {
|
||||
otherUserId = Utils.getUserIdFromChannelName(channel);
|
||||
}
|
||||
|
||||
return {
|
||||
channel,
|
||||
memberChannel: ChannelStore.getMyMember(this.props.channelId),
|
||||
users,
|
||||
userCount: stats.member_count,
|
||||
currentUser: UserStore.getCurrentUser(),
|
||||
otherUserId,
|
||||
enableFormatting: PreferenceStore.getBool(Preferences.CATEGORY_ADVANCED_SETTINGS, 'formatting', true),
|
||||
isBusy: WebrtcStore.isBusy(),
|
||||
isFavorite: channel && ChannelUtils.isFavoriteChannel(channel)
|
||||
@@ -84,7 +90,6 @@ export default class ChannelHeader extends React.Component {
|
||||
if (!this.state.channel ||
|
||||
!this.state.memberChannel ||
|
||||
!this.state.users ||
|
||||
(Object.keys(this.state.users).length === 0 && this.state.channel.type === 'D') ||
|
||||
!this.state.userCount ||
|
||||
!this.state.currentUser) {
|
||||
return false;
|
||||
@@ -240,7 +245,10 @@ export default class ChannelHeader extends React.Component {
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
if (!this.validState()) {
|
||||
return null;
|
||||
// Use an empty div to make sure the header's height stays constant
|
||||
return (
|
||||
<div className='channel-header'/>
|
||||
);
|
||||
}
|
||||
|
||||
const channel = this.state.channel;
|
||||
@@ -285,7 +293,7 @@ export default class ChannelHeader extends React.Component {
|
||||
|
||||
if (isDirect) {
|
||||
const userMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||
const contact = this.state.users[0];
|
||||
const otherUserId = this.state.otherUserId;
|
||||
|
||||
const teammateId = Utils.getUserIdFromChannelName(channel);
|
||||
channelTitle = Utils.displayUsername(teammateId);
|
||||
@@ -293,7 +301,7 @@ export default class ChannelHeader extends React.Component {
|
||||
const webrtcEnabled = global.mm_config.EnableWebrtc === 'true' && userMedia && Utils.isFeatureEnabled(PreReleaseFeatures.WEBRTC_PREVIEW);
|
||||
|
||||
if (webrtcEnabled) {
|
||||
const isOffline = UserStore.getStatus(contact.id) === UserStatuses.OFFLINE;
|
||||
const isOffline = UserStore.getStatus(otherUserId) === UserStatuses.OFFLINE;
|
||||
const busy = this.state.isBusy;
|
||||
let circleClass = '';
|
||||
let webrtcMessage;
|
||||
@@ -332,7 +340,7 @@ export default class ChannelHeader extends React.Component {
|
||||
<div className='webrtc__header'>
|
||||
<a
|
||||
href='#'
|
||||
onClick={() => this.initWebrtc(contact.id, !isOffline)}
|
||||
onClick={() => this.initWebrtc(otherUserId, !isOffline)}
|
||||
disabled={isOffline}
|
||||
>
|
||||
<OverlayTrigger
|
||||
|
||||
@@ -51,6 +51,7 @@ export default class CodePreview extends React.Component {
|
||||
async: true,
|
||||
url: props.fileUrl,
|
||||
type: 'GET',
|
||||
dataType: 'text',
|
||||
error: this.handleReceivedError,
|
||||
success: this.handleReceivedCode
|
||||
});
|
||||
@@ -61,7 +62,11 @@ export default class CodePreview extends React.Component {
|
||||
if (data.nodeName === '#document') {
|
||||
code = new XMLSerializer().serializeToString(data);
|
||||
}
|
||||
this.setState({code, loading: false, success: true});
|
||||
this.setState({
|
||||
code,
|
||||
loading: false,
|
||||
success: true
|
||||
});
|
||||
}
|
||||
|
||||
handleReceivedError() {
|
||||
|
||||
@@ -17,11 +17,12 @@ export default class PostViewCache extends React.Component {
|
||||
|
||||
this.onChannelChange = this.onChannelChange.bind(this);
|
||||
|
||||
const currentChannelId = ChannelStore.getCurrentId();
|
||||
const channel = ChannelStore.getCurrent();
|
||||
|
||||
this.state = {
|
||||
currentChannelId: channel.id,
|
||||
channels: [channel]
|
||||
currentChannelId,
|
||||
channels: channel ? [channel] : []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ export default class PostViewCache extends React.Component {
|
||||
const channels = Object.assign([], this.state.channels);
|
||||
const currentChannel = ChannelStore.getCurrent();
|
||||
|
||||
if (currentChannel == null) {
|
||||
if (!currentChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -202,8 +202,13 @@ export default class PostViewController extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onSetNewMessageIndicator(lastViewed, ownNewMessage) {
|
||||
this.setState({lastViewed, ownNewMessage});
|
||||
onSetNewMessageIndicator() {
|
||||
let lastViewed = Number.MAX_VALUE;
|
||||
const member = ChannelStore.getMyMember(this.props.channel.id);
|
||||
if (member != null) {
|
||||
lastViewed = member.last_viewed_at;
|
||||
}
|
||||
this.setState({lastViewed});
|
||||
}
|
||||
|
||||
onPostListScroll(atBottom) {
|
||||
|
||||
@@ -90,7 +90,7 @@ export default class RhsRootPost extends React.Component {
|
||||
var isOwner = this.props.currentUser.id === post.user_id;
|
||||
var isAdmin = TeamStore.isTeamAdminForCurrentTeam() || UserStore.isSystemAdminForCurrentUser();
|
||||
const isSystemMessage = post.type && post.type.startsWith(Constants.SYSTEM_MESSAGE_PREFIX);
|
||||
var timestamp = user.update_at;
|
||||
var timestamp = user ? user.update_at : 0;
|
||||
var channel = ChannelStore.get(post.channel_id);
|
||||
const flagIcon = Constants.FLAG_ICON_SVG;
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
this.showGetTeamInviteLinkModal = this.showGetTeamInviteLinkModal.bind(this);
|
||||
this.showTeamMembersModal = this.showTeamMembersModal.bind(this);
|
||||
this.hideTeamMembersModal = this.hideTeamMembersModal.bind(this);
|
||||
this.handleSwitchTeams = this.handleSwitchTeams.bind(this);
|
||||
|
||||
this.onTeamChange = this.onTeamChange.bind(this);
|
||||
this.openAccountSettings = this.openAccountSettings.bind(this);
|
||||
@@ -131,6 +132,11 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
handleSwitchTeams() {
|
||||
// The actual switching of teams is handled by the react-router Link
|
||||
this.setState({showDropdown: false});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
TeamStore.addChangeListener(this.onTeamChange);
|
||||
document.addEventListener('keydown', this.openAccountSettings);
|
||||
@@ -367,6 +373,7 @@ export default class SidebarHeaderDropdown extends React.Component {
|
||||
<li key={'team_' + team.name}>
|
||||
<Link
|
||||
to={'/' + team.name + '/channels/town-square'}
|
||||
onClick={this.handleSwitchTeams}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='navbar_dropdown.switchTo'
|
||||
|
||||
@@ -29,13 +29,16 @@ function doChannelChange(state, replace, callback) {
|
||||
channel = JSON.parse(state.location.query.fakechannel);
|
||||
} else {
|
||||
channel = ChannelStore.getByName(state.params.channel);
|
||||
if (!channel) {
|
||||
channel = ChannelStore.getMoreByName(state.params.channel);
|
||||
}
|
||||
|
||||
if (!channel) {
|
||||
Client.joinChannelByName(
|
||||
state.params.channel,
|
||||
(data) => {
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_CHANNEL,
|
||||
channel: data
|
||||
});
|
||||
|
||||
GlobalActions.emitChannelClickEvent(data);
|
||||
callback();
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@charset 'UTF-8';
|
||||
|
||||
.channel-header {
|
||||
@include flex(0 0 56px);
|
||||
@include flex(0 0 57px);
|
||||
border-left: none;
|
||||
font-size: 14px;
|
||||
line-height: 56px;
|
||||
|
||||
@@ -59,8 +59,8 @@ class ChannelStoreClass extends EventEmitter {
|
||||
this.removeListener(STATS_EVENT, callback);
|
||||
}
|
||||
|
||||
emitLastViewed(lastViewed, ownNewMessage) {
|
||||
this.emit(LAST_VIEVED_EVENT, lastViewed, ownNewMessage);
|
||||
emitLastViewed() {
|
||||
this.emit(LAST_VIEVED_EVENT);
|
||||
}
|
||||
|
||||
addLastViewedListener(callback) {
|
||||
@@ -373,6 +373,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
}
|
||||
ChannelStore.setUnreadCountsByMembers(action.members);
|
||||
ChannelStore.emitChange();
|
||||
ChannelStore.emitLastViewed();
|
||||
break;
|
||||
case ActionTypes.RECEIVED_CHANNEL_MEMBER:
|
||||
ChannelStore.storeMyChannelMember(action.member);
|
||||
@@ -382,6 +383,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
|
||||
}
|
||||
ChannelStore.setUnreadCountsByCurrentMembers();
|
||||
ChannelStore.emitChange();
|
||||
ChannelStore.emitLastViewed();
|
||||
break;
|
||||
case ActionTypes.RECEIVED_MORE_CHANNELS:
|
||||
ChannelStore.storeMoreChannels(action.channels);
|
||||
|
||||
@@ -298,15 +298,17 @@ export function getChannelMember(channelId, userId) {
|
||||
}
|
||||
|
||||
export function getUser(userId) {
|
||||
if (isCallInProgress(`getUser${userId}`)) {
|
||||
const callName = `getUser${userId}`;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[`getUser${userId}`] = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getUser(
|
||||
userId,
|
||||
(data) => {
|
||||
callTracker[`getUser${userId}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILE,
|
||||
@@ -314,23 +316,25 @@ export function getUser(userId) {
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`getUser${userId}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getUser');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
|
||||
if (isCallInProgress(`getProfiles${offset}${limit}`)) {
|
||||
const callName = `getProfiles${offset}${limit}`;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[`getProfiles${offset}${limit}`] = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getProfiles(
|
||||
offset,
|
||||
limit,
|
||||
(data) => {
|
||||
callTracker[`getProfiles${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES,
|
||||
@@ -338,24 +342,26 @@ export function getProfiles(offset = UserStore.getPagingOffset(), limit = Consta
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`getProfiles${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getProfiles');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getProfilesInTeam(teamId = TeamStore.getCurrentId(), offset = UserStore.getInTeamPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
|
||||
if (isCallInProgress(`getProfilesInTeam${offset}${limit}`)) {
|
||||
const callName = `getProfilesInTeam${teamId}${offset}${limit}`;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[`getProfilesInTeam${offset}${limit}`] = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getProfilesInTeam(
|
||||
teamId,
|
||||
offset,
|
||||
limit,
|
||||
(data) => {
|
||||
callTracker[`getProfilesInTeam${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
|
||||
@@ -366,24 +372,26 @@ export function getProfilesInTeam(teamId = TeamStore.getCurrentId(), offset = Us
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`getProfilesInTeam${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getProfilesInTeam');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getProfilesInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
|
||||
if (isCallInProgress(`getProfilesInChannel${offset}${limit}`)) {
|
||||
const callName = `getProfilesInChannel${channelId}${offset}${limit}`;
|
||||
|
||||
if (isCallInProgress()) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[`getProfilesInChannel${offset}${limit}`] = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getProfilesInChannel(
|
||||
channelId,
|
||||
offset,
|
||||
limit,
|
||||
(data) => {
|
||||
callTracker[`getProfilesInChannel${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
|
||||
@@ -396,24 +404,26 @@ export function getProfilesInChannel(channelId = ChannelStore.getCurrentId(), of
|
||||
loadStatusesForProfilesMap(data);
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`getProfilesInChannel${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getProfilesInChannel');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getNotInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
|
||||
if (isCallInProgress(`getProfilesNotInChannel${offset}${limit}`)) {
|
||||
const callName = `getProfilesNotInChannel${channelId}${offset}${limit}`;
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker[`getProfilesNotInChannel${offset}${limit}`] = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getProfilesNotInChannel(
|
||||
channelId,
|
||||
offset,
|
||||
limit,
|
||||
(data) => {
|
||||
callTracker[`getProfilesNotInChannel${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL,
|
||||
@@ -426,14 +436,16 @@ export function getProfilesNotInChannel(channelId = ChannelStore.getCurrentId(),
|
||||
loadStatusesForProfilesMap(data);
|
||||
},
|
||||
(err) => {
|
||||
callTracker[`getProfilesNotInChannel${offset}${limit}`] = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getProfilesNotInChannel');
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getProfilesByIds(userIds) {
|
||||
if (isCallInProgress('getProfilesByIds')) {
|
||||
const callName = 'getProfilesByIds' + JSON.stringify(userIds);
|
||||
|
||||
if (isCallInProgress(callName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -441,11 +453,11 @@ export function getProfilesByIds(userIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
callTracker.getProfilesByIds = utils.getTimestamp();
|
||||
callTracker[callName] = utils.getTimestamp();
|
||||
Client.getProfilesByIds(
|
||||
userIds,
|
||||
(data) => {
|
||||
callTracker.getProfilesByIds = 0;
|
||||
callTracker[callName] = 0;
|
||||
|
||||
AppDispatcher.handleServerAction({
|
||||
type: ActionTypes.RECEIVED_PROFILES,
|
||||
@@ -453,7 +465,7 @@ export function getProfilesByIds(userIds) {
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
callTracker.getProfilesByIds = 0;
|
||||
callTracker[callName] = 0;
|
||||
dispatchError(err, 'getProfilesByIds');
|
||||
}
|
||||
);
|
||||
|
||||
@@ -854,7 +854,7 @@ export const Constants = {
|
||||
MENTION_SPECIAL: 'mention.special',
|
||||
DEFAULT_NOTIFICATION_DURATION: 5000,
|
||||
STATUS_INTERVAL: 60000,
|
||||
AUTOCOMPLETE_TIMEOUT: 200
|
||||
AUTOCOMPLETE_TIMEOUT: 100
|
||||
};
|
||||
|
||||
export default Constants;
|
||||
|
||||
Ссылка в новой задаче
Block a user