diff --git a/src/text/discordMarkdown.test.ts b/src/text/discordMarkdown.test.ts index d02dd44..7d74257 100644 --- a/src/text/discordMarkdown.test.ts +++ b/src/text/discordMarkdown.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { parseDiscordMarkdown, stripDiscordMarkdown } from './discordMarkdown' +import { segmentStyledText } from './segment' describe('stripDiscordMarkdown', () => { it('leaves plain text alone', () => { @@ -202,4 +203,44 @@ describe('parseDiscordMarkdown', () => { it('reveals a spoiler as plain, unstyled text', () => { expect(parseDiscordMarkdown('||spoiler||')).toEqual([{ value: 'spoiler' }]) }) + + it('keeps a custom emoji bracketed, not stripped to its bare shortcode:id', () => { + expect(parseDiscordMarkdown('<:blob_x:1245750752115167293>')).toEqual([ + { value: '<:blob_x:1245750752115167293>' }, + ]) + expect(parseDiscordMarkdown('')).toEqual([ + { value: '' }, + ]) + }) + + it('keeps a mention bracketed, not stripped to its bare id', () => { + expect(parseDiscordMarkdown('<@123456789012345678>')).toEqual([ + { value: '<@123456789012345678>' }, + ]) + expect(parseDiscordMarkdown('<@!123456789012345678>')).toEqual([ + { value: '<@!123456789012345678>' }, + ]) + expect(parseDiscordMarkdown('<@&123456789012345678>')).toEqual([ + { value: '<@&123456789012345678>' }, + ]) + expect(parseDiscordMarkdown('<#123456789012345678>')).toEqual([ + { value: '<#123456789012345678>' }, + ]) + }) + + it('keeps a custom emoji bracketed within stripDiscordMarkdown too', () => { + expect(stripDiscordMarkdown('<:blob_x:1245750752115167293> hi')).toBe( + '<:blob_x:1245750752115167293> hi', + ) + }) + + it('resolves to an actual emoji segment once fed through segmentStyledText, matching the raw path', () => { + const raw = segmentStyledText([{ value: '<:blob_x:1245750752115167293> hi' }]) + const viaDiscordMode = segmentStyledText( + parseDiscordMarkdown('<:blob_x:1245750752115167293> hi'), + ) + + expect(viaDiscordMode).toEqual(raw) + expect(viaDiscordMode[0]).toMatchObject({ kind: 'emoji', source: 'discord' }) + }) }) diff --git a/src/text/discordMarkdown.ts b/src/text/discordMarkdown.ts index ee0b175..fa9310a 100644 --- a/src/text/discordMarkdown.ts +++ b/src/text/discordMarkdown.ts @@ -29,12 +29,25 @@ const WRAPPER_ELEMENTS = new Set([ 'list', ]) +/** + * Element kinds whose delimiters are the whole point — `.content` strips exactly the `<`/`>` that later stages (`segment.ts`'s `splitDiscord()`, for `emoji`) need to recognize the construct, so `.raw` has to survive instead. `code`/`codeBlock` are the opposite: their fence is not meant to display, so `.content` (the body without it) is correct for those. + */ +const RAW_ELEMENTS = new Set([ + 'emoji', + 'mention', + 'globalMention', + 'roleMention', + 'gameMention', + 'channelMention', + 'slashCommand', +]) + function parseInto(text: string, style: TextStyle | undefined): StyledRun[] { const out: StyledRun[] = [] for (const token of parse(text)) { if (!WRAPPER_ELEMENTS.has(token.element)) { - pushStyledRun(out, token.content, style) + pushStyledRun(out, RAW_ELEMENTS.has(token.element) ? token.raw : token.content, style) continue } const addedStyle = styleFor(token.element)