diff --git a/src/markdown/render.ts b/src/markdown/render.ts index 502ab69ef..dad833a05 100644 --- a/src/markdown/render.ts +++ b/src/markdown/render.ts @@ -87,40 +87,36 @@ export function renderMarkdownWithMarkers(ir: MarkdownIR, options: RenderOptions } const points = [...boundaries].sort((a, b) => a - b); - const stack: MarkdownStyleSpan[] = []; + // Unified stack for both styles and links, tracking close string and end position + const stack: { close: string; end: number }[] = []; let out = ""; for (let i = 0; i < points.length; i += 1) { const pos = points[i]; + // Close ALL elements (styles and links) in LIFO order at this position while (stack.length && stack[stack.length - 1]?.end === pos) { - const span = stack.pop(); - if (!span) break; - const marker = styleMarkers[span.style]; - if (marker) out += marker.close; - } - - const closingLinks = linkEnds.get(pos); - if (closingLinks && closingLinks.length > 0) { - for (const link of closingLinks) { - out += link.close; - } + const item = stack.pop(); + if (item) out += item.close; } + // Open links first (so they close after styles that start at the same position) const openingLinks = linkStarts.get(pos); if (openingLinks && openingLinks.length > 0) { for (const link of openingLinks) { out += link.open; + stack.push({ close: link.close, end: link.end }); } } + // Open styles second (so they close before links that start at the same position) const openingStyles = startsAt.get(pos); if (openingStyles) { for (const span of openingStyles) { const marker = styleMarkers[span.style]; if (!marker) continue; - stack.push(span); out += marker.open; + stack.push({ close: marker.close, end: span.end }); } } diff --git a/src/telegram/format.test.ts b/src/telegram/format.test.ts index 831782815..9b2cb60c6 100644 --- a/src/telegram/format.test.ts +++ b/src/telegram/format.test.ts @@ -47,4 +47,14 @@ describe("markdownToTelegramHtml", () => { const res = markdownToTelegramHtml("```js\nconst x = 1;\n```"); expect(res).toBe("
const x = 1;\n
"); }); + + it("properly nests overlapping bold and autolink (#4071)", () => { + const res = markdownToTelegramHtml("**start https://example.com** end"); + expect(res).toMatch(/start https:\/\/example\.com<\/a><\/b> end/); + }); + + it("properly nests link inside bold", () => { + const res = markdownToTelegramHtml("**bold [link](https://example.com) text**"); + expect(res).toBe('bold link text'); + }); });