mattermost-redux 11.2.0 → 11.3.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.
@@ -41,5 +41,8 @@ declare const _default: {
41
41
  DELETE_ACK_POST_SUCCESS: "DELETE_ACK_POST_SUCCESS";
42
42
  MOVE_POST_SUCCESS: "MOVE_POST_SUCCESS";
43
43
  MOVE_POST_FAILURE: "MOVE_POST_FAILURE";
44
+ REVEAL_BURN_ON_READ_SUCCESS: "REVEAL_BURN_ON_READ_SUCCESS";
45
+ POST_RECIPIENTS_UPDATED: "POST_RECIPIENTS_UPDATED";
46
+ BURN_ON_READ_ALL_REVEALED: "BURN_ON_READ_ALL_REVEALED";
44
47
  };
45
48
  export default _default;
@@ -49,4 +49,7 @@ exports.default = (0, key_mirror_1.default)({
49
49
  DELETE_ACK_POST_SUCCESS: null,
50
50
  MOVE_POST_SUCCESS: null,
51
51
  MOVE_POST_FAILURE: null,
52
+ REVEAL_BURN_ON_READ_SUCCESS: null,
53
+ POST_RECIPIENTS_UPDATED: null,
54
+ BURN_ON_READ_ALL_REVEALED: null,
52
55
  });
@@ -1,4 +1,4 @@
1
- import type { AccessControlPoliciesResult, AccessControlPolicy, AccessControlTestResult } from '@mattermost/types/access_control';
1
+ import type { AccessControlPoliciesResult, AccessControlPolicy, AccessControlPolicyActiveUpdate, AccessControlTestResult } 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): ActionFuncAsync<AccessControlPolicy, import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
@@ -18,3 +18,4 @@ export declare function validateExpressionAgainstRequester(expression: string, c
18
18
  export declare function createAccessControlSyncJob(jobData: {
19
19
  policy_id: string;
20
20
  }): ActionFuncAsync<any>;
21
+ export declare function updateAccessControlPoliciesActive(states: AccessControlPolicyActiveUpdate[]): ActionFuncAsync<AccessControlPolicy[], import("@mattermost/types/store").GlobalState, import("redux").AnyAction>;
@@ -15,6 +15,7 @@ exports.searchUsersForExpression = searchUsersForExpression;
15
15
  exports.getVisualAST = getVisualAST;
16
16
  exports.validateExpressionAgainstRequester = validateExpressionAgainstRequester;
17
17
  exports.createAccessControlSyncJob = createAccessControlSyncJob;
18
+ exports.updateAccessControlPoliciesActive = updateAccessControlPoliciesActive;
18
19
  const redux_batched_actions_1 = require("redux-batched-actions");
19
20
  const action_types_1 = require("mattermost-redux/action_types");
20
21
  const client_1 = require("mattermost-redux/client");
@@ -170,3 +171,9 @@ function createAccessControlSyncJob(jobData) {
170
171
  return { data };
171
172
  };
172
173
  }
174
+ function updateAccessControlPoliciesActive(states) {
175
+ return (0, helpers_1.bindClientFunc)({
176
+ clientFunc: client_1.Client4.updateAccessControlPoliciesActive,
177
+ params: [states],
178
+ });
179
+ }
@@ -41,7 +41,7 @@ export declare function removeUserFromTeam(teamId: string, userId: string): Acti
41
41
  export declare function sendEmailInvitesToTeam(teamId: string, emails: string[]): ActionFuncAsync<import("@mattermost/types/client4").StatusOK, import("@mattermost/types/store").GlobalState, AnyAction>;
42
42
  export declare function sendEmailGuestInvitesToChannels(teamId: string, channelIds: string[], emails: string[], message: string): ActionFuncAsync<import("@mattermost/types/client4").StatusOK, import("@mattermost/types/store").GlobalState, AnyAction>;
43
43
  export declare function sendEmailInvitesToTeamGracefully(teamId: string, emails: string[]): ActionFuncAsync<import("@mattermost/types/teams").TeamInviteWithError[], import("@mattermost/types/store").GlobalState, AnyAction>;
44
- export declare function sendEmailGuestInvitesToChannelsGracefully(teamId: string, channelIds: string[], emails: string[], message: string): ActionFuncAsync<import("@mattermost/types/teams").TeamInviteWithError[], import("@mattermost/types/store").GlobalState, AnyAction>;
44
+ export declare function sendEmailGuestInvitesToChannelsGracefully(teamId: string, channelIds: string[], emails: string[], message: string, guestMagicLink?: boolean): ActionFuncAsync<import("@mattermost/types/teams").TeamInviteWithError[], import("@mattermost/types/store").GlobalState, AnyAction>;
45
45
  export declare function sendEmailInvitesToTeamAndChannelsGracefully(teamId: string, channelIds: string[], emails: string[], message: string): ActionFuncAsync<import("@mattermost/types/teams").TeamInviteWithError[], import("@mattermost/types/store").GlobalState, AnyAction>;
46
46
  export declare function getTeamInviteInfo(inviteId: string): ActionFuncAsync<{
47
47
  display_name: string;
@@ -544,7 +544,7 @@ function sendEmailInvitesToTeamGracefully(teamId, emails) {
544
544
  ],
545
545
  });
546
546
  }
547
- function sendEmailGuestInvitesToChannelsGracefully(teamId, channelIds, emails, message) {
547
+ function sendEmailGuestInvitesToChannelsGracefully(teamId, channelIds, emails, message, guestMagicLink = false) {
548
548
  return (0, helpers_1.bindClientFunc)({
549
549
  clientFunc: client_1.Client4.sendEmailGuestInvitesToChannelsGracefully,
550
550
  params: [
@@ -552,6 +552,7 @@ function sendEmailGuestInvitesToChannelsGracefully(teamId, channelIds, emails, m
552
552
  channelIds,
553
553
  emails,
554
554
  message,
555
+ guestMagicLink,
555
556
  ],
556
557
  });
557
558
  }
@@ -27,6 +27,7 @@ export declare const PostTypes: {
27
27
  REMINDER: PostType;
28
28
  WRANGLER: PostType;
29
29
  GM_CONVERTED_TO_CHANNEL: PostType;
30
+ BURN_ON_READ: PostType;
30
31
  };
31
32
  declare const _default: {
32
33
  POST_CHUNK_SIZE: number;
@@ -61,6 +62,7 @@ declare const _default: {
61
62
  REMINDER: PostType;
62
63
  WRANGLER: PostType;
63
64
  GM_CONVERTED_TO_CHANNEL: PostType;
65
+ BURN_ON_READ: PostType;
64
66
  };
65
67
  MESSAGE_TYPES: {
66
68
  POST: string;
@@ -70,5 +72,22 @@ declare const _default: {
70
72
  POST_COLLAPSE_TIMEOUT: number;
71
73
  IGNORE_POST_TYPES: PostType[];
72
74
  USER_ACTIVITY_POST_TYPES: PostType[];
75
+ BURN_ON_READ: {
76
+ DURATION_1_MINUTE: number;
77
+ DURATION_5_MINUTES: number;
78
+ DURATION_10_MINUTES: number;
79
+ DURATION_30_MINUTES: number;
80
+ DURATION_1_HOUR: number;
81
+ DURATION_8_HOURS: number;
82
+ DURATION_DEFAULT: number;
83
+ MAX_TTL_2_MINUTES: number;
84
+ MAX_TTL_5_MINUTES: number;
85
+ MAX_TTL_1_DAY: number;
86
+ MAX_TTL_3_DAYS: number;
87
+ MAX_TTL_7_DAYS: number;
88
+ MAX_TTL_14_DAYS: number;
89
+ MAX_TTL_30_DAYS: number;
90
+ MAX_TTL_DEFAULT: number;
91
+ };
73
92
  };
74
93
  export default _default;
@@ -31,6 +31,7 @@ exports.PostTypes = {
31
31
  REMINDER: 'reminder',
32
32
  WRANGLER: 'system_wrangler',
33
33
  GM_CONVERTED_TO_CHANNEL: 'system_gm_to_channel',
34
+ BURN_ON_READ: 'burn_on_read',
34
35
  };
35
36
  exports.default = {
36
37
  POST_CHUNK_SIZE: 60,
@@ -68,4 +69,21 @@ exports.default = {
68
69
  exports.PostTypes.LEAVE_TEAM,
69
70
  exports.PostTypes.REMOVE_FROM_TEAM,
70
71
  ],
72
+ BURN_ON_READ: {
73
+ DURATION_1_MINUTE: 60,
74
+ DURATION_5_MINUTES: 300,
75
+ DURATION_10_MINUTES: 600,
76
+ DURATION_30_MINUTES: 1800,
77
+ DURATION_1_HOUR: 3600,
78
+ DURATION_8_HOURS: 28800,
79
+ DURATION_DEFAULT: 600,
80
+ MAX_TTL_2_MINUTES: 120,
81
+ MAX_TTL_5_MINUTES: 300,
82
+ MAX_TTL_1_DAY: 86400,
83
+ MAX_TTL_3_DAYS: 259200,
84
+ MAX_TTL_7_DAYS: 604800,
85
+ MAX_TTL_14_DAYS: 1209600,
86
+ MAX_TTL_30_DAYS: 2592000,
87
+ MAX_TTL_DEFAULT: 604800,
88
+ },
71
89
  };
@@ -62,6 +62,8 @@ declare const Preferences: {
62
62
  HIDE_MYSQL_STATS_NOTIFICATION: string;
63
63
  CATEGORY_OVERAGE_USERS_BANNER: string;
64
64
  CATEGORY_POST_HISTORY_LIMIT_BANNER: string;
65
+ CATEGORY_BURN_ON_READ: string;
66
+ BURN_ON_READ_SKIP_CONFIRMATION: string;
65
67
  CATEGORY_THEME: string;
66
68
  THEMES: Record<ThemeKey, Theme>;
67
69
  RECENT_EMOJIS: string;
@@ -66,6 +66,8 @@ const Preferences = {
66
66
  HIDE_MYSQL_STATS_NOTIFICATION: 'hide_mysql_stats_notifcation',
67
67
  CATEGORY_OVERAGE_USERS_BANNER: 'overage_users_banner',
68
68
  CATEGORY_POST_HISTORY_LIMIT_BANNER: 'post_history_limit_banner',
69
+ CATEGORY_BURN_ON_READ: 'burn_on_read',
70
+ BURN_ON_READ_SKIP_CONFIRMATION: 'skip_delete_confirmation',
69
71
  CATEGORY_THEME: 'theme',
70
72
  THEMES: {
71
73
  denim: {
@@ -49,5 +49,8 @@ declare const WebsocketEvents: {
49
49
  THREAD_READ_CHANGED: string;
50
50
  FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: string;
51
51
  GROUP_MEMBER_DELETED: string;
52
+ BURN_ON_READ_POST_REVEALED: string;
53
+ BURN_ON_READ_POST_BURNED: string;
54
+ BURN_ON_READ_ALL_REVEALED: string;
52
55
  };
53
56
  export default WebsocketEvents;
@@ -53,5 +53,8 @@ const WebsocketEvents = {
53
53
  THREAD_READ_CHANGED: 'thread_read_changed',
54
54
  FIRST_ADMIN_VISIT_MARKETPLACE_STATUS_RECEIVED: 'first_admin_visit_marketplace_status_received',
55
55
  GROUP_MEMBER_DELETED: 'group_member_deleted',
56
+ BURN_ON_READ_POST_REVEALED: 'post_revealed',
57
+ BURN_ON_READ_POST_BURNED: 'post_burned',
58
+ BURN_ON_READ_ALL_REVEALED: 'burn_on_read_all_revealed',
56
59
  };
57
60
  exports.default = WebsocketEvents;
@@ -20,6 +20,7 @@ exports.limitedViews = limitedViews;
20
20
  exports.default = reducer;
21
21
  const action_types_1 = require("mattermost-redux/action_types");
22
22
  const constants_1 = require("mattermost-redux/constants");
23
+ const posts_1 = require("mattermost-redux/constants/posts");
23
24
  const post_utils_1 = require("mattermost-redux/utils/post_utils");
24
25
  function removeUnneededMetadata(post) {
25
26
  if (!post.metadata) {
@@ -151,6 +152,11 @@ function handlePosts(state = {}, action) {
151
152
  if (!state[post.id]) {
152
153
  return state;
153
154
  }
155
+ if (state[post.id].type === posts_1.PostTypes.BURN_ON_READ) {
156
+ const nextState = { ...state };
157
+ Reflect.deleteProperty(nextState, post.id);
158
+ return nextState;
159
+ }
154
160
  // Mark the post as deleted
155
161
  const nextState = {
156
162
  ...state,
@@ -229,6 +235,68 @@ function handlePosts(state = {}, action) {
229
235
  },
230
236
  };
231
237
  }
238
+ case action_types_1.PostTypes.REVEAL_BURN_ON_READ_SUCCESS: {
239
+ const { post, expireAt } = action.data;
240
+ if (!state[post.id]) {
241
+ return state;
242
+ }
243
+ const currentPost = state[post.id];
244
+ const currentMetadata = currentPost.metadata || {};
245
+ const newMetadata = post.metadata || {};
246
+ return {
247
+ ...state,
248
+ [post.id]: {
249
+ ...currentPost,
250
+ ...post,
251
+ metadata: {
252
+ ...currentMetadata,
253
+ ...newMetadata,
254
+ expire_at: expireAt,
255
+ },
256
+ },
257
+ };
258
+ }
259
+ case action_types_1.PostTypes.POST_RECIPIENTS_UPDATED: {
260
+ const { postId, recipients } = action.data;
261
+ if (!state[postId]) {
262
+ return state;
263
+ }
264
+ const currentPost = state[postId];
265
+ const currentMetadata = currentPost.metadata || {};
266
+ const currentRecipients = currentMetadata.recipients || [];
267
+ // Merge new recipients with existing ones (don't replace).
268
+ // Server sends incremental updates (only the revealing user), so we must merge.
269
+ const mergedRecipients = [...new Set([...currentRecipients, ...recipients])];
270
+ return {
271
+ ...state,
272
+ [postId]: {
273
+ ...currentPost,
274
+ metadata: {
275
+ ...currentMetadata,
276
+ recipients: mergedRecipients,
277
+ },
278
+ },
279
+ };
280
+ }
281
+ case action_types_1.PostTypes.BURN_ON_READ_ALL_REVEALED: {
282
+ const { postId, senderExpireAt } = action.data;
283
+ if (!state[postId]) {
284
+ return state;
285
+ }
286
+ const currentPost = state[postId];
287
+ const currentMetadata = currentPost.metadata || {};
288
+ // Set sender's expiration time to trigger timer display
289
+ return {
290
+ ...state,
291
+ [postId]: {
292
+ ...currentPost,
293
+ metadata: {
294
+ ...currentMetadata,
295
+ expire_at: senderExpireAt,
296
+ },
297
+ },
298
+ };
299
+ }
232
300
  case action_types_1.ChannelTypes.LEAVE_CHANNEL: {
233
301
  const channelId = action.data.id;
234
302
  let postDeleted = false;
@@ -605,17 +673,24 @@ function postsInChannel(state = {}, action, prevPosts, nextPosts) {
605
673
  }
606
674
  case action_types_1.PostTypes.POST_DELETED: {
607
675
  const post = action.data;
608
- // Deleting a post removes its comments from the order, but does not remove the post itself
609
676
  const postsForChannel = state[post.channel_id] || [];
610
677
  if (postsForChannel.length === 0) {
611
678
  return state;
612
679
  }
613
680
  let changed = false;
614
681
  let nextPostsForChannel = [...postsForChannel];
682
+ const isBoRPost = prevPosts[post.id]?.type === posts_1.PostTypes.BURN_ON_READ;
683
+ const shouldRemovePost = (postId) => {
684
+ const isTheDeletedPost = postId === post.id;
685
+ const isReplyToDeletedPost = prevPosts[postId]?.root_id === post.id;
686
+ if (isBoRPost) {
687
+ return isTheDeletedPost;
688
+ }
689
+ return isReplyToDeletedPost;
690
+ };
615
691
  for (let i = 0; i < nextPostsForChannel.length; i++) {
616
692
  const block = nextPostsForChannel[i];
617
- // Remove any comments for this post
618
- const nextOrder = block.order.filter((postId) => prevPosts[postId].root_id !== post.id);
693
+ const nextOrder = block.order.filter((postId) => !shouldRemovePost(postId));
619
694
  if (nextOrder.length !== block.order.length) {
620
695
  nextPostsForChannel[i] = {
621
696
  ...block,
@@ -642,11 +717,16 @@ function postsInChannel(state = {}, action, prevPosts, nextPosts) {
642
717
  return state;
643
718
  }
644
719
  let changed = false;
720
+ const isBoRPost = prevPosts[post.id]?.type === posts_1.PostTypes.BURN_ON_READ;
645
721
  // Remove the post and its comments from the channel
646
722
  let nextPostsForChannel = [...postsForChannel];
647
723
  for (let i = 0; i < nextPostsForChannel.length; i++) {
648
724
  const block = nextPostsForChannel[i];
649
- const nextOrder = block.order.filter((postId) => postId !== post.id && prevPosts[postId].root_id !== post.id);
725
+ // For BoR posts: only remove the post itself (BoR doesn't support threads)
726
+ // For regular posts: remove the post and its thread replies
727
+ const nextOrder = isBoRPost ?
728
+ block.order.filter((postId) => postId !== post.id) :
729
+ block.order.filter((postId) => postId !== post.id && prevPosts[postId]?.root_id !== post.id);
650
730
  if (nextOrder.length !== block.order.length) {
651
731
  nextPostsForChannel[i] = {
652
732
  ...block,
@@ -1,3 +1,2 @@
1
1
  import type { GlobalState } from '@mattermost/types/store';
2
- export declare const selectChannelBannerEnabled: (state: GlobalState) => boolean;
3
2
  export declare const selectShowChannelBanner: (state: GlobalState, channelId: string) => boolean;
@@ -2,21 +2,11 @@
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.selectShowChannelBanner = exports.selectChannelBannerEnabled = void 0;
5
+ exports.selectShowChannelBanner = void 0;
6
6
  const channels_1 = require("@mattermost/types/channels");
7
7
  const constants_1 = require("mattermost-redux/constants");
8
8
  const channels_2 = require("mattermost-redux/selectors/entities/channels");
9
- const general_1 = require("mattermost-redux/selectors/entities/general");
10
- const selectChannelBannerEnabled = (state) => {
11
- const license = (0, general_1.getLicense)(state);
12
- return license?.SkuShortName === constants_1.General.SKUEnterpriseAdvanced;
13
- };
14
- exports.selectChannelBannerEnabled = selectChannelBannerEnabled;
15
9
  const selectShowChannelBanner = (state, channelId) => {
16
- const enabled = (0, exports.selectChannelBannerEnabled)(state);
17
- if (!enabled) {
18
- return false;
19
- }
20
10
  const channelBannerInfo = (0, channels_2.getChannelBanner)(state, channelId);
21
11
  const channel = (0, channels_2.getChannel)(state, channelId);
22
12
  const isValidChannelType = Boolean(channel && (channel.type === constants_1.General.OPEN_CHANNEL || channel.type === constants_1.General.PRIVATE_CHANNEL));
@@ -132,7 +132,8 @@ function isPostInteractable(post) {
132
132
  !(0, post_utils_1.isPostEphemeral)(post) &&
133
133
  !(0, post_utils_1.isSystemMessage)(post) &&
134
134
  !(0, post_utils_1.isPostPendingOrFailed)(post) &&
135
- post.state !== constants_1.Posts.POST_DELETED;
135
+ post.state !== constants_1.Posts.POST_DELETED &&
136
+ post.type !== constants_1.Posts.POST_TYPES.BURN_ON_READ;
136
137
  }
137
138
  function getLatestInteractablePostId(state, channelId, rootId = '') {
138
139
  const postsIds = rootId ? getPostsInThreadOrdered(state, rootId) : getPostIdsInChannel(state, channelId);
@@ -398,7 +399,13 @@ function getUnreadPostsChunk(state, channelId, timeStamp) {
398
399
  return oldestPostsChunk;
399
400
  }
400
401
  }
401
- return getPostsChunkInChannelAroundTime(state, channelId, timeStamp);
402
+ // Try to find a chunk where lastViewedAt falls within the post range
403
+ const chunkAroundTime = getPostsChunkInChannelAroundTime(state, channelId, timeStamp);
404
+ if (chunkAroundTime) {
405
+ return chunkAroundTime;
406
+ }
407
+ // All fetched posts are newer than lastViewedAt. Return the recent chunk.
408
+ return recentChunk;
402
409
  }
403
410
  const isPostsChunkIncludingUnreadsPosts = (state, chunk, timeStamp) => {
404
411
  const postsEntity = state.entities.posts;
@@ -73,6 +73,16 @@ function makeFilterPostsAndAddSeparators() {
73
73
  if ((0, post_utils_1.shouldFilterJoinLeavePost)(post, showJoinLeave, currentUser.username)) {
74
74
  continue;
75
75
  }
76
+ // Filter out expired burn-on-read posts
77
+ // Note: BoR posts should display regardless of feature flag being enabled/disabled
78
+ // The feature flag only controls creation of NEW BoR messages, not display of existing ones
79
+ if (post.type === constants_1.Posts.POST_TYPES.BURN_ON_READ) {
80
+ // Skip if already expired and deleted
81
+ const expireAt = post.metadata?.expire_at;
82
+ if (expireAt && typeof expireAt === 'number' && expireAt <= Date.now()) {
83
+ continue;
84
+ }
85
+ }
76
86
  lastDate = pushPostDateIfNeeded(post, currentUser, out, lastDate);
77
87
  if (lastViewedAt &&
78
88
  post.create_at > lastViewedAt &&
@@ -62,6 +62,9 @@ function canEditPost(state, config, license, teamId, channelId, userId, post) {
62
62
  if (!post || isSystemMessage(post)) {
63
63
  return false;
64
64
  }
65
+ if (post.type === constants_1.Posts.POST_TYPES.BURN_ON_READ) {
66
+ return false;
67
+ }
65
68
  const isOwner = isPostOwner(userId, post);
66
69
  let canEdit = true;
67
70
  const permission = isOwner ? constants_1.Permissions.EDIT_POST : constants_1.Permissions.EDIT_OTHERS_POSTS;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-redux",
3
- "version": "11.2.0",
3
+ "version": "11.3.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.2.0",
43
- "@mattermost/types": "11.2.0",
42
+ "@mattermost/client": "11.3.0",
43
+ "@mattermost/types": "11.3.0",
44
44
  "@redux-devtools/extension": "3.3.0",
45
45
  "lodash": "^4.17.21",
46
46
  "moment-timezone": "^0.5.38",