Skip to content

Commit aef710d

Browse files
committed
Sync minimap markdown list rendering
1 parent 90dc930 commit aef710d

2 files changed

Lines changed: 180 additions & 60 deletions

File tree

tools/minimap/ui/app.js

Lines changed: 166 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -171,99 +171,205 @@ function renderInlineMarkdown(value) {
171171

172172
function renderMarkdownToHtml(markdown) {
173173
const normalized = String(markdown || "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
174-
const lines = normalized.split("\n");
174+
const sourceLines = normalized.split("\n");
175175
const blocks = [];
176-
let paragraphLines = [];
177-
let listItems = [];
178-
let orderedListItems = [];
179-
let codeLines = [];
180-
let inCodeBlock = false;
181-
182-
function flushParagraph() {
183-
if (paragraphLines.length === 0) {
184-
return;
185-
}
186-
blocks.push(`<p>${renderInlineMarkdown(paragraphLines.join(" "))}</p>`);
187-
paragraphLines = [];
176+
177+
function expandIndentation(value) {
178+
return String(value || "").replace(/\t/g, " ");
179+
}
180+
181+
function getIndentation(value) {
182+
const expanded = expandIndentation(value);
183+
const match = expanded.match(/^(\s*)/);
184+
return match ? match[1].length : 0;
188185
}
189186

190-
function flushList() {
191-
if (listItems.length > 0) {
192-
blocks.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</ul>`);
193-
listItems = [];
187+
function getListMarker(value) {
188+
const expanded = expandIndentation(value);
189+
let match = expanded.match(/^(\s*)([-*])\s+(.+)$/);
190+
if (match) {
191+
return { indent: match[1].length, ordered: false, content: match[3] };
194192
}
195193

196-
if (orderedListItems.length > 0) {
197-
blocks.push(`<ol>${orderedListItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join("")}</ol>`);
198-
orderedListItems = [];
194+
match = expanded.match(/^(\s*)(\d+)\.\s+(.+)$/);
195+
if (match) {
196+
return { indent: match[1].length, ordered: true, content: match[3] };
199197
}
198+
199+
return null;
200200
}
201201

202-
function flushCodeBlock() {
203-
if (codeLines.length === 0) {
204-
return;
202+
function parseCodeBlock(startIndex) {
203+
const codeLines = [];
204+
let index = startIndex + 1;
205+
206+
while (index < sourceLines.length && !sourceLines[index].startsWith("```")) {
207+
codeLines.push(sourceLines[index]);
208+
index += 1;
205209
}
206-
blocks.push(`<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`);
207-
codeLines = [];
208-
}
209-
for (const line of lines) {
210-
if (line.startsWith("```")) {
211-
flushParagraph();
212-
flushList();
213-
if (inCodeBlock) {
214-
flushCodeBlock();
215-
}
216-
inCodeBlock = !inCodeBlock;
217-
continue;
210+
211+
if (index < sourceLines.length && sourceLines[index].startsWith("```")) {
212+
index += 1;
218213
}
219214

220-
if (inCodeBlock) {
221-
codeLines.push(line);
222-
continue;
215+
return {
216+
html: `<pre><code>${escapeHtml(codeLines.join("\n"))}</code></pre>`,
217+
nextIndex: index,
218+
};
219+
}
220+
221+
function parseParagraph(startIndex) {
222+
const paragraphLines = [];
223+
let index = startIndex;
224+
225+
while (index < sourceLines.length) {
226+
const rawLine = sourceLines[index];
227+
const trimmed = rawLine.trim();
228+
if (!trimmed) {
229+
break;
230+
}
231+
if (trimmed.startsWith("```") || trimmed.match(/^(#{1,6})\s+(.+)$/) || getListMarker(rawLine)) {
232+
break;
233+
}
234+
235+
paragraphLines.push(trimmed);
236+
index += 1;
223237
}
224238

225-
const trimmed = line.trim();
239+
return {
240+
html: `<p>${renderInlineMarkdown(paragraphLines.join(" "))}</p>`,
241+
nextIndex: index,
242+
};
243+
}
244+
245+
function parseList(startIndex, baseIndent, ordered) {
246+
const tagName = ordered ? "ol" : "ul";
247+
const items = [];
248+
let index = startIndex;
226249

227-
if (trimmed === "") {
228-
flushParagraph();
229-
flushList();
230-
continue;
250+
while (index < sourceLines.length) {
251+
const marker = getListMarker(sourceLines[index]);
252+
if (!marker || marker.indent !== baseIndent || marker.ordered !== ordered) {
253+
break;
254+
}
255+
256+
const paragraphLines = [marker.content.trim()];
257+
const children = [];
258+
index += 1;
259+
260+
while (index < sourceLines.length) {
261+
const rawLine = sourceLines[index];
262+
const trimmed = rawLine.trim();
263+
264+
if (!trimmed) {
265+
let lookahead = index + 1;
266+
while (lookahead < sourceLines.length && !sourceLines[lookahead].trim()) {
267+
lookahead += 1;
268+
}
269+
if (lookahead >= sourceLines.length) {
270+
index = lookahead;
271+
break;
272+
}
273+
274+
const nextMarker = getListMarker(sourceLines[lookahead]);
275+
const nextIndent = getIndentation(sourceLines[lookahead]);
276+
const nextTrimmed = sourceLines[lookahead].trim();
277+
if ((nextMarker && nextMarker.indent <= baseIndent) || (!nextMarker && nextIndent <= baseIndent && !nextTrimmed.startsWith("```") && !nextTrimmed.match(/^(#{1,6})\s+(.+)$/))) {
278+
index = lookahead;
279+
break;
280+
}
281+
282+
index = lookahead;
283+
continue;
284+
}
285+
286+
if (trimmed.startsWith("```")) {
287+
const parsedCode = parseCodeBlock(index);
288+
children.push(parsedCode.html);
289+
index = parsedCode.nextIndex;
290+
continue;
291+
}
292+
293+
const nestedMarker = getListMarker(rawLine);
294+
if (nestedMarker) {
295+
if (nestedMarker.indent > baseIndent) {
296+
const parsedList = parseList(index, nestedMarker.indent, nestedMarker.ordered);
297+
children.push(parsedList.html);
298+
index = parsedList.nextIndex;
299+
continue;
300+
}
301+
302+
break;
303+
}
304+
305+
const indent = getIndentation(rawLine);
306+
if (trimmed.match(/^(#{1,6})\s+(.+)$/) && indent <= baseIndent) {
307+
break;
308+
}
309+
310+
if (indent > baseIndent) {
311+
paragraphLines.push(trimmed);
312+
index += 1;
313+
continue;
314+
}
315+
316+
break;
317+
}
318+
319+
const paragraphHtml = renderInlineMarkdown(paragraphLines.join(" "));
320+
if (children.length > 0) {
321+
items.push(`<li><p>${paragraphHtml}</p>${children.join("")}</li>`);
322+
} else {
323+
items.push(`<li>${paragraphHtml}</li>`);
324+
}
231325
}
232326

233-
const unorderedMatch = trimmed.match(/^[-*]\s+(.+)$/);
234-
if (unorderedMatch) {
235-
flushParagraph();
236-
orderedListItems = [];
237-
listItems.push(unorderedMatch[1]);
327+
return {
328+
html: `<${tagName}>${items.join("")}</${tagName}>`,
329+
nextIndex: index,
330+
};
331+
}
332+
333+
let index = 0;
334+
while (index < sourceLines.length) {
335+
const rawLine = sourceLines[index];
336+
const trimmed = rawLine.trim();
337+
338+
if (!trimmed) {
339+
index += 1;
238340
continue;
239341
}
240342

241-
const orderedMatch = trimmed.match(/^\d+[.]\s+(.+)$/);
242-
if (orderedMatch) {
243-
flushParagraph();
244-
listItems = [];
245-
orderedListItems.push(orderedMatch[1]);
343+
if (trimmed.startsWith("```")) {
344+
const parsedCode = parseCodeBlock(index);
345+
blocks.push(parsedCode.html);
346+
index = parsedCode.nextIndex;
246347
continue;
247348
}
248349

249350
const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
250351
if (headingMatch) {
251-
flushParagraph();
252-
flushList();
253352
const level = headingMatch[1].length;
254353
blocks.push(`<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`);
354+
index += 1;
255355
continue;
256356
}
257357

258-
paragraphLines.push(trimmed);
358+
const listMarker = getListMarker(rawLine);
359+
if (listMarker) {
360+
const parsedList = parseList(index, listMarker.indent, listMarker.ordered);
361+
blocks.push(parsedList.html);
362+
index = parsedList.nextIndex;
363+
continue;
364+
}
365+
366+
const paragraph = parseParagraph(index);
367+
blocks.push(paragraph.html);
368+
index = paragraph.nextIndex;
259369
}
260370

261-
flushParagraph();
262-
flushList();
263-
flushCodeBlock();
264371
return blocks.join("");
265372
}
266-
267373
function ensureSelectValue(select, value) {
268374
if (!Array.from(select.options).some((option) => option.value === value)) {
269375
const option = document.createElement("option");

tools/minimap/ui/styles.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,20 @@ h3 {
12081208
padding-left: 1.3rem;
12091209
}
12101210

1211+
.preview-markdown li + li {
1212+
margin-top: 0.22rem;
1213+
}
1214+
1215+
.preview-markdown li > p {
1216+
margin: 0;
1217+
}
1218+
1219+
.preview-markdown li > ul,
1220+
.preview-markdown li > ol {
1221+
margin-top: 0.45rem;
1222+
margin-bottom: 0.2rem;
1223+
}
1224+
12111225
.preview-markdown code {
12121226
padding: 0.1rem 0.34rem;
12131227
border-radius: 999px;

0 commit comments

Comments
 (0)