forked from Ivasoft/mattermost-mobile
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 commit24caa9773f. * Revert "removed post_options from screens constant" This reverts commit863e2faaf7. * 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:
@@ -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))));
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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/>}
|
||||
</>
|
||||
);
|
||||
|
||||
1
types/api/config.d.ts
vendored
1
types/api/config.d.ts
vendored
@@ -5,6 +5,7 @@ interface ClientConfig {
|
||||
AboutLink: string;
|
||||
AllowBannerDismissal: string;
|
||||
AllowCustomThemes: string;
|
||||
AllowEditPost: string;
|
||||
AllowedThemes: string;
|
||||
AndroidAppDownloadLink: string;
|
||||
AndroidLatestVersion: string;
|
||||
|
||||
Reference in New Issue
Block a user