@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/content-writer",
3
- "version": "0.3.1",
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.0"
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}`
@@ -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
+ })