├── .github
└── workflows
│ ├── gh-pages.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .nojekyll
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── demo.jsx
├── index.html
├── index.jsx
├── package.json
├── test.jsx
└── yarn.lock
/.github/workflows/gh-pages.yml:
--------------------------------------------------------------------------------
1 | name: github pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | build-deploy:
10 | name: Publish demo website
11 | runs-on: ubuntu-20.04
12 |
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@v2
16 |
17 | - uses: actions/setup-node@v2
18 | with:
19 | node-version: '14'
20 |
21 | - run: yarn install
22 | - run: yarn run parcel build --target demo --no-optimize index.html --dist-dir dist/demo --public-url https://mehdisadeghi.github.io/react-mathjax-preview
23 | - run: touch dist/demo/.nojekyll
24 |
25 | - name: Deploy page
26 | uses: JamesIves/github-pages-deploy-action@4.1.0
27 | with:
28 | branch: gh-pages
29 | folder: dist/demo
30 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | test:
8 | runs-on: ubuntu-20.04
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v2
12 | with:
13 | node-version: '14'
14 | - run: yarn install
15 | - run: yarn run test
16 | release:
17 | needs: test
18 | runs-on: ubuntu-20.04
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: actions/setup-node@v2
22 | with:
23 | node-version: '14'
24 | - run: yarn install
25 | - run: yarn build
26 | - uses: mikeal/merge-release@master
27 | env:
28 | NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: run tests
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | runs-on: ubuntu-20.04
8 |
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-node@v2
12 | with:
13 | node-version: '14'
14 | - run: yarn install
15 | - run: yarn run test
16 |
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .parcel-cache
3 | dist
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mehdisadeghi/react-mathjax-preview/f131a2d5f6190a3f3c0d12ed6cdc054941e1cbbe/.nojekyll
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | [Node.js](http://nodejs.org/) >= v4 must be installed.
4 |
5 | ## Installation
6 |
7 | - Running `yarn install` in the components's root directory will install everything you need for development.
8 |
9 | ## Development
10 |
11 | - `yarn start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading.
12 |
13 | - `yarn test` runs the tests with `jest`.
14 |
15 | - `yarn build` builds the component for publishing to npm in the `dist` folder.
16 |
17 | - `yarn build:demo` builds the demo app in the `dist` folder.
18 |
19 | ## Packaging
20 | For packaging and transforms `parcel` and `bable` have been used. The `.babelrc`
21 | is there because of `jest`, otherwise the default `parcel` configs would work build
22 | the react project properly.
23 |
24 | Initially I had used nwb and later neutrinojs, however they were unnecessary for this extremely small project, which is basically one single component. So, using
25 | `parcel` and removing extra folders simplified everything a great deal.
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Mehdi Sadeghi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-mathjax-preview
2 |
3 | [![Travis][build-badge]][build]
4 | [![npm package][npm-badge]][npm]
5 |
6 | `react-mathjax-preview` provides one React component to render MathML, TeX or ASCIImath formulas. See [demo](https://mehdisadeghi.github.io/react-mathjax-preview/).
7 |
8 | ## Installation
9 | Install `react-mathjax-preview` as a dependency:
10 |
11 | ```
12 | yarn add react-mathjax-preview
13 | ```
14 |
15 | ## Usage
16 | Import the package and fill the `math` property with some text containing your formals. Wrap TeX in `$` or `$$` and ASCIImath in \`. Paste MathML as is.
17 |
18 | ```js
19 | import React, {Component} from 'react'
20 | import {render} from 'react-dom'
21 | import MathJax from 'react-mathjax-preview'
22 |
23 | const asciimath = '`sum_(i=1)^n i^3=((n(n+1))/2)^2`' # Because of the backtick
24 | const math = String.raw`
25 |
30 |
31 | $$\lim_{x \to \infty} \exp(-x) = 0$$
32 |
33 | ${asciimath}`
34 |
35 | class Demo extends Component {
36 | constructor(props) {
37 | super(props);
38 | this.state = {
39 | math: tex
40 | }
41 | render() {
42 | return
43 | }
44 | }
45 | ```
46 |
47 | ## Props
48 |
49 | ### className
50 | Wrapper classname
51 |
52 | ### id
53 | Wrapper id
54 |
55 | ### style
56 | Style object
57 |
58 | ### wrapperTag
59 | Wrapper tag, `"div"` is default
60 |
61 | ### math
62 | MathJax content
63 |
64 | ### msDelayDisplay
65 | Milliseconds to delay display of div, 300 is default
66 |
67 | ### onDisplay (Function)
68 | Triggered after delay and div is shown, hopefully typeset has finished
69 |
70 | ### config (Object)
71 | MathJax configuration
72 |
73 | ### onLoad (Function)
74 | Triggered after MathJax script finishes loading before children are allowed to render.
75 |
76 | ### onError (Function)
77 | Triggered when any Math Processing Error occurs
78 |
79 | ### sanitizeOptions
80 | DOMPurify configuration object (optional). See https://github.com/cure53/DOMPurify#can-i-configure-dompurify
81 |
82 | ## Development
83 | Clone the repo and run yarn commands available in the `package.json` file.
84 |
85 | ```
86 | $ git clone https://github.com/mehdisadeghi/react-mathjax-preview && cd react-mathjax-preview
87 | $ yarn install // install dependencies
88 | $ yarn start // start the development server which serves the demo page
89 | $ yarn build // make a production build inside the dist folder
90 | $ yarn build:demo // make a demo build inside the dist folder
91 | ```
92 |
93 | And browse to [localhost:3000](http://localhost:3000).
94 |
95 | # License
96 | MIT
97 |
98 | [build-badge]: https://img.shields.io/travis/mehdisadeghi/react-mathjax-preview/master
99 | [build]: https://travis-ci.org/mehdisadeghi/react-mathjax-preview
100 |
101 | [npm-badge]: https://img.shields.io/npm/v/react-mathjax-preview
102 | [npm]: https://www.npmjs.org/package/react-mathjax-preview
103 |
104 | [coveralls-badge]: https://img.shields.io/coveralls/mehdisadeghi/react-mathjax-preview/master
105 | [coveralls]: https://coveralls.io/github/mehdisadeghi/react-mathjax-preview
106 |
--------------------------------------------------------------------------------
/demo.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import ReactDOM from 'react-dom'
3 | import ReactMathjaxPreview from './index.jsx'
4 |
5 | const asciimath = '`sum_(i=1)^n i^3=((n(n+1))/2)^2`'
6 | const someMath = String.raw`
7 |
12 |
13 | $$\lim_{x \to \infty} \exp(-x) = 0$$
14 |
15 | ${asciimath}
16 |
17 | `
50 |
51 | const App = () => {
52 | const [math, setMath] = useState(someMath)
53 |
54 | return (
55 |
56 |
react-mathjax-preview
57 |
58 | Enter some TeX, asciimath or MathML in the box below, or mix them all.
59 |
60 |
81 | )
82 | }
83 |
84 | ReactDOM.render(, document.getElementById('root'))
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | react-mathjax-preview
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import PropTypes from "prop-types";
3 | import DOMPurify from "dompurify";
4 |
5 | const baseConfig = {
6 | showMathMenu: true,
7 | tex2jax: {
8 | inlineMath: [
9 | ["$", "$"],
10 | ["\\(", "\\)"],
11 | ],
12 | },
13 | skipStartupTypeset: true,
14 | };
15 |
16 | const defaultSanitizeOptions = {
17 | USE_PROFILES: {mathMl: true},
18 | ADD_ATTR: ['columnalign'],
19 | }
20 |
21 | const MathJaxPreview = React.forwardRef(({
22 | script,
23 | config,
24 | className,
25 | math,
26 | id,
27 | style,
28 | wrapperTag,
29 | msDelayDisplay, //milliseconds to delay display of div, 300 is default
30 | onDisplay,
31 | onLoad,
32 | onError,
33 | sanitizeOptions,
34 | }, ref) => {
35 | const sanitizedMath = DOMPurify.sanitize(math, {...defaultSanitizeOptions, ...sanitizeOptions});
36 | const previewRef = useRef();
37 | const [display, setDisplay] = useState("none"); //prevent display during processing
38 | const [loadingState, setLoadingState] = useState(
39 | window.MathJax ? "loaded" : "loading"
40 | );
41 |
42 | useEffect(() => {
43 | let mathjaxScriptTag = document.querySelector(`script[src="${script}"]`);
44 | if (!mathjaxScriptTag) {
45 | mathjaxScriptTag = document.createElement("script");
46 | mathjaxScriptTag.async = true;
47 | mathjaxScriptTag.src = script;
48 |
49 | for (const [k, v] of Object.entries(config || {})) {
50 | mathjaxScriptTag.setAttribute(k, v);
51 | }
52 | const node = document.head || document.getElementsByTagName("head")[0];
53 | node.appendChild(mathjaxScriptTag);
54 | }
55 | const onloadHandler = () => {
56 | setLoadingState("loaded");
57 | window.MathJax.Hub.Config({ ...baseConfig, ...config });
58 | onLoad && onLoad();
59 | };
60 | const onerrorHandler = () => {
61 | setLoadingState("failed");
62 | onError && onError();
63 | };
64 |
65 | mathjaxScriptTag.addEventListener("load", onloadHandler);
66 | mathjaxScriptTag.addEventListener("error", onerrorHandler);
67 |
68 | return () => {
69 | mathjaxScriptTag.removeEventListener("load", onloadHandler);
70 | mathjaxScriptTag.removeEventListener("error", onloadHandler);
71 | };
72 | }, [setLoadingState, config, baseConfig]);
73 |
74 | useEffect(() => {
75 | if (loadingState !== "loaded") {
76 | return;
77 | }
78 | previewRef.current.innerHTML = sanitizedMath;
79 | window.MathJax.Hub.Queue([
80 | "Typeset",
81 | window.MathJax.Hub,
82 | previewRef.current,
83 | ]);
84 |
85 | //delay display of div
86 | var msDelay;
87 | if ( //msDelayDisplay prop is a reasonable number of ms
88 | msDelayDisplay !== null &&
89 | !isNaN(+msDelayDisplay) &&
90 | +msDelayDisplay >= 0 &&
91 | +msDelayDisplay < 10000
92 | ) {
93 | msDelay = +msDelayDisplay;
94 | } else {
95 | msDelay = 300; // default 300ms delay
96 | }
97 | const timeout = setTimeout(() => {
98 | setDisplay("inline"); //display div after delay, hopefully typeset has finished
99 | onDisplay && onDisplay();
100 | }, msDelay);
101 |
102 | return () => {
103 | setDisplay("none");
104 | clearTimeout(timeout);
105 | };
106 | }, [sanitizedMath, loadingState, previewRef]);
107 |
108 | return React.createElement(
109 | wrapperTag,
110 | { className, id, style: { display, ...style }, ref },
111 | <>
112 | {loadingState === "failed" && fail loading mathjax lib}
113 | {React.createElement(wrapperTag, {
114 | className: "react-mathjax-preview-result",
115 | ref: previewRef,
116 | })}
117 | >
118 | );
119 | });
120 |
121 | MathJaxPreview.displayName = 'MathJaxPreview';
122 |
123 | MathJaxPreview.propTypes = {
124 | script: PropTypes.string,
125 | config: PropTypes.object,
126 | className: PropTypes.string,
127 | math: PropTypes.string,
128 | style: PropTypes.object,
129 | wrapperTag: PropTypes.string,
130 | id: PropTypes.string,
131 | onLoad: PropTypes.func,
132 | onError: PropTypes.func,
133 | onDisplay: PropTypes.func,
134 | sanitizeOptions: PropTypes.object,
135 | };
136 |
137 | MathJaxPreview.defaultProps = {
138 | script:
139 | "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-MML-AM_HTMLorMML",
140 | id: "react-mathjax-preview",
141 | sanitizeOptions: {},
142 | wrapperTag: "div",
143 | };
144 |
145 | export default MathJaxPreview;
146 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-mathjax-preview",
3 | "version": "2.2.1",
4 | "description": "The MathJax React component you were looking for.",
5 | "author": "Mehdi Sadeghi",
6 | "homepage": "https://github.com/mehdisadeghi/react-mathjax-preview",
7 | "license": "MIT",
8 | "repository": "https://github.com/mehdisadeghi/react-mathjax-preview",
9 | "keywords": [
10 | "react",
11 | "react-component",
12 | "mathjax",
13 | "tex",
14 | "asciimath",
15 | "mathml"
16 | ],
17 | "main": "dist/index.js",
18 | "files": [
19 | "dist/index.*"
20 | ],
21 | "peerDependencies": {
22 | "prop-types": "^15",
23 | "react": "^16 || ^17",
24 | "react-dom": "^16 || ^17"
25 | },
26 | "scripts": {
27 | "start": "parcel serve -p 3000 index.html",
28 | "build": "parcel build --target main --no-optimize index.jsx",
29 | "build:demo": "parcel build --target demo --no-optimize index.html --dist-dir dist/demo",
30 | "test": "jest"
31 | },
32 | "devDependencies": {
33 | "@babel/preset-env": "^7.16.11",
34 | "@babel/preset-react": "^7.16.7",
35 | "babel-jest": "^27.5.1",
36 | "jest": "^27.5.1",
37 | "parcel": "^2.3.2",
38 | "prop-types": "^15",
39 | "react": "^16 || ^17",
40 | "react-dom": "^16 || ^17"
41 | },
42 | "dependencies": {
43 | "dompurify": "^2.0.8"
44 | },
45 | "targets": {
46 | "main": {},
47 | "demo": {}
48 | },
49 | "jest": {
50 | "transform": {
51 | "\\.[jt]sx?$": "babel-jest"
52 | },
53 | "testEnvironment": "jsdom"
54 | },
55 | "babel": {
56 | "presets": [
57 | "@babel/preset-react",
58 | "@babel/preset-env"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/test.jsx:
--------------------------------------------------------------------------------
1 | import expect from "expect";
2 | import React from "react";
3 | import { render, unmountComponentAtNode } from "react-dom";
4 |
5 | import ReactMathjaxPreview from "./index.jsx";
6 |
7 | describe("ReactMathjaxPreview", () => {
8 | let node;
9 |
10 | beforeEach(() => {
11 | node = document.createElement("div");
12 | });
13 |
14 | afterEach(() => {
15 | unmountComponentAtNode(node);
16 | });
17 |
18 | // TODO: test MathJax rendered output
19 | it("renders MathJax", () => {
20 | render(
21 | ,
24 | node,
25 | () => {
26 | expect(node.innerHTML).toContain("react-mathjax-preview-result");
27 | }
28 | );
29 | });
30 |
31 | it("renders MathJax with dynamic script", () => {
32 | render(
33 | ,
37 | node,
38 | () => {
39 | expect(node.innerHTML).toContain("react-mathjax-preview-result");
40 | }
41 | );
42 | });
43 | });
44 |
--------------------------------------------------------------------------------