* Start moving webapp to Redux

* Fix localforage import

* Updates per feedback

* Feedback udpates and a few fixes

* Minor updates

* Fix statuses, config not loading properly, getMe sanitizing too much

* Fix preferences

* Fix user autocomplete

* Fix sessions and audits

* Fix error handling for all redux actions

* Use new directory structure for components and containers

* Refresh immediately on logout instead of after timeout

* Add fetch polyfill
Этот коммит содержится в:
Joram Wilander
2017-04-25 11:46:02 -04:00
коммит произвёл Christopher Speller
родитель cc07c00507
Коммит 6c4c706313
57 изменённых файлов: 1148 добавлений и 1454 удалений

Просмотреть файл

@@ -5,6 +5,7 @@ package api4
import (
"net/http"
"strconv"
l4g "github.com/alecthomas/log4go"
"github.com/mattermost/platform/app"
@@ -202,7 +203,14 @@ func getClientConfig(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
w.Write([]byte(model.MapToJson(utils.ClientCfg)))
respCfg := map[string]string{}
for k, v := range utils.ClientCfg {
respCfg[k] = v
}
respCfg["NoAccounts"] = strconv.FormatBool(app.IsFirstUserAccount())
w.Write([]byte(model.MapToJson(respCfg)))
}
func getClientLicense(c *Context, w http.ResponseWriter, r *http.Request) {

Просмотреть файл

@@ -111,7 +111,11 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
if HandleEtag(etag, "Get User", w, r) {
return
} else {
app.SanitizeProfile(user, c.IsSystemAdmin())
if c.Session.UserId == user.Id {
user.Sanitize(map[string]bool{})
} else {
app.SanitizeProfile(user, c.IsSystemAdmin())
}
w.Header().Set(model.HEADER_ETAG_SERVER, etag)
w.Write([]byte(user.ToJson()))
return

Просмотреть файл

@@ -5,21 +5,12 @@ import Client from 'client/web_client.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {browserHistory} from 'react-router/es6';
export function revokeSession(altId, success, error) {
Client.revokeSession(altId,
() => {
AsyncClient.getSessions();
if (success) {
success();
}
},
(err) => {
if (error) {
error(err);
}
}
);
}
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getUser} from 'mattermost-redux/actions/users';
export function saveConfig(config, success, error) {
Client.saveConfig(
@@ -57,7 +48,7 @@ export function adminResetMfa(userId, success, error) {
Client.adminResetMfa(
userId,
() => {
AsyncClient.getUser(userId);
getUser(userId)(dispatch, getState);
if (success) {
success();

Просмотреть файл

@@ -10,6 +10,12 @@ import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getProfilesByIds} from 'mattermost-redux/actions/users';
export function loadEmoji(getProfiles = true) {
Client.listEmoji(
(data) => {
@@ -42,5 +48,5 @@ function loadProfilesForEmoji(emojiList) {
return;
}
AsyncClient.getProfilesByIds(list);
getProfilesByIds(list)(dispatch, getState);
}

Просмотреть файл

@@ -9,7 +9,6 @@ import UserStore from 'stores/user_store.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import SearchStore from 'stores/search_store.jsx';
import {handleNewPost, loadPosts, loadPostsBefore, loadPostsAfter} from 'actions/post_actions.jsx';
@@ -32,6 +31,12 @@ import en from 'i18n/en.json';
import * as I18n from 'i18n/i18n.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {ChannelTypes} from 'mattermost-redux/action_types';
export function emitChannelClickEvent(channel) {
function userVisitedFakeChannel(chan, success, fail) {
const otherUserId = Utils.getUserIdFromChannelName(chan);
@@ -77,6 +82,11 @@ export function emitChannelClickEvent(channel) {
channelMember,
prev: oldChannelId
});
dispatch({
type: ChannelTypes.SELECT_CHANNEL,
data: chan.id
}, getState);
}
if (channel.fake) {
@@ -94,85 +104,6 @@ export function emitChannelClickEvent(channel) {
}
}
export function emitInitialLoad(callback) {
Client.getInitialLoad(
(data) => {
global.window.mm_config = data.client_cfg;
global.window.mm_license = data.license_cfg;
if (global.window && global.window.analytics) {
global.window.analytics.identify(global.window.mm_config.DiagnosticId, {}, {
context: {
ip: '0.0.0.0'
},
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
});
}
UserStore.setNoAccounts(data.no_accounts);
if (data.user && data.user.id) {
global.window.mm_user = data.user;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ME,
me: data.user
});
}
if (data.preferences) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PREFERENCES,
preferences: data.preferences
});
}
if (data.teams) {
var teams = {};
data.teams.forEach((team) => {
teams[team.id] = team;
});
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ALL_TEAMS,
teams
});
}
if (data.team_members) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_MY_TEAM_MEMBERS,
team_members: data.team_members
});
}
if (data.direct_profiles) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_DIRECT_PROFILES,
profiles: data.direct_profiles
});
}
if (callback) {
callback();
}
},
(err) => {
AsyncClient.dispatchError(err, 'getInitialLoad');
if (callback) {
callback();
}
}
);
}
export function doFocusPost(channelId, postId, data) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_FOCUSED_POST,
@@ -536,12 +467,11 @@ export function emitUserLoggedOutEvent(redirectTo = '/', shouldSignalLogout = tr
export function clientLogout(redirectTo = '/') {
BrowserStore.clear();
ErrorStore.clearLastError();
PreferenceStore.clear();
UserStore.clear();
TeamStore.clear();
ChannelStore.clear();
stopPeriodicStatusUpdates();
WebsocketActions.close();
localStorage.removeItem('currentUserId');
window.location.href = redirectTo;
}

Просмотреть файл

@@ -11,6 +11,12 @@ import Client from 'client/web_client.jsx';
import {ActionTypes} from 'utils/constants.jsx';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getProfilesByIds} from 'mattermost-redux/actions/users';
export function loadIncomingHooks() {
Client.listIncomingHooks(
(data) => {
@@ -42,7 +48,7 @@ function loadProfilesForIncomingHooks(hooks) {
return;
}
AsyncClient.getProfilesByIds(list);
getProfilesByIds(list)(dispatch, getState);
}
export function loadOutgoingHooks() {
@@ -76,7 +82,7 @@ function loadProfilesForOutgoingHooks(hooks) {
return;
}
AsyncClient.getProfilesByIds(list);
getProfilesByIds(list)(dispatch, getState);
}
export function loadTeamCommands() {
@@ -110,5 +116,5 @@ function loadProfilesForCommands(commands) {
return;
}
AsyncClient.getProfilesByIds(list);
getProfilesByIds(list)(dispatch, getState);
}

Просмотреть файл

@@ -20,6 +20,12 @@ import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const Preferences = Constants.Preferences;
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getProfilesByIds} from 'mattermost-redux/actions/users';
export function handleNewPost(post, msg) {
let websocketMessageProps = {};
if (msg) {
@@ -310,7 +316,7 @@ export function loadProfilesForPosts(posts) {
return;
}
AsyncClient.getProfilesByIds(list);
getProfilesByIds(list)(dispatch, getState);
}
export function addReaction(channelId, postId, emojiName) {

Просмотреть файл

@@ -1,16 +1,19 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import PostStore from 'stores/post_store.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import UserStore from 'stores/user_store.jsx';
import Client from 'client/web_client.jsx';
import {Preferences, Constants} from 'utils/constants.jsx';
import {ActionTypes, Preferences, Constants} from 'utils/constants.jsx';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getStatusesByIds} from 'mattermost-redux/actions/users';
export function loadStatusesForChannel(channelId = ChannelStore.getCurrentId()) {
const postList = PostStore.getVisiblePosts(channelId);
@@ -108,15 +111,7 @@ export function loadStatusesByIds(userIds) {
return;
}
Client.getStatusesByIds(
userIds,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_STATUSES,
statuses: data
});
}
);
getStatusesByIds(userIds)(dispatch, getState);
}
let intervalId = '';

Просмотреть файл

@@ -13,6 +13,13 @@ import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {getUser} from 'mattermost-redux/actions/users';
export function checkIfTeamExists(teamName, onSuccess, onError) {
Client.findTeamByName(teamName, onSuccess, onError);
}
@@ -62,7 +69,7 @@ export function removeUserFromTeam(teamId, userId, success, error) {
TeamStore.removeMemberInTeam(teamId, userId);
UserStore.removeProfileFromTeam(teamId, userId);
UserStore.emitInTeamChange();
AsyncClient.getUser(userId);
getUser(userId)(dispatch, getState);
AsyncClient.getTeamStats(teamId);
if (success) {

Просмотреть файл

@@ -19,6 +19,79 @@ import Client from 'client/web_client.jsx';
import {Constants, ActionTypes, Preferences} from 'utils/constants.jsx';
import {browserHistory} from 'react-router/es6';
// Redux actions
import store from 'stores/redux_store.jsx';
const dispatch = store.dispatch;
const getState = store.getState;
import {
getProfiles,
getProfilesInChannel,
getProfilesInTeam,
getProfilesWithoutTeam,
getProfilesByIds,
getMe,
searchProfiles,
autocompleteUsers as autocompleteRedux,
updateMe,
updateUserMfa,
checkMfa as checkMfaRedux,
updateUserPassword,
createUser,
login,
loadMe as loadMeRedux
} from 'mattermost-redux/actions/users';
import {getClientConfig, getLicenseConfig} from 'mattermost-redux/actions/general';
export function loadMe(callback) {
loadMeRedux()(dispatch, getState).then(
() => {
localStorage.setItem('currentUserId', UserStore.getCurrentId());
if (callback) {
callback();
}
}
);
}
export function loadMeAndConfig(callback) {
loadMe(() => {
getClientConfig()(store.dispatch, store.getState).then(
(config) => {
global.window.mm_config = config;
if (global.window && global.window.analytics) {
global.window.analytics.identify(global.window.mm_config.DiagnosticId, {}, {
context: {
ip: '0.0.0.0'
},
page: {
path: '',
referrer: '',
search: '',
title: '',
url: ''
},
anonymousId: '00000000000000000000000000'
});
}
getLicenseConfig()(store.dispatch, store.getState).then(
(license) => { // eslint-disable-line max-nested-callbacks
global.window.mm_license = license;
if (callback) {
callback();
}
}
);
}
);
});
}
export function switchFromLdapToEmail(email, password, token, ldapPassword, onSuccess, onError) {
Client.ldapToEmail(
email,
@@ -38,80 +111,30 @@ export function switchFromLdapToEmail(email, password, token, ldapPassword, onSu
);
}
export function loadProfilesAndTeamMembers(offset, limit, teamId = TeamStore.getCurrentId(), success, error) {
Client.getProfilesInTeam(
teamId,
offset,
limit,
export function loadProfilesAndTeamMembers(page, perPage, teamId = TeamStore.getCurrentId(), success) {
getProfilesInTeam(teamId, page, perPage)(dispatch, getState).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
profiles: data,
team_id: teamId,
offset,
count: Object.keys(data).length
});
loadTeamMembersForProfilesMap(data, teamId, success, error);
loadStatusesForProfilesMap(data);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesInTeam');
loadTeamMembersForProfilesList(data, teamId, success);
loadStatusesForProfilesList(data);
}
);
}
export function loadProfilesAndTeamMembersAndChannelMembers(offset, limit, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
Client.getProfilesInChannel(
channelId,
offset,
limit,
export function loadProfilesAndTeamMembersAndChannelMembers(page, perPage, teamId = TeamStore.getCurrentId(), channelId = ChannelStore.getCurrentId(), success, error) {
getProfilesInChannel(channelId, page, perPage)(dispatch, getState).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
profiles: data,
channel_id: channelId,
offset,
count: Object.keys(data).length
});
loadTeamMembersForProfilesMap(
loadTeamMembersForProfilesList(
data,
teamId,
() => {
loadChannelMembersForProfilesMap(data, channelId, success, error);
loadStatusesForProfilesMap(data);
});
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesInChannel');
loadChannelMembersForProfilesList(data, channelId, success, error);
loadStatusesForProfilesList(data);
}
);
}
);
}
export function loadTeamMembersForProfilesMap(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (const pid in profiles) {
if (!profiles.hasOwnProperty(pid)) {
continue;
}
if (!TeamStore.hasActiveMemberInTeam(teamId, pid)) {
membersToLoad[pid] = true;
}
}
const list = Object.keys(membersToLoad);
if (list.length === 0) {
if (success) {
success({});
}
return;
}
loadTeamMembersForProfiles(list, teamId, success, error);
}
export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getCurrentId(), success, error) {
const membersToLoad = {};
for (let i = 0; i < profiles.length; i++) {
@@ -133,24 +156,13 @@ export function loadTeamMembersForProfilesList(profiles, teamId = TeamStore.getC
loadTeamMembersForProfiles(list, teamId, success, error);
}
export function loadProfilesWithoutTeam(page, perPage, success, error) {
Client.getProfilesWithoutTeam(
page,
perPage,
export function loadProfilesWithoutTeam(page, perPage, success) {
getProfilesWithoutTeam(page, perPage)(dispatch, getState).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM,
profiles: data,
page
});
loadStatusesForProfilesMap(data);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesWithoutTeam');
if (error) {
error(err);
if (success) {
success(data);
}
}
);
@@ -248,9 +260,9 @@ function populateDMChannelsWithProfiles(userIds) {
}
}
function populateChannelWithProfiles(channelId, userIds) {
for (let i = 0; i < userIds.length; i++) {
UserStore.saveUserIdInChannel(channelId, userIds[i]);
function populateChannelWithProfiles(channelId, users) {
for (let i = 0; i < users.length; i++) {
UserStore.saveUserIdInChannel(channelId, users[i].id);
}
UserStore.emitInChannelChange();
}
@@ -360,17 +372,9 @@ export function loadProfilesForGM() {
});
}
Client.getProfilesInChannel(
channel.id,
0,
Constants.MAX_USERS_IN_GM,
getProfilesInChannel(channel.id, 0, Constants.MAX_USERS_IN_GM)(dispatch, getState).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
populateChannelWithProfiles(channel.id, Object.keys(data));
populateChannelWithProfiles(channel.id, data);
}
);
}
@@ -420,20 +424,10 @@ export function loadProfilesForDM() {
}
if (profilesToLoad.length > 0) {
Client.getProfilesByIds(
profilesToLoad,
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
// Use membersToLoad so we get all the DM profiles even if they were already loaded
getProfilesByIds(profilesToLoad)(dispatch, getState).then(
() => {
populateDMChannelsWithProfiles(profileIds);
},
(err) => {
AsyncClient.dispatchError(err, 'getProfilesByIds');
}
);
} else {
populateDMChannelsWithProfiles(profileIds);
@@ -491,119 +485,70 @@ function onThemeSaved(teamId, theme, onSuccess) {
onSuccess();
}
export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
Client.searchUsers(
term,
teamId,
options,
export function searchUsers(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
searchProfiles(term, {team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'searchUsers');
if (error) {
error(err);
}
}
);
}
export function searchUsersNotInTeam(term, teamId = TeamStore.getCurrentId(), options = {}, success, error) {
Client.searchUsersNotInTeam(
term,
teamId,
options,
export function searchUsersNotInTeam(term, teamId = TeamStore.getCurrentId(), options = {}, success) {
searchProfiles(term, {not_in_team_id: teamId, ...options})(dispatch, getState).then(
(data) => {
loadStatusesForProfilesList(data);
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'searchUsersNotInTeam');
}
);
}
if (error) {
error(err);
export function autocompleteUsersInChannel(username, channelId, success) {
const channel = ChannelStore.get(channelId);
const teamId = channel ? channel.team_id : TeamStore.getCurrentId();
autocompleteRedux(username, teamId, channelId)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
}
);
}
export function autocompleteUsersInChannel(username, channelId, success, error) {
Client.autocompleteUsersInChannel(
username,
channelId,
export function autocompleteUsersInTeam(username, success) {
autocompleteRedux(username, TeamStore.getCurrentId())(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'autocompleteUsersInChannel');
if (error) {
error(err);
}
}
);
}
export function autocompleteUsersInTeam(username, success, error) {
Client.autocompleteUsersInTeam(
username,
export function autocompleteUsers(username, success) {
autocompleteRedux(username)(dispatch, getState).then(
(data) => {
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'autocompleteUsersInTeam');
if (error) {
error(err);
}
}
);
}
export function autocompleteUsers(username, success, error) {
Client.autocompleteUsers(
username,
export function updateUser(user, type, success, error) {
updateMe(user)(dispatch, getState).then(
(data) => {
if (success) {
if (data && success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'autocompleteUsers');
if (error) {
error(err);
}
}
);
}
export function updateUser(username, type, success, error) {
Client.updateUser(
username,
type,
(data) => {
if (success) {
success(data);
}
},
(err) => {
if (error) {
error(err);
} else {
AsyncClient.dispatchError(err, 'updateUser');
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -626,74 +571,55 @@ export function generateMfaSecret(success, error) {
);
}
export function updateUserNotifyProps(data, success, error) {
Client.updateUserNotifyProps(
data,
() => {
AsyncClient.getMe();
if (success) {
success();
}
},
(err) => {
if (error) {
error(err);
}
}
export function updateUserNotifyProps(props, success, error) {
updateMe({notify_props: props})(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateMe.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function updateUserRoles(userId, newRoles, success, error) {
Client.updateUserRoles(
userId,
newRoles,
() => {
AsyncClient.getUser(
userId,
success,
error
);
},
error
updateUserRoles(userId, newRoles)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function activateMfa(code, success, error) {
Client.updateMfa(
code,
true,
() => {
AsyncClient.getMe();
if (success) {
success();
updateUserMfa(UserStore.getCurrentId(), true, code)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
},
(err) => {
if (error) {
error(err);
}
}
);
}
export function deactivateMfa(success, error) {
Client.updateMfa(
'',
false,
() => {
AsyncClient.getMe();
if (success) {
success();
updateUserMfa(UserStore.getCurrentId(), false)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
},
(err) => {
if (error) {
error(err);
}
}
);
}
@@ -703,16 +629,13 @@ export function checkMfa(loginId, success, error) {
return;
}
Client.checkMfa(
loginId,
checkMfaRedux(loginId)(dispatch, getState).then(
(data) => {
if (success) {
success(data && data.mfa_required === 'true');
}
},
(err) => {
if (error) {
error(err);
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.checkMfa.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -735,15 +658,13 @@ export function updateActive(userId, active, success, error) {
}
export function updatePassword(userId, currentPassword, newPassword, success, error) {
Client.updatePassword(userId, currentPassword, newPassword,
() => {
if (success) {
success();
}
},
(err) => {
if (error) {
error(err);
updateUserPassword(userId, currentPassword, newPassword)(dispatch, getState).then(
(data) => {
if (data && success) {
success(data);
} else if (data == null && error) {
const serverError = getState().requests.users.updateUser.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -820,37 +741,27 @@ export function loginById(userId, password, mfaToken, success, error) {
}
export function createUserWithInvite(user, data, emailHash, inviteId, success, error) {
Client.createUserWithInvite(
user,
data,
emailHash,
inviteId,
(response) => {
if (success) {
success(response);
}
},
(err) => {
if (error) {
error(err);
createUser(user, data, emailHash, inviteId)(dispatch, getState).then(
(resp) => {
if (resp && success) {
success(resp);
} else if (resp == null && error) {
const serverError = getState().requests.users.create.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
}
export function webLogin(loginId, password, token, success, error) {
Client.webLogin(
loginId,
password,
token,
() => {
if (success) {
login(loginId, password, token)(dispatch, getState).then(
(ok) => {
if (ok && success) {
localStorage.setItem('currentUserId', UserStore.getCurrentId());
success();
}
},
(err) => {
if (error) {
error(err);
} else if (!ok && error) {
const serverError = getState().requests.users.login.error;
error({id: serverError.server_error_id, ...serverError});
}
}
);
@@ -907,7 +818,7 @@ export function uploadProfileImage(userPicture, success, error) {
Client.uploadProfileImage(
userPicture,
() => {
AsyncClient.getMe();
getMe()(dispatch, getState);
if (success) {
success();
}
@@ -920,38 +831,24 @@ export function uploadProfileImage(userPicture, success, error) {
);
}
export function loadProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE, success, error) {
Client.getProfiles(
offset,
limit,
export function loadProfiles(page, perPage, success) {
getProfiles(page, perPage)(dispatch, getState).then(
(data) => {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
if (success) {
success(data);
}
},
(err) => {
AsyncClient.dispatchError(err, 'getProfiles');
if (error) {
error(err);
}
}
);
}
export function getMissingProfiles(ids, success, error) {
export function getMissingProfiles(ids) {
const missingIds = ids.filter((id) => !UserStore.hasProfile(id));
if (missingIds.length === 0) {
return;
}
AsyncClient.getProfilesByIds(missingIds, success, error);
getProfilesByIds(missingIds)(dispatch, getState);
}
export function loadMyTeamMembers() {

Просмотреть файл

@@ -338,7 +338,6 @@ function handleUserUpdatedEvent(msg) {
const user = msg.data.user;
if (UserStore.getCurrentId() !== user.id) {
UserStore.saveProfile(user);
UserStore.emitChange(user.id);
}
}

Просмотреть файл

@@ -1,12 +1,11 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from './loading_screen.jsx';
import AuditTable from './audit_table.jsx';
import LoadingScreen from 'components/loading_screen.jsx';
import AuditTable from 'components/audit_table.jsx';
import UserStore from 'stores/user_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
@@ -16,6 +15,13 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
export default class AccessHistoryModal extends React.Component {
static propTypes = {
onHide: React.PropTypes.func.isRequired,
actions: React.PropTypes.shape({
getUserAudits: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -37,7 +43,7 @@ export default class AccessHistoryModal extends React.Component {
}
onShow() {
AsyncClient.getAudits();
this.props.actions.getUserAudits(UserStore.getCurrentId(), 0, 200);
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
@@ -100,7 +106,3 @@ export default class AccessHistoryModal extends React.Component {
);
}
}
AccessHistoryModal.propTypes = {
onHide: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getUserAudits} from 'mattermost-redux/actions/users';
import AccessHistoryModal from './access_history_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUserAudits
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AccessHistoryModal);

Просмотреть файл

@@ -1,11 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import LoadingScreen from './loading_screen.jsx';
import LoadingScreen from 'components/loading_screen.jsx';
import UserStore from 'stores/user_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import $ from 'jquery';
@@ -13,9 +12,15 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage, FormattedTime, FormattedDate} from 'react-intl';
import {revokeSession} from 'actions/admin_actions.jsx';
export default class ActivityLogModal extends React.Component {
static propTypes = {
onHide: React.PropTypes.func.isRequired,
actions: React.PropTypes.shape({
getSessions: React.PropTypes.func.isRequired,
revokeSession: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -35,7 +40,6 @@ export default class ActivityLogModal extends React.Component {
getStateFromStores() {
return {
sessions: UserStore.getSessions(),
serverError: null,
clientError: null
};
}
@@ -47,18 +51,11 @@ export default class ActivityLogModal extends React.Component {
setTimeout(() => {
modalContent.removeClass('animation--highlight');
}, 1500);
revokeSession(altId,
null,
(err) => {
const state = this.getStateFromStores();
state.serverError = err;
this.setState(state);
}
);
this.props.actions.revokeSession(UserStore.getCurrentId(), altId);
}
onShow() {
AsyncClient.getSessions();
this.props.actions.getSessions(UserStore.getCurrentId());
if (!Utils.isMobile()) {
$('.modal-body').perfectScrollbar();
}
@@ -302,7 +299,3 @@ export default class ActivityLogModal extends React.Component {
);
}
}
ActivityLogModal.propTypes = {
onHide: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -0,0 +1,25 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {revokeSession, getSessions} from 'mattermost-redux/actions/users';
import ActivityLogModal from './activity_log_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getSessions,
revokeSession
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ActivityLogModal);

Просмотреть файл

@@ -10,7 +10,6 @@ import {searchUsersNotInTeam} from 'actions/user_actions.jsx';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
import {displayUsernameForUser} from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
@@ -20,10 +19,20 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
import {searchProfilesNotInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
const MAX_SELECTABLE_VALUES = 20;
export default class AddUsersToTeam extends React.Component {
static propTypes = {
onModalDismissed: React.PropTypes.func,
actions: React.PropTypes.shape({
getProfilesNotInTeam: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -34,11 +43,12 @@ export default class AddUsersToTeam extends React.Component {
this.onChange = this.onChange.bind(this);
this.search = this.search.bind(this);
this.addValue = this.addValue.bind(this);
this.handlePageChange = this.handlePageChange.bind(this);
this.searchTimeoutId = 0;
this.state = {
users: null,
users: Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true)),
values: [],
show: true,
search: false
@@ -50,7 +60,7 @@ export default class AddUsersToTeam extends React.Component {
UserStore.addNotInTeamChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
AsyncClient.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
}
componentWillUnmount() {
@@ -97,13 +107,14 @@ export default class AddUsersToTeam extends React.Component {
this.setState({values});
}
onChange(force) {
if (this.state.search && !force) {
return;
onChange() {
let users;
if (this.term) {
users = Object.assign([], searchProfilesNotInCurrentTeam(store.getState(), this.term, true));
} else {
users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
}
const users = Object.assign([], UserStore.getProfileListNotInTeam(TeamStore.getCurrentId(), true));
for (let i = 0; i < users.length; i++) {
const user = Object.assign({}, users[i]);
user.value = user.id;
@@ -118,53 +129,25 @@ export default class AddUsersToTeam extends React.Component {
handlePageChange(page, prevPage) {
if (page > prevPage) {
AsyncClient.getProfilesNotInTeam((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
this.props.actions.getProfilesNotInTeam(TeamStore.getCurrentId(), page + 1, USERS_PER_PAGE);
}
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.onChange(true);
this.setState({search: false});
this.searchTimeoutId = '';
this.onChange();
return;
}
const teamId = TeamStore.getCurrentId();
const searchTimeoutId = setTimeout(
this.searchTimeoutId = setTimeout(
() => {
searchUsersNotInTeam(
term,
teamId,
{},
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
let indexToDelete = -1;
for (let i = 0; i < users.length; i++) {
if (users[i].id === UserStore.getCurrentId()) {
indexToDelete = i;
}
users[i].value = users[i].id;
users[i].label = '@' + users[i].username;
}
if (indexToDelete !== -1) {
users.splice(indexToDelete, 1);
}
this.setState({search: true, users});
}
);
searchUsersNotInTeam(term, TeamStore.getCurrentId(), {});
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
this.searchTimeoutId = searchTimeoutId;
}
handleDelete(values) {
@@ -272,7 +255,3 @@ export default class AddUsersToTeam extends React.Component {
);
}
}
AddUsersToTeam.propTypes = {
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfilesNotInTeam} from 'mattermost-redux/actions/users';
import AddUsersToTeam from './add_users_to_team.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfilesNotInTeam
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(AddUsersToTeam);

Просмотреть файл

@@ -23,6 +23,9 @@ import * as Utils from 'utils/utils.jsx';
import SystemUsersList from './system_users_list.jsx';
import store from 'stores/redux_store.jsx';
import {searchProfiles, searchProfilesInTeam} from 'mattermost-redux/selectors/entities/users';
const ALL_USERS = '';
const NO_TEAM = 'no_team';
@@ -120,25 +123,18 @@ export default class SystemUsers extends React.Component {
updateUsersFromStore(teamId = this.state.teamId, term = this.state.term) {
if (term) {
if (teamId === this.state.teamId) {
// Search results aren't in the store, so manually update the users in them
const users = [...this.state.users];
for (let i = 0; i < users.length; i++) {
const user = users[i];
if (UserStore.hasProfile(user.id)) {
users[i] = UserStore.getProfile(user.id);
}
}
this.setState({
users
});
let users;
if (teamId) {
users = searchProfilesInTeam(store.getState(), teamId, term);
} else {
this.doSearch(teamId, term, true);
users = searchProfiles(store.getState(), term);
}
if (users.length === 0 && UserStore.hasProfile(term)) {
users = [UserStore.getProfile(term)];
}
this.setState({users});
return;
}
@@ -179,11 +175,11 @@ export default class SystemUsers extends React.Component {
// Paging isn't supported while searching
if (this.state.teamId === ALL_USERS) {
loadProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.loadComplete);
loadProfiles(page, USERS_PER_PAGE, this.loadComplete);
} else if (this.state.teamId === NO_TEAM) {
loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
} else {
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
}
}
@@ -204,11 +200,9 @@ export default class SystemUsers extends React.Component {
doSearch(teamId, term, now = false) {
clearTimeout(this.searchTimeoutId);
this.term = term;
this.setState({
loading: true,
users: []
});
this.setState({loading: true});
const options = {
[UserSearchOptions.ALLOW_INACTIVE]: true
@@ -217,74 +211,45 @@ export default class SystemUsers extends React.Component {
options[UserSearchOptions.WITHOUT_TEAM] = true;
}
const searchTimeoutId = setTimeout(
this.searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
teamId,
options,
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
if (users.length > 0) {
this.setState({
loading: false,
users
});
} else if (term.length === USER_ID_LENGTH) {
if (users.length === 0 && term.length === USER_ID_LENGTH) {
// This term didn't match any users name, but it does look like it might be a user's ID
this.getUserById(term, searchTimeoutId);
this.getUserById(term);
} else {
this.setState({
loading: false
});
this.setState({loading: false});
}
},
() => {
this.setState({
loading: false
});
this.setState({loading: false});
}
);
},
now ? 0 : Constants.SEARCH_TIMEOUT_MILLISECONDS
);
this.searchTimeoutId = searchTimeoutId;
}
getUserById(id, searchTimeoutId) {
getUserById(id) {
if (UserStore.hasProfile(id)) {
this.setState({
loading: false,
users: [UserStore.getProfile(id)]
});
this.setState({loading: false});
return;
}
getUser(
id,
(user) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
() => {
this.setState({
loading: false,
users: [user]
loading: false
});
},
() => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({
loading: false,
users: []
loading: false
});
}
);

Просмотреть файл

@@ -5,11 +5,11 @@ import $ from 'jquery';
import 'bootstrap';
import NavbarSearchBox from './search_bar.jsx';
import MessageWrapper from './message_wrapper.jsx';
import PopoverListMembers from './popover_list_members.jsx';
import PopoverListMembers from 'components/popover_list_members';
import EditChannelHeaderModal from './edit_channel_header_modal.jsx';
import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';

Просмотреть файл

@@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ChannelInviteButton from './channel_invite_button.jsx';
import ChannelInviteButton from 'components/channel_invite_button.jsx';
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
import LoadingScreen from './loading_screen.jsx';
import LoadingScreen from 'components/loading_screen.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -19,9 +19,20 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import store from 'stores/redux_store.jsx';
import {searchProfilesNotInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
export default class ChannelInviteModal extends React.Component {
static propTypes = {
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired,
actions: React.PropTypes.shape({
getProfilesNotInChannel: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -39,10 +50,9 @@ export default class ChannelInviteModal extends React.Component {
const teamStats = TeamStore.getCurrentStats();
this.state = {
users: null,
users: UserStore.getProfileListNotInChannel(props.channel.id, true),
total: teamStats.active_member_count - channelStats.member_count,
show: true,
search: false,
statusChange: false
};
}
@@ -53,7 +63,7 @@ export default class ChannelInviteModal extends React.Component {
UserStore.addNotInChannelChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onStatusChange);
AsyncClient.getProfilesNotInChannel(this.props.channel.id, 0);
this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, 0);
AsyncClient.getTeamStats(TeamStore.getCurrentId());
}
@@ -64,17 +74,19 @@ export default class ChannelInviteModal extends React.Component {
UserStore.removeStatusesChangeListener(this.onStatusChange);
}
onChange(force) {
if (this.state.search && !force) {
this.search(this.term);
return;
onChange() {
let users;
if (this.term) {
users = searchProfilesNotInCurrentChannel(store.getState(), this.term, true);
} else {
users = UserStore.getProfileListNotInChannel(this.props.channel.id, true);
}
const channelStats = ChannelStore.getStats(this.props.channel.id);
const teamStats = TeamStore.getCurrentStats();
this.setState({
users: UserStore.getProfileListNotInChannel(this.props.channel.id, true),
users,
total: teamStats.active_member_count - channelStats.member_count
});
}
@@ -103,40 +115,24 @@ export default class ChannelInviteModal extends React.Component {
}
nextPage(page) {
AsyncClient.getProfilesNotInChannel(this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
this.props.actions.getProfilesNotInChannel(TeamStore.getCurrentId(), this.props.channel.id, (page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.onChange(true);
this.setState({search: false});
this.searchTimeoutId = '';
this.onChange();
return;
}
const searchTimeoutId = setTimeout(
this.searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
TeamStore.getCurrentId(),
{not_in_channel_id: this.props.channel.id},
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({search: true, users});
}
);
searchUsers(term, TeamStore.getCurrentId(), {not_in_channel_id: this.props.channel.id});
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
this.searchTimeoutId = searchTimeoutId;
}
render() {
@@ -190,8 +186,3 @@ export default class ChannelInviteModal extends React.Component {
);
}
}
ChannelInviteModal.propTypes = {
onHide: React.PropTypes.func.isRequired,
channel: React.PropTypes.object.isRequired
};

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfilesNotInChannel} from 'mattermost-redux/actions/users';
import ChannelInviteModal from './channel_invite_modal.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfilesNotInChannel
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChannelInviteModal);

Просмотреть файл

@@ -209,19 +209,15 @@ export default class LoginController extends React.Component {
}
finishSignin(team) {
GlobalActions.emitInitialLoad(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
if (query.redirect_to) {
browserHistory.push(query.redirect_to);
} else if (team) {
browserHistory.push(`/${team.name}`);
} else {
GlobalActions.redirectUserToDefaultTeam();
}
}
);
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
if (query.redirect_to) {
browserHistory.push(query.redirect_to);
} else if (team) {
browserHistory.push(`/${team.name}`);
} else {
GlobalActions.redirectUserToDefaultTeam();
}
}
handleLoginIdChange(e) {

Просмотреть файл

@@ -17,6 +17,9 @@ import * as UserAgent from 'utils/user_agent.jsx';
import React from 'react';
import store from 'stores/redux_store.jsx';
import {searchProfilesInCurrentChannel} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
export default class MemberListChannel extends React.Component {
@@ -29,6 +32,7 @@ export default class MemberListChannel extends React.Component {
this.loadComplete = this.loadComplete.bind(this);
this.searchTimeoutId = 0;
this.term = '';
const stats = ChannelStore.getCurrentStats();
@@ -37,8 +41,6 @@ export default class MemberListChannel extends React.Component {
teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
channelMembers: Object.assign({}, ChannelStore.getMembersInChannel()),
total: stats.member_count,
search: false,
term: '',
loading: true
};
}
@@ -66,16 +68,16 @@ export default class MemberListChannel extends React.Component {
this.setState({loading: false});
}
onChange(force) {
if (this.state.search && !force) {
return;
} else if (this.state.search) {
this.search(this.state.term);
return;
onChange() {
let users;
if (this.term) {
users = searchProfilesInCurrentChannel(store.getState(), this.term);
} else {
users = UserStore.getProfileListInChannel();
}
this.setState({
users: UserStore.getProfileListInChannel(),
users,
teamMembers: Object.assign({}, TeamStore.getMembersInTeam()),
channelMembers: Object.assign({}, ChannelStore.getMembersInChannel())
});
@@ -87,43 +89,30 @@ export default class MemberListChannel extends React.Component {
}
nextPage(page) {
loadProfilesAndTeamMembersAndChannelMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
loadProfilesAndTeamMembersAndChannelMembers(page + 1, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.setState({
search: false,
term,
users: UserStore.getProfileListInChannel(),
teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
});
this.setState({loading: false});
this.searchTimeoutId = '';
this.onChange();
return;
}
const searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
TeamStore.getCurrentId(),
{},
searchUsers(term, TeamStore.getCurrentId(), {},
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({
loading: true,
search: true,
users,
term,
teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
channelMembers: Object.assign([], ChannelStore.getMembersInChannel())
});
this.setState({loading: true});
loadTeamMembersAndChannelMembersForProfilesList(users, TeamStore.getCurrentId(), ChannelStore.getCurrentId(), this.loadComplete);
}
);

Просмотреть файл

@@ -2,7 +2,7 @@
// See License.txt for license information.
import SearchableUserList from 'components/searchable_user_list/searchable_user_list_container.jsx';
import TeamMembersDropdown from 'components/team_members_dropdown.jsx';
import TeamMembersDropdown from 'components/team_members_dropdown';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
@@ -16,6 +16,9 @@ import * as UserAgent from 'utils/user_agent.jsx';
import React from 'react';
import store from 'stores/redux_store.jsx';
import {searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
export default class MemberListTeam extends React.Component {
@@ -29,6 +32,7 @@ export default class MemberListTeam extends React.Component {
this.loadComplete = this.loadComplete.bind(this);
this.searchTimeoutId = 0;
this.term = '';
const stats = TeamStore.getCurrentStats();
@@ -36,8 +40,6 @@ export default class MemberListTeam extends React.Component {
users: UserStore.getProfileListInTeam(),
teamMembers: Object.assign([], TeamStore.getMembersInTeam()),
total: stats.total_member_count,
search: false,
term: '',
loading: true
};
}
@@ -67,15 +69,15 @@ export default class MemberListTeam extends React.Component {
this.onChange(true);
}
onChange(force) {
if (this.state.search && !force) {
return;
} else if (this.state.search) {
this.search(this.state.term);
return;
onChange() {
let users;
if (this.term) {
users = searchProfilesInCurrentTeam(store.getState(), this.term);
} else {
users = UserStore.getProfileListInTeam();
}
this.setState({users: UserStore.getProfileListInTeam(), teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
this.setState({users, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
}
onStatsChange() {
@@ -84,15 +86,17 @@ export default class MemberListTeam extends React.Component {
}
nextPage(page) {
loadProfilesAndTeamMembers((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
loadProfilesAndTeamMembers(page, USERS_PER_PAGE);
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.setState({search: false, term, users: UserStore.getProfileListInTeam(), teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
this.setState({loading: false});
this.searchTimeoutId = '';
this.onChange();
return;
}
@@ -106,7 +110,7 @@ export default class MemberListTeam extends React.Component {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
this.setState({loading: true, search: true, users, term, teamMembers: Object.assign([], TeamStore.getMembersInTeam())});
this.setState({loading: true});
loadTeamMembersForProfilesList(users, TeamStore.getCurrentId(), this.loadComplete);
}
);

Просмотреть файл

@@ -0,0 +1,25 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfiles, getProfilesInTeam} from 'mattermost-redux/actions/users';
import MoreDirectChannels from './more_direct_channels.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfiles,
getProfilesInTeam
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MoreDirectChannels);

Просмотреть файл

@@ -10,7 +10,6 @@ import {openDirectChannelToUser, openGroupChannelToUsers} from 'actions/channel_
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Constants from 'utils/constants.jsx';
import {displayUsernameForUser} from 'utils/utils.jsx';
import Client from 'client/web_client.jsx';
@@ -20,10 +19,22 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
import store from 'stores/redux_store.jsx';
import {searchProfiles, searchProfilesInCurrentTeam} from 'mattermost-redux/selectors/entities/users';
const USERS_PER_PAGE = 50;
const MAX_SELECTABLE_VALUES = Constants.MAX_USERS_IN_GM - 1;
export default class MoreDirectChannels extends React.Component {
static propTypes = {
startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
onModalDismissed: React.PropTypes.func,
actions: React.PropTypes.shape({
getProfiles: React.PropTypes.func.isRequired,
getProfilesInTeam: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -36,6 +47,7 @@ export default class MoreDirectChannels extends React.Component {
this.addValue = this.addValue.bind(this);
this.searchTimeoutId = 0;
this.term = '';
this.listType = global.window.mm_config.RestrictDirectMessage;
const values = [];
@@ -63,9 +75,9 @@ export default class MoreDirectChannels extends React.Component {
UserStore.addStatusesChangeListener(this.onChange);
if (this.listType === 'any') {
AsyncClient.getProfiles(0, USERS_PER_PAGE * 2);
this.props.actions.getProfiles(0, USERS_PER_PAGE * 2);
} else {
AsyncClient.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
this.props.actions.getProfilesInTeam(TeamStore.getCurrentId(), 0, USERS_PER_PAGE * 2);
}
}
@@ -134,13 +146,15 @@ export default class MoreDirectChannels extends React.Component {
this.setState({values});
}
onChange(force) {
if (this.state.search && !force) {
return;
}
onChange() {
let users;
if (this.listType === 'any') {
if (this.term) {
if (this.listType === 'any') {
users = Object.assign([], searchProfiles(store.getState(), this.term, true));
} else {
users = Object.assign([], searchProfilesInCurrentTeam(store.getState(), this.term, true));
}
} else if (this.listType === 'any') {
users = Object.assign([], UserStore.getProfileList(true));
} else {
users = Object.assign([], UserStore.getProfileListInTeam(TeamStore.getCurrentId(), true));
@@ -160,17 +174,20 @@ export default class MoreDirectChannels extends React.Component {
handlePageChange(page, prevPage) {
if (page > prevPage) {
AsyncClient.getProfiles((page + 1) * USERS_PER_PAGE, USERS_PER_PAGE);
if (this.listType === 'any') {
this.props.actions.getProfiles(page + 1, USERS_PER_PAGE);
} else {
this.props.actions.getProfilesInTeam(page + 1, USERS_PER_PAGE);
}
}
}
search(term) {
clearTimeout(this.searchTimeoutId);
this.term = term;
if (term === '') {
this.onChange(true);
this.setState({search: false});
this.searchTimeoutId = '';
this.onChange();
return;
}
@@ -181,37 +198,12 @@ export default class MoreDirectChannels extends React.Component {
teamId = TeamStore.getCurrentId();
}
const searchTimeoutId = setTimeout(
this.searchTimeoutId = setTimeout(
() => {
searchUsers(
term,
teamId,
{},
(users) => {
if (searchTimeoutId !== this.searchTimeoutId) {
return;
}
let indexToDelete = -1;
for (let i = 0; i < users.length; i++) {
if (users[i].id === UserStore.getCurrentId()) {
indexToDelete = i;
}
users[i].value = users[i].id;
users[i].label = '@' + users[i].username;
}
if (indexToDelete !== -1) {
users.splice(indexToDelete, 1);
}
this.setState({search: true, users});
}
);
searchUsers(term, teamId);
},
Constants.SEARCH_TIMEOUT_MILLISECONDS
);
this.searchTimeoutId = searchTimeoutId;
}
handleDelete(values) {
@@ -334,8 +326,3 @@ export default class MoreDirectChannels extends React.Component {
);
}
}
MoreDirectChannels.propTypes = {
startingUsers: React.PropTypes.arrayOf(React.PropTypes.object),
onModalDismissed: React.PropTypes.func
};

Просмотреть файл

@@ -7,7 +7,7 @@ import EditChannelPurposeModal from './edit_channel_purpose_modal.jsx';
import MessageWrapper from './message_wrapper.jsx';
import NotifyCounts from './notify_counts.jsx';
import ChannelInfoModal from './channel_info_modal.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import ChannelInviteModal from 'components/channel_invite_modal';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelNotificationsModal from './channel_notifications_modal.jsx';
import DeleteChannelModal from './delete_channel_modal.jsx';

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getProfilesInChannel} from 'mattermost-redux/actions/users';
import PopoverListMembers from './popover_list_members.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getProfilesInChannel
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(PopoverListMembers);

Просмотреть файл

@@ -7,13 +7,12 @@ import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import ChannelMembersModal from './channel_members_modal.jsx';
import ChannelInviteModal from './channel_invite_modal.jsx';
import TeamMembersModal from 'components/team_members_modal.jsx';
import ChannelMembersModal from 'components/channel_members_modal.jsx';
import ChannelInviteModal from 'components/channel_invite_modal';
import {openDirectChannelToUser} from 'actions/channel_actions.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -26,6 +25,15 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
export default class PopoverListMembers extends React.Component {
static propTypes = {
channel: React.PropTypes.object.isRequired,
members: React.PropTypes.array.isRequired,
memberCount: React.PropTypes.number,
actions: React.PropTypes.shape({
getProfilesInChannel: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -239,7 +247,7 @@ export default class PopoverListMembers extends React.Component {
ref='member_popover_target'
onClick={(e) => {
this.setState({popoverTarget: e.target, showPopover: !this.state.showPopover});
AsyncClient.getProfilesInChannel(this.props.channel.id, 0);
this.props.actions.getProfilesInChannel(this.props.channel.id, 0);
}}
>
{countText}
@@ -272,8 +280,3 @@ export default class PopoverListMembers extends React.Component {
}
}
PopoverListMembers.propTypes = {
channel: React.PropTypes.object.isRequired,
members: React.PropTypes.array.isRequired,
memberCount: React.PropTypes.number
};

Просмотреть файл

@@ -136,6 +136,7 @@ export default class Root extends React.Component {
);
}
}
Root.defaultProps = {
};

Просмотреть файл

@@ -4,7 +4,7 @@
import $ from 'jquery';
import ReactDOM from 'react-dom';
import NewChannelFlow from './new_channel_flow.jsx';
import MoreDirectChannels from './more_direct_channels.jsx';
import MoreDirectChannels from 'components/more_direct_channels';
import MoreChannels from 'components/more_channels.jsx';
import SidebarHeader from './sidebar_header.jsx';
import UnreadChannelIndicator from './unread_channel_indicator.jsx';

Просмотреть файл

@@ -14,7 +14,7 @@ import AboutBuildModal from './about_build_modal.jsx';
import SidebarHeaderDropdownButton from './sidebar_header_dropdown_button.jsx';
import TeamMembersModal from './team_members_modal.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import AddUsersToTeam from './add_users_to_team.jsx';
import AddUsersToTeam from 'components/add_users_to_team';
import {Constants, WebrtcActionTypes} from 'utils/constants.jsx';

Просмотреть файл

@@ -6,7 +6,7 @@ import TeamMembersModal from './team_members_modal.jsx';
import ToggleModalButton from './toggle_modal_button.jsx';
import UserSettingsModal from './user_settings/user_settings_modal.jsx';
import AboutBuildModal from './about_build_modal.jsx';
import AddUsersToTeam from './add_users_to_team.jsx';
import AddUsersToTeam from 'components/add_users_to_team';
import UserStore from 'stores/user_store.jsx';
import TeamStore from 'stores/team_store.jsx';

Просмотреть файл

@@ -8,7 +8,7 @@ import {trackEvent} from 'actions/diagnostics_actions.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import {getInviteInfo} from 'actions/team_actions.jsx';
import {loginById, createUserWithInvite} from 'actions/user_actions.jsx';
import {loadMe, loginById, createUserWithInvite} from 'actions/user_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -108,7 +108,7 @@ export default class SignupEmail extends React.Component {
}
finishSignup() {
GlobalActions.emitInitialLoad(
loadMe(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();
@@ -132,7 +132,7 @@ export default class SignupEmail extends React.Component {
BrowserStore.setGlobalItem(this.state.hash, JSON.stringify({usedBefore: true}));
}
GlobalActions.emitInitialLoad(
loadMe(
() => {
const query = this.props.location.query;
if (query.redirect_to) {

Просмотреть файл

@@ -5,7 +5,7 @@ import FormError from 'components/form_error.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {addUserToTeamFromInvite} from 'actions/team_actions.jsx';
import {webLoginByLdap} from 'actions/user_actions.jsx';
import {loadMe, webLoginByLdap} from 'actions/user_actions.jsx';
import {trackEvent} from 'actions/diagnostics_actions.jsx';
import * as Utils from 'utils/utils.jsx';
@@ -97,7 +97,7 @@ export default class SignupLdap extends React.Component {
}
finishSignup() {
GlobalActions.emitInitialLoad(
loadMe(
() => {
const query = this.props.location.query;
GlobalActions.loadDefaultLocale();

Просмотреть файл

@@ -13,6 +13,7 @@ import * as AsyncClient from 'utils/async_client.jsx';
import Client from 'client/web_client.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {addUserToTeamFromInvite, getInviteInfo} from 'actions/team_actions.jsx';
import {loadMe} from 'actions/user_actions.jsx';
import logoImage from 'images/logo.png';
import ErrorBar from 'components/error_bar.jsx';
@@ -74,7 +75,7 @@ export default class SignupController extends React.Component {
hash,
inviteId,
(team) => {
GlobalActions.emitInitialLoad(
loadMe(
() => {
browserHistory.push('/' + team.name + '/channels/town-square');
}

Просмотреть файл

@@ -127,14 +127,14 @@ export default class AtMentionProvider extends Provider {
return;
}
const members = data.in_channel;
const members = Object.assign([], data.users);
for (const id of Object.keys(members)) {
members[id].type = Constants.MENTION_MEMBERS;
members[id] = {...members[id], type: Constants.MENTION_MEMBERS};
}
const nonmembers = data.out_of_channel;
const nonmembers = data.out_of_channel || [];
for (const id of Object.keys(nonmembers)) {
nonmembers[id].type = Constants.MENTION_NONMEMBERS;
nonmembers[id] = {...nonmembers[id], type: Constants.MENTION_NONMEMBERS};
}
let specialMentions = [];

Просмотреть файл

@@ -72,7 +72,7 @@ export default class SearchUserProvider extends Provider {
return;
}
const users = data.in_team;
const users = Object.assign([], data.users);
const mentions = users.map((user) => user.username);
AppDispatcher.handleServerAction({

Просмотреть файл

@@ -67,7 +67,9 @@ export default class SwitchChannelProvider extends Provider {
autocompleteUsers(
channelPrefix,
(users) => {
(data) => {
const users = Object.assign([], data.users);
if (this.shouldCancelDispatch(channelPrefix)) {
return;
}

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getUser} from 'mattermost-redux/actions/users';
import TeamMembersDropdown from './team_members_dropdown.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getUser
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TeamMembersDropdown);

Просмотреть файл

@@ -1,7 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import ConfirmModal from './confirm_modal.jsx';
import ConfirmModal from 'components/confirm_modal.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
@@ -18,6 +18,14 @@ import {FormattedMessage} from 'react-intl';
import {browserHistory} from 'react-router/es6';
export default class TeamMembersDropdown extends React.Component {
static propTypes = {
user: React.PropTypes.object.isRequired,
teamMember: React.PropTypes.object.isRequired,
actions: React.PropTypes.shape({
getUser: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -48,7 +56,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user',
() => {
AsyncClient.getUser(this.props.user.id);
this.props.actions.getUser(this.props.user.id);
if (this.props.user.id === me.id) {
loadMyTeamMembers();
@@ -110,7 +118,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
'team_user team_admin',
() => {
AsyncClient.getUser(this.props.user.id);
this.props.actions.getUser(this.props.user.id);
},
(err) => {
this.setState({serverError: err.message});
@@ -145,7 +153,7 @@ export default class TeamMembersDropdown extends React.Component {
this.props.user.id,
this.state.newRole,
() => {
AsyncClient.getUser(this.props.user.id);
this.props.actions.getUser(this.props.user.id);
const teamUrl = TeamStore.getCurrentTeamUrl();
if (teamUrl) {
@@ -385,8 +393,3 @@ export default class TeamMembersDropdown extends React.Component {
);
}
}
TeamMembersDropdown.propTypes = {
user: React.PropTypes.object.isRequired,
teamMember: React.PropTypes.object.isRequired
};

Просмотреть файл

@@ -4,8 +4,8 @@
import UserStore from 'stores/user_store.jsx';
import * as utils from 'utils/utils.jsx';
import NotificationsTab from './user_settings_notifications.jsx';
import SecurityTab from './user_settings_security.jsx';
import GeneralTab from './user_settings_general.jsx';
import SecurityTab from './user_settings_security';
import GeneralTab from './user_settings_general';
import DisplayTab from './user_settings_display.jsx';
import AdvancedTab from './user_settings_advanced.jsx';

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getMe} from 'mattermost-redux/actions/users';
import UserSettingsGeneralTab from './user_settings_general.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getMe
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(UserSettingsGeneralTab);

Просмотреть файл

@@ -2,16 +2,15 @@
// See License.txt for license information.
import $ from 'jquery';
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import SettingPicture from '../setting_picture.jsx';
import SettingItemMin from 'components/setting_item_min.jsx';
import SettingItemMax from 'components/setting_item_max.jsx';
import SettingPicture from 'components/setting_picture.jsx';
import UserStore from 'stores/user_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import Client from 'client/web_client.jsx';
import Constants from 'utils/constants.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import {intlShape, injectIntl, defineMessages, FormattedMessage, FormattedHTMLMessage, FormattedDate} from 'react-intl';
@@ -80,6 +79,19 @@ const holders = defineMessages({
import React from 'react';
class UserSettingsGeneralTab extends React.Component {
static propTypes = {
intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired,
updateSection: React.PropTypes.func.isRequired,
updateTab: React.PropTypes.func.isRequired,
activeSection: React.PropTypes.string.isRequired,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired,
actions: React.PropTypes.shape({
getMe: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
this.submitActive = false;
@@ -205,7 +217,7 @@ class UserSettingsGeneralTab extends React.Component {
updateUser(user, type,
() => {
this.updateSection('');
AsyncClient.getMe();
this.props.actions.getMe();
const verificationEnabled = global.window.mm_config.SendEmailNotifications === 'true' && global.window.mm_config.RequireEmailVerification === 'true' && emailUpdated;
if (verificationEnabled) {
@@ -1194,14 +1206,4 @@ class UserSettingsGeneralTab extends React.Component {
}
}
UserSettingsGeneralTab.propTypes = {
intl: intlShape.isRequired,
user: React.PropTypes.object.isRequired,
updateSection: React.PropTypes.func.isRequired,
updateTab: React.PropTypes.func.isRequired,
activeSection: React.PropTypes.string.isRequired,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
export default injectIntl(UserSettingsGeneralTab);

Просмотреть файл

@@ -0,0 +1,24 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {getMe} from 'mattermost-redux/actions/users';
import SecurityTab from './user_settings_security.jsx';
function mapStateToProps(state, ownProps) {
return {
...ownProps
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
getMe
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SecurityTab);

Просмотреть файл

@@ -1,15 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import SettingItemMin from '../setting_item_min.jsx';
import SettingItemMax from '../setting_item_max.jsx';
import AccessHistoryModal from '../access_history_modal.jsx';
import ActivityLogModal from '../activity_log_modal.jsx';
import ToggleModalButton from '../toggle_modal_button.jsx';
import SettingItemMin from 'components/setting_item_min.jsx';
import SettingItemMax from 'components/setting_item_max.jsx';
import AccessHistoryModal from 'components/access_history_modal';
import ActivityLogModal from 'components/activity_log_modal';
import ToggleModalButton from 'components/toggle_modal_button.jsx';
import PreferenceStore from 'stores/preference_store.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
@@ -23,6 +22,19 @@ import {browserHistory, Link} from 'react-router/es6';
import icon50 from 'images/icon50x50.png';
export default class SecurityTab extends React.Component {
static propTypes = {
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired,
setEnforceFocus: React.PropTypes.func.isRequired,
actions: React.PropTypes.shape({
getMe: React.PropTypes.func.isRequired
}).isRequired
}
constructor(props) {
super(props);
@@ -98,7 +110,7 @@ export default class SecurityTab extends React.Component {
newPassword,
() => {
this.props.updateSection('');
AsyncClient.getMe();
this.props.actions.getMe();
this.setState(this.getDefaultState());
},
(err) => {
@@ -1022,12 +1034,3 @@ SecurityTab.defaultProps = {
user: {},
activeSection: ''
};
SecurityTab.propTypes = {
user: React.PropTypes.object,
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func,
updateTab: React.PropTypes.func,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired,
setEnforceFocus: React.PropTypes.func.isRequired
};

Просмотреть файл

@@ -19,8 +19,10 @@
"intl": "1.2.5",
"jasny-bootstrap": "3.1.3",
"jquery": "3.1.1",
"localforage": "1.5.0",
"marked": "mattermost/marked#8f5902fff9bad793cd6c66e0c44002c9e79e1317",
"match-at": "0.1.0",
"mattermost-redux": "mattermost/mattermost-redux#webapp-part2",
"object-assign": "4.1.1",
"pdfjs-dist": "1.7.363",
"perfect-scrollbar": "0.6.16",
@@ -30,19 +32,26 @@
"react-custom-scrollbars": "4.0.2",
"react-dom": "15.4.2",
"react-intl": "2.2.3",
"react-redux": "5.0.4",
"react-router": "2.8.1",
"react-select": "1.0.0-rc.3",
"redux-batched-actions": "0.1.5",
"redux-persist": "4.6.0",
"redux-persist-transform-filter": "0.0.9",
"superagent": "3.5.0",
"twemoji": "2.2.5",
"velocity-animate": "1.4.3",
"webrtc-adapter": "3.2.0",
"whatwg-fetch": "2.0.3",
"xregexp": "3.1.1"
},
"devDependencies": {
"babel-cli": "6.24.1",
"babel-core": "6.24.0",
"babel-eslint": "7.1.1",
"babel-jest": "19.0.0",
"babel-loader": "6.4.0",
"babel-plugin-module-resolver": "2.7.0",
"babel-plugin-transform-runtime": "6.23.0",
"babel-polyfill": "6.23.0",
"babel-preset-es2015": "6.24.0",
@@ -73,9 +82,11 @@
"raw-loader": "0.5.1",
"react-addons-test-utils": "15.4.2",
"react-dom": "15.4.2",
"react-outside-event": "1.2.4",
"remote-redux-devtools": "0.5.7",
"remote-redux-devtools-on-debugger": "0.7.0",
"sass-loader": "6.0.3",
"style-loader": "0.13.2",
"react-outside-event": "1.2.4",
"url-loader": "0.5.8",
"webpack": "2.2.1",
"webpack-node-externals": "1.5.4"

Просмотреть файл

@@ -6,22 +6,28 @@ require('perfect-scrollbar/jquery')($);
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {Router, browserHistory} from 'react-router/es6';
import PDFJS from 'pdfjs-dist';
import * as GlobalActions from 'actions/global_actions.jsx';
import * as Websockets from 'actions/websocket_actions.jsx';
import {loadMeAndConfig} from 'actions/user_actions.jsx';
import BrowserStore from 'stores/browser_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import UserStore from 'stores/user_store.jsx';
import * as I18n from 'i18n/i18n.jsx';
import * as AsyncClient from 'utils/async_client.jsx';
import {getClientConfig, getLicenseConfig, setUrl} from 'mattermost-redux/actions/general';
// Import our styles
import 'bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css';
import 'google-fonts/google-fonts.css';
import 'sass/styles.scss';
import 'katex/dist/katex.min.css';
import store from 'stores/redux_store.jsx';
// Import the root of our routing tree
import rRoot from 'routes/route_root.jsx';
@@ -51,11 +57,26 @@ function preRenderSetup(callwhendone) {
var d1 = $.Deferred(); //eslint-disable-line new-cap
GlobalActions.emitInitialLoad(
() => {
d1.resolve();
}
);
setUrl(window.location.origin);
const currentUserId = localStorage.getItem('currentUserId');
if (currentUserId) {
loadMeAndConfig(() => d1.resolve());
} else {
getClientConfig()(store.dispatch, store.getState).then(
(config) => {
global.window.mm_config = config;
getLicenseConfig()(store.dispatch, store.getState).then(
(license) => {
global.window.mm_license = license;
d1.resolve();
}
);
}
);
}
// Make sure the websockets close and reset version
$(window).on('beforeunload',
@@ -86,10 +107,12 @@ function preRenderSetup(callwhendone) {
function renderRootComponent() {
ReactDOM.render((
<Router
history={browserHistory}
routes={rRoot}
/>
<Provider store={store}>
<Router
history={browserHistory}
routes={rRoot}
/>
</Provider>
),
document.getElementById('root'));
}

112
webapp/store/index.js Обычный файл
Просмотреть файл

@@ -0,0 +1,112 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {batchActions} from 'redux-batched-actions';
import configureServiceStore from 'mattermost-redux/store';
import {General, RequestStatus} from 'mattermost-redux/constants';
import reduxInitialState from 'mattermost-redux/store/initial_state';
import {createTransform, persistStore} from 'redux-persist';
import localForage from 'localforage';
import {transformSet} from './utils';
const usersSetTransform = [
'profilesInChannel',
'profilesNotInChannel',
'profilesInTeam',
'profilesNotInTeam'
];
const teamSetTransform = [
'membersInTeam'
];
const setTransforms = [
...usersSetTransform,
...teamSetTransform
];
export default function configureStore(initialState) {
const setTransformer = createTransform(
(inboundState, key) => {
if (key === 'entities') {
const state = {...inboundState};
for (const prop in state) {
if (state.hasOwnProperty(prop)) {
state[prop] = transformSet(state[prop], setTransforms);
}
}
return state;
}
return inboundState;
},
(outboundState, key) => {
if (key === 'entities') {
const state = {...outboundState};
for (const prop in state) {
if (state.hasOwnProperty(prop)) {
state[prop] = transformSet(state[prop], setTransforms, false);
}
}
return state;
}
return outboundState;
}
);
const offlineOptions = {
persist: (store, options) => {
const persistor = persistStore(store, {storage: localForage, ...options}, () => {
store.dispatch({
type: General.STORE_REHYDRATION_COMPLETE,
complete: true
});
});
let purging = false;
// check to see if the logout request was successful
store.subscribe(() => {
const state = store.getState();
if (state.requests.users.logout.status === RequestStatus.SUCCESS && !purging) {
purging = true;
persistor.purge();
store.dispatch(batchActions([
{
type: General.OFFLINE_STORE_RESET,
data: Object.assign({}, reduxInitialState, initialState)
}
]));
localStorage.removeItem('currentUserId');
window.location.href = '/';
setTimeout(() => {
purging = false;
}, 500);
}
});
return persistor;
},
persistOptions: {
autoRehydrate: {
log: false
},
blacklist: ['errors', 'offline', 'requests', 'entities'],
debounce: 500,
transforms: [
setTransformer
]
}
};
return configureServiceStore({}, {}, offlineOptions);
}

42
webapp/store/utils.js Обычный файл
Просмотреть файл

@@ -0,0 +1,42 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
function transformFromSet(incoming) {
const state = {...incoming};
for (const key in state) {
if (state.hasOwnProperty(key)) {
if (state[key] instanceof Set) {
state[key] = Array.from([...state[key]]);
}
}
}
return state;
}
function transformToSet(incoming) {
const state = {...incoming};
for (const key in state) {
if (state.hasOwnProperty(key)) {
state[key] = new Set(state[key]);
}
}
return state;
}
export function transformSet(incoming, setTransforms, toStorage = true) {
const state = {...incoming};
const transformer = toStorage ? transformFromSet : transformToSet;
for (const key in state) {
if (state.hasOwnProperty(key) && setTransforms.includes(key)) {
state[key] = transformer(state[key]);
}
}
return state;
}

Просмотреть файл

@@ -8,6 +8,10 @@ import EventEmitter from 'events';
const CHANGE_EVENT = 'change';
import store from 'stores/redux_store.jsx';
import * as Selectors from 'mattermost-redux/selectors/entities/preferences';
import {PreferenceTypes} from 'mattermost-redux/action_types';
class PreferenceStore extends EventEmitter {
constructor() {
super();
@@ -16,6 +20,23 @@ class PreferenceStore extends EventEmitter {
this.dispatchToken = AppDispatcher.register(this.handleEventPayload);
this.preferences = new Map();
this.entities = Selectors.getMyPreferences(store.getState());
Object.keys(this.entities).forEach((key) => {
this.preferences.set(key, this.entities[key].value);
});
store.subscribe(() => {
const newEntities = Selectors.getMyPreferences(store.getState());
if (this.entities !== newEntities) {
this.preferences = new Map();
Object.keys(newEntities).forEach((key) => {
this.preferences.set(key, newEntities[key].value);
});
this.emitChange();
}
this.entities = newEntities;
});
this.setMaxListeners(600);
}
@@ -79,21 +100,24 @@ class PreferenceStore extends EventEmitter {
}
setPreference(category, name, value) {
this.preferences.set(this.getKey(category, name), value);
store.dispatch({
type: PreferenceTypes.RECEIVED_PREFERENCES,
data: [{category, name, value}]
});
}
setPreferencesFromServer(newPreferences) {
for (const preference of newPreferences) {
this.setPreference(preference.category, preference.name, preference.value);
}
store.dispatch({
type: PreferenceTypes.RECEIVED_PREFERENCES,
data: newPreferences
});
}
deletePreference(preference) {
this.preferences.delete(this.getKey(preference.category, preference.name));
}
clear() {
this.preferences.clear();
store.dispatch({
type: PreferenceTypes.DELETED_PREFERENCES,
data: [preference]
});
}
emitChange(category) {

19
webapp/stores/redux_store.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
// This is a temporary store while we are transitioning from Flux to Redux. This file exports
// the configured Redux store for use by actions and selectors.
import configureStore from 'store';
const store = configureStore();
export function bindActionToRedux(action, ...args) {
return async () => {
await action(...args)(store.dispatch, store.getState);
};
}
window.store = store;
export default store;

Просмотреть файл

@@ -17,17 +17,33 @@ const CHANGE_EVENT = 'change';
const STATS_EVENT = 'stats';
const UNREAD_EVENT = 'unread';
import store from 'stores/redux_store.jsx';
import * as Selectors from 'mattermost-redux/selectors/entities/teams';
import {TeamTypes} from 'mattermost-redux/action_types';
var Utils;
class TeamStoreClass extends EventEmitter {
constructor() {
super();
this.clear();
store.subscribe(() => {
const newEntities = store.getState().entities.teams;
if (newEntities.teams !== this.entities.teams) {
this.emitChange();
}
if (newEntities.myMembers !== this.entities.myMembers) {
this.emitChange();
}
this.entities = newEntities;
});
}
clear() {
this.teams = {};
this.my_team_members = [];
this.entities = {};
this.members_in_team = {};
this.members_not_in_team = {};
this.stats = {};
@@ -91,7 +107,7 @@ class TeamStoreClass extends EventEmitter {
}
getAll() {
return this.teams;
return store.getState().entities.teams.teams;
}
getCurrentId() {
@@ -100,10 +116,14 @@ class TeamStoreClass extends EventEmitter {
setCurrentId(id) {
this.currentTeamId = id;
store.dispatch({
type: TeamTypes.SELECT_TEAM,
data: id
});
}
getCurrent() {
const team = this.teams[this.currentTeamId];
const team = this.getAll()[this.currentTeamId];
if (team) {
return team;
@@ -165,17 +185,21 @@ class TeamStoreClass extends EventEmitter {
}
saveTeam(team) {
this.teams[team.id] = team;
this.saveTeams([team]);
}
saveTeams(teams) {
this.teams = teams;
store.dispatch({
type: TeamTypes.RECEIVED_TEAMS_LIST,
data: teams
});
}
updateTeam(team) {
const t = JSON.parse(team);
if (this.teams && this.teams[t.id]) {
this.teams[t.id] = t;
const teams = this.getAll();
if (teams && teams[t.id]) {
this.saveTeam(t);
}
if (this.teamListings && this.teamListings[t.id]) {
@@ -193,7 +217,7 @@ class TeamStoreClass extends EventEmitter {
saveMyTeam(team) {
this.saveTeam(team);
this.currentTeamId = team.id;
this.setCurrentId(team.id);
}
saveStats(teamId, stats) {
@@ -201,20 +225,26 @@ class TeamStoreClass extends EventEmitter {
}
saveMyTeamMembers(members) {
this.my_team_members = members;
store.dispatch({
type: TeamTypes.RECEIVED_MY_TEAM_MEMBERS,
data: members
});
}
appendMyTeamMember(member) {
this.my_team_members.push(member);
const members = this.getMyTeamMembers();
members.push(member);
this.saveMyTeamMembers(members);
}
saveMyTeamMembersUnread(members) {
for (let i = 0; i < this.my_team_members.length; i++) {
const team = this.my_team_members[i];
const myMembers = this.getMyTeamMembers();
for (let i = 0; i < myMembers.length; i++) {
const team = myMembers[i];
const member = members.filter((m) => m.team_id === team.team_id)[0];
if (member) {
this.my_team_members[i] = Object.assign({},
myMembers[i] = Object.assign({},
team,
{
msg_count: member.msg_count,
@@ -222,19 +252,23 @@ class TeamStoreClass extends EventEmitter {
});
}
}
this.saveMyTeamMembers(myMembers);
}
removeMyTeamMember(teamId) {
for (let i = 0; i < this.my_team_members.length; i++) {
if (this.my_team_members[i].team_id === teamId) {
this.my_team_members.splice(i, 1);
const myMembers = this.getMyTeamMembers();
for (let i = 0; i < myMembers.length; i++) {
if (myMembers[i].team_id === teamId) {
myMembers.splice(i, 1);
}
}
this.emitChange();
this.saveMyTeamMembers(myMembers);
}
getMyTeamMembers() {
return this.my_team_members;
return Object.values(Selectors.getTeamMemberships(store.getState()));
}
saveMembersInTeam(teamId = this.getCurrentId(), members) {
@@ -320,19 +354,21 @@ class TeamStoreClass extends EventEmitter {
}
updateUnreadCount(teamId, totalMsgCount, channelMember) {
const member = this.my_team_members.filter((m) => m.team_id === teamId)[0];
let member = this.getMyTeamMembers().filter((m) => m.team_id === teamId)[0];
if (member) {
member = Object.assign({}, member);
member.msg_count -= (totalMsgCount - channelMember.msg_count);
member.mention_count -= channelMember.mention_count;
}
}
subtractUnread(teamId, msgs, mentions) {
const member = this.my_team_members.filter((m) => m.team_id === teamId)[0];
let member = this.getMyTeamMembers().filter((m) => m.team_id === teamId)[0];
if (member) {
const msgCount = member.msg_count - msgs;
const mentionCount = member.mention_count - mentions;
member = Object.assign({}, member);
member.msg_count = (msgCount > 0) ? msgCount : 0;
member.mention_count = (mentionCount > 0) ? mentionCount : 0;
}
@@ -344,7 +380,7 @@ class TeamStoreClass extends EventEmitter {
return;
}
const member = this.my_team_members.filter((m) => m.team_id === id)[0];
const member = Object.assign({}, this.getMyTeamMembers().filter((m) => m.team_id === id)[0]);
member.msg_count++;
}
@@ -355,7 +391,7 @@ class TeamStoreClass extends EventEmitter {
}
if (mentions.indexOf(UserStore.getCurrentId()) !== -1) {
const member = this.my_team_members.filter((m) => m.team_id === id)[0];
const member = Object.assign({}, this.getMyTeamMembers().filter((m) => m.team_id === id)[0]);
member.mention_count++;
}
}

Просмотреть файл

@@ -1,16 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import AppDispatcher from '../dispatcher/app_dispatcher.jsx';
import EventEmitter from 'events';
import * as GlobalActions from 'actions/global_actions.jsx';
import LocalizationStore from './localization_store.jsx';
import ChannelStore from 'stores/channel_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import Constants from 'utils/constants.jsx';
const ActionTypes = Constants.ActionTypes;
const UserStatuses = Constants.UserStatuses;
const CHANGE_EVENT_NOT_IN_CHANNEL = 'change_not_in_channel';
@@ -23,48 +19,52 @@ const CHANGE_EVENT_SESSIONS = 'change_sessions';
const CHANGE_EVENT_AUDITS = 'change_audits';
const CHANGE_EVENT_STATUSES = 'change_statuses';
import store from 'stores/redux_store.jsx';
import * as Selectors from 'mattermost-redux/selectors/entities/users';
import {UserTypes} from 'mattermost-redux/action_types';
var Utils;
class UserStoreClass extends EventEmitter {
constructor() {
super();
this.clear();
}
clear() {
// All the profiles, regardless of where they came from
this.profiles = {};
this.paging_offset = 0;
this.paging_count = 0;
// Lists of sorted IDs for users in a team
this.profiles_not_in_team = {};
this.not_in_team_offset = 0;
this.not_in_team_count = 0;
// Lists of sorted IDs for users in a team
this.profiles_in_team = {};
this.in_team_offset = 0;
this.in_team_count = 0;
// Lists of sorted IDs for users in a channel
this.profiles_in_channel = {};
this.in_channel_offset = {};
this.in_channel_count = {};
// Lists of sorted IDs for users not in a channel
this.profiles_not_in_channel = {};
this.not_in_channel_offset = {};
this.not_in_channel_count = {};
// Lists of sorted IDs for users without a team
this.profiles_without_team = {};
this.statuses = {};
this.sessions = {};
this.audits = [];
this.currentUserId = '';
this.noAccounts = false;
this.entities = {};
store.subscribe(() => {
const newEntities = store.getState().entities.users;
if (newEntities.profiles !== this.entities.profiles) {
this.emitChange();
}
if (newEntities.profilesInChannel !== this.entities.profilesInChannel) {
this.emitInChannelChange();
}
if (newEntities.profilesNotInChannel !== this.entities.profilesNotInChannel) {
this.emitNotInChannelChange();
}
if (newEntities.profilesInTeam !== this.entities.profilesInTeam) {
this.emitInTeamChange();
}
if (newEntities.profilesNotInTeam !== this.entities.profilesNotInTeam) {
this.emitNotInTeamChange();
}
if (newEntities.profilesWithoutTeam !== this.entities.profilesWithoutTeam) {
this.emitWithoutTeamChange();
}
if (newEntities.statuses !== this.entities.statuses) {
this.emitStatusesChange();
}
if (newEntities.myAudits !== this.entities.myAudits) {
this.emitAuditsChange();
}
if (newEntities.mySessions !== this.entities.mySessions) {
this.emitSessionsChange();
}
this.entities = newEntities;
});
}
emitChange(userId) {
@@ -178,49 +178,21 @@ class UserStoreClass extends EventEmitter {
// General
getCurrentUser() {
return this.getProfiles()[this.currentUserId];
}
setCurrentUser(user) {
this.saveProfile(user);
this.currentUserId = user.id;
global.window.mm_current_user_id = this.currentUserId;
if (LocalizationStore.getLocale() !== user.locale) {
setTimeout(() => GlobalActions.newLocalizationSelected(user.locale), 0);
}
return Selectors.getCurrentUser(store.getState());
}
getCurrentId() {
var user = this.getCurrentUser();
if (user) {
return user.id;
}
return null;
return Selectors.getCurrentUserId(store.getState());
}
// System-Wide Profiles
saveProfiles(profiles) {
const newProfiles = Object.assign({}, profiles);
const currentId = this.getCurrentId();
if (newProfiles[currentId]) {
Reflect.deleteProperty(newProfiles, currentId);
}
this.profiles = Object.assign({}, this.profiles, newProfiles);
}
getProfiles() {
return this.profiles;
return Selectors.getUsers(store.getState());
}
getProfile(userId) {
if (this.profiles[userId]) {
return Object.assign({}, this.profiles[userId]);
}
return null;
return Selectors.getUser(store.getState(), userId);
}
getProfileListForIds(userIds, skipCurrent = false, skipInactive = false) {
@@ -257,17 +229,7 @@ class UserStoreClass extends EventEmitter {
}
getProfilesUsernameMap() {
var profileUsernameMap = {};
var profiles = this.getProfiles();
for (var key in profiles) {
if (profiles.hasOwnProperty(key)) {
var profile = profiles[key];
profileUsernameMap[profile.username] = profile;
}
}
return profileUsernameMap;
return Selectors.getUsersByUsername(store.getState());
}
getActiveOnlyProfiles(skipCurrent) {
@@ -310,10 +272,11 @@ class UserStoreClass extends EventEmitter {
getProfileList(skipCurrent = false, allowInactive = false) {
const profiles = [];
const currentId = this.getCurrentId();
const profileMap = this.getProfiles();
for (const id in this.profiles) {
if (this.profiles.hasOwnProperty(id)) {
var profile = this.profiles[id];
for (const id in profileMap) {
if (profileMap.hasOwnProperty(id)) {
var profile = profileMap[id];
if (skipCurrent && id === currentId) {
continue;
@@ -339,103 +302,32 @@ class UserStoreClass extends EventEmitter {
}
saveProfile(profile) {
this.profiles[profile.id] = profile;
store.dispatch({
type: UserTypes.RECEIVED_PROFILE,
data: profile
});
}
// Team-Wide Profiles
saveProfilesInTeam(teamId, profiles) {
const oldProfileList = this.profiles_in_team[teamId] || [];
const oldProfileMap = {};
for (let i = 0; i < oldProfileList.length; i++) {
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
}
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
const newProfileList = Object.keys(newProfileMap);
newProfileList.sort((a, b) => {
const aProfile = newProfileMap[a];
const bProfile = newProfileMap[b];
if (aProfile.username < bProfile.username) {
return -1;
}
if (aProfile.username > bProfile.username) {
return 1;
}
return 0;
});
this.profiles_in_team[teamId] = newProfileList;
this.saveProfiles(profiles);
}
getProfileListInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
const userIds = this.profiles_in_team[teamId] || [];
const userIds = Array.from(Selectors.getUserIdsInTeams(store.getState())[teamId] || []);
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
}
removeProfileFromTeam(teamId, userId) {
const userIds = this.profiles_in_team[teamId];
if (!userIds) {
return;
}
const index = userIds.indexOf(userId);
if (index === -1) {
return;
}
userIds.splice(index, 1);
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_NOT_IN_TEAM,
data: {user_id: userId},
id: teamId
});
}
// Not In Team Profiles
saveProfilesNotInTeam(teamId, profiles) {
const oldProfileList = this.profiles_not_in_team[teamId] || [];
const oldProfileMap = {};
for (let i = 0; i < oldProfileList.length; i++) {
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
}
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
const newProfileList = Object.keys(newProfileMap);
newProfileList.sort((a, b) => {
const aProfile = newProfileMap[a];
const bProfile = newProfileMap[b];
if (aProfile.username < bProfile.username) {
return -1;
}
if (aProfile.username > bProfile.username) {
return 1;
}
return 0;
});
this.profiles_not_in_team[teamId] = newProfileList;
this.saveProfiles(profiles);
}
removeProfileNotInTeam(teamId, userId) {
const userIds = this.profiles_not_in_team[teamId];
if (!userIds) {
return;
}
const index = userIds.indexOf(userId);
if (index === -1) {
return;
}
userIds.splice(index, 1);
}
getProfileListNotInTeam(teamId = TeamStore.getCurrentId(), skipCurrent = false, skipInactive = false) {
const userIds = this.profiles_not_in_team[teamId] || [];
const userIds = Array.from(Selectors.getUserIdsNotInTeams(store.getState())[teamId] || []);
const profiles = [];
const currentId = this.getCurrentId();
@@ -460,178 +352,84 @@ class UserStoreClass extends EventEmitter {
return profiles;
}
// Channel-Wide Profiles
saveProfilesInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
const oldProfileList = this.profiles_in_channel[channelId] || [];
const oldProfileMap = {};
for (let i = 0; i < oldProfileList.length; i++) {
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
}
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
const newProfileList = Object.keys(newProfileMap);
newProfileList.sort((a, b) => {
const aProfile = newProfileMap[a];
const bProfile = newProfileMap[b];
if (aProfile.username < bProfile.username) {
return -1;
}
if (aProfile.username > bProfile.username) {
return 1;
}
return 0;
removeProfileNotInTeam(teamId, userId) {
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_IN_TEAM,
data: {user_id: userId},
id: teamId
});
this.profiles_in_channel[channelId] = newProfileList;
this.saveProfiles(profiles);
}
// Channel-Wide Profiles
saveProfileInChannel(channelId = ChannelStore.getCurrentId(), profile) {
const profileMap = {};
profileMap[profile.id] = profile;
this.saveProfilesInChannel(channelId, profileMap);
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
data: {user_id: profile.id},
id: channelId
});
}
saveUserIdInChannel(channelId = ChannelStore.getCurrentId(), userId) {
const profile = this.getProfile(userId);
// Must have profile or we can't sort the list
if (!profile) {
return false;
}
this.saveProfileInChannel(channelId, profile);
return true;
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
data: {user_id: userId},
id: channelId
});
}
removeProfileInChannel(channelId, userId) {
const userIds = this.profiles_in_channel[channelId];
if (!userIds) {
return;
}
const index = userIds.indexOf(userId);
if (index === -1) {
return;
}
userIds.splice(index, 1);
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
data: {user_id: userId},
id: channelId
});
}
getProfileListInChannel(channelId = ChannelStore.getCurrentId(), skipCurrent = false) {
const userIds = this.profiles_in_channel[channelId] || [];
const userIds = Array.from(Selectors.getUserIdsInChannels(store.getState())[channelId] || []);
return this.getProfileListForIds(userIds, skipCurrent, false);
}
saveProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), profiles) {
const oldProfileList = this.profiles_not_in_channel[channelId] || [];
const oldProfileMap = {};
for (let i = 0; i < oldProfileList.length; i++) {
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
}
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
const newProfileList = Object.keys(newProfileMap);
newProfileList.sort((a, b) => {
const aProfile = newProfileMap[a];
const bProfile = newProfileMap[b];
if (aProfile.username < bProfile.username) {
return -1;
}
if (aProfile.username > bProfile.username) {
return 1;
}
return 0;
});
this.profiles_not_in_channel[channelId] = newProfileList;
this.saveProfiles(profiles);
}
saveProfileNotInChannel(channelId = ChannelStore.getCurrentId(), profile) {
const profileMap = {};
profileMap[profile.id] = profile;
this.saveProfilesNotInChannel(channelId, profileMap);
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_NOT_IN_CHANNEL,
data: {user_id: profile.id},
id: channelId
});
}
removeProfileNotInChannel(channelId, userId) {
const userIds = this.profiles_not_in_channel[channelId];
if (!userIds) {
return;
}
const index = userIds.indexOf(userId);
if (index === -1) {
return;
}
userIds.splice(index, 1);
store.dispatch({
type: UserTypes.RECEIVED_PROFILE_IN_CHANNEL,
data: {user_id: userId},
id: channelId
});
}
getProfileListNotInChannel(channelId = ChannelStore.getCurrentId(), skipInactive = false) {
const userIds = this.profiles_not_in_channel[channelId] || [];
const userIds = Array.from(Selectors.getUserIdsNotInChannels(store.getState())[channelId] || []);
return this.getProfileListForIds(userIds, false, skipInactive);
}
// Profiles without any teams
saveProfilesWithoutTeam(profiles) {
const oldProfileList = this.profiles_without_team;
const oldProfileMap = {};
for (let i = 0; i < oldProfileList.length; i++) {
oldProfileMap[oldProfileList[i]] = this.getProfile(oldProfileList[i]);
}
const newProfileMap = Object.assign({}, oldProfileMap, profiles);
const newProfileList = Object.keys(newProfileMap);
newProfileList.sort((a, b) => {
const aProfile = newProfileMap[a];
const bProfile = newProfileMap[b];
if (aProfile.username < bProfile.username) {
return -1;
}
if (aProfile.username > bProfile.username) {
return 1;
}
return 0;
});
this.profiles_without_team = newProfileList;
this.saveProfiles(profiles);
}
getProfileListWithoutTeam(skipCurrent = false, skipInactive = false) {
const userIds = this.profiles_without_team || [];
const userIds = Array.from(Selectors.getUserIdsWithoutTeam(store.getState()) || []);
return this.getProfileListForIds(userIds, skipCurrent, skipInactive);
}
// Other
setSessions(sessions) {
this.sessions = sessions;
}
getSessions() {
return this.sessions;
}
setAudits(audits) {
this.audits = audits;
return store.getState().entities.users.mySessions;
}
getAudits() {
return this.audits;
return store.getState().entities.users.myAudits;
}
getCurrentMentionKeys() {
@@ -668,17 +466,16 @@ class UserStoreClass extends EventEmitter {
return keys;
}
setStatuses(statuses) {
this.statuses = Object.assign(this.statuses, statuses);
}
setStatus(userId, status) {
this.statuses[userId] = status;
this.emitStatusesChange();
const data = [{user_id: userId, status}];
store.dispatch({
type: UserTypes.RECEIVED_STATUSES,
data
});
}
getStatuses() {
return this.statuses;
return store.getState().entities.users.statuses;
}
getStatus(id) {
@@ -686,7 +483,7 @@ class UserStoreClass extends EventEmitter {
}
getNoAccounts() {
return this.noAccounts;
return global.window.mm_config.NoAccounts === 'true';
}
setNoAccounts(noAccounts) {
@@ -706,141 +503,9 @@ class UserStoreClass extends EventEmitter {
return false;
}
setPage(offset, count) {
this.paging_offset = offset + count;
this.paging_count = this.paging_count + count;
}
getPagingOffset() {
return this.paging_offset;
}
getPagingCount() {
return this.paging_count;
}
setInTeamPage(offset, count) {
this.in_team_offset = offset + count;
this.in_team_count = this.in_team_count + count;
}
getInTeamPagingOffset() {
return this.in_team_offset;
}
getInTeamPagingCount() {
return this.in_team_count;
}
setNotInTeamPage(offset, count) {
this.not_in_team_offset = offset + count;
this.not_in_team_count = this.not_in_team_count + count;
}
getNotInTeamPagingOffset() {
return this.not_in_team_offset;
}
getNotInTeamPagingCount() {
return this.not_in_team_count;
}
setInChannelPage(channelId, offset, count) {
this.in_channel_offset[channelId] = offset + count;
this.in_channel_count[channelId] = this.dm_paging_count + count;
}
getInChannelPagingOffset(channelId) {
return this.in_channel_offset[channelId] | 0;
}
getInChannelPagingCount(channelId) {
return this.in_channel_count[channelId] | 0;
}
setNotInChannelPage(channelId, offset, count) {
this.not_in_channel_offset[channelId] = offset + count;
this.not_in_channel_count[channelId] = this.dm_paging_count + count;
}
getNotInChannelPagingOffset(channelId) {
return this.not_in_channel_offset[channelId] | 0;
}
getNotInChannelPagingCount(channelId) {
return this.not_in_channel_count[channelId] | 0;
}
}
var UserStore = new UserStoreClass();
UserStore.setMaxListeners(600);
UserStore.dispatchToken = AppDispatcher.register((payload) => {
var action = payload.action;
switch (action.type) {
case ActionTypes.RECEIVED_PROFILES:
UserStore.saveProfiles(action.profiles);
if (action.offset != null && action.count != null) {
UserStore.setPage(action.offset, action.count);
}
UserStore.emitChange();
break;
case ActionTypes.RECEIVED_PROFILES_IN_TEAM:
UserStore.saveProfilesInTeam(action.team_id, action.profiles);
if (action.offset != null && action.count != null) {
UserStore.setInTeamPage(action.offset, action.count);
}
UserStore.emitInTeamChange();
break;
case ActionTypes.RECEIVED_PROFILES_NOT_IN_TEAM:
UserStore.saveProfilesNotInTeam(action.team_id, action.profiles);
if (action.offset != null && action.count != null) {
UserStore.setNotInTeamPage(action.offset, action.count);
}
UserStore.emitNotInTeamChange();
break;
case ActionTypes.RECEIVED_PROFILES_IN_CHANNEL:
UserStore.saveProfilesInChannel(action.channel_id, action.profiles);
if (action.offset != null && action.count != null) {
UserStore.setInChannelPage(action.offset, action.count);
}
UserStore.emitInChannelChange();
break;
case ActionTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL:
UserStore.saveProfilesNotInChannel(action.channel_id, action.profiles);
if (action.offset != null && action.count != null) {
UserStore.setNotInChannelPage(action.offset, action.count);
}
UserStore.emitNotInChannelChange();
break;
case ActionTypes.RECEIVED_PROFILES_WITHOUT_TEAM:
UserStore.saveProfilesWithoutTeam(action.profiles);
UserStore.emitWithoutTeamChange();
break;
case ActionTypes.RECEIVED_PROFILE:
UserStore.saveProfile(action.profile);
UserStore.emitChange();
break;
case ActionTypes.RECEIVED_ME:
UserStore.setCurrentUser(action.me);
UserStore.emitChange(action.me.id);
break;
case ActionTypes.RECEIVED_SESSIONS:
UserStore.setSessions(action.sessions);
UserStore.emitSessionsChange();
break;
case ActionTypes.RECEIVED_AUDITS:
UserStore.setAudits(action.audits);
UserStore.emitAuditsChange();
break;
case ActionTypes.RECEIVED_STATUSES:
UserStore.setStatuses(action.statuses);
UserStore.emitStatusesChange();
break;
default:
}
});
export {UserStore as default};

Просмотреть файл

@@ -8,7 +8,6 @@ import TeamStore from 'stores/team_store.jsx';
import ErrorStore from 'stores/error_store.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {loadStatusesForProfilesMap} from 'actions/status_actions.jsx';
import AppDispatcher from 'dispatcher/app_dispatcher.jsx';
import Client from 'client/web_client.jsx';
@@ -323,231 +322,6 @@ export function getUser(userId, success, error) {
);
}
export function getProfiles(offset = UserStore.getPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
const callName = `getProfiles${offset}${limit}`;
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfiles(
offset,
limit,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfiles');
}
);
}
export function getProfilesInTeam(teamId = TeamStore.getCurrentId(), offset = UserStore.getInTeamPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
const callName = `getProfilesInTeam${teamId}${offset}${limit}`;
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfilesInTeam(
teamId,
offset,
limit,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_TEAM,
profiles: data,
team_id: teamId,
offset,
count: Object.keys(data).length
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfilesInTeam');
}
);
}
export function getProfilesNotInTeam(teamId = TeamStore.getCurrentId(), offset = UserStore.getInTeamPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
const callName = `getProfilesNotInTeam${teamId}${offset}${limit}`;
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfilesNotInTeam(
teamId,
offset,
limit,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_NOT_IN_TEAM,
profiles: data,
team_id: teamId,
offset,
count: Object.keys(data).length
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfilesNotInTeam');
}
);
}
export function getProfilesInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
const callName = `getProfilesInChannel${channelId}${offset}${limit}`;
if (isCallInProgress()) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfilesInChannel(
channelId,
offset,
limit,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_IN_CHANNEL,
channel_id: channelId,
profiles: data,
offset,
count: Object.keys(data).length
});
loadStatusesForProfilesMap(data);
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfilesInChannel');
}
);
}
export function getProfilesNotInChannel(channelId = ChannelStore.getCurrentId(), offset = UserStore.getNotInChannelPagingOffset(), limit = Constants.PROFILE_CHUNK_SIZE) {
const callName = `getProfilesNotInChannel${channelId}${offset}${limit}`;
if (isCallInProgress(callName)) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfilesNotInChannel(
channelId,
offset,
limit,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES_NOT_IN_CHANNEL,
channel_id: channelId,
profiles: data,
offset,
count: Object.keys(data).length
});
loadStatusesForProfilesMap(data);
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfilesNotInChannel');
}
);
}
export function getProfilesByIds(userIds) {
const callName = 'getProfilesByIds' + JSON.stringify(userIds);
if (isCallInProgress(callName)) {
return;
}
if (!userIds || userIds.length === 0) {
return;
}
callTracker[callName] = utils.getTimestamp();
Client.getProfilesByIds(
userIds,
(data) => {
callTracker[callName] = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_PROFILES,
profiles: data
});
},
(err) => {
callTracker[callName] = 0;
dispatchError(err, 'getProfilesByIds');
}
);
}
export function getSessions() {
if (isCallInProgress('getSessions')) {
return;
}
callTracker.getSessions = utils.getTimestamp();
Client.getSessions(
UserStore.getCurrentId(),
(data) => {
callTracker.getSessions = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_SESSIONS,
sessions: data
});
},
(err) => {
callTracker.getSessions = 0;
dispatchError(err, 'getSessions');
}
);
}
export function getAudits() {
if (isCallInProgress('getAudits')) {
return;
}
callTracker.getAudits = utils.getTimestamp();
Client.getAudits(
UserStore.getCurrentId(),
(data) => {
callTracker.getAudits = 0;
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_AUDITS,
audits: data
});
},
(err) => {
callTracker.getAudits = 0;
dispatchError(err, 'getAudits');
}
);
}
export function getLogs() {
if (isCallInProgress('getLogs')) {
return;

Просмотреть файл

@@ -2,7 +2,7 @@
// See License.txt for license information.
import * as Utils from './utils.jsx';
import ChannelInviteModal from 'components/channel_invite_modal.jsx';
import ChannelInviteModal from 'components/channel_invite_modal';
import EditChannelHeaderModal from 'components/edit_channel_header_modal.jsx';
import ToggleModalButton from 'components/toggle_modal_button.jsx';
import UserProfile from 'components/user_profile.jsx';

Просмотреть файл

@@ -23,7 +23,7 @@ if (NPM_TARGET === 'test') {
}
var config = {
entry: ['babel-polyfill', './root.jsx', 'root.html'],
entry: ['babel-polyfill', 'whatwg-fetch', './root.jsx', 'root.html'],
output: {
path: 'dist',
publicPath: '/static/',
@@ -33,7 +33,7 @@ var config = {
module: {
loaders: [
{
test: /\.jsx?$/,
test: /\.(js|jsx)?$/,
loader: 'babel-loader',
exclude: /(node_modules|non_npm_dependencies)/,
query: {