prosemirror-math 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -32
- package/dist/prosemirror-math.d.ts +65 -8
- package/dist/prosemirror-math.js +145 -44
- package/package.json +7 -5
- package/src/create-element.spec.ts +1 -1
- package/src/cursor-inside-plugin.spec.ts +8 -8
- package/src/cursor-inside-plugin.ts +35 -8
- package/src/index.ts +7 -7
- package/src/katex.ts +9 -0
- package/src/math-block-enter-rule.spec.ts +2 -2
- package/src/math-block-enter-rule.ts +6 -0
- package/src/math-block-spec.spec.ts +2 -2
- package/src/math-block-spec.ts +12 -3
- package/src/math-block-view.spec.ts +146 -26
- package/src/math-block-view.ts +25 -20
- package/src/math-inline-input-rule.spec.ts +2 -2
- package/src/math-inline-input-rule.ts +8 -0
- package/src/math-inline-spec.spec.ts +2 -2
- package/src/math-inline-spec.ts +9 -3
- package/src/math-inline-view.spec.ts +133 -19
- package/src/math-inline-view.ts +31 -21
- package/src/math-view-render.ts +75 -0
- package/src/mathjax.ts +28 -0
- package/src/temml.ts +9 -0
- package/src/testing.ts +61 -27
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ Math editing extensions for [ProseMirror](https://prosemirror.net/). Provides no
|
|
|
8
8
|
npm install prosemirror-math
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
<!-- TODO: Add live demo link -->
|
|
12
|
+
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
13
15
|
### Node specs
|
|
@@ -23,8 +25,8 @@ const schema = new Schema({
|
|
|
23
25
|
doc: { content: 'block+' },
|
|
24
26
|
paragraph: { content: 'inline*', group: 'block', parseDOM: [{ tag: 'p' }], toDOM: () => ['p', 0] },
|
|
25
27
|
text: { group: 'inline' },
|
|
26
|
-
mathBlock: { ...mathBlockSpec
|
|
27
|
-
mathInline: { ...mathInlineSpec
|
|
28
|
+
mathBlock: { ...mathBlockSpec },
|
|
29
|
+
mathInline: { ...mathInlineSpec },
|
|
28
30
|
},
|
|
29
31
|
})
|
|
30
32
|
```
|
|
@@ -41,12 +43,12 @@ import Temml from 'temml'
|
|
|
41
43
|
const view = new EditorView(document.body, {
|
|
42
44
|
state,
|
|
43
45
|
nodeViews: {
|
|
44
|
-
mathBlock: (node) => createMathBlockView(
|
|
46
|
+
mathBlock: (node, view, getPos, decorations) => createMathBlockView((text, element) => {
|
|
45
47
|
Temml.render(text, element, { displayMode: true })
|
|
46
|
-
}),
|
|
47
|
-
mathInline: (node) => createMathInlineView(
|
|
48
|
+
}, node, decorations),
|
|
49
|
+
mathInline: (node, view, getPos, decorations) => createMathInlineView((text, element) => {
|
|
48
50
|
Temml.render(text, element, { displayMode: false })
|
|
49
|
-
}),
|
|
51
|
+
}, node, decorations),
|
|
50
52
|
},
|
|
51
53
|
})
|
|
52
54
|
```
|
|
@@ -66,7 +68,7 @@ const plugin = inputRules({
|
|
|
66
68
|
|
|
67
69
|
### Enter rule
|
|
68
70
|
|
|
69
|
-
`mathBlockEnterRule` is an [EnterRule](https://
|
|
71
|
+
`mathBlockEnterRule` is an [EnterRule](https://www.npmjs.com/package/prosemirror-enter-rules) that converts a paragraph containing `$$` into a math block when Enter is pressed.
|
|
70
72
|
|
|
71
73
|
```ts
|
|
72
74
|
import { createEnterRulePlugin } from 'prosemirror-enter-rules'
|
|
@@ -79,39 +81,17 @@ const plugin = createEnterRulePlugin({
|
|
|
79
81
|
|
|
80
82
|
### Cursor inside plugin
|
|
81
83
|
|
|
82
|
-
`createCursorInsidePlugin` adds a `
|
|
84
|
+
`createCursorInsidePlugin` adds a `prosemirror-math-head-inside` CSS class to math nodes when the cursor is inside them, useful for styling the active math node.
|
|
83
85
|
|
|
84
86
|
```ts
|
|
85
87
|
import { createCursorInsidePlugin } from 'prosemirror-math'
|
|
86
88
|
|
|
87
|
-
const plugin = createCursorInsidePlugin(
|
|
89
|
+
const plugin = createCursorInsidePlugin()
|
|
88
90
|
```
|
|
89
91
|
|
|
90
92
|
## API
|
|
91
93
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- **`mathBlockSpec`** — NodeSpec for block math (`div.prosekit-math-block > pre > code`)
|
|
95
|
-
- **`mathInlineSpec`** — NodeSpec for inline math (`span.prosekit-math-inline > code`)
|
|
96
|
-
|
|
97
|
-
### Node views
|
|
98
|
-
|
|
99
|
-
- **`createMathBlockView(node, renderMath)`** — Creates a block math NodeView
|
|
100
|
-
- **`createMathInlineView(node, renderMath)`** — Creates an inline math NodeView
|
|
101
|
-
|
|
102
|
-
The `renderMath` callback receives `(text: string, element: HTMLElement)` and should render the math into the element.
|
|
103
|
-
|
|
104
|
-
### Input rules
|
|
105
|
-
|
|
106
|
-
- **`createMathInlineInputRule(nodeType)`** — Creates an InputRule for inline math
|
|
107
|
-
|
|
108
|
-
### Enter rules
|
|
109
|
-
|
|
110
|
-
- **`mathBlockEnterRule`** — EnterRule for converting `$$` into a math block
|
|
111
|
-
|
|
112
|
-
### Plugins
|
|
113
|
-
|
|
114
|
-
- **`createCursorInsidePlugin(nodeTypes)`** — Plugin that decorates math nodes containing the cursor
|
|
94
|
+
[API Reference](https://npmx.dev/package-docs/prosemirror-math)
|
|
115
95
|
|
|
116
96
|
## License
|
|
117
97
|
|
|
@@ -1,42 +1,99 @@
|
|
|
1
1
|
import { Plugin } from "prosemirror-state";
|
|
2
|
-
import { NodeView } from "prosemirror-view";
|
|
2
|
+
import { Decoration, NodeView } from "prosemirror-view";
|
|
3
3
|
import { EnterRule } from "prosemirror-enter-rules";
|
|
4
4
|
import { InputRule } from "prosemirror-inputrules";
|
|
5
5
|
import { Node, NodeSpec } from "prosemirror-model";
|
|
6
6
|
|
|
7
7
|
//#region src/cursor-inside-plugin.d.ts
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
|
|
10
|
+
* nodes when the text selection head is inside them. This is useful for styling
|
|
11
|
+
* math nodes differently while they are being edited.
|
|
12
|
+
*
|
|
13
|
+
* The plugin automatically detects nodes in the `math` group.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
declare function createCursorInsidePlugin(): Plugin;
|
|
9
18
|
//#endregion
|
|
10
19
|
//#region src/math-block-enter-rule.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* An {@link EnterRule} that converts a textblock node that only contains `$$` into a math
|
|
22
|
+
* block node when Enter is pressed.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
11
26
|
declare const mathBlockEnterRule: EnterRule;
|
|
12
27
|
//#endregion
|
|
13
28
|
//#region src/math-block-spec.d.ts
|
|
29
|
+
/**
|
|
30
|
+
* A {@link NodeSpec} for a block-level math node.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
14
34
|
declare const mathBlockSpec: NodeSpec;
|
|
15
35
|
//#endregion
|
|
16
36
|
//#region src/math-block-view.d.ts
|
|
17
37
|
/**
|
|
18
38
|
* The function to render a math block.
|
|
19
39
|
*
|
|
20
|
-
* @param
|
|
40
|
+
* @param text - The text of the math block. For example, a TeX expression.
|
|
21
41
|
* @param element - A `<div>` element to render the math block.
|
|
22
42
|
*/
|
|
23
|
-
type RenderMathBlock = (
|
|
24
|
-
|
|
43
|
+
type RenderMathBlock = (text: string, element: HTMLElement) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a {@link NodeView} for a block-level math node. The view will show a
|
|
46
|
+
* source editor or a rendered display area based on the text cursor position.
|
|
47
|
+
*
|
|
48
|
+
* @param renderMathBlock - A function that renders math text (e.g. TeX) into
|
|
49
|
+
* the display element. You can use libraries like
|
|
50
|
+
* [Temml](https://temml.org/) or [KaTeX](https://katex.org/).
|
|
51
|
+
* @param node - The ProseMirror node to render.
|
|
52
|
+
* @param decorations - The decorations applied to the node.
|
|
53
|
+
*
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
declare function createMathBlockView(renderMathBlock: RenderMathBlock, node: Node, decorations: readonly Decoration[]): NodeView;
|
|
25
57
|
//#endregion
|
|
26
58
|
//#region src/math-inline-input-rule.d.ts
|
|
59
|
+
/**
|
|
60
|
+
* Creates a ProseMirror {@link InputRule} that converts text wrapped in `$` or
|
|
61
|
+
* `$$` (e.g. `$x^2$`) into an inline math node.
|
|
62
|
+
*
|
|
63
|
+
* @param nodeType - The name of the inline math node type in your schema.
|
|
64
|
+
*
|
|
65
|
+
* @public
|
|
66
|
+
*/
|
|
27
67
|
declare function createMathInlineInputRule(nodeType: string): InputRule;
|
|
28
68
|
//#endregion
|
|
29
69
|
//#region src/math-inline-spec.d.ts
|
|
70
|
+
/**
|
|
71
|
+
* A {@link NodeSpec} for an inline math node.
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
74
|
+
*/
|
|
30
75
|
declare const mathInlineSpec: NodeSpec;
|
|
31
76
|
//#endregion
|
|
32
77
|
//#region src/math-inline-view.d.ts
|
|
33
78
|
/**
|
|
34
79
|
* The function to render a math inline.
|
|
35
80
|
*
|
|
36
|
-
* @param
|
|
81
|
+
* @param text - The text of the math inline. For example, a TeX expression.
|
|
37
82
|
* @param element - A `<span>` element to render the math inline.
|
|
38
83
|
*/
|
|
39
|
-
type RenderMathInline = (
|
|
40
|
-
|
|
84
|
+
type RenderMathInline = (text: string, element: HTMLElement) => void;
|
|
85
|
+
/**
|
|
86
|
+
* Creates a {@link NodeView} for an inline math node. The view will show a
|
|
87
|
+
* source editor or a rendered display area based on the text cursor position.
|
|
88
|
+
*
|
|
89
|
+
* @param renderMathInline - A function that renders math text (e.g. TeX) into
|
|
90
|
+
* the display element. You can use libraries like [Temml](https://temml.org/)
|
|
91
|
+
* or [KaTeX](https://katex.org/).
|
|
92
|
+
* @param node - The ProseMirror node to render.
|
|
93
|
+
* @param decorations - The decorations applied to the node.
|
|
94
|
+
*
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
declare function createMathInlineView(renderMathInline: RenderMathInline, node: Node, decorations: readonly Decoration[]): NodeView;
|
|
41
98
|
//#endregion
|
|
42
99
|
export { type RenderMathBlock, type RenderMathInline, createCursorInsidePlugin, createMathBlockView, createMathInlineInputRule, createMathInlineView, mathBlockEnterRule, mathBlockSpec, mathInlineSpec };
|
package/dist/prosemirror-math.js
CHANGED
|
@@ -5,23 +5,39 @@ import { supportsRegexLookbehind } from "@ocavue/utils";
|
|
|
5
5
|
import { InputRule } from "prosemirror-inputrules";
|
|
6
6
|
|
|
7
7
|
//#region src/cursor-inside-plugin.ts
|
|
8
|
-
|
|
8
|
+
const DECORATION_SPEC = "MATH_CURSOR_INSIDE";
|
|
9
|
+
function createCursorInsideDecoration(state) {
|
|
9
10
|
const { $head } = state.selection;
|
|
10
11
|
const node = $head.parent;
|
|
11
|
-
if (!
|
|
12
|
+
if (!node.type.isInGroup("math")) return;
|
|
12
13
|
const before = $head.before();
|
|
13
|
-
const deco = Decoration.node(before, before + node.nodeSize, { class: "
|
|
14
|
+
const deco = Decoration.node(before, before + node.nodeSize, { class: "prosemirror-math-head-inside" }, DECORATION_SPEC);
|
|
14
15
|
return DecorationSet.create(state.doc, [deco]);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function hasCursorInsideDecoration(decorations) {
|
|
21
|
+
return decorations.some((deco) => deco.spec === DECORATION_SPEC);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
|
|
25
|
+
* nodes when the text selection head is inside them. This is useful for styling
|
|
26
|
+
* math nodes differently while they are being edited.
|
|
27
|
+
*
|
|
28
|
+
* The plugin automatically detects nodes in the `math` group.
|
|
29
|
+
*
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
function createCursorInsidePlugin() {
|
|
33
|
+
const key = new PluginKey("prosemirror-math-cursor-inside");
|
|
18
34
|
return new Plugin({
|
|
19
35
|
key,
|
|
20
36
|
state: {
|
|
21
37
|
init() {},
|
|
22
38
|
apply(tr, oldValue, oldState, newState) {
|
|
23
|
-
if (oldState.selection.
|
|
24
|
-
return createCursorInsideDecoration(newState
|
|
39
|
+
if (oldState.selection.head === newState.selection.head && !tr.docChanged) return oldValue;
|
|
40
|
+
return createCursorInsideDecoration(newState);
|
|
25
41
|
}
|
|
26
42
|
},
|
|
27
43
|
props: { decorations(state) {
|
|
@@ -33,6 +49,12 @@ function createCursorInsidePlugin(nodeTypes) {
|
|
|
33
49
|
//#endregion
|
|
34
50
|
//#region src/math-block-enter-rule.ts
|
|
35
51
|
const MATH_BLOCK_ENTER_REGEXP = /^\$\$$/;
|
|
52
|
+
/**
|
|
53
|
+
* An {@link EnterRule} that converts a textblock node that only contains `$$` into a math
|
|
54
|
+
* block node when Enter is pressed.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
36
58
|
const mathBlockEnterRule = /* @__PURE__ */ createTextBlockEnterRule({
|
|
37
59
|
regex: MATH_BLOCK_ENTER_REGEXP,
|
|
38
60
|
type: "mathBlock"
|
|
@@ -40,19 +62,27 @@ const mathBlockEnterRule = /* @__PURE__ */ createTextBlockEnterRule({
|
|
|
40
62
|
|
|
41
63
|
//#endregion
|
|
42
64
|
//#region src/math-block-spec.ts
|
|
65
|
+
/**
|
|
66
|
+
* A {@link NodeSpec} for a block-level math node.
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
43
70
|
const mathBlockSpec = {
|
|
44
71
|
atom: false,
|
|
45
|
-
group: "block",
|
|
72
|
+
group: "block math",
|
|
46
73
|
content: "text*",
|
|
47
74
|
code: true,
|
|
48
75
|
toDOM() {
|
|
49
76
|
return [
|
|
50
77
|
"div",
|
|
51
|
-
{ class: "
|
|
78
|
+
{ class: "prosemirror-math-block" },
|
|
52
79
|
["pre", ["code", 0]]
|
|
53
80
|
];
|
|
54
81
|
},
|
|
55
|
-
parseDOM: [{
|
|
82
|
+
parseDOM: [{
|
|
83
|
+
tag: "div.prosemirror-math-block",
|
|
84
|
+
contentElement: "code"
|
|
85
|
+
}]
|
|
56
86
|
};
|
|
57
87
|
|
|
58
88
|
//#endregion
|
|
@@ -64,27 +94,76 @@ function createElement(tag, className, ...children) {
|
|
|
64
94
|
return element;
|
|
65
95
|
}
|
|
66
96
|
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/math-view-render.ts
|
|
99
|
+
function createMathViewRender(renderMath, source, display, inline) {
|
|
100
|
+
let prevNode;
|
|
101
|
+
let prevText;
|
|
102
|
+
let prevSelected;
|
|
103
|
+
function updateDisplay(node) {
|
|
104
|
+
if (node === prevNode) return;
|
|
105
|
+
prevNode = node;
|
|
106
|
+
const text = node.textContent;
|
|
107
|
+
if (text === prevText) return;
|
|
108
|
+
prevText = text;
|
|
109
|
+
renderMath(text, display);
|
|
110
|
+
}
|
|
111
|
+
function updateStyle(decorations) {
|
|
112
|
+
const selected = hasCursorInsideDecoration(decorations);
|
|
113
|
+
if (selected === prevSelected) return;
|
|
114
|
+
prevSelected = selected;
|
|
115
|
+
display.style.display = selected ? "none" : "";
|
|
116
|
+
if (!inline) source.style.display = selected ? "" : "none";
|
|
117
|
+
else Object.assign(source.style, selected ? visibleInlineSourceStyle : hiddenInlineSourceStyle);
|
|
118
|
+
}
|
|
119
|
+
return function updateMathView(node, decorations) {
|
|
120
|
+
updateDisplay(node);
|
|
121
|
+
updateStyle(decorations);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const hiddenInlineSourceStyle = {
|
|
125
|
+
display: "inline-flex",
|
|
126
|
+
opacity: "0",
|
|
127
|
+
pointerEvents: "none",
|
|
128
|
+
maxWidth: "0",
|
|
129
|
+
maxHeight: "0",
|
|
130
|
+
overflow: "hidden"
|
|
131
|
+
};
|
|
132
|
+
const visibleInlineSourceStyle = {
|
|
133
|
+
display: "inline-flex",
|
|
134
|
+
opacity: "1",
|
|
135
|
+
pointerEvents: "",
|
|
136
|
+
maxWidth: "",
|
|
137
|
+
maxHeight: "",
|
|
138
|
+
overflow: ""
|
|
139
|
+
};
|
|
140
|
+
|
|
67
141
|
//#endregion
|
|
68
142
|
//#region src/math-block-view.ts
|
|
69
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Creates a {@link NodeView} for a block-level math node. The view will show a
|
|
145
|
+
* source editor or a rendered display area based on the text cursor position.
|
|
146
|
+
*
|
|
147
|
+
* @param renderMathBlock - A function that renders math text (e.g. TeX) into
|
|
148
|
+
* the display element. You can use libraries like
|
|
149
|
+
* [Temml](https://temml.org/) or [KaTeX](https://katex.org/).
|
|
150
|
+
* @param node - The ProseMirror node to render.
|
|
151
|
+
* @param decorations - The decorations applied to the node.
|
|
152
|
+
*
|
|
153
|
+
* @public
|
|
154
|
+
*/
|
|
155
|
+
function createMathBlockView(renderMathBlock, node, decorations) {
|
|
70
156
|
const code = createElement("code");
|
|
71
|
-
const source = createElement("pre", "
|
|
72
|
-
const display = createElement("div", "
|
|
73
|
-
const dom = createElement("div", "
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const nodeText = node.textContent;
|
|
77
|
-
if (prevText !== nodeText) {
|
|
78
|
-
prevText = nodeText;
|
|
79
|
-
renderMathBlock(nodeText, display);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
render(node);
|
|
157
|
+
const source = createElement("pre", "prosemirror-math-source", code);
|
|
158
|
+
const display = createElement("div", "prosemirror-math-display");
|
|
159
|
+
const dom = createElement("div", "prosemirror-math-block", source, display);
|
|
160
|
+
const render = createMathViewRender(renderMathBlock, source, display, false);
|
|
161
|
+
render(node, decorations);
|
|
83
162
|
return {
|
|
84
163
|
dom,
|
|
85
164
|
contentDOM: code,
|
|
86
|
-
update: (node) => {
|
|
87
|
-
render(node);
|
|
165
|
+
update: (node, decorations) => {
|
|
166
|
+
render(node, decorations);
|
|
88
167
|
return true;
|
|
89
168
|
}
|
|
90
169
|
};
|
|
@@ -93,6 +172,14 @@ function createMathBlockView(node, renderMathBlock) {
|
|
|
93
172
|
//#endregion
|
|
94
173
|
//#region src/math-inline-input-rule.ts
|
|
95
174
|
const MATH_INPUT_REGEXP = (supportsRegexLookbehind() ? "(?<!\\$)" : "") + "(\\$\\$?)([^\\s$](?:[^$]*[^\\s$])?)\\1$";
|
|
175
|
+
/**
|
|
176
|
+
* Creates a ProseMirror {@link InputRule} that converts text wrapped in `$` or
|
|
177
|
+
* `$$` (e.g. `$x^2$`) into an inline math node.
|
|
178
|
+
*
|
|
179
|
+
* @param nodeType - The name of the inline math node type in your schema.
|
|
180
|
+
*
|
|
181
|
+
* @public
|
|
182
|
+
*/
|
|
96
183
|
function createMathInlineInputRule(nodeType) {
|
|
97
184
|
return new InputRule(new RegExp(MATH_INPUT_REGEXP), (state, match, start, end) => {
|
|
98
185
|
const { tr, schema } = state;
|
|
@@ -108,43 +195,57 @@ function createMathInlineInputRule(nodeType) {
|
|
|
108
195
|
|
|
109
196
|
//#endregion
|
|
110
197
|
//#region src/math-inline-spec.ts
|
|
198
|
+
/**
|
|
199
|
+
* A {@link NodeSpec} for an inline math node.
|
|
200
|
+
*
|
|
201
|
+
* @public
|
|
202
|
+
*/
|
|
111
203
|
const mathInlineSpec = {
|
|
112
204
|
atom: false,
|
|
113
205
|
inline: true,
|
|
114
|
-
group: "inline",
|
|
206
|
+
group: "inline math",
|
|
115
207
|
content: "text*",
|
|
116
208
|
selectable: false,
|
|
117
209
|
code: true,
|
|
118
210
|
toDOM() {
|
|
119
211
|
return [
|
|
120
212
|
"span",
|
|
121
|
-
{ class: "
|
|
213
|
+
{ class: "prosemirror-math-inline" },
|
|
122
214
|
["code", 0]
|
|
123
215
|
];
|
|
124
216
|
},
|
|
125
|
-
parseDOM: [{
|
|
217
|
+
parseDOM: [{
|
|
218
|
+
tag: "span.prosemirror-math-inline",
|
|
219
|
+
contentElement: "code"
|
|
220
|
+
}]
|
|
126
221
|
};
|
|
127
222
|
|
|
128
223
|
//#endregion
|
|
129
224
|
//#region src/math-inline-view.ts
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
225
|
+
/**
|
|
226
|
+
* Creates a {@link NodeView} for an inline math node. The view will show a
|
|
227
|
+
* source editor or a rendered display area based on the text cursor position.
|
|
228
|
+
*
|
|
229
|
+
* @param renderMathInline - A function that renders math text (e.g. TeX) into
|
|
230
|
+
* the display element. You can use libraries like [Temml](https://temml.org/)
|
|
231
|
+
* or [KaTeX](https://katex.org/).
|
|
232
|
+
* @param node - The ProseMirror node to render.
|
|
233
|
+
* @param decorations - The decorations applied to the node.
|
|
234
|
+
*
|
|
235
|
+
* @public
|
|
236
|
+
*/
|
|
237
|
+
function createMathInlineView(renderMathInline, node, decorations) {
|
|
238
|
+
const code = createElement("code");
|
|
239
|
+
const source = createElement("span", "prosemirror-math-source", code);
|
|
240
|
+
const display = createElement("span", "prosemirror-math-display");
|
|
241
|
+
const dom = createElement("span", "prosemirror-math-inline", source, display);
|
|
242
|
+
const render = createMathViewRender(renderMathInline, source, display, true);
|
|
243
|
+
render(node, decorations);
|
|
143
244
|
return {
|
|
144
245
|
dom,
|
|
145
|
-
contentDOM:
|
|
146
|
-
update: (node) => {
|
|
147
|
-
render(node);
|
|
246
|
+
contentDOM: code,
|
|
247
|
+
update: (node, decorations) => {
|
|
248
|
+
render(node, decorations);
|
|
148
249
|
return true;
|
|
149
250
|
}
|
|
150
251
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prosemirror-math",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Math extensions for ProseMirror",
|
|
7
7
|
"author": {
|
|
@@ -37,22 +37,24 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@ocavue/utils": "^1.5.0",
|
|
40
|
-
"prosemirror-inputrules": "^1.
|
|
40
|
+
"prosemirror-inputrules": "^1.5.1",
|
|
41
41
|
"prosemirror-model": "^1.25.4",
|
|
42
42
|
"prosemirror-state": "^1.4.4",
|
|
43
43
|
"prosemirror-view": "^1.41.6",
|
|
44
|
-
"prosemirror-enter-rules": "^0.1.
|
|
44
|
+
"prosemirror-enter-rules": "^0.1.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
+
"@mathjax/src": "^4.1.0",
|
|
48
|
+
"@ocavue/tsconfig": "^0.6.3",
|
|
47
49
|
"diffable-html-snapshot": "^0.2.0",
|
|
50
|
+
"katex": "^0.16.28",
|
|
48
51
|
"temml": "^0.13.1",
|
|
49
52
|
"tsdown": "^0.20.3",
|
|
50
53
|
"typescript": "~5.9.3",
|
|
51
54
|
"vitest": "^4.0.18",
|
|
52
55
|
"vitest-browser-commands": "^0.2.0",
|
|
53
56
|
"@prosekit/config-vitest": "0.0.0",
|
|
54
|
-
"@prosekit/core": "^0.10.0"
|
|
55
|
-
"@prosekit/pm": "^0.1.15"
|
|
57
|
+
"@prosekit/core": "^0.10.0"
|
|
56
58
|
},
|
|
57
59
|
"publishConfig": {
|
|
58
60
|
"dev": {}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { TextSelection } from '
|
|
1
|
+
import { TextSelection } from 'prosemirror-state'
|
|
2
2
|
import { describe, expect, it } from 'vitest'
|
|
3
3
|
|
|
4
|
-
import { setupTest } from './testing'
|
|
4
|
+
import { setupTest } from './testing.ts'
|
|
5
5
|
|
|
6
6
|
describe('cursorInsidePlugin', () => {
|
|
7
7
|
it('applies decoration when cursor is inside mathBlock', () => {
|
|
@@ -15,8 +15,8 @@ describe('cursorInsidePlugin', () => {
|
|
|
15
15
|
)
|
|
16
16
|
editor.view.dispatch(tr)
|
|
17
17
|
|
|
18
|
-
const mathBlock = editor.view.dom.querySelector('.
|
|
19
|
-
expect(mathBlock?.classList.contains('
|
|
18
|
+
const mathBlock = editor.view.dom.querySelector('.prosemirror-math-block')
|
|
19
|
+
expect(mathBlock?.classList.contains('prosemirror-math-head-inside')).toBe(true)
|
|
20
20
|
})
|
|
21
21
|
|
|
22
22
|
it('applies decoration when cursor is inside mathInline', () => {
|
|
@@ -30,8 +30,8 @@ describe('cursorInsidePlugin', () => {
|
|
|
30
30
|
)
|
|
31
31
|
editor.view.dispatch(tr)
|
|
32
32
|
|
|
33
|
-
const mathInline = editor.view.dom.querySelector('.
|
|
34
|
-
expect(mathInline?.classList.contains('
|
|
33
|
+
const mathInline = editor.view.dom.querySelector('.prosemirror-math-inline')
|
|
34
|
+
expect(mathInline?.classList.contains('prosemirror-math-head-inside')).toBe(true)
|
|
35
35
|
})
|
|
36
36
|
|
|
37
37
|
it('does not apply decoration when cursor is outside math nodes', () => {
|
|
@@ -45,7 +45,7 @@ describe('cursorInsidePlugin', () => {
|
|
|
45
45
|
)
|
|
46
46
|
editor.view.dispatch(tr)
|
|
47
47
|
|
|
48
|
-
const mathBlock = editor.view.dom.querySelector('.
|
|
49
|
-
expect(mathBlock?.classList.contains('
|
|
48
|
+
const mathBlock = editor.view.dom.querySelector('.prosemirror-math-block')
|
|
49
|
+
expect(mathBlock?.classList.contains('prosemirror-math-head-inside')).toBe(false)
|
|
50
50
|
})
|
|
51
51
|
})
|
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import { Plugin, PluginKey, type EditorState } from 'prosemirror-state'
|
|
2
2
|
import { Decoration, DecorationSet } from 'prosemirror-view'
|
|
3
3
|
|
|
4
|
+
const DECORATION_SPEC = 'MATH_CURSOR_INSIDE'
|
|
5
|
+
|
|
4
6
|
function createCursorInsideDecoration(
|
|
5
7
|
state: EditorState,
|
|
6
|
-
nodeTypes: string[],
|
|
7
8
|
): DecorationSet | undefined {
|
|
8
9
|
const { $head } = state.selection
|
|
9
10
|
const node = $head.parent
|
|
10
11
|
|
|
11
|
-
if (!
|
|
12
|
+
if (!node.type.isInGroup('math')) return
|
|
13
|
+
|
|
12
14
|
const before = $head.before()
|
|
13
|
-
const deco = Decoration.node(
|
|
15
|
+
const deco = Decoration.node(
|
|
16
|
+
before,
|
|
17
|
+
before + node.nodeSize,
|
|
18
|
+
{ class: 'prosemirror-math-head-inside' },
|
|
19
|
+
DECORATION_SPEC,
|
|
20
|
+
)
|
|
14
21
|
return DecorationSet.create(state.doc, [deco])
|
|
15
22
|
}
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function hasCursorInsideDecoration(decorations: readonly Decoration[]): boolean {
|
|
28
|
+
return decorations.some(deco => deco.spec === DECORATION_SPEC)
|
|
29
|
+
}
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
type PluginState = DecorationSet | undefined
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Creates a plugin that adds a `prosemirror-math-head-inside` CSS class to math
|
|
35
|
+
* nodes when the text selection head is inside them. This is useful for styling
|
|
36
|
+
* math nodes differently while they are being edited.
|
|
37
|
+
*
|
|
38
|
+
* The plugin automatically detects nodes in the `math` group.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export function createCursorInsidePlugin(): Plugin {
|
|
43
|
+
const key = new PluginKey<PluginState>('prosemirror-math-cursor-inside')
|
|
22
44
|
return new Plugin<PluginState>({
|
|
23
45
|
key,
|
|
24
46
|
state: {
|
|
@@ -26,8 +48,13 @@ export function createCursorInsidePlugin(nodeTypes: string[]): Plugin {
|
|
|
26
48
|
return undefined
|
|
27
49
|
},
|
|
28
50
|
apply(tr, oldValue, oldState, newState): PluginState {
|
|
29
|
-
if (
|
|
30
|
-
|
|
51
|
+
if (
|
|
52
|
+
oldState.selection.head === newState.selection.head
|
|
53
|
+
&& !tr.docChanged
|
|
54
|
+
) {
|
|
55
|
+
return oldValue
|
|
56
|
+
}
|
|
57
|
+
return createCursorInsideDecoration(newState)
|
|
31
58
|
},
|
|
32
59
|
},
|
|
33
60
|
props: {
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { createCursorInsidePlugin } from './cursor-inside-plugin'
|
|
2
|
-
export { mathBlockEnterRule } from './math-block-enter-rule'
|
|
3
|
-
export { mathBlockSpec } from './math-block-spec'
|
|
4
|
-
export { createMathBlockView, type RenderMathBlock } from './math-block-view'
|
|
5
|
-
export { createMathInlineInputRule } from './math-inline-input-rule'
|
|
6
|
-
export { mathInlineSpec } from './math-inline-spec'
|
|
7
|
-
export { createMathInlineView, type RenderMathInline } from './math-inline-view'
|
|
1
|
+
export { createCursorInsidePlugin } from './cursor-inside-plugin.ts'
|
|
2
|
+
export { mathBlockEnterRule } from './math-block-enter-rule.ts'
|
|
3
|
+
export { mathBlockSpec } from './math-block-spec.ts'
|
|
4
|
+
export { createMathBlockView, type RenderMathBlock } from './math-block-view.ts'
|
|
5
|
+
export { createMathInlineInputRule } from './math-inline-input-rule.ts'
|
|
6
|
+
export { mathInlineSpec } from './math-inline-spec.ts'
|
|
7
|
+
export { createMathInlineView, type RenderMathInline } from './math-inline-view.ts'
|
package/src/katex.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import katex from 'katex'
|
|
2
|
+
|
|
3
|
+
export function renderKaTeXMathBlock(text: string, element: HTMLElement) {
|
|
4
|
+
katex.render(text, element, { displayMode: true, throwOnError: false })
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function renderKaTeXMathInline(text: string, element: HTMLElement) {
|
|
8
|
+
katex.render(text, element, { displayMode: false, throwOnError: false })
|
|
9
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
2
|
import { userEvent } from 'vitest/browser'
|
|
3
3
|
|
|
4
|
-
import { MATH_BLOCK_ENTER_REGEXP } from './math-block-enter-rule'
|
|
5
|
-
import { setupTest } from './testing'
|
|
4
|
+
import { MATH_BLOCK_ENTER_REGEXP } from './math-block-enter-rule.ts'
|
|
5
|
+
import { setupTest } from './testing.ts'
|
|
6
6
|
|
|
7
7
|
describe('MATH_BLOCK_ENTER_REGEXP', () => {
|
|
8
8
|
const cases: Array<[input: string, matched: boolean]> = [
|