Skip to content

Commit

Permalink
feat: support math equations
Browse files Browse the repository at this point in the history
closes #164
  • Loading branch information
juancarlosfarah committed Aug 15, 2019
1 parent 4e69811 commit 1140667
Show file tree
Hide file tree
Showing 13 changed files with 562 additions and 18 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"i18next": "15.1.0",
"immutable": "4.0.0-rc.12",
"is-online": "8.2.0",
"katex": "0.11.0",
"lodash": "4.17.13",
"lowdb": "1.0.0",
"md5": "2.2.1",
Expand All @@ -93,6 +94,7 @@
"react-immutable-proptypes": "2.1.0",
"react-json-view": "1.19.1",
"react-loading": "2.0.3",
"react-quill": "1.3.3",
"react-redux": "7.0.3",
"react-redux-toastr": "7.4.9",
"react-resizable": "1.8.0",
Expand Down
56 changes: 56 additions & 0 deletions src/components/common/Text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactQuill from 'react-quill';
import { hasMath, renderMath } from '../../utils/math';

const modules = {
toolbar: false,
};

const formats = [
'header',
'bold',
'italic',
'underline',
'strike',
'blockquote',
'list',
'bullet',
'indent',
'link',
'image',
'formula',
];

const Text = ({ content, style, className }) => {
let parsedContent = content;
if (hasMath(content)) {
parsedContent = renderMath(parsedContent);
}
return (
<div style={style}>
<ReactQuill
className={className}
value={parsedContent}
modules={modules}
formats={formats}
style={{ border: '0' }}
readOnly
/>
</div>
);
};

Text.propTypes = {
content: PropTypes.string,
className: PropTypes.string,
style: PropTypes.shape({}),
};

Text.defaultProps = {
content: '',
className: '',
style: {},
};

export default Text;
3 changes: 3 additions & 0 deletions src/components/phase/PhaseDescription.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.PhaseDescriptionText p {
font-size: x-large;
}
14 changes: 13 additions & 1 deletion src/components/phase/PhaseDescription.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import Text from '../common/Text';
import './PhaseDescription.css';

const style = {
marginBottom: '2rem',
};

const PhaseDescription = ({ description }) => {
if (description && description !== '') {
return <div dangerouslySetInnerHTML={{ __html: description }} />;
return (
<Text
content={description}
style={style}
className="PhaseDescriptionText"
/>
);
}
return null;
};
Expand Down
10 changes: 7 additions & 3 deletions src/components/phase/PhaseText.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import Text from '../common/Text';

const PhaseText = ({ content }) => (
<div dangerouslySetInnerHTML={{ __html: content }} />
);
const style = {
marginTop: '2rem',
marginBottom: '2rem',
};

const PhaseText = ({ content }) => <Text content={content} style={style} />;

PhaseText.propTypes = {
content: PropTypes.string,
Expand Down
18 changes: 11 additions & 7 deletions src/components/space/SpaceDescription.css
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
.SpaceDescription {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}

.SpaceDescriptionDiv {
justify-content: center;
align-items: center;
}
justify-content: center;
align-items: center;
}

.SpaceDescriptionText p {
font-size: xx-large;
}
17 changes: 13 additions & 4 deletions src/components/space/SpaceDescription.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import Button from '@material-ui/core/Button';
import { Typography, withStyles } from '@material-ui/core';
import { withStyles } from '@material-ui/core';
import { useTranslation } from 'react-i18next';
import Styles from '../../Styles';
import './SpaceDescription.css';
import Banner from '../common/Banner';
import Text from '../common/Text';

const style = {
fontSize: 'large',
};

const renderPreviewWarning = t => {
return (
Expand All @@ -25,9 +30,13 @@ const SpaceDescription = ({ description, classes, start, saved }) => {
<div className="SpaceDescription">
<div>
{saved ? null : renderPreviewWarning(t)}
<Typography variant="h4" className={classes.spaceDescription}>
<div dangerouslySetInnerHTML={{ __html: description }} />
</Typography>
<div className={classes.spaceDescription}>
<Text
content={description}
style={style}
className="SpaceDescriptionText"
/>
</div>
<Button
variant="contained"
className={classes.button}
Expand Down
8 changes: 8 additions & 0 deletions src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ export const CONTROL_TYPES = {
};
export const MIN_CARD_WIDTH = 345;
export const DEFAULT_PROTOCOL = 'https';

// math
export const BLOCK_MATH_DIV = 'p';
export const INLINE_MATH_DIV = 'span';
export const BLOCK_MATH_INDICATOR = '\\[';
export const INLINE_MATH_INDICATOR = '\\(';
export const BLOCK_MATH_REGEX = /(\\\[(.*?)\\])/g;
export const INLINE_MATH_REGEX = /(\\\((.*?)\\\))/g;
6 changes: 6 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import './index.css';
import { Provider } from 'react-redux';
import * as Sentry from '@sentry/browser';
import { I18nextProvider } from 'react-i18next';
import katex from 'katex';
import i18nConfig from './config/i18n';
import { WHITELISTED_ERRORS } from './config/errors';
import 'react-redux-toastr/lib/css/react-redux-toastr.min.css';
import 'react-resizable/css/styles.css';
import 'katex/dist/katex.min.css';
import 'react-quill/dist/quill.core.css';
import App from './App';
import configureStore from './store/configure';

// bind katex to the window object
window.katex = katex;

const { REACT_APP_SENTRY_DSN } = process.env;

// set up sentry
Expand Down
145 changes: 145 additions & 0 deletions src/test/fixtures/math.js

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions src/utils/math.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import katex from 'katex';
import _ from 'lodash';
import {
BLOCK_MATH_DIV,
BLOCK_MATH_INDICATOR,
BLOCK_MATH_REGEX,
INLINE_MATH_DIV,
INLINE_MATH_INDICATOR,
INLINE_MATH_REGEX,
} from '../config/constants';

const hasMath = (input = '') => {
return (
_.isString(input) &&
(input.includes(BLOCK_MATH_INDICATOR) ||
input.includes(INLINE_MATH_INDICATOR))
);
};

const renderToString = (input = '', indicator, regex, div) => {
let output = input;
if (input.includes(indicator)) {
const matches = [...input.matchAll(regex)];
matches.forEach(match => {
const text = match[2];
const matched = match[0];
const parsed = `<${div} class="ql-formula" data-value="${text}">
<span contenteditable="false">
${katex.renderToString(text, {
throwOnError: false,
})}
</span>
<${div}>`;
output = output.replace(matched, parsed);
});
}
return output;
};

const renderMathBlock = input =>
renderToString(input, BLOCK_MATH_INDICATOR, BLOCK_MATH_REGEX, BLOCK_MATH_DIV);
const renderMathInline = input =>
renderToString(
input,
INLINE_MATH_INDICATOR,
INLINE_MATH_REGEX,
INLINE_MATH_DIV
);

const renderMath = input => {
let rvalue = input;
rvalue = renderMathInline(rvalue);
rvalue = renderMathBlock(rvalue);
return rvalue;
};

export { renderMathInline, renderMathBlock, renderMath, hasMath };
Loading

0 comments on commit 1140667

Please sign in to comment.