mattermost-redux 11.7.0 → 11.8.0-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 (34) hide show
  1. package/lib/actions/access_control.d.ts +14 -1
  2. package/lib/actions/access_control.js +30 -0
  3. package/lib/actions/channel_categories.js +3 -0
  4. package/lib/actions/channels.d.ts +2 -0
  5. package/lib/actions/channels.js +35 -0
  6. package/lib/actions/properties.d.ts +12 -0
  7. package/lib/actions/properties.js +55 -0
  8. package/lib/actions/recaps.d.ts +3 -0
  9. package/lib/actions/recaps.js +14 -0
  10. package/lib/actions/teams.d.ts +1 -0
  11. package/lib/actions/teams.js +18 -0
  12. package/lib/actions/users.d.ts +1 -1
  13. package/lib/actions/users.js +7 -7
  14. package/lib/constants/general.d.ts +1 -0
  15. package/lib/constants/general.js +1 -0
  16. package/lib/constants/permissions.d.ts +1 -0
  17. package/lib/constants/permissions.js +1 -0
  18. package/lib/constants/posts.d.ts +2 -0
  19. package/lib/constants/posts.js +1 -0
  20. package/lib/constants/preferences.d.ts +4 -0
  21. package/lib/constants/preferences.js +4 -0
  22. package/lib/reducers/entities/properties.js +21 -8
  23. package/lib/selectors/entities/channel_categories.d.ts +2 -0
  24. package/lib/selectors/entities/channel_categories.js +16 -0
  25. package/lib/selectors/entities/general.d.ts +26 -0
  26. package/lib/selectors/entities/general.js +38 -0
  27. package/lib/selectors/entities/recaps.d.ts +10 -0
  28. package/lib/selectors/entities/recaps.js +20 -1
  29. package/lib/selectors/entities/report_a_problem.d.ts +1 -0
  30. package/lib/selectors/entities/report_a_problem.js +31 -4
  31. package/lib/utils/browser_info.d.ts +2 -0
  32. package/lib/utils/browser_info.js +11 -0
  33. package/lib/utils/integration_utils.js +7 -5
  34. package/package.json +3 -3
@@ -1,4 +1,4 @@
1
- import type { AccessControlPoliciesResult, AccessControlPolicy, AccessControlPolicyActiveUpdate, AccessControlTestResult } from '@mattermost/types/access_control';
1
+ import type { AccessControlPoliciesResult, AccessControlPolicy, AccessControlPolicyActiveUpdate, AccessControlTestResult, PolicySimulationResponse, PolicySimulationByUsersParams } from '@mattermost/types/access_control';
2
2
  import type { ChannelSearchOpts, ChannelsWithTotalCount } from '@mattermost/types/channels';
3
3
  import type { ActionFuncAsync } from 'mattermost-redux/types/actions';
4
4
  export declare function getAccessControlPolicy(id: string, channelId?: string, teamId?: string): ActionFuncAsync<AccessControlPolicy, import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
@@ -12,6 +12,19 @@ export declare function assignChannelsToAccessControlPolicy(policyId: string, ch
12
12
  export declare function unassignChannelsFromAccessControlPolicy(policyId: string, channelIds: string[], teamId?: string): ActionFuncAsync<import("@mattermost/types/client4").StatusOK, import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
13
13
  export declare function getAccessControlFields(after: string, limit: number, channelId?: string, teamId?: string): ActionFuncAsync<import("@mattermost/types/properties").UserPropertyField[], import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
14
14
  export declare function searchUsersForExpression(expression: string, term: string, after: string, limit: number, channelId?: string, teamId?: string): ActionFuncAsync<AccessControlTestResult>;
15
+ /**
16
+ * Run the dual-lane PDP simulation against a draft policy for an explicit
17
+ * set of users (with optional per-user session attribute overrides) and
18
+ * return per-user, per-action ALLOW/DENY decisions with blame attribution.
19
+ * Backs the picker-based "Simulate access" modal in the System Console
20
+ * and Channel Settings so authors can see how a draft interacts with
21
+ * persisted higher-scoped policies before saving.
22
+ *
23
+ * The redux action only forwards profiles into the user store on success;
24
+ * decisions and blame metadata stay on the returned data and are consumed
25
+ * directly by the modal.
26
+ */
27
+ export declare function simulatePolicyForUsers(params: PolicySimulationByUsersParams): ActionFuncAsync<PolicySimulationResponse>;
15
28
  export declare function getVisualAST(expression: string, channelId?: string, teamId?: string): ActionFuncAsync<import("@mattermost/types/access_control").AccessControlVisualAST, import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
16
29
  export declare function validateExpressionAgainstRequester(expression: string, channelId?: string, teamId?: string): ActionFuncAsync<{
17
30
  requester_matches: boolean;
@@ -13,6 +13,7 @@ exports.assignChannelsToAccessControlPolicy = assignChannelsToAccessControlPolic
13
13
  exports.unassignChannelsFromAccessControlPolicy = unassignChannelsFromAccessControlPolicy;
14
14
  exports.getAccessControlFields = getAccessControlFields;
15
15
  exports.searchUsersForExpression = searchUsersForExpression;
16
+ exports.simulatePolicyForUsers = simulatePolicyForUsers;
16
17
  exports.getVisualAST = getVisualAST;
17
18
  exports.validateExpressionAgainstRequester = validateExpressionAgainstRequester;
18
19
  exports.createAccessControlSyncJob = createAccessControlSyncJob;
@@ -147,6 +148,35 @@ function searchUsersForExpression(expression, term, after, limit, channelId, tea
147
148
  return { data };
148
149
  };
149
150
  }
151
+ /**
152
+ * Run the dual-lane PDP simulation against a draft policy for an explicit
153
+ * set of users (with optional per-user session attribute overrides) and
154
+ * return per-user, per-action ALLOW/DENY decisions with blame attribution.
155
+ * Backs the picker-based "Simulate access" modal in the System Console
156
+ * and Channel Settings so authors can see how a draft interacts with
157
+ * persisted higher-scoped policies before saving.
158
+ *
159
+ * The redux action only forwards profiles into the user store on success;
160
+ * decisions and blame metadata stay on the returned data and are consumed
161
+ * directly by the modal.
162
+ */
163
+ function simulatePolicyForUsers(params) {
164
+ return async (dispatch, getState) => {
165
+ let data;
166
+ try {
167
+ data = await client_1.Client4.simulateAccessControlPolicyForUsers(params);
168
+ }
169
+ catch (error) {
170
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
171
+ return { error };
172
+ }
173
+ const profiles = data.results?.map((r) => r.user).filter(Boolean) ?? [];
174
+ if (profiles.length > 0) {
175
+ dispatch({ type: action_types_1.UserTypes.RECEIVED_PROFILES, data: profiles });
176
+ }
177
+ return { data };
178
+ };
179
+ }
150
180
  function getVisualAST(expression, channelId, teamId) {
151
181
  return (0, helpers_1.bindClientFunc)({
152
182
  clientFunc: () => client_1.Client4.expressionToVisualFormat(expression, channelId, teamId),
@@ -175,6 +175,9 @@ function addChannelToInitialCategory(channel, setOnServer = false) {
175
175
  if ((0, channel_categories_3.areManagedCategoriesEnabled)(state)) {
176
176
  dispatch(fetchChannelManagedCategoryMapping(channel));
177
177
  }
178
+ if (channel.default_category_name) {
179
+ return { data: false };
180
+ }
178
181
  // Add the new channel to the Channels category on the channel's team
179
182
  if (categories.some((category) => category.channel_ids.some((channelId) => channelId === channel.id))) {
180
183
  return { data: false };
@@ -39,9 +39,11 @@ export declare function deleteChannel(channelId: string): ActionFuncAsync;
39
39
  export declare function unarchiveChannel(channelId: string): ActionFuncAsync;
40
40
  export declare function updateApproximateViewTime(channelId: string): ActionFuncAsync;
41
41
  export declare function unsetActiveChannelOnServer(): ActionFuncAsync;
42
+ export declare function readAllMessages(userId: string): ActionFuncAsync;
42
43
  export declare function readMultipleChannels(channelIds: string[]): ActionFuncAsync;
43
44
  export declare function getChannels(teamId: string, page?: number, perPage?: number): ActionFuncAsync<Channel[]>;
44
45
  export declare function getArchivedChannels(teamId: string, page?: number, perPage?: number): ActionFuncAsync<Channel[]>;
46
+ export declare function getRecommendedChannelsForUser(teamId: string): ActionFuncAsync<Channel[]>;
45
47
  export declare function getAllChannelsWithCount(page?: number, perPage?: number, notAssociatedToGroup?: string, excludeDefaultChannels?: boolean, includeDeleted?: boolean, excludePolicyConstrained?: boolean, accessControlPolicyEnforced?: boolean, excludeAccessControlPolicyEnforced?: boolean): ActionFuncAsync<ChannelsWithTotalCount>;
46
48
  export declare function getAllChannels(page?: number, perPage?: number, notAssociatedToGroup?: string, excludeDefaultChannels?: boolean, excludePolicyConstrained?: boolean, excludeAccessControlPolicyEnforced?: boolean, accessControlPolicyEnforced?: boolean): ActionFuncAsync<ChannelWithTeamData[]>;
47
49
  export declare function autocompleteChannels(teamId: string, term: string): ActionFuncAsync<Channel[]>;
@@ -26,9 +26,11 @@ exports.deleteChannel = deleteChannel;
26
26
  exports.unarchiveChannel = unarchiveChannel;
27
27
  exports.updateApproximateViewTime = updateApproximateViewTime;
28
28
  exports.unsetActiveChannelOnServer = unsetActiveChannelOnServer;
29
+ exports.readAllMessages = readAllMessages;
29
30
  exports.readMultipleChannels = readMultipleChannels;
30
31
  exports.getChannels = getChannels;
31
32
  exports.getArchivedChannels = getArchivedChannels;
33
+ exports.getRecommendedChannelsForUser = getRecommendedChannelsForUser;
32
34
  exports.getAllChannelsWithCount = getAllChannelsWithCount;
33
35
  exports.getAllChannels = getAllChannels;
34
36
  exports.autocompleteChannels = autocompleteChannels;
@@ -693,6 +695,21 @@ function unsetActiveChannelOnServer() {
693
695
  return { data: true };
694
696
  };
695
697
  }
698
+ function readAllMessages(userId) {
699
+ return async (dispatch, getState) => {
700
+ let response;
701
+ try {
702
+ response = await client_1.Client4.markAllMessagesAsRead(userId);
703
+ }
704
+ catch (error) {
705
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
706
+ dispatch((0, errors_1.logError)(error));
707
+ return { error };
708
+ }
709
+ dispatch(markMultipleChannelsAsRead(response.last_viewed_at_times));
710
+ return { data: true };
711
+ };
712
+ }
696
713
  function readMultipleChannels(channelIds) {
697
714
  return async (dispatch, getState) => {
698
715
  let response;
@@ -752,6 +769,24 @@ function getArchivedChannels(teamId, page = 0, perPage = constants_1.General.CHA
752
769
  return { data: channels };
753
770
  };
754
771
  }
772
+ function getRecommendedChannelsForUser(teamId) {
773
+ return async (dispatch, getState) => {
774
+ let channels;
775
+ try {
776
+ channels = await client_1.Client4.getRecommendedChannelsForUser(teamId);
777
+ }
778
+ catch (error) {
779
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
780
+ return { error };
781
+ }
782
+ dispatch({
783
+ type: action_types_1.ChannelTypes.RECEIVED_CHANNELS,
784
+ teamId,
785
+ data: channels,
786
+ });
787
+ return { data: channels };
788
+ };
789
+ }
755
790
  function getAllChannelsWithCount(page = 0, perPage = constants_1.General.CHANNELS_CHUNK_SIZE, notAssociatedToGroup = '', excludeDefaultChannels = false, includeDeleted = false, excludePolicyConstrained = false, accessControlPolicyEnforced = false, excludeAccessControlPolicyEnforced = false) {
756
791
  return async (dispatch, getState) => {
757
792
  dispatch({ type: action_types_1.ChannelTypes.GET_ALL_CHANNELS_REQUEST, data: null });
@@ -0,0 +1,12 @@
1
+ import type { PropertyField, PropertyValue } from '@mattermost/types/properties';
2
+ import type { ActionFuncAsync } from 'mattermost-redux/types/actions';
3
+ /**
4
+ * Fetches property fields for a given group, object type, and target scope,
5
+ * then stores them in the Redux property fields state.
6
+ */
7
+ export declare function fetchPropertyFields(groupName: string, objectType: string, targetType: string, targetId?: string): ActionFuncAsync<PropertyField[]>;
8
+ /**
9
+ * Fetches all system-scoped property values for a given group via the
10
+ * dedicated `/system/values` endpoint, then stores them in Redux.
11
+ */
12
+ export declare function fetchSystemPropertyValues<T = unknown>(groupName: string): ActionFuncAsync<Array<PropertyValue<T>>>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
3
+ // See LICENSE.txt for license information.
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.fetchPropertyFields = fetchPropertyFields;
9
+ exports.fetchSystemPropertyValues = fetchSystemPropertyValues;
10
+ const client_1 = require("mattermost-redux/client");
11
+ const properties_1 = __importDefault(require("../action_types/properties"));
12
+ /**
13
+ * Fetches property fields for a given group, object type, and target scope,
14
+ * then stores them in the Redux property fields state.
15
+ */
16
+ function fetchPropertyFields(groupName, objectType, targetType, targetId) {
17
+ return async (dispatch) => {
18
+ let fields = [];
19
+ const maxItems = 500;
20
+ let fetched = 0;
21
+ let cursorId;
22
+ let cursorCreateAt;
23
+ while (fetched < maxItems) {
24
+ // eslint-disable-next-line no-await-in-loop
25
+ const page = await client_1.Client4.getPropertyFields(groupName, objectType, targetType, targetId, { cursorId, cursorCreateAt });
26
+ fields = fields.concat(page);
27
+ if (page.length === 0) {
28
+ break;
29
+ }
30
+ fetched += page.length;
31
+ const last = page[page.length - 1];
32
+ cursorId = last.id;
33
+ cursorCreateAt = last.create_at;
34
+ }
35
+ dispatch({
36
+ type: properties_1.default.RECEIVED_PROPERTY_FIELDS,
37
+ data: { fields },
38
+ });
39
+ return { data: fields };
40
+ };
41
+ }
42
+ /**
43
+ * Fetches all system-scoped property values for a given group via the
44
+ * dedicated `/system/values` endpoint, then stores them in Redux.
45
+ */
46
+ function fetchSystemPropertyValues(groupName) {
47
+ return async (dispatch) => {
48
+ const values = (await client_1.Client4.getSystemPropertyValues(groupName)) ?? [];
49
+ dispatch({
50
+ type: properties_1.default.RECEIVED_PROPERTY_VALUES,
51
+ data: { values },
52
+ });
53
+ return { data: values };
54
+ };
55
+ }
@@ -4,5 +4,8 @@ export declare function createRecap(title: string, channelIds: string[], agentId
4
4
  export declare function getRecaps(page?: number, perPage?: number): ActionFuncAsync<Recap[]>;
5
5
  export declare function getRecap(recapId: string): ActionFuncAsync<Recap>;
6
6
  export declare function markRecapAsRead(recapId: string): ActionFuncAsync<Recap>;
7
+ export declare function markRecapsAsViewed(): ActionFuncAsync<{
8
+ recap_ids: string[];
9
+ }>;
7
10
  export declare function regenerateRecap(recapId: string): ActionFuncAsync<Recap>;
8
11
  export declare function deleteRecap(recapId: string): ActionFuncAsync;
@@ -6,6 +6,7 @@ exports.createRecap = createRecap;
6
6
  exports.getRecaps = getRecaps;
7
7
  exports.getRecap = getRecap;
8
8
  exports.markRecapAsRead = markRecapAsRead;
9
+ exports.markRecapsAsViewed = markRecapsAsViewed;
9
10
  exports.regenerateRecap = regenerateRecap;
10
11
  exports.deleteRecap = deleteRecap;
11
12
  const action_types_1 = require("mattermost-redux/action_types");
@@ -44,6 +45,19 @@ function markRecapAsRead(recapId) {
44
45
  onFailure: action_types_1.RecapTypes.MARK_RECAP_READ_FAILURE,
45
46
  });
46
47
  }
48
+ function markRecapsAsViewed() {
49
+ return async (dispatch, getState) => {
50
+ try {
51
+ const data = await client_1.Client4.markRecapsAsViewed();
52
+ return { data };
53
+ }
54
+ catch (error) {
55
+ dispatch((0, errors_1.logError)(error));
56
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
57
+ return { error };
58
+ }
59
+ };
60
+ }
47
61
  function regenerateRecap(recapId) {
48
62
  return (0, helpers_1.bindClientFunc)({
49
63
  clientFunc: () => client_1.Client4.regenerateRecap(recapId),
@@ -61,3 +61,4 @@ export declare function invalidateAllEmailInvites(): ActionFuncAsync<import("@ma
61
61
  export declare function membersMinusGroupMembers(teamID: string, groupIDs: string[], page?: number, perPage?: number): ActionFuncAsync<import("@mattermost/types/groups").UsersWithGroupsAndCount, import("@mattermost/types/store").GlobalState, AnyAction>;
62
62
  export declare function getInProductNotices(teamId: string, client: string, clientVersion: string): ActionFuncAsync<import("@mattermost/types/product_notices").ProductNotices, import("@mattermost/types/store").GlobalState, AnyAction>;
63
63
  export declare function updateNoticesAsViewed(noticeIds: string[]): ActionFuncAsync<import("@mattermost/types/client4").StatusOK, import("@mattermost/types/store").GlobalState, AnyAction>;
64
+ export declare function markAllInTeamAsRead(userId: string, teamId: string): ActionFuncAsync;
@@ -44,6 +44,7 @@ exports.invalidateAllEmailInvites = invalidateAllEmailInvites;
44
44
  exports.membersMinusGroupMembers = membersMinusGroupMembers;
45
45
  exports.getInProductNotices = getInProductNotices;
46
46
  exports.updateNoticesAsViewed = updateNoticesAsViewed;
47
+ exports.markAllInTeamAsRead = markAllInTeamAsRead;
47
48
  const redux_batched_actions_1 = require("redux-batched-actions");
48
49
  const action_types_1 = require("mattermost-redux/action_types");
49
50
  const channels_1 = require("mattermost-redux/actions/channels");
@@ -57,6 +58,7 @@ const common_1 = require("mattermost-redux/selectors/entities/common");
57
58
  const teams_1 = require("mattermost-redux/selectors/entities/teams");
58
59
  const users_2 = require("mattermost-redux/selectors/entities/users");
59
60
  const event_emitter_1 = __importDefault(require("mattermost-redux/utils/event_emitter"));
61
+ const threads_1 = require("./threads");
60
62
  async function getProfilesAndStatusesForMembers(userIds, dispatch, getState) {
61
63
  const state = getState();
62
64
  const { currentUserId, profiles, statuses, } = state.entities.users;
@@ -667,3 +669,19 @@ function updateNoticesAsViewed(noticeIds) {
667
669
  ],
668
670
  });
669
671
  }
672
+ function markAllInTeamAsRead(userId, teamId) {
673
+ return async (dispatch, getState) => {
674
+ let response;
675
+ try {
676
+ response = await client_1.Client4.markAllInTeamAsRead(userId, teamId);
677
+ }
678
+ catch (error) {
679
+ (0, helpers_1.forceLogoutIfNecessary)(error, dispatch, getState);
680
+ dispatch((0, errors_1.logError)(error));
681
+ return { error };
682
+ }
683
+ dispatch((0, channels_1.markMultipleChannelsAsRead)(response.last_viewed_at_times));
684
+ dispatch((0, threads_1.handleAllMarkedRead)(teamId));
685
+ return { data: response };
686
+ };
687
+ }
@@ -25,7 +25,7 @@ export declare enum ProfilesInChannelSortBy {
25
25
  export declare function getProfilesInChannel(channelId: string, page: number, perPage?: number, sort?: string, options?: {
26
26
  active?: boolean;
27
27
  }): ActionFuncAsync<UserProfile[]>;
28
- export declare function batchGetProfilesInChannel(channelId: string): ActionFuncAsync<Array<Channel['id']>>;
28
+ export declare function batchGetProfilesInGroupChannel(channelId: string): ActionFuncAsync<Array<Channel['id']>>;
29
29
  export declare function getProfilesInGroupChannels(channelsIds: string[]): ActionFuncAsync;
30
30
  export declare function getProfilesNotInChannel(teamId: string, channelId: string, groupConstrained: boolean, page: number, perPage?: number, cursorId?: string): ActionFuncAsync<UserProfile[]>;
31
31
  export declare function getMe(): ActionFuncAsync<UserProfile>;
@@ -17,7 +17,7 @@ exports.getProfilesByUsernames = getProfilesByUsernames;
17
17
  exports.getProfilesInTeam = getProfilesInTeam;
18
18
  exports.getProfilesNotInTeam = getProfilesNotInTeam;
19
19
  exports.getProfilesInChannel = getProfilesInChannel;
20
- exports.batchGetProfilesInChannel = batchGetProfilesInChannel;
20
+ exports.batchGetProfilesInGroupChannel = batchGetProfilesInGroupChannel;
21
21
  exports.getProfilesInGroupChannels = getProfilesInGroupChannels;
22
22
  exports.getProfilesNotInChannel = getProfilesNotInChannel;
23
23
  exports.getMe = getMe;
@@ -365,16 +365,16 @@ function getProfilesInChannel(channelId, page, perPage = constants_1.General.PRO
365
365
  return { data: profiles };
366
366
  };
367
367
  }
368
- function batchGetProfilesInChannel(channelId) {
368
+ function batchGetProfilesInGroupChannel(channelId) {
369
369
  return async (dispatch, getState, { loaders }) => {
370
- if (!loaders.profilesInChannelLoader) {
371
- loaders.profilesInChannelLoader = new data_loader_1.DelayedDataLoader({
372
- fetchBatch: (channelIds) => dispatch(getProfilesInChannel(channelIds[0], 0)),
373
- maxBatchSize: 1,
370
+ if (!loaders.profilesInGroupChannelLoader) {
371
+ loaders.profilesInGroupChannelLoader = new data_loader_1.DelayedDataLoader({
372
+ fetchBatch: (channelIds) => dispatch(getProfilesInGroupChannels(channelIds)),
373
+ maxBatchSize: constants_1.General.MAX_GROUP_CHANNELS_FOR_PROFILES,
374
374
  wait: missingProfilesWait,
375
375
  });
376
376
  }
377
- await loaders.profilesInChannelLoader.queueAndWait([channelId]);
377
+ await loaders.profilesInGroupChannelLoader.queueAndWait([channelId]);
378
378
  return {};
379
379
  };
380
380
  }
@@ -74,6 +74,7 @@ declare const _default: {
74
74
  DEFAULT_GROUP: string;
75
75
  CUSTOM_GROUP_USER_ROLE: string;
76
76
  MAX_GET_ROLES_BY_NAMES: number;
77
+ SKUEntry: string;
77
78
  SKUEnterprise: string;
78
79
  SKUEnterpriseAdvanced: string;
79
80
  };
@@ -78,6 +78,7 @@ exports.default = {
78
78
  DEFAULT_GROUP: 'board',
79
79
  CUSTOM_GROUP_USER_ROLE: 'custom_group_user',
80
80
  MAX_GET_ROLES_BY_NAMES: 100,
81
+ SKUEntry: 'entry',
81
82
  SKUEnterprise: 'enterprise',
82
83
  SKUEnterpriseAdvanced: 'advanced',
83
84
  };
@@ -44,6 +44,7 @@ declare const values: {
44
44
  REMOVE_OTHERS_REACTIONS: string;
45
45
  PERMANENT_DELETE_USER: string;
46
46
  UPLOAD_FILE: string;
47
+ EDIT_FILE_ATTACHMENT: string;
47
48
  GET_PUBLIC_LINK: string;
48
49
  MANAGE_WEBHOOKS: string;
49
50
  MANAGE_OTHERS_WEBHOOKS: string;
@@ -48,6 +48,7 @@ const values = {
48
48
  REMOVE_OTHERS_REACTIONS: 'remove_others_reactions',
49
49
  PERMANENT_DELETE_USER: 'permanent_delete_user',
50
50
  UPLOAD_FILE: 'upload_file',
51
+ EDIT_FILE_ATTACHMENT: 'edit_file_attachment',
51
52
  GET_PUBLIC_LINK: 'get_public_link',
52
53
  MANAGE_WEBHOOKS: 'manage_webhooks',
53
54
  MANAGE_OTHERS_WEBHOOKS: 'manage_others_webhooks',
@@ -29,6 +29,7 @@ export declare const PostTypes: {
29
29
  WRANGLER: PostType;
30
30
  GM_CONVERTED_TO_CHANNEL: PostType;
31
31
  BURN_ON_READ: PostType;
32
+ SHARED_CHANNEL_STATE: PostType;
32
33
  };
33
34
  declare const _default: {
34
35
  POST_CHUNK_SIZE: number;
@@ -65,6 +66,7 @@ declare const _default: {
65
66
  WRANGLER: PostType;
66
67
  GM_CONVERTED_TO_CHANNEL: PostType;
67
68
  BURN_ON_READ: PostType;
69
+ SHARED_CHANNEL_STATE: PostType;
68
70
  };
69
71
  MESSAGE_TYPES: {
70
72
  POST: string;
@@ -33,6 +33,7 @@ exports.PostTypes = {
33
33
  WRANGLER: 'system_wrangler',
34
34
  GM_CONVERTED_TO_CHANNEL: 'system_gm_to_channel',
35
35
  BURN_ON_READ: 'burn_on_read',
36
+ SHARED_CHANNEL_STATE: 'system_shared_chan_state',
36
37
  };
37
38
  exports.default = {
38
39
  POST_CHUNK_SIZE: 60,
@@ -46,6 +46,10 @@ declare const Preferences: {
46
46
  ADVANCED_SYNC_DRAFTS: string;
47
47
  CATEGORY_WHATS_NEW_MODAL: string;
48
48
  HAS_SEEN_SIDEBAR_WHATS_NEW_MODAL: string;
49
+ CATEGORY_SHORTCUT_ACTIONS: string;
50
+ MARK_ALL_READ_WITHOUT_CONFIRM: string;
51
+ CATEGORY_NEW_FEATURES: string;
52
+ HAS_SEEN_MARK_ALL_READ_FEATURE: string;
49
53
  CATEGORY_PERFORMANCE_DEBUGGING: string;
50
54
  NAME_DISABLE_CLIENT_PLUGINS: string;
51
55
  NAME_DISABLE_TYPING_MESSAGES: string;
@@ -50,6 +50,10 @@ const Preferences = {
50
50
  ADVANCED_SYNC_DRAFTS: 'sync_drafts',
51
51
  CATEGORY_WHATS_NEW_MODAL: 'whats_new_modal',
52
52
  HAS_SEEN_SIDEBAR_WHATS_NEW_MODAL: 'has_seen_sidebar_whats_new_modal',
53
+ CATEGORY_SHORTCUT_ACTIONS: 'shortcut_actions',
54
+ MARK_ALL_READ_WITHOUT_CONFIRM: 'mark_all_read_without_confirm',
55
+ CATEGORY_NEW_FEATURES: 'new_features',
56
+ HAS_SEEN_MARK_ALL_READ_FEATURE: 'mark_all_read_seen',
53
57
  CATEGORY_PERFORMANCE_DEBUGGING: 'performance_debugging',
54
58
  NAME_DISABLE_CLIENT_PLUGINS: 'disable_client_plugins',
55
59
  NAME_DISABLE_TYPING_MESSAGES: 'disable_typing_messages',
@@ -28,7 +28,7 @@ function fieldsReducer(state = initialFieldsState, action) {
28
28
  const nextByObjectType = { ...state.byObjectType };
29
29
  let changed = false;
30
30
  for (const field of fields) {
31
- if ((0, property_utils_1.isPSAv1PropertyField)(field) || field.delete_at > 0) {
31
+ if ((0, property_utils_1.isPSAv1PropertyField)(field)) {
32
32
  continue;
33
33
  }
34
34
  changed = true;
@@ -38,16 +38,27 @@ function fieldsReducer(state = initialFieldsState, action) {
38
38
  if (!nextByObjectType[objectType]) {
39
39
  nextByObjectType[objectType] = {};
40
40
  }
41
- else if (nextByObjectType[objectType] === state.byObjectType[objectType]) {
42
- nextByObjectType[objectType] = { ...nextByObjectType[objectType] };
41
+ else if (nextByObjectType[objectType] ===
42
+ state.byObjectType[objectType]) {
43
+ nextByObjectType[objectType] = {
44
+ ...nextByObjectType[objectType],
45
+ };
43
46
  }
44
47
  if (!nextByObjectType[objectType][groupId]) {
45
48
  nextByObjectType[objectType][groupId] = {};
46
49
  }
47
- else if (nextByObjectType[objectType][groupId] === state.byObjectType[objectType]?.[groupId]) {
48
- nextByObjectType[objectType][groupId] = { ...nextByObjectType[objectType][groupId] };
50
+ else if (nextByObjectType[objectType][groupId] ===
51
+ state.byObjectType[objectType]?.[groupId]) {
52
+ nextByObjectType[objectType][groupId] = {
53
+ ...nextByObjectType[objectType][groupId],
54
+ };
55
+ }
56
+ if (field.delete_at > 0) {
57
+ Reflect.deleteProperty(nextByObjectType[objectType][groupId], field.id);
58
+ }
59
+ else {
60
+ nextByObjectType[objectType][groupId][field.id] = field;
49
61
  }
50
- nextByObjectType[objectType][groupId][field.id] = field;
51
62
  }
52
63
  if (!changed) {
53
64
  return state;
@@ -66,7 +77,9 @@ function fieldsReducer(state = initialFieldsState, action) {
66
77
  Reflect.deleteProperty(nextById, fieldId);
67
78
  const nextByObjectType = { ...state.byObjectType };
68
79
  nextByObjectType[objectType] = { ...nextByObjectType[objectType] };
69
- nextByObjectType[objectType][groupId] = { ...nextByObjectType[objectType][groupId] };
80
+ nextByObjectType[objectType][groupId] = {
81
+ ...nextByObjectType[objectType][groupId],
82
+ };
70
83
  Reflect.deleteProperty(nextByObjectType[objectType][groupId], fieldId);
71
84
  // Clean up empty buckets
72
85
  if (Object.keys(nextByObjectType[objectType][groupId]).length === 0) {
@@ -86,7 +99,7 @@ function fieldsReducer(state = initialFieldsState, action) {
86
99
  function valuesReducer(state = initialValuesState, action) {
87
100
  switch (action.type) {
88
101
  case action_types_1.PropertyTypes.RECEIVED_PROPERTY_VALUES: {
89
- const values = action.data.values;
102
+ const values = action.data.values ?? [];
90
103
  if (values.length === 0) {
91
104
  return state;
92
105
  }
@@ -21,8 +21,10 @@ export declare function makeGetChannelIdsForCategory(): (state: GlobalState, cat
21
21
  export declare function makeFilterAndSortChannelsForCategory(): (state: GlobalState, originalChannels: Channel[], category: ChannelCategory) => Channel[];
22
22
  export declare function makeGetChannelsByCategory(): (state: GlobalState, teamId: string) => RelationOneToOne<ChannelCategory, Channel[]>;
23
23
  export declare function areManagedCategoriesEnabled(state: GlobalState): boolean;
24
+ export declare function isChannelCategorySortingEnabled(state: GlobalState): boolean;
24
25
  export declare function getManagedCategoryMappings(state: GlobalState, teamId: string): Record<string, string> | undefined;
25
26
  export declare function isChannelInManagedCategory(state: GlobalState, channelId: string): boolean;
26
27
  export declare function getChannelManagedCategoryName(state: GlobalState, channelId: string): string | undefined;
27
28
  export declare function makeGetCategoriesForTeam(): (state: GlobalState, teamId: string) => ChannelCategory[];
29
+ export declare function makeGetSidebarCategoryNamesForTeam(): (state: GlobalState, teamId: string) => string[];
28
30
  export declare function makeGetManagedCategoriesForTeam(): (state: GlobalState, teamId: string) => ChannelCategory[];
@@ -24,10 +24,12 @@ exports.makeGetChannelIdsForCategory = makeGetChannelIdsForCategory;
24
24
  exports.makeFilterAndSortChannelsForCategory = makeFilterAndSortChannelsForCategory;
25
25
  exports.makeGetChannelsByCategory = makeGetChannelsByCategory;
26
26
  exports.areManagedCategoriesEnabled = areManagedCategoriesEnabled;
27
+ exports.isChannelCategorySortingEnabled = isChannelCategorySortingEnabled;
27
28
  exports.getManagedCategoryMappings = getManagedCategoryMappings;
28
29
  exports.isChannelInManagedCategory = isChannelInManagedCategory;
29
30
  exports.getChannelManagedCategoryName = getChannelManagedCategoryName;
30
31
  exports.makeGetCategoriesForTeam = makeGetCategoriesForTeam;
32
+ exports.makeGetSidebarCategoryNamesForTeam = makeGetSidebarCategoryNamesForTeam;
31
33
  exports.makeGetManagedCategoriesForTeam = makeGetManagedCategoriesForTeam;
32
34
  const shallow_equals_1 = __importDefault(require("shallow-equals"));
33
35
  const channel_categories_1 = require("@mattermost/types/channel_categories");
@@ -348,6 +350,9 @@ function isUnreadChannel(channelId, messageCounts, members, crtEnabled) {
348
350
  function areManagedCategoriesEnabled(state) {
349
351
  return (0, general_1.getConfig)(state).FeatureFlagManagedChannelCategories === 'true';
350
352
  }
353
+ function isChannelCategorySortingEnabled(state) {
354
+ return (0, general_1.getConfig)(state).EnableChannelCategorySorting === 'true';
355
+ }
351
356
  function getManagedCategoryMappings(state, teamId) {
352
357
  if (!areManagedCategoriesEnabled(state)) {
353
358
  return undefined;
@@ -396,6 +401,17 @@ function makeGetCategoriesForTeam() {
396
401
  ];
397
402
  });
398
403
  }
404
+ function makeGetSidebarCategoryNamesForTeam() {
405
+ const getCategoriesForTeam = makeGetCategoriesForTeam();
406
+ return (0, create_selector_1.createSelector)('makeGetSidebarCategoryNamesForTeam', (state, teamId) => getCategoriesForTeam(state, teamId), (state) => (0, i18n_1.getCurrentUserLocale)(state), (categories, locale) => {
407
+ const names = categories.
408
+ filter((c) => c.type === channel_categories_2.CategoryTypes.CUSTOM || c.type === channel_categories_2.CategoryTypes.MANAGED).
409
+ map((c) => c.display_name);
410
+ const unique = [...new Set(names)];
411
+ unique.sort((a, b) => a.localeCompare(b, locale, { numeric: true }));
412
+ return unique;
413
+ });
414
+ }
399
415
  function makeGetManagedCategoriesForTeam() {
400
416
  return (0, create_selector_1.createSelector)('makeGetManagedCategoriesForTeam', (state, teamId) => getManagedCategoryMappings(state, teamId), (state) => (0, users_1.getCurrentUserId)(state), (_, teamId) => teamId, (mappings, currentUserId, teamId) => {
401
417
  if (!mappings || Object.keys(mappings).length === 0) {
@@ -9,6 +9,31 @@ export declare function getConfig(state: GlobalState): Partial<ClientConfig>;
9
9
  export declare function getFeatureFlagValue(state: GlobalState, key: keyof FeatureFlags): string | undefined;
10
10
  export declare function isCustomProfileAttributesEnabled(state: GlobalState): boolean;
11
11
  export declare function isPermissionPoliciesEnabled(state: GlobalState): boolean;
12
+ /**
13
+ * Whether channel-scope policies may carry permission-rule actions
14
+ * (file upload/download) — i.e. whether the Channel Settings →
15
+ * Permissions Policy tab should be exposed.
16
+ *
17
+ * The sub-flag `ChannelPermissionPolicies` AND the umbrella
18
+ * `PermissionPolicies` must BOTH be on. Mirrors the server-side
19
+ * `FeatureFlags.IsChannelPermissionPoliciesEnabled()` helper so the
20
+ * dependency direction is consistent across the wire. Centralizing
21
+ * the check here means every consumer (settings tab, save buttons,
22
+ * etc.) automatically picks up future changes to the dependency.
23
+ */
24
+ export declare function isChannelPermissionPoliciesEnabled(state: GlobalState): boolean;
25
+ /**
26
+ * Whether the "Simulate access" preview UX should be exposed
27
+ * (System Console policy editor + Channel Settings tab).
28
+ *
29
+ * The sub-flag `PolicySimulation` AND the umbrella
30
+ * `PermissionPolicies` must BOTH be on. Mirrors the server-side
31
+ * `FeatureFlags.IsPolicySimulationEnabled()` helper. The backing
32
+ * `/cel/simulate_users` endpoint returns 501 when this is off, so
33
+ * hiding the entry points here also prevents users from seeing an
34
+ * "Evaluating…" button that can never resolve.
35
+ */
36
+ export declare function isPolicySimulationEnabled(state: GlobalState): boolean;
12
37
  export type PasswordConfig = {
13
38
  minimumLength: number;
14
39
  requireLowercase: boolean;
@@ -18,6 +43,7 @@ export type PasswordConfig = {
18
43
  };
19
44
  export declare const getPasswordConfig: (state: GlobalState) => PasswordConfig;
20
45
  export declare function getLicense(state: GlobalState): ClientLicense;
46
+ export declare function isFreeEdition(state: GlobalState): boolean;
21
47
  export declare const isCloudLicense: (state: GlobalState) => boolean;
22
48
  export declare function isCompatibleWithJoinViewTeamPermissions(state: GlobalState): boolean;
23
49
  export declare const canUploadFilesOnMobile: (a: GlobalState) => boolean;
@@ -7,7 +7,10 @@ exports.getConfig = getConfig;
7
7
  exports.getFeatureFlagValue = getFeatureFlagValue;
8
8
  exports.isCustomProfileAttributesEnabled = isCustomProfileAttributesEnabled;
9
9
  exports.isPermissionPoliciesEnabled = isPermissionPoliciesEnabled;
10
+ exports.isChannelPermissionPoliciesEnabled = isChannelPermissionPoliciesEnabled;
11
+ exports.isPolicySimulationEnabled = isPolicySimulationEnabled;
10
12
  exports.getLicense = getLicense;
13
+ exports.isFreeEdition = isFreeEdition;
11
14
  exports.isCompatibleWithJoinViewTeamPermissions = isCompatibleWithJoinViewTeamPermissions;
12
15
  exports.getFirstAdminVisitMarketplaceStatus = getFirstAdminVisitMarketplaceStatus;
13
16
  exports.getFirstAdminSetupComplete = getFirstAdminSetupComplete;
@@ -34,6 +37,37 @@ function isCustomProfileAttributesEnabled(state) {
34
37
  function isPermissionPoliciesEnabled(state) {
35
38
  return getConfig(state).FeatureFlagPermissionPolicies === 'true';
36
39
  }
40
+ /**
41
+ * Whether channel-scope policies may carry permission-rule actions
42
+ * (file upload/download) — i.e. whether the Channel Settings →
43
+ * Permissions Policy tab should be exposed.
44
+ *
45
+ * The sub-flag `ChannelPermissionPolicies` AND the umbrella
46
+ * `PermissionPolicies` must BOTH be on. Mirrors the server-side
47
+ * `FeatureFlags.IsChannelPermissionPoliciesEnabled()` helper so the
48
+ * dependency direction is consistent across the wire. Centralizing
49
+ * the check here means every consumer (settings tab, save buttons,
50
+ * etc.) automatically picks up future changes to the dependency.
51
+ */
52
+ function isChannelPermissionPoliciesEnabled(state) {
53
+ return isPermissionPoliciesEnabled(state) &&
54
+ getConfig(state).FeatureFlagChannelPermissionPolicies === 'true';
55
+ }
56
+ /**
57
+ * Whether the "Simulate access" preview UX should be exposed
58
+ * (System Console policy editor + Channel Settings tab).
59
+ *
60
+ * The sub-flag `PolicySimulation` AND the umbrella
61
+ * `PermissionPolicies` must BOTH be on. Mirrors the server-side
62
+ * `FeatureFlags.IsPolicySimulationEnabled()` helper. The backing
63
+ * `/cel/simulate_users` endpoint returns 501 when this is off, so
64
+ * hiding the entry points here also prevents users from seeing an
65
+ * "Evaluating…" button that can never resolve.
66
+ */
67
+ function isPolicySimulationEnabled(state) {
68
+ return isPermissionPoliciesEnabled(state) &&
69
+ getConfig(state).FeatureFlagPolicySimulation === 'true';
70
+ }
37
71
  exports.getPasswordConfig = (0, create_selector_1.createSelector)('getPasswordConfig', getConfig, (config) => {
38
72
  return {
39
73
  minimumLength: parseInt(config.PasswordMinimumLength, 10),
@@ -46,6 +80,10 @@ exports.getPasswordConfig = (0, create_selector_1.createSelector)('getPasswordCo
46
80
  function getLicense(state) {
47
81
  return state.entities.general.license;
48
82
  }
83
+ function isFreeEdition(state) {
84
+ const license = getLicense(state);
85
+ return license.IsLicensed !== 'true' || license.SkuShortName === constants_1.General.SKUEntry;
86
+ }
49
87
  exports.isCloudLicense = (0, create_selector_1.createSelector)('isCloudLicense', getLicense, (license) => license?.Cloud === 'true');
50
88
  function isCompatibleWithJoinViewTeamPermissions(state) {
51
89
  const version = state.entities.general.serverVersion;
@@ -9,3 +9,13 @@ export declare const getCompletedRecaps: import("mattermost-redux/selectors/crea
9
9
  export declare const getPendingRecaps: import("mattermost-redux/selectors/create_selector").OutputSelector<GlobalState, Recap[], (res: Recap[]) => Recap[]>;
10
10
  export declare const getUnreadRecaps: import("mattermost-redux/selectors/create_selector").OutputSelector<GlobalState, Recap[], (res: Recap[]) => Recap[]>;
11
11
  export declare const getReadRecaps: import("mattermost-redux/selectors/create_selector").OutputSelector<GlobalState, Recap[], (res: Recap[]) => Recap[]>;
12
+ export declare const getUnreadFinishedRecapsBadge: import("mattermost-redux/selectors/create_selector").OutputSelector<GlobalState, {
13
+ count: number;
14
+ hasFailed: boolean;
15
+ }, (res: {
16
+ byId: Record<string, Recap>;
17
+ allIds: string[];
18
+ }) => {
19
+ count: number;
20
+ hasFailed: boolean;
21
+ }>;
@@ -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.getReadRecaps = exports.getUnreadRecaps = exports.getPendingRecaps = exports.getCompletedRecaps = exports.getSortedRecaps = exports.getRecapsByStatus = void 0;
5
+ exports.getUnreadFinishedRecapsBadge = exports.getReadRecaps = exports.getUnreadRecaps = exports.getPendingRecaps = exports.getCompletedRecaps = exports.getSortedRecaps = exports.getRecapsByStatus = void 0;
6
6
  exports.getAllRecaps = getAllRecaps;
7
7
  exports.getRecap = getRecap;
8
8
  const recaps_1 = require("@mattermost/types/recaps");
@@ -32,3 +32,22 @@ exports.getUnreadRecaps = (0, create_selector_1.createSelector)('getUnreadRecaps
32
32
  exports.getReadRecaps = (0, create_selector_1.createSelector)('getReadRecaps', getAllRecaps, (recaps) => {
33
33
  return recaps.filter((recap) => recap.read_at > 0).sort((a, b) => b.read_at - a.read_at);
34
34
  });
35
+ const getRecapsSlice = (state) => state.entities.recaps;
36
+ exports.getUnreadFinishedRecapsBadge = (0, create_selector_1.createSelector)('getUnreadFinishedRecapsBadge', getRecapsSlice, ({ byId, allIds }) => {
37
+ let count = 0;
38
+ let hasFailed = false;
39
+ for (const id of allIds) {
40
+ const recap = byId[id];
41
+ if (!recap || recap.viewed_at !== 0) {
42
+ continue;
43
+ }
44
+ if (recap.status === recaps_1.RecapStatus.COMPLETED) {
45
+ count++;
46
+ }
47
+ else if (recap.status === recaps_1.RecapStatus.FAILED) {
48
+ count++;
49
+ hasFailed = true;
50
+ }
51
+ }
52
+ return { count, hasFailed };
53
+ });
@@ -1,3 +1,4 @@
1
1
  import type { GlobalState } from '@mattermost/types/store';
2
2
  export declare function getReportAProblemLink(state: GlobalState): string;
3
+ export declare const getDefaultReportAProblemMailtoLink: import("mattermost-redux/selectors/create_selector").OutputSelector<GlobalState, string, (res1: string, res2: string, res3: string | undefined, res4: string | undefined) => string>;
3
4
  export declare const getSystemInfoMailtoLink: import("mattermost-redux/selectors/create_selector").OutputParametricSelector<GlobalState, string | undefined, string, (res1: string, res2: string, res3: string | undefined, res4: string | undefined, res5: string | undefined, res6: string | undefined) => string>;
@@ -2,13 +2,14 @@
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.getSystemInfoMailtoLink = void 0;
5
+ exports.getSystemInfoMailtoLink = exports.getDefaultReportAProblemMailtoLink = void 0;
6
6
  exports.getReportAProblemLink = getReportAProblemLink;
7
7
  const create_selector_1 = require("mattermost-redux/selectors/create_selector");
8
8
  const browser_info_1 = require("mattermost-redux/utils/browser_info");
9
9
  const general_1 = require("./general");
10
10
  const teams_1 = require("./teams");
11
11
  const users_1 = require("./users");
12
+ const MATTERMOST_REPORT_PROBLEM_EMAIL = 'reportaproblem@mattermost.com';
12
13
  function getReportAProblemLink(state) {
13
14
  const config = (0, general_1.getConfig)(state);
14
15
  const type = config.ReportAProblemType;
@@ -21,15 +22,41 @@ function getReportAProblemLink(state) {
21
22
  }
22
23
  // falls through
23
24
  case 'default': {
24
- const isLicensed = (0, general_1.getLicense)(state).IsLicensed === 'true';
25
- if (isLicensed) {
26
- return 'https://mattermost.com/pl/report_a_problem_licensed';
25
+ if (!(0, general_1.isFreeEdition)(state)) {
26
+ return (0, exports.getDefaultReportAProblemMailtoLink)(state);
27
27
  }
28
28
  return 'https://mattermost.com/pl/report_a_problem_unlicensed';
29
29
  }
30
30
  }
31
31
  return '';
32
32
  }
33
+ exports.getDefaultReportAProblemMailtoLink = (0, create_selector_1.createSelector)('getDefaultReportAProblemMailtoLink', users_1.getCurrentUserId, teams_1.getCurrentTeamId, (state) => (0, general_1.getConfig)(state).Version, (state) => (0, general_1.getConfig)(state).BuildNumber, (currentUserId, currentTeamId, version, buildNumber) => {
34
+ const { browser, browserVersion } = (0, browser_info_1.getBrowserInfo)();
35
+ const platformName = (0, browser_info_1.getPlatformInfo)();
36
+ let appLine = '';
37
+ let logsInstructions = '';
38
+ if ((0, browser_info_1.isDesktopApp)()) {
39
+ appLine = `Desktop Version: ${(0, browser_info_1.getDesktopVersion)()}`;
40
+ logsInstructions = 'desktop app logs (https://support.mattermost.com/hc/en-us/articles/37269786544916)';
41
+ }
42
+ else {
43
+ appLine = `Browser: ${browser} ${browserVersion}`;
44
+ logsInstructions = 'browser console logs (https://support.mattermost.com/hc/en-us/articles/35971622382484)';
45
+ }
46
+ const subject = 'Problem with Mattermost app';
47
+ const body = `Please share a description of the problem with reproduction steps:
48
+
49
+
50
+ You may also attach any relevant screenshots and ${logsInstructions}, if applicable:
51
+
52
+ App metadata:
53
+ - Current User Id: ${currentUserId}
54
+ - Current Team Id: ${currentTeamId}
55
+ - Server Version: ${version} (Build ${buildNumber})
56
+ - App Platform: ${platformName}
57
+ - ${appLine}`.trim();
58
+ return `mailto:${MATTERMOST_REPORT_PROBLEM_EMAIL}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
59
+ });
33
60
  exports.getSystemInfoMailtoLink = (0, create_selector_1.createSelector)('getSystemInfoMailtoLink', users_1.getCurrentUserId, teams_1.getCurrentTeamId, (state) => (0, general_1.getConfig)(state).Version, (state) => (0, general_1.getConfig)(state).BuildNumber, (state) => (0, general_1.getConfig)(state).SiteName, (state, supportEmail) => supportEmail, (currentUserId, currentTeamId, version, buildNumber, siteName, supportEmail) => {
34
61
  const { browser, browserVersion } = (0, browser_info_1.getBrowserInfo)();
35
62
  const platformName = (0, browser_info_1.getPlatformInfo)();
@@ -2,4 +2,6 @@ export declare function getBrowserInfo(): {
2
2
  browser: string;
3
3
  browserVersion: string;
4
4
  };
5
+ export declare function isDesktopApp(): boolean;
6
+ export declare function getDesktopVersion(): string;
5
7
  export declare function getPlatformInfo(): string;
@@ -3,6 +3,8 @@
3
3
  // See LICENSE.txt for license information.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.getBrowserInfo = getBrowserInfo;
6
+ exports.isDesktopApp = isDesktopApp;
7
+ exports.getDesktopVersion = getDesktopVersion;
6
8
  exports.getPlatformInfo = getPlatformInfo;
7
9
  function getBrowserInfo() {
8
10
  const userAgent = window.navigator.userAgent.toLowerCase();
@@ -63,6 +65,15 @@ function getBrowserInfo() {
63
65
  }
64
66
  return { browser, browserVersion };
65
67
  }
68
+ function isDesktopApp() {
69
+ return window.navigator.userAgent.indexOf('Mattermost') !== -1 && window.navigator.userAgent.indexOf('Electron') !== -1;
70
+ }
71
+ function getDesktopVersion() {
72
+ // use if the value window.desktop.version is not set yet
73
+ const regex = /Mattermost\/(\d+\.\d+\.\d+)/gm;
74
+ const match = regex.exec(window.navigator.appVersion)?.[1] || '';
75
+ return match;
76
+ }
66
77
  function getPlatformInfo() {
67
78
  // Casting to undefined in case it is deprecated in any browser
68
79
  const platform = window.navigator.platform;
@@ -72,9 +72,11 @@ function validateDateTimeValue(value, elem) {
72
72
  defaultMessage: 'DateTime field must be in YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss+HH:MM format',
73
73
  });
74
74
  }
75
- // Range validation against min_date / max_date
76
- if (elem.min_date) {
77
- const minDate = resolveBoundToDate(elem.min_date);
75
+ // Range validation against min_date / max_date (datetime_config takes precedence over legacy fields)
76
+ const effectiveMinDate = elem.datetime_config?.min_date ?? elem.min_date;
77
+ const effectiveMaxDate = elem.datetime_config?.max_date ?? elem.max_date;
78
+ if (effectiveMinDate) {
79
+ const minDate = resolveBoundToDate(effectiveMinDate);
78
80
  if (minDate && parsedDate < minDate) {
79
81
  return (0, react_intl_1.defineMessage)({
80
82
  id: 'interactive_dialog.error.before_min_date',
@@ -82,8 +84,8 @@ function validateDateTimeValue(value, elem) {
82
84
  });
83
85
  }
84
86
  }
85
- if (elem.max_date) {
86
- const maxDate = resolveBoundToDate(elem.max_date);
87
+ if (effectiveMaxDate) {
88
+ const maxDate = resolveBoundToDate(effectiveMaxDate);
87
89
  if (maxDate && parsedDate > maxDate) {
88
90
  return (0, react_intl_1.defineMessage)({
89
91
  id: 'interactive_dialog.error.after_max_date',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-redux",
3
- "version": "11.7.0",
3
+ "version": "11.8.0-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.7.0",
43
- "@mattermost/types": "11.7.0",
42
+ "@mattermost/client": "11.8.0-0",
43
+ "@mattermost/types": "11.8.0-0",
44
44
  "@redux-devtools/extension": "3.3.0",
45
45
  "lodash": "^4.17.21",
46
46
  "moment-timezone": "^0.5.38",