@uniweb/content-writer 0.2.2 → 0.2.4
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/package.json +2 -2
- package/src/marks.js +4 -0
- package/src/nodes.js +24 -0
- package/src/serializer.js +2 -0
- package/tests/math.test.js +72 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/content-writer",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "ProseMirror document structure to Markdown converter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"jest": "^29.7.0",
|
|
22
|
-
"@uniweb/content-reader": "1.1.
|
|
22
|
+
"@uniweb/content-reader": "1.1.5"
|
|
23
23
|
},
|
|
24
24
|
"jest": {
|
|
25
25
|
"testEnvironment": "node",
|
package/src/marks.js
CHANGED
|
@@ -222,6 +222,10 @@ function serializePlainNode(node) {
|
|
|
222
222
|
if (node.type === 'image') {
|
|
223
223
|
return serializeInlineImage(node)
|
|
224
224
|
}
|
|
225
|
+
if (node.type === 'math_inline') {
|
|
226
|
+
const latex = node.attrs?.latex || ''
|
|
227
|
+
return node.attrs?.display ? '$$' + latex + '$$' : '$' + latex + '$'
|
|
228
|
+
}
|
|
225
229
|
if (node.type !== 'text') return ''
|
|
226
230
|
return serializeTextWithMarks(node.text, node.marks || [])
|
|
227
231
|
}
|
package/src/nodes.js
CHANGED
|
@@ -60,6 +60,30 @@ export function serializeCodeBlock(node) {
|
|
|
60
60
|
return `\`\`\`${lang}${tag}\n${text}\n\`\`\``
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Serialize a display-math node back to markdown.
|
|
65
|
+
*
|
|
66
|
+
* Labeled math (with an id) always uses the fenced form `` ```math:<id> ``
|
|
67
|
+
* because `$$...$$` has no label syntax.
|
|
68
|
+
*
|
|
69
|
+
* Single-line LaTeX uses `$$...$$` (compact, ecosystem-standard).
|
|
70
|
+
* Multi-line LaTeX uses fenced ```math (friendlier diffs, no ambiguity
|
|
71
|
+
* with paragraphs that contain a stray `$$`).
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} node - math_display node with attrs.latex and optional attrs.id
|
|
74
|
+
* @returns {string} Markdown math block
|
|
75
|
+
*/
|
|
76
|
+
export function serializeMathDisplay(node) {
|
|
77
|
+
const latex = node.attrs?.latex || ''
|
|
78
|
+
const id = node.attrs?.id
|
|
79
|
+
if (id) {
|
|
80
|
+
return '```math:' + id + '\n' + latex + '\n```'
|
|
81
|
+
}
|
|
82
|
+
return latex.includes('\n')
|
|
83
|
+
? '```math\n' + latex + '\n```'
|
|
84
|
+
: '$$' + latex + '$$'
|
|
85
|
+
}
|
|
86
|
+
|
|
63
87
|
/**
|
|
64
88
|
* Serialize a data block node.
|
|
65
89
|
* @param {Object} node - Data block node with attrs.tag and attrs.data
|
package/src/serializer.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
serializeOrderedList,
|
|
17
17
|
serializeDivider,
|
|
18
18
|
serializeTable,
|
|
19
|
+
serializeMathDisplay,
|
|
19
20
|
setSerializer,
|
|
20
21
|
} from './nodes.js'
|
|
21
22
|
|
|
@@ -31,6 +32,7 @@ const NODE_SERIALIZERS = {
|
|
|
31
32
|
orderedList: serializeOrderedList,
|
|
32
33
|
divider: serializeDivider,
|
|
33
34
|
table: serializeTable,
|
|
35
|
+
math_display: serializeMathDisplay,
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Math round-trip tests: markdown → content-reader → content-writer → content-reader.
|
|
3
|
+
*
|
|
4
|
+
* The identity we check is at the ProseMirror-doc level, not the markdown
|
|
5
|
+
* string — content-writer is free to normalise whitespace or attribute
|
|
6
|
+
* order as long as the re-parsed doc matches the original.
|
|
7
|
+
*
|
|
8
|
+
* The `mathml` attribute is expected to drift across the round trip only
|
|
9
|
+
* if Temml output is non-deterministic; in practice it is stable, so we
|
|
10
|
+
* assert both `latex` and `mathml` survive.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { markdownToProseMirror } from '@uniweb/content-reader'
|
|
14
|
+
import { proseMirrorToMarkdown } from '../src/index.js'
|
|
15
|
+
|
|
16
|
+
function roundTrip(markdown) {
|
|
17
|
+
const parsed = markdownToProseMirror(markdown)
|
|
18
|
+
const serialized = proseMirrorToMarkdown(parsed)
|
|
19
|
+
const reparsed = markdownToProseMirror(serialized)
|
|
20
|
+
return { parsed, serialized, reparsed }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('Math round-trip', () => {
|
|
24
|
+
test('inline math: $x^2$', () => {
|
|
25
|
+
const { parsed, reparsed } = roundTrip('Let $x^2$ be a square.')
|
|
26
|
+
expect(reparsed).toEqual(parsed)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('block display math: $$...$$', () => {
|
|
30
|
+
const { parsed, reparsed } = roundTrip('$$\\sum_{i=1}^n i$$')
|
|
31
|
+
expect(reparsed).toEqual(parsed)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('multi-line block math uses fenced ```math form', () => {
|
|
35
|
+
const input = '```math\n\\int_0^\\infty\ne^{-x}\\,dx\n```'
|
|
36
|
+
const { parsed, serialized, reparsed } = roundTrip(input)
|
|
37
|
+
expect(serialized.startsWith('```math')).toBe(true)
|
|
38
|
+
expect(reparsed).toEqual(parsed)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('mixed paragraph: text, inline math, more text', () => {
|
|
42
|
+
const { parsed, reparsed } = roundTrip(
|
|
43
|
+
'Given $a + b$ we derive $c$.',
|
|
44
|
+
)
|
|
45
|
+
expect(reparsed).toEqual(parsed)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('inline math_display via $$...$$ mid-paragraph', () => {
|
|
49
|
+
const { parsed, reparsed } = roundTrip('text $$x^2$$ more')
|
|
50
|
+
expect(reparsed).toEqual(parsed)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('inline math serializes with single $ delimiters', () => {
|
|
54
|
+
const { serialized } = roundTrip('value is $\\pi$.')
|
|
55
|
+
expect(serialized).toContain('$\\pi$')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('block math serializes with $$ delimiters on single line', () => {
|
|
59
|
+
const { serialized } = roundTrip('$$E = mc^2$$')
|
|
60
|
+
expect(serialized).toContain('$$E = mc^2$$')
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('labeled fenced math preserves id through roundtrip', () => {
|
|
64
|
+
const input = '```math:einstein\nE = mc^2\n```'
|
|
65
|
+
const { parsed, serialized, reparsed } = roundTrip(input)
|
|
66
|
+
expect(serialized).toContain('```math:einstein')
|
|
67
|
+
expect(reparsed).toEqual(parsed)
|
|
68
|
+
// The id is carried as an attribute on math_display.
|
|
69
|
+
const node = parsed.content.find((n) => n.type === 'math_display')
|
|
70
|
+
expect(node?.attrs?.id).toBe('einstein')
|
|
71
|
+
})
|
|
72
|
+
})
|