Skip to content

Commit 8ba46dc

Browse files
johanneswilmcaio-pizzol
authored andcommitted
feat: collapsed selection when switching list type - switch type of
entire list
1 parent abccf2c commit 8ba46dc

1 file changed

Lines changed: 121 additions & 13 deletions

File tree

packages/super-editor/src/core/commands/changeListNumberingType.js

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,78 @@ import { ListHelpers } from '@helpers/list-numbering-helpers.js';
44
import { updateNumberingProperties } from './changeListLevel.js';
55
import { 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

Comments
 (0)