From f6a23e0b0fb0dd25fcc76b8d237d884b27ef3638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Wed, 30 Sep 2026 15:12:12 +0200 Subject: [PATCH] Move comment preview text into lib The regex-heavy markdown stripping was untested in the component. --- src/components/ReviewCommentList.svelte | 24 ++-------------------- src/lib/markdownPlain.ts | 20 ++++++++++++++++++ tests/unit/markdownPlain.test.ts | 27 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 22 deletions(-) create mode 100644 src/lib/markdownPlain.ts create mode 100644 tests/unit/markdownPlain.test.ts diff --git a/src/components/ReviewCommentList.svelte b/src/components/ReviewCommentList.svelte index 4cbfa1c36..d2f10d29e 100644 --- a/src/components/ReviewCommentList.svelte +++ b/src/components/ReviewCommentList.svelte @@ -5,6 +5,7 @@ import type { Thread } from "@bindings/cob/thread/Thread"; import type { Resolution } from "@app/lib/commentResolutions"; + import { plainText } from "@app/lib/markdownPlain"; import type { CommentAnchor } from "@app/lib/pierreComments"; import { anchorOf, formatAnchorLines } from "@app/lib/pierreComments"; import { @@ -51,30 +52,9 @@ return author.alias ?? truncateId(authorKey(author)); } - /// Inline markdown, dropped down to the words it decorates. A row is a couple - /// of truncated lines, and on a narrow column the punctuation crowds out the - /// text that says what the comment is about. Deliberately shallow — this is a - /// preview, not a parser; the comment itself is one click away in the diff. - function plain(body: string): string { - return ( - body - .replace(/!?\[([^\]]*)\]\([^)]*\)/gu, "$1") - .replace(/`/gu, "") - .replace(/~~(.+?)~~/gu, "$1") - // Emphasis only where a marker sits on a word boundary, so `snake_case`, - // `MAX_SIZE` and `_private` survive being read as italics. - .replace(/(^|[^\w])[*_]{1,2}([^*_]+?)[*_]{1,2}(?=[^\w]|$)/gu, "$1$2") - .replace(/^\s{0,3}#{1,6}\s+/gmu, "") - .replace(/^\s{0,3}>\s?/gmu, "") - .replace(/^\s{0,3}(?:[-+*]|\d+\.)\s+/gmu, "") - .replace(/\s+/gu, " ") - .trim() - ); - } - /// The last edit is the current text. function preview(comment: Comment): string { - return plain(comment.edits.at(-1)?.body ?? ""); + return plainText(comment.edits.at(-1)?.body ?? ""); } diff --git a/src/lib/markdownPlain.ts b/src/lib/markdownPlain.ts new file mode 100644 index 000000000..7c587e465 --- /dev/null +++ b/src/lib/markdownPlain.ts @@ -0,0 +1,20 @@ +/// Inline markdown, dropped down to the words it decorates. A row is a couple +/// of truncated lines, and on a narrow column the punctuation crowds out the +/// text that says what the comment is about. Deliberately shallow — this is a +/// preview, not a parser; the comment itself is one click away in the diff. +export function plainText(body: string): string { + return ( + body + .replace(/!?\[([^\]]*)\]\([^)]*\)/gu, "$1") + .replace(/`/gu, "") + .replace(/~~(.+?)~~/gu, "$1") + // Emphasis only where a marker sits on a word boundary, so `snake_case`, + // `MAX_SIZE` and `_private` survive being read as italics. + .replace(/(^|[^\w])[*_]{1,2}([^*_]+?)[*_]{1,2}(?=[^\w]|$)/gu, "$1$2") + .replace(/^\s{0,3}#{1,6}\s+/gmu, "") + .replace(/^\s{0,3}>\s?/gmu, "") + .replace(/^\s{0,3}(?:[-+*]|\d+\.)\s+/gmu, "") + .replace(/\s+/gu, " ") + .trim() + ); +} diff --git a/tests/unit/markdownPlain.test.ts b/tests/unit/markdownPlain.test.ts new file mode 100644 index 000000000..8c6640b3c --- /dev/null +++ b/tests/unit/markdownPlain.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "vitest"; + +import { plainText } from "@app/lib/markdownPlain"; + +test.each([ + ["[the docs](https://example.com)", "the docs"], + ["![diagram](a.png) here", "diagram here"], + ["run `npm test` now", "run npm test now"], + ["~~old~~ new", "old new"], + ["**bold** and _italic_", "bold and italic"], + ["__strong__ and *em*", "strong and em"], + ["# Heading", "Heading"], + ["### Deep heading", "Deep heading"], + ["> quoted\n> text", "quoted text"], + ["- one\n* two\n+ three\n1. four", "one two three four"], + ["line one\n\n line two", "line one line two"], + [" padded ", "padded"], +])("%j reads as %j", (markdown, expected) => { + expect(plainText(markdown)).toBe(expected); +}); + +test.each(["snake_case_name", "MAX_SIZE", "_private", "a*b*c"])( + "leaves %j alone", + text => { + expect(plainText(text)).toBe(text); + }, +);