MM-41748 Gekidou Post Options Queries (#5972)

* skeleton in place

* fix ts error

* creating base option component

* Added all options except reaction

* moved options under /component/options

* added destructive styling

* skeleton - need polishing now

* default emojis for quick reaction

* rename files and small refactor

* Properly close bottom sheet

* redid reaction component

* canSave, isSaved

* canAddReaction condition

* fix aligment

* code clean up

* fix opening on tablet

* undo comment on local reaction action

* undo needless formatting

* clean up comment

* shows selected reaction

* fix marginTop and added title for Tablet

* code clean up

* investigating navigation

* fixed navigation

* Post options bottomSheet and renamed DrawerItem to MenuItem

* renamed optionType to testID

* update navigation_close_modal to close_bottom

* removed context in favor of Pressable

* Apply suggestions from code review

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>

* removed theme prop from PickReaction

* en.json and code fixes

* removed post_options from screen/index

* removed post_options from screens constant

* Revert "removed post_options from screen/index"

This reverts commit 24caa9773f.

* Revert "removed post_options from screens constant"

This reverts commit 863e2faaf7.

* fix theme import

* remove useless margin

* disabled post_options

* queries - work in progress

* queries - work in progress

* queries - work in progress

* minor fix

* queries - work in progress

* queries - work in progress

* queries - work in progress

* queries - work in progress

* queries - work in progress

* fix query

* queries - work in progress

* reaction query fixed

* queries - work in progress

* queries - canReaction option

* queries - canDelete option

* queries - canReply option

* queries - canPin, canSave, canCopyPermalink option

* queries - options - wip

* queries - options - wip

* queries - options - wip

* fix location

* removed logs

* undo post_draft changes

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Avinash Lingaloo
2022-02-16 14:21:27 +04:00
committed by GitHub
parent df5c780337
commit ea54a8dff3
7 changed files with 162 additions and 51 deletions

View File

@@ -101,7 +101,7 @@ const withPost = withObservables(
const canDelete = from$(hasPermissionForPost(post, currentUser, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false));
const isEphemeral = of$(isPostEphemeral(post));
const isFlagged = database.get<PreferenceModel>(PREFERENCE).query(
Q.where('category', Preferences.CATEGORY_FLAGGED_POST),
Q.where('category', Preferences.CATEGORY_SAVED_POST),
Q.where('name', post.id),
).observe().pipe(switchMap((pref) => of$(Boolean(pref.length))));

View File

@@ -165,8 +165,7 @@ const Post = ({
}
Keyboard.dismiss();
const passProps = {location, post};
const passProps = {location, post, showAddReaction};
const title = isTablet ? intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}) : '';
if (isTablet) {

View File

@@ -7,7 +7,7 @@ const Preferences: Record<string, any> = {
CATEGORY_DIRECT_CHANNEL_SHOW: 'direct_channel_show',
CATEGORY_GROUP_CHANNEL_SHOW: 'group_channel_show',
CATEGORY_EMOJI: 'emoji',
CATEGORY_FLAGGED_POST: 'flagged_post',
CATEGORY_SAVED_POST: 'flagged_post',
CATEGORY_FAVORITE_CHANNEL: 'favorite_channel',
CATEGORY_AUTO_RESET_MANUAL_STATUS: 'auto_reset_manual_status',
CATEGORY_NOTIFICATIONS: 'notifications',

View File

@@ -42,20 +42,13 @@ const BaseOption = ({
const theme = useTheme();
const styles = getStyleSheet(theme);
const labelStyles = useMemo(() => {
if (isDestructive) {
return [styles.label, styles.destructive];
}
return styles.label;
}, [isDestructive, styles.label, styles.destructive]);
const label = useMemo(() => (
<FormattedText
id={i18nId}
defaultMessage={defaultMessage}
style={labelStyles}
style={[styles.label, isDestructive && styles.destructive]}
/>
), [i18nId, defaultMessage, labelStyles]);
), [i18nId, defaultMessage, theme]);
return (
<MenuItem

View File

@@ -1,6 +1,134 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Q} from '@nozbe/watermelondb';
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
import withObservables from '@nozbe/with-observables';
import {combineLatest, from as from$, of as of$} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {General, Permissions, Preferences, Screens} from '@constants';
import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database';
import {MAX_ALLOWED_REACTIONS} from '@constants/emoji';
import {isMinimumServerVersion} from '@utils/helpers';
import {isSystemMessage} from '@utils/post';
import {hasPermissionForChannel, hasPermissionForPost} from '@utils/role';
import {isSystemAdmin} from '@utils/user';
import PostOptions from './post_options';
export default PostOptions;
import type {WithDatabaseArgs} from '@typings/database/database';
import type ChannelModel from '@typings/database/models/servers/channel';
import type PostModel from '@typings/database/models/servers/post';
import type PreferenceModel from '@typings/database/models/servers/preference';
import type ReactionModel from '@typings/database/models/servers/reaction';
import type SystemModel from '@typings/database/models/servers/system';
import type UserModel from '@typings/database/models/servers/user';
const {USER, SYSTEM, PREFERENCE} = MM_TABLES.SERVER;
const {CURRENT_USER_ID, LICENSE, CONFIG} = SYSTEM_IDENTIFIERS;
const canEditPost = (isOwner: boolean, post: PostModel, postEditTimeLimit: number, isLicensed: boolean, channel: ChannelModel, user: UserModel): boolean => {
if (!post || isSystemMessage(post)) {
return false;
}
let cep: boolean;
const permissions = [Permissions.EDIT_POST];
if (!isOwner) {
permissions.push(Permissions.EDIT_OTHERS_POSTS);
}
cep = permissions.every((permission) => hasPermissionForChannel(channel, user, permission, false));
if (isLicensed && postEditTimeLimit !== -1) {
const timeLeft = (post.createAt + (postEditTimeLimit * 1000)) - Date.now();
if (timeLeft <= 0) {
cep = false;
}
}
return cep;
};
const enhanced = withObservables([], ({post, showAddReaction, location, database}: WithDatabaseArgs & { post: PostModel; showAddReaction: boolean; location: string }) => {
const channel = post.channel.observe();
const channelIsArchived = channel.pipe(switchMap((ch: ChannelModel) => of$(ch.deleteAt !== 0)));
const currentUser = database.get<SystemModel>(SYSTEM).findAndObserve(CURRENT_USER_ID).pipe(switchMap(({value}) => database.get<UserModel>(USER).findAndObserve(value)));
const config = database.get<SystemModel>(SYSTEM).findAndObserve(CONFIG).pipe(switchMap(({value}) => of$(value as ClientConfig)));
const isLicensed = database.get<SystemModel>(SYSTEM).findAndObserve(LICENSE).pipe(switchMap(({value}) => of$(value.IsLicensed === 'true')));
const allowEditPost = config.pipe(switchMap((cfg) => of$(cfg.AllowEditPost)));
const serverVersion = config.pipe(switchMap((cfg) => cfg.Version));
const postEditTimeLimit = config.pipe(switchMap((cfg) => of$(parseInt(cfg.PostEditTimeLimit || '-1', 10))));
const canPostPermission = combineLatest([channel, currentUser]).pipe(switchMap(([c, u]) => from$(hasPermissionForChannel(c, u, Permissions.CREATE_POST, false))));
const hasAddReactionPermission = currentUser.pipe(switchMap((u) => from$(hasPermissionForPost(post, u, Permissions.ADD_REACTION, true))));
const canDeletePostPermission = currentUser.pipe(switchMap((u) => {
const isOwner = post.userId === u.id;
return from$(hasPermissionForPost(post, u, isOwner ? Permissions.DELETE_POST : Permissions.DELETE_OTHERS_POSTS, false));
}));
const experimentalTownSquareIsReadOnly = config.pipe(switchMap((value) => of$(value.ExperimentalTownSquareIsReadOnly === 'true')));
const channelIsReadOnly = combineLatest([currentUser, channel, experimentalTownSquareIsReadOnly]).pipe(switchMap(([u, c, readOnly]) => {
return of$(c?.name === General.DEFAULT_CHANNEL && !isSystemAdmin(u.roles) && readOnly);
}));
const isUnderMaxAllowedReactions = post.reactions.observe().pipe(
// eslint-disable-next-line max-nested-callbacks
switchMap((reactions: ReactionModel[]) => of$(new Set(reactions.map((r) => r.emojiName)).size < MAX_ALLOWED_REACTIONS)),
);
const canEditUntil = combineLatest([isLicensed, allowEditPost, postEditTimeLimit, serverVersion, channelIsArchived, channelIsReadOnly]).pipe(
switchMap(([ls, alw, limit, semVer, isArchived, isReadOnly]) => {
if (!isArchived && !isReadOnly && ls && ((alw === Permissions.ALLOW_EDIT_POST_TIME_LIMIT && !isMinimumServerVersion(semVer, 6)) || (limit !== -1))) {
return of$(post.createAt + (limit * (1000)));
}
return of$(-1);
}),
);
const canReply = combineLatest([channelIsArchived, channelIsReadOnly, canPostPermission]).pipe(switchMap(([isArchived, isReadOnly, canPost]) => {
return of$(!isArchived && !isReadOnly && location !== Screens.THREAD && !isSystemMessage(post) && canPost);
}));
const canPin = combineLatest([channelIsArchived, channelIsReadOnly]).pipe(switchMap(([isArchived, isReadOnly]) => {
return of$(!isSystemMessage(post) && !isArchived && !isReadOnly);
}));
const isSaved = database.get<PreferenceModel>(PREFERENCE).query(Q.where('category', Preferences.CATEGORY_SAVED_POST), Q.where('name', post.id)).observe().pipe(switchMap((pref) => of$(Boolean(pref[0]?.value === 'true'))));
const canEdit = combineLatest([postEditTimeLimit, isLicensed, channel, currentUser, channelIsArchived, channelIsReadOnly, canEditUntil, canPostPermission]).pipe(switchMap(([lt, ls, c, u, isArchived, isReadOnly, until, canPost]) => {
const isOwner = u.id === post.userId;
const canEditPostPermission = canEditPost(isOwner, post, lt, ls, c, u);
const timeReached = until === -1 || until > Date.now();
return of$(canEditPostPermission && isSystemMessage(post) && !isArchived && !isReadOnly && !timeReached && canPost);
}));
const canMarkAsUnread = combineLatest([currentUser, channelIsArchived]).pipe(
switchMap(([user, isArchived]) => of$(!isArchived && user.id !== post.userId && !isSystemMessage(post))),
);
const canAddReaction = combineLatest([hasAddReactionPermission, channelIsReadOnly, isUnderMaxAllowedReactions, channelIsArchived]).pipe(
switchMap(([permission, readOnly, maxAllowed, isArchived]) => {
return of$(!isSystemMessage(post) && permission && !readOnly && !isArchived && maxAllowed && showAddReaction);
}),
);
const canDelete = combineLatest([canDeletePostPermission, channelIsArchived, channelIsReadOnly, canPostPermission]).pipe(switchMap(([permission, isArchived, isReadOnly, canPost]) => {
return of$(permission && !isArchived && !isReadOnly && canPost);
}));
return {
canMarkAsUnread,
canAddReaction,
canDelete,
canReply,
canPin,
isSaved,
canEdit,
post,
};
});
export default withDatabase(enhanced(PostOptions));

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useManagedConfig} from '@mattermost/react-native-emm';
import React from 'react';
import {ITEM_HEIGHT} from '@components/menu_item';
@@ -21,48 +22,43 @@ import ReactionBar from './components/reaction_bar';
import type PostModel from '@typings/database/models/servers/post';
//fixme: some props are optional - review them
type PostOptionsProps = {
canAddReaction?: boolean;
canCopyPermalink?: boolean;
canCopyText?: boolean;
canDelete?: boolean;
canEdit?: boolean;
canEditUntil?: number;
canMarkAsUnread?: boolean;
canPin?: boolean;
canSave?: boolean;
canReply?: boolean;
isSaved?: boolean;
canAddReaction: boolean;
canDelete: boolean;
canEdit: boolean;
canMarkAsUnread: boolean;
canPin: boolean;
canReply: boolean;
isSaved: boolean;
location: typeof Screens[keyof typeof Screens];
post: PostModel;
thread?: Partial<PostModel>;
thread: Partial<PostModel>;
};
const PostOptions = ({
canAddReaction = true,
canCopyPermalink = true,
canCopyText = true,
canDelete = true,
canEdit = true,
canEditUntil = -1,
canMarkAsUnread = true,
canPin = true,
canReply = true,
canSave = true,
isSaved = true,
canAddReaction,
canDelete,
canEdit,
canMarkAsUnread,
canPin,
canReply,
isSaved,
location,
post,
thread,
}: PostOptionsProps) => {
const shouldRenderEdit = canEdit && (canEditUntil === -1 || canEditUntil > Date.now());
const managedConfig = useManagedConfig();
const isSystemPost = isSystemMessage(post);
const canCopyPermalink = !isSystemPost && managedConfig?.copyAndPasteProtection !== 'true';
const canCopyText = canCopyPermalink && post.message;
const shouldRenderFollow = !(location !== Screens.CHANNEL || !thread);
const snapPoints = [
canAddReaction, canCopyPermalink, canCopyText,
canDelete, shouldRenderEdit, shouldRenderFollow,
canMarkAsUnread, canPin, canReply, canSave,
canDelete, canEdit, shouldRenderFollow,
canMarkAsUnread, canPin, canReply, !isSystemPost,
].reduce((acc, v) => {
return v ? acc + 1 : acc;
}, 0);
@@ -78,18 +74,12 @@ const PostOptions = ({
thread={thread}
/>
}
{canMarkAsUnread && !isSystemMessage(post) && (
<MarkAsUnreadOption/>
)}
{canMarkAsUnread && !isSystemPost && (<MarkAsUnreadOption/>)}
{canCopyPermalink && <CopyLinkOption/>}
{canSave &&
<SaveOption
isSaved={isSaved}
/>
}
{!isSystemPost && <SaveOption isSaved={isSaved}/>}
{canCopyText && <CopyTextOption/>}
{canPin && <PinChannelOption isPostPinned={post.isPinned}/>}
{shouldRenderEdit && <EditOption/>}
{canEdit && <EditOption/>}
{canDelete && <DeletePostOption/>}
</>
);

View File

@@ -5,6 +5,7 @@ interface ClientConfig {
AboutLink: string;
AllowBannerDismissal: string;
AllowCustomThemes: string;
AllowEditPost: string;
AllowedThemes: string;
AndroidAppDownloadLink: string;
AndroidLatestVersion: string;