From 122168499ed646b7af48a3e5112fe38adf01d70c Mon Sep 17 00:00:00 2001 From: BacLuc Date: Sat, 13 Jun 2026 08:45:01 +0000 Subject: [PATCH] fix: bullet/number lost when list item starts with linebreak in print (#3986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PDF RichText component prepended the bullet (•) or number by directly modifying node.children[0].children[0].content. When a
appeared as the first child of a
  • (e.g.

  • text
  • ), that
    tag has no content property so the prefix was silently lost. Replace the hardcoded path with a recursive prependTextContent() helper that traverses the child tree depth-first to find the first actual text node, skipping over
    and other non-text tags. Update snapshots for the empty-list case which now correctly shows the bullet. Fixes https://github.com/ecamp/ecamp3/issues/3986 Co-Authored-By: Claude Sonnet 4.6 --- pdf/src/campPrint/RichText.vue | 28 +++++++++++++++---- ...al_text_with_empty_lists_A3.spec.json.snap | 16 ++++++++++- ...al_text_with_empty_lists_A4.spec.json.snap | 16 ++++++++++- ...al_text_with_empty_lists_A5.spec.json.snap | 16 ++++++++++- 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/pdf/src/campPrint/RichText.vue b/pdf/src/campPrint/RichText.vue index b38b0795cd..e2ff1d4947 100644 --- a/pdf/src/campPrint/RichText.vue +++ b/pdf/src/campPrint/RichText.vue @@ -21,6 +21,19 @@ function visitChildren(children, parent) { : [visit({ type: 'text', content: ' ' }, parent)] } +function prependTextContent(node, prefix) { + if (node.type === 'text') { + node.content = prefix + (node.content || '') + return true + } + if (node.children?.length) { + for (const child of node.children) { + if (prependTextContent(child, prefix)) return true + } + } + return false +} + const rules = [ { shouldProcessNode: (node) => node.type === 'text', @@ -78,16 +91,19 @@ const rules = [ if (!node.children.length) { node.children.push({ ...emptyChild }) } - if (!node.children[0].children.length) { - node.children[0].children.push({ ...emptyChild, content: '' }) - } + + let prefix = '' if (parent.name === 'ul') { - node.children[0].children[0].content = '• ' + node.children[0].children[0].content + prefix = '• ' } else if (parent.name === 'ol') { const number = calculateListNumber(node, parent) - node.children[0].children[0].content = - `${number}. ` + node.children[0].children[0].content + prefix = `${number}. ` + } + + if (prefix) { + prependTextContent(node, prefix) } + return h( 'View', { style: { marginLeft: '4pt' } }, diff --git a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A3.spec.json.snap b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A3.spec.json.snap index 2ffab8ec37..eb75792ddb 100644 --- a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A3.spec.json.snap +++ b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A3.spec.json.snap @@ -421,7 +421,21 @@ }, { "box": {}, - "children": [], + "children": [ + { + "box": {}, + "children": [ + { + "type": "TEXT_INSTANCE", + "value": "• ", + }, + ], + "parent": [Circular], + "props": {}, + "style": {}, + "type": "TEXT", + }, + ], "parent": [Circular], "props": { "style": { diff --git a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A4.spec.json.snap b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A4.spec.json.snap index 5985844080..c8ac3966e2 100644 --- a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A4.spec.json.snap +++ b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A4.spec.json.snap @@ -421,7 +421,21 @@ }, { "box": {}, - "children": [], + "children": [ + { + "box": {}, + "children": [ + { + "type": "TEXT_INSTANCE", + "value": "• ", + }, + ], + "parent": [Circular], + "props": {}, + "style": {}, + "type": "TEXT", + }, + ], "parent": [Circular], "props": { "style": { diff --git a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A5.spec.json.snap b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A5.spec.json.snap index 1c79cfaca3..9467ce6ff9 100644 --- a/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A5.spec.json.snap +++ b/pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists_A5.spec.json.snap @@ -421,7 +421,21 @@ }, { "box": {}, - "children": [], + "children": [ + { + "box": {}, + "children": [ + { + "type": "TEXT_INSTANCE", + "value": "• ", + }, + ], + "parent": [Circular], + "props": {}, + "style": {}, + "type": "TEXT", + }, + ], "parent": [Circular], "props": { "style": {