diff --git a/app/components/post_list/post/header/header.tsx b/app/components/post_list/post/header/header.tsx
index cc9f6f7acf..cc2bc8293b 100644
--- a/app/components/post_list/post/header/header.tsx
+++ b/app/components/post_list/post/header/header.tsx
@@ -6,6 +6,7 @@ import {View} from 'react-native';
import CustomStatusEmoji from '@components/custom_status/custom_status_emoji';
import FormattedTime from '@components/formatted_time';
+import PostPriorityLabel from '@components/post_priority/post_priority_label';
import {CHANNEL, THREAD} from '@constants/screens';
import {useTheme} from '@context/theme';
import {postUserDisplayName} from '@utils/post';
@@ -31,6 +32,7 @@ type HeaderProps = {
isEphemeral: boolean;
isMilitaryTime: boolean;
isPendingOrFailed: boolean;
+ isPostPriorityEnabled: boolean;
isSystemPost: boolean;
isTimezoneEnabled: boolean;
isWebHook: boolean;
@@ -58,9 +60,12 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
color: theme.centerChannelColor,
marginTop: 5,
opacity: 0.5,
- flex: 1,
...typography('Body', 75, 'Regular'),
},
+ postPriority: {
+ alignSelf: 'center',
+ marginLeft: 6,
+ },
customStatusEmoji: {
color: theme.centerChannelColor,
marginRight: 4,
@@ -72,7 +77,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const Header = (props: HeaderProps) => {
const {
author, commentCount = 0, currentUser, enablePostUsernameOverride, isAutoResponse, isCRTEnabled,
- isEphemeral, isMilitaryTime, isPendingOrFailed, isSystemPost, isTimezoneEnabled, isWebHook,
+ isEphemeral, isMilitaryTime, isPendingOrFailed, isPostPriorityEnabled, isSystemPost, isTimezoneEnabled, isWebHook,
location, post, rootPostAuthor, shouldRenderReplyButton, teammateNameDisplay,
} = props;
const theme = useTheme();
@@ -126,6 +131,13 @@ const Header = (props: HeaderProps) => {
style={style.time}
testID='post_header.date_time'
/>
+ {Boolean(isPostPriorityEnabled && post.props?.priority) && (
+
+
+
+ )}
{!isCRTEnabled && showReply && commentCount > 0 &&
{
justifyContent: 'flex-end',
minWidth: 40,
paddingTop: 2,
- paddingBottom: 10,
flex: 1,
},
replyText: {
diff --git a/app/components/post_list/post/index.ts b/app/components/post_list/post/index.ts
index 7642409457..5e8343b699 100644
--- a/app/components/post_list/post/index.ts
+++ b/app/components/post_list/post/index.ts
@@ -12,7 +12,7 @@ import {queryAllCustomEmojis} from '@queries/servers/custom_emoji';
import {queryPostsBetween} from '@queries/servers/post';
import {queryPreferencesByCategoryAndName} from '@queries/servers/preference';
import {observeCanManageChannelMembers, observePermissionForPost} from '@queries/servers/role';
-import {observeConfigBooleanValue} from '@queries/servers/system';
+import {observeIsPostPriorityEnabled, observeConfigBooleanValue} from '@queries/servers/system';
import {observeThreadById} from '@queries/servers/thread';
import {observeCurrentUser} from '@queries/servers/user';
import {hasJumboEmojiOnly} from '@utils/emoji/helpers';
@@ -168,6 +168,7 @@ const withPost = withObservables(
isJumboEmoji,
isLastReply,
isPostAddChannelMember,
+ isPostPriorityEnabled: observeIsPostPriorityEnabled(database),
post: post.observe(),
thread: isCRTEnabled ? observeThreadById(database, post.id) : of$(undefined),
hasReactions,
diff --git a/app/components/post_list/post/post.tsx b/app/components/post_list/post/post.tsx
index 970608cece..0035bee1cb 100644
--- a/app/components/post_list/post/post.tsx
+++ b/app/components/post_list/post/post.tsx
@@ -53,6 +53,7 @@ type PostProps = {
isJumboEmoji: boolean;
isLastReply?: boolean;
isPostAddChannelMember: boolean;
+ isPostPriorityEnabled: boolean;
location: string;
post: PostModel;
rootId?: string;
@@ -107,7 +108,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
const Post = ({
appsEnabled, canDelete, currentUser, differentThreadSequence, hasFiles, hasReplies, highlight, highlightPinnedOrSaved = true, highlightReplyBar,
- isCRTEnabled, isConsecutivePost, isEphemeral, isFirstReply, isSaved, isJumboEmoji, isLastReply, isPostAddChannelMember,
+ isCRTEnabled, isConsecutivePost, isEphemeral, isFirstReply, isSaved, isJumboEmoji, isLastReply, isPostAddChannelMember, isPostPriorityEnabled,
location, post, rootId, hasReactions, searchPatterns, shouldRenderReplyButton, skipSavedHeader, skipPinnedHeader, showAddReaction = true, style,
testID, thread, previousPost,
}: PostProps) => {
@@ -221,8 +222,9 @@ const Post = ({
let header: ReactNode;
let postAvatar: ReactNode;
let consecutiveStyle: StyleProp;
- const sameSecuence = hasReplies ? (hasReplies && post.rootId) : !post.rootId;
- if (hasSameRoot && isConsecutivePost && sameSecuence) {
+ const isProrityPost = isPostPriorityEnabled && post.props.priority;
+ const sameSequence = hasReplies ? (hasReplies && post.rootId) : !post.rootId;
+ if (!isProrityPost && hasSameRoot && isConsecutivePost && sameSequence) {
consecutiveStyle = styles.consective;
postAvatar = ;
} else {
@@ -254,6 +256,7 @@ const Post = ({
differentThreadSequence={differentThreadSequence}
isAutoResponse={isAutoResponder}
isCRTEnabled={isCRTEnabled}
+ isPostPriorityEnabled={isPostPriorityEnabled}
isEphemeral={isEphemeral}
isPendingOrFailed={isPendingOrFailed}
isSystemPost={isSystemPost}
diff --git a/app/components/post_priority/post_priority_label.tsx b/app/components/post_priority/post_priority_label.tsx
new file mode 100644
index 0000000000..8eb35505b1
--- /dev/null
+++ b/app/components/post_priority/post_priority_label.tsx
@@ -0,0 +1,67 @@
+// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
+// See LICENSE.txt for license information.
+
+import React from 'react';
+import {useIntl} from 'react-intl';
+import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native';
+
+import CompassIcon from '@components/compass_icon';
+import {PostPriorityTypes} from '@constants/post';
+import {typography} from '@utils/typography';
+
+const style = StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ borderRadius: 4,
+ alignItems: 'center',
+ height: 16,
+ paddingHorizontal: 4,
+ },
+ urgent: {
+ backgroundColor: '#D24B4E',
+ },
+ important: {
+ backgroundColor: '#5D89EA',
+ },
+ label: {
+ color: '#fff',
+ ...typography('Body', 25, 'SemiBold'),
+ },
+ icon: {
+ color: '#fff',
+ fontSize: 12,
+ marginRight: 4,
+ },
+});
+
+type Props = {
+ label: string;
+};
+
+const PostPriorityLabel = ({label}: Props) => {
+ const intl = useIntl();
+
+ const containerStyle: StyleProp = [style.container];
+ let iconName = '';
+ let labelText = '';
+ if (label === PostPriorityTypes.URGENT) {
+ containerStyle.push(style.urgent);
+ iconName = 'alert-outline';
+ labelText = intl.formatMessage({id: 'post_priority.label.urgent', defaultMessage: 'URGENT'});
+ } else {
+ containerStyle.push(style.important);
+ iconName = 'alert-circle-outline';
+ labelText = intl.formatMessage({id: 'post_priority.label.important', defaultMessage: 'IMPORTANT'});
+ }
+ return (
+
+
+ {labelText}
+
+ );
+};
+
+export default PostPriorityLabel;
diff --git a/app/constants/post.ts b/app/constants/post.ts
index bcbc86af5a..0363368581 100644
--- a/app/constants/post.ts
+++ b/app/constants/post.ts
@@ -34,11 +34,17 @@ export const PostTypes: Record = {
CUSTOM_CALLS: 'custom_calls',
};
+export const PostPriorityTypes: Record = {
+ URGENT: 'urgent',
+ IMPORTANT: 'important',
+};
+
export const POST_TIME_TO_FAIL = 10000;
export default {
POST_COLLAPSE_TIMEOUT: 1000 * 60 * 5,
POST_TYPES: PostTypes,
+ POST_PRIORITY_TYPES: PostPriorityTypes,
USER_ACTIVITY_POST_TYPES: [
PostTypes.ADD_TO_CHANNEL,
PostTypes.JOIN_CHANNEL,
diff --git a/app/queries/servers/system.ts b/app/queries/servers/system.ts
index 62ae4faabc..1542ee7c29 100644
--- a/app/queries/servers/system.ts
+++ b/app/queries/servers/system.ts
@@ -5,7 +5,7 @@ import {Database, Q} from '@nozbe/watermelondb';
import {of as of$, Observable} from 'rxjs';
import {switchMap} from 'rxjs/operators';
-import {Preferences} from '@constants';
+import {Config, Preferences} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {PUSH_PROXY_STATUS_UNKNOWN} from '@constants/push_proxy';
@@ -167,6 +167,15 @@ export const observeConfigIntValue = (database: Database, key: keyof ClientConfi
);
};
+export const observeIsPostPriorityEnabled = (database: Database) => {
+ const config = observeConfig(database);
+ return config.pipe(
+ switchMap(
+ (cfg) => of$(cfg?.FeatureFlagPostPriority === Config.TRUE && cfg?.PostPriority === Config.TRUE),
+ ),
+ );
+};
+
export const observeLicense = (database: Database): Observable => {
return querySystemValue(database, SYSTEM_IDENTIFIERS.LICENSE).observe().pipe(
switchMap((result) => (result.length ? result[0].observe() : of$({value: undefined}))),
diff --git a/assets/base/i18n/en.json b/assets/base/i18n/en.json
index 46b7f266dd..956f7520ca 100644
--- a/assets/base/i18n/en.json
+++ b/assets/base/i18n/en.json
@@ -701,6 +701,8 @@
"post_info.system": "System",
"post_message_view.edited": "(edited)",
"post.options.title": "Options",
+ "post_priority.label.important": "IMPORTANT",
+ "post_priority.label.urgent": "URGENT",
"post.reactions.title": "Reactions",
"posts_view.newMsg": "New Messages",
"public_link_copied": "Link copied to clipboard",
diff --git a/types/api/config.d.ts b/types/api/config.d.ts
index 606bf272e0..277dcf31a9 100644
--- a/types/api/config.d.ts
+++ b/types/api/config.d.ts
@@ -119,6 +119,7 @@ interface ClientConfig {
FeatureFlagAppsEnabled?: string;
FeatureFlagCollapsedThreads?: string;
FeatureFlagGraphQL?: string;
+ FeatureFlagPostPriority?: string;
GfycatApiKey: string;
GfycatApiSecret: string;
GoogleDeveloperKey: string;
@@ -151,6 +152,7 @@ interface ClientConfig {
PasswordRequireUppercase: string;
PluginsEnabled: string;
PostEditTimeLimit: string;
+ PostPriority: string;
PrivacyPolicyLink: string;
ReportAProblemLink: string;
RequireEmailVerification: string;