diff --git a/web/src/app.css b/web/src/app.css index 4e7845e7e..ff26e7d99 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -178,6 +178,19 @@ html { --color-ansi-bright-cyan: #04a5e5; --color-ansi-bright-white: #bcc0cc; + --color-syntax-foreground: #4c4f69; + --color-syntax-comment: #7c7f93; + --color-syntax-string: #40a02b; + --color-syntax-keyword: #8839ef; + --color-syntax-function: #1e66f5; + --color-syntax-constant: #fe640b; + --color-syntax-parameter: #e64553; + --color-syntax-punctuation: #7c7f93; + --color-syntax-link: #1e66f5; + --color-syntax-inserted: #40a02b; + --color-syntax-deleted: #d20f39; + --color-syntax-changed: #df8e1d; + --animate-spin-step: spin-step 2.5s infinite; @keyframes spin-step { @@ -690,6 +703,19 @@ html { --color-ansi-bright-magenta: #f4dbd6; --color-ansi-bright-cyan: #91d7e3; --color-ansi-bright-white: #f0c6c6; + + --color-syntax-foreground: #cdd6f4; + --color-syntax-comment: #9399b2; + --color-syntax-string: #a6e3a1; + --color-syntax-keyword: #cba6f7; + --color-syntax-function: #89b4fa; + --color-syntax-constant: #fab387; + --color-syntax-parameter: #eba0ac; + --color-syntax-punctuation: #9399b2; + --color-syntax-link: #89b4fa; + --color-syntax-inserted: #a6e3a1; + --color-syntax-deleted: #f38ba8; + --color-syntax-changed: #f9e2af; } } @@ -705,5 +731,5 @@ html { /* chromium doesn't break links so we have to set it explicitely */ p > a { - line-break: anywhere; + line-break: anywhere; } diff --git a/web/src/lib/components/comment/Comment.svelte b/web/src/lib/components/comment/Comment.svelte index 1d78dee54..3d847e9fa 100644 --- a/web/src/lib/components/comment/Comment.svelte +++ b/web/src/lib/components/comment/Comment.svelte @@ -5,6 +5,7 @@ import Separator from "$lib/components/ui/Separator.svelte"; import TimeAgo from "$lib/components/ui/TimeAgo.svelte"; import Reactions from "$lib/components/reaction/Reactions.svelte"; + import { enhanceCodeblocks } from "$lib/markup/highlight"; import type { ReactionGroup } from "$lib/components/reaction/reactions"; interface Props { @@ -62,8 +63,10 @@ {#if editor}
["diffStyle"]>;
-const PIERRE_THEME_NAME = "tangled";
+export const PIERRE_THEME_NAME = "tangled";
registerCustomCSSVariableTheme(PIERRE_THEME_NAME, {
- // pierre.css defines the real tokens on diffs-container, these are just
- // fallbacks for the generated bindings
background: "#ffffff",
- foreground: "#4c4f69",
- "token-comment": "#7c7f93",
- "token-string": "#40a02b",
- "token-string-expression": "#40a02b",
- "token-keyword": "#8839ef",
- "token-function": "#1e66f5",
- "token-constant": "#fe640b",
- "token-parameter": "#e64553",
- "token-punctuation": "#7c7f93",
- "token-link": "#1e66f5",
- "token-inserted": "#40a02b",
- "token-deleted": "#d20f39",
- "token-changed": "#df8e1d"
+ foreground: "#4c4f69"
});
// pierre skips highlighting lines past 1e3 chars by default.
diff --git a/web/src/lib/components/repo/pulls/PullReviewComment.svelte b/web/src/lib/components/repo/pulls/PullReviewComment.svelte
index 8d9d044e4..34f5f8c38 100644
--- a/web/src/lib/components/repo/pulls/PullReviewComment.svelte
+++ b/web/src/lib/components/repo/pulls/PullReviewComment.svelte
@@ -8,6 +8,7 @@
import TimeAgo from "$lib/components/ui/TimeAgo.svelte";
import User from "$lib/components/ui/User.svelte";
import type { MarkupContext } from "$lib/markup";
+ import { enhanceCodeblocks } from "$lib/markup/highlight";
import { renderMarkdown } from "$lib/markup/markdown";
import PullReviewCommentEditor from "$lib/components/repo/pulls/PullReviewCommentEditor.svelte";
@@ -32,7 +33,7 @@
}: Props = $props();
async function submit(body: string) {
- const bodyHtml = renderMarkdown(body, markup);
+ const bodyHtml = await renderMarkdown(body, markup);
await onedit({ ...comment, body, bodyHtml });
editing = false;
}
@@ -94,7 +95,10 @@
/>
{:else}
{#if comment.bodyHtml}
-
+
{@html comment.bodyHtml}
diff --git a/web/src/lib/components/repo/tickets/TicketBody.svelte b/web/src/lib/components/repo/tickets/TicketBody.svelte
index 1d321bcb2..6355bbe94 100644
--- a/web/src/lib/components/repo/tickets/TicketBody.svelte
+++ b/web/src/lib/components/repo/tickets/TicketBody.svelte
@@ -1,4 +1,6 @@
")).not.toContain("alert");
- expect(render('
')).not.toContain("onerror");
+ it("does not let highlighted code smuggle styles past the sanitizer", async () => {
+ const html = await render("```rust\nfn main() {}\n```");
+ expect(html).not.toContain("background-color");
+ expect(html).not.toContain("shiki");
+ });
+
+ it("strips scripts, event handlers and javascript urls", async () => {
+ expect(await render("")).not.toContain("alert");
+ expect(await render('
')).not.toContain("onerror");
// markdown-it refuses the destination outright, so this stays plain text
- expect(render("[x](javascript:alert(1))")).not.toContain("x')).not.toContain("data:");
+ expect(await render("[x](javascript:alert(1))")).not.toContain("x')).not.toContain("data:");
});
- it("drops classes it does not recognise", () => {
- expect(render('boo')).not.toContain("inset-0");
+ it("drops classes it does not recognise", async () => {
+ expect(await render('boo')).not.toContain(
+ "inset-0"
+ );
});
- it("drops inputs that are not task list checkboxes", () => {
- expect(render('')).not.toContain(" {
+ expect(await render('')).not.toContain(" {
- const html = render(
+ it("keeps the html a readme actually uses for layout", async () => {
+ const html = await render(
'infra
\n\nmore
\n\nhidden\n\n'
);
expect(html).toContain('');
diff --git a/web/src/lib/markup/markdown.ts b/web/src/lib/markup/markdown.ts
index 6370501bb..5135effdb 100644
--- a/web/src/lib/markup/markdown.ts
+++ b/web/src/lib/markup/markdown.ts
@@ -4,6 +4,7 @@ import { tasklist } from "@mdit/plugin-tasklist";
import MarkdownIt from "markdown-it";
import anchor from "markdown-it-anchor";
import { full as emoji } from "markdown-it-emoji";
+import { highlightCodeblocks } from "$lib/markup/highlight";
import { sanitizeMarkup } from "$lib/markup/sanitize";
import type { MarkupContext } from "$lib/markup/paths";
@@ -86,8 +87,8 @@ const commitLinks =
});
};
-// todo: no syntax highlighting, mermaid or math yet. the first two want a client
-// side renderer, math wants mathjax loaded on demand the way the appview does it
+// todo: mermaid and math still want a client side renderer, math wants mathjax
+// loaded on demand the way the appview does it
const build = (host: string): MarkdownIt => {
const md = new MarkdownIt({
html: true,
@@ -121,5 +122,8 @@ const rendererFor = (host: string): MarkdownIt => {
return md;
};
-export const renderMarkdown = (source: string, ctx: MarkupContext): string =>
- sanitizeMarkup(rendererFor(ctx.host ?? "").render(source), ctx);
+export const renderMarkdown = async (source: string, ctx: MarkupContext): Promise => {
+ const raw = rendererFor(ctx.host ?? "").render(source);
+ // sanitize before highlighting so the allowlist doesn't have to allow spans
+ return highlightCodeblocks(sanitizeMarkup(raw, ctx));
+};
diff --git a/web/src/pierre.css b/web/src/pierre.css
index 3c066e183..f5af618fc 100644
--- a/web/src/pierre.css
+++ b/web/src/pierre.css
@@ -1,14 +1,30 @@
+:root {
+ /* the "tangled" theme (pierre.ts) binds --diffs-bg/--diffs-fg to these and
+ forces color-scheme: dark. without them the bg chain goes
+ guaranteed-invalid and light-dark() is stuck on the dark branch */
+ --diffs-background: var(--color-background-default);
+ --diffs-foreground: var(--color-syntax-foreground);
+
+ --diffs-token-comment: var(--color-syntax-comment);
+ --diffs-token-string: var(--color-syntax-string);
+ --diffs-token-string-expression: var(--color-syntax-string);
+ --diffs-token-keyword: var(--color-syntax-keyword);
+ --diffs-token-function: var(--color-syntax-function);
+ --diffs-token-constant: var(--color-syntax-constant);
+ --diffs-token-parameter: var(--color-syntax-parameter);
+ --diffs-token-punctuation: var(--color-syntax-punctuation);
+ --diffs-token-link: var(--color-syntax-link);
+ --diffs-token-inserted: var(--color-syntax-inserted);
+ --diffs-token-deleted: var(--color-syntax-deleted);
+ --diffs-token-changed: var(--color-syntax-changed);
+}
+
/* Override Pierre's unlayered :host stylesheet using unlayered :root diffs-container. */
:root diffs-container {
--diffs-font-family: var(--font-mono);
--diffs-font-size: 0.875rem;
--diffs-line-height: 1.5;
- /* the "tangled" theme (pierre.ts) binds --diffs-bg/--diffs-fg to these and
- forces color-scheme: dark. without them the bg chain goes
- guaranteed-invalid and light-dark() is stuck on the dark branch */
- --diffs-background: var(--color-background-default);
- --diffs-foreground: #4c4f69;
color-scheme: light dark;
--diffs-light-bg: var(--color-background-default);
--diffs-dark-bg: var(--color-background-default);
@@ -24,20 +40,6 @@
--diffs-deletion-color-override: var(--color-foreground-danger);
--diffs-editor-selection-bg: color-mix(in srgb, Highlight 50%, transparent);
-
- /* syntax palette — catppuccin latte */
- --diffs-token-comment: #7c7f93; /* overlay2 */
- --diffs-token-string: #40a02b; /* green */
- --diffs-token-string-expression: #40a02b; /* green */
- --diffs-token-keyword: #8839ef; /* mauve */
- --diffs-token-function: #1e66f5; /* blue */
- --diffs-token-constant: #fe640b; /* peach */
- --diffs-token-parameter: #e64553; /* maroon */
- --diffs-token-punctuation: #7c7f93; /* overlay2 */
- --diffs-token-link: #1e66f5; /* blue */
- --diffs-token-inserted: #40a02b; /* green */
- --diffs-token-deleted: #d20f39; /* red */
- --diffs-token-changed: #df8e1d; /* yellow */
}
/* a card whose hunks are still on the wire has pierre's reserved height and
@@ -77,22 +79,5 @@
--diffs-bg-gutter: var(--color-background-default);
--diffs-bg-gutter-wash: var(--color-alpha-dark-5);
-
- /* catppuccin mocha text (background stays on the app token) */
- --diffs-foreground: #cdd6f4;
-
- /* syntax palette — catppuccin mocha */
- --diffs-token-comment: #9399b2; /* overlay2 */
- --diffs-token-string: #a6e3a1; /* green */
- --diffs-token-string-expression: #a6e3a1; /* green */
- --diffs-token-keyword: #cba6f7; /* mauve */
- --diffs-token-function: #89b4fa; /* blue */
- --diffs-token-constant: #fab387; /* peach */
- --diffs-token-parameter: #eba0ac; /* maroon */
- --diffs-token-punctuation: #9399b2; /* overlay2 */
- --diffs-token-link: #89b4fa; /* blue */
- --diffs-token-inserted: #a6e3a1; /* green */
- --diffs-token-deleted: #f38ba8; /* red */
- --diffs-token-changed: #f9e2af; /* yellow */
}
}