forked from Ivasoft/mattermost-mobile
[Gekidou] pinned posts (#6336)
* Pinned messages * Move isCRTEnabled query to be called earlier and only once * Update Channel stats when post is (un)pinned * Create svg module type definition * Add missing localization strings * feedback review
This commit is contained in:
@@ -1044,3 +1044,85 @@ export async function fetchSavedPosts(serverUrl: string, teamId?: string, channe
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchPinnedPosts(serverUrl: string, channelId: string) {
|
||||
const operator = DatabaseManager.serverDatabases[serverUrl]?.operator;
|
||||
if (!operator) {
|
||||
return {error: `${serverUrl} database not found`};
|
||||
}
|
||||
let client;
|
||||
try {
|
||||
client = NetworkManager.getClient(serverUrl);
|
||||
} catch (error) {
|
||||
return {error};
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await client.getPinnedPosts(channelId);
|
||||
const posts = data.posts || {};
|
||||
const order = data.order || [];
|
||||
const postsArray = order.map((id) => posts[id]);
|
||||
|
||||
if (!postsArray.length) {
|
||||
return {
|
||||
order,
|
||||
posts: postsArray,
|
||||
};
|
||||
}
|
||||
|
||||
const promises: Array<Promise<Model[]>> = [];
|
||||
const {database} = operator;
|
||||
const isCRTEnabled = await getIsCRTEnabled(database);
|
||||
|
||||
const {authors} = await fetchPostAuthors(serverUrl, postsArray, true);
|
||||
const {channels, channelMemberships} = await fetchMissingChannelsFromPosts(serverUrl, postsArray, true);
|
||||
|
||||
if (authors?.length) {
|
||||
promises.push(
|
||||
operator.handleUsers({
|
||||
users: authors,
|
||||
prepareRecordsOnly: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (channels?.length && channelMemberships?.length) {
|
||||
const channelPromises = prepareMissingChannelsForAllTeams(operator, channels, channelMemberships, isCRTEnabled);
|
||||
if (channelPromises.length) {
|
||||
promises.push(...channelPromises);
|
||||
}
|
||||
}
|
||||
|
||||
promises.push(
|
||||
operator.handlePosts({
|
||||
actionType: '',
|
||||
order: [],
|
||||
posts: postsArray,
|
||||
previousPostId: '',
|
||||
prepareRecordsOnly: true,
|
||||
}),
|
||||
);
|
||||
|
||||
if (isCRTEnabled) {
|
||||
promises.push(prepareThreadsFromReceivedPosts(operator, postsArray));
|
||||
}
|
||||
|
||||
const modelArrays = await Promise.all(promises);
|
||||
const models = modelArrays.flatMap((mdls) => {
|
||||
if (!mdls || !mdls.length) {
|
||||
return [];
|
||||
}
|
||||
return mdls;
|
||||
});
|
||||
|
||||
await operator.batchRecords(models);
|
||||
|
||||
return {
|
||||
order,
|
||||
posts: postsArray,
|
||||
};
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(serverUrl, error as ClientErrorProps);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {DeviceEventEmitter} from 'react-native';
|
||||
import {storeMyChannelsForTeam, markChannelAsUnread, markChannelAsViewed, updateLastPostAt} from '@actions/local/channel';
|
||||
import {markPostAsDeleted} from '@actions/local/post';
|
||||
import {createThreadFromNewPost, updateThread} from '@actions/local/thread';
|
||||
import {fetchMyChannel, markChannelAsRead} from '@actions/remote/channel';
|
||||
import {fetchChannelStats, fetchMyChannel, markChannelAsRead} from '@actions/remote/channel';
|
||||
import {fetchPostAuthors, fetchPostById} from '@actions/remote/post';
|
||||
import {fetchThread} from '@actions/remote/thread';
|
||||
import {ActionType, Events, Screens} from '@constants';
|
||||
@@ -196,6 +196,12 @@ export async function handlePostEdited(serverUrl: string, msg: WebSocketMessage)
|
||||
}
|
||||
|
||||
const models: Model[] = [];
|
||||
const {database} = operator;
|
||||
|
||||
const oldPost = await getPostById(database, post.id);
|
||||
if (oldPost && oldPost.isPinned !== post.is_pinned) {
|
||||
fetchChannelStats(serverUrl, post.channel_id);
|
||||
}
|
||||
|
||||
const {authors} = await fetchPostAuthors(serverUrl, [post], true);
|
||||
if (authors?.length) {
|
||||
|
||||
@@ -18,7 +18,7 @@ export interface ClientPostsMix {
|
||||
getPostsAfter: (channelId: string, postId: string, page?: number, perPage?: number, collapsedThreads?: boolean, collapsedThreadsExtended?: boolean) => Promise<PostResponse>;
|
||||
getFileInfosForPost: (postId: string) => Promise<FileInfo[]>;
|
||||
getSavedPosts: (userId: string, channelId?: string, teamId?: string, page?: number, perPage?: number) => Promise<PostResponse>;
|
||||
getPinnedPosts: (channelId: string) => Promise<any>;
|
||||
getPinnedPosts: (channelId: string) => Promise<PostResponse>;
|
||||
markPostAsUnread: (userId: string, postId: string) => Promise<any>;
|
||||
pinPost: (postId: string) => Promise<any>;
|
||||
unpinPost: (postId: string) => Promise<any>;
|
||||
|
||||
@@ -37,6 +37,7 @@ const InfoBox = ({channelId, containerStyle, showAsLabel = false, testID}: Props
|
||||
testID: closeButtonId,
|
||||
}],
|
||||
},
|
||||
modal: {swipeToDismiss: false},
|
||||
};
|
||||
showModal(Screens.CHANNEL_INFO, title, {channelId, closeButtonId}, options);
|
||||
}, [intl, channelId, theme]);
|
||||
|
||||
@@ -150,7 +150,7 @@ const OptionItem = ({
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
{Boolean(actionComponent) &&
|
||||
{Boolean(actionComponent || info) &&
|
||||
<View style={styles.actionContainer}>
|
||||
{Boolean(info) &&
|
||||
<View style={styles.infoContainer}>
|
||||
|
||||
@@ -13,7 +13,6 @@ import {calculateDimensions, getViewPortWidth} from '@utils/images';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getYouTubeVideoId, tryOpenURL} from '@utils/url';
|
||||
|
||||
// @ts-expect-error import svg
|
||||
import YouTubeLogo from './youtube.svg';
|
||||
|
||||
type YouTubeProps = {
|
||||
|
||||
@@ -133,7 +133,7 @@ const Post = ({
|
||||
}, [isConsecutivePost, post, previousPost, isFirstReply]);
|
||||
|
||||
const handlePostPress = () => {
|
||||
if ([Screens.SAVED_POSTS, Screens.MENTIONS, Screens.SEARCH].includes(location)) {
|
||||
if ([Screens.SAVED_POSTS, Screens.MENTIONS, Screens.SEARCH, Screens.PINNED_MESSAGES].includes(location)) {
|
||||
showPermalink(serverUrl, '', post.id, intl);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,6 +131,5 @@ export const NOT_READY = [
|
||||
CREATE_TEAM,
|
||||
INTEGRATION_SELECTOR,
|
||||
INTERACTIVE_DIALOG,
|
||||
PINNED_MESSAGES,
|
||||
USER_PROFILE,
|
||||
];
|
||||
|
||||
@@ -178,3 +178,16 @@ export const queryPostsBetween = (database: Database, earliest: number, latest:
|
||||
}
|
||||
return database.get<PostModel>(POST).query(...clauses);
|
||||
};
|
||||
|
||||
export const queryPinnedPostsInChannel = (database: Database, channelId: string) => {
|
||||
return database.get<PostModel>(POST).query(
|
||||
Q.and(
|
||||
Q.where('channel_id', channelId),
|
||||
Q.where('is_pinned', Q.eq(true)),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const observePinnedPostsInChannel = (database: Database, channelId: string) => {
|
||||
return queryPinnedPostsInChannel(database, channelId).observe();
|
||||
};
|
||||
|
||||
@@ -114,6 +114,7 @@ const ChannelHeader = ({
|
||||
testID: closeButtonId,
|
||||
}],
|
||||
},
|
||||
modal: {swipeToDismiss: false},
|
||||
};
|
||||
showModal(Screens.CHANNEL_INFO, title, {channelId, closeButtonId}, options);
|
||||
}), [channelId, channelType, intl, theme]);
|
||||
|
||||
@@ -21,7 +21,6 @@ import {isEmail} from '@utils/helpers';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
// @ts-expect-error svg extension
|
||||
import Inbox from './inbox.svg';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -9,7 +9,6 @@ import {useTheme} from '@context/theme';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
// @ts-expect-error svg extension
|
||||
import SearchHintSVG from './illustrations/search_hint.svg';
|
||||
|
||||
const getStyles = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
|
||||
@@ -150,6 +150,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
case Screens.PERMALINK:
|
||||
screen = withServerDatabase(require('@screens/permalink').default);
|
||||
break;
|
||||
case Screens.PINNED_MESSAGES:
|
||||
screen = withServerDatabase(require('@screens/pinned_messages').default);
|
||||
break;
|
||||
case Screens.POST_OPTIONS:
|
||||
screen = withServerDatabase(
|
||||
require('@screens/post_options').default,
|
||||
|
||||
@@ -24,7 +24,6 @@ import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
// @ts-expect-error svg extension
|
||||
import Shield from './mfa.svg';
|
||||
|
||||
type MFAProps = {
|
||||
|
||||
9
app/screens/pinned_messages/empty/empty.svg
Normal file
9
app/screens/pinned_messages/empty/empty.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="140" height="141" viewBox="0 0 140 141" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M55.8798 90.1401L55.6921 90.3277L54.9625 91.2451C53.9816 92.3473 52.9374 93.3915 51.8354 94.3725L49.8132 96.2177L39.525 105.225L27.3917 114.961C27.2644 115.052 27.1205 115.116 26.9683 115.151C26.8162 115.186 26.6586 115.19 26.5047 115.164C26.3508 115.138 26.2035 115.082 26.0713 114.999C25.9391 114.916 25.8245 114.807 25.7342 114.68C25.5037 114.457 25.3595 114.161 25.327 113.842C25.2946 113.523 25.3761 113.203 25.5571 112.939L29.2263 108.342L42.0892 93.0903L46.1336 88.493C47.3636 87.2698 48.3434 86.2899 49.0731 85.5533L50.3656 84.4482C51.1885 83.8489 52.1957 83.5575 53.2114 83.6247C53.7075 83.6459 54.1935 83.7721 54.6373 83.9949C55.0811 84.2177 55.4726 84.5321 55.786 84.9173C56.4338 85.6169 56.8044 86.5287 56.8284 87.4818C56.8009 88.4459 56.4688 89.3764 55.8798 90.1401V90.1401Z" fill="#A4A9B7"/>
|
||||
<path d="M36.6804 102.268L48.8138 87.6328L50.9323 88.7882L36.6804 102.268Z" fill="#E8E9ED"/>
|
||||
<path d="M114.68 58.6262C114.69 59.1216 114.596 59.6136 114.406 60.0711C114.216 60.5286 113.933 60.9418 113.575 61.2845L112.293 62.5771C110.383 64.4609 107.809 65.5171 105.127 65.5171C102.444 65.5171 99.8699 64.4609 97.9603 62.5771C97.9177 62.519 97.862 62.4717 97.7977 62.4391C97.7334 62.4065 97.6624 62.3894 97.5903 62.3894C97.5182 62.3894 97.4472 62.4065 97.3829 62.4391C97.3186 62.4717 97.2629 62.519 97.2203 62.5771L81.9703 77.6409C81.5019 78.1292 81.2251 78.77 81.1907 79.4458C81.1562 80.1217 81.3663 80.7872 81.7826 81.3207C83.5802 83.9612 84.3954 87.1483 84.0863 90.3277C83.7742 93.5145 82.3799 96.4983 80.1357 98.7821L74.2567 104.662C72.7177 106.191 70.636 107.05 68.4662 107.05C66.2964 107.05 64.2148 106.191 62.6759 104.662L35.6574 77.8285C34.1278 76.2894 33.2693 74.2076 33.2693 72.0376C33.2693 69.8676 34.1278 67.7857 35.6574 66.2467L41.7241 60.3671C43.9488 58.0251 46.9584 56.5851 50.1778 56.3223C53.363 56.0805 56.5366 56.9218 59.1839 58.7096C59.7146 59.0731 60.3572 59.236 60.9969 59.1691C61.6366 59.1023 62.2318 58.81 62.6759 58.3447L77.905 43.3019C77.9661 43.1881 77.9981 43.061 77.9981 42.9318C77.9981 42.8026 77.9661 42.6755 77.905 42.5617C76.0213 40.6519 74.9653 38.0772 74.9653 35.3947C74.9653 32.7122 76.0213 30.1375 77.905 28.2277L79.01 26.9455C79.7886 26.2368 80.8028 25.8429 81.8556 25.8405C82.3524 25.8321 82.8455 25.9258 83.3046 26.1159C83.7636 26.306 84.1786 26.5884 84.5241 26.9455L113.554 55.9783C113.914 56.3188 114.199 56.7296 114.393 57.1851C114.586 57.6406 114.684 58.1312 114.68 58.6262Z" fill="#FFBC1F"/>
|
||||
<path d="M69.0357 71.8425C62.7186 65.5264 61.011 61.1231 60.9468 59.7109C52.4728 56.0522 51.8948 57.5927 55.9394 62.2143C60.0548 66.9169 76.932 79.7377 69.0357 71.8425Z" fill="#FFD470"/>
|
||||
<path d="M71.1295 66.0693C70.8433 66.3651 70.4996 66.599 70.1196 66.7568C69.7395 66.9146 69.3311 66.9928 68.9197 66.9866C68.5096 66.9939 68.1025 66.9161 67.724 66.7582C67.3454 66.6003 67.0037 66.3658 66.7203 66.0693C66.435 65.8014 66.2067 65.4786 66.0491 65.1204C65.8914 64.7621 65.8077 64.3758 65.803 63.9844C65.8242 63.1316 66.1495 62.3144 66.7203 61.6805L78.8431 49.7442C79.265 49.3329 79.7976 49.0534 80.3758 48.9398C80.9539 48.8262 81.5525 48.8835 82.0986 49.1046C82.6448 49.3257 83.1147 49.7012 83.451 50.185C83.7873 50.6688 83.9754 51.2401 83.9924 51.8291C83.9865 52.2203 83.9023 52.6064 83.7448 52.9644C83.5872 53.3225 83.3595 53.6454 83.0751 53.9141L71.1295 66.0485V66.0693Z" fill="#FFD470"/>
|
||||
<path d="M95.2103 60.5901L93.3436 58.6854C82.7457 48.1638 76.082 46.6005 73.9995 47.2033L78.1721 43.2046L93.3436 58.6854C93.9529 59.2903 94.5752 59.9247 95.2103 60.5901Z" fill="#F5AB00"/>
|
||||
<ellipse cx="59" cy="115" rx="34" ry="2.5" fill="black" fill-opacity="0.06"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
55
app/screens/pinned_messages/empty/index.tsx
Normal file
55
app/screens/pinned_messages/empty/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
import EmptyIllustration from './empty.svg';
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
||||
container: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingHorizontal: 40,
|
||||
},
|
||||
title: {
|
||||
color: theme.centerChannelColor,
|
||||
...typography('Heading', 400, 'SemiBold'),
|
||||
},
|
||||
paragraph: {
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||
...typography('Body', 200),
|
||||
},
|
||||
}));
|
||||
|
||||
function EmptySavedPosts() {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<EmptyIllustration/>
|
||||
<FormattedText
|
||||
defaultMessage='No pinned messages yet'
|
||||
id='pinned_messages.empty.title'
|
||||
style={styles.title}
|
||||
testID='pinned_messages.empty.title'
|
||||
/>
|
||||
<FormattedText
|
||||
defaultMessage={'To pin important messages, long-press on a message and chose Pin To Channel. Pinned messages will be visible to everyone in this channel.'}
|
||||
id='pinned_messages.empty.paragraph'
|
||||
style={styles.paragraph}
|
||||
testID='pinned_messages.empty.paragraph'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default EmptySavedPosts;
|
||||
35
app/screens/pinned_messages/index.ts
Normal file
35
app/screens/pinned_messages/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observePinnedPostsInChannel} from '@queries/servers/post';
|
||||
import {observeConfigBooleanValue} from '@queries/servers/system';
|
||||
import {observeIsCRTEnabled} from '@queries/servers/thread';
|
||||
import {observeCurrentUser} from '@queries/servers/user';
|
||||
import {getTimezone} from '@utils/user';
|
||||
|
||||
import PinnedMessages from './pinned_messages';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
type Props = WithDatabaseArgs & {
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
const enhance = withObservables(['channelId'], ({channelId, database}: Props) => {
|
||||
const currentUser = observeCurrentUser(database);
|
||||
const posts = observePinnedPostsInChannel(database, channelId);
|
||||
|
||||
return {
|
||||
currentTimezone: currentUser.pipe((switchMap((user) => of$(getTimezone(user?.timezone || null))))),
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
isTimezoneEnabled: observeConfigBooleanValue(database, 'ExperimentalTimezone'),
|
||||
posts,
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhance(PinnedMessages));
|
||||
177
app/screens/pinned_messages/pinned_messages.tsx
Normal file
177
app/screens/pinned_messages/pinned_messages.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {BackHandler, DeviceEventEmitter, FlatList, StyleSheet, View} from 'react-native';
|
||||
import {Edge, SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {fetchPinnedPosts} from '@actions/remote/post';
|
||||
import Loading from '@components/loading';
|
||||
import DateSeparator from '@components/post_list/date_separator';
|
||||
import Post from '@components/post_list/post';
|
||||
import {Events, Screens} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {popTopScreen} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isDateLine, getDateForDateLine, selectOrderedPosts} from '@utils/post_list';
|
||||
|
||||
import EmptyState from './empty';
|
||||
|
||||
import type {ViewableItemsChanged} from '@typings/components/post_list';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
componentId?: string;
|
||||
currentTimezone: string | null;
|
||||
isCRTEnabled: boolean;
|
||||
isTimezoneEnabled: boolean;
|
||||
posts: PostModel[];
|
||||
}
|
||||
|
||||
const edges: Edge[] = ['bottom', 'left', 'right'];
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
empty: {
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
list: {
|
||||
paddingVertical: 8,
|
||||
},
|
||||
loading: {
|
||||
height: 40,
|
||||
width: 40,
|
||||
justifyContent: 'center' as const,
|
||||
},
|
||||
});
|
||||
|
||||
function SavedMessages({
|
||||
channelId,
|
||||
componentId,
|
||||
currentTimezone,
|
||||
isCRTEnabled,
|
||||
isTimezoneEnabled,
|
||||
posts,
|
||||
}: Props) {
|
||||
const [loading, setLoading] = useState(!posts.length);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const theme = useTheme();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const data = useMemo(() => selectOrderedPosts(posts, 0, false, '', '', false, isTimezoneEnabled, currentTimezone, false).reverse(), [posts]);
|
||||
|
||||
const close = () => {
|
||||
if (componentId) {
|
||||
popTopScreen(componentId);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPinnedPosts(serverUrl, channelId).finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = BackHandler.addEventListener('hardwareBackPress', () => {
|
||||
if (EphemeralStore.getNavigationTopComponentId() === componentId) {
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return () => listener.remove();
|
||||
}, [componentId]);
|
||||
|
||||
const onViewableItemsChanged = useCallback(({viewableItems}: ViewableItemsChanged) => {
|
||||
if (!viewableItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewableItemsMap = viewableItems.reduce((acc: Record<string, boolean>, {item, isViewable}) => {
|
||||
if (isViewable) {
|
||||
acc[`${Screens.PINNED_MESSAGES}-${item.id}`] = true;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
DeviceEventEmitter.emit(Events.ITEM_IN_VIEWPORT, viewableItemsMap);
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setRefreshing(true);
|
||||
await fetchPinnedPosts(serverUrl, channelId);
|
||||
setRefreshing(false);
|
||||
}, [serverUrl, channelId]);
|
||||
|
||||
const emptyList = useMemo(() => (
|
||||
<View style={styles.empty}>
|
||||
{loading ? (
|
||||
<Loading
|
||||
color={theme.buttonBg}
|
||||
size='large'
|
||||
/>
|
||||
) : (
|
||||
<EmptyState/>
|
||||
)}
|
||||
</View>
|
||||
), [loading, theme.buttonBg]);
|
||||
|
||||
const renderItem = useCallback(({item}) => {
|
||||
if (typeof item === 'string') {
|
||||
if (isDateLine(item)) {
|
||||
return (
|
||||
<DateSeparator
|
||||
date={getDateForDateLine(item)}
|
||||
theme={theme}
|
||||
timezone={isTimezoneEnabled ? currentTimezone : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Post
|
||||
highlightPinnedOrSaved={false}
|
||||
isCRTEnabled={isCRTEnabled}
|
||||
location={Screens.PINNED_MESSAGES}
|
||||
nextPost={undefined}
|
||||
post={item}
|
||||
previousPost={undefined}
|
||||
showAddReaction={false}
|
||||
shouldRenderReplyButton={false}
|
||||
skipSavedHeader={true}
|
||||
skipPinnedHeader={true}
|
||||
/>
|
||||
);
|
||||
}, [currentTimezone, isTimezoneEnabled, theme]);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={styles.flex}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={data.length ? styles.list : [styles.empty]}
|
||||
ListEmptyComponent={emptyList}
|
||||
data={data}
|
||||
onRefresh={handleRefresh}
|
||||
refreshing={refreshing}
|
||||
renderItem={renderItem}
|
||||
scrollToOverflowEnabled={true}
|
||||
onViewableItemsChanged={onViewableItemsChanged}
|
||||
/>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
export default SavedMessages;
|
||||
@@ -149,7 +149,7 @@ function SavedMessages({
|
||||
<EmptyState/>
|
||||
)}
|
||||
</View>
|
||||
), [loading, theme.centerChannelColor]);
|
||||
), [loading, theme.buttonBg]);
|
||||
|
||||
const renderItem = useCallback(({item}) => {
|
||||
if (typeof item === 'string') {
|
||||
|
||||
@@ -597,6 +597,8 @@
|
||||
"permalink.show_dialog_warn.description": "You are about to join {channel} without explicitly being added by the channel admin. Are you sure you wish to join this private channel?",
|
||||
"permalink.show_dialog_warn.join": "Join",
|
||||
"permalink.show_dialog_warn.title": "Join private channel",
|
||||
"pinned_messages.empty.paragraph": "To pin important messages, long-press on a message and chose Pin To Channel. Pinned messages will be visible to everyone in this channel.",
|
||||
"pinned_messages.empty.title": "No pinned messages yet",
|
||||
"plus_menu.browse_channels.title": "Browse Channels",
|
||||
"plus_menu.create_new_channel.title": "Create New Channel",
|
||||
"plus_menu.open_direct_message.title": "Open a Direct Message",
|
||||
|
||||
7
types/global/svg.d.ts
vendored
Normal file
7
types/global/svg.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
declare module '*.svg' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
Reference in New Issue
Block a user