|
43 | 43 | {{- /* Process all headings */ -}} |
44 | 44 | {{- $s := newScratch -}} |
45 | 45 | {{- $s.Set "keys" slice -}} |
46 | | -{{- $s.Set "titles" slice -}} |
| 46 | +{{- $s.Set "markers" slice -}} |
| 47 | +{{- $s.Set "endTags" slice -}} |
47 | 48 | {{- $s.Set "ids" slice -}} |
48 | 49 |
|
49 | 50 | {{- $headingData := partial "utils/headings.html" $page -}} |
|
53 | 54 | {{- end -}} |
54 | 55 |
|
55 | 56 | {{- $headingKeys := $s.Get "keys" -}} |
56 | | -{{- $headingTitles := $s.Get "titles" -}} |
| 57 | +{{- $headingMarkers := $s.Get "markers" -}} |
| 58 | +{{- $headingEndTags := $s.Get "endTags" -}} |
57 | 59 | {{- $headingIDs := $s.Get "ids" -}} |
58 | 60 |
|
59 | 61 | {{- $content := $page.Content | htmlUnescape -}} |
|
65 | 67 | {{ if eq $len 0 }} |
66 | 68 | {{ $data = $data | merge (dict "" ($page.Plain | htmlUnescape | strings.TrimSpace)) }} |
67 | 69 | {{ else }} |
68 | | - {{/* Split the raw content from bottom to top */}} |
| 70 | + {{/* Split the rendered content from bottom to top using stable heading identifiers. */}} |
69 | 71 | {{ range seq $len }} |
70 | 72 | {{ $i := sub $len . }} |
71 | 73 | {{ $headingKey := index $headingKeys $i }} |
72 | | - {{ $headingTitle := index $headingTitles $i }} |
| 74 | + {{ $headingMarker := index $headingMarkers $i }} |
| 75 | + {{ $headingEndTag := index $headingEndTags $i }} |
73 | 76 | {{ $headingID := index $headingIDs $i }} |
74 | 77 |
|
75 | | - {{ if eq $i 0 }} |
76 | | - {{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | strings.TrimSpace)) }} |
77 | | - {{ else }} |
78 | | - {{ $splitMarker := $headingTitle }} |
79 | | - {{ $usedIDMarker := false }} |
80 | | - {{ $parts := split $content (printf "%s" $headingTitle) }} |
81 | | - |
82 | | - {{ if and (eq (len $parts) 1) $headingID }} |
83 | | - {{ $splitMarker = printf "id=\"%s\"" $headingID }} |
84 | | - {{ $usedIDMarker = true }} |
85 | | - {{ $parts = split $content $splitMarker }} |
86 | | - {{ end }} |
| 78 | + {{ with $headingMarker }} |
| 79 | + {{ $headingParts := split $content . }} |
| 80 | + |
| 81 | + {{ if gt (len $headingParts) 1 }} |
| 82 | + {{ $beforeHeading := index $headingParts 0 }} |
| 83 | + {{ $headingAndContent := delimit (after 1 $headingParts) . }} |
| 84 | + {{ $parts := split $headingAndContent $headingEndTag }} |
| 85 | + |
| 86 | + {{ if gt (len $parts) 1 }} |
| 87 | + {{ $fragment := delimit (after 1 $parts) $headingEndTag }} |
| 88 | + {{ $data = $data | merge (dict $headingKey ($fragment | plainify | htmlUnescape | strings.TrimSpace)) }} |
| 89 | + {{ $content = $beforeHeading }} |
| 90 | + {{ else }} |
| 91 | + {{/* Preserve the remaining content when a custom render hook has malformed output. */}} |
| 92 | + {{ $data = $data | merge (dict $headingKey "") }} |
| 93 | + {{ end }} |
| 94 | + {{ else if $headingID }} |
| 95 | + {{/* Fallback for external renderers (e.g. AsciiDoc) where id is on the heading element itself */}} |
| 96 | + {{ $idMarker := printf "id=\"%s\"" $headingID }} |
| 97 | + {{ $idParts := split $content $idMarker }} |
87 | 98 |
|
88 | | - {{ if gt (len $parts) 1 }} |
89 | | - {{ $lastPart := index $parts (sub (len $parts) 1) }} |
90 | | - {{ if $usedIDMarker }} |
| 99 | + {{ if gt (len $idParts) 1 }} |
| 100 | + {{ $lastPart := index $idParts (sub (len $idParts) 1) }} |
91 | 101 | {{ $lastPart = replaceRE `^[^>]*>` "" $lastPart }} |
92 | | - {{ end }} |
93 | 102 |
|
94 | | - {{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | strings.TrimSpace)) }} |
95 | | - {{ $content = strings.TrimSuffix $lastPart $content }} |
96 | | - {{ $content = strings.TrimSuffix (printf "%s" $splitMarker) $content }} |
| 103 | + {{ $data = $data | merge (dict $headingKey ($lastPart | plainify | htmlUnescape | strings.TrimSpace)) }} |
| 104 | + {{ $content = strings.TrimSuffix $lastPart $content }} |
| 105 | + {{ $content = strings.TrimSuffix $idMarker $content }} |
| 106 | + {{/* Strip the partial opening heading tag from the tail of the remaining content */}} |
| 107 | + {{ $content = replaceRE `<[^>]*$` "" $content }} |
| 108 | + {{ else }} |
| 109 | + {{/* Fail open so one unmatched heading cannot swallow fragments above it. */}} |
| 110 | + {{ $data = $data | merge (dict $headingKey "") }} |
| 111 | + {{ end }} |
97 | 112 | {{ else }} |
98 | | - {{ $data = $data | merge (dict $headingKey ($content | plainify | htmlUnescape | strings.TrimSpace)) }} |
| 113 | + {{/* Fail open so one unmatched heading cannot swallow fragments above it. */}} |
| 114 | + {{ $data = $data | merge (dict $headingKey "") }} |
99 | 115 | {{ end }} |
100 | 116 | {{ end }} |
101 | 117 | {{ end }} |
| 118 | + |
| 119 | + {{ $preamble := $content | plainify | htmlUnescape | strings.TrimSpace }} |
| 120 | + {{ with $preamble }} |
| 121 | + {{ $data = $data | merge (dict "" .) }} |
| 122 | + {{ end }} |
102 | 123 | {{ end }} |
103 | 124 | {{ else if (eq $type "heading" ) }} |
104 | 125 | {{/* Put heading keys with empty content to the data object */}} |
|
0 commit comments