mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
fix: resolve rendering issues with GFM alert boxes in <DynamicParameter /> (#22241)
Closes #22189 GFM alerts (e.g., `> [!IMPORTANT]`) in Markdown content failed to render when the alert body contained inline formatting like `**bold**`, `*italic*`, or `` `code` ``. The alert marker and subsequent text were merged into a single string node by the parser, causing the type detection to fail and fall back to a plain blockquote. Additionally, multi-line alert content (`> line one\n> line two`) lost its line breaks — all lines collapsed into one. - Split the alert marker from trailing content in shared string nodes so type detection works with inline formatting - Preserve embedded newlines as `<br/>` elements to match GitHub's GFM alert rendering - Wrap plain-text children instead of splitting on `\n` to avoid stripping newline information early <img width="447" height="187" alt="image" src="https://github.com/user-attachments/assets/d2fa3495-0b31-483c-97d8-12fed6819e24" />
This commit is contained in:
@@ -81,8 +81,8 @@ export const GFMAlerts: Story = {
|
||||
> [!NOTE]
|
||||
> Useful information that users should know, even when skimming content.
|
||||
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Key information users need to know to achieve their goal.
|
||||
@@ -95,3 +95,13 @@ export const GFMAlerts: Story = {
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
export const GFMAlertWithInlineFormatting: Story = {
|
||||
args: {
|
||||
children: `
|
||||
> [!IMPORTANT]
|
||||
> Larger **instances** cost more. Choose based on your workload.
|
||||
> Test line two
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Interpolation, Theme } from "@emotion/react";
|
||||
import Link from "@mui/material/Link";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import {
|
||||
createElement,
|
||||
type FC,
|
||||
type HTMLProps,
|
||||
isValidElement,
|
||||
@@ -256,8 +257,9 @@ function parseChildrenAsAlertContent(
|
||||
if (typeof parentChildren === "string") {
|
||||
// Children will only be an array if the parsed text contains other
|
||||
// content that can be turned into HTML. If there aren't any, you
|
||||
// just get one big string
|
||||
parentChildren = parentChildren.split("\n");
|
||||
// just get one big string. Wrap it rather than splitting so that
|
||||
// embedded newlines are preserved for line-break conversion later.
|
||||
parentChildren = [parentChildren];
|
||||
}
|
||||
if (!Array.isArray(parentChildren)) {
|
||||
return null;
|
||||
@@ -304,7 +306,17 @@ function parseChildrenAsAlertContent(
|
||||
return null;
|
||||
}
|
||||
|
||||
const alertType = firstEl
|
||||
// The alert marker (e.g., "[!IMPORTANT]") may share a string node
|
||||
// with subsequent content when inline formatting follows on the
|
||||
// next blockquote line. Split on the first newline so we only
|
||||
// test the marker portion.
|
||||
const firstNewline = firstEl.indexOf("\n");
|
||||
const alertCandidate =
|
||||
firstNewline === -1 ? firstEl : firstEl.substring(0, firstNewline);
|
||||
const trailingContent =
|
||||
firstNewline === -1 ? null : firstEl.substring(firstNewline + 1);
|
||||
|
||||
const alertType = alertCandidate
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace("!", "")
|
||||
@@ -314,15 +326,40 @@ function parseChildrenAsAlertContent(
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trailingContent) {
|
||||
remainingChildren.unshift(trailingContent);
|
||||
}
|
||||
|
||||
const hasLeadingLinebreak =
|
||||
isValidElement(remainingChildren[0]) && remainingChildren[0].type === "br";
|
||||
if (hasLeadingLinebreak) {
|
||||
remainingChildren.shift();
|
||||
}
|
||||
|
||||
// GitHub's GFM alerts preserve line breaks within alert content,
|
||||
// but the markdown parser treats them as soft wraps (spaces).
|
||||
// Convert embedded newlines in text nodes to <br/> elements to
|
||||
// match GitHub's rendering behavior.
|
||||
const withLineBreaks: ReactNode[] = remainingChildren.flatMap((child, i) => {
|
||||
if (typeof child !== "string" || !child.includes("\n")) {
|
||||
return [child];
|
||||
}
|
||||
const parts = child.split("\n");
|
||||
const result: ReactNode[] = [];
|
||||
for (let j = 0; j < parts.length; j++) {
|
||||
if (j > 0) {
|
||||
result.push(createElement("br", { key: `alert-br-${i}-${j}` }));
|
||||
}
|
||||
if (parts[j]) {
|
||||
result.push(parts[j]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
return {
|
||||
type: alertType,
|
||||
children: remainingChildren,
|
||||
children: withLineBreaks,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user