forked from Ivasoft/mattermost-mobile
[Gekidou] Add Latex support (#6195)
* Add Latex support * Markdown memoization * feedback review * feedback review 2
This commit is contained in:
@@ -89,7 +89,7 @@ const AtMention = ({
|
||||
return user.mentionKeys;
|
||||
}, [currentUserId, mentionKeys, user]);
|
||||
|
||||
const goToUserProfile = useCallback(() => {
|
||||
const goToUserProfile = () => {
|
||||
const screen = 'UserProfile';
|
||||
const title = intl.formatMessage({id: 'mobile.routes.user_profile', defaultMessage: 'Profile'});
|
||||
const passProps = {
|
||||
@@ -109,7 +109,7 @@ const AtMention = ({
|
||||
};
|
||||
|
||||
showModal(screen, title, passProps, options);
|
||||
}, [user]);
|
||||
};
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
@@ -230,4 +230,4 @@ const AtMention = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(AtMention);
|
||||
export default AtMention;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback} from 'react';
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {StyleProp, Text, TextStyle} from 'react-native';
|
||||
|
||||
@@ -65,7 +65,7 @@ const ChannelMention = ({
|
||||
const serverUrl = useServerUrl();
|
||||
const channel = getChannelFromChannelName(channelName, channels, channelMentions, team.name);
|
||||
|
||||
const handlePress = useCallback(preventDoubleTap(async () => {
|
||||
const handlePress = preventDoubleTap(async () => {
|
||||
let c = channel;
|
||||
|
||||
if (!c?.id && c?.display_name) {
|
||||
@@ -90,7 +90,7 @@ const ChannelMention = ({
|
||||
await dismissAllModals();
|
||||
await popToRoot();
|
||||
}
|
||||
}), [channel?.display_name, channel?.id]);
|
||||
});
|
||||
|
||||
if (!channel) {
|
||||
return <Text style={textStyle}>{`~${channelName}`}</Text>;
|
||||
|
||||
27
app/components/markdown/index.ts
Normal file
27
app/components/markdown/index.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 React from 'react';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeConfig} from '@queries/servers/system';
|
||||
|
||||
import Markdown from './markdown';
|
||||
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
|
||||
const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
const config = observeConfig(database);
|
||||
const enableLatex = config.pipe(switchMap((c) => of$(c?.EnableLatex === 'true')));
|
||||
const enableInlineLatex = config.pipe(switchMap((c) => of$(c?.EnableInlineLatex === 'true')));
|
||||
|
||||
return {
|
||||
enableLatex,
|
||||
enableInlineLatex,
|
||||
};
|
||||
});
|
||||
|
||||
export default React.memo(withDatabase(enhanced(Markdown)));
|
||||
@@ -1,510 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Parser, Node} from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import React, {PureComponent, ReactElement} from 'react';
|
||||
import {GestureResponderEvent, Platform, StyleProp, Text, TextStyle, View} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Hashtag from '@components/markdown/hashtag';
|
||||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getScheme} from '@utils/url';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
import ChannelMention, {ChannelMentions} from './channel_mention';
|
||||
import MarkdownBlockQuote from './markdown_block_quote';
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
import MarkdownImage from './markdown_image';
|
||||
import MarkdownLink from './markdown_link';
|
||||
import MarkdownList from './markdown_list';
|
||||
import MarkdownListItem from './markdown_list_item';
|
||||
import MarkdownTable from './markdown_table';
|
||||
import MarkdownTableCell, {MarkdownTableCellProps} from './markdown_table_cell';
|
||||
import MarkdownTableImage from './markdown_table_image';
|
||||
import MarkdownTableRow, {MarkdownTableRowProps} from './markdown_table_row';
|
||||
import {addListItemIndices, combineTextNodes, highlightMentions, pullOutImages} from './transform';
|
||||
|
||||
import type {
|
||||
MarkdownAtMentionRenderer, MarkdownBaseRenderer, MarkdownBlockStyles, MarkdownChannelMentionRenderer,
|
||||
MarkdownEmojiRenderer, MarkdownImageRenderer, MarkdownTextStyles, UserMentionKey,
|
||||
} from '@typings/global/markdown';
|
||||
|
||||
type MarkdownProps = {
|
||||
autolinkedUrlSchemes?: string[];
|
||||
baseTextStyle: StyleProp<TextStyle>;
|
||||
blockStyles: MarkdownBlockStyles;
|
||||
channelMentions?: ChannelMentions;
|
||||
disableAtMentions?: boolean;
|
||||
disableAtChannelMentionHighlight?: boolean;
|
||||
disableChannelLink?: boolean;
|
||||
disableGallery?: boolean;
|
||||
disableHashtags?: boolean;
|
||||
imagesMetadata?: Record<string, PostImage>;
|
||||
isEdited?: boolean;
|
||||
isReplyPost?: boolean;
|
||||
isSearchResult?: boolean;
|
||||
layoutWidth?: number;
|
||||
location?: string;
|
||||
mentionKeys?: UserMentionKey[];
|
||||
minimumHashtagLength?: number;
|
||||
onPostPress?: (event: GestureResponderEvent) => void;
|
||||
postId?: string;
|
||||
textStyles: MarkdownTextStyles;
|
||||
theme: Theme;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
// Android has trouble giving text transparency depending on how it's nested,
|
||||
// so we calculate the resulting colour manually
|
||||
const editedOpacity = Platform.select({
|
||||
ios: 0.3,
|
||||
android: 1.0,
|
||||
});
|
||||
const editedColor = Platform.select({
|
||||
ios: theme.centerChannelColor,
|
||||
android: blendColors(theme.centerChannelBg, theme.centerChannelColor, 0.3),
|
||||
});
|
||||
|
||||
return {
|
||||
block: {
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
editedIndicatorText: {
|
||||
color: editedColor,
|
||||
opacity: editedOpacity,
|
||||
},
|
||||
atMentionOpacity: {
|
||||
opacity: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
class Markdown extends PureComponent<MarkdownProps> {
|
||||
static defaultProps = {
|
||||
textStyles: {},
|
||||
blockStyles: {},
|
||||
disableHashtags: false,
|
||||
disableAtMentions: false,
|
||||
disableAtChannelMentionHighlight: false,
|
||||
disableChannelLink: false,
|
||||
disableGallery: false,
|
||||
layoutWidth: undefined,
|
||||
value: '',
|
||||
minimumHashtagLength: 3,
|
||||
};
|
||||
|
||||
private parser: Parser;
|
||||
private renderer: Renderer.Renderer;
|
||||
|
||||
constructor(props: MarkdownProps) {
|
||||
super(props);
|
||||
|
||||
this.parser = this.createParser();
|
||||
this.renderer = this.createRenderer();
|
||||
}
|
||||
|
||||
createParser = () => {
|
||||
return new Parser({
|
||||
urlFilter: this.urlFilter,
|
||||
minimumHashtagLength: this.props.minimumHashtagLength,
|
||||
});
|
||||
};
|
||||
|
||||
urlFilter = (url: string) => {
|
||||
const scheme = getScheme(url);
|
||||
return !scheme || this.props.autolinkedUrlSchemes?.indexOf(scheme) !== -1;
|
||||
};
|
||||
|
||||
createRenderer = () => {
|
||||
const renderers: any = {
|
||||
text: this.renderText,
|
||||
|
||||
emph: Renderer.forwardChildren,
|
||||
strong: Renderer.forwardChildren,
|
||||
del: Renderer.forwardChildren,
|
||||
code: this.renderCodeSpan,
|
||||
link: this.renderLink,
|
||||
image: this.renderImage,
|
||||
atMention: this.renderAtMention,
|
||||
channelLink: this.renderChannelLink,
|
||||
emoji: this.renderEmoji,
|
||||
hashtag: this.renderHashtag,
|
||||
latexinline: this.renderParagraph,
|
||||
|
||||
paragraph: this.renderParagraph,
|
||||
heading: this.renderHeading,
|
||||
codeBlock: this.renderCodeBlock,
|
||||
blockQuote: this.renderBlockQuote,
|
||||
|
||||
list: this.renderList,
|
||||
item: this.renderListItem,
|
||||
|
||||
hardBreak: this.renderHardBreak,
|
||||
thematicBreak: this.renderThematicBreak,
|
||||
softBreak: this.renderSoftBreak,
|
||||
|
||||
htmlBlock: this.renderHtml,
|
||||
htmlInline: this.renderHtml,
|
||||
|
||||
table: this.renderTable,
|
||||
table_row: this.renderTableRow,
|
||||
table_cell: this.renderTableCell,
|
||||
|
||||
mention_highlight: Renderer.forwardChildren,
|
||||
|
||||
editedIndicator: this.renderEditedIndicator,
|
||||
};
|
||||
|
||||
return new Renderer({
|
||||
renderers,
|
||||
renderParagraphsInLists: true,
|
||||
getExtraPropsForNode: this.getExtraPropsForNode,
|
||||
});
|
||||
};
|
||||
|
||||
getExtraPropsForNode = (node: any) => {
|
||||
const extraProps: Record<string, any> = {
|
||||
continue: node.continue,
|
||||
index: node.index,
|
||||
};
|
||||
|
||||
if (node.type === 'image') {
|
||||
extraProps.reactChildren = node.react.children;
|
||||
extraProps.linkDestination = node.linkDestination;
|
||||
extraProps.size = node.size;
|
||||
}
|
||||
|
||||
return extraProps;
|
||||
};
|
||||
|
||||
computeTextStyle = (baseStyle: StyleProp<TextStyle>, context: any) => {
|
||||
const {textStyles} = this.props;
|
||||
type TextType = keyof typeof textStyles;
|
||||
const contextStyles: TextStyle[] = context.map((type: any) => textStyles[type as TextType]).filter((f: any) => f !== undefined);
|
||||
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
|
||||
};
|
||||
|
||||
renderText = ({context, literal}: MarkdownBaseRenderer) => {
|
||||
if (context.indexOf('image') !== -1) {
|
||||
// If this text is displayed, it will be styled by the image component
|
||||
return (
|
||||
<Text testID='markdown_text'>
|
||||
{literal}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
// Construct the text style based off of the parents of this node since RN's inheritance is limited
|
||||
const style = this.computeTextStyle(this.props.baseTextStyle, context);
|
||||
|
||||
return (
|
||||
<Text
|
||||
testID='markdown_text'
|
||||
style={style}
|
||||
>
|
||||
{literal}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
renderCodeSpan = ({context, literal}: MarkdownBaseRenderer) => {
|
||||
const {baseTextStyle, textStyles: {code}} = this.props;
|
||||
return <Text style={this.computeTextStyle([baseTextStyle, code], context)}>{literal}</Text>;
|
||||
};
|
||||
|
||||
renderImage = ({linkDestination, context, src, size}: MarkdownImageRenderer) => {
|
||||
if (!this.props.imagesMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.indexOf('table') !== -1) {
|
||||
// We have enough problems rendering images as is, so just render a link inside of a table
|
||||
return (
|
||||
<MarkdownTableImage
|
||||
disabled={this.props.disableGallery ?? Boolean(!this.props.location)}
|
||||
imagesMetadata={this.props.imagesMetadata}
|
||||
location={this.props.location}
|
||||
postId={this.props.postId!}
|
||||
source={src}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MarkdownImage
|
||||
disabled={this.props.disableGallery ?? Boolean(!this.props.location)}
|
||||
errorTextStyle={[this.computeTextStyle(this.props.baseTextStyle, context), this.props.textStyles.error]}
|
||||
layoutWidth={this.props.layoutWidth}
|
||||
linkDestination={linkDestination}
|
||||
imagesMetadata={this.props.imagesMetadata}
|
||||
isReplyPost={this.props.isReplyPost}
|
||||
location={this.props.location}
|
||||
postId={this.props.postId!}
|
||||
source={src}
|
||||
sourceSize={size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderAtMention = ({context, mentionName}: MarkdownAtMentionRenderer) => {
|
||||
if (this.props.disableAtMentions) {
|
||||
return this.renderText({context, literal: `@${mentionName}`});
|
||||
}
|
||||
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return (
|
||||
<AtMention
|
||||
disableAtChannelMentionHighlight={this.props.disableAtChannelMentionHighlight}
|
||||
mentionStyle={this.props.textStyles.mention}
|
||||
textStyle={[this.computeTextStyle(this.props.baseTextStyle, context), style.atMentionOpacity]}
|
||||
isSearchResult={this.props.isSearchResult}
|
||||
mentionName={mentionName}
|
||||
onPostPress={this.props.onPostPress}
|
||||
mentionKeys={this.props.mentionKeys}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderChannelLink = ({context, channelName}: MarkdownChannelMentionRenderer) => {
|
||||
if (this.props.disableChannelLink) {
|
||||
return this.renderText({context, literal: `~${channelName}`});
|
||||
}
|
||||
|
||||
return (
|
||||
<ChannelMention
|
||||
linkStyle={this.props.textStyles.link}
|
||||
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
|
||||
channelName={channelName}
|
||||
channelMentions={this.props.channelMentions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderEmoji = ({context, emojiName, literal}: MarkdownEmojiRenderer) => {
|
||||
return (
|
||||
<Emoji
|
||||
emojiName={emojiName}
|
||||
literal={literal}
|
||||
testID='markdown_emoji'
|
||||
textStyle={this.computeTextStyle(this.props.baseTextStyle, context)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderHashtag = ({context, hashtag}: {context: string[]; hashtag: string}) => {
|
||||
if (this.props.disableHashtags) {
|
||||
return this.renderText({context, literal: `#${hashtag}`});
|
||||
}
|
||||
|
||||
return (
|
||||
<Hashtag
|
||||
hashtag={hashtag}
|
||||
linkStyle={this.props.textStyles.link}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderParagraph = ({children, first}: {children: ReactElement[]; first: boolean}) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
const blockStyle = [style.block];
|
||||
if (!first) {
|
||||
blockStyle.push(this.props.blockStyles.adjacentParagraph);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={blockStyle}>
|
||||
<Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderHeading = ({children, level}: {children: ReactElement; level: string}) => {
|
||||
const {textStyles} = this.props;
|
||||
const containerStyle = [
|
||||
getStyleSheet(this.props.theme).block,
|
||||
textStyles[`heading${level}`],
|
||||
];
|
||||
const textStyle = textStyles[`heading${level}Text`];
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<Text style={textStyle}>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
renderCodeBlock = (props: any) => {
|
||||
// These sometimes include a trailing newline
|
||||
const content = props.literal.replace(/\n$/, '');
|
||||
|
||||
return (
|
||||
<MarkdownCodeBlock
|
||||
content={content}
|
||||
language={props.language}
|
||||
textStyle={this.props.textStyles.codeBlock}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderBlockQuote = ({children, ...otherProps}: any) => {
|
||||
return (
|
||||
<MarkdownBlockQuote
|
||||
iconStyle={this.props.blockStyles.quoteBlockIcon}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</MarkdownBlockQuote>
|
||||
);
|
||||
};
|
||||
|
||||
renderList = ({children, start, tight, type}: any) => {
|
||||
return (
|
||||
<MarkdownList
|
||||
ordered={type !== 'bullet'}
|
||||
start={start}
|
||||
tight={tight}
|
||||
>
|
||||
{children}
|
||||
</MarkdownList>
|
||||
);
|
||||
};
|
||||
|
||||
renderListItem = ({children, context, ...otherProps}: any) => {
|
||||
const level = context.filter((type: string) => type === 'list').length;
|
||||
|
||||
return (
|
||||
<MarkdownListItem
|
||||
bulletStyle={this.props.baseTextStyle}
|
||||
level={level}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</MarkdownListItem>
|
||||
);
|
||||
};
|
||||
|
||||
renderHardBreak = () => {
|
||||
return <Text>{'\n'}</Text>;
|
||||
};
|
||||
|
||||
renderThematicBreak = () => {
|
||||
return (
|
||||
<View
|
||||
style={this.props.blockStyles.horizontalRule}
|
||||
testID='markdown_thematic_break'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
renderSoftBreak = () => {
|
||||
return <Text>{'\n'}</Text>;
|
||||
};
|
||||
|
||||
renderHtml = (props: any) => {
|
||||
let rendered = this.renderText(props);
|
||||
|
||||
if (props.isBlock) {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
rendered = (
|
||||
<View style={style.block}>
|
||||
{rendered}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
renderTable = ({children, numColumns}: {children: ReactElement; numColumns: number}) => {
|
||||
return (
|
||||
<MarkdownTable
|
||||
numColumns={numColumns}
|
||||
theme={this.props.theme}
|
||||
>
|
||||
{children}
|
||||
</MarkdownTable>
|
||||
);
|
||||
};
|
||||
|
||||
renderTableRow = (args: MarkdownTableRowProps) => {
|
||||
return <MarkdownTableRow {...args}/>;
|
||||
};
|
||||
|
||||
renderTableCell = (args: MarkdownTableCellProps) => {
|
||||
return <MarkdownTableCell {...args}/>;
|
||||
};
|
||||
|
||||
renderLink = ({children, href}: {children: ReactElement; href: string}) => {
|
||||
return (
|
||||
<MarkdownLink href={href}>
|
||||
{children}
|
||||
</MarkdownLink>
|
||||
);
|
||||
};
|
||||
|
||||
renderEditedIndicator = ({context}: {context: string[]}) => {
|
||||
let spacer = '';
|
||||
if (context[0] === 'paragraph') {
|
||||
spacer = ' ';
|
||||
}
|
||||
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
const styles = [
|
||||
this.props.baseTextStyle,
|
||||
style.editedIndicatorText,
|
||||
];
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={styles}
|
||||
>
|
||||
{spacer}
|
||||
<FormattedText
|
||||
id='post_message_view.edited'
|
||||
defaultMessage='(edited)'
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
let ast = this.parser.parse(this.props.value.toString());
|
||||
|
||||
ast = combineTextNodes(ast);
|
||||
ast = addListItemIndices(ast);
|
||||
ast = pullOutImages(ast);
|
||||
if (this.props.mentionKeys) {
|
||||
ast = highlightMentions(ast, this.props.mentionKeys);
|
||||
}
|
||||
|
||||
if (this.props.isEdited) {
|
||||
const editIndicatorNode = new Node('edited_indicator');
|
||||
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
|
||||
ast.appendChild(editIndicatorNode);
|
||||
} else {
|
||||
const node = new Node('paragraph');
|
||||
node.appendChild(editIndicatorNode);
|
||||
|
||||
ast.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
return this.renderer.render(ast);
|
||||
}
|
||||
}
|
||||
|
||||
export default Markdown;
|
||||
503
app/components/markdown/markdown.tsx
Normal file
503
app/components/markdown/markdown.tsx
Normal file
@@ -0,0 +1,503 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {Parser, Node} from 'commonmark';
|
||||
import Renderer from 'commonmark-react-renderer';
|
||||
import React, {ReactElement, useRef} from 'react';
|
||||
import {Dimensions, GestureResponderEvent, Platform, StyleProp, Text, TextStyle, View} from 'react-native';
|
||||
|
||||
import Emoji from '@components/emoji';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import Hashtag from '@components/markdown/hashtag';
|
||||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getScheme} from '@utils/url';
|
||||
|
||||
import AtMention from './at_mention';
|
||||
import ChannelMention, {ChannelMentions} from './channel_mention';
|
||||
import MarkdownBlockQuote from './markdown_block_quote';
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
import MarkdownImage from './markdown_image';
|
||||
import MarkdownLatexCodeBlock from './markdown_latex_block';
|
||||
import MarkdownLatexInline from './markdown_latex_inline';
|
||||
import MarkdownLink from './markdown_link';
|
||||
import MarkdownList from './markdown_list';
|
||||
import MarkdownListItem from './markdown_list_item';
|
||||
import MarkdownTable from './markdown_table';
|
||||
import MarkdownTableCell, {MarkdownTableCellProps} from './markdown_table_cell';
|
||||
import MarkdownTableImage from './markdown_table_image';
|
||||
import MarkdownTableRow, {MarkdownTableRowProps} from './markdown_table_row';
|
||||
import {addListItemIndices, combineTextNodes, highlightMentions, pullOutImages} from './transform';
|
||||
|
||||
import type {
|
||||
MarkdownAtMentionRenderer, MarkdownBaseRenderer, MarkdownBlockStyles, MarkdownChannelMentionRenderer,
|
||||
MarkdownEmojiRenderer, MarkdownImageRenderer, MarkdownLatexRenderer, MarkdownTextStyles, UserMentionKey,
|
||||
} from '@typings/global/markdown';
|
||||
|
||||
type MarkdownProps = {
|
||||
autolinkedUrlSchemes?: string[];
|
||||
baseTextStyle: StyleProp<TextStyle>;
|
||||
blockStyles?: MarkdownBlockStyles;
|
||||
channelMentions?: ChannelMentions;
|
||||
disableAtChannelMentionHighlight?: boolean;
|
||||
disableAtMentions?: boolean;
|
||||
disableChannelLink?: boolean;
|
||||
disableGallery?: boolean;
|
||||
disableHashtags?: boolean;
|
||||
enableLatex: boolean;
|
||||
enableInlineLatex: boolean;
|
||||
imagesMetadata?: Record<string, PostImage>;
|
||||
isEdited?: boolean;
|
||||
isReplyPost?: boolean;
|
||||
isSearchResult?: boolean;
|
||||
layoutWidth?: number;
|
||||
location?: string;
|
||||
mentionKeys?: UserMentionKey[];
|
||||
minimumHashtagLength?: number;
|
||||
onPostPress?: (event: GestureResponderEvent) => void;
|
||||
postId?: string;
|
||||
textStyles?: MarkdownTextStyles;
|
||||
theme: Theme;
|
||||
value?: string | number;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
// Android has trouble giving text transparency depending on how it's nested,
|
||||
// so we calculate the resulting colour manually
|
||||
const editedOpacity = Platform.select({
|
||||
ios: 0.3,
|
||||
android: 1.0,
|
||||
});
|
||||
const editedColor = Platform.select({
|
||||
ios: theme.centerChannelColor,
|
||||
android: blendColors(theme.centerChannelBg, theme.centerChannelColor, 0.3),
|
||||
});
|
||||
|
||||
return {
|
||||
block: {
|
||||
alignItems: 'flex-start',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
editedIndicatorText: {
|
||||
color: editedColor,
|
||||
opacity: editedOpacity,
|
||||
},
|
||||
atMentionOpacity: {
|
||||
opacity: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const getExtraPropsForNode = (node: any) => {
|
||||
const extraProps: Record<string, any> = {
|
||||
continue: node.continue,
|
||||
index: node.index,
|
||||
};
|
||||
|
||||
if (node.type === 'image') {
|
||||
extraProps.reactChildren = node.react.children;
|
||||
extraProps.linkDestination = node.linkDestination;
|
||||
extraProps.size = node.size;
|
||||
}
|
||||
|
||||
return extraProps;
|
||||
};
|
||||
|
||||
const computeTextStyle = (textStyles: MarkdownTextStyles, baseStyle: StyleProp<TextStyle>, context: any) => {
|
||||
type TextType = keyof typeof textStyles;
|
||||
const contextStyles: TextStyle[] = context.map((type: any) => textStyles[type as TextType]).filter((f: any) => f !== undefined);
|
||||
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
|
||||
};
|
||||
|
||||
const Markdown = ({
|
||||
autolinkedUrlSchemes, baseTextStyle, blockStyles, channelMentions,
|
||||
disableAtChannelMentionHighlight = false, disableAtMentions = false, disableChannelLink = false,
|
||||
disableGallery = false, disableHashtags = false, enableInlineLatex, enableLatex,
|
||||
imagesMetadata, isEdited, isReplyPost, isSearchResult, layoutWidth,
|
||||
location, mentionKeys, minimumHashtagLength = 3, onPostPress, postId,
|
||||
textStyles = {}, theme, value = '',
|
||||
}: MarkdownProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const urlFilter = (url: string) => {
|
||||
const scheme = getScheme(url);
|
||||
return !scheme || autolinkedUrlSchemes?.indexOf(scheme) !== -1;
|
||||
};
|
||||
|
||||
const renderAtMention = ({context, mentionName}: MarkdownAtMentionRenderer) => {
|
||||
if (disableAtMentions) {
|
||||
return renderText({context, literal: `@${mentionName}`});
|
||||
}
|
||||
|
||||
return (
|
||||
<AtMention
|
||||
disableAtChannelMentionHighlight={disableAtChannelMentionHighlight}
|
||||
mentionStyle={textStyles.mention}
|
||||
textStyle={[computeTextStyle(textStyles, baseTextStyle, context), style.atMentionOpacity]}
|
||||
isSearchResult={isSearchResult}
|
||||
mentionName={mentionName}
|
||||
onPostPress={onPostPress}
|
||||
mentionKeys={mentionKeys}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderBlockQuote = ({children, ...otherProps}: any) => {
|
||||
return (
|
||||
<MarkdownBlockQuote
|
||||
iconStyle={blockStyles?.quoteBlockIcon}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</MarkdownBlockQuote>
|
||||
);
|
||||
};
|
||||
|
||||
const renderBreak = () => {
|
||||
return <Text>{'\n'}</Text>;
|
||||
};
|
||||
|
||||
const renderChannelLink = ({context, channelName}: MarkdownChannelMentionRenderer) => {
|
||||
if (disableChannelLink) {
|
||||
return renderText({context, literal: `~${channelName}`});
|
||||
}
|
||||
|
||||
return (
|
||||
<ChannelMention
|
||||
linkStyle={textStyles.link}
|
||||
textStyle={computeTextStyle(textStyles, baseTextStyle, context)}
|
||||
channelName={channelName}
|
||||
channelMentions={channelMentions}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCodeBlock = (props: any) => {
|
||||
// These sometimes include a trailing newline
|
||||
const content = props.literal.replace(/\n$/, '');
|
||||
|
||||
if (enableLatex && props.language === 'latex') {
|
||||
return (
|
||||
<MarkdownLatexCodeBlock
|
||||
content={content}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MarkdownCodeBlock
|
||||
content={content}
|
||||
language={props.language}
|
||||
textStyle={textStyles.codeBlock}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCodeSpan = ({context, literal}: MarkdownBaseRenderer) => {
|
||||
const {code} = textStyles;
|
||||
return <Text style={computeTextStyle(textStyles, [baseTextStyle, code], context)}>{literal}</Text>;
|
||||
};
|
||||
|
||||
const renderEditedIndicator = ({context}: {context: string[]}) => {
|
||||
let spacer = '';
|
||||
const styles = [baseTextStyle, style.editedIndicatorText];
|
||||
|
||||
if (context[0] === 'paragraph') {
|
||||
spacer = ' ';
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={styles}
|
||||
>
|
||||
{spacer}
|
||||
<FormattedText
|
||||
id='post_message_view.edited'
|
||||
defaultMessage='(edited)'
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const renderEmoji = ({context, emojiName, literal}: MarkdownEmojiRenderer) => {
|
||||
return (
|
||||
<Emoji
|
||||
emojiName={emojiName}
|
||||
literal={literal}
|
||||
testID='markdown_emoji'
|
||||
textStyle={computeTextStyle(textStyles, baseTextStyle, context)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderHashtag = ({context, hashtag}: {context: string[]; hashtag: string}) => {
|
||||
if (disableHashtags) {
|
||||
return renderText({context, literal: `#${hashtag}`});
|
||||
}
|
||||
|
||||
return (
|
||||
<Hashtag
|
||||
hashtag={hashtag}
|
||||
linkStyle={textStyles.link}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderHeading = ({children, level}: {children: ReactElement; level: string}) => {
|
||||
const containerStyle = [
|
||||
style.block,
|
||||
textStyles[`heading${level}`],
|
||||
];
|
||||
const textStyle = textStyles[`heading${level}Text`];
|
||||
return (
|
||||
<View style={containerStyle}>
|
||||
<Text style={textStyle}>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderHtml = (props: any) => {
|
||||
let rendered = renderText(props);
|
||||
|
||||
if (props.isBlock) {
|
||||
rendered = (
|
||||
<View style={style.block}>
|
||||
{rendered}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return rendered;
|
||||
};
|
||||
|
||||
const renderImage = ({linkDestination, context, src, size}: MarkdownImageRenderer) => {
|
||||
if (!imagesMetadata) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (context.indexOf('table') !== -1) {
|
||||
// We have enough problems rendering images as is, so just render a link inside of a table
|
||||
return (
|
||||
<MarkdownTableImage
|
||||
disabled={disableGallery ?? Boolean(!location)}
|
||||
imagesMetadata={imagesMetadata}
|
||||
location={location}
|
||||
postId={postId!}
|
||||
source={src}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MarkdownImage
|
||||
disabled={disableGallery ?? Boolean(!location)}
|
||||
errorTextStyle={[computeTextStyle(textStyles, baseTextStyle, context), textStyles.error]}
|
||||
layoutWidth={layoutWidth}
|
||||
linkDestination={linkDestination}
|
||||
imagesMetadata={imagesMetadata}
|
||||
isReplyPost={isReplyPost}
|
||||
location={location}
|
||||
postId={postId!}
|
||||
source={src}
|
||||
sourceSize={size}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLatexInline = ({context, latexCode}: MarkdownLatexRenderer) => {
|
||||
if (!enableInlineLatex) {
|
||||
return renderText({context, literal: `$${latexCode}$`});
|
||||
}
|
||||
|
||||
return (
|
||||
<Text>
|
||||
<MarkdownLatexInline
|
||||
content={latexCode}
|
||||
maxMathWidth={Dimensions.get('window').width * 0.75}
|
||||
theme={theme}
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const renderLink = ({children, href}: {children: ReactElement; href: string}) => {
|
||||
return (
|
||||
<MarkdownLink href={href}>
|
||||
{children}
|
||||
</MarkdownLink>
|
||||
);
|
||||
};
|
||||
|
||||
const renderList = ({children, start, tight, type}: any) => {
|
||||
return (
|
||||
<MarkdownList
|
||||
ordered={type !== 'bullet'}
|
||||
start={start}
|
||||
tight={tight}
|
||||
>
|
||||
{children}
|
||||
</MarkdownList>
|
||||
);
|
||||
};
|
||||
|
||||
const renderListItem = ({children, context, ...otherProps}: any) => {
|
||||
const level = context.filter((type: string) => type === 'list').length;
|
||||
|
||||
return (
|
||||
<MarkdownListItem
|
||||
bulletStyle={baseTextStyle}
|
||||
level={level}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</MarkdownListItem>
|
||||
);
|
||||
};
|
||||
|
||||
const renderParagraph = ({children, first}: {children: ReactElement[]; first: boolean}) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const blockStyle = [style.block];
|
||||
if (!first) {
|
||||
blockStyle.push(blockStyles?.adjacentParagraph);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={blockStyle}>
|
||||
<Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTable = ({children, numColumns}: {children: ReactElement; numColumns: number}) => {
|
||||
return (
|
||||
<MarkdownTable
|
||||
numColumns={numColumns}
|
||||
theme={theme}
|
||||
>
|
||||
{children}
|
||||
</MarkdownTable>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTableCell = (args: MarkdownTableCellProps) => {
|
||||
return <MarkdownTableCell {...args}/>;
|
||||
};
|
||||
|
||||
const renderTableRow = (args: MarkdownTableRowProps) => {
|
||||
return <MarkdownTableRow {...args}/>;
|
||||
};
|
||||
|
||||
const renderText = ({context, literal}: MarkdownBaseRenderer) => {
|
||||
if (context.indexOf('image') !== -1) {
|
||||
// If this text is displayed, it will be styled by the image component
|
||||
return (
|
||||
<Text testID='markdown_text'>
|
||||
{literal}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
// Construct the text style based off of the parents of this node since RN's inheritance is limited
|
||||
const styles = computeTextStyle(textStyles, baseTextStyle, context);
|
||||
|
||||
return (
|
||||
<Text
|
||||
testID='markdown_text'
|
||||
style={styles}
|
||||
>
|
||||
{literal}
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
const renderThematicBreak = () => {
|
||||
return (
|
||||
<View
|
||||
style={blockStyles?.horizontalRule}
|
||||
testID='markdown_thematic_break'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const createRenderer = () => {
|
||||
const renderers: any = {
|
||||
text: renderText,
|
||||
|
||||
emph: Renderer.forwardChildren,
|
||||
strong: Renderer.forwardChildren,
|
||||
del: Renderer.forwardChildren,
|
||||
code: renderCodeSpan,
|
||||
link: renderLink,
|
||||
image: renderImage,
|
||||
atMention: renderAtMention,
|
||||
channelLink: renderChannelLink,
|
||||
emoji: renderEmoji,
|
||||
hashtag: renderHashtag,
|
||||
latexinline: renderLatexInline,
|
||||
|
||||
paragraph: renderParagraph,
|
||||
heading: renderHeading,
|
||||
codeBlock: renderCodeBlock,
|
||||
blockQuote: renderBlockQuote,
|
||||
|
||||
list: renderList,
|
||||
item: renderListItem,
|
||||
|
||||
hardBreak: renderBreak,
|
||||
thematicBreak: renderThematicBreak,
|
||||
softBreak: renderBreak,
|
||||
|
||||
htmlBlock: renderHtml,
|
||||
htmlInline: renderHtml,
|
||||
|
||||
table: renderTable,
|
||||
table_row: renderTableRow,
|
||||
table_cell: renderTableCell,
|
||||
|
||||
mention_highlight: Renderer.forwardChildren,
|
||||
|
||||
editedIndicator: renderEditedIndicator,
|
||||
};
|
||||
|
||||
return new Renderer({
|
||||
renderers,
|
||||
renderParagraphsInLists: true,
|
||||
getExtraPropsForNode,
|
||||
});
|
||||
};
|
||||
|
||||
const parser = useRef(new Parser({urlFilter, minimumHashtagLength})).current;
|
||||
const renderer = useRef(createRenderer()).current;
|
||||
let ast = parser.parse(value.toString());
|
||||
|
||||
ast = combineTextNodes(ast);
|
||||
ast = addListItemIndices(ast);
|
||||
ast = pullOutImages(ast);
|
||||
if (mentionKeys) {
|
||||
ast = highlightMentions(ast, mentionKeys);
|
||||
}
|
||||
|
||||
if (isEdited) {
|
||||
const editIndicatorNode = new Node('edited_indicator');
|
||||
if (ast.lastChild && ['heading', 'paragraph'].includes(ast.lastChild.type)) {
|
||||
ast.appendChild(editIndicatorNode);
|
||||
} else {
|
||||
const node = new Node('paragraph');
|
||||
node.appendChild(editIndicatorNode);
|
||||
|
||||
ast.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
return renderer.render(ast) as JSX.Element;
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -71,7 +71,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
||||
const insets = useSafeAreaInsets();
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const handlePress = preventDoubleTap(() => {
|
||||
const handlePress = useCallback(preventDoubleTap(() => {
|
||||
const screen = Screens.CODE;
|
||||
const passProps = {
|
||||
code: content,
|
||||
@@ -102,7 +102,7 @@ const MarkdownCodeBlock = ({language = '', content, textStyle}: MarkdownCodeBloc
|
||||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
});
|
||||
}), [content, intl.locale, language]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
|
||||
@@ -81,13 +81,7 @@ const MarkdownImage = ({
|
||||
const originalSize = getMarkdownImageSize(isReplyPost, isTablet, sourceSize, metadata, layoutWidth);
|
||||
const serverUrl = useServerUrl();
|
||||
const galleryIdentifier = `${postId}-${genericFileId}-${location}`;
|
||||
const uri = useMemo(() => {
|
||||
if (source.startsWith('/')) {
|
||||
return serverUrl + source;
|
||||
}
|
||||
|
||||
return source;
|
||||
}, [source, serverUrl]);
|
||||
const uri = source.startsWith('/') ? serverUrl + source : source;
|
||||
|
||||
const fileInfo = useMemo(() => {
|
||||
const link = decodeURIComponent(uri);
|
||||
@@ -119,7 +113,7 @@ const MarkdownImage = ({
|
||||
type: 'image',
|
||||
};
|
||||
openGalleryAtIndex(galleryIdentifier, 0, [item]);
|
||||
}, []);
|
||||
}, [fileInfo]);
|
||||
|
||||
const {ref, onGestureEvent, styles} = useGalleryItem(
|
||||
galleryIdentifier,
|
||||
@@ -188,7 +182,7 @@ const MarkdownImage = ({
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig, intl, insets, theme]);
|
||||
}, [managedConfig, intl.locale, insets.bottom, theme]);
|
||||
|
||||
const handleOnError = useCallback(() => {
|
||||
setFailed(true);
|
||||
|
||||
222
app/components/markdown/markdown_latex_block/index.tsx
Normal file
222
app/components/markdown/markdown_latex_block/index.tsx
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useManagedConfig} from '@mattermost/react-native-emm';
|
||||
import Clipboard from '@react-native-community/clipboard';
|
||||
import React, {useCallback, useMemo} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, View, Text, StyleSheet, Platform} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import SlideUpPanelItem, {ITEM_HEIGHT} from '@components/slide_up_panel_item';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {Screens} from '@constants';
|
||||
import {bottomSheet, dismissBottomSheet, goToScreen} from '@screens/navigation';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {getHighlightLanguageName} from '@utils/markdown';
|
||||
import {splitLatexCodeInLines} from '@utils/markdown/latex';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
const MAX_LINES = 2;
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const codeVerticalPadding = Platform.select({
|
||||
ios: 4,
|
||||
android: 0,
|
||||
});
|
||||
|
||||
return {
|
||||
bottomSheet: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
borderColor: changeOpacity(theme.centerChannelColor, 0.15),
|
||||
borderRadius: 3,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
flexDirection: 'row',
|
||||
flex: 1,
|
||||
},
|
||||
rightColumn: {
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
paddingLeft: 6,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
code: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
marginLeft: 5,
|
||||
paddingVertical: codeVerticalPadding,
|
||||
},
|
||||
plusMoreLinesText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.4),
|
||||
...typography('Body', 50),
|
||||
marginTop: 2,
|
||||
},
|
||||
language: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.sidebarHeaderBg,
|
||||
justifyContent: 'center',
|
||||
opacity: 0.8,
|
||||
padding: 6,
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: 0,
|
||||
},
|
||||
languageText: {
|
||||
color: theme.sidebarHeaderTextColor,
|
||||
...typography('Body', 75),
|
||||
},
|
||||
errorText: {
|
||||
...typography('Body', 100),
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const LatexCodeBlock = ({content, theme}: Props) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
const managedConfig = useManagedConfig<ManagedConfig>();
|
||||
const styles = getStyleSheet(theme);
|
||||
const languageDisplayName = getHighlightLanguageName('latex');
|
||||
|
||||
const split = useMemo(() => {
|
||||
const lines = splitLatexCodeInLines(content);
|
||||
const numberOfLines = lines.length;
|
||||
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
return {
|
||||
content: lines.slice(0, MAX_LINES),
|
||||
numberOfLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
lines,
|
||||
numberOfLines,
|
||||
};
|
||||
}, [content]);
|
||||
|
||||
const handlePress = useCallback(preventDoubleTap(() => {
|
||||
const screen = Screens.LATEX;
|
||||
const passProps = {
|
||||
content,
|
||||
};
|
||||
const title = intl.formatMessage({
|
||||
id: 'mobile.routes.code',
|
||||
defaultMessage: '{language} Code',
|
||||
}, {
|
||||
language: languageDisplayName,
|
||||
});
|
||||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
}), [content, languageDisplayName, intl.locale]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
const renderContent = () => {
|
||||
return (
|
||||
<View
|
||||
testID='at_mention.bottom_sheet'
|
||||
style={styles.bottomSheet}
|
||||
>
|
||||
<SlideUpPanelItem
|
||||
icon='content-copy'
|
||||
onPress={() => {
|
||||
dismissBottomSheet();
|
||||
Clipboard.setString(content);
|
||||
}}
|
||||
testID='at_mention.bottom_sheet.copy_code'
|
||||
text={intl.formatMessage({id: 'mobile.markdown.code.copy_code', defaultMessage: 'Copy Code'})}
|
||||
/>
|
||||
<SlideUpPanelItem
|
||||
destructive={true}
|
||||
icon='cancel'
|
||||
onPress={dismissBottomSheet}
|
||||
testID='at_mention.bottom_sheet.cancel'
|
||||
text={intl.formatMessage({id: 'mobile.post.cancel', defaultMessage: 'Cancel'})}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
bottomSheet({
|
||||
closeButtonId: 'close-code-block',
|
||||
renderContent,
|
||||
snapPoints: [bottomSheetSnapPoint(2, ITEM_HEIGHT, insets.bottom), 10],
|
||||
title: intl.formatMessage({id: 'post.options.title', defaultMessage: 'Options'}),
|
||||
theme,
|
||||
});
|
||||
}
|
||||
}, [managedConfig?.copyAndPasteProtection, intl, insets, theme]);
|
||||
|
||||
const onRenderErrorMessage = useCallback(({error}: {error: Error}) => {
|
||||
return <Text style={styles.errorText}>{'Render error: ' + error.message}</Text>;
|
||||
}, []);
|
||||
|
||||
let plusMoreLines = null;
|
||||
if (split.numberOfLines > MAX_LINES) {
|
||||
plusMoreLines = (
|
||||
<FormattedText
|
||||
style={styles.plusMoreLinesText}
|
||||
id='mobile.markdown.code.plusMoreLines'
|
||||
defaultMessage='+{count, number} more {count, plural, one {line} other {lines}}'
|
||||
values={{
|
||||
count: split.numberOfLines - MAX_LINES,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note on the error behavior of math view:
|
||||
* - onError returns an Error object
|
||||
* - renderError returns an options object with an error attribute that contains the real Error.
|
||||
*/
|
||||
return (
|
||||
<TouchableWithFeedback
|
||||
onPress={handlePress}
|
||||
onLongPress={handleLongPress}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.rightColumn}>
|
||||
{split.lines?.map((latexCode) => (
|
||||
<View
|
||||
style={styles.code}
|
||||
key={latexCode}
|
||||
>
|
||||
<MathView
|
||||
math={latexCode}
|
||||
renderError={onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{plusMoreLines}
|
||||
</View>
|
||||
<View style={styles.language}>
|
||||
<Text style={styles.languageText}>
|
||||
{languageDisplayName}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatexCodeBlock;
|
||||
59
app/components/markdown/markdown_latex_inline/index.tsx
Normal file
59
app/components/markdown/markdown_latex_inline/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
maxMathWidth: number | string;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
type MathViewErrorProps = {
|
||||
error: Error;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
mathStyle: {
|
||||
marginBottom: Platform.select({default: -10, ios: 2.5}),
|
||||
},
|
||||
viewStyle: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
errorText: {
|
||||
flexDirection: 'row',
|
||||
color: theme.errorTextColor,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const LatexInline = ({content, maxMathWidth, theme}: Props) => {
|
||||
const style = getStyleSheet(theme);
|
||||
|
||||
const onRenderErrorMessage = (errorMsg: MathViewErrorProps) => {
|
||||
return <Text style={style.errorText}>{'Latex render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={style.viewStyle}
|
||||
key={content}
|
||||
>
|
||||
<MathView
|
||||
style={[style.mathStyle, {maxWidth: maxMathWidth || '100%'}]}
|
||||
math={content}
|
||||
renderError={onRenderErrorMessage}
|
||||
resizeMode='contain'
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatexInline;
|
||||
@@ -36,6 +36,19 @@ const style = StyleSheet.create({
|
||||
},
|
||||
});
|
||||
|
||||
const parseLinkLiteral = (literal: string) => {
|
||||
let nextLiteral = literal;
|
||||
|
||||
const WWW_REGEX = /\b^(?:www.)/i;
|
||||
if (nextLiteral.match(WWW_REGEX)) {
|
||||
nextLiteral = literal.replace(WWW_REGEX, 'www.');
|
||||
}
|
||||
|
||||
const parsed = urlParse(nextLiteral, {});
|
||||
|
||||
return parsed.href;
|
||||
};
|
||||
|
||||
const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteURL}: MarkdownLinkProps) => {
|
||||
const intl = useIntl();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -45,7 +58,7 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
||||
|
||||
const {formatMessage} = intl;
|
||||
|
||||
const handlePress = preventDoubleTap(async () => {
|
||||
const handlePress = useCallback(preventDoubleTap(async () => {
|
||||
const url = normalizeProtocol(href);
|
||||
|
||||
if (!url) {
|
||||
@@ -80,22 +93,9 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
||||
|
||||
tryOpenURL(url, onError);
|
||||
}
|
||||
});
|
||||
}), [href, intl.locale, serverUrl, siteURL]);
|
||||
|
||||
const parseLinkLiteral = (literal: string) => {
|
||||
let nextLiteral = literal;
|
||||
|
||||
const WWW_REGEX = /\b^(?:www.)/i;
|
||||
if (nextLiteral.match(WWW_REGEX)) {
|
||||
nextLiteral = literal.replace(WWW_REGEX, 'www.');
|
||||
}
|
||||
|
||||
const parsed = urlParse(nextLiteral, {});
|
||||
|
||||
return parsed.href;
|
||||
};
|
||||
|
||||
const parseChildren = () => {
|
||||
const parseChildren = useCallback(() => {
|
||||
return Children.map(children, (child: ReactElement) => {
|
||||
if (!child.props.literal || typeof child.props.literal !== 'string' || (child.props.context && child.props.context.length && !child.props.context.includes('link'))) {
|
||||
return child;
|
||||
@@ -115,7 +115,7 @@ const MarkdownLink = ({children, experimentalNormalizeMarkdownLinks, href, siteU
|
||||
...otherChildProps,
|
||||
};
|
||||
});
|
||||
};
|
||||
}, [children]);
|
||||
|
||||
const handleLongPress = useCallback(() => {
|
||||
if (managedConfig?.copyAndPasteProtection !== 'true') {
|
||||
|
||||
@@ -86,7 +86,7 @@ const MarkTableImage = ({disabled, imagesMetadata, location, postId, serverURL,
|
||||
type: 'image',
|
||||
};
|
||||
openGalleryAtIndex(galleryIdentifier, 0, [item]);
|
||||
}, []);
|
||||
}, [metadata, source, serverURL, currentServerUrl, postId]);
|
||||
|
||||
const {ref, onGestureEvent, styles} = useGalleryItem(
|
||||
galleryIdentifier,
|
||||
|
||||
@@ -27,6 +27,7 @@ export const HOME = 'Home';
|
||||
export const INTEGRATION_SELECTOR = 'IntegrationSelector';
|
||||
export const INTERACTIVE_DIALOG = 'InteractiveDialog';
|
||||
export const IN_APP_NOTIFICATION = 'InAppNotification';
|
||||
export const LATEX = 'Latex';
|
||||
export const LOGIN = 'Login';
|
||||
export const MENTIONS = 'Mentions';
|
||||
export const MFA = 'MFA';
|
||||
@@ -71,6 +72,7 @@ export default {
|
||||
INTEGRATION_SELECTOR,
|
||||
INTERACTIVE_DIALOG,
|
||||
IN_APP_NOTIFICATION,
|
||||
LATEX,
|
||||
LOGIN,
|
||||
MENTIONS,
|
||||
MFA,
|
||||
|
||||
@@ -132,6 +132,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
||||
);
|
||||
return;
|
||||
}
|
||||
case Screens.LATEX:
|
||||
screen = withServerDatabase(require('@screens/latex').default);
|
||||
break;
|
||||
case Screens.LOGIN:
|
||||
screen = withIntl(require('@screens/login').default);
|
||||
break;
|
||||
|
||||
99
app/screens/latex/index.tsx
Normal file
99
app/screens/latex/index.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Platform, ScrollView, Text, View} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
import {SafeAreaView, Edge} from 'react-native-safe-area-context';
|
||||
|
||||
import {useTheme} from '@context/theme';
|
||||
import {splitLatexCodeInLines} from '@utils/markdown/latex';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const edges: Edge[] = ['left', 'right'];
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
const codeVerticalPadding = Platform.select({
|
||||
ios: 4,
|
||||
android: 0,
|
||||
});
|
||||
|
||||
return {
|
||||
scrollContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
minHeight: '100%',
|
||||
},
|
||||
scrollCode: {
|
||||
minHeight: '100%',
|
||||
flexDirection: 'column',
|
||||
paddingLeft: 10,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
code: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
marginHorizontal: 5,
|
||||
paddingVertical: codeVerticalPadding,
|
||||
},
|
||||
errorText: {
|
||||
...typography('Body', 100),
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const Latex = ({content}: Props) => {
|
||||
const theme = useTheme();
|
||||
const style = getStyleSheet(theme);
|
||||
const lines = splitLatexCodeInLines(content);
|
||||
|
||||
const onErrorMessage = (errorMsg: Error) => {
|
||||
return <Text style={style.errorText}>{'Error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
const onRenderErrorMessage = ({error}: {error: Error}) => {
|
||||
return <Text style={style.errorText}>{'Render error: ' + error.message}</Text>;
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={edges}
|
||||
style={style.scrollContainer}
|
||||
>
|
||||
<ScrollView
|
||||
style={style.scrollContainer}
|
||||
contentContainerStyle={style.container}
|
||||
>
|
||||
<ScrollView
|
||||
style={style.scrollContainer}
|
||||
contentContainerStyle={style.scrollCode}
|
||||
horizontal={true}
|
||||
>
|
||||
{lines.map((latexCode) => (
|
||||
<View
|
||||
style={style.code}
|
||||
key={latexCode}
|
||||
>
|
||||
<MathView
|
||||
math={latexCode}
|
||||
onError={onErrorMessage}
|
||||
renderError={onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
};
|
||||
|
||||
export default Latex;
|
||||
66
app/utils/markdown/latex.test.ts
Normal file
66
app/utils/markdown/latex.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
|
||||
import {splitLatexCodeInLines} from './latex';
|
||||
|
||||
describe('LatexUtilTest', () => {
|
||||
test('Simple lines test', () => {
|
||||
const content = '\\frac{1}{2} = 0.5 \\\\ \\pi == 3';
|
||||
|
||||
const result = splitLatexCodeInLines(content);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[0]).toEqual('\\frac{1}{2} = 0.5');
|
||||
expect(result[1]).toEqual('\\pi == 3');
|
||||
});
|
||||
|
||||
test('Multi line with cases test', () => {
|
||||
const content = `b_n=\\frac{1}{\\pi}\\int\\limits_{-\\pi}^{\\pi}f(x)\\sin nx\\,\\mathrm{d}x=\\frac{1}{\\pi}\\int\\limits_{-\\pi}^{\\pi}x^2\\sin nx\\,\\mathrm{d}x\\\\
|
||||
X(m, n) = \\left.
|
||||
\\begin{cases}
|
||||
x(n), & \\text{for } 0 \\leq n \\leq 1 \\\\
|
||||
x(n - 1), & \\text{for } 0 \\leq n \\leq 1 \\\\
|
||||
x(n - 1), & \\text{for } 0 \\leq n \\leq 1
|
||||
\\end{cases} \\right\\} = xy\\\\
|
||||
\\lim_{a\\to \\infty} \\tfrac{1}{a}\\\\
|
||||
\\lim_{a \\underset{>}{\\to} 0} \\frac{1}{a}\\\\
|
||||
x = a_0 + \\frac{1}{a_1 + \\frac{1}{a_2 + \\frac{1}{a_3 + a_4}}}`;
|
||||
|
||||
const result = splitLatexCodeInLines(content);
|
||||
|
||||
expect(result.length).toEqual(5);
|
||||
expect(result[0]).toEqual('b_n=\\frac{1}{\\pi}\\int\\limits_{-\\pi}^{\\pi}f(x)\\sin nx\\,\\mathrm{d}x=\\frac{1}{\\pi}\\int\\limits_{-\\pi}^{\\pi}x^2\\sin nx\\,\\mathrm{d}x');
|
||||
expect(result[1]).toEqual(`X(m, n) = \\left.
|
||||
\\begin{cases}
|
||||
x(n), & \\text{for } 0 \\leq n \\leq 1 \\\\
|
||||
x(n - 1), & \\text{for } 0 \\leq n \\leq 1 \\\\
|
||||
x(n - 1), & \\text{for } 0 \\leq n \\leq 1
|
||||
\\end{cases} \\right\\} = xy`);
|
||||
expect(result[2]).toEqual('\\lim_{a\\to \\infty} \\tfrac{1}{a}');
|
||||
expect(result[3]).toEqual('\\lim_{a \\underset{>}{\\to} 0} \\frac{1}{a}');
|
||||
expect(result[4]).toEqual('x = a_0 + \\frac{1}{a_1 + \\frac{1}{a_2 + \\frac{1}{a_3 + a_4}}}');
|
||||
});
|
||||
|
||||
test('Escaped bracket test', () => {
|
||||
const content = 'test = \\frac{1\\{}{2} = \\alpha \\\\ line = 2';
|
||||
|
||||
const result = splitLatexCodeInLines(content);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result[0]).toEqual('test = \\frac{1\\{}{2} = \\alpha');
|
||||
expect(result[1]).toEqual('line = 2');
|
||||
});
|
||||
|
||||
test('Escaped begin and end statement', () => {
|
||||
const content = 'test = \\\\begin \\\\ line = 2';
|
||||
|
||||
const result = splitLatexCodeInLines(content);
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
expect(result[0]).toEqual('test =');
|
||||
expect(result[1]).toEqual('begin');
|
||||
expect(result[2]).toEqual('line = 2');
|
||||
});
|
||||
});
|
||||
97
app/utils/markdown/latex.ts
Normal file
97
app/utils/markdown/latex.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/**
|
||||
* Splits up latex code in an array consisting of each line of latex code.
|
||||
* A latex linebreak is denoted by `\\`.
|
||||
* A line is not broken in 2 cases:
|
||||
* - The linebreak is in between brackets.
|
||||
* - The linebreak occurs inbetween a `\begin` and `\end` statement.
|
||||
*/
|
||||
|
||||
export function splitLatexCodeInLines(content: string): string[] {
|
||||
let outLines = content.split('\\\\');
|
||||
|
||||
let i = 0;
|
||||
while (i < outLines.length) {
|
||||
if (testLatexLineBreak(outLines[i])) { //Line has no linebreak in between brackets
|
||||
i += 1;
|
||||
} else if (i < outLines.length - 2) {
|
||||
outLines = outLines.slice(0, i).concat([outLines[i] + '\\\\' + outLines[i + 1]], outLines.slice(i + 2));
|
||||
} else if (i === outLines.length - 2) {
|
||||
outLines = outLines.slice(0, i).concat([outLines[i] + '\\\\' + outLines[i + 1]]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return outLines.map((line) => line.trim());
|
||||
}
|
||||
|
||||
function testLatexLineBreak(latexCode: string): boolean {
|
||||
/**
|
||||
* These checks are special because they need to check if the braces and statements are not escaped
|
||||
*/
|
||||
|
||||
//Check for cases
|
||||
let beginCases = 0;
|
||||
let endCases = 0;
|
||||
|
||||
let i = 0;
|
||||
while (i < latexCode.length) {
|
||||
const firstBegin = latexCode.indexOf('\\begin{', i);
|
||||
const firstEnd = latexCode.indexOf('\\end{', i);
|
||||
|
||||
if (firstBegin === -1 && firstEnd === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstBegin !== -1 && (firstBegin < firstEnd || firstEnd === -1)) {
|
||||
if (latexCode[firstBegin - 1] !== '\\') {
|
||||
beginCases += 1;
|
||||
}
|
||||
i = firstBegin + '\\begin{'.length;
|
||||
} else {
|
||||
if (latexCode[firstEnd - 1] !== '\\') {
|
||||
endCases += 1;
|
||||
}
|
||||
i = firstEnd + '\\end{'.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (beginCases !== endCases) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//Check for braces
|
||||
let curlyOpenCases = 0;
|
||||
let curlyCloseCases = 0;
|
||||
|
||||
i = 0;
|
||||
while (i < latexCode.length) {
|
||||
const firstBegin = latexCode.indexOf('{', i);
|
||||
const firstEnd = latexCode.indexOf('}', i);
|
||||
|
||||
if (firstBegin === -1 && firstEnd === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (firstBegin !== -1 && (firstBegin < firstEnd || firstEnd === -1)) {
|
||||
if (latexCode[firstBegin - 1] !== '\\') {
|
||||
curlyOpenCases += 1;
|
||||
}
|
||||
i = firstBegin + 1;
|
||||
} else {
|
||||
if (latexCode[firstEnd - 1] !== '\\') {
|
||||
curlyCloseCases += 1;
|
||||
}
|
||||
i = firstEnd + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (curlyOpenCases !== curlyCloseCases) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -747,6 +747,7 @@
|
||||
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
|
||||
"${PODS_ROOT}/YoutubePlayer-in-WKWebView/WKYTPlayerView/WKYTPlayerView.bundle",
|
||||
"${PODS_CONFIGURATION_BUILD_DIR}/iosMath/mathFonts.bundle",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputPaths = (
|
||||
@@ -768,6 +769,7 @@
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/WKYTPlayerView.bundle",
|
||||
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/mathFonts.bundle",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
||||
@@ -78,6 +78,7 @@ PODS:
|
||||
- glog (0.3.5)
|
||||
- hermes-engine (0.11.0)
|
||||
- HMSegmentedControl (1.5.6)
|
||||
- iosMath (0.9.4)
|
||||
- jail-monkey (2.6.0):
|
||||
- React-Core
|
||||
- libevent (2.1.12)
|
||||
@@ -469,6 +470,8 @@ PODS:
|
||||
- React-Core
|
||||
- RNLocalize (2.2.1):
|
||||
- React-Core
|
||||
- RNMathView (1.0.0):
|
||||
- iosMath
|
||||
- RNPermissions (3.3.1):
|
||||
- React-Core
|
||||
- RNReactNativeHapticFeedback (1.13.1):
|
||||
@@ -632,6 +635,7 @@ DEPENDENCIES:
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNKeychain (from `../node_modules/react-native-keychain`)
|
||||
- RNLocalize (from `../node_modules/react-native-localize`)
|
||||
- RNMathView (from `../node_modules/react-native-math-view/ios`)
|
||||
- RNPermissions (from `../node_modules/react-native-permissions`)
|
||||
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
@@ -664,6 +668,7 @@ SPEC REPOS:
|
||||
- fmt
|
||||
- hermes-engine
|
||||
- HMSegmentedControl
|
||||
- iosMath
|
||||
- libevent
|
||||
- libwebp
|
||||
- OpenSSL-Universal
|
||||
@@ -811,6 +816,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-keychain"
|
||||
RNLocalize:
|
||||
:path: "../node_modules/react-native-localize"
|
||||
RNMathView:
|
||||
:path: "../node_modules/react-native-math-view/ios"
|
||||
RNPermissions:
|
||||
:path: "../node_modules/react-native-permissions"
|
||||
RNReactNativeHapticFeedback:
|
||||
@@ -865,6 +872,7 @@ SPEC CHECKSUMS:
|
||||
glog: 476ee3e89abb49e07f822b48323c51c57124b572
|
||||
hermes-engine: 84e3af1ea01dd7351ac5d8689cbbea1f9903ffc3
|
||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||
iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba
|
||||
jail-monkey: 07b83767601a373db876e939b8dbf3f5eb15f073
|
||||
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
|
||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
||||
@@ -928,6 +936,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: 6e757e487a4834e7280e98e9bac66d2d9c575e9c
|
||||
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
|
||||
RNLocalize: cbcb55d0e19c78086ea4eea20e03fe8000bbbced
|
||||
RNMathView: 4c8a3c081fa671ab3136c51fa0bdca7ffb708bd5
|
||||
RNPermissions: 34d678157c800b25b22a488e4d8babb57456e796
|
||||
RNReactNativeHapticFeedback: 4085973f5a38b40d3c6793a3ee5724773eae045e
|
||||
RNReanimated: 89a32ebf01d2dac2eb35b3b1628952f626db93d7
|
||||
|
||||
@@ -20,6 +20,6 @@ module.exports = {
|
||||
'<rootDir>/dist/assets/images/video_player/$1@2x.png',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(@react-native|react-native)|jail-monkey|@sentry/react-native|@react-native-community/cameraroll|react-clone-referenced-element|@react-native-community|react-navigation|@react-navigation/.*|validator|react-syntax-highlighter/.*)',
|
||||
'node_modules/(?!(@react-native|react-native)|jail-monkey|@sentry/react-native|@react-native-community/cameraroll|react-clone-referenced-element|@react-native-community|react-navigation|@react-navigation/.*|validator|react-syntax-highlighter/.*|hast-util-from-selector|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|zwitch)',
|
||||
],
|
||||
};
|
||||
|
||||
285
package-lock.json
generated
285
package-lock.json
generated
@@ -71,6 +71,7 @@
|
||||
"react-native-keychain": "8.0.0",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-localize": "2.2.1",
|
||||
"react-native-math-view": "3.9.5",
|
||||
"react-native-navigation": "7.26.0",
|
||||
"react-native-neomorph-shadows": "1.1.2",
|
||||
"react-native-notifications": "4.2.4",
|
||||
@@ -8896,6 +8897,11 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-selector-parser": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
|
||||
"integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
@@ -11988,6 +11994,76 @@
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-selector/-/hast-util-from-selector-2.0.0.tgz",
|
||||
"integrity": "sha512-ynzm+z7xEecWF8DvnJ5onpGIsfmXphKRsZUnWCfinKwP+fL1/2mYW1nWOVife61syQpF74j4h/57BT6e5niDwA==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"css-selector-parser": "^1.0.0",
|
||||
"hastscript": "^7.0.0",
|
||||
"zwitch": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector/node_modules/comma-separated-tokens": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz",
|
||||
"integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector/node_modules/hast-util-parse-selector": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz",
|
||||
"integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector/node_modules/hastscript": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz",
|
||||
"integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==",
|
||||
"dependencies": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-parse-selector": "^3.0.0",
|
||||
"property-information": "^6.0.0",
|
||||
"space-separated-tokens": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector/node_modules/property-information": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
|
||||
"integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-from-selector/node_modules/space-separated-tokens": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz",
|
||||
"integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-parse-selector": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||
@@ -15806,6 +15882,17 @@
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
},
|
||||
"node_modules/mathjax-full": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.0.tgz",
|
||||
"integrity": "sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==",
|
||||
"dependencies": {
|
||||
"esm": "^3.2.25",
|
||||
"mhchemparser": "^4.1.0",
|
||||
"mj-context-menu": "^0.6.1",
|
||||
"speech-rule-engine": "^3.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -16752,6 +16839,11 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/mhchemparser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz",
|
||||
"integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA=="
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -16886,6 +16978,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mj-context-menu": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
|
||||
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
@@ -19244,6 +19341,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-math-view": {
|
||||
"version": "3.9.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-math-view/-/react-native-math-view-3.9.5.tgz",
|
||||
"integrity": "sha512-UJxrisNafszfqIW+utoSDylb72SkZ92cKz1IfE5Dm0s+uIaHxOxepF2DdRbktAV8c0FEFllnXfErcGdh8sfIBw==",
|
||||
"dependencies": {
|
||||
"hast-util-from-selector": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mathjax-full": "^3.1.4",
|
||||
"transformation-matrix": "^2.8.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": "*",
|
||||
"react-native-svg": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-navigation": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-7.26.0.tgz",
|
||||
@@ -20832,6 +20944,27 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/speech-rule-engine": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz",
|
||||
"integrity": "sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==",
|
||||
"dependencies": {
|
||||
"commander": ">=7.0.0",
|
||||
"wicked-good-xpath": "^1.3.0",
|
||||
"xmldom-sre": "^0.1.31"
|
||||
},
|
||||
"bin": {
|
||||
"sre": "bin/sre"
|
||||
}
|
||||
},
|
||||
"node_modules/speech-rule-engine/node_modules/commander": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
|
||||
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
@@ -21712,6 +21845,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/transformation-matrix": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.11.1.tgz",
|
||||
"integrity": "sha512-srlkTrmetYwTBJ1RHdukkwJ8S8D+2JjgSb1DbvmTwj+DsIpCpRYHbWgOXe/Ql2rX37WqlKLIgidpYlHPGsrwgA==",
|
||||
"funding": {
|
||||
"url": "https://www.paypal.me/chrvadala/25"
|
||||
}
|
||||
},
|
||||
"node_modules/traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
@@ -23147,6 +23288,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wicked-good-xpath": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
|
||||
"integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w="
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
@@ -23357,6 +23503,14 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmldom-sre": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
|
||||
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==",
|
||||
"engines": {
|
||||
"node": ">=0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/xregexp": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.0.tgz",
|
||||
@@ -23504,6 +23658,15 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
},
|
||||
"node_modules/zwitch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
"integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -30100,6 +30263,11 @@
|
||||
"nth-check": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"css-selector-parser": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-1.4.1.tgz",
|
||||
"integrity": "sha512-HYPSb7y/Z7BNDCOrakL4raGO2zltZkbeXyAd6Tg9obzix6QhzxCotdBl6VT0Dv4vZfJGVz3WL/xaEI9Ly3ul0g=="
|
||||
},
|
||||
"css-tree": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz",
|
||||
@@ -32482,6 +32650,54 @@
|
||||
"minimalistic-assert": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hast-util-from-selector": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-from-selector/-/hast-util-from-selector-2.0.0.tgz",
|
||||
"integrity": "sha512-ynzm+z7xEecWF8DvnJ5onpGIsfmXphKRsZUnWCfinKwP+fL1/2mYW1nWOVife61syQpF74j4h/57BT6e5niDwA==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"css-selector-parser": "^1.0.0",
|
||||
"hastscript": "^7.0.0",
|
||||
"zwitch": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"comma-separated-tokens": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz",
|
||||
"integrity": "sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg=="
|
||||
},
|
||||
"hast-util-parse-selector": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.0.tgz",
|
||||
"integrity": "sha512-AyjlI2pTAZEOeu7GeBPZhROx0RHBnydkQIXlhnFzDi0qfXTmGUWoCYZtomHbrdrheV4VFUlPcfJ6LMF5T6sQzg==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"hastscript": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.0.2.tgz",
|
||||
"integrity": "sha512-uA8ooUY4ipaBvKcMuPehTAB/YfFLSSzCwFSwT6ltJbocFUKH/GDHLN+tflq7lSRf9H86uOuxOFkh1KgIy3Gg2g==",
|
||||
"requires": {
|
||||
"@types/hast": "^2.0.0",
|
||||
"comma-separated-tokens": "^2.0.0",
|
||||
"hast-util-parse-selector": "^3.0.0",
|
||||
"property-information": "^6.0.0",
|
||||
"space-separated-tokens": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"property-information": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
|
||||
"integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w=="
|
||||
},
|
||||
"space-separated-tokens": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz",
|
||||
"integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"hast-util-parse-selector": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
|
||||
@@ -35353,6 +35569,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mathjax-full": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.0.tgz",
|
||||
"integrity": "sha512-D2EBNvUG+mJyhn+M1C858k0f2Fc4KxXvbEX2WCMXroV10212JwfYqaBJ336ECBSz5X9L5LRoamxb7AJtg3KaJA==",
|
||||
"requires": {
|
||||
"esm": "^3.2.25",
|
||||
"mhchemparser": "^4.1.0",
|
||||
"mj-context-menu": "^0.6.1",
|
||||
"speech-rule-engine": "^3.3.3"
|
||||
}
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
@@ -36165,6 +36392,11 @@
|
||||
"nullthrows": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"mhchemparser": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.1.1.tgz",
|
||||
"integrity": "sha512-R75CUN6O6e1t8bgailrF1qPq+HhVeFTM3XQ0uzI+mXTybmphy3b6h4NbLOYhemViQ3lUs+6CKRkC3Ws1TlYREA=="
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
|
||||
@@ -36271,6 +36503,11 @@
|
||||
"is-extendable": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"mj-context-menu": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz",
|
||||
"integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA=="
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
@@ -38195,6 +38432,17 @@
|
||||
"integrity": "sha512-BuPaQWvxLZG1NrCDGqgAnecDrNQu3LED9/Pyl4H2LwTMHcEngXpE5PfVntW2GiLumdr6nUOkWmMnh8PynZqrsw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-native-math-view": {
|
||||
"version": "3.9.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-math-view/-/react-native-math-view-3.9.5.tgz",
|
||||
"integrity": "sha512-UJxrisNafszfqIW+utoSDylb72SkZ92cKz1IfE5Dm0s+uIaHxOxepF2DdRbktAV8c0FEFllnXfErcGdh8sfIBw==",
|
||||
"requires": {
|
||||
"hast-util-from-selector": "^2.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mathjax-full": "^3.1.4",
|
||||
"transformation-matrix": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"react-native-navigation": {
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-7.26.0.tgz",
|
||||
@@ -39371,6 +39619,23 @@
|
||||
"resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
|
||||
"integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="
|
||||
},
|
||||
"speech-rule-engine": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-3.3.3.tgz",
|
||||
"integrity": "sha512-0exWw+0XauLjat+f/aFeo5T8SiDsO1JtwpY3qgJE4cWt+yL/Stl0WP4VNDWdh7lzGkubUD9lWP4J1ASnORXfyQ==",
|
||||
"requires": {
|
||||
"commander": ">=7.0.0",
|
||||
"wicked-good-xpath": "^1.3.0",
|
||||
"xmldom-sre": "^0.1.31"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz",
|
||||
"integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
@@ -40071,6 +40336,11 @@
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"transformation-matrix": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmjs.org/transformation-matrix/-/transformation-matrix-2.11.1.tgz",
|
||||
"integrity": "sha512-srlkTrmetYwTBJ1RHdukkwJ8S8D+2JjgSb1DbvmTwj+DsIpCpRYHbWgOXe/Ql2rX37WqlKLIgidpYlHPGsrwgA=="
|
||||
},
|
||||
"traverse": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
|
||||
@@ -41222,6 +41492,11 @@
|
||||
"is-typed-array": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"wicked-good-xpath": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz",
|
||||
"integrity": "sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
@@ -41386,6 +41661,11 @@
|
||||
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz",
|
||||
"integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg=="
|
||||
},
|
||||
"xmldom-sre": {
|
||||
"version": "0.1.31",
|
||||
"resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz",
|
||||
"integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw=="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-5.1.0.tgz",
|
||||
@@ -41498,6 +41778,11 @@
|
||||
"version": "3.14.2",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.14.2.tgz",
|
||||
"integrity": "sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw=="
|
||||
},
|
||||
"zwitch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
"integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"react-native-keychain": "8.0.0",
|
||||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-localize": "2.2.1",
|
||||
"react-native-math-view": "3.9.5",
|
||||
"react-native-navigation": "7.26.0",
|
||||
"react-native-neomorph-shadows": "1.1.2",
|
||||
"react-native-notifications": "4.2.4",
|
||||
|
||||
1
types/api/config.d.ts
vendored
1
types/api/config.d.ts
vendored
@@ -63,6 +63,7 @@ interface ClientConfig {
|
||||
EnableGuestAccounts: string;
|
||||
EnableIncomingWebhooks: string;
|
||||
EnableLatex: string;
|
||||
EnableInlineLatex: string;
|
||||
EnableLdap: string;
|
||||
EnableLinkPreviews: string;
|
||||
EnableMarketplace: string;
|
||||
|
||||
@@ -46,3 +46,7 @@ export type MarkdownImageRenderer = {
|
||||
height?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export type MarkdownLatexRenderer = MarkdownBaseRenderer & {
|
||||
latexCode: string;
|
||||
}
|
||||
|
||||
4
types/modules/react-native-math-view.d.ts
vendored
Normal file
4
types/modules/react-native-math-view.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
declare module 'react-native-math-view';
|
||||
Reference in New Issue
Block a user