From b6e5917eaa436aeb05050a1f364bc030b75033d3 Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Tue, 27 Aug 2024 21:57:51 +0200 Subject: [PATCH 1/5] Handle custom admonition syntax Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/src/components/Markdown/P.tsx | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Markdown/P.tsx b/frontend/src/components/Markdown/P.tsx index 87f1d951..a05827a1 100644 --- a/frontend/src/components/Markdown/P.tsx +++ b/frontend/src/components/Markdown/P.tsx @@ -1,7 +1,55 @@ -import { HTMLAttributes } from "react"; +import { HTMLAttributes, ReactNode } from "react"; import { Paragraph } from "../Paragraph"; +import clsx from "clsx"; + +const admonitionRegex = /^(?!>|~>|->)\s+(?.*)$/; + +const getAdmonitionClassName = (prefix: string) => { + switch (prefix) { + case "->": + return "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-100"; + case "!>": + return "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-100"; + case "~>": + return "bg-yellow-100 text-yellow-800 dark:bg-yellow-950 dark:text-yellow-100"; + default: + return ""; + } +}; + +function getFirstLine(children: ReactNode) { + if (typeof children === "string") { + return children; + } + + return Array.isArray(children) && typeof children[0] === "string" + ? children[0] + : null; +} export function MarkdownP({ children }: HTMLAttributes) { + const firstLine = getFirstLine(children); + + if (firstLine && admonitionRegex.test(firstLine)) { + const match = firstLine.match(admonitionRegex); + + if (match?.groups) { + const { prefix, text } = match.groups; + const className = getAdmonitionClassName(prefix); + const remainingLines = Array.isArray(children) ? children.slice(1) : null; + + return ( +
&:first-child]:mt-0", className)} + > + {text} + {remainingLines} +
+ ); + } + } + return ( {children} From d85534f26636b1c11897766cb786062d45bb659f Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:52:08 +0200 Subject: [PATCH 2/5] Clean up the code Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/src/components/Markdown/P.tsx | 63 ++++++++++++++------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/Markdown/P.tsx b/frontend/src/components/Markdown/P.tsx index a05827a1..f803ddd7 100644 --- a/frontend/src/components/Markdown/P.tsx +++ b/frontend/src/components/Markdown/P.tsx @@ -2,9 +2,9 @@ import { HTMLAttributes, ReactNode } from "react"; import { Paragraph } from "../Paragraph"; import clsx from "clsx"; -const admonitionRegex = /^(?!>|~>|->)\s+(?.*)$/; +const admonitionRegex = /^(?!>|~>|->)\s+(?.*)$/; -const getAdmonitionClassName = (prefix: string) => { +function getAdmonitionClassName(prefix: string) { switch (prefix) { case "->": return "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-100"; @@ -15,39 +15,46 @@ const getAdmonitionClassName = (prefix: string) => { default: return ""; } -}; +} + +function getAdmonitionMatch(children: ReactNode) { + let content = ""; -function getFirstLine(children: ReactNode) { if (typeof children === "string") { - return children; + content = children; + } else if (Array.isArray(children) && typeof children[0] === "string") { + content = children[0]; } - return Array.isArray(children) && typeof children[0] === "string" - ? children[0] - : null; + const match = content.match(admonitionRegex); + + if (!match || !match.groups) { + return null; + } + + return { + prefix: match.groups.prefix, + content: match.groups.content, + }; } export function MarkdownP({ children }: HTMLAttributes) { - const firstLine = getFirstLine(children); - - if (firstLine && admonitionRegex.test(firstLine)) { - const match = firstLine.match(admonitionRegex); - - if (match?.groups) { - const { prefix, text } = match.groups; - const className = getAdmonitionClassName(prefix); - const remainingLines = Array.isArray(children) ? children.slice(1) : null; - - return ( -
&:first-child]:mt-0", className)} - > - {text} - {remainingLines} -
- ); - } + const admonitionMatch = getAdmonitionMatch(children); + + if (admonitionMatch) { + const { prefix, content } = admonitionMatch; + const className = getAdmonitionClassName(prefix); + const remainingContent = Array.isArray(children) ? children.slice(1) : null; + + return ( +
&:first-child]:mt-0", className)} + > + {content} + {remainingContent} +
+ ); } return ( From 3779e9a5548a4338d2c9c2d426d8c78adc702421 Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:54:33 +0200 Subject: [PATCH 3/5] Skip matching if the content is empty Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/src/components/Markdown/P.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Markdown/P.tsx b/frontend/src/components/Markdown/P.tsx index f803ddd7..4c1c6674 100644 --- a/frontend/src/components/Markdown/P.tsx +++ b/frontend/src/components/Markdown/P.tsx @@ -26,9 +26,13 @@ function getAdmonitionMatch(children: ReactNode) { content = children[0]; } + if (!content) { + return null; + } + const match = content.match(admonitionRegex); - if (!match || !match.groups) { + if (!match?.groups?.prefix || !match?.groups?.content) { return null; } From 4ccf0c9550da0fa2238178949a66a9fa753df103 Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:14:37 +0200 Subject: [PATCH 4/5] Try out the regexp-less approach Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/src/components/Markdown/P.tsx | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Markdown/P.tsx b/frontend/src/components/Markdown/P.tsx index 4c1c6674..2e02abbc 100644 --- a/frontend/src/components/Markdown/P.tsx +++ b/frontend/src/components/Markdown/P.tsx @@ -2,7 +2,9 @@ import { HTMLAttributes, ReactNode } from "react"; import { Paragraph } from "../Paragraph"; import clsx from "clsx"; -const admonitionRegex = /^(?!>|~>|->)\s+(?.*)$/; +const WARNING_MARK = "~>"; +const DANGER_MARK = "!>"; +const NOTE_MARK = "->"; function getAdmonitionClassName(prefix: string) { switch (prefix) { @@ -30,16 +32,18 @@ function getAdmonitionMatch(children: ReactNode) { return null; } - const match = content.match(admonitionRegex); - - if (!match?.groups?.prefix || !match?.groups?.content) { - return null; + if ( + content.startsWith(WARNING_MARK) || + content.startsWith(DANGER_MARK) || + content.startsWith(NOTE_MARK) + ) { + return { + prefix: content.slice(0, 2), + content: content.slice(2).trim(), + }; } - return { - prefix: match.groups.prefix, - content: match.groups.content, - }; + return null; } export function MarkdownP({ children }: HTMLAttributes) { From 2ff4d8c9e0661588bdcd7048456e7742b9b9fe5c Mon Sep 17 00:00:00 2001 From: Damian Stasik <920747+damianstasik@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:15:48 +0200 Subject: [PATCH 5/5] Reuse the marks in the switch case Signed-off-by: Damian Stasik <920747+damianstasik@users.noreply.github.com> --- frontend/src/components/Markdown/P.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Markdown/P.tsx b/frontend/src/components/Markdown/P.tsx index 2e02abbc..22c81b85 100644 --- a/frontend/src/components/Markdown/P.tsx +++ b/frontend/src/components/Markdown/P.tsx @@ -8,11 +8,11 @@ const NOTE_MARK = "->"; function getAdmonitionClassName(prefix: string) { switch (prefix) { - case "->": + case NOTE_MARK: return "bg-sky-100 text-sky-800 dark:bg-sky-950 dark:text-sky-100"; - case "!>": + case DANGER_MARK: return "bg-red-100 text-red-800 dark:bg-red-950 dark:text-red-100"; - case "~>": + case WARNING_MARK: return "bg-yellow-100 text-yellow-800 dark:bg-yellow-950 dark:text-yellow-100"; default: return "";