Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(forum) enables latex mathematical syntax #277

Merged
merged 3 commits into from
Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 gitlint/gitlint_emoji.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
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
98 changes: 0 additions & 98 deletions src/frontend/js/draftjs-plugins/code-editor/index.ts

This file was deleted.

26 changes: 0 additions & 26 deletions src/frontend/js/types/libs/draft-js-code/draft-js-code.d.ts

This file was deleted.

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'),
});
}
});
};
Loading