Skip to content

Commit 18be6bf

Browse files
committed
feat: syntax highlighting in MarkDown
1 parent 5d8d682 commit 18be6bf

3 files changed

Lines changed: 51 additions & 13 deletions

File tree

frontend/src/commons/custom_fields/MarkdownField.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
import Markdown from "markdown-to-jsx";
22
import { marked } from "marked";
3-
import { Fragment } from "react";
3+
import { Fragment, React } from "react";
4+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
45

56
import { useLinkStyles } from "../../commons/layout/themes";
7+
import { getPrismTheme } from "../functions";
68
import { getResolvedSettingTheme } from "../user_settings/functions";
79
import LabeledTextField from "./LabeledTextField";
810

11+
declare global {
12+
interface Window {
13+
hljs?: {
14+
highlightElement: (el: HTMLElement) => void;
15+
};
16+
}
17+
}
18+
export {};
19+
920
interface MarkdownProps {
1021
content: string;
1122
label: string;
@@ -45,6 +56,31 @@ function isMarkdownValue(value: string): boolean {
4556
return isMarkdown;
4657
}
4758

59+
type CodeProps = React.HTMLAttributes<HTMLElement>;
60+
61+
function SyntaxHighlightedCode({ className, children, ...rest }: CodeProps) {
62+
// markdown-to-jsx tags fenced blocks with `lang-<language>`.
63+
// Inline code (`like this`) has no such class — render it as-is.
64+
const match = /lang-(\w+)/.exec(className ?? "");
65+
if (!match) {
66+
return (
67+
<code className={className} {...rest}>
68+
{children}
69+
</code>
70+
);
71+
}
72+
73+
return (
74+
<SyntaxHighlighter
75+
language={match[1]}
76+
style={getPrismTheme()}
77+
PreTag="div" // markdown-to-jsx already wraps fenced blocks in <pre>; avoid nesting
78+
>
79+
{String(children).replace(/\n$/, "")}
80+
</SyntaxHighlighter>
81+
);
82+
}
83+
4884
const MarkdownField = (props: MarkdownProps) => {
4985
const { classes } = useLinkStyles({ setting_theme: getResolvedSettingTheme() });
5086

@@ -64,6 +100,7 @@ const MarkdownField = (props: MarkdownProps) => {
64100
className: classes.link,
65101
},
66102
},
103+
code: SyntaxHighlightedCode,
67104
},
68105
}}
69106
>

frontend/src/commons/functions.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PackageURL } from "packageurl-js";
22
import { SortPayload } from "ra-core";
3+
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
34

45
import { httpClient } from "../commons/ra-data-django-rest-framework";
56
import {
@@ -406,3 +407,12 @@ export function has_attribute(attribute: string, data: any, sort: SortPayload |
406407
}
407408
});
408409
}
410+
411+
export function getPrismTheme() {
412+
const theme = getResolvedSettingTheme();
413+
if (theme === "dark") {
414+
return oneDark;
415+
} else {
416+
return oneLight;
417+
}
418+
}

frontend/src/rules/functions.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import {
1616
} from "react-admin";
1717
import { useWatch } from "react-hook-form";
1818
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
19+
1920
// import { PrismLight as SyntaxHighlighter } from "react-syntax-highlighter";
2021
// import rego from "react-syntax-highlighter/dist/esm/languages/prism/rego";
21-
import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
2222

2323
import MarkdownEdit from "../commons/custom_fields/MarkdownEdit";
2424
import MarkdownField from "../commons/custom_fields/MarkdownField";
@@ -33,6 +33,7 @@ import {
3333
import {
3434
feature_general_rules_need_approval_enabled,
3535
feature_vex_enabled,
36+
getPrismTheme,
3637
justificationIsEnabledForStatus,
3738
remediationsAreEnabledForStatus,
3839
settings_vex_justification_style,
@@ -45,7 +46,6 @@ import {
4546
useStyles,
4647
} from "../commons/layout/themes";
4748
import { VEX_JUSTIFICATION_TYPE_CSAF_OPENVEX, VEX_JUSTIFICATION_TYPE_CYCLONEDX } from "../commons/types";
48-
import { getResolvedSettingTheme } from "../commons/user_settings/functions";
4949
import {
5050
OBSERVATION_CYCLONEDX_VEX_JUSTIFICATION_CHOICES,
5151
OBSERVATION_SEVERITY_CHOICES,
@@ -58,15 +58,6 @@ import { RULE_TYPE_CHOICES, RULE_TYPE_FIELDS, RULE_TYPE_REGO } from "./types";
5858

5959
// SyntaxHighlighter.registerLanguage("rego", rego);
6060

61-
export function getRegoTheme() {
62-
const theme = getResolvedSettingTheme();
63-
if (theme === "dark") {
64-
return oneDark;
65-
} else {
66-
return oneLight;
67-
}
68-
}
69-
7061
export const validateRuleForm = (values: any) => {
7162
const errors: any = {};
7263

@@ -196,7 +187,7 @@ export const RuleShowComponent = ({ rule }: any) => {
196187
<Labeled label="Rego module">
197188
<SyntaxHighlighter
198189
language="rego"
199-
style={getRegoTheme()}
190+
style={getPrismTheme()}
200191
wrapLongLines
201192
customStyle={{ lineHeight: "1.43", fontSize: "0.875rem" }}
202193
codeTagProps={{

0 commit comments

Comments
 (0)