diff --git a/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx b/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx index 0cb0e25094f..659854876b8 100644 --- a/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx +++ b/src/renderer/src/components/sidebar/remark-disable-definitions.test.tsx @@ -22,6 +22,12 @@ describe('disableLinkDefinitions', () => { ['inside a bullet', '- [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], ['inside an ordered item', '1. [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], ['inside a blockquote', '> [Image #1]: /tmp/a.png', '[Image #1]: /tmp/a.png'], + // GFM adds a second definition construct that swallows prose the same way. + [ + 'as a footnote definition', + '[^note]: I meant this literally', + '[^note]: I meant this literally' + ], // A link label may span lines, so the swallow is not a single-line shape. [ 'with a wrapped label', @@ -49,6 +55,10 @@ describe('disableLinkDefinitions', () => { '~~~\ncode\n```\n[Image #1]: /tmp/a.png\n~~~', '| a | b |\n| - | - |\n| 1 | 2 |' ])('leaves content the pipeline already rendered untouched', (typed) => { + // Why the second assertion: comparing the two prop values to each other is + // trivially true if the plugin ever stops attaching, so pin that these + // render something before trusting that they render the same thing. + expect(rendered(typed, true)).not.toBe('') expect(rendered(typed, true)).toBe(rendered(typed, false)) }) diff --git a/src/renderer/src/components/sidebar/remark-disable-definitions.ts b/src/renderer/src/components/sidebar/remark-disable-definitions.ts index 67b1d46c9e0..b9fae1bf54d 100644 --- a/src/renderer/src/components/sidebar/remark-disable-definitions.ts +++ b/src/renderer/src/components/sidebar/remark-disable-definitions.ts @@ -1,19 +1,24 @@ import type { Processor } from 'unified' /** - * Stop CommonMark from reading `[label]: /some/target` as a link reference - * definition. + * Stop the parser from reading `[label]: /some/target` (and its GFM footnote + * sibling `[^label]: text`) as a definition. * - * Definitions render to nothing at all, so on content that is prose rather than - * authored Markdown — a chat turn the user typed — such a line silently deletes - * itself. Escaping the bracket in the source text only reaches lines at the top - * level; a definition inside a list item or blockquote, or one whose label wraps - * across lines, is still swallowed. Turning the construct off covers every - * container, and leaves inline links, reference *links*, and GFM intact. + * A definition renders to nothing at all, so on content that is prose rather + * than authored Markdown — a chat turn the user typed — such a line silently + * deletes itself. Escaping the bracket in the source text only reaches lines at + * the top level; a definition inside a list item or blockquote, or one whose + * label wraps across lines, is still swallowed. Turning the constructs off + * covers every container without rewriting a byte of what the user wrote. + * + * Inline links and the rest of GFM are untouched. Reference links necessarily + * render literally, because they resolve *against* the definitions this removes + * — which is the right reading for prose, where `[d]` is far more likely to be + * a bracket the user typed than a link they meant to define. */ export function remarkDisableDefinitions(this: Processor): undefined { const data = this.data() const extensions = (data.micromarkExtensions ??= []) - extensions.push({ disable: { null: ['definition'] } }) + extensions.push({ disable: { null: ['definition', 'gfmFootnoteDefinition'] } }) return undefined }