fix(telegram): properly nest overlapping HTML tags (#4071)

Unify style and link closing in render.ts to use LIFO order across
both element types, fixing cases where bold/italic spans containing
autolinks produced invalid HTML like <b><a></b></a>.
This commit is contained in:
ThanhNguyxn
2026-01-30 17:33:49 +07:00
committed by Ayaan Zaidi
parent fa9ec6e854
commit b05d57964b
2 changed files with 19 additions and 13 deletions

View File

@@ -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 });
}
}

View File

@@ -47,4 +47,14 @@ describe("markdownToTelegramHtml", () => {
const res = markdownToTelegramHtml("```js\nconst x = 1;\n```");
expect(res).toBe("<pre><code>const x = 1;\n</code></pre>");
});
it("properly nests overlapping bold and autolink (#4071)", () => {
const res = markdownToTelegramHtml("**start https://example.com** end");
expect(res).toMatch(/<b>start <a href="https:\/\/example\.com">https:\/\/example\.com<\/a><\/b> end/);
});
it("properly nests link inside bold", () => {
const res = markdownToTelegramHtml("**bold [link](https://example.com) text**");
expect(res).toBe('<b>bold <a href="https://example.com">link</a> text</b>');
});
});