Skip to content

Commit cd3b902

Browse files
committed
fix: address review findings for list numbering format UI
- Fix correctness bug: ranged selection now groups paragraphs by numId so separate lists stay separate instead of merging into one - Extract shared isOrderedListParagraph() and applyFormatToGroup() helpers - Remove duplicate setNumberedListIcon in defaultItems.js, use canonical superToolbar.updateNumberedListIcon() - Simplify numbering-icon-map.js to direct lookup with nullish coalescing - Simplify toggleList.js branching for format config
1 parent 8ba46dc commit cd3b902

4 files changed

Lines changed: 82 additions & 112 deletions

File tree

packages/super-editor/src/components/toolbar/defaultItems.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,7 @@ export const makeDefaultItems = ({
681681
// Store the selected type for future list creation
682682
superToolbar.selectedNumberingType = numberingType;
683683
// Update the numbered list button icon
684-
setNumberedListIcon(numberedList, numberingType);
684+
superToolbar.updateNumberedListIcon(numberedList, numberingType);
685685
// If we're in an active list, change its type
686686
const buttonWithCommand = { ...numberedListType, command: 'changeListNumberingType' };
687687
superToolbar.emitCommand({ item: buttonWithCommand, argument: numberingType });
@@ -699,23 +699,12 @@ export const makeDefaultItems = ({
699699
],
700700
onActivate: ({ numberingType }) => {
701701
if (numberingType) {
702-
// Update stored type when activating on an existing list
703702
superToolbar.selectedNumberingType = numberingType;
704-
setNumberedListIcon(numberedList, numberingType);
703+
superToolbar.updateNumberedListIcon(numberedList, numberingType);
705704
}
706705
},
707-
onDeactivate: () => {
708-
// Keep the selected type even when deactivated
709-
},
710706
});
711707

712-
const setNumberedListIcon = (button, numberingType) => {
713-
const icon = getNumberingIcon(numberingType);
714-
if (button.icon && button.icon.value !== icon) {
715-
button.icon.value = icon;
716-
}
717-
};
718-
719708
// indent left
720709
const indentLeft = useToolbarItem({
721710
type: 'button',

packages/super-editor/src/components/toolbar/numbering-icon-map.js

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,9 @@ import { numberingIcons } from './numbering-icons.js';
33

44
/**
55
* Get the icon for a given numbering format
6-
* Maps numbering format keys to their corresponding icons
76
* @param {string} numberingType - The numbering format key
87
* @returns {string} The SVG icon for the format
98
*/
109
export function getNumberingIcon(numberingType) {
11-
const iconMap = {
12-
decimalPlain: numberingIcons.decimalPlain,
13-
decimal: numberingIcons.decimal,
14-
decimalParen: numberingIcons.decimalParen,
15-
upperLetter: numberingIcons.upperLetter,
16-
lowerLetter: numberingIcons.lowerLetter,
17-
letterParen: numberingIcons.letterParen,
18-
upperRoman: numberingIcons.upperRoman,
19-
lowerRoman: numberingIcons.lowerRoman,
20-
};
21-
22-
return iconMap[numberingType] || numberingIcons.decimal;
10+
return numberingIcons[numberingType] ?? numberingIcons.decimal;
2311
}

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

Lines changed: 68 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ 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+
* 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';
1529
function 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);

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

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -93,23 +93,18 @@ export const toggleList =
9393

9494
if (mode === 'create') {
9595
const numId = ListHelpers.getNewListId(editor);
96+
const formatConfig = listType === 'orderedList' && numberingFormat ? getFormatConfig(numberingFormat) : null;
9697

97-
// Use the provided numbering format or default settings
98-
if (listType === 'orderedList' && numberingFormat) {
99-
const formatConfig = getFormatConfig(numberingFormat);
100-
if (formatConfig) {
101-
ListHelpers.generateNewListDefinition({
102-
numId: Number(numId),
103-
listType,
104-
level: '0',
105-
start: '1',
106-
text: formatConfig.lvlText,
107-
fmt: formatConfig.fmt,
108-
editor,
109-
});
110-
} else {
111-
ListHelpers.generateNewListDefinition({ numId: Number(numId), listType, editor });
112-
}
98+
if (formatConfig) {
99+
ListHelpers.generateNewListDefinition({
100+
numId: Number(numId),
101+
listType,
102+
level: '0',
103+
start: '1',
104+
text: formatConfig.lvlText,
105+
fmt: formatConfig.fmt,
106+
editor,
107+
});
113108
} else {
114109
ListHelpers.generateNewListDefinition({ numId: Number(numId), listType, editor });
115110
}

0 commit comments

Comments
 (0)