Show at mentions in RemoveMarkdown renderer (#7664)
This commit is contained in:
parent
c9fe72460b
commit
044cebc978
5 changed files with 189 additions and 66 deletions
|
|
@ -11,24 +11,20 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
|||
import {fetchUserOrGroupsByMentionsInBatch} from '@actions/remote/user';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import {Screens} from '@constants';
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import GroupModel from '@database/models/server/group';
|
||||
import UserModel from '@database/models/server/user';
|
||||
import {useMemoMentionedGroup, useMemoMentionedUser} from '@hooks/markdown';
|
||||
import {bottomSheet, dismissBottomSheet, openAsBottomSheet} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {displayUsername, getUsersByUsername} from '@utils/user';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type GroupModelType from '@typings/database/models/servers/group';
|
||||
import type GroupMembershipModel from '@typings/database/models/servers/group_membership';
|
||||
import type UserModelType from '@typings/database/models/servers/user';
|
||||
|
||||
type AtMentionProps = {
|
||||
channelId?: string;
|
||||
currentUserId: string;
|
||||
database: Database;
|
||||
disableAtChannelMentionHighlight?: boolean;
|
||||
isSearchResult?: boolean;
|
||||
location: string;
|
||||
|
|
@ -43,8 +39,6 @@ type AtMentionProps = {
|
|||
groupMemberships: GroupMembershipModel[];
|
||||
}
|
||||
|
||||
const {SERVER: {GROUP, USER}} = MM_TABLES;
|
||||
|
||||
const style = StyleSheet.create({
|
||||
bottomSheet: {flex: 1},
|
||||
});
|
||||
|
|
@ -52,7 +46,6 @@ const style = StyleSheet.create({
|
|||
const AtMention = ({
|
||||
channelId,
|
||||
currentUserId,
|
||||
database,
|
||||
disableAtChannelMentionHighlight,
|
||||
isSearchResult,
|
||||
location,
|
||||
|
|
@ -72,31 +65,15 @@ const AtMention = ({
|
|||
const {bottom} = useSafeAreaInsets();
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const user = useMemo(() => {
|
||||
const usersByUsername = getUsersByUsername(users);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (usersByUsername[mn]) {
|
||||
return usersByUsername[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error: The model constructor is hidden within WDB type definition
|
||||
return new UserModel(database.get(USER), {username: ''});
|
||||
}, [users, mentionName]);
|
||||
const user = useMemoMentionedUser(users, mentionName);
|
||||
|
||||
const userMentionKeys = useMemo(() => {
|
||||
if (mentionKeys) {
|
||||
return mentionKeys;
|
||||
}
|
||||
if (!user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (user.id !== currentUserId) {
|
||||
return [];
|
||||
|
|
@ -105,50 +82,21 @@ const AtMention = ({
|
|||
return user.mentionKeys;
|
||||
}, [currentUserId, mentionKeys, user]);
|
||||
|
||||
// Checks if the mention is a group
|
||||
const group = useMemo(() => {
|
||||
if (user?.username) {
|
||||
return undefined;
|
||||
}
|
||||
const getGroupsByName = (gs: GroupModelType[]) => {
|
||||
const groupsByName: Dictionary<GroupModelType> = {};
|
||||
|
||||
for (const g of gs) {
|
||||
groupsByName[g.name] = g;
|
||||
}
|
||||
|
||||
return groupsByName;
|
||||
};
|
||||
|
||||
const groupsByName = getGroupsByName(groups);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (groupsByName[mn]) {
|
||||
return groupsByName[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error: The model constructor is hidden within WDB type definition
|
||||
return new GroupModel(database.get(GROUP), {name: ''});
|
||||
}, [groups, user, mentionName]);
|
||||
const group = useMemoMentionedGroup(groups, user, mentionName);
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
// Fetches and updates the local db store with the mention
|
||||
if (!user.username && !group?.name) {
|
||||
if (!user?.username && !group?.name) {
|
||||
fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const openUserProfile = () => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const screen = Screens.USER_PROFILE;
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const closeButtonId = 'close-user-profile';
|
||||
|
|
@ -171,7 +119,7 @@ const AtMention = ({
|
|||
onPress={() => {
|
||||
dismissBottomSheet();
|
||||
let username = mentionName;
|
||||
if (user.username) {
|
||||
if (user?.username) {
|
||||
username = user.username;
|
||||
}
|
||||
|
||||
|
|
|
|||
69
app/components/remove_markdown/at_mention/at_mention.tsx
Normal file
69
app/components/remove_markdown/at_mention/at_mention.tsx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useEffect} from 'react';
|
||||
import {type StyleProp, Text, type TextStyle} from 'react-native';
|
||||
|
||||
import {fetchUserOrGroupsByMentionsInBatch} from '@actions/remote/user';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import GroupModel from '@database/models/server/group';
|
||||
import {useMemoMentionedGroup, useMemoMentionedUser} from '@hooks/markdown';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type UserModelType from '@typings/database/models/servers/user';
|
||||
|
||||
type AtMentionProps = {
|
||||
database: Database;
|
||||
mentionName: string;
|
||||
teammateNameDisplay: string;
|
||||
textStyle?: StyleProp<TextStyle>;
|
||||
users: UserModelType[];
|
||||
groups: GroupModel[];
|
||||
}
|
||||
|
||||
const AtMention = ({
|
||||
mentionName,
|
||||
teammateNameDisplay,
|
||||
textStyle,
|
||||
users,
|
||||
groups,
|
||||
}: AtMentionProps) => {
|
||||
const serverUrl = useServerUrl();
|
||||
|
||||
const user = useMemoMentionedUser(users, mentionName);
|
||||
const group = useMemoMentionedGroup(groups, user, mentionName);
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
// Fetches and updates the local db store with the mention
|
||||
if (!user?.username && !group?.name) {
|
||||
fetchUserOrGroupsByMentionsInBatch(serverUrl, mentionName);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let mention;
|
||||
|
||||
if (user?.username) {
|
||||
mention = displayUsername(user, user.locale, teammateNameDisplay);
|
||||
} else if (group?.name) {
|
||||
mention = group.name;
|
||||
} else {
|
||||
const pattern = new RegExp(/\b(all|channel|here)(?:\.\B|_\b|\b)/, 'i');
|
||||
const mentionMatch = pattern.exec(mentionName);
|
||||
|
||||
if (mentionMatch) {
|
||||
mention = mentionMatch.length > 1 ? mentionMatch[1] : mentionMatch[0];
|
||||
} else {
|
||||
mention = mentionName;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Text style={textStyle}>
|
||||
{'@' + mention}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
export default AtMention;
|
||||
29
app/components/remove_markdown/at_mention/index.ts
Normal file
29
app/components/remove_markdown/at_mention/index.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// 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 {queryGroupsByName} from '@queries/servers/group';
|
||||
import {observeTeammateNameDisplay, queryUsersLike} from '@queries/servers/user';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhance = withObservables(['mentionName'], ({database, mentionName}: {mentionName: string} & WithDatabaseArgs) => {
|
||||
const teammateNameDisplay = observeTeammateNameDisplay(database);
|
||||
|
||||
let mn = mentionName.toLowerCase();
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
}
|
||||
|
||||
return {
|
||||
teammateNameDisplay,
|
||||
users: queryUsersLike(database, mn).observeWithColumns(['username']),
|
||||
groups: queryGroupsByName(database, mn).observeWithColumns(['name']),
|
||||
};
|
||||
});
|
||||
|
||||
export default withDatabase(enhance(AtMention));
|
||||
|
|
@ -9,6 +9,8 @@ import {type StyleProp, Text, type TextStyle} from 'react-native';
|
|||
import Emoji from '@components/emoji';
|
||||
import {computeTextStyle} from '@utils/markdown';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
|
||||
import type {MarkdownBaseRenderer, MarkdownEmojiRenderer, MarkdownTextStyles} from '@typings/global/markdown';
|
||||
|
||||
type Props = {
|
||||
|
|
@ -45,6 +47,15 @@ const RemoveMarkdown = ({enableEmoji, enableHardBreak, enableSoftBreak, enableCo
|
|||
return <Text style={baseStyle}>{literal}</Text>;
|
||||
}, [baseStyle]);
|
||||
|
||||
const renderAtMention = ({context, mentionName}: {context: string[]; mentionName: string}) => {
|
||||
return (
|
||||
<AtMention
|
||||
textStyle={computeTextStyle(textStyle, baseStyle, context)}
|
||||
mentionName={mentionName}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCodeSpan = useCallback(({context, literal}: MarkdownBaseRenderer) => {
|
||||
if (!enableCodeSpan) {
|
||||
return renderText({literal});
|
||||
|
|
@ -76,7 +87,7 @@ const RemoveMarkdown = ({enableEmoji, enableHardBreak, enableSoftBreak, enableCo
|
|||
code: renderCodeSpan,
|
||||
link: Renderer.forwardChildren,
|
||||
image: renderNull,
|
||||
atMention: Renderer.forwardChildren,
|
||||
atMention: renderAtMention,
|
||||
channelLink: Renderer.forwardChildren,
|
||||
emoji: renderEmoji,
|
||||
hashtag: Renderer.forwardChildren,
|
||||
|
|
|
|||
66
app/hooks/markdown.ts
Normal file
66
app/hooks/markdown.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {useMemo} from 'react';
|
||||
|
||||
import {getUsersByUsername} from '@utils/user';
|
||||
|
||||
import type GroupModel from '@typings/database/models/servers/group';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
export function useMemoMentionedUser(users: UserModel[], mentionName: string) {
|
||||
return useMemo(() => {
|
||||
const usersByUsername = getUsersByUsername(users);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (usersByUsername[mn]) {
|
||||
return usersByUsername[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [users, mentionName]);
|
||||
}
|
||||
|
||||
export function useMemoMentionedGroup(groups: GroupModel[], user: UserModel | undefined, mentionName: string) {
|
||||
// Checks if the mention is a group
|
||||
return useMemo(() => {
|
||||
if (user?.username) {
|
||||
return undefined;
|
||||
}
|
||||
const getGroupsByName = (gs: GroupModel[]) => {
|
||||
const groupsByName: Dictionary<GroupModel> = {};
|
||||
|
||||
for (const g of gs) {
|
||||
groupsByName[g.name] = g;
|
||||
}
|
||||
|
||||
return groupsByName;
|
||||
};
|
||||
|
||||
const groupsByName = getGroupsByName(groups);
|
||||
let mn = mentionName.toLowerCase();
|
||||
|
||||
while (mn.length > 0) {
|
||||
if (groupsByName[mn]) {
|
||||
return groupsByName[mn];
|
||||
}
|
||||
|
||||
// Repeatedly trim off trailing punctuation in case this is at the end of a sentence
|
||||
if ((/[._-]$/).test(mn)) {
|
||||
mn = mn.substring(0, mn.length - 1);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [groups, user, mentionName]);
|
||||
}
|
||||
Loading…
Reference in a new issue