From df741bbfc00980b91c1fe3229175f9dda56c0237 Mon Sep 17 00:00:00 2001 From: Jeeves Date: Wed, 29 Jul 2026 02:29:36 -0700 Subject: [PATCH 1/5] feat: add plain list cycle command --- packages/core/src/extensions/list.test.ts | 46 +++++++++++++++++++++++ packages/core/src/extensions/list.ts | 16 ++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/core/src/extensions/list.test.ts b/packages/core/src/extensions/list.test.ts index 0076abf2..577e783d 100644 --- a/packages/core/src/extensions/list.test.ts +++ b/packages/core/src/extensions/list.test.ts @@ -120,6 +120,52 @@ describe('commands', () => { fixture.editor.commands.cycleCheckableList() expect(docToMarkdown(fixture.doc)).toBe('+ [ ] outer\n - [ ] inner\n') }) + + it('cyclePlainList cycles plain content through bullet, ordered, and text', () => { + using fixture = setupFixture() + const { n } = fixture + fixture.set(n.doc(n.paragraph('todo'))) + + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc)).toBe('- todo\n') + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc)).toBe('1. todo\n') + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc)).toBe('todo\n') + }) + + it('cyclePlainList converts a task to a plain bullet first', () => { + using fixture = setupFixture() + const { n } = fixture + fixture.set(n.doc(n.list({ kind: 'task', marker: '+', checked: true }, n.paragraph('done')))) + + fixture.editor.commands.cyclePlainList() + + expect(fixture.doc.child(0).attrs).toMatchObject({ + kind: 'bullet', + checked: false, + }) + expect(docToMarkdown(fixture.doc)).toBe('- done\n') + }) + + it('cyclePlainList changes only the closest nested list', () => { + using fixture = setupFixture() + const { n } = fixture + fixture.set( + n.doc( + n.list( + { kind: 'bullet' }, + n.paragraph('outer'), + n.list({ kind: 'bullet' }, n.paragraph('inner')), + ), + ), + ) + + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc)).toBe('- outer\n 1. inner\n') + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc)).toBe('- outer\n\n inner\n') + }) }) describe('keymap', () => { diff --git a/packages/core/src/extensions/list.ts b/packages/core/src/extensions/list.ts index 14ae3f2d..38907d25 100644 --- a/packages/core/src/extensions/list.ts +++ b/packages/core/src/extensions/list.ts @@ -316,9 +316,25 @@ function cycleCheckableList(): Command { } } +/** + * Cycle the selected block between plain list states. Non-plain-list content + * becomes a bullet; bullets become ordered lists; ordered lists unwrap. + */ +function cyclePlainList(): Command { + return (state, dispatch, view) => { + const attrs = getListAttrsAtSelection(state) + const next: MeowdownListAttrs = + attrs?.kind === 'bullet' || attrs?.kind === 'ordered' + ? { kind: 'ordered', marker: null, checked: false, collapsed: false } + : { kind: 'bullet', marker: null, checked: false, collapsed: false } + return toggleList(next)(state, dispatch, view) + } +} + function defineTaskCommands() { return defineCommands({ cycleCheckableList, + cyclePlainList, wrapInCircleTask, wrapInSquareTask, }) From ddc1ecec2052eb7ba048500d378cc029e7bf0b97 Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 30 Jul 2026 12:47:12 +1000 Subject: [PATCH 2/5] update test --- packages/core/src/extensions/list.test.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/src/extensions/list.test.ts b/packages/core/src/extensions/list.test.ts index 577e783d..8ecbf626 100644 --- a/packages/core/src/extensions/list.test.ts +++ b/packages/core/src/extensions/list.test.ts @@ -126,12 +126,15 @@ describe('commands', () => { const { n } = fixture fixture.set(n.doc(n.paragraph('todo'))) + expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"todo"`) fixture.editor.commands.cyclePlainList() - expect(docToMarkdown(fixture.doc)).toBe('- todo\n') + expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"- todo"`) fixture.editor.commands.cyclePlainList() - expect(docToMarkdown(fixture.doc)).toBe('1. todo\n') + expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"1. todo"`) fixture.editor.commands.cyclePlainList() - expect(docToMarkdown(fixture.doc)).toBe('todo\n') + expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"todo"`) + fixture.editor.commands.cyclePlainList() + expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"- todo"`) }) it('cyclePlainList converts a task to a plain bullet first', () => { From f04f181f2f7d764c62bffaf28fcd999e2e5cf182 Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 30 Jul 2026 12:56:44 +1000 Subject: [PATCH 3/5] fix --- packages/core/src/extensions/list.test.ts | 20 ++++++++++---------- packages/core/src/extensions/list.ts | 19 +++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/core/src/extensions/list.test.ts b/packages/core/src/extensions/list.test.ts index 8ecbf626..c6f405d6 100644 --- a/packages/core/src/extensions/list.test.ts +++ b/packages/core/src/extensions/list.test.ts @@ -121,28 +121,28 @@ describe('commands', () => { expect(docToMarkdown(fixture.doc)).toBe('+ [ ] outer\n - [ ] inner\n') }) - it('cyclePlainList cycles plain content through bullet, ordered, and text', () => { + it('cycleOrderedList cycles plain content through bullet, ordered, and text', () => { using fixture = setupFixture() const { n } = fixture fixture.set(n.doc(n.paragraph('todo'))) expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"todo"`) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"- todo"`) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"1. todo"`) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"todo"`) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc).trim()).toMatchInlineSnapshot(`"- todo"`) }) - it('cyclePlainList converts a task to a plain bullet first', () => { + it('cycleOrderedList converts a task to a plain bullet first', () => { using fixture = setupFixture() const { n } = fixture fixture.set(n.doc(n.list({ kind: 'task', marker: '+', checked: true }, n.paragraph('done')))) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(fixture.doc.child(0).attrs).toMatchObject({ kind: 'bullet', @@ -151,7 +151,7 @@ describe('commands', () => { expect(docToMarkdown(fixture.doc)).toBe('- done\n') }) - it('cyclePlainList changes only the closest nested list', () => { + it('cycleOrderedList changes only the closest nested list', () => { using fixture = setupFixture() const { n } = fixture fixture.set( @@ -164,9 +164,9 @@ describe('commands', () => { ), ) - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc)).toBe('- outer\n 1. inner\n') - fixture.editor.commands.cyclePlainList() + fixture.editor.commands.cycleOrderedList() expect(docToMarkdown(fixture.doc)).toBe('- outer\n\n inner\n') }) }) diff --git a/packages/core/src/extensions/list.ts b/packages/core/src/extensions/list.ts index 38907d25..1dcb9db1 100644 --- a/packages/core/src/extensions/list.ts +++ b/packages/core/src/extensions/list.ts @@ -20,8 +20,8 @@ import { type ListAttrs, } from '@prosekit/extensions/list' import type { ProseMirrorNode } from '@prosekit/pm/model' -import { Plugin } from '@prosekit/pm/state' import type { Command, EditorState } from '@prosekit/pm/state' +import { Plugin } from '@prosekit/pm/state' import { createListRenderingPlugin, createSafariInputMethodWorkaroundPlugin, @@ -300,7 +300,11 @@ function getListAttrsAtSelection(state: EditorState): MeowdownListAttrs | null { } /** - * Cycle the selected block between square and circle checkbox tasks. Non-task + * Cycle the selected block between square and circle checkbox tasks. + * + * - A square task becomes a circle task; + * - A circle task becomes a square task; + * - Other content becomes a square task. * content becomes an unchecked square task; shape changes preserve checked state. */ function cycleCheckableList(): Command { @@ -317,10 +321,13 @@ function cycleCheckableList(): Command { } /** - * Cycle the selected block between plain list states. Non-plain-list content - * becomes a bullet; bullets become ordered lists; ordered lists unwrap. + * Cycle the selected block between bullet and ordered list. + * + * - A bullet list become a ordered list; + * - An ordered list unwrap; + * - Other content becomes a bullet list; */ -function cyclePlainList(): Command { +function cycleOrderedList(): Command { return (state, dispatch, view) => { const attrs = getListAttrsAtSelection(state) const next: MeowdownListAttrs = @@ -334,7 +341,7 @@ function cyclePlainList(): Command { function defineTaskCommands() { return defineCommands({ cycleCheckableList, - cyclePlainList, + cycleOrderedList, wrapInCircleTask, wrapInSquareTask, }) From f094c3640fc482e1bde71a53fa7457900aa216b2 Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 30 Jul 2026 13:09:53 +1000 Subject: [PATCH 4/5] fix --- packages/core/src/extensions/list.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/core/src/extensions/list.ts b/packages/core/src/extensions/list.ts index 1dcb9db1..eeb4f631 100644 --- a/packages/core/src/extensions/list.ts +++ b/packages/core/src/extensions/list.ts @@ -338,12 +338,17 @@ function cycleOrderedList(): Command { } } -function defineTaskCommands() { +function toggleListCollapsed(): Command { + return createToggleCollapsedCommand({ isToggleable: isCollapsibleBullet }) +} + +function defineMeowdownListCommands() { return defineCommands({ cycleCheckableList, cycleOrderedList, wrapInCircleTask, wrapInSquareTask, + toggleListCollapsed, }) } @@ -436,12 +441,6 @@ function defineMeowdownListPlugins(): PlainExtension { ]) } -function defineCollapseCommands() { - return defineCommands({ - toggleListCollapsed: () => createToggleCollapsedCommand({ isToggleable: isCollapsibleBullet }), - }) -} - function defineMeowdownListKeymap(): PlainExtension { return defineKeymap({ 'Mod-Enter': rotateSquareTask(), @@ -473,7 +472,6 @@ export function defineMeowdownList() { defineListMarkerAttr(), defineListTaskMarkerAttr(), defineListMarkerGapAttr(), - defineTaskCommands(), - defineCollapseCommands(), + defineMeowdownListCommands(), ) } From c9026b4673e5a9142593c61bc92b2e10e4296a7f Mon Sep 17 00:00:00 2001 From: ocavue Date: Thu, 30 Jul 2026 13:12:02 +1000 Subject: [PATCH 5/5] =?UTF-8?q?=C3=8Ffix=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/extensions/list.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/core/src/extensions/list.ts b/packages/core/src/extensions/list.ts index eeb4f631..75698079 100644 --- a/packages/core/src/extensions/list.ts +++ b/packages/core/src/extensions/list.ts @@ -302,10 +302,9 @@ function getListAttrsAtSelection(state: EditorState): MeowdownListAttrs | null { /** * Cycle the selected block between square and circle checkbox tasks. * - * - A square task becomes a circle task; - * - A circle task becomes a square task; - * - Other content becomes a square task. - * content becomes an unchecked square task; shape changes preserve checked state. + * - A square task becomes a circle task, keeping its checked state; + * - A circle task becomes a square task, keeping its checked state; + * - Other content becomes an unchecked square task. */ function cycleCheckableList(): Command { return (state, dispatch, view) => { @@ -321,11 +320,11 @@ function cycleCheckableList(): Command { } /** - * Cycle the selected block between bullet and ordered list. + * Cycle the selected block between a bullet list, an ordered list, and no list. * - * - A bullet list become a ordered list; - * - An ordered list unwrap; - * - Other content becomes a bullet list; + * - A bullet list becomes an ordered list; + * - An ordered list unwraps; + * - Other content, including a task, becomes a bullet list. */ function cycleOrderedList(): Command { return (state, dispatch, view) => {