@uniweb/content-writer 0.3.1 → 0.3.2
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/nodes.js +17 -1
- package/tests/roundtrip.test.js +35 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/content-writer",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "ProseMirror document structure to Markdown converter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"vitest": "^4.1.7",
|
|
23
|
-
"@uniweb/content-reader": "1.2.
|
|
23
|
+
"@uniweb/content-reader": "1.2.1"
|
|
24
24
|
},
|
|
25
25
|
"directories": {
|
|
26
26
|
"test": "tests"
|
package/src/nodes.js
CHANGED
|
@@ -148,7 +148,7 @@ export function serializeInsetBlock(node) {
|
|
|
148
148
|
* @returns {string} Fenced concept block
|
|
149
149
|
*/
|
|
150
150
|
export function serializeConceptBlock(node) {
|
|
151
|
-
const { tag } = node.attrs || {}
|
|
151
|
+
const { tag, syntax } = node.attrs || {}
|
|
152
152
|
if (!tag) return ''
|
|
153
153
|
|
|
154
154
|
const { serializeNode } = await_serializer()
|
|
@@ -157,6 +157,22 @@ export function serializeConceptBlock(node) {
|
|
|
157
157
|
.filter(Boolean)
|
|
158
158
|
.join('\n\n')
|
|
159
159
|
|
|
160
|
+
// Write back the spelling the author wrote. One node, two surfaces — a
|
|
161
|
+
// ```md:<tag> fence and GitHub's `> [!WARNING]` — and `syntax` is how the
|
|
162
|
+
// reader records which. Normalizing instead would rewrite someone's file on
|
|
163
|
+
// the next editor sync, which is exactly what `dataBlock.language` was added
|
|
164
|
+
// to stop when a ```yaml block came back as ```json.
|
|
165
|
+
if (syntax === 'gfm') {
|
|
166
|
+
// Every line of a blockquote carries its own marker, including the blank
|
|
167
|
+
// ones between blocks — a bare `>` rather than `> ` so no trailing space
|
|
168
|
+
// is written into the author's file.
|
|
169
|
+
const quoted = body
|
|
170
|
+
.split('\n')
|
|
171
|
+
.map(line => (line ? `> ${line}` : '>'))
|
|
172
|
+
.join('\n')
|
|
173
|
+
return `> [!${tag.toUpperCase()}]\n${quoted}`
|
|
174
|
+
}
|
|
175
|
+
|
|
160
176
|
const fence = '`'.repeat(fenceWidthFor(body))
|
|
161
177
|
|
|
162
178
|
return `${fence}md:${tag}\n${body}\n${fence}`
|
package/tests/roundtrip.test.js
CHANGED
|
@@ -457,3 +457,38 @@ describe('Round-trip: Concept blocks', () => {
|
|
|
457
457
|
expect(proseMirrorToMarkdown(doc).trim()).not.toBe('')
|
|
458
458
|
})
|
|
459
459
|
})
|
|
460
|
+
|
|
461
|
+
describe('Round-trip: GitHub alerts', () => {
|
|
462
|
+
testRoundTrip('> [!WARNING]\n> Back up your database first.', 'a GitHub alert')
|
|
463
|
+
testRoundTrip('> [!NOTE]\n> Something **worth** knowing, with a [link](/x).', 'marks inside one')
|
|
464
|
+
testRoundTrip('> [!TIP]\n> One thing.\n>\n> And another.', 'multiple blocks inside one')
|
|
465
|
+
testRoundTrip('> A plain quotation, **unchanged**.', 'an ordinary blockquote still round-trips')
|
|
466
|
+
|
|
467
|
+
test('the spelling is PRESERVED, not normalized', () => {
|
|
468
|
+
// The whole reason `syntax` exists. Normalizing would rewrite an author's
|
|
469
|
+
// file on the next editor sync — the defect dataBlock.language was added
|
|
470
|
+
// to stop, where a ```yaml block came back as ```json.
|
|
471
|
+
const gfm = '> [!WARNING]\n> Back up first.'
|
|
472
|
+
const fence = '```md:warning\nBack up first.\n```'
|
|
473
|
+
|
|
474
|
+
expect(proseMirrorToMarkdown(markdownToProseMirror(gfm)).trim()).toBe(gfm)
|
|
475
|
+
expect(proseMirrorToMarkdown(markdownToProseMirror(fence)).trim()).toBe(fence)
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
test('both spellings carry the same content', () => {
|
|
479
|
+
const gfm = markdownToProseMirror('> [!WARNING]\n> Back up **first**.')
|
|
480
|
+
const fence = markdownToProseMirror('```md:warning\nBack up **first**.\n```')
|
|
481
|
+
expect(gfm.content[0].content).toEqual(fence.content[0].content)
|
|
482
|
+
expect(gfm.content[0].attrs.tag).toBe(fence.content[0].attrs.tag)
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
test('is a FIXED POINT, asserted on the body', () => {
|
|
486
|
+
const original = '> [!CAUTION]\n> Mind the gap.'
|
|
487
|
+
const pass1 = proseMirrorToMarkdown(markdownToProseMirror(original))
|
|
488
|
+
const pass2 = proseMirrorToMarkdown(markdownToProseMirror(pass1))
|
|
489
|
+
// A dropped node is trivially a fixed point — pin the content first.
|
|
490
|
+
expect(pass1).toContain('Mind the gap.')
|
|
491
|
+
expect(pass1).toContain('[!CAUTION]')
|
|
492
|
+
expect(pass2).toBe(pass1)
|
|
493
|
+
})
|
|
494
|
+
})
|