diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58364012..402e5b46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/gitlint/gitlint_emoji.py b/gitlint/gitlint_emoji.py
index 89b5702c..c8331dea 100644
--- a/gitlint/gitlint_emoji.py
+++ b/gitlint/gitlint_emoji.py
@@ -28,7 +28,8 @@ def validate(self, title, _commit):
title contains one of them.
"""
gitmojis = requests.get(
- "https://raw.githubusercontent.com/carloscuesta/gitmoji/master/src/data/gitmojis.json"
+ "https://raw.githubusercontent.com/carloscuesta/gitmoji/"
+ "master/packages/gitmojis/src/gitmojis.json"
).json()["gitmojis"]
emojis = [item["emoji"] for item in gitmojis]
pattern = r"^({:s})\(.*\)\s[a-z].*$".format("|".join(emojis))
diff --git a/src/ashley/defaults.py b/src/ashley/defaults.py
index 49e2a0f1..0660c716 100644
--- a/src/ashley/defaults.py
+++ b/src/ashley/defaults.py
@@ -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"
@@ -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,
}
diff --git a/src/ashley/editor/decorators.py b/src/ashley/editor/decorators.py
index f88515bc..8549313f 100644
--- a/src/ashley/editor/decorators.py
+++ b/src/ashley/editor/decorators.py
@@ -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
diff --git a/src/frontend/jest.config.js b/src/frontend/jest.config.js
index bbe1d649..94c83549 100644
--- a/src/frontend/jest.config.js
+++ b/src/frontend/jest.config.js
@@ -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)/)',
+ ],
};
diff --git a/src/frontend/js/ashley.ts b/src/frontend/js/ashley.ts
index 265e0ba7..273ba096 100644
--- a/src/frontend/js/ashley.ts
+++ b/src/frontend/js/ashley.ts
@@ -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();
});
diff --git a/src/frontend/js/components/AshleyEditor/index.spec.tsx b/src/frontend/js/components/AshleyEditor/index.spec.tsx
index 4591f937..db78cab2 100644
--- a/src/frontend/js/components/AshleyEditor/index.spec.tsx
+++ b/src/frontend/js/components/AshleyEditor/index.spec.tsx
@@ -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';
@@ -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
@@ -181,6 +184,53 @@ describe('AshleyEditor', () => {
);
});
+ it('recognizes a block of type TEXBLOCK using atomic blocks', () => {
+ render(
+
+
+ ,
+ );
+ 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(
+
+
+ ,
+ );
+ 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(
+
+
+ ,
+ );
+
+ 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(
diff --git a/src/frontend/js/components/AshleyEditor/index.tsx b/src/frontend/js/components/AshleyEditor/index.tsx
index 619937d0..b442a45e 100644
--- a/src/frontend/js/components/AshleyEditor/index.tsx
+++ b/src/frontend/js/components/AshleyEditor/index.tsx
@@ -31,7 +31,6 @@ 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';
@@ -39,6 +38,7 @@ 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;
@@ -67,7 +67,6 @@ export const AshleyEditor = (props: MyEditorProps) => {
emojiPlugin,
linkPlugin,
toolbarPlugin,
- codeEditorPlugin,
blockDndPlugin,
alignmentPlugin,
focusPlugin,
@@ -85,7 +84,6 @@ export const AshleyEditor = (props: MyEditorProps) => {
},
}),
toolbarPlugin: createToolbarPlugin(),
- codeEditorPlugin: createCodeEditorPlugin(),
blockDndPlugin: createBlockDndPlugin(),
alignmentPlugin: createAlignmentPlugin(),
focusPlugin: createFocusPlugin(),
@@ -107,7 +105,9 @@ export const AshleyEditor = (props: MyEditorProps) => {
const [{ imagePlugin }] = useState({
imagePlugin: createImagePlugin({ decorator }),
});
-
+ const [{ LaTeXPlugin }] = useState({
+ LaTeXPlugin: getLaTeXPlugin({}),
+ });
const { AlignmentTool } = alignmentPlugin;
useEffect(() => {
@@ -169,12 +169,12 @@ export const AshleyEditor = (props: MyEditorProps) => {
toolbarPlugin,
emojiPlugin,
linkPlugin,
- codeEditorPlugin,
blockDndPlugin,
focusPlugin,
alignmentPlugin,
resizeablePlugin,
imagePlugin,
+ LaTeXPlugin,
];
if (props.mentions) {
@@ -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!));
}, []);
diff --git a/src/frontend/js/draftjs-plugins/code-editor/index.ts b/src/frontend/js/draftjs-plugins/code-editor/index.ts
deleted file mode 100644
index 541fe732..00000000
--- a/src/frontend/js/draftjs-plugins/code-editor/index.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-/**
- * Credits :
- * Forked from https://github.com/withspectrum/draft-js-code-editor-plugin
- * - added an unstyle block after key return is pressed. This is done by inserting a new
- * block when the split-block keyCommand is fired.
- */
-
-import CodeUtils from 'draft-js-code';
-import Draft, {
- ContentBlock,
- EditorState,
- KeyBindingUtil,
- RichUtils,
- DraftEditorCommand,
-} from 'draft-js';
-import { EditorPlugin, PluginFunctions } from '@draft-js-plugins/editor/lib';
-import React from 'react';
-import { List } from 'immutable';
-
-const createCodeEditorPlugin = (): EditorPlugin => {
- return {
- handleKeyCommand(
- command: string,
- editorState: EditorState,
- eventTimeStamp: number,
- { setEditorState }: PluginFunctions,
- ) {
- const newState = CodeUtils.hasSelectionInBlock(editorState)
- ? CodeUtils.handleKeyCommand(editorState, command)
- : RichUtils.handleKeyCommand(editorState, command);
-
- if (newState) {
- setEditorState(newState);
- return 'handled';
- }
- return 'not-handled';
- },
- keyBindingFn(
- evt: React.KeyboardEvent,
- { getEditorState }: PluginFunctions,
- ) {
- return !CodeUtils.hasSelectionInBlock(getEditorState())
- ? Draft.getDefaultKeyBinding(evt)
- : (CodeUtils.getKeyBinding(evt) as DraftEditorCommand);
- },
- handleReturn(
- evt: React.KeyboardEvent,
- editorState: EditorState,
- { setEditorState }: PluginFunctions,
- ) {
- if (!CodeUtils.hasSelectionInBlock(editorState)) return 'not-handled';
- const selection = editorState.getSelection();
- // Add an unstyle block after key return is pressed. Detect when the keyCommand is fired and split the block
- if (selection.isCollapsed() && KeyBindingUtil.hasCommandModifier(evt)) {
- const contentState = editorState.getCurrentContent();
- const newBlock = new ContentBlock({
- key: Draft.genKey(),
- type: 'unstyled',
- text: '',
- characterList: List(),
- });
- const newBlockMap = contentState
- .getBlockMap()
- .set(newBlock.getKey(), newBlock);
- const newState = Draft.EditorState.push(
- editorState,
- Draft.ContentState.createFromBlockArray(newBlockMap.toArray()).set(
- 'selectionAfter',
- contentState.getSelectionAfter().merge({
- anchorKey: newBlock.getKey(),
- anchorOffset: 0,
- focusKey: newBlock.getKey(),
- focusOffset: 0,
- isBackward: false,
- }),
- ) as Draft.ContentState,
- 'split-block',
- );
- setEditorState(newState);
- return 'handled';
- }
- setEditorState(CodeUtils.handleReturn(evt, editorState));
- return 'handled';
- },
- onTab(
- evt: React.KeyboardEvent,
- { getEditorState, setEditorState }: PluginFunctions,
- ) {
- const editorState = getEditorState();
- if (!CodeUtils.hasSelectionInBlock(editorState)) return;
-
- setEditorState(CodeUtils.onTab(evt, editorState));
- return true;
- },
- };
-};
-
-export default createCodeEditorPlugin;
diff --git a/src/frontend/js/types/libs/draft-js-code/draft-js-code.d.ts b/src/frontend/js/types/libs/draft-js-code/draft-js-code.d.ts
deleted file mode 100644
index 8fb28dcc..00000000
--- a/src/frontend/js/types/libs/draft-js-code/draft-js-code.d.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-declare module 'draft-js-code' {
- import { EditorState } from 'draft-js';
-
- function getKeyBinding(event: React.KeyboardEvent): string;
- function hasSelectionInBlock(editorState: EditorState): boolean;
- function handleKeyCommand(
- editorState: EditorState,
- command: string,
- ): EditorState;
- function handleReturn(
- event: React.KeyboardEvent,
- editorState: EditorState,
- ): EditorState;
- function onTab(
- event: React.KeyboardEvent,
- editorState: EditorState,
- ): EditorState;
-
- export = {
- getKeyBinding,
- hasSelectionInBlock,
- handleKeyCommand,
- handleReturn,
- onTab,
- };
-}
diff --git a/src/frontend/js/types/libs/draft-js-latex-plugin/draft-js-latex-plugin.d.ts b/src/frontend/js/types/libs/draft-js-latex-plugin/draft-js-latex-plugin.d.ts
new file mode 100644
index 00000000..ed780c6c
--- /dev/null
+++ b/src/frontend/js/types/libs/draft-js-latex-plugin/draft-js-latex-plugin.d.ts
@@ -0,0 +1 @@
+declare module 'draft-js-latex-plugin';
diff --git a/src/frontend/js/utils/latex.ts b/src/frontend/js/utils/latex.ts
new file mode 100644
index 00000000..96b0f7c4
--- /dev/null
+++ b/src/frontend/js/utils/latex.ts
@@ -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'),
+ });
+ }
+ });
+};
diff --git a/src/frontend/package.json b/src/frontend/package.json
index 57165156..492fe760 100644
--- a/src/frontend/package.json
+++ b/src/frontend/package.json
@@ -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",
@@ -77,15 +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-code": "0.3.0",
"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",
diff --git a/src/frontend/scss/_main.scss b/src/frontend/scss/_main.scss
index 379e1f77..359b2cd3 100644
--- a/src/frontend/scss/_main.scss
+++ b/src/frontend/scss/_main.scss
@@ -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';
diff --git a/src/frontend/webpack.config.js b/src/frontend/webpack.config.js
index 1d48cbc7..7579bfbc 100644
--- a/src/frontend/webpack.config.js
+++ b/src/frontend/webpack.config.js
@@ -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('|')}.*)`),
diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock
index e960179a..bba97a55 100644
--- a/src/frontend/yarn.lock
+++ b/src/frontend/yarn.lock
@@ -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/katex@0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe"
+ integrity sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==
+
"@types/lodash-es@4.17.4":
version "4.17.4"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97"
@@ -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"
@@ -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"
@@ -3084,16 +3082,6 @@ dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"
-draft-js-code@0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/draft-js-code/-/draft-js-code-0.3.0.tgz#d955d134367baeee5844b57f897c8d848bc979c5"
- integrity sha512-e/Rblaj1FvYzvQK45QHt8qAl5lBLFSHWPzNrF8Avv/8Bx0ZtPHD8ykCdazM2NjTvinZmPJ5dLoRHphgFQycasg==
- dependencies:
- detect-indent "^4.0.0"
- detect-newline "^2.1.0"
- ends-with "^0.2.0"
- immutable "3.x"
-
draft-js-emoji-plugin@2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/draft-js-emoji-plugin/-/draft-js-emoji-plugin-2.1.3.tgz#c5fb2884c9bdbf4b07e51b1d49ec89a58aca54f4"
@@ -3109,6 +3097,11 @@ draft-js-emoji-plugin@2.1.3:
react-icons "^2.2.6"
to-style "^1.3.3"
+draft-js-latex-plugin@0.1.2:
+ 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==
+
draft-js-mention-plugin@3.1.5:
version "3.1.5"
resolved "https://registry.yarnpkg.com/draft-js-mention-plugin/-/draft-js-mention-plugin-3.1.5.tgz#5ee76fc4b4d1e1b2ef996fcec84ffe5565c8d1a9"
@@ -3184,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"
@@ -3526,6 +3514,14 @@ fetch-mock@9.11.0:
querystring "^0.2.0"
whatwg-url "^6.5.0"
+file-loader@6.2.0:
+ 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"
@@ -4006,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=
-immutable@3.x:
- 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"
@@ -4195,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"
@@ -4900,6 +4886,13 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
+katex@0.16.4:
+ 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"
@@ -6177,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"
diff --git a/tests/ashley/draftjs_exporter/test_draftjs_exporter_decorator_latex.py b/tests/ashley/draftjs_exporter/test_draftjs_exporter_decorator_latex.py
new file mode 100644
index 00000000..ae435687
--- /dev/null
+++ b/tests/ashley/draftjs_exporter/test_draftjs_exporter_decorator_latex.py
@@ -0,0 +1,129 @@
+from django.test import TestCase
+from draftjs_exporter.dom import DOM
+
+from ashley.editor.decorators import inlinetex, render_children
+
+
+class TestInlinetexDecorator(TestCase):
+ """Test custom inlinetex decorator for draftjs_exporter"""
+
+ def test_custom_decorator_inlinetex_ok(self):
+ """
+ check custom decorator ‘inlinetex‘ returns expected html
+ """
+
+ tex = "\left.\frac{x^3}{3}\right|_0^1" # noqa: W605
+ self.assertEqual(
+ DOM.render(DOM.create_element(inlinetex, {"tex": tex})),
+ f'{tex}',
+ )
+
+ def test_custom_decorator_inlinetex_empty(self):
+ """
+ check custom decorator ‘inlinetex‘ returns expected html even when
+ the content is empty
+ """
+ self.assertEqual(
+ DOM.render(DOM.create_element(inlinetex, {"tex": ""})),
+ '',
+ )
+
+ def test_custom_decorator_inlinetex_no_maths(self):
+ """
+ check custom decorator ‘inlinetex‘ returns expected html even when
+ the content is not a formula but a regular string
+ """
+ self.assertEqual(
+ DOM.render(DOM.create_element(inlinetex, {"tex": "a common string"})),
+ 'a common string',
+ )
+
+ def test_custom_decorator_displaytex_ok(self):
+ """
+ check custom decorator for `render_children` of tex returns expected html
+ """
+
+ tex = "\left.\frac{x^3}{3}\right|_0^1" # noqa: W605
+
+ self.assertEqual(
+ DOM.render(
+ DOM.create_element(
+ render_children,
+ {
+ "block": {
+ "key": "a215p",
+ "text": "",
+ "type": "atomic",
+ "data": {"tex": tex, "type": "TEXBLOCK"},
+ }
+ },
+ )
+ ),
+ f'{tex}',
+ )
+
+ def test_custom_decorator_displaytex_empty(self):
+ """
+ check custom decorator `render_children` returns expected html even when
+ the content is empty
+ """
+ self.assertEqual(
+ DOM.render(
+ DOM.create_element(
+ render_children,
+ {
+ "block": {
+ "key": "a215p",
+ "text": "",
+ "type": "atomic",
+ "data": {"tex": "", "type": "TEXBLOCK"},
+ }
+ },
+ )
+ ),
+ '',
+ )
+
+ def test_custom_decorator_displaytex_no_maths(self):
+ """
+ check custom decorator `render_children` returns expected html even when
+ the content is not a formula but a regular string
+ """
+ self.assertEqual(
+ DOM.render(
+ DOM.create_element(
+ render_children,
+ {
+ "block": {
+ "key": "a215p",
+ "text": "",
+ "type": "atomic",
+ "data": {"tex": "a common string", "type": "TEXBLOCK"},
+ }
+ },
+ )
+ ),
+ 'a common string',
+ )
+
+ def test_custom_decorator_displaytex_no_malformed(self):
+ """
+ check custom decorator `render_children` returns expected html even when
+ the `tex` attribute is missing
+ """
+ self.assertEqual(
+ DOM.render(
+ DOM.create_element(
+ render_children,
+ {
+ "block": {
+ "key": "a215p",
+ "text": "",
+ "type": "atomic",
+ "data": {"type": "TEXBLOCK"},
+ }
+ },
+ )
+ ),
+ '',
+ )