Latex rendering with react-native-math-view (#5651)
* Added a Latex code window that displays the given code using katex * Latex is now displayed without having to go to the next screen * MathView test * MathView WIP: Displays math good, but in verry small font View does not load if there is a syntax error and we are hard stuck in the app * Math view now is rendered properly, few working points: Text is not fixed size, it renders in a weird size Multi Line subscript is not working * Latex is now rendered properly * Fixed Error message and moved helper function to utils * Added some padding and vertical scroll on latex screen * Updated config option to work on real phone too * Removed unused code and fixed tests * Added LatexInline and tried to modify parser, not yet finished though. InlineLatexOption is not yet coded for * Bugfixes and better line break checking * Few margin changes for ios, also updated pod files * padding is now dependend on system * Add platform import * Reverted package-lock change * Added tests * Bugfixes, UI tweaks and added unit test for latex util * Added enableLatex to markdown options instead getting the state and config * Changed the rendering a little bit, it is better but still far from perfect * Width and height is now correctely displayed. Still not clean enough though * Inline latex is now rendered properly for most equations * Inline latex is now working properly * tests for escaping and unescaping functions * Added inline latex setting support * Added inline latex tests * Delete unused comment * Added extra regex rule: there must be no word character in front of the inline latex code * Upgraded math-view package and deleted duplicate entry from gemfile.lock * Inline error functions are now class functions * Updated error color to theme error color * Latex screen now extends the code screen * Markdown changes: Now uses dimensions instead of windowdimensions Removed inline latex function extracted latex rendering into separate function * updated latex util test * Hardcoded latex language in latex code block * Added proptypes and cleaned up latex code block * Bugfix latex split lines * ÃDeleted language prop from latex code block * Changed commonmark packages to personal repo's * Latex rendering now goes by commonmark * Deleted unused function and tests * Commonmark packages back to normal versions with patch package * Updated commonmark patch file to right file and changed package lock to point at right repo * Updated patch files to latest version * Updated commonmark patch * Fixed linter errors * Integrety of commonmark fix * First fix of ios problems * Updated snapshot Co-authored-by: Kobe Bergmans <kobe.bergmans@student.kuleuven.be> Co-authored-by: Administrator <admin@Macintosh-3.localdomain> Co-authored-by: kobe bergmans <kobebergmans@kobes-MacBook-Pro.local>
This commit is contained in:
parent
70d025fe1d
commit
21adcf6085
24 changed files with 1677 additions and 4 deletions
|
|
@ -17,6 +17,8 @@ function mapStateToProps(state, ownProps) {
|
|||
mentionKeys: ownProps.mentionKeys || getAllUserMentionKeys(state),
|
||||
minimumHashtagLength: MinimumHashtagLength ? parseInt(MinimumHashtagLength, 10) : 3,
|
||||
theme: getTheme(state),
|
||||
enableLatex: getConfig(state).EnableLatex === 'true',
|
||||
enableInlineLatex: getConfig(state).EnableInlineLatex === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,278 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have 2 lines 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{2}{1} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should have moreLinesText 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{5}{2} = 2"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="2_{nd} = line"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
<InjectIntl(FormattedText)
|
||||
defaultMessage="+{count, number} more {count, plural, one {line} other {lines}}"
|
||||
id="mobile.markdown.code.plusMoreLines"
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.4)",
|
||||
"fontSize": 11,
|
||||
"marginTop": 2,
|
||||
}
|
||||
}
|
||||
values={
|
||||
Object {
|
||||
"count": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<TouchableWithFeedbackIOS
|
||||
onLongPress={[Function]}
|
||||
onPress={[Function]}
|
||||
type="opacity"
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"borderColor": "rgba(63,67,80,0.15)",
|
||||
"borderRadius": 3,
|
||||
"borderWidth": 0.5,
|
||||
"flex": 1,
|
||||
"flexDirection": "row",
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"flexDirection": "column",
|
||||
"paddingLeft": 6,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginLeft": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#192a4d",
|
||||
"justifyContent": "center",
|
||||
"opacity": 0.8,
|
||||
"padding": 6,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 12,
|
||||
}
|
||||
}
|
||||
>
|
||||
LaTeX
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithFeedbackIOS>
|
||||
`;
|
||||
16
app/components/markdown/latex_code_block/index.js
Normal file
16
app/components/markdown/latex_code_block/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexCodeBlock);
|
||||
209
app/components/markdown/latex_code_block/latex_code_block.js
Normal file
209
app/components/markdown/latex_code_block/latex_code_block.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {PropTypes} from 'prop-types';
|
||||
import React from 'react';
|
||||
import {Keyboard, View, Text, StyleSheet, Platform} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {goToScreen} from '@actions/navigation';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
import TouchableWithFeedback from '@components/touchable_with_feedback';
|
||||
import {splitLatexCodeInLines} from '@utils/latex';
|
||||
import {getDisplayNameForLanguage} from '@utils/markdown';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import MarkdownCodeBlock from '../markdown_code_block/markdown_code_block';
|
||||
|
||||
const MAX_LINES = 2;
|
||||
|
||||
export default class LatexCodeBlock extends MarkdownCodeBlock {
|
||||
static propTypes = {
|
||||
theme: PropTypes.object.isRequired,
|
||||
content: PropTypes.string.isRequired,
|
||||
textStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
|
||||
};
|
||||
|
||||
handlePress = preventDoubleTap(() => {
|
||||
const {content} = this.props;
|
||||
const {intl} = this.context;
|
||||
const screen = 'Latex';
|
||||
const passProps = {
|
||||
content,
|
||||
};
|
||||
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
let title;
|
||||
if (languageDisplayName) {
|
||||
title = intl.formatMessage(
|
||||
{
|
||||
id: 'mobile.routes.code',
|
||||
defaultMessage: '{language} Code',
|
||||
},
|
||||
{
|
||||
language: languageDisplayName,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
title = intl.formatMessage({
|
||||
id: 'mobile.routes.code.noLanguage',
|
||||
defaultMessage: 'LaTeX Code',
|
||||
});
|
||||
}
|
||||
|
||||
Keyboard.dismiss();
|
||||
requestAnimationFrame(() => {
|
||||
goToScreen(screen, title, passProps);
|
||||
});
|
||||
});
|
||||
|
||||
splitContent = (content) => {
|
||||
const lines = splitLatexCodeInLines(content);
|
||||
|
||||
const numberOfLines = lines.length;
|
||||
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
return {
|
||||
content: lines.slice(0, MAX_LINES),
|
||||
numberOfLines,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
content: lines,
|
||||
numberOfLines,
|
||||
};
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
let language = null;
|
||||
const languageDisplayName = getDisplayNameForLanguage('latex');
|
||||
|
||||
if (languageDisplayName) {
|
||||
language = (
|
||||
<View style={style.language}>
|
||||
<Text style={style.languageText}>
|
||||
{languageDisplayName}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const {content, numberOfLines} = this.splitContent(this.props.content);
|
||||
|
||||
let plusMoreLines = null;
|
||||
if (numberOfLines > MAX_LINES) {
|
||||
plusMoreLines = (
|
||||
<FormattedText
|
||||
style={style.plusMoreLinesText}
|
||||
id='mobile.markdown.code.plusMoreLines'
|
||||
defaultMessage='+{count, number} more {count, plural, one {line} other {lines}}'
|
||||
values={{
|
||||
count: 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={this.handlePress}
|
||||
onLongPress={this.handleLongPress}
|
||||
type={'opacity'}
|
||||
>
|
||||
<View style={style.container}>
|
||||
<View style={style.rightColumn}>
|
||||
{content.map((latexCode) => (
|
||||
<View
|
||||
style={style.code}
|
||||
key={latexCode}
|
||||
>
|
||||
<MathView
|
||||
math={latexCode}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
{plusMoreLines}
|
||||
</View>
|
||||
{language}
|
||||
</View>
|
||||
</TouchableWithFeedback>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
const codeVerticalPadding = Platform.select({
|
||||
ios: 4,
|
||||
android: 0,
|
||||
});
|
||||
|
||||
return {
|
||||
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),
|
||||
fontSize: 11,
|
||||
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,
|
||||
fontSize: 12,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
|
||||
describe('LatexCodeBlock', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
language: 'latex',
|
||||
textStyle: {},
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have 2 lines', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{2}{1} = 2 \\\\ 2_{nd} = line'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have moreLinesText', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexCodeBlock
|
||||
{...baseProps}
|
||||
content={'\\frac{5}{2} = 2 \\\\ 2_{nd} = line \\\\ \\pi = 3.14'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LatexCodeBlock should have maxWidth 10 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": 10,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
|
||||
exports[`LatexCodeBlock should match snapshot 1`] = `
|
||||
<View
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"flexWrap": "wrap",
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"height": undefined,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} == 0.5^{0*\\\\pi} - 0.5"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="contain"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"marginBottom": -7,
|
||||
},
|
||||
Object {
|
||||
"maxWidth": "100%",
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
`;
|
||||
16
app/components/markdown/latex_inline/index.js
Normal file
16
app/components/markdown/latex_inline/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import LatexInline from './latex_inline';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(LatexInline);
|
||||
78
app/components/markdown/latex_inline/latex_inline.js
Normal file
78
app/components/markdown/latex_inline/latex_inline.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import React, {PureComponent} from 'react';
|
||||
import {Platform, Text, View} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
export default class LatexInline extends PureComponent {
|
||||
static propTypes = {
|
||||
content: PropTypes.string.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
onLayout: PropTypes.func,
|
||||
maxMathWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
mathHeight: PropTypes.number,
|
||||
};
|
||||
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Latex render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
const viewStyleObj = Platform.select({
|
||||
ios: () => [style.viewStyle, {height: this.props.mathHeight}],
|
||||
android: () => [style.viewStyle],
|
||||
})();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={viewStyleObj}
|
||||
key={this.props.content}
|
||||
onLayout={this.props.onLayout}
|
||||
>
|
||||
<MathView
|
||||
style={[style.mathStyle, {maxWidth: this.props.maxMathWidth ? this.props.maxMathWidth : '100%'}]}
|
||||
math={this.props.content}
|
||||
onError={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'contain'}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme) => {
|
||||
return {
|
||||
mathStyle: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
viewStyle: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
...Platform.select({
|
||||
ios: {
|
||||
marginBottom: -7,
|
||||
},
|
||||
}),
|
||||
},
|
||||
errorText: {
|
||||
flexDirection: 'row',
|
||||
color: theme.errorTextColor,
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
};
|
||||
});
|
||||
38
app/components/markdown/latex_inline/latex_inline.test.js
Normal file
38
app/components/markdown/latex_inline/latex_inline.test.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import LatexInline from './latex_inline';
|
||||
|
||||
describe('LatexCodeBlock', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexInline
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have maxWidth 10', () => {
|
||||
const wrapper = shallow(
|
||||
<LatexInline
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} == 0.5^{0*\\pi} - 0.5'}
|
||||
maxMathWidth={10}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
Platform,
|
||||
Text,
|
||||
View,
|
||||
Dimensions,
|
||||
} from 'react-native';
|
||||
|
||||
import AtMention from '@components/at_mention';
|
||||
|
|
@ -19,6 +20,8 @@ import Hashtag from '@components/markdown/hashtag';
|
|||
import {blendColors, concatStyles, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {getScheme} from '@utils/url';
|
||||
|
||||
import LatexCodeBlock from './latex_code_block';
|
||||
import LatexInline from './latex_inline';
|
||||
import MarkdownBlockQuote from './markdown_block_quote';
|
||||
import MarkdownCodeBlock from './markdown_code_block';
|
||||
import MarkdownImage from './markdown_image';
|
||||
|
|
@ -59,6 +62,8 @@ export default class Markdown extends PureComponent {
|
|||
disableChannelLink: PropTypes.bool,
|
||||
disableAtChannelMentionHighlight: PropTypes.bool,
|
||||
disableGallery: PropTypes.bool,
|
||||
enableLatex: PropTypes.bool,
|
||||
enableInlineLatex: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -78,6 +83,10 @@ export default class Markdown extends PureComponent {
|
|||
|
||||
this.parser = this.createParser();
|
||||
this.renderer = this.createRenderer();
|
||||
|
||||
this.state = {
|
||||
inlineLatexHeight: 20,
|
||||
};
|
||||
}
|
||||
|
||||
createParser = () => {
|
||||
|
|
@ -108,7 +117,7 @@ export default class Markdown extends PureComponent {
|
|||
channelLink: this.renderChannelLink,
|
||||
emoji: this.renderEmoji,
|
||||
hashtag: this.renderHashtag,
|
||||
latexinline: this.renderParagraph,
|
||||
latexInline: this.renderLatexInline,
|
||||
|
||||
paragraph: this.renderParagraph,
|
||||
heading: this.renderHeading,
|
||||
|
|
@ -158,6 +167,11 @@ export default class Markdown extends PureComponent {
|
|||
return contextStyles.length ? concatStyles(baseStyle, contextStyles) : baseStyle;
|
||||
};
|
||||
|
||||
onInlineLatexLayout = (event) => {
|
||||
const mathLineHeight = Math.max(event.nativeEvent.layout.height, this.state.inlineLatexHeight);
|
||||
this.setState({inlineLatexHeight: mathLineHeight});
|
||||
};
|
||||
|
||||
renderText = ({context, literal}) => {
|
||||
if (context.indexOf('image') !== -1) {
|
||||
// If this text is displayed, it will be styled by the image component
|
||||
|
|
@ -280,6 +294,25 @@ export default class Markdown extends PureComponent {
|
|||
);
|
||||
};
|
||||
|
||||
renderLatexInline = ({context, latexCode}) => {
|
||||
if (!this.props.enableInlineLatex) {
|
||||
return this.renderText({context, literal: `$${latexCode}$`});
|
||||
}
|
||||
|
||||
return (
|
||||
<Text
|
||||
style={{lineHeight: this.state.inlineLatexHeight}}
|
||||
>
|
||||
<LatexInline
|
||||
content={latexCode}
|
||||
onLayout={this.onInlineLatexLayout}
|
||||
maxMathWidth={Dimensions.get('window').width * 0.75}
|
||||
mathHeight={this.state.inlineLatexHeight}
|
||||
/>
|
||||
</Text>
|
||||
);
|
||||
};
|
||||
|
||||
renderParagraph = ({children, first}) => {
|
||||
if (!children || children.length === 0) {
|
||||
return null;
|
||||
|
|
@ -319,6 +352,16 @@ export default class Markdown extends PureComponent {
|
|||
// These sometimes include a trailing newline
|
||||
const content = props.literal.replace(/\n$/, '');
|
||||
|
||||
if (this.props.enableLatex && props.language === 'latex') {
|
||||
return (
|
||||
<LatexCodeBlock
|
||||
content={content}
|
||||
language={props.language}
|
||||
textStyle={this.props.textStyles.codeBlock}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MarkdownCodeBlock
|
||||
content={content}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ export type Config = {
|
|||
EnableGuestAccounts: string;
|
||||
EnableIncomingWebhooks: string;
|
||||
EnableLatex: string;
|
||||
EnableInlineLatex: string;
|
||||
EnableLdap: string;
|
||||
EnableLegacySidebar: string;
|
||||
EnableLinkPreviews: string;
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ Navigation.setLazyComponentRegistrator((screenName) => {
|
|||
case 'InteractiveDialog':
|
||||
screen = require('@screens/interactive_dialog').default;
|
||||
break;
|
||||
case 'Latex':
|
||||
screen = require('@screens/latex').default;
|
||||
break;
|
||||
case 'Login':
|
||||
screen = require('@screens/login').default;
|
||||
break;
|
||||
|
|
|
|||
205
app/screens/latex/__snapshots__/latex.test.js.snap
Normal file
205
app/screens/latex/__snapshots__/latex.test.js.snap
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Latex should have 2 lines 1`] = `
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"minHeight": "100%",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"minHeight": "100%",
|
||||
"paddingLeft": 10,
|
||||
"paddingVertical": 10,
|
||||
}
|
||||
}
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginHorizontal": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\\\pi - \\\\frac{2*pi}}{2}} \\\\\\\\ Test = 1"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`Latex should have 4 lines 1`] = `
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"minHeight": "100%",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"minHeight": "100%",
|
||||
"paddingLeft": 10,
|
||||
"paddingVertical": 10,
|
||||
}
|
||||
}
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginHorizontal": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\\\pi - \\\\frac{2*pi}}{2}} \\\\\\\\ Test = 1 \\\\\\\\ \\\\alpha = \\\\beta \\\\\\\\ 1 != 0"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
|
||||
exports[`Latex should match snapshot 1`] = `
|
||||
<RNCSafeAreaView
|
||||
edges={
|
||||
Array [
|
||||
"bottom",
|
||||
"left",
|
||||
"right",
|
||||
]
|
||||
}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"minHeight": "100%",
|
||||
}
|
||||
}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"flex": 1,
|
||||
},
|
||||
]
|
||||
}
|
||||
>
|
||||
<ScrollView
|
||||
contentContainerStyle={
|
||||
Object {
|
||||
"flexDirection": "column",
|
||||
"minHeight": "100%",
|
||||
"paddingLeft": 10,
|
||||
"paddingVertical": 10,
|
||||
}
|
||||
}
|
||||
horizontal={true}
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"flexDirection": "row",
|
||||
"justifyContent": "flex-start",
|
||||
"marginHorizontal": 5,
|
||||
"paddingVertical": 4,
|
||||
}
|
||||
}
|
||||
>
|
||||
<ForwardRef
|
||||
color="black"
|
||||
math="\\\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\\\pi - \\\\frac{2*pi}}{2}}"
|
||||
onError={[Function]}
|
||||
renderError={[Function]}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</RNCSafeAreaView>
|
||||
`;
|
||||
16
app/screens/latex/index.js
Normal file
16
app/screens/latex/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
|
||||
import Latex from './latex';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
theme: getTheme(state),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Latex);
|
||||
102
app/screens/latex/latex.js
Normal file
102
app/screens/latex/latex.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
Text,
|
||||
View,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import MathView from 'react-native-math-view';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
|
||||
import {splitLatexCodeInLines} from '@utils/latex';
|
||||
import {makeStyleSheetFromTheme} from '@utils/theme';
|
||||
|
||||
import Code from '../code/code';
|
||||
|
||||
export default class Latex extends Code {
|
||||
onErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Error: ' + errorMsg.message}</Text>;
|
||||
};
|
||||
|
||||
onRenderErrorMessage = (errorMsg) => {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
return <Text style={style.errorText}>{'Render error: ' + errorMsg.error.message}</Text>;
|
||||
};
|
||||
|
||||
render() {
|
||||
const style = getStyleSheet(this.props.theme);
|
||||
|
||||
const lines = splitLatexCodeInLines(this.props.content);
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
edges={['bottom', 'left', 'right']}
|
||||
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={this.onErrorMessage}
|
||||
renderError={this.onRenderErrorMessage}
|
||||
resizeMode={'cover'}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((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: {
|
||||
fontSize: 14,
|
||||
marginHorizontal: 5,
|
||||
color: theme.errorTextColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
48
app/screens/latex/latex.test.js
Normal file
48
app/screens/latex/latex.test.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {shallow} from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
||||
import Latex from './latex';
|
||||
|
||||
describe('Latex', () => {
|
||||
const baseProps = {
|
||||
theme: Preferences.THEMES.denim,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<Latex
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\pi - \\frac{2*pi}}{2}}'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have 2 lines', () => {
|
||||
const wrapper = shallow(
|
||||
<Latex
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\pi - \\frac{2*pi}}{2}} \\\\ Test = 1'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should have 4 lines', () => {
|
||||
const wrapper = shallow(
|
||||
<Latex
|
||||
{...baseProps}
|
||||
content={'\\frac{1}{2} = 0.5^1 + (x^0 - e^{\\pi - \\frac{2*pi}}{2}} \\\\ Test = 1 \\\\ \\alpha = \\beta \\\\ 1 != 0'}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
66
app/utils/latex.test.ts
Normal file
66
app/utils/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');
|
||||
});
|
||||
});
|
||||
96
app/utils/latex.ts
Normal file
96
app/utils/latex.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
// 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;
|
||||
}
|
||||
|
|
@ -89,6 +89,7 @@ GEM
|
|||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x64-mingw32
|
||||
|
||||
DEPENDENCIES
|
||||
cocoapods (= 1.11.2)
|
||||
|
|
|
|||
|
|
@ -787,6 +787,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 = (
|
||||
|
|
@ -808,6 +809,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;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ PODS:
|
|||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- HMSegmentedControl (1.5.6)
|
||||
- iosMath (0.9.4)
|
||||
- jail-monkey (2.6.0):
|
||||
- React-Core
|
||||
- libwebp (1.2.1):
|
||||
|
|
@ -386,6 +387,8 @@ PODS:
|
|||
- React-Core
|
||||
- RNLocalize (2.1.9):
|
||||
- React-Core
|
||||
- RNMathView (1.0.0):
|
||||
- iosMath
|
||||
- RNPermissions (3.2.0):
|
||||
- React-Core
|
||||
- RNReactNativeHapticFeedback (1.13.0):
|
||||
|
|
@ -521,6 +524,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`)
|
||||
|
|
@ -538,6 +542,7 @@ SPEC REPOS:
|
|||
trunk:
|
||||
- fmt
|
||||
- HMSegmentedControl
|
||||
- iosMath
|
||||
- libwebp
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
|
|
@ -689,6 +694,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:
|
||||
|
|
@ -726,8 +733,9 @@ SPEC CHECKSUMS:
|
|||
FBLazyVector: 244195e30d63d7f564c55da4410b9a24e8fbceaa
|
||||
FBReactNativeSpec: c94002c1d93da3658f4d5119c6994d19961e3d52
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85
|
||||
glog: 5337263514dd6f09803962437687240c5dc39aa4
|
||||
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
|
||||
iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba
|
||||
jail-monkey: 07b83767601a373db876e939b8dbf3f5eb15f073
|
||||
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
|
||||
MMKV: 76033b9ace2006623308910a3afcc0e25eba3140
|
||||
|
|
@ -738,7 +746,7 @@ SPEC CHECKSUMS:
|
|||
Permission-Notifications: bb420c3d28328df24de1b476b41ed8249ccf2537
|
||||
Permission-PhotoLibrary: 7bec836dcdd04a0bfb200c314f1aae06d4476357
|
||||
Permission-PhotoLibraryAddOnly: 06fb0cdb1d35683b235ad8c464ef0ecc88859ea3
|
||||
RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685
|
||||
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
|
||||
RCTRequired: cd47794163052d2b8318c891a7a14fcfaccc75ab
|
||||
RCTTypeSafety: 393bb40b3e357b224cde53d3fec26813c52428b1
|
||||
RCTYouTube: a8bb45705622a6fc9decf64be04128d3658ed411
|
||||
|
|
@ -795,6 +803,7 @@ SPEC CHECKSUMS:
|
|||
RNGestureHandler: bf572f552ea324acd5b5464b8d30755b2d8c1de6
|
||||
RNKeychain: 4f63aada75ebafd26f4bc2c670199461eab85d94
|
||||
RNLocalize: 291e5e21b53cd30b5205c10ed95d31b0dc9b8d63
|
||||
RNMathView: 4c8a3c081fa671ab3136c51fa0bdca7ffb708bd5
|
||||
RNPermissions: f7ebe52db07c00901127966ca080b4ec6a6ceb0a
|
||||
RNReactNativeHapticFeedback: b83bfb4b537bdd78eb4f6ffe63c6884f7b049ead
|
||||
RNReanimated: 1326679461fa5d2399d54c18ca1432ba3e816b9e
|
||||
|
|
|
|||
|
|
@ -28,6 +28,6 @@ module.exports = {
|
|||
'assets/images/video_player/(.*).png': '<rootDir>/dist/assets/images/video_player/$1@2x.png',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(react-native|@react-native|jail-monkey|serialize-error|@sentry/react-native|react-navigation|@react-native-community/cameraroll))',
|
||||
'node_modules/(?!(react-native|@react-native|jail-monkey|serialize-error|@sentry/react-native|react-navigation|@react-native-community/cameraroll|hast-util-from-selector|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|zwitch))',
|
||||
],
|
||||
};
|
||||
|
|
|
|||
322
package-lock.json
generated
322
package-lock.json
generated
|
|
@ -64,6 +64,7 @@
|
|||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-local-auth": "1.6.0",
|
||||
"react-native-localize": "2.1.9",
|
||||
"react-native-math-view": "3.9.5",
|
||||
"react-native-mmkv-storage": "0.6.11",
|
||||
"react-native-navigation": "7.25.1",
|
||||
"react-native-notifications": "4.1.3",
|
||||
|
|
@ -4629,6 +4630,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
|
||||
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
|
||||
"integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==",
|
||||
"dependencies": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
|
|
@ -4850,6 +4859,11 @@
|
|||
"integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"node_modules/@types/url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.7.tgz",
|
||||
|
|
@ -7190,6 +7204,15 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"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/command-exists": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
|
|
@ -7465,6 +7488,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",
|
||||
|
|
@ -9167,6 +9195,14 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
|
||||
|
|
@ -10583,6 +10619,49 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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-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/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/he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
|
|
@ -15652,6 +15731,17 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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/mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
|
|
@ -16871,6 +16961,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",
|
||||
|
|
@ -16950,6 +17045,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",
|
||||
|
|
@ -18963,6 +19063,15 @@
|
|||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"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/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
|
|
@ -19460,6 +19569,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-mmkv-storage": {
|
||||
"version": "0.6.11",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.6.11.tgz",
|
||||
|
|
@ -21701,6 +21825,15 @@
|
|||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
|
||||
"deprecated": "See https://github.com/lydell/source-map-url#deprecated"
|
||||
},
|
||||
"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/spawn-wrap": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
|
||||
|
|
@ -21789,6 +21922,27 @@
|
|||
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
|
||||
"dev": true
|
||||
},
|
||||
"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.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.1.0.tgz",
|
||||
"integrity": "sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w==",
|
||||
"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",
|
||||
|
|
@ -22687,6 +22841,14 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"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/truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
|
|
@ -23617,6 +23779,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",
|
||||
|
|
@ -23813,6 +23980,14 @@
|
|||
"sax": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -23964,6 +24139,15 @@
|
|||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
|
@ -27188,6 +27372,14 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.41.tgz",
|
||||
"integrity": "sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA=="
|
||||
},
|
||||
"@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz",
|
||||
"integrity": "sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==",
|
||||
"requires": {
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
|
|
@ -27408,6 +27600,11 @@
|
|||
"integrity": "sha512-uO4CD2ELOjw8tasUrAhvnn2W4A0ZECOvMjCivJr4gA9pGgjv+qxKWY9GLTMVEK8ej85BxQOocUyE7hImmSQYcg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
|
||||
},
|
||||
"@types/url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/url-parse/-/url-parse-1.4.7.tgz",
|
||||
|
|
@ -29197,6 +29394,11 @@
|
|||
"delayed-stream": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"command-exists": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
|
|
@ -29426,6 +29628,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",
|
||||
|
|
@ -30728,6 +30935,11 @@
|
|||
"integrity": "sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==",
|
||||
"dev": true
|
||||
},
|
||||
"esm": {
|
||||
"version": "3.2.25",
|
||||
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
|
||||
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
|
||||
},
|
||||
"espree": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz",
|
||||
|
|
@ -31827,6 +32039,37 @@
|
|||
"type-fest": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"he": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
|
||||
|
|
@ -35749,6 +35992,17 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
|
||||
|
|
@ -36780,6 +37034,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",
|
||||
|
|
@ -36835,6 +37094,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",
|
||||
|
|
@ -38364,6 +38628,11 @@
|
|||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"property-information": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.1.1.tgz",
|
||||
"integrity": "sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w=="
|
||||
},
|
||||
"proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
|
|
@ -38837,6 +39106,17 @@
|
|||
"integrity": "sha512-n2Bi5P0cII8BrnG23Fs9PmpYcufD3JRbr1IBn3ArIA3Z3cGWg4eebv6am+CgdFC4kTPWUlVzX9fEgEMA+X8d6w==",
|
||||
"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-mmkv-storage": {
|
||||
"version": "0.6.11",
|
||||
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.6.11.tgz",
|
||||
|
|
@ -40527,6 +40807,11 @@
|
|||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"spawn-wrap": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
|
||||
|
|
@ -40599,6 +40884,23 @@
|
|||
"integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==",
|
||||
"dev": true
|
||||
},
|
||||
"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.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.1.0.tgz",
|
||||
"integrity": "sha512-i0/MaqBtdbnJ4XQs4Pmyb+oFQl+q0lsAmokVUH92SlSw4fkeAcG3bVon+Qt7hmtF+u3Het6o4VgrcY3qAoEB6w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"split-on-first": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||
|
|
@ -41274,6 +41576,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=="
|
||||
},
|
||||
"truncate-utf8-bytes": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
|
||||
|
|
@ -41978,6 +42285,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",
|
||||
|
|
@ -42128,6 +42440,11 @@
|
|||
"sax": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
|
@ -42240,6 +42557,11 @@
|
|||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"zwitch": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.2.tgz",
|
||||
"integrity": "sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@
|
|||
"react-native-linear-gradient": "2.5.6",
|
||||
"react-native-local-auth": "1.6.0",
|
||||
"react-native-localize": "2.1.9",
|
||||
"react-native-math-view": "3.9.5",
|
||||
"react-native-mmkv-storage": "0.6.11",
|
||||
"react-native-navigation": "7.25.1",
|
||||
"react-native-notifications": "4.1.3",
|
||||
|
|
|
|||
Loading…
Reference in a new issue