mattermost-redux 11.5.0 → 11.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/action_types/content_flagging.d.ts +3 -0
  2. package/lib/action_types/content_flagging.js +3 -0
  3. package/lib/action_types/files.d.ts +1 -0
  4. package/lib/action_types/files.js +1 -0
  5. package/lib/actions/content_flagging.d.ts +14 -0
  6. package/lib/actions/content_flagging.js +119 -0
  7. package/lib/actions/limits.js +2 -1
  8. package/lib/actions/posts.js +14 -0
  9. package/lib/actions/search.js +14 -2
  10. package/lib/actions/shared_channels.d.ts +1 -1
  11. package/lib/actions/shared_channels.js +2 -2
  12. package/lib/actions/users.d.ts +3 -1
  13. package/lib/actions/users.js +19 -0
  14. package/lib/constants/general.d.ts +2 -0
  15. package/lib/constants/general.js +2 -0
  16. package/lib/constants/stats.d.ts +1 -0
  17. package/lib/constants/stats.js +1 -0
  18. package/lib/reducers/entities/admin.js +3 -0
  19. package/lib/reducers/entities/content_flagging.d.ts +18 -0
  20. package/lib/reducers/entities/content_flagging.js +51 -0
  21. package/lib/reducers/entities/files.d.ts +3 -0
  22. package/lib/reducers/entities/files.js +19 -0
  23. package/lib/reducers/entities/index.d.ts +30 -0
  24. package/lib/reducers/index.d.ts +30 -0
  25. package/lib/selectors/entities/access_control.d.ts +0 -1
  26. package/lib/selectors/entities/access_control.js +0 -6
  27. package/lib/selectors/entities/agents.d.ts +1 -0
  28. package/lib/selectors/entities/agents.js +6 -1
  29. package/lib/selectors/entities/content_flagging.d.ts +4 -0
  30. package/lib/selectors/entities/content_flagging.js +28 -1
  31. package/lib/selectors/entities/files.d.ts +2 -0
  32. package/lib/selectors/entities/files.js +9 -0
  33. package/lib/store/initial_state.js +1 -0
  34. package/lib/utils/data_loader.d.ts +4 -0
  35. package/lib/utils/data_loader.js +22 -4
  36. package/lib/utils/emoji_utils.d.ts +2 -2
  37. package/lib/utils/emoji_utils.js +6 -10
  38. package/lib/utils/integration_utils.js +58 -2
  39. package/lib/utils/user_utils.js +2 -0
  40. package/package.json +3 -3
@@ -3,5 +3,8 @@ declare const _default: {
3
3
  RECEIVED_POST_CONTENT_FLAGGING_FIELDS: "RECEIVED_POST_CONTENT_FLAGGING_FIELDS";
4
4
  RECEIVED_POST_CONTENT_FLAGGING_VALUES: "RECEIVED_POST_CONTENT_FLAGGING_VALUES";
5
5
  CONTENT_FLAGGING_REPORT_VALUE_UPDATED: "CONTENT_FLAGGING_REPORT_VALUE_UPDATED";
6
+ RECEIVED_FLAGGED_POST: "RECEIVED_FLAGGED_POST";
7
+ RECEIVED_CONTENT_FLAGGING_CHANNEL: "RECEIVED_CONTENT_FLAGGING_CHANNEL";
8
+ RECEIVED_CONTENT_FLAGGING_TEAM: "RECEIVED_CONTENT_FLAGGING_TEAM";
6
9
  };
7
10
  export default _default;
@@ -11,4 +11,7 @@ exports.default = (0, key_mirror_1.default)({
11
11
  RECEIVED_POST_CONTENT_FLAGGING_FIELDS: null,
12
12
  RECEIVED_POST_CONTENT_FLAGGING_VALUES: null,
13
13
  CONTENT_FLAGGING_REPORT_VALUE_UPDATED: null,
14
+ RECEIVED_FLAGGED_POST: null,
15
+ RECEIVED_CONTENT_FLAGGING_CHANNEL: null,
16
+ RECEIVED_CONTENT_FLAGGING_TEAM: null,
14
17
  });
@@ -8,5 +8,6 @@ declare const _default: {
8
8
  RECEIVED_UPLOAD_FILES: "RECEIVED_UPLOAD_FILES";
9
9
  RECEIVED_FILE_PUBLIC_LINK: "RECEIVED_FILE_PUBLIC_LINK";
10
10
  REMOVED_FILE: "REMOVED_FILE";
11
+ FILE_DOWNLOAD_REJECTED: "FILE_DOWNLOAD_REJECTED";
11
12
  };
12
13
  export default _default;
@@ -16,4 +16,5 @@ exports.default = (0, key_mirror_1.default)({
16
16
  RECEIVED_UPLOAD_FILES: null,
17
17
  RECEIVED_FILE_PUBLIC_LINK: null,
18
18
  REMOVED_FILE: null,
19
+ FILE_DOWNLOAD_REJECTED: null,
19
20
  });
@@ -1,10 +1,24 @@
1
+ import type { Channel } from '@mattermost/types/channels';
1
2
  import type { ContentFlaggingConfig } from '@mattermost/types/content_flagging';
3
+ import type { Post } from '@mattermost/types/posts';
2
4
  import type { NameMappedPropertyFields, PropertyValue } from '@mattermost/types/properties';
5
+ import type { Team } from '@mattermost/types/teams';
3
6
  import type { ActionFuncAsync } from 'mattermost-redux/types/actions';
7
+ export type ContentFlaggingChannelRequestIdentifier = {
8
+ channelId?: string;
9
+ flaggedPostId?: string;
10
+ };
11
+ export type ContentFlaggingTeamRequestIdentifier = {
12
+ teamId?: string;
13
+ flaggedPostId?: string;
14
+ };
4
15
  export declare function getTeamContentFlaggingStatus(teamId: string): ActionFuncAsync<{
5
16
  enabled: boolean;
6
17
  }>;
7
18
  export declare function getContentFlaggingConfig(teamId?: string): ActionFuncAsync<ContentFlaggingConfig>;
8
19
  export declare function getPostContentFlaggingFields(): ActionFuncAsync<NameMappedPropertyFields>;
9
20
  export declare function loadPostContentFlaggingFields(): ActionFuncAsync<NameMappedPropertyFields>;
21
+ export declare function loadFlaggedPost(flaggedPostId: string): ActionFuncAsync<Post>;
22
+ export declare function loadContentFlaggingChannel(identifier: ContentFlaggingChannelRequestIdentifier): ActionFuncAsync<Channel>;
23
+ export declare function loadContentFlaggingTeam(identifier: ContentFlaggingTeamRequestIdentifier): ActionFuncAsync<Team>;
10
24
  export declare function getPostContentFlaggingValues(postId: string): ActionFuncAsync<Array<PropertyValue<unknown>>>;
@@ -6,12 +6,21 @@ exports.getTeamContentFlaggingStatus = getTeamContentFlaggingStatus;
6
6
  exports.getContentFlaggingConfig = getContentFlaggingConfig;
7
7
  exports.getPostContentFlaggingFields = getPostContentFlaggingFields;
8
8
  exports.loadPostContentFlaggingFields = loadPostContentFlaggingFields;
9
+ exports.loadFlaggedPost = loadFlaggedPost;
10
+ exports.loadContentFlaggingChannel = loadContentFlaggingChannel;
11
+ exports.loadContentFlaggingTeam = loadContentFlaggingTeam;
9
12
  exports.getPostContentFlaggingValues = getPostContentFlaggingValues;
10
13
  const action_types_1 = require("mattermost-redux/action_types");
11
14
  const errors_1 = require("mattermost-redux/actions/errors");
12
15
  const helpers_1 = require("mattermost-redux/actions/helpers");
13
16
  const client_1 = require("mattermost-redux/client");
14
17
  const data_loader_1 = require("mattermost-redux/utils/data_loader");
18
+ function channelComparator(a, b) {
19
+ return a.channelId === b.channelId;
20
+ }
21
+ function teamComparator(a, b) {
22
+ return a.teamId === b.teamId;
23
+ }
15
24
  function getTeamContentFlaggingStatus(teamId) {
16
25
  return async (dispatch, getState) => {
17
26
  let response;
@@ -84,6 +93,116 @@ function loadPostContentFlaggingFields() {
84
93
  return {};
85
94
  };
86
95
  }
96
+ function getFlaggedPost(flaggedPostId) {
97
+ return async (dispatch, getState) => {
98
+ let data;
99
+ try {
100
+ data = await client_1.Client4.getFlaggedPost(flaggedPostId);
101
+ }
102
+ catch (error) {
103
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
104
+ dispatch((0, errors_1.logError)(error));
105
+ return { error };
106
+ }
107
+ dispatch({
108
+ type: action_types_1.ContentFlaggingTypes.RECEIVED_FLAGGED_POST,
109
+ data,
110
+ });
111
+ return { data };
112
+ };
113
+ }
114
+ function loadFlaggedPost(flaggedPostId) {
115
+ return async (dispatch, getState, { loaders }) => {
116
+ if (!loaders.flaggedPostLoader) {
117
+ loaders.flaggedPostLoader = new data_loader_1.DelayedDataLoader({
118
+ fetchBatch: ([postId]) => dispatch(getFlaggedPost(postId)),
119
+ maxBatchSize: 1,
120
+ wait: 200,
121
+ });
122
+ }
123
+ const loader = loaders.flaggedPostLoader;
124
+ loader.queue([flaggedPostId]);
125
+ return {};
126
+ };
127
+ }
128
+ function getContentFlaggingChannel(channelId, flaggedPostId) {
129
+ return async (dispatch, getState) => {
130
+ let data;
131
+ try {
132
+ data = await client_1.Client4.getChannel(channelId, true, flaggedPostId);
133
+ }
134
+ catch (error) {
135
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
136
+ dispatch((0, errors_1.logError)(error));
137
+ return { error };
138
+ }
139
+ dispatch({
140
+ type: action_types_1.ContentFlaggingTypes.RECEIVED_CONTENT_FLAGGING_CHANNEL,
141
+ data,
142
+ });
143
+ return { data };
144
+ };
145
+ }
146
+ function loadContentFlaggingChannel(identifier) {
147
+ return async (dispatch, getState, { loaders }) => {
148
+ if (!loaders.contentFlaggingChannelLoader) {
149
+ loaders.contentFlaggingChannelLoader =
150
+ new data_loader_1.DelayedDataLoader({
151
+ fetchBatch: ([{ flaggedPostId, channelId }]) => {
152
+ if (channelId && flaggedPostId) {
153
+ return dispatch(getContentFlaggingChannel(channelId, flaggedPostId));
154
+ }
155
+ return Promise.resolve(null);
156
+ },
157
+ maxBatchSize: 1,
158
+ wait: 200,
159
+ comparator: channelComparator,
160
+ });
161
+ }
162
+ const loader = loaders.contentFlaggingChannelLoader;
163
+ loader.queue([identifier]);
164
+ return {};
165
+ };
166
+ }
167
+ function getContentFlaggingTeam(teamId, flaggedPostId) {
168
+ return async (dispatch, getState) => {
169
+ let data;
170
+ try {
171
+ data = await client_1.Client4.getTeam(teamId, true, flaggedPostId);
172
+ }
173
+ catch (error) {
174
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
175
+ dispatch((0, errors_1.logError)(error));
176
+ return { error };
177
+ }
178
+ dispatch({
179
+ type: action_types_1.ContentFlaggingTypes.RECEIVED_CONTENT_FLAGGING_TEAM,
180
+ data,
181
+ });
182
+ return { data };
183
+ };
184
+ }
185
+ function loadContentFlaggingTeam(identifier) {
186
+ return async (dispatch, getState, { loaders }) => {
187
+ if (!loaders.contentFlaggingTeamLoader) {
188
+ loaders.contentFlaggingTeamLoader =
189
+ new data_loader_1.DelayedDataLoader({
190
+ fetchBatch: ([{ flaggedPostId, teamId }]) => {
191
+ if (teamId && flaggedPostId) {
192
+ return dispatch(getContentFlaggingTeam(teamId, flaggedPostId));
193
+ }
194
+ return Promise.resolve(null);
195
+ },
196
+ maxBatchSize: 1,
197
+ wait: 200,
198
+ comparator: teamComparator,
199
+ });
200
+ }
201
+ const loader = loaders.contentFlaggingTeamLoader;
202
+ loader.queue([identifier]);
203
+ return {};
204
+ };
205
+ }
87
206
  function getPostContentFlaggingValues(postId) {
88
207
  return async (dispatch, getState) => {
89
208
  let response;
@@ -23,9 +23,10 @@ function getServerLimits() {
23
23
  activeUserCount: response?.data?.activeUserCount ?? 0,
24
24
  maxUsersLimit: response?.data?.maxUsersLimit ?? 0,
25
25
  maxUsersHardLimit: response?.data?.maxUsersHardLimit ?? 0,
26
- // Post history limit fields from server response
27
26
  lastAccessiblePostTime: response?.data?.lastAccessiblePostTime ?? 0,
28
27
  postHistoryLimit: response?.data?.postHistoryLimit ?? 0,
28
+ singleChannelGuestCount: response?.data?.singleChannelGuestCount ?? 0,
29
+ singleChannelGuestLimit: response?.data?.singleChannelGuestLimit ?? 0,
29
30
  };
30
31
  dispatch({ type: action_types_1.LimitsTypes.RECEIVED_APP_LIMITS, data });
31
32
  return { data };
@@ -235,6 +235,20 @@ function createPost(post, files = [], afterSubmit) {
235
235
  update_at: timestamp,
236
236
  reply_count: 0,
237
237
  };
238
+ // Add current_team_id for DM/GM posts to enable proper channel mention resolution
239
+ // This prevents cross-team information disclosure and makes mention resolution deterministic
240
+ const channel = state.entities.channels.channels[post.channel_id];
241
+ const currentTeamId = state.entities.teams.currentTeamId;
242
+ if (channel && !channel.team_id && currentTeamId) {
243
+ // DM/GM channel - add current team context
244
+ newPost = {
245
+ ...newPost,
246
+ props: {
247
+ ...newPost.props,
248
+ current_team_id: currentTeamId,
249
+ },
250
+ };
251
+ }
238
252
  if (post.root_id) {
239
253
  newPost.reply_count = PostSelectors.getPostRepliesCount(state, post.root_id) + 1;
240
254
  }
@@ -29,7 +29,13 @@ function getMissingChannelsFromPosts(posts) {
29
29
  const promises = [];
30
30
  Object.values(posts).forEach((post) => {
31
31
  const id = post.channel_id;
32
- if (!channels[id] || !myMembers[id]) {
32
+ if (!channels[id]) {
33
+ // Fetch channel data independently so a 403 on the membership request (e.g. for public channels
34
+ // the user hasn't joined) doesn't prevent the channel from being loaded.
35
+ promises.push(dispatch((0, channels_1.getChannel)(id)));
36
+ }
37
+ if (!myMembers[id]) {
38
+ // Best-effort: will 403 for non-member public channels, which is fine.
33
39
  promises.push(dispatch((0, channels_1.getChannelAndMyMember)(id)));
34
40
  }
35
41
  if (!membersInChannel[id]) {
@@ -45,7 +51,13 @@ function getMissingChannelsFromFiles(files) {
45
51
  const promises = [];
46
52
  Object.values(files).forEach((file) => {
47
53
  const id = file.channel_id;
48
- if (!channels[id] || !myMembers[id]) {
54
+ if (!channels[id]) {
55
+ // Fetch channel data independently so a 403 on the membership request (e.g. for public channels
56
+ // the user hasn't joined) doesn't prevent the channel from being loaded.
57
+ promises.push(dispatch((0, channels_1.getChannel)(id)));
58
+ }
59
+ if (!myMembers[id]) {
60
+ // Best-effort: will 403 for non-member public channels, which is fine.
49
61
  promises.push(dispatch((0, channels_1.getChannelAndMyMember)(id)));
50
62
  }
51
63
  if (!membersInChannel[id]) {
@@ -15,4 +15,4 @@ export declare function receivedRemoteClusterInfo(remoteId: string, remoteInfo:
15
15
  };
16
16
  };
17
17
  export declare function fetchChannelRemotes(channelId: string, forceRefresh?: boolean): ActionFuncAsync<RemoteClusterInfo[]>;
18
- export declare function fetchRemoteClusterInfo(remoteId: string, forceRefresh?: boolean): ActionFuncAsync<RemoteClusterInfo>;
18
+ export declare function fetchRemoteClusterInfo(remoteId: string, includeDeleted?: boolean, forceRefresh?: boolean): ActionFuncAsync<RemoteClusterInfo>;
@@ -55,7 +55,7 @@ function fetchChannelRemotes(channelId, forceRefresh = false) {
55
55
  return { data };
56
56
  };
57
57
  }
58
- function fetchRemoteClusterInfo(remoteId, forceRefresh = false) {
58
+ function fetchRemoteClusterInfo(remoteId, includeDeleted, forceRefresh = false) {
59
59
  return async (dispatch, getState) => {
60
60
  // Check if we already have the remote info cached
61
61
  const state = getState();
@@ -65,7 +65,7 @@ function fetchRemoteClusterInfo(remoteId, forceRefresh = false) {
65
65
  }
66
66
  let data;
67
67
  try {
68
- data = await client_1.Client4.getRemoteClusterInfo(remoteId);
68
+ data = await client_1.Client4.getRemoteClusterInfo(remoteId, includeDeleted);
69
69
  }
70
70
  catch (error) {
71
71
  (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
@@ -1,7 +1,7 @@
1
1
  import type { AnyAction } from 'redux';
2
2
  import type { UserAutocomplete } from '@mattermost/types/autocomplete';
3
3
  import type { Channel } from '@mattermost/types/channels';
4
- import type { UserProfile, UserStatus, GetFilteredUsersStatsOpts, UsersStats, UserCustomStatus, UserAccessToken } from '@mattermost/types/users';
4
+ import type { UserProfile, UserStatus, GetFilteredUsersStatsOpts, UsersStats, UserCustomStatus, UserAccessToken, UserAuthUpdate } from '@mattermost/types/users';
5
5
  import type { ActionFuncAsync } from 'mattermost-redux/types/actions';
6
6
  export declare const maxUserIdsPerProfilesRequest = 100;
7
7
  export declare const maxUserIdsPerStatusesRequest = 200;
@@ -60,6 +60,7 @@ export declare function searchProfiles(term: string, options?: any): ActionFuncA
60
60
  export declare function updateMe(user: Partial<UserProfile>): ActionFuncAsync<UserProfile>;
61
61
  export declare function saveCustomProfileAttribute(userID: string, attributeID: string, attributeValue: string | string[]): ActionFuncAsync<Record<string, string | string[]>>;
62
62
  export declare function patchUser(user: UserProfile): ActionFuncAsync<UserProfile>;
63
+ export declare function updateUserAuth(userId: string, userAuth: UserAuthUpdate): ActionFuncAsync<UserAuthUpdate>;
63
64
  export declare function updateUserRoles(userId: string, roles: string): ActionFuncAsync;
64
65
  export declare function updateUserMfa(userId: string, activate: boolean, code?: string): ActionFuncAsync;
65
66
  export declare function updateUserPassword(userId: string, currentPassword: string, newPassword: string): ActionFuncAsync;
@@ -104,6 +105,7 @@ declare const _default: {
104
105
  getUserAudits: typeof getUserAudits;
105
106
  searchProfiles: typeof searchProfiles;
106
107
  updateMe: typeof updateMe;
108
+ updateUserAuth: typeof updateUserAuth;
107
109
  updateUserRoles: typeof updateUserRoles;
108
110
  updateUserMfa: typeof updateUserMfa;
109
111
  updateUserPassword: typeof updateUserPassword;
@@ -48,6 +48,7 @@ exports.searchProfiles = searchProfiles;
48
48
  exports.updateMe = updateMe;
49
49
  exports.saveCustomProfileAttribute = saveCustomProfileAttribute;
50
50
  exports.patchUser = patchUser;
51
+ exports.updateUserAuth = updateUserAuth;
51
52
  exports.updateUserRoles = updateUserRoles;
52
53
  exports.updateUserMfa = updateUserMfa;
53
54
  exports.updateUserPassword = updateUserPassword;
@@ -945,6 +946,23 @@ function patchUser(user) {
945
946
  return { data };
946
947
  };
947
948
  }
949
+ function updateUserAuth(userId, userAuth) {
950
+ return async (dispatch, getState) => {
951
+ let data;
952
+ try {
953
+ data = await client_1.Client4.updateUserAuth(userId, userAuth);
954
+ }
955
+ catch (error) {
956
+ dispatch((0, errors_1.logError)(error));
957
+ return { error };
958
+ }
959
+ const profile = getState().entities.users.profiles[userId];
960
+ if (profile) {
961
+ dispatch({ type: action_types_1.UserTypes.RECEIVED_PROFILE, data: { ...profile, auth_data: data.auth_data, auth_service: data.auth_service } });
962
+ }
963
+ return { data };
964
+ };
965
+ }
948
966
  function updateUserRoles(userId, roles) {
949
967
  return async (dispatch, getState) => {
950
968
  try {
@@ -1309,6 +1327,7 @@ exports.default = {
1309
1327
  getUserAudits,
1310
1328
  searchProfiles,
1311
1329
  updateMe,
1330
+ updateUserAuth,
1312
1331
  updateUserRoles,
1313
1332
  updateUserMfa,
1314
1333
  updateUserPassword,
@@ -32,6 +32,8 @@ declare const _default: {
32
32
  SYSTEM_USER_MANAGER_ROLE: string;
33
33
  SYSTEM_READ_ONLY_ADMIN_ROLE: string;
34
34
  SYSTEM_MANAGER_ROLE: string;
35
+ SHARED_CHANNEL_MANAGER_ROLE: string;
36
+ SECURE_CONNECTION_MANAGER_ROLE: string;
35
37
  SYSTEM_USER_ACCESS_TOKEN_ROLE: string;
36
38
  SYSTEM_POST_ALL_ROLE: string;
37
39
  SYSTEM_POST_ALL_PUBLIC_ROLE: string;
@@ -36,6 +36,8 @@ exports.default = {
36
36
  SYSTEM_USER_MANAGER_ROLE: 'system_user_manager',
37
37
  SYSTEM_READ_ONLY_ADMIN_ROLE: 'system_read_only_admin',
38
38
  SYSTEM_MANAGER_ROLE: 'system_manager',
39
+ SHARED_CHANNEL_MANAGER_ROLE: 'shared_channel_manager',
40
+ SECURE_CONNECTION_MANAGER_ROLE: 'secure_connection_manager',
39
41
  SYSTEM_USER_ACCESS_TOKEN_ROLE: 'system_user_access_token',
40
42
  SYSTEM_POST_ALL_ROLE: 'system_post_all',
41
43
  SYSTEM_POST_ALL_PUBLIC_ROLE: 'system_post_all_public',
@@ -22,5 +22,6 @@ declare const _default: {
22
22
  REGISTERED_USERS: "REGISTERED_USERS";
23
23
  TOTAL_FILE_COUNT: "TOTAL_FILE_COUNT";
24
24
  TOTAL_FILE_SIZE: "TOTAL_FILE_SIZE";
25
+ SINGLE_CHANNEL_GUESTS: "SINGLE_CHANNEL_GUESTS";
25
26
  };
26
27
  export default _default;
@@ -30,4 +30,5 @@ exports.default = (0, key_mirror_1.default)({
30
30
  REGISTERED_USERS: null,
31
31
  TOTAL_FILE_COUNT: null,
32
32
  TOTAL_FILE_SIZE: null,
33
+ SINGLE_CHANNEL_GUESTS: null,
33
34
  });
@@ -209,6 +209,9 @@ function convertAnalyticsRowsToStats(data, name) {
209
209
  case 'total_file_size':
210
210
  key = constants_1.Stats.TOTAL_FILE_SIZE;
211
211
  break;
212
+ case 'single_channel_guest_count':
213
+ key = constants_1.Stats.SINGLE_CHANNEL_GUESTS;
214
+ break;
212
215
  }
213
216
  if (key) {
214
217
  stats[key] = row.value;
@@ -6,11 +6,29 @@ declare const _default: import("redux").Reducer<{
6
6
  postValues: {
7
7
  [key: string]: PropertyValue<unknown>[];
8
8
  };
9
+ flaggedPosts: {
10
+ [key: string]: import("@mattermost/types/posts").Post;
11
+ };
12
+ channels: {
13
+ [key: string]: import("@mattermost/types/channels").Channel;
14
+ };
15
+ teams: {
16
+ [key: string]: import("@mattermost/types/teams").Team;
17
+ };
9
18
  }, import("redux").AnyAction, Partial<{
10
19
  settings: ContentFlaggingConfig | undefined;
11
20
  fields: NameMappedPropertyFields | undefined;
12
21
  postValues: {
13
22
  [key: string]: PropertyValue<unknown>[];
14
23
  } | undefined;
24
+ flaggedPosts: {
25
+ [key: string]: import("@mattermost/types/posts").Post;
26
+ } | undefined;
27
+ channels: {
28
+ [key: string]: import("@mattermost/types/channels").Channel;
29
+ } | undefined;
30
+ teams: {
31
+ [key: string]: import("@mattermost/types/teams").Team;
32
+ } | undefined;
15
33
  }>>;
16
34
  export default _default;
@@ -12,6 +12,8 @@ function settings(state = {}, action) {
12
12
  ...action.data,
13
13
  };
14
14
  }
15
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
16
+ return {};
15
17
  default:
16
18
  return state;
17
19
  }
@@ -24,6 +26,8 @@ function fields(state = {}, action) {
24
26
  ...action.data,
25
27
  };
26
28
  }
29
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
30
+ return {};
27
31
  default:
28
32
  return state;
29
33
  }
@@ -52,6 +56,50 @@ function postValues(state = {}, action) {
52
56
  [postId]: Object.values(valuesByFieldId),
53
57
  };
54
58
  }
59
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
60
+ return {};
61
+ default:
62
+ return state;
63
+ }
64
+ }
65
+ function flaggedPosts(state = {}, action) {
66
+ switch (action.type) {
67
+ case action_types_1.ContentFlaggingTypes.RECEIVED_FLAGGED_POST: {
68
+ return {
69
+ ...state,
70
+ [action.data.id]: action.data,
71
+ };
72
+ }
73
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
74
+ return {};
75
+ default:
76
+ return state;
77
+ }
78
+ }
79
+ function channels(state = {}, action) {
80
+ switch (action.type) {
81
+ case action_types_1.ContentFlaggingTypes.RECEIVED_CONTENT_FLAGGING_CHANNEL: {
82
+ return {
83
+ ...state,
84
+ [action.data.id]: action.data,
85
+ };
86
+ }
87
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
88
+ return {};
89
+ default:
90
+ return state;
91
+ }
92
+ }
93
+ function teams(state = {}, action) {
94
+ switch (action.type) {
95
+ case action_types_1.ContentFlaggingTypes.RECEIVED_CONTENT_FLAGGING_TEAM: {
96
+ return {
97
+ ...state,
98
+ [action.data.id]: action.data,
99
+ };
100
+ }
101
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
102
+ return {};
55
103
  default:
56
104
  return state;
57
105
  }
@@ -60,4 +108,7 @@ exports.default = (0, redux_1.combineReducers)({
60
108
  settings,
61
109
  fields,
62
110
  postValues,
111
+ flaggedPosts,
112
+ channels,
113
+ teams,
63
114
  });
@@ -3,11 +3,13 @@ import type { MMReduxAction } from 'mattermost-redux/action_types';
3
3
  export declare function files(state: Record<string, FileInfo> | undefined, action: MMReduxAction): any;
4
4
  export declare function filesFromSearch(state: Record<string, FileSearchResultItem> | undefined, action: MMReduxAction): any;
5
5
  export declare function fileIdsByPostId(state: Record<string, string[]> | undefined, action: MMReduxAction): any;
6
+ export declare function rejectedFiles(state: Set<string> | undefined, action: MMReduxAction): Set<unknown>;
6
7
  declare const _default: import("redux").Reducer<{
7
8
  files: any;
8
9
  filesFromSearch: any;
9
10
  fileIdsByPostId: any;
10
11
  filePublicLink: any;
12
+ rejectedFiles: Set<unknown>;
11
13
  }, import("redux").AnyAction, Partial<{
12
14
  files: Record<string, FileInfo> | undefined;
13
15
  filesFromSearch: Record<string, FileSearchResultItem> | undefined;
@@ -15,5 +17,6 @@ declare const _default: import("redux").Reducer<{
15
17
  filePublicLink: {
16
18
  link: string;
17
19
  } | undefined;
20
+ rejectedFiles: Set<string> | undefined;
18
21
  }>>;
19
22
  export default _default;
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.files = files;
6
6
  exports.filesFromSearch = filesFromSearch;
7
7
  exports.fileIdsByPostId = fileIdsByPostId;
8
+ exports.rejectedFiles = rejectedFiles;
8
9
  const redux_1 = require("redux");
9
10
  const action_types_1 = require("mattermost-redux/action_types");
10
11
  function files(state = {}, action) {
@@ -182,9 +183,27 @@ function filePublicLink(state = { link: '' }, action) {
182
183
  return state;
183
184
  }
184
185
  }
186
+ function rejectedFiles(state = new Set(), action) {
187
+ switch (action.type) {
188
+ case action_types_1.FileTypes.FILE_DOWNLOAD_REJECTED: {
189
+ const { file_id: fileId } = action.data;
190
+ if (fileId) {
191
+ const nextState = new Set(state);
192
+ nextState.add(fileId);
193
+ return nextState;
194
+ }
195
+ return state;
196
+ }
197
+ case action_types_1.UserTypes.LOGOUT_SUCCESS:
198
+ return new Set();
199
+ default:
200
+ return state;
201
+ }
202
+ }
185
203
  exports.default = (0, redux_1.combineReducers)({
186
204
  files,
187
205
  filesFromSearch,
188
206
  fileIdsByPostId,
189
207
  filePublicLink,
208
+ rejectedFiles,
190
209
  });
@@ -95,6 +95,7 @@ declare const _default: import("redux").Reducer<{
95
95
  filesFromSearch: any;
96
96
  fileIdsByPostId: any;
97
97
  filePublicLink: any;
98
+ rejectedFiles: Set<unknown>;
98
99
  };
99
100
  preferences: {
100
101
  myPreferences: any;
@@ -326,6 +327,15 @@ declare const _default: import("redux").Reducer<{
326
327
  postValues: {
327
328
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
328
329
  };
330
+ flaggedPosts: {
331
+ [key: string]: import("@mattermost/types/posts").Post;
332
+ };
333
+ channels: {
334
+ [key: string]: import("@mattermost/types/channels").Channel;
335
+ };
336
+ teams: {
337
+ [key: string]: import("@mattermost/types/teams").Team;
338
+ };
329
339
  };
330
340
  }, import("redux").AnyAction, Partial<{
331
341
  general: {
@@ -466,6 +476,7 @@ declare const _default: import("redux").Reducer<{
466
476
  filesFromSearch: any;
467
477
  fileIdsByPostId: any;
468
478
  filePublicLink: any;
479
+ rejectedFiles: Set<unknown>;
469
480
  } | Partial<{
470
481
  files: Record<string, import("@mattermost/types/files").FileInfo> | undefined;
471
482
  filesFromSearch: Record<string, import("@mattermost/types/files").FileSearchResultItem> | undefined;
@@ -473,6 +484,7 @@ declare const _default: import("redux").Reducer<{
473
484
  filePublicLink: {
474
485
  link: string;
475
486
  } | undefined;
487
+ rejectedFiles: Set<string> | undefined;
476
488
  }> | undefined;
477
489
  preferences: {
478
490
  myPreferences: any;
@@ -782,12 +794,30 @@ declare const _default: import("redux").Reducer<{
782
794
  postValues: {
783
795
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
784
796
  };
797
+ flaggedPosts: {
798
+ [key: string]: import("@mattermost/types/posts").Post;
799
+ };
800
+ channels: {
801
+ [key: string]: import("@mattermost/types/channels").Channel;
802
+ };
803
+ teams: {
804
+ [key: string]: import("@mattermost/types/teams").Team;
805
+ };
785
806
  } | Partial<{
786
807
  settings: import("@mattermost/types/content_flagging").ContentFlaggingConfig | undefined;
787
808
  fields: import("@mattermost/types/properties").NameMappedPropertyFields | undefined;
788
809
  postValues: {
789
810
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
790
811
  } | undefined;
812
+ flaggedPosts: {
813
+ [key: string]: import("@mattermost/types/posts").Post;
814
+ } | undefined;
815
+ channels: {
816
+ [key: string]: import("@mattermost/types/channels").Channel;
817
+ } | undefined;
818
+ teams: {
819
+ [key: string]: import("@mattermost/types/teams").Team;
820
+ } | undefined;
791
821
  }> | undefined;
792
822
  }>>;
793
823
  export default _default;
@@ -97,6 +97,7 @@ declare const _default: {
97
97
  filesFromSearch: any;
98
98
  fileIdsByPostId: any;
99
99
  filePublicLink: any;
100
+ rejectedFiles: Set<unknown>;
100
101
  };
101
102
  preferences: {
102
103
  myPreferences: any;
@@ -328,6 +329,15 @@ declare const _default: {
328
329
  postValues: {
329
330
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
330
331
  };
332
+ flaggedPosts: {
333
+ [key: string]: import("@mattermost/types/posts").Post;
334
+ };
335
+ channels: {
336
+ [key: string]: import("@mattermost/types/channels").Channel;
337
+ };
338
+ teams: {
339
+ [key: string]: import("@mattermost/types/teams").Team;
340
+ };
331
341
  };
332
342
  }, import("redux").AnyAction, Partial<{
333
343
  general: {
@@ -468,6 +478,7 @@ declare const _default: {
468
478
  filesFromSearch: any;
469
479
  fileIdsByPostId: any;
470
480
  filePublicLink: any;
481
+ rejectedFiles: Set<unknown>;
471
482
  } | Partial<{
472
483
  files: Record<string, import("@mattermost/types/files").FileInfo> | undefined;
473
484
  filesFromSearch: Record<string, import("@mattermost/types/files").FileSearchResultItem> | undefined;
@@ -475,6 +486,7 @@ declare const _default: {
475
486
  filePublicLink: {
476
487
  link: string;
477
488
  } | undefined;
489
+ rejectedFiles: Set<string> | undefined;
478
490
  }> | undefined;
479
491
  preferences: {
480
492
  myPreferences: any;
@@ -784,12 +796,30 @@ declare const _default: {
784
796
  postValues: {
785
797
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
786
798
  };
799
+ flaggedPosts: {
800
+ [key: string]: import("@mattermost/types/posts").Post;
801
+ };
802
+ channels: {
803
+ [key: string]: import("@mattermost/types/channels").Channel;
804
+ };
805
+ teams: {
806
+ [key: string]: import("@mattermost/types/teams").Team;
807
+ };
787
808
  } | Partial<{
788
809
  settings: import("@mattermost/types/content_flagging").ContentFlaggingConfig | undefined;
789
810
  fields: import("@mattermost/types/properties").NameMappedPropertyFields | undefined;
790
811
  postValues: {
791
812
  [key: string]: import("@mattermost/types/properties").PropertyValue<unknown>[];
792
813
  } | undefined;
814
+ flaggedPosts: {
815
+ [key: string]: import("@mattermost/types/posts").Post;
816
+ } | undefined;
817
+ channels: {
818
+ [key: string]: import("@mattermost/types/channels").Channel;
819
+ } | undefined;
820
+ teams: {
821
+ [key: string]: import("@mattermost/types/teams").Team;
822
+ } | undefined;
793
823
  }> | undefined;
794
824
  }>>;
795
825
  errors: (state: Array<{
@@ -2,7 +2,6 @@ import type { Channel, ChannelWithTeamData, ChannelSearchOpts } from '@mattermos
2
2
  import type { AccessControlSettings } from '@mattermost/types/config';
3
3
  import type { GlobalState } from '@mattermost/types/store';
4
4
  export declare const getAccessControlSettings: import("../create_selector").OutputSelector<GlobalState, AccessControlSettings, (res1: AccessControlSettings, res2: any) => AccessControlSettings>;
5
- export declare function isChannelScopeAccessControlEnabled(state: GlobalState): boolean;
6
5
  export declare function getAccessControlPolicy(state: GlobalState, id: string): import("@mattermost/types/access_control").AccessControlPolicy;
7
6
  export declare const getChannelIdsForAccessControlPolicy: (state: GlobalState, parentId: string) => string[];
8
7
  export declare function makeGetChannelsInAccessControlPolicy(): (b: GlobalState, a: {
@@ -3,7 +3,6 @@
3
3
  // See LICENSE.txt for license information.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.getChannelIdsForAccessControlPolicy = exports.getAccessControlSettings = void 0;
6
- exports.isChannelScopeAccessControlEnabled = isChannelScopeAccessControlEnabled;
7
6
  exports.getAccessControlPolicy = getAccessControlPolicy;
8
7
  exports.makeGetChannelsInAccessControlPolicy = makeGetChannelsInAccessControlPolicy;
9
8
  exports.searchChannelsInheritsPolicy = searchChannelsInheritsPolicy;
@@ -19,14 +18,9 @@ exports.getAccessControlSettings = (0, create_selector_1.createSelector)('getAcc
19
18
  // Otherwise, build from client config (for regular users/channel admins)
20
19
  return {
21
20
  EnableAttributeBasedAccessControl: config?.EnableAttributeBasedAccessControl === 'true',
22
- EnableChannelScopeAccessControl: config?.EnableChannelScopeAccessControl === 'true',
23
21
  EnableUserManagedAttributes: config?.EnableUserManagedAttributes === 'true',
24
22
  };
25
23
  });
26
- function isChannelScopeAccessControlEnabled(state) {
27
- const settings = (0, exports.getAccessControlSettings)(state);
28
- return settings?.EnableChannelScopeAccessControl || false;
29
- }
30
24
  function getAccessControlPolicy(state, id) {
31
25
  return state.entities.admin.accessControlPolicies[id];
32
26
  }
@@ -6,4 +6,5 @@ export declare function getAgentsStatus(state: GlobalState): {
6
6
  reason?: string;
7
7
  };
8
8
  export declare function getAgent(state: GlobalState, agentId: string): Agent | undefined;
9
+ export declare function getDefaultAgent(state: GlobalState): Agent | undefined;
9
10
  export declare function getLLMServices(state: GlobalState): LLMService[];
@@ -5,9 +5,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.getAgents = getAgents;
6
6
  exports.getAgentsStatus = getAgentsStatus;
7
7
  exports.getAgent = getAgent;
8
+ exports.getDefaultAgent = getDefaultAgent;
8
9
  exports.getLLMServices = getLLMServices;
9
10
  function getAgents(state) {
10
- return state.entities.agents?.agents;
11
+ return state.entities.agents?.agents || [];
11
12
  }
12
13
  function getAgentsStatus(state) {
13
14
  return state.entities.agents?.agentsStatus || { available: false };
@@ -16,6 +17,10 @@ function getAgent(state, agentId) {
16
17
  const agents = getAgents(state);
17
18
  return agents.find((agent) => agent.id === agentId);
18
19
  }
20
+ function getDefaultAgent(state) {
21
+ const agents = getAgents(state);
22
+ return agents?.find((agent) => agent.is_default === true);
23
+ }
19
24
  function getLLMServices(state) {
20
25
  return state.entities.agents?.llmServices || [];
21
26
  }
@@ -1,5 +1,9 @@
1
1
  import type { GlobalState } from '@mattermost/types/store';
2
+ import type { ContentFlaggingChannelRequestIdentifier, ContentFlaggingTeamRequestIdentifier } from 'mattermost-redux/actions/content_flagging';
2
3
  export declare const contentFlaggingFeatureEnabled: (state: GlobalState) => boolean;
3
4
  export declare const contentFlaggingConfig: (state: GlobalState) => import("@mattermost/types/content_flagging").ContentFlaggingConfig | undefined;
4
5
  export declare const contentFlaggingFields: (state: GlobalState) => import("@mattermost/types/properties").NameMappedPropertyFields | undefined;
5
6
  export declare const postContentFlaggingValues: (state: GlobalState, postId: string) => import("@mattermost/types/properties").PropertyValue<unknown>[];
7
+ export declare const getFlaggedPost: (state: GlobalState, flaggedPostId: string) => import("@mattermost/types/posts").Post | undefined;
8
+ export declare const getContentFlaggingChannel: (state: GlobalState, { channelId }: ContentFlaggingChannelRequestIdentifier) => import("@mattermost/types/channels").Channel | undefined;
9
+ export declare const getContentFlaggingTeam: (state: GlobalState, { teamId }: ContentFlaggingTeamRequestIdentifier) => import("@mattermost/types/teams").Team | undefined;
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
3
  // See LICENSE.txt for license information.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.postContentFlaggingValues = exports.contentFlaggingFields = exports.contentFlaggingConfig = exports.contentFlaggingFeatureEnabled = void 0;
5
+ exports.getContentFlaggingTeam = exports.getContentFlaggingChannel = exports.getFlaggedPost = exports.postContentFlaggingValues = exports.contentFlaggingFields = exports.contentFlaggingConfig = exports.contentFlaggingFeatureEnabled = void 0;
6
6
  const general_1 = require("mattermost-redux/selectors/entities/general");
7
7
  const contentFlaggingFeatureEnabled = (state) => {
8
8
  const featureFlagEnabled = (0, general_1.getFeatureFlagValue)(state, 'ContentFlagging') === 'true';
@@ -25,3 +25,30 @@ const postContentFlaggingValues = (state, postId) => {
25
25
  return values[postId];
26
26
  };
27
27
  exports.postContentFlaggingValues = postContentFlaggingValues;
28
+ const getFlaggedPost = (state, flaggedPostId) => {
29
+ return state.entities.contentFlagging.flaggedPosts?.[flaggedPostId];
30
+ };
31
+ exports.getFlaggedPost = getFlaggedPost;
32
+ const getContentFlaggingChannel = (state, { channelId }) => {
33
+ // Return channel from the regular channel store if available, else get it from the content flagging store
34
+ if (!channelId) {
35
+ return undefined;
36
+ }
37
+ const channel = state.entities.channels.channels[channelId];
38
+ if (channel) {
39
+ return channel;
40
+ }
41
+ return state.entities.contentFlagging.channels?.[channelId];
42
+ };
43
+ exports.getContentFlaggingChannel = getContentFlaggingChannel;
44
+ const getContentFlaggingTeam = (state, { teamId }) => {
45
+ if (!teamId) {
46
+ return undefined;
47
+ }
48
+ const team = state.entities.teams.teams[teamId];
49
+ if (team) {
50
+ return team;
51
+ }
52
+ return state.entities.contentFlagging.teams?.[teamId];
53
+ };
54
+ exports.getContentFlaggingTeam = getContentFlaggingTeam;
@@ -5,6 +5,8 @@ export declare function getFile(state: GlobalState, id: string): FileInfo;
5
5
  export declare function getFilePublicLink(state: GlobalState): {
6
6
  link: string;
7
7
  } | undefined;
8
+ export declare function getRejectedFiles(state: GlobalState): Set<string>;
9
+ export declare function isFileRejected(state: GlobalState, fileId: string): boolean;
8
10
  export declare function makeGetFileIdsForPost(): (state: GlobalState, postId: string) => string[];
9
11
  export declare function makeGetFilesForPost(): (state: GlobalState, postId: string) => FileInfo[];
10
12
  export declare function makeGetFilesForEditHistory(): (state: GlobalState, editHistoryPost: Post) => FileInfo[];
@@ -5,6 +5,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.getSearchFilesResults = void 0;
6
6
  exports.getFile = getFile;
7
7
  exports.getFilePublicLink = getFilePublicLink;
8
+ exports.getRejectedFiles = getRejectedFiles;
9
+ exports.isFileRejected = isFileRejected;
8
10
  exports.makeGetFileIdsForPost = makeGetFileIdsForPost;
9
11
  exports.makeGetFilesForPost = makeGetFilesForPost;
10
12
  exports.makeGetFilesForEditHistory = makeGetFilesForEditHistory;
@@ -23,6 +25,13 @@ function getAllFilesFromSearch(state) {
23
25
  function getFilePublicLink(state) {
24
26
  return state.entities.files.filePublicLink;
25
27
  }
28
+ function getRejectedFiles(state) {
29
+ return state.entities.files.rejectedFiles || new Set();
30
+ }
31
+ function isFileRejected(state, fileId) {
32
+ const rejectedFiles = getRejectedFiles(state);
33
+ return rejectedFiles.has(fileId);
34
+ }
26
35
  function makeGetFileIdsForPost() {
27
36
  return (0, create_selector_1.createSelector)('makeGetFileIdsForPost', (state, postId) => state.entities.files.fileIdsByPostId[postId], (fileIds) => {
28
37
  return fileIds || [];
@@ -151,6 +151,7 @@ const state = {
151
151
  files: {},
152
152
  filesFromSearch: {},
153
153
  fileIdsByPostId: {},
154
+ rejectedFiles: new Set(),
154
155
  },
155
156
  emojis: {
156
157
  customEmoji: {},
@@ -1,3 +1,4 @@
1
+ export type Comparator = (a: any, b: any) => boolean;
1
2
  /**
2
3
  * A DataLoader is an object that can be used to batch requests for fetching objects from the server for performance
3
4
  * reasons.
@@ -5,10 +6,12 @@
5
6
  declare abstract class DataLoader<Identifier, Result = unknown> {
6
7
  protected readonly fetchBatch: (identifiers: Identifier[]) => Result;
7
8
  private readonly maxBatchSize;
9
+ private readonly comparator?;
8
10
  protected readonly pendingIdentifiers: Set<Identifier>;
9
11
  constructor(args: {
10
12
  fetchBatch: (identifiers: Identifier[]) => Result;
11
13
  maxBatchSize: number;
14
+ comparator?: Comparator;
12
15
  });
13
16
  queue(identifiersToLoad: Identifier[]): void;
14
17
  /**
@@ -61,6 +64,7 @@ export declare class DelayedDataLoader<Identifier> extends DataLoader<Identifier
61
64
  fetchBatch: (identifiers: Identifier[]) => Promise<unknown>;
62
65
  maxBatchSize: number;
63
66
  wait: number;
67
+ comparator?: Comparator;
64
68
  });
65
69
  queue(identifiersToLoad: Identifier[]): void;
66
70
  queueAndWait(identifiersToLoad: Identifier[]): Promise<void>;
@@ -10,17 +10,35 @@ exports.DelayedDataLoader = exports.BackgroundDataLoader = void 0;
10
10
  class DataLoader {
11
11
  fetchBatch;
12
12
  maxBatchSize;
13
+ comparator;
13
14
  pendingIdentifiers = new Set();
14
15
  constructor(args) {
15
16
  this.fetchBatch = args.fetchBatch;
16
17
  this.maxBatchSize = args.maxBatchSize;
18
+ this.comparator = args.comparator;
17
19
  }
18
20
  queue(identifiersToLoad) {
19
21
  for (const identifier of identifiersToLoad) {
20
22
  if (!identifier) {
21
23
  continue;
22
24
  }
23
- this.pendingIdentifiers.add(identifier);
25
+ // If a custom comparator is provided, manually check for duplicates
26
+ if (this.comparator) {
27
+ let exists = false;
28
+ for (const existing of this.pendingIdentifiers) {
29
+ if (this.comparator(existing, identifier)) {
30
+ exists = true;
31
+ break;
32
+ }
33
+ }
34
+ if (!exists) {
35
+ this.pendingIdentifiers.add(identifier);
36
+ }
37
+ }
38
+ else {
39
+ // Without a comparator, Set automatically handles uniqueness
40
+ this.pendingIdentifiers.add(identifier);
41
+ }
24
42
  }
25
43
  }
26
44
  /**
@@ -30,11 +48,11 @@ class DataLoader {
30
48
  */
31
49
  prepareBatch() {
32
50
  let nextBatch;
33
- // Since we can only fetch a defined number of user statuses at a time, we need to batch the requests
51
+ // Since we can only fetch a defined number of identifiers at a time, we need to batch the requests
34
52
  if (this.pendingIdentifiers.size >= this.maxBatchSize) {
35
53
  nextBatch = [];
36
54
  // We use temp buffer here to store up until max buffer size
37
- // and clear out processed user ids
55
+ // and clear out processed identifiers
38
56
  for (const identifier of this.pendingIdentifiers) {
39
57
  nextBatch.push(identifier);
40
58
  this.pendingIdentifiers.delete(identifier);
@@ -44,7 +62,7 @@ class DataLoader {
44
62
  }
45
63
  }
46
64
  else {
47
- // If we have less than max buffer size, we can directly fetch the statuses
65
+ // If we have less than max buffer size, we can directly fetch the data
48
66
  nextBatch = Array.from(this.pendingIdentifiers);
49
67
  this.pendingIdentifiers.clear();
50
68
  }
@@ -1,5 +1,5 @@
1
- import type { Emoji, SystemEmoji } from '@mattermost/types/emojis';
2
- export declare function isSystemEmoji(emoji: Emoji): emoji is SystemEmoji;
1
+ import { isSystemEmoji, type Emoji } from '@mattermost/types/emojis';
2
+ export { isSystemEmoji };
3
3
  export declare function getEmojiImageUrl(emoji: Emoji): string;
4
4
  export declare function getEmojiName(emoji: Emoji): string;
5
5
  export declare function parseEmojiNamesFromText(text: string): string[];
@@ -2,23 +2,19 @@
2
2
  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
3
  // See LICENSE.txt for license information.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.isSystemEmoji = isSystemEmoji;
5
+ exports.isSystemEmoji = void 0;
6
6
  exports.getEmojiImageUrl = getEmojiImageUrl;
7
7
  exports.getEmojiName = getEmojiName;
8
8
  exports.parseEmojiNamesFromText = parseEmojiNamesFromText;
9
+ const emojis_1 = require("@mattermost/types/emojis");
10
+ Object.defineProperty(exports, "isSystemEmoji", { enumerable: true, get: function () { return emojis_1.isSystemEmoji; } });
9
11
  const client_1 = require("mattermost-redux/client");
10
- function isSystemEmoji(emoji) {
11
- if ('category' in emoji) {
12
- return emoji.category !== 'custom';
13
- }
14
- return !('id' in emoji);
15
- }
16
12
  function getEmojiImageUrl(emoji) {
17
13
  // If its the mattermost custom emoji
18
- if (!isSystemEmoji(emoji) && emoji.id === 'mattermost') {
14
+ if (!(0, emojis_1.isSystemEmoji)(emoji) && emoji.id === 'mattermost') {
19
15
  return client_1.Client4.getSystemEmojiImageUrl('mattermost');
20
16
  }
21
- if (isSystemEmoji(emoji)) {
17
+ if ((0, emojis_1.isSystemEmoji)(emoji)) {
22
18
  const emojiUnified = emoji?.unified?.toLowerCase() ?? '';
23
19
  const filename = emojiUnified || emoji.short_names[0];
24
20
  return client_1.Client4.getSystemEmojiImageUrl(filename);
@@ -26,7 +22,7 @@ function getEmojiImageUrl(emoji) {
26
22
  return client_1.Client4.getEmojiRoute(emoji.id) + '/image';
27
23
  }
28
24
  function getEmojiName(emoji) {
29
- return isSystemEmoji(emoji) ? emoji.short_name : emoji.name;
25
+ return (0, emojis_1.isSystemEmoji)(emoji) ? emoji.short_name : emoji.name;
30
26
  }
31
27
  function parseEmojiNamesFromText(text) {
32
28
  if (!text.includes(':')) {
@@ -8,7 +8,44 @@ const date_fns_1 = require("date-fns");
8
8
  const react_intl_1 = require("react-intl");
9
9
  // Validation patterns for exact storage format matching
10
10
  const DATE_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}$/; // YYYY-MM-DD
11
- const DATETIME_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/; // YYYY-MM-DDTHH:mm:ssZ
11
+ const DATETIME_FORMAT_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(Z|[+-]\d{2}:\d{2})$/; // YYYY-MM-DDTHH:mm:ssZ or with offset
12
+ // Relative pattern: [+-]NNN[dwmHMS]
13
+ const RELATIVE_PATTERN = /^([+-]\d{1,3})([dwmHMS])$/;
14
+ /**
15
+ * Resolves a min_date/max_date bound string to a Date.
16
+ * Handles relative patterns (+2H, +30M, +7d, etc.) and ISO date/datetime strings.
17
+ * Returns null if the value cannot be resolved.
18
+ */
19
+ function resolveBoundToDate(value) {
20
+ // Named relative words
21
+ if (value === 'today') {
22
+ return (0, date_fns_1.startOfDay)(new Date());
23
+ }
24
+ if (value === 'tomorrow') {
25
+ return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(new Date(), 1));
26
+ }
27
+ if (value === 'yesterday') {
28
+ return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(new Date(), -1));
29
+ }
30
+ // Dynamic relative patterns: +2H, +30M, +7d, etc.
31
+ const match = value.match(RELATIVE_PATTERN);
32
+ if (match) {
33
+ const amount = parseInt(match[1], 10);
34
+ const unit = match[2];
35
+ const now = new Date();
36
+ switch (unit) {
37
+ case 'd': return (0, date_fns_1.startOfDay)((0, date_fns_1.addDays)(now, amount));
38
+ case 'w': return (0, date_fns_1.startOfDay)((0, date_fns_1.addWeeks)(now, amount));
39
+ case 'm': return (0, date_fns_1.startOfDay)((0, date_fns_1.addMonths)(now, amount));
40
+ case 'H': return (0, date_fns_1.addHours)(now, amount);
41
+ case 'M': return (0, date_fns_1.addMinutes)(now, amount);
42
+ case 'S': return (0, date_fns_1.addSeconds)(now, amount);
43
+ default: return null;
44
+ }
45
+ }
46
+ const parsed = (0, date_fns_1.parseISO)(value);
47
+ return (0, date_fns_1.isValid)(parsed) ? parsed : null;
48
+ }
12
49
  /**
13
50
  * Validates date/datetime field values for format and range constraints
14
51
  */
@@ -32,9 +69,28 @@ function validateDateTimeValue(value, elem) {
32
69
  else if (!DATETIME_FORMAT_PATTERN.test(value)) {
33
70
  return (0, react_intl_1.defineMessage)({
34
71
  id: 'interactive_dialog.error.bad_datetime_format',
35
- defaultMessage: 'DateTime field must be in YYYY-MM-DDTHH:mm:ssZ format',
72
+ defaultMessage: 'DateTime field must be in YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:MM format',
36
73
  });
37
74
  }
75
+ // Range validation against min_date / max_date
76
+ if (elem.min_date) {
77
+ const minDate = resolveBoundToDate(elem.min_date);
78
+ if (minDate && parsedDate < minDate) {
79
+ return (0, react_intl_1.defineMessage)({
80
+ id: 'interactive_dialog.error.before_min_date',
81
+ defaultMessage: 'Selected time is before the minimum allowed date.',
82
+ });
83
+ }
84
+ }
85
+ if (elem.max_date) {
86
+ const maxDate = resolveBoundToDate(elem.max_date);
87
+ if (maxDate && parsedDate > maxDate) {
88
+ return (0, react_intl_1.defineMessage)({
89
+ id: 'interactive_dialog.error.after_max_date',
90
+ defaultMessage: 'Selected time is after the maximum allowed date.',
91
+ });
92
+ }
93
+ }
38
94
  return null;
39
95
  }
40
96
  function checkDialogElementForError(elem, value) {
@@ -81,6 +81,8 @@ function includesAnAdminRole(roles) {
81
81
  constants_1.General.SYSTEM_USER_MANAGER_ROLE,
82
82
  constants_1.General.SYSTEM_READ_ONLY_ADMIN_ROLE,
83
83
  constants_1.General.SYSTEM_MANAGER_ROLE,
84
+ constants_1.General.SHARED_CHANNEL_MANAGER_ROLE,
85
+ constants_1.General.SECURE_CONNECTION_MANAGER_ROLE,
84
86
  ].some((el) => rolesArray.includes(el));
85
87
  }
86
88
  function isChannelAdmin(roles) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-redux",
3
- "version": "11.5.0",
3
+ "version": "11.6.0",
4
4
  "description": "Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client",
5
5
  "keywords": [
6
6
  "mattermost"
@@ -39,8 +39,8 @@
39
39
  "directory": "webapp/platform/mattermost-redux"
40
40
  },
41
41
  "dependencies": {
42
- "@mattermost/client": "11.5.0",
43
- "@mattermost/types": "11.5.0",
42
+ "@mattermost/client": "11.6.0",
43
+ "@mattermost/types": "11.6.0",
44
44
  "@redux-devtools/extension": "3.3.0",
45
45
  "lodash": "^4.17.21",
46
46
  "moment-timezone": "^0.5.38",