mattermost-redux
Version:
Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client
231 lines (191 loc) • 8.53 kB
text/typescript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {General, Posts, Preferences, Permissions} from '../constants';
import {hasNewPermissions} from 'selectors/entities/general';
import {haveIChannelPermission} from 'selectors/entities/roles';
import {GlobalState} from 'types/store';
import {PreferenceType} from 'types/preferences';
import {Post, PostType, PostMetadata, PostEmbed} from 'types/posts';
import {UserProfile} from 'types/users';
import {Team} from 'types/teams';
import {Channel} from 'types/channels';
import {$ID} from 'types/utilities';
import {getPreferenceKey} from './preference_utils';
export function isPostFlagged(postId: $ID<Post>, myPreferences: {
[x: string]: PreferenceType;
}): boolean {
const key = getPreferenceKey(Preferences.CATEGORY_FLAGGED_POST, postId);
return myPreferences.hasOwnProperty(key);
}
export function isSystemMessage(post: Post): boolean {
return Boolean(post.type && post.type.startsWith(Posts.SYSTEM_MESSAGE_PREFIX));
}
export function isMeMessage(post: Post): boolean {
return Boolean(post.type && post.type === Posts.POST_TYPES.ME);
}
export function isFromWebhook(post: Post): boolean {
return post.props && post.props.from_webhook;
}
export function isPostEphemeral(post: Post): boolean {
return post.type === Posts.POST_TYPES.EPHEMERAL || post.type === Posts.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL || post.state === Posts.POST_DELETED;
}
export function shouldIgnorePost(post: Post, userId?: $ID<UserProfile>): boolean {
const postTypeCheck = post.type && (post.type === Posts.POST_TYPES.ADD_TO_CHANNEL);
const userIdCheck = post.props && post.props.addedUserId && (post.props.addedUserId === userId);
if (postTypeCheck && userIdCheck) {
return false;
}
return Posts.IGNORE_POST_TYPES.includes(post.type);
}
export function isUserActivityPost(postType: PostType): boolean {
return Posts.USER_ACTIVITY_POST_TYPES.includes(postType);
}
export function isPostOwner(userId: $ID<UserProfile>, post: Post) {
return userId === post.user_id;
}
export function isEdited(post: Post): boolean {
return post.edit_at > 0;
}
export function canDeletePost(state: GlobalState, config: any, license: any, teamId: $ID<Team>, channelId: $ID<Channel>, userId: $ID<UserProfile>, post: Post, isAdmin: boolean, isSystemAdmin: boolean): boolean {
if (!post) {
return false;
}
const isOwner = isPostOwner(userId, post);
if (hasNewPermissions(state)) {
const canDelete = haveIChannelPermission(state, {team: teamId, channel: channelId, permission: Permissions.DELETE_POST});
if (!isOwner) {
return canDelete && haveIChannelPermission(state, {team: teamId, channel: channelId, permission: Permissions.DELETE_OTHERS_POSTS});
}
return canDelete;
}
// Backwards compatibility with pre-advanced permissions config settings.
if (license.IsLicensed === 'true') {
return (config.RestrictPostDelete === General.PERMISSIONS_ALL && (isOwner || isAdmin)) ||
(config.RestrictPostDelete === General.PERMISSIONS_TEAM_ADMIN && isAdmin) ||
(config.RestrictPostDelete === General.PERMISSIONS_SYSTEM_ADMIN && isSystemAdmin);
}
return isOwner || isAdmin;
}
export function canEditPost(state: GlobalState, config: any, license: any, teamId: $ID<Team>, channelId: $ID<Channel>, userId: $ID<UserProfile>, post: Post): boolean {
if (!post || isSystemMessage(post)) {
return false;
}
const isOwner = isPostOwner(userId, post);
let canEdit = true;
if (hasNewPermissions(state)) {
const permission = isOwner ? Permissions.EDIT_POST : Permissions.EDIT_OTHERS_POSTS;
canEdit = haveIChannelPermission(state, {team: teamId, channel: channelId, permission});
if (license.IsLicensed === 'true' && config.PostEditTimeLimit !== '-1' && config.PostEditTimeLimit !== -1) {
const timeLeft = (post.create_at + (config.PostEditTimeLimit * 1000)) - Date.now();
if (timeLeft <= 0) {
canEdit = false;
}
}
} else {
// Backwards compatibility with pre-advanced permissions config settings.
canEdit = isOwner && config.AllowEditPost !== 'never';
if (config.AllowEditPost === General.ALLOW_EDIT_POST_TIME_LIMIT) {
const timeLeft = (post.create_at + (config.PostEditTimeLimit * 1000)) - Date.now();
if (timeLeft <= 0) {
canEdit = false;
}
}
}
return canEdit;
}
export function getLastCreateAt(postsArray: Post[]): number {
const createAt = postsArray.map((p) => p.create_at);
if (createAt.length) {
return Reflect.apply(Math.max, null, createAt);
}
return 0;
}
const joinLeavePostTypes = [
Posts.POST_TYPES.JOIN_LEAVE,
Posts.POST_TYPES.JOIN_CHANNEL,
Posts.POST_TYPES.LEAVE_CHANNEL,
Posts.POST_TYPES.ADD_REMOVE,
Posts.POST_TYPES.ADD_TO_CHANNEL,
Posts.POST_TYPES.REMOVE_FROM_CHANNEL,
Posts.POST_TYPES.JOIN_TEAM,
Posts.POST_TYPES.LEAVE_TEAM,
Posts.POST_TYPES.ADD_TO_TEAM,
Posts.POST_TYPES.REMOVE_FROM_TEAM,
Posts.POST_TYPES.COMBINED_USER_ACTIVITY,
];
// Returns true if a post should be hidden when the user has Show Join/Leave Messages disabled
export function shouldFilterJoinLeavePost(post: Post, showJoinLeave: boolean, currentUsername: string): boolean {
if (showJoinLeave) {
return false;
}
// Don't filter out non-join/leave messages
if (joinLeavePostTypes.indexOf(post.type) === -1) {
return false;
}
// Don't filter out join/leave messages about the current user
return !isJoinLeavePostForUsername(post, currentUsername);
}
function isJoinLeavePostForUsername(post: Post, currentUsername: string): boolean {
if (!post.props || !currentUsername) {
return false;
}
if (post.user_activity_posts) {
for (const childPost of post.user_activity_posts) {
if (isJoinLeavePostForUsername(childPost, currentUsername)) {
// If any of the contained posts are for this user, the client will
// need to figure out how to render the post
return true;
}
}
}
return post.props.username === currentUsername ||
post.props.addedUsername === currentUsername ||
post.props.removedUsername === currentUsername;
}
export function isPostPendingOrFailed(post: Post): boolean {
return post.failed || post.id === post.pending_post_id;
}
export function comparePosts(a: Post, b: Post): number {
const aIsPendingOrFailed = isPostPendingOrFailed(a);
const bIsPendingOrFailed = isPostPendingOrFailed(b);
if (aIsPendingOrFailed && !bIsPendingOrFailed) {
return -1;
} else if (!aIsPendingOrFailed && bIsPendingOrFailed) {
return 1;
}
if (a.create_at > b.create_at) {
return -1;
} else if (a.create_at < b.create_at) {
return 1;
}
return 0;
}
export function isPostCommentMention({post, currentUser, threadRepliedToByCurrentUser, rootPost}: {post: Post; currentUser: UserProfile; threadRepliedToByCurrentUser: boolean; rootPost: Post}): boolean {
let commentsNotifyLevel = Preferences.COMMENTS_NEVER;
let isCommentMention = false;
let threadCreatedByCurrentUser = false;
if (rootPost && rootPost.user_id === currentUser.id) {
threadCreatedByCurrentUser = true;
}
if (currentUser.notify_props && currentUser.notify_props.comments) {
commentsNotifyLevel = currentUser.notify_props.comments;
}
const notCurrentUser = post.user_id !== currentUser.id || (post.props && post.props.from_webhook);
if (notCurrentUser) {
if (commentsNotifyLevel === Preferences.COMMENTS_ANY && (threadCreatedByCurrentUser || threadRepliedToByCurrentUser)) {
isCommentMention = true;
} else if (commentsNotifyLevel === Preferences.COMMENTS_ROOT && threadCreatedByCurrentUser) {
isCommentMention = true;
}
}
return isCommentMention;
}
export function fromAutoResponder(post: Post): boolean {
return Boolean(post.type && (post.type === Posts.SYSTEM_AUTO_RESPONDER));
}
export function getEmbedFromMetadata(metadata: PostMetadata): PostEmbed | null {
if (!metadata || !metadata.embeds || metadata.embeds.length === 0) {
return null;
}
return metadata.embeds[0];
}