Skip to content

Commit

Permalink
✨(forum) enables latext mathematical syntax
Browse files Browse the repository at this point in the history
Using katex, we enable latex mathematical syntax in
draft.js to write equations in forums.
In draft.js press $ for starting Inline TeX, to
write the $ character, type \$, and Press CMD + M for
starting Tex Block
  • Loading branch information
carofun committed Dec 29, 2022
1 parent 13a301f commit f740c76
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 43 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Enabling latex mathematical equations in forums

## [1.2.5] - 2022-10-14

### Fixed
Expand Down
17 changes: 15 additions & 2 deletions src/ashley/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
from draftjs_exporter.defaults import STYLE_MAP as DEFAULT_STYLE_MAP
from draftjs_exporter.dom import DOM

from ashley.editor.decorators import emoji, image, link, mention
from ashley.editor.decorators import (
emoji,
image,
inlinetex,
link,
mention,
render_children,
)

_FORUM_ROLE_ADMINISTRATOR = "administrator"
_FORUM_ROLE_INSTRUCTOR = "instructor"
Expand Down Expand Up @@ -95,9 +102,15 @@
"emoji": emoji,
"mention": mention,
"IMAGE": image,
"INLINETEX": inlinetex,
},
"composite_decorators": [],
"block_map": DEFAULT_BLOCK_MAP,
"block_map": dict(
DEFAULT_BLOCK_MAP,
**{
"atomic": render_children,
},
),
"style_map": DEFAULT_STYLE_MAP,
"engine": DOM.STRING,
}
Expand Down
25 changes: 25 additions & 0 deletions src/ashley/editor/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,28 @@ def image(props):
)

return None


def inlinetex(props):
"""
Decorator for the `INLINETEX` entity in Draft.js ContentState.
"""
return DOM.create_element(
"span", {"class": "ashley-latex-inline"}, props.get("tex", None)
)


def render_children(props):
"""
Decorator for the blocks in Draft.js ContentState. TEXBLOCK is a custom
block that is used through atomic blocks.
"""
if props.get("block").get("data").get("type") == "TEXBLOCK":
return DOM.create_element(
"span",
{"class": "ashley-latex-display"},
props.get("block").get("data").get("tex", None),
)

# default behaviour
return props
4 changes: 3 additions & 1 deletion src/frontend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ module.exports = {
setupFilesAfterEnv: ['./testSetup.ts', 'regenerator-runtime/runtime'],
testMatch: [__dirname + '/js/**/*.spec.+(ts|tsx|js)'],
testURL: 'https://localhost',
transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'],
transformIgnorePatterns: [
'/node_modules/(?!(lodash-es|draft-js-latex-plugin)/)',
],
};
2 changes: 2 additions & 0 deletions src/frontend/js/ashley.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { renderEmojis } from './utils/emojis';
import { renderHighlight } from './utils/highlight';
import { renderLatex } from './utils/latex';

// expose some modules to the global window object

document.addEventListener('DOMContentLoaded', (event) => {
renderEmojis();
renderHighlight();
renderLatex();
});
52 changes: 51 additions & 1 deletion src/frontend/js/components/AshleyEditor/index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createEvent } from '@testing-library/dom';
import { render, fireEvent, screen } from '@testing-library/react';
import { render, fireEvent, screen, within } from '@testing-library/react';
import user from '@testing-library/user-event';
import React from 'react';
import { IntlProvider } from 'react-intl';
Expand All @@ -25,6 +25,9 @@ describe('AshleyEditor', () => {
};
afterEach(() => {
jest.resetAllMocks();
if (target) {
target.value = '';
}
});

// add input target, it's value is linked with editor content and needed for all the tests
Expand Down Expand Up @@ -181,6 +184,53 @@ describe('AshleyEditor', () => {
);
});

it('recognizes a block of type TEXBLOCK using atomic blocks', () => {
render(
<IntlProvider locale="en">
<AshleyEditor target="target" {...props} />
</IntlProvider>,
);
expect(screen.queryByRole('figure')).not.toBeInTheDocument();

// TEXBLOCK are recognized
target.value = BlockMapFactory([
{
type: 'atomic',
text: 'displaystylesum_{i=1}^{k+1}i',
data: {
tex: '',
type: 'TEXBLOCK',
},
},
]);

render(
<IntlProvider locale="en">
<AshleyEditor target="target" {...props} />
</IntlProvider>,
);
const figure = screen.getByRole('figure');
const latex = within(figure).getByRole('textbox');
expect(latex).toBeInTheDocument();
});

it('generates a block of type inlinetex when `$` key is pressed', () => {
const { container } = render(
<IntlProvider locale="en">
<AshleyEditor target="target" {...props} />
</IntlProvider>,
);

expect(screen.queryAllByRole('textbox').length).toEqual(1);
// select the draft-js editor
const editorNode = container.querySelector('.public-DraftEditor-content')!;

fireEvent.keyDown(editorNode, { key: '$', code: '221', charCode: 36 });
expect(screen.queryAllByRole('textbox').length).toEqual(2);
const latex = screen.queryAllByRole('textbox')[1];
expect(latex).toHaveClass('TeXInput');
});

it('renders the editor with a list of users to mention', () => {
// load the editor with no list of users to mention
render(
Expand Down
11 changes: 6 additions & 5 deletions src/frontend/js/components/AshleyEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ import {
BlockquoteButton,
CodeBlockButton,
} from '@draft-js-plugins/buttons';
import createCodeEditorPlugin from '../../draftjs-plugins/code-editor';
import { ImageAdd } from './ImageAdd';
import createAlignmentPlugin from '@draft-js-plugins/alignment';
import createFocusPlugin from '@draft-js-plugins/focus';
import createResizeablePlugin from '@draft-js-plugins/resizeable';
import createBlockDndPlugin from '@draft-js-plugins/drag-n-drop';
import { useIntl } from 'react-intl';
import { messagesEditor } from './messages';
import { getLaTeXPlugin } from 'draft-js-latex-plugin';

interface MyEditorProps {
autofocus?: boolean;
Expand Down Expand Up @@ -67,7 +67,6 @@ export const AshleyEditor = (props: MyEditorProps) => {
emojiPlugin,
linkPlugin,
toolbarPlugin,
codeEditorPlugin,
blockDndPlugin,
alignmentPlugin,
focusPlugin,
Expand All @@ -85,7 +84,6 @@ export const AshleyEditor = (props: MyEditorProps) => {
},
}),
toolbarPlugin: createToolbarPlugin(),
codeEditorPlugin: createCodeEditorPlugin(),
blockDndPlugin: createBlockDndPlugin(),
alignmentPlugin: createAlignmentPlugin(),
focusPlugin: createFocusPlugin(),
Expand All @@ -107,7 +105,9 @@ export const AshleyEditor = (props: MyEditorProps) => {
const [{ imagePlugin }] = useState({
imagePlugin: createImagePlugin({ decorator }),
});

const [{ LaTeXPlugin }] = useState({
LaTeXPlugin: getLaTeXPlugin({}),
});
const { AlignmentTool } = alignmentPlugin;

useEffect(() => {
Expand Down Expand Up @@ -169,12 +169,12 @@ export const AshleyEditor = (props: MyEditorProps) => {
toolbarPlugin,
emojiPlugin,
linkPlugin,
codeEditorPlugin,
blockDndPlugin,
focusPlugin,
alignmentPlugin,
resizeablePlugin,
imagePlugin,
LaTeXPlugin,
];

if (props.mentions) {
Expand All @@ -183,6 +183,7 @@ export const AshleyEditor = (props: MyEditorProps) => {
});
const [open, setOpen] = useState(true);
const [suggestions, setSuggestions] = useState(props.mentions);

const onSearchChange = useCallback(({ value }: { value: string }) => {
setSuggestions(defaultSuggestionsFilter(value, props.mentions!));
}, []);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'draft-js-latex-plugin';
11 changes: 11 additions & 0 deletions src/frontend/js/utils/latex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import katex from 'katex';

export const renderLatex = () => {
document.querySelectorAll('[class^=ashley-latex]').forEach((math) => {
if (math.textContent) {
math.innerHTML = katex.renderToString(math.textContent, {
displayMode: math.classList.contains('ashley-latex-display'),
});
}
});
};
4 changes: 4 additions & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"cljs-merge": "1.1.1",
"css-loader": "5.2.4",
"fetch-mock": "9.11.0",
"file-loader": "6.2.0",
"intl-pluralrules": "1.2.2",
"jest": "26.6.3",
"lodash-es": "4.17.21",
Expand Down Expand Up @@ -77,14 +78,17 @@
"@formatjs/intl-relativetimeformat": "8.1.6",
"@fortawesome/fontawesome-free": "5.15.3",
"@testing-library/user-event": "13.1.9",
"@types/katex": "0.14.0",
"bootstrap": "4.6.0",
"core-js": "3.11.1",
"draft-js": "0.11.7",
"draft-js-emoji-plugin": "2.1.3",
"draft-js-latex-plugin": "0.1.2",
"draft-js-mention-plugin": "3.1.5",
"emojione": "4.5.0",
"es6-shim": "0.35.6",
"highlight.js": "10.7.2",
"katex": "0.16.4",
"mdn-polyfills": "5.20.0",
"react": "17.0.2",
"react-autosuggest": "10.1.0",
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/scss/_main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
@import '@draft-js-plugins/mention/lib/plugin';
@import '@draft-js-plugins/static-toolbar/lib/plugin';

//latex code style
@import 'draft-js-latex-plugin/lib/styles';
@import 'katex/dist/katex';

//highlight code style
@import 'highlight.js/styles/github';

Expand Down
4 changes: 4 additions & 0 deletions src/frontend/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ module.exports = () => {

module: {
rules: [
{
test: /\.(woff|woff2|ttf|eot|png|jpg|svg|gif)$/i,
use: ['file-loader'],
},
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{
test: new RegExp(`(${babelCompileDeps.join('|')}.*)`),
Expand Down
Loading

0 comments on commit f740c76

Please sign in to comment.