Skip to content

Commit 9d7293d

Browse files
committed
feat(Math)!: add math hints
1 parent 4e2c49c commit 9d7293d

File tree

7 files changed

+144
-18
lines changed

7 files changed

+144
-18
lines changed

src/extensions/yfm/Math/hint.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import {createPortal} from 'react-dom';
3+
import {HelpPopover, HelpPopoverProps, Link} from '@gravity-ui/uikit';
4+
5+
import {cn} from '../../../classname';
6+
import {i18n} from '../../../i18n/math-hint';
7+
8+
export const b = cn('MathHint');
9+
10+
const MathHintContent: React.FC = function MathHintContent() {
11+
return (
12+
<>
13+
{i18n('math_hint')}{' '}
14+
<Link href={'https://katex.org/'} target={'blank'}>
15+
{i18n('math_hint_katex')}
16+
</Link>
17+
</>
18+
);
19+
};
20+
21+
type MathHintProps = Pick<HelpPopoverProps, 'offset' | 'hasArrow'>;
22+
23+
const MathHint: React.FC<MathHintProps> = function MathHint(props) {
24+
return (
25+
<HelpPopover
26+
placement={['bottom-end', 'top-end']}
27+
{...props}
28+
content={<MathHintContent />}
29+
/>
30+
);
31+
};
32+
33+
export function renderMathHint(props: MathHintProps, container: Element): React.ReactNode {
34+
return createPortal(<MathHint {...props} />, container);
35+
}

src/extensions/yfm/Math/index.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,18 @@ export const Math: ExtensionAuto = (builder) => {
3737
),
3838
}));
3939

40-
builder.addPlugin(mathViewAndEditPlugin).addInputRules((deps) => ({
41-
rules: [
42-
textblockTypeInputRule(/^\$\$\s$/, mathBType(deps.schema)),
43-
inlineNodeInputRule(/\$[^$\s]+\$$/, (match) =>
44-
mathIType(deps.schema).create(null, deps.schema.text(match.replace(/\$/g, ''))),
45-
),
46-
],
47-
}));
40+
builder
41+
.addPlugin(() =>
42+
mathViewAndEditPlugin({reactRenderer: builder.context.get('reactrenderer')!}),
43+
)
44+
.addInputRules((deps) => ({
45+
rules: [
46+
textblockTypeInputRule(/^\$\$\s$/, mathBType(deps.schema)),
47+
inlineNodeInputRule(/\$[^$\s]+\$$/, (match) =>
48+
mathIType(deps.schema).create(null, deps.schema.text(match.replace(/\$/g, ''))),
49+
),
50+
],
51+
}));
4852
builder
4953
.addAction(mathIAction, (deps) => {
5054
const type = mathIType(deps.schema);

src/extensions/yfm/Math/view-and-edit.scss

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,26 @@
4141

4242
opacity: 0;
4343
}
44+
45+
.ye-MathHint {
46+
&__block-view {
47+
display: none;
48+
}
49+
50+
&__inline-view {
51+
display: none;
52+
}
53+
}
54+
}
55+
56+
.ye-MathHint {
57+
&__block-view {
58+
float: right;
59+
}
60+
61+
&__inline-view {
62+
display: inline-block;
63+
64+
margin-right: 6px;
65+
}
4466
}

src/extensions/yfm/Math/view-and-edit.ts

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,42 @@ import type {Node} from 'prosemirror-model';
44
import {keydownHandler} from 'prosemirror-keymap';
55
import {Decoration, DecorationSet, NodeView} from 'prosemirror-view';
66
import {isTextSelection} from '../../../utils/selection';
7+
import type {ReactRenderer, RendererItem} from '../../../extensions/behavior/ReactRenderer';
78
import {moveCursorToEndOfMathInline} from './commands';
89
import {CLASSNAMES, MathNode} from './const';
10+
import {b, renderMathHint} from './hint';
911

1012
import 'katex/dist/katex.min.css';
1113
import './view-and-edit.scss';
1214

1315
export abstract class MathNodeView implements NodeView {
1416
dom: HTMLElement;
1517
contentDOM: HTMLElement;
18+
1619
protected node: Node;
1720
protected texContent: string;
1821
protected mathViewDOM: HTMLElement;
22+
protected mathHintContainerDOM: HTMLElement;
23+
24+
protected hintRendererItem: RendererItem;
25+
protected readonly reactRenderer: ReactRenderer;
1926

20-
abstract createDOM(): Record<'dom' | 'contentDOM' | 'mathViewDOM', HTMLElement>;
27+
abstract createDOM(): Record<
28+
'dom' | 'contentDOM' | 'mathViewDOM' | 'mathHintContainerDOM',
29+
HTMLElement
30+
> & {hintRendererItem: RendererItem};
2131
abstract isDisplayMode(): boolean;
2232

23-
constructor(node: Node) {
33+
constructor(node: Node, reactRenderer: ReactRenderer) {
2434
this.node = node;
35+
this.reactRenderer = reactRenderer;
2536
this.texContent = this.getTexContent();
2637
const elems = this.createDOM();
2738
this.dom = elems.dom;
2839
this.contentDOM = elems.contentDOM;
2940
this.mathViewDOM = elems.mathViewDOM;
41+
this.mathHintContainerDOM = elems.mathHintContainerDOM;
42+
this.hintRendererItem = elems.hintRendererItem;
3043
this.renderKatex();
3144
}
3245

@@ -41,6 +54,14 @@ export abstract class MathNodeView implements NodeView {
4154
return true;
4255
}
4356

57+
ignoreMutation(mutation: MutationRecord): boolean {
58+
return mutation.type === 'childList' && mutation.target === this.mathHintContainerDOM;
59+
}
60+
61+
destroy() {
62+
this.hintRendererItem.remove();
63+
}
64+
4465
protected renderKatex() {
4566
try {
4667
katex.render(this.texContent, this.mathViewDOM, {
@@ -62,11 +83,15 @@ export abstract class MathNodeView implements NodeView {
6283
}
6384

6485
export class MathInlineNodeView extends MathNodeView {
86+
destroy() {
87+
this.hintRendererItem?.remove();
88+
}
89+
6590
isDisplayMode(): boolean {
6691
return false;
6792
}
6893

69-
createDOM(): Record<'dom' | 'contentDOM' | 'mathViewDOM', HTMLElement> {
94+
createDOM() {
7095
const dom = document.createElement('span');
7196
dom.classList.add('math-container', 'math-inline-container');
7297

@@ -76,10 +101,25 @@ export class MathInlineNodeView extends MathNodeView {
76101

77102
const mathInlineDOM = this.createMathInlineDOM();
78103

104+
const mathHintContainerDOM = document.createElement('div');
105+
mathHintContainerDOM.contentEditable = 'false';
106+
mathHintContainerDOM.classList.add(b('inline-view'));
107+
79108
dom.appendChild(mathViewDOM);
80109
dom.appendChild(mathInlineDOM.container);
81-
82-
return {dom, contentDOM: mathInlineDOM.content, mathViewDOM};
110+
dom.appendChild(mathHintContainerDOM);
111+
112+
const hintRendererItem = this.reactRenderer.createItem('math-inline-hint', () =>
113+
renderMathHint({offset: {left: 3, top: -1}}, mathHintContainerDOM),
114+
);
115+
116+
return {
117+
dom,
118+
contentDOM: mathInlineDOM.content,
119+
mathViewDOM,
120+
mathHintContainerDOM,
121+
hintRendererItem,
122+
};
83123
}
84124

85125
// same as math-inline spec toDOM()
@@ -108,7 +148,7 @@ export class MathBlockNodeView extends MathNodeView {
108148
return true;
109149
}
110150

111-
createDOM(): Record<'dom' | 'contentDOM' | 'mathViewDOM', HTMLElement> {
151+
createDOM() {
112152
const dom = document.createElement('div');
113153
dom.classList.add('math-container', 'math-block-container');
114154

@@ -119,19 +159,28 @@ export class MathBlockNodeView extends MathNodeView {
119159
const contentDOM = document.createElement('div');
120160
contentDOM.classList.add('math-block');
121161

162+
const mathHintContainerDOM = document.createElement('div');
163+
mathHintContainerDOM.contentEditable = 'false';
164+
mathHintContainerDOM.classList.add(b('block-view'));
165+
166+
dom.appendChild(mathHintContainerDOM);
122167
dom.appendChild(mathViewDOM);
123168
dom.appendChild(contentDOM);
124169

125-
return {dom, contentDOM, mathViewDOM};
170+
const hintRendererItem = this.reactRenderer.createItem('math-block-hint', () =>
171+
renderMathHint({offset: {left: -3}}, mathHintContainerDOM),
172+
);
173+
174+
return {dom, contentDOM, mathViewDOM, mathHintContainerDOM, hintRendererItem};
126175
}
127176
}
128177

129-
export const mathViewAndEditPlugin = () =>
178+
export const mathViewAndEditPlugin = ({reactRenderer}: {reactRenderer: ReactRenderer}) =>
130179
new Plugin({
131180
props: {
132181
nodeViews: {
133-
[MathNode.Block]: (node) => new MathBlockNodeView(node),
134-
[MathNode.Inline]: (node) => new MathInlineNodeView(node),
182+
[MathNode.Block]: (node) => new MathBlockNodeView(node, reactRenderer),
183+
[MathNode.Inline]: (node) => new MathInlineNodeView(node, reactRenderer),
135184
},
136185
handleKeyDown: keydownHandler({
137186
ArrowLeft: moveCursorToEndOfMathInline,

src/i18n/math-hint/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"math_hint": "Math uses",
3+
"math_hint_katex": "Katex syntax"
4+
}

src/i18n/math-hint/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {registerKeyset} from '../i18n';
2+
3+
import en from './en.json';
4+
import ru from './ru.json';
5+
6+
const KEYSET = 'math-hint';
7+
8+
export const i18n = registerKeyset(KEYSET, {en, ru});

src/i18n/math-hint/ru.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"math_hint": "При создании формул используется",
3+
"math_hint_katex": "синтаксис Katex"
4+
}

0 commit comments

Comments
 (0)