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 and to
write the $ character, type \$
  • Loading branch information
carofun committed Dec 16, 2022
1 parent 4355cb3 commit 18b28a3
Show file tree
Hide file tree
Showing 14 changed files with 166 additions and 42 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
3 changes: 2 additions & 1 deletion src/ashley/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
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

_FORUM_ROLE_ADMINISTRATOR = "administrator"
_FORUM_ROLE_INSTRUCTOR = "instructor"
Expand Down Expand Up @@ -95,6 +95,7 @@
"emoji": emoji,
"mention": mention,
"IMAGE": image,
"INLINETEX": inlinetex,
},
"composite_decorators": [],
"block_map": DEFAULT_BLOCK_MAP,
Expand Down
7 changes: 7 additions & 0 deletions src/ashley/editor/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,10 @@ def image(props):
)

return None


def inlinetex(props):
"""
Decorator for the `INLINETEX` entity in Draft.js ContentState.
"""
return DOM.create_element("span", {"class": "latex"}, props.get("tex", None))
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('creates a block of type inlinetex', () => {
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';
9 changes: 9 additions & 0 deletions src/frontend/js/utils/latex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import katex from 'katex';

export const renderLatex = () => {
document.querySelectorAll('span.latex').forEach((math) => {
if (math.textContent) {
math.innerHTML = katex.renderToString(math.textContent);
}
});
};
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
64 changes: 30 additions & 34 deletions src/frontend/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1565,6 +1565,11 @@
resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e"
integrity sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw==

"@types/[email protected]":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe"
integrity sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==

"@types/[email protected]":
version "4.17.4"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97"
Expand Down Expand Up @@ -2727,6 +2732,11 @@ commander@^2.12.1, commander@^2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==

commander@^8.0.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==

commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
Expand Down Expand Up @@ -3029,18 +3039,6 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=

detect-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg=
dependencies:
repeating "^2.0.0"

detect-newline@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=

detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
Expand Down Expand Up @@ -3099,6 +3097,11 @@ [email protected]:
react-icons "^2.2.6"
to-style "^1.3.3"

[email protected]:
version "0.1.2"
resolved "https://registry.yarnpkg.com/draft-js-latex-plugin/-/draft-js-latex-plugin-0.1.2.tgz#49f8619a69d3dd838bed1462ce30f7d0a5812da0"
integrity sha512-6Qeaw2PJGuIzZ/NgjAPPgf0HqMXhkoUSIyXglm2vs/smUIFdei77mV5ggvvk2DL2zgYwUBbHmBzx0RhkEjHJsQ==

[email protected]:
version "3.1.5"
resolved "https://registry.yarnpkg.com/draft-js-mention-plugin/-/draft-js-mention-plugin-3.1.5.tgz#5ee76fc4b4d1e1b2ef996fcec84ffe5565c8d1a9"
Expand Down Expand Up @@ -3174,11 +3177,6 @@ end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"

ends-with@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ends-with/-/ends-with-0.2.0.tgz#2f9da98d57a50cfda4571ce4339000500f4e6b8a"
integrity sha1-L52pjVelDP2kVxzkM5AAUA9Oa4o=

enhanced-resolve@^5.8.0:
version "5.8.2"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b"
Expand Down Expand Up @@ -3516,6 +3514,14 @@ [email protected]:
querystring "^0.2.0"
whatwg-url "^6.5.0"

[email protected]:
version "6.2.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d"
integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"

fill-range@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
Expand Down Expand Up @@ -3996,11 +4002,6 @@ ignore-by-default@^1.0.1:
resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=

[email protected]:
version "3.8.2"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=

immutable@~3.7.4:
version "3.7.6"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
Expand Down Expand Up @@ -4185,11 +4186,6 @@ is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=

is-finite@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==

is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
Expand Down Expand Up @@ -4890,6 +4886,13 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"

[email protected]:
version "0.16.4"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.4.tgz#87021bc3bbd80586ef715aeb476794cba6a49ad4"
integrity sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==
dependencies:
commander "^8.0.0"

keyv@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9"
Expand Down Expand Up @@ -6167,13 +6170,6 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=

repeating@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=
dependencies:
is-finite "^1.0.0"

request@^2.88.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
Expand Down
Loading

0 comments on commit 18b28a3

Please sign in to comment.