From 377a77de605831472e6f6381e36233546ea88a67 Mon Sep 17 00:00:00 2001 From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:37:52 +0530 Subject: [PATCH 1/2] fix: keep formatting out of unselected lines --- resources/js/lib/markdown-format.ts | 4 +++- tests/Unit/MarkdownFormat.test.ts | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/resources/js/lib/markdown-format.ts b/resources/js/lib/markdown-format.ts index b193d03df..c0d3e7f84 100644 --- a/resources/js/lib/markdown-format.ts +++ b/resources/js/lib/markdown-format.ts @@ -73,7 +73,9 @@ export function formatMarkdown( const prefix = PREFIXES[format] ?? (() => ''); const from = value.lastIndexOf('\n', start - 1) + 1; - const lineEnd = value.indexOf('\n', end); + // A selection ending just after a newline does not include the next line. + const lastSelected = end > start && value[end - 1] === '\n' ? end - 1 : end; + const lineEnd = value.indexOf('\n', lastSelected); const to = lineEnd === -1 ? value.length : lineEnd; const insert = value .slice(from, to) diff --git a/tests/Unit/MarkdownFormat.test.ts b/tests/Unit/MarkdownFormat.test.ts index b6e0fb6bf..93454bb80 100644 --- a/tests/Unit/MarkdownFormat.test.ts +++ b/tests/Unit/MarkdownFormat.test.ts @@ -35,3 +35,14 @@ await test('prefixes every line the selection touches', () => { assert.deepEqual(apply('What shipped', 3, 3, 'heading'), ['## What shipped', '']); assert.deepEqual(apply('a\nquote', 2, 2, 'quote'), ['a\n> quote', '']); }); + +await test('does not prefix the next line when selection ends after a newline', () => { + assert.deepEqual(apply('first\nsecond', 0, 6, 'bulleted'), [ + '- first\nsecond', + '', + ]); + assert.deepEqual(apply('first\nsecond\nthird', 0, 13, 'numbered'), [ + '1. first\n2. second\nthird', + '', + ]); +}); From 59c9b10165009caa0ff8bdcfba5d6787dc7ff3bb Mon Sep 17 00:00:00 2001 From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:37:56 +0530 Subject: [PATCH 2/2] fix: handle selected first-line newline in markdown formatting --- resources/js/lib/markdown-format.ts | 2 +- tests/Unit/MarkdownFormat.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/resources/js/lib/markdown-format.ts b/resources/js/lib/markdown-format.ts index c0d3e7f84..fb7bfa962 100644 --- a/resources/js/lib/markdown-format.ts +++ b/resources/js/lib/markdown-format.ts @@ -72,7 +72,7 @@ export function formatMarkdown( } const prefix = PREFIXES[format] ?? (() => ''); - const from = value.lastIndexOf('\n', start - 1) + 1; + const from = start === 0 ? 0 : value.lastIndexOf('\n', start - 1) + 1; // A selection ending just after a newline does not include the next line. const lastSelected = end > start && value[end - 1] === '\n' ? end - 1 : end; const lineEnd = value.indexOf('\n', lastSelected); diff --git a/tests/Unit/MarkdownFormat.test.ts b/tests/Unit/MarkdownFormat.test.ts index 93454bb80..77e3cbffb 100644 --- a/tests/Unit/MarkdownFormat.test.ts +++ b/tests/Unit/MarkdownFormat.test.ts @@ -46,3 +46,10 @@ await test('does not prefix the next line when selection ends after a newline', '', ]); }); + +void test('formats a selected first-line newline without an invalid range', () => { + assert.deepEqual(apply('\nsecond', 0, 1, 'bulleted'), [ + '- \nsecond', + '', + ]); +});