@@ -4,6 +4,20 @@ import { ListHelpers } from '@helpers/list-numbering-helpers.js';
44import { updateNumberingProperties } from './changeListLevel.js' ;
55import { getFormatConfig } from '@helpers/numbering-format-config.js' ;
66
7+ /**
8+ * Check if a paragraph node is an ordered list item
9+ * @param {import('prosemirror-model').Node } node
10+ * @param {object } paraProps - Resolved paragraph properties
11+ * @returns {boolean }
12+ */
13+ function isOrderedListParagraph ( node , paraProps ) {
14+ return ! ! (
15+ paraProps . numberingProperties &&
16+ node . attrs . listRendering &&
17+ node . attrs . listRendering . numberingType !== 'bullet'
18+ ) ;
19+ }
20+
721/**
822 * Find all adjacent paragraphs that share the same numbering properties (numId and ilvl)
923 * @param {import('prosemirror-model').Node } doc - The document
@@ -15,31 +29,22 @@ import { getFormatConfig } from '@helpers/numbering-format-config.js';
1529function findAdjacentListItems ( doc , startPos , targetNumId , targetIlvl ) {
1630 const matchingParagraphs = [ ] ;
1731
18- // Helper to check if a paragraph matches our criteria
1932 const matchesTarget = ( node ) => {
2033 if ( node . type . name !== 'paragraph' ) return false ;
21-
2234 const paraProps = getResolvedParagraphProperties ( node ) ;
23- const isOrderedList =
24- paraProps . numberingProperties && node . attrs . listRendering && node . attrs . listRendering . numberingType !== 'bullet' ;
25-
26- if ( ! isOrderedList ) return false ;
27-
35+ if ( ! isOrderedListParagraph ( node , paraProps ) ) return false ;
2836 const numId = paraProps . numberingProperties ?. numId ;
2937 const ilvl = paraProps . numberingProperties ?. ilvl ?? 0 ;
30-
3138 return numId === targetNumId && ilvl === targetIlvl ;
3239 } ;
3340
34- // Collect all paragraphs in order, tracking their positions
3541 const allParagraphs = [ ] ;
3642 doc . descendants ( ( node , pos ) => {
3743 if ( node . type . name === 'paragraph' ) {
3844 allParagraphs . push ( { node, pos, paraProps : getResolvedParagraphProperties ( node ) } ) ;
3945 }
4046 } ) ;
4147
42- // Find the index of the paragraph at startPos
4348 let startIndex = - 1 ;
4449 for ( let i = 0 ; i < allParagraphs . length ; i ++ ) {
4550 if ( allParagraphs [ i ] . pos === startPos ) {
@@ -52,30 +57,55 @@ function findAdjacentListItems(doc, startPos, targetNumId, targetIlvl) {
5257 return matchingParagraphs ;
5358 }
5459
55- // Add the starting paragraph
5660 matchingParagraphs . push ( allParagraphs [ startIndex ] ) ;
5761
58- // Search backwards for consecutive matching paragraphs
5962 for ( let i = startIndex - 1 ; i >= 0 ; i -- ) {
6063 if ( matchesTarget ( allParagraphs [ i ] . node ) ) {
6164 matchingParagraphs . unshift ( allParagraphs [ i ] ) ;
6265 } else {
63- break ; // Stop when we hit a non-matching paragraph
66+ break ;
6467 }
6568 }
6669
67- // Search forwards for consecutive matching paragraphs
6870 for ( let i = startIndex + 1 ; i < allParagraphs . length ; i ++ ) {
6971 if ( matchesTarget ( allParagraphs [ i ] . node ) ) {
7072 matchingParagraphs . push ( allParagraphs [ i ] ) ;
7173 } else {
72- break ; // Stop when we hit a non-matching paragraph
74+ break ;
7375 }
7476 }
7577
7678 return matchingParagraphs ;
7779}
7880
81+ /**
82+ * Apply a numbering format change to a group of paragraphs sharing the same numId
83+ * @param {Array<{node: any, pos: number, paraProps: any}> } paragraphs
84+ * @param {object } formatConfig - { fmt, lvlText }
85+ * @param {object } editor
86+ * @param {object } tr - ProseMirror transaction
87+ */
88+ function applyFormatToGroup ( paragraphs , formatConfig , editor , tr ) {
89+ const firstItem = paragraphs [ 0 ] ;
90+ const existingIlvl = firstItem . paraProps . numberingProperties ?. ilvl ?? 0 ;
91+
92+ const newNumId = ListHelpers . getNewListId ( editor ) ;
93+ ListHelpers . generateNewListDefinition ( {
94+ numId : Number ( newNumId ) ,
95+ listType : 'orderedList' ,
96+ level : String ( existingIlvl ) ,
97+ start : '1' ,
98+ text : formatConfig . lvlText ,
99+ fmt : formatConfig . fmt ,
100+ editor,
101+ } ) ;
102+
103+ for ( const { node, pos, paraProps } of paragraphs ) {
104+ const currentIlvl = paraProps . numberingProperties ?. ilvl ?? 0 ;
105+ updateNumberingProperties ( { numId : Number ( newNumId ) , ilvl : currentIlvl } , node , pos , editor , tr ) ;
106+ }
107+ }
108+
79109/**
80110 * Change the numbering type of an ordered list
81111 * @param {string } numberingFormat - The format to apply (decimal, lowerRoman, upperRoman, lowerLetter, upperLetter, etc.)
@@ -87,55 +117,39 @@ export const changeListNumberingType =
87117 const { selection } = state ;
88118 const { from, to } = selection ;
89119
90- // Collect all paragraphs in selection that are part of an ordered list
91120 let paragraphsInSelection = [ ] ;
92-
93- // Check if selection is collapsed (cursor position)
94121 const isCollapsed = from === to ;
95122
96123 if ( isCollapsed ) {
97- // Find the paragraph at cursor position
98- let cursorParagraph = null ;
99124 let cursorPos = null ;
100125
101126 state . doc . nodesBetween ( from - 1 , from + 1 , ( node , pos ) => {
102127 if ( node . type . name === 'paragraph' && pos <= from && from <= pos + node . nodeSize ) {
103128 const paraProps = getResolvedParagraphProperties ( node ) ;
104- const isOrderedList =
105- paraProps . numberingProperties &&
106- node . attrs . listRendering &&
107- node . attrs . listRendering . numberingType !== 'bullet' ;
108-
109- if ( isOrderedList ) {
110- cursorParagraph = { node, pos, paraProps } ;
129+ if ( isOrderedListParagraph ( node , paraProps ) ) {
111130 cursorPos = pos ;
112131 }
113132 return false ;
114133 }
115134 return true ;
116135 } ) ;
117136
118- if ( cursorParagraph ) {
119- const targetNumId = cursorParagraph . paraProps . numberingProperties ?. numId ;
120- const targetIlvl = cursorParagraph . paraProps . numberingProperties ?. ilvl ?? 0 ;
121-
122- // Find all adjacent paragraphs with the same numbering properties
137+ if ( cursorPos != null ) {
138+ const $pos = state . doc . resolve ( cursorPos ) ;
139+ const node = $pos . nodeAfter ;
140+ const paraProps = getResolvedParagraphProperties ( node ) ;
141+ const targetNumId = paraProps . numberingProperties ?. numId ;
142+ const targetIlvl = paraProps . numberingProperties ?. ilvl ?? 0 ;
123143 paragraphsInSelection = findAdjacentListItems ( state . doc , cursorPos , targetNumId , targetIlvl ) ;
124144 }
125145 } else {
126- // Non-collapsed selection: collect paragraphs within selection
127146 state . doc . nodesBetween ( from , to , ( node , pos ) => {
128147 if ( node . type . name === 'paragraph' ) {
129148 const paraProps = getResolvedParagraphProperties ( node ) ;
130- const isOrderedList =
131- paraProps . numberingProperties &&
132- node . attrs . listRendering &&
133- node . attrs . listRendering . numberingType !== 'bullet' ;
134-
135- if ( isOrderedList ) {
149+ if ( isOrderedListParagraph ( node , paraProps ) ) {
136150 paragraphsInSelection . push ( { node, pos, paraProps } ) ;
137151 }
138- return false ; // don't descend into paragraph content
152+ return false ;
139153 }
140154 return true ;
141155 } ) ;
@@ -145,42 +159,26 @@ export const changeListNumberingType =
145159 return false ;
146160 }
147161
148- // Get the numId from the first list item to determine if we need to create a new definition
149- const firstListItem = paragraphsInSelection [ 0 ] ;
150- const existingNumId = firstListItem . paraProps . numberingProperties ?. numId ;
151- const existingIlvl = firstListItem . paraProps . numberingProperties ?. ilvl ?? 0 ;
152-
153- if ( ! existingNumId ) {
162+ const formatConfig = getFormatConfig ( numberingFormat ) ;
163+ if ( ! formatConfig ) {
154164 return false ;
155165 }
156166
157- // Map numbering format to Word's numFmt and lvlText
158- const formatConfig = getFormatConfig ( numberingFormat ) ;
159- if ( ! formatConfig ) {
167+ // Group paragraphs by numId so separate lists stay separate
168+ const groups = new Map ( ) ;
169+ for ( const item of paragraphsInSelection ) {
170+ const numId = item . paraProps . numberingProperties ?. numId ;
171+ if ( ! numId ) continue ;
172+ if ( ! groups . has ( numId ) ) groups . set ( numId , [ ] ) ;
173+ groups . get ( numId ) . push ( item ) ;
174+ }
175+
176+ if ( groups . size === 0 ) {
160177 return false ;
161178 }
162179
163- // Create a new list definition with the new numbering format
164- const newNumId = ListHelpers . getNewListId ( editor ) ;
165- ListHelpers . generateNewListDefinition ( {
166- numId : Number ( newNumId ) ,
167- listType : 'orderedList' ,
168- level : String ( existingIlvl ) ,
169- start : '1' ,
170- text : formatConfig . lvlText ,
171- fmt : formatConfig . fmt ,
172- editor,
173- } ) ;
174-
175- // Apply the new numbering properties to all selected list items
176- for ( const { node, pos, paraProps } of paragraphsInSelection ) {
177- const currentIlvl = paraProps . numberingProperties ?. ilvl ?? 0 ;
178- const newNumberingProps = {
179- numId : Number ( newNumId ) ,
180- ilvl : currentIlvl ,
181- } ;
182-
183- updateNumberingProperties ( newNumberingProps , node , pos , editor , tr ) ;
180+ for ( const group of groups . values ( ) ) {
181+ applyFormatToGroup ( group , formatConfig , editor , tr ) ;
184182 }
185183
186184 if ( dispatch ) dispatch ( tr ) ;
0 commit comments