diff --git a/src/app/utils/kumaStatus.test.ts b/src/app/utils/kumaStatus.test.ts index ad08f850c..93088a411 100644 --- a/src/app/utils/kumaStatus.test.ts +++ b/src/app/utils/kumaStatus.test.ts @@ -1,6 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { + plain, DEFAULT_GROUPS, kumaBeatTime, kumaUrls, @@ -260,3 +261,27 @@ test('a dismissed calls-down banner lets the next one through, never hides serve 'server-down', ); }); + +test('plain: strips Markdown syntax only, keeps ordinary #, _, > and * (#308)', () => { + assert.equal( + plain('join #lotus-general or #announcements:lotusguild.org'), + 'join #lotus-general or #announcements:lotusguild.org', + ); + assert.equal( + plain('ping @some_user about snake_case and C#'), + 'ping @some_user about snake_case and C#', + ); + assert.equal( + plain('calls -> fixed, > 5 people, 5 * 3 = 15'), + 'calls -> fixed, > 5 people, 5 * 3 = 15', + ); + assert.equal( + plain('# Heading\n* item one\n- item two\n> quoted'), + 'Heading\nitem one\nitem two\nquoted', + ); + assert.equal(plain('**bold**, *em*, _em_, `/sfu/get`'), 'bold, em, em, /sfu/get'); + assert.equal( + plain('Read the guide [docs](https://x.y)'), + 'Read the guide docs', + ); +}); diff --git a/src/app/utils/kumaStatus.ts b/src/app/utils/kumaStatus.ts index 4b4b71837..7e23274c2 100644 --- a/src/app/utils/kumaStatus.ts +++ b/src/app/utils/kumaStatus.ts @@ -240,12 +240,25 @@ const incidentTone = (style: string): BannerTone => { }; /** Markdown-ish incident text as plain text for the Details line. */ -const plain = (s: string): string => +/** + * [Gitea #308] Markdown → plain text for the Details line. Strips only the + * syntax: removing every `*_\`#>` also broke "#lotus-general", "snake_case", + * "->" and "5 * 3". HTML tags are dropped (React escapes the text anyway). + */ +export const plain = (s: string): string => s .replace(/!\[[^\]]*\]\([^)]*\)/g, '') .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') - .replace(/[*_`#>]/g, '') - .replace(/\s+\n/g, '\n') + .replace(/<\/?[a-z][^>]*>/gi, '') + .replace(/^[ \t]{0,3}#{1,6}[ \t]+/gm, '') + .replace(/^[ \t]*>[ \t]?/gm, '') + .replace(/^[ \t]*[*+-][ \t]+/gm, '') + .replace(/`([^`\n]+)`/g, '$1') + .replace(/\*\*(?=\S)([^*\n]*?\S)\*\*/g, '$1') + .replace(/__(?=\S)([^_\n]*?\S)__/g, '$1') + .replace(/(^|[^\w*])\*(?=\S)([^*\n]*?\S)\*(?![\w*])/g, '$1$2') + .replace(/(^|[^\w])_(?=\S)([^_\n]*?\S)_(?!\w)/g, '$1$2') + .replace(/[ \t]+\n/g, '\n') .trim(); /**