All checks were successful
Deploy MineGeo to Dokploy / deploy (push) Successful in 5m17s
64 lines
1.8 KiB
TypeScript
64 lines
1.8 KiB
TypeScript
import { Fragment } from "react";
|
|
|
|
// Render a small, safe subset of Discord Markdown; React escapes raw HTML.
|
|
function inline(text: string) {
|
|
return text
|
|
.split(/(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^\s)]+\))/g)
|
|
.map((part, i) => {
|
|
if (part.startsWith("**") && part.endsWith("**"))
|
|
return <strong key={i}>{part.slice(2, -2)}</strong>;
|
|
if (part.startsWith("`") && part.endsWith("`"))
|
|
return <code key={i}>{part.slice(1, -1)}</code>;
|
|
const link = /^\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)$/.exec(part);
|
|
if (link)
|
|
return (
|
|
<a key={i} href={link[2]} target="_blank" rel="noopener noreferrer">
|
|
{link[1]}
|
|
</a>
|
|
);
|
|
return <Fragment key={i}>{part}</Fragment>;
|
|
});
|
|
}
|
|
|
|
export function ReleaseNotes({ body }: { body: string }) {
|
|
const blocks: { list: boolean; lines: string[] }[] = [];
|
|
let current: (typeof blocks)[number] | undefined;
|
|
for (const line of body.split(/\r?\n/)) {
|
|
if (!line.trim()) {
|
|
current = undefined;
|
|
continue;
|
|
}
|
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(line);
|
|
if (!bullet && current?.list && /^\s{2,}\S/.test(line)) {
|
|
current.lines[current.lines.length - 1] += ` ${line.trim()}`;
|
|
continue;
|
|
}
|
|
const list = !!bullet;
|
|
if (!current || current.list !== list) {
|
|
current = { list, lines: [] };
|
|
blocks.push(current);
|
|
}
|
|
current.lines.push(bullet ? bullet[1] : line);
|
|
}
|
|
return blocks.map(({ list, lines }, i) => {
|
|
if (list)
|
|
return (
|
|
<ul key={i}>
|
|
{lines.map((line, j) => (
|
|
<li key={j}>{inline(line)}</li>
|
|
))}
|
|
</ul>
|
|
);
|
|
return (
|
|
<p key={i}>
|
|
{lines.map((line, j) => (
|
|
<Fragment key={j}>
|
|
{j > 0 && <br />}
|
|
{inline(line)}
|
|
</Fragment>
|
|
))}
|
|
</p>
|
|
);
|
|
});
|
|
}
|