|
38 | 38 | .replaceAll("/", ""); |
39 | 39 | } |
40 | 40 |
|
41 | | - function filterToModule(markdownContainer, moduleSlug) { |
42 | | - if (!moduleSlug) { |
43 | | - return null; |
44 | | - } |
45 | | - |
46 | | - const headings = [...markdownContainer.querySelectorAll("h3")]; |
47 | | - const targetHeading = headings.find( |
48 | | - (heading) => normalizeModuleSlug(heading.textContent) === moduleSlug |
| 41 | + function parseModuleHeadingText(line) { |
| 42 | + const linkedHeadingMatch = line.match( |
| 43 | + /^###\s+\[([^\]]+)\]\([^)]*\)\s*$/u |
49 | 44 | ); |
50 | | - |
51 | | - if (!targetHeading) { |
52 | | - return null; |
53 | | - } |
54 | | - |
55 | | - const section = [targetHeading]; |
56 | | - let node = targetHeading.nextElementSibling; |
57 | | - while (node && node.tagName !== "H3") { |
58 | | - section.push(node); |
59 | | - node = node.nextElementSibling; |
| 45 | + if (linkedHeadingMatch) { |
| 46 | + return linkedHeadingMatch[1]; |
60 | 47 | } |
61 | 48 |
|
62 | | - markdownContainer.replaceChildren(...section); |
63 | | - return targetHeading.textContent; |
| 49 | + const plainHeadingMatch = line.match(/^###\s+(.+)$/u); |
| 50 | + return plainHeadingMatch ? plainHeadingMatch[1].trim() : ""; |
64 | 51 | } |
65 | 52 |
|
66 | | - function extractGeneralNotes(markdownContainer) { |
67 | | - const heading = [...markdownContainer.querySelectorAll("h2")].find( |
68 | | - (node) => node.textContent === "General notes" |
| 53 | + function extractGeneralNotesMarkdown(lines) { |
| 54 | + const startIndex = lines.findIndex( |
| 55 | + (line) => line.trim() === "## General notes" |
69 | 56 | ); |
70 | | - |
71 | | - if (!heading) { |
| 57 | + if (startIndex === -1) { |
72 | 58 | return []; |
73 | 59 | } |
74 | 60 |
|
75 | 61 | const notes = []; |
76 | | - let node = heading.nextElementSibling; |
77 | | - while (node && node.tagName !== "H2") { |
78 | | - notes.push(node.cloneNode(true)); |
79 | | - const nextNode = node.nextElementSibling; |
80 | | - node.remove(); |
81 | | - node = nextNode; |
| 62 | + for (let i = startIndex + 1; i < lines.length; i++) { |
| 63 | + if (/^##\s+/u.test(lines[i])) { |
| 64 | + break; |
| 65 | + } |
| 66 | + notes.push(lines[i]); |
82 | 67 | } |
83 | 68 |
|
84 | | - heading.remove(); |
85 | 69 | return notes; |
86 | 70 | } |
87 | 71 |
|
88 | | - function injectModuleIntroSections(markdownContainer, notes) { |
89 | | - if (notes.length === 0) { |
90 | | - return; |
91 | | - } |
| 72 | + function extractModuleSectionMarkdown(lines, moduleSlug) { |
| 73 | + let startIndex = -1; |
92 | 74 |
|
93 | | - const headings = [...markdownContainer.querySelectorAll("h3")]; |
94 | | - for (const heading of headings) { |
95 | | - const introSection = document.createElement("section"); |
96 | | - introSection.className = "module-intro"; |
| 75 | + for (let i = 0; i < lines.length; i++) { |
| 76 | + if (!lines[i].startsWith("### ")) { |
| 77 | + continue; |
| 78 | + } |
97 | 79 |
|
98 | | - const notesHeading = document.createElement("h4"); |
99 | | - notesHeading.className = "module-section-heading"; |
100 | | - notesHeading.textContent = "General notes"; |
| 80 | + const headingText = parseModuleHeadingText(lines[i]); |
| 81 | + if (normalizeModuleSlug(headingText) === moduleSlug) { |
| 82 | + startIndex = i; |
| 83 | + break; |
| 84 | + } |
| 85 | + } |
101 | 86 |
|
102 | | - const findingsHeading = document.createElement("h4"); |
103 | | - findingsHeading.className = "module-section-heading"; |
104 | | - findingsHeading.textContent = "Findings"; |
| 87 | + if (startIndex === -1) { |
| 88 | + return null; |
| 89 | + } |
105 | 90 |
|
106 | | - introSection.appendChild(notesHeading); |
107 | | - for (const note of notes) { |
108 | | - introSection.appendChild(note.cloneNode(true)); |
| 91 | + let endIndex = lines.length; |
| 92 | + for (let i = startIndex + 1; i < lines.length; i++) { |
| 93 | + if (lines[i].startsWith("### ")) { |
| 94 | + endIndex = i; |
| 95 | + break; |
109 | 96 | } |
110 | | - introSection.appendChild(findingsHeading); |
111 | | - heading.insertAdjacentElement("afterend", introSection); |
112 | 97 | } |
| 98 | + |
| 99 | + return { |
| 100 | + bodyLines: lines.slice(startIndex + 1, endIndex), |
| 101 | + title: parseModuleHeadingText(lines[startIndex]) |
| 102 | + }; |
| 103 | + } |
| 104 | + |
| 105 | + function buildFilteredModuleMarkdown(markdownText, moduleSlug) { |
| 106 | + const lines = markdownText.split("\n"); |
| 107 | + const moduleSection = extractModuleSectionMarkdown(lines, moduleSlug); |
| 108 | + if (!moduleSection) { |
| 109 | + return null; |
| 110 | + } |
| 111 | + |
| 112 | + const generalNotes = extractGeneralNotesMarkdown(lines); |
| 113 | + const outputLines = []; |
| 114 | + |
| 115 | + if (generalNotes.length > 0) { |
| 116 | + outputLines.push("## General notes", "", ...generalNotes, ""); |
| 117 | + } |
| 118 | + |
| 119 | + outputLines.push("## Findings", "", ...moduleSection.bodyLines); |
| 120 | + |
| 121 | + return { |
| 122 | + markdown: outputLines.join("\n").trim(), |
| 123 | + title: moduleSection.title |
| 124 | + }; |
113 | 125 | } |
114 | 126 |
|
115 | 127 | function setPageTitle(title) { |
|
136 | 148 | ); |
137 | 149 | } |
138 | 150 | const markdownText = await response.text(); |
139 | | - markdownContainer.innerHTML = marked.parse(markdownText); |
| 151 | + |
140 | 152 | if (moduleSlug) { |
141 | | - const generalNotes = extractGeneralNotes(markdownContainer); |
142 | | - injectModuleIntroSections(markdownContainer, generalNotes); |
143 | | - } |
144 | | - const moduleTitle = filterToModule(markdownContainer, moduleSlug); |
145 | | - if (moduleTitle) { |
146 | | - setPageTitle(moduleTitle); |
147 | | - fullResultsLink.href = "result.html"; |
148 | | - fullResultsLink.textContent = "Full hints list"; |
149 | | - } else if (moduleSlug) { |
150 | | - markdownContainer.innerHTML = |
151 | | - "<p>No hints found for this module.</p>"; |
152 | | - setPageTitle("Module hints"); |
153 | | - fullResultsLink.href = "result.html"; |
154 | | - fullResultsLink.textContent = "Full hints list"; |
| 153 | + const filtered = buildFilteredModuleMarkdown( |
| 154 | + markdownText, |
| 155 | + moduleSlug |
| 156 | + ); |
| 157 | + if (!filtered) { |
| 158 | + markdownContainer.innerHTML = |
| 159 | + "<p>No hints found for this module.</p>"; |
| 160 | + setPageTitle("Module hints"); |
| 161 | + fullResultsLink.href = "result.html"; |
| 162 | + fullResultsLink.textContent = "Full hints list"; |
| 163 | + } else { |
| 164 | + markdownContainer.innerHTML = marked.parse(filtered.markdown); |
| 165 | + setPageTitle(filtered.title); |
| 166 | + fullResultsLink.href = "result.html"; |
| 167 | + fullResultsLink.textContent = "Full hints list"; |
| 168 | + } |
| 169 | + } else { |
| 170 | + markdownContainer.innerHTML = marked.parse(markdownText); |
155 | 171 | } |
| 172 | + |
156 | 173 | addHeadingAnchors(); |
157 | 174 | } catch (error) { |
158 | 175 | console.error("Error loading markdown:", error); |
|
165 | 182 | const markdownContainer = document.getElementById("markdown-container"); |
166 | 183 | const headings = markdownContainer.querySelectorAll("h1, h2, h3, h4"); |
167 | 184 | headings.forEach((heading) => { |
168 | | - if (heading.classList.contains("module-section-heading")) { |
169 | | - return; |
170 | | - } |
171 | | - |
172 | 185 | const anchorId = normalizeModuleSlug(heading.textContent); |
173 | 186 | const anchor = document.createElement("a"); |
174 | 187 | anchor.id = anchorId; |
|
0 commit comments