From b0d55f34f79de89fc49de8a24abd185a627eab32 Mon Sep 17 00:00:00 2001 From: Daniel Pecos Martinez Date: Thu, 16 Apr 2026 13:29:47 +0200 Subject: [PATCH] fix: recognise ??? notes separator with trailing whitespace The notes separator regex required a newline or end-of-string immediately after ???, so any trailing spaces or tabs on the same line caused the separator to be silently ignored and the notes to be rendered as slide content instead. Added optional [ \t]* before the end anchor in the NOTES_SEPARATOR regex, consistent with remark's behaviour. Closes #1 --- src/__tests__/lexer.test.ts | 8 ++++++++ src/__tests__/parser.test.ts | 12 ++++++++++++ src/mdeck/lexer.ts | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/__tests__/lexer.test.ts b/src/__tests__/lexer.test.ts index c274ac5..315e119 100644 --- a/src/__tests__/lexer.test.ts +++ b/src/__tests__/lexer.test.ts @@ -54,6 +54,14 @@ describe('Lexer', () => { it('recognizes ??? notes separator', () => { expect(lexer.lex('\n???\n')).toEqual([{ type: 'notes_separator', text: '???' }]); }); + + it('recognizes ??? with trailing spaces (issue #1)', () => { + expect(lexer.lex('\n??? \n')).toEqual([{ type: 'notes_separator', text: '???' }]); + }); + + it('recognizes ??? with trailing tabs (issue #1)', () => { + expect(lexer.lex('\n???\t\n')).toEqual([{ type: 'notes_separator', text: '???' }]); + }); }); describe('code blocks', () => { diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index a8ca2b7..577b7c8 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -69,6 +69,18 @@ describe('Parser', () => { it('removes notes from slide content', () => { expect(parser.parse('content\n???\nnotes')[0].content).toEqual(['content']); }); + + it('parses notes after ??? with trailing spaces (issue #1)', () => { + const slide = parser.parse('content\n??? \nnotes')[0]; + expect(slide.notes).toEqual(['notes']); + expect(slide.content).toEqual(['content']); + }); + + it('parses notes after ??? with trailing tab (issue #1)', () => { + const slide = parser.parse('content\n???\t\nnotes')[0]; + expect(slide.notes).toEqual(['notes']); + expect(slide.content).toEqual(['content']); + }); }); describe('fenced code blocks', () => { diff --git a/src/mdeck/lexer.ts b/src/mdeck/lexer.ts index e6e4cc9..46cd67b 100644 --- a/src/mdeck/lexer.ts +++ b/src/mdeck/lexer.ts @@ -29,7 +29,7 @@ const regexByName: Record = { MACRO: /!\[:([^\] ]+)([^\]]*)\](?:\(([^\)]*)\))?/, SLIDE_SEPARATOR: /(?:^|\n)(---|)(?:\n|$)/, FRAGMENT_SEPARATOR: /(?:^|\n)(--)(?![^\n])/, - NOTES_SEPARATOR: /(?:^|\n)(\?{3})(?:\n|$)/, + NOTES_SEPARATOR: /(?:^|\n)(\?{3})[ \t]*(?:\n|$)/, }; function replace(regex: RegExp, replacements: Record): RegExp {