@@ -4,6 +4,78 @@ import { ListHelpers } from '@helpers/list-numbering-helpers.js';
44import { updateNumberingProperties } from './changeListLevel.js' ;
55import { getFormatConfig } from '@helpers/numbering-format-config.js' ;
66
7+ /**
8+ * Find all adjacent paragraphs that share the same numbering properties (numId and ilvl)
9+ * @param {import('prosemirror-model').Node } doc - The document
10+ * @param {number } startPos - The position to start searching from
11+ * @param {number } targetNumId - The numId to match
12+ * @param {number } targetIlvl - The ilvl to match
13+ * @returns {Array<{node: import('prosemirror-model').Node, pos: number, paraProps: any}> }
14+ */
15+ function findAdjacentListItems ( doc , startPos , targetNumId , targetIlvl ) {
16+ const matchingParagraphs = [ ] ;
17+
18+ // Helper to check if a paragraph matches our criteria
19+ const matchesTarget = ( node ) => {
20+ if ( node . type . name !== 'paragraph' ) return false ;
21+
22+ 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+
28+ const numId = paraProps . numberingProperties ?. numId ;
29+ const ilvl = paraProps . numberingProperties ?. ilvl ?? 0 ;
30+
31+ return numId === targetNumId && ilvl === targetIlvl ;
32+ } ;
33+
34+ // Collect all paragraphs in order, tracking their positions
35+ const allParagraphs = [ ] ;
36+ doc . descendants ( ( node , pos ) => {
37+ if ( node . type . name === 'paragraph' ) {
38+ allParagraphs . push ( { node, pos, paraProps : getResolvedParagraphProperties ( node ) } ) ;
39+ }
40+ } ) ;
41+
42+ // Find the index of the paragraph at startPos
43+ let startIndex = - 1 ;
44+ for ( let i = 0 ; i < allParagraphs . length ; i ++ ) {
45+ if ( allParagraphs [ i ] . pos === startPos ) {
46+ startIndex = i ;
47+ break ;
48+ }
49+ }
50+
51+ if ( startIndex === - 1 || ! matchesTarget ( allParagraphs [ startIndex ] . node ) ) {
52+ return matchingParagraphs ;
53+ }
54+
55+ // Add the starting paragraph
56+ matchingParagraphs . push ( allParagraphs [ startIndex ] ) ;
57+
58+ // Search backwards for consecutive matching paragraphs
59+ for ( let i = startIndex - 1 ; i >= 0 ; i -- ) {
60+ if ( matchesTarget ( allParagraphs [ i ] . node ) ) {
61+ matchingParagraphs . unshift ( allParagraphs [ i ] ) ;
62+ } else {
63+ break ; // Stop when we hit a non-matching paragraph
64+ }
65+ }
66+
67+ // Search forwards for consecutive matching paragraphs
68+ for ( let i = startIndex + 1 ; i < allParagraphs . length ; i ++ ) {
69+ if ( matchesTarget ( allParagraphs [ i ] . node ) ) {
70+ matchingParagraphs . push ( allParagraphs [ i ] ) ;
71+ } else {
72+ break ; // Stop when we hit a non-matching paragraph
73+ }
74+ }
75+
76+ return matchingParagraphs ;
77+ }
78+
779/**
880 * Change the numbering type of an ordered list
981 * @param {string } numberingFormat - The format to apply (decimal, lowerRoman, upperRoman, lowerLetter, upperLetter, etc.)
@@ -17,21 +89,57 @@ export const changeListNumberingType =
1789
1890 // Collect all paragraphs in selection that are part of an ordered list
1991 let paragraphsInSelection = [ ] ;
20- state . doc . nodesBetween ( from , to , ( node , pos ) => {
21- if ( node . type . name === 'paragraph' ) {
22- const paraProps = getResolvedParagraphProperties ( node ) ;
23- const isOrderedList =
24- paraProps . numberingProperties &&
25- node . attrs . listRendering &&
26- node . attrs . listRendering . numberingType !== 'bullet' ;
27-
28- if ( isOrderedList ) {
29- paragraphsInSelection . push ( { node, pos, paraProps } ) ;
92+
93+ // Check if selection is collapsed (cursor position)
94+ const isCollapsed = from === to ;
95+
96+ if ( isCollapsed ) {
97+ // Find the paragraph at cursor position
98+ let cursorParagraph = null ;
99+ let cursorPos = null ;
100+
101+ state . doc . nodesBetween ( from - 1 , from + 1 , ( node , pos ) => {
102+ if ( node . type . name === 'paragraph' && pos <= from && from <= pos + node . nodeSize ) {
103+ 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 } ;
111+ cursorPos = pos ;
112+ }
113+ return false ;
30114 }
31- return false ; // don't descend into paragraph content
115+ return true ;
116+ } ) ;
117+
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
123+ paragraphsInSelection = findAdjacentListItems ( state . doc , cursorPos , targetNumId , targetIlvl ) ;
32124 }
33- return true ;
34- } ) ;
125+ } else {
126+ // Non-collapsed selection: collect paragraphs within selection
127+ state . doc . nodesBetween ( from , to , ( node , pos ) => {
128+ if ( node . type . name === 'paragraph' ) {
129+ const paraProps = getResolvedParagraphProperties ( node ) ;
130+ const isOrderedList =
131+ paraProps . numberingProperties &&
132+ node . attrs . listRendering &&
133+ node . attrs . listRendering . numberingType !== 'bullet' ;
134+
135+ if ( isOrderedList ) {
136+ paragraphsInSelection . push ( { node, pos, paraProps } ) ;
137+ }
138+ return false ; // don't descend into paragraph content
139+ }
140+ return true ;
141+ } ) ;
142+ }
35143
36144 if ( paragraphsInSelection . length === 0 ) {
37145 return false ;
0 commit comments