Skip to content

Commit 5842a3a

Browse files
committed
chore: fix review comments
1 parent 41ab28a commit 5842a3a

4 files changed

Lines changed: 77 additions & 4 deletions

File tree

packages/super-editor/src/document-api-adapters/helpers/list-item-resolver.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,40 @@ describe('list-item-resolver', () => {
142142
expect(result.items[0]?.id).toBe('li-1');
143143
});
144144

145+
it('keeps listId stable across within scopes', () => {
146+
const editor = makeEditor([
147+
makeParagraph({
148+
id: 'li-1',
149+
text: 'First',
150+
numId: 1,
151+
ilvl: 0,
152+
markerText: '1.',
153+
path: [1],
154+
numberingType: 'decimal',
155+
}),
156+
makeParagraph({
157+
id: 'li-2',
158+
text: 'Second',
159+
numId: 1,
160+
ilvl: 0,
161+
markerText: '2.',
162+
path: [2],
163+
numberingType: 'decimal',
164+
}),
165+
]);
166+
167+
const unscoped = listListItems(editor);
168+
const scoped = listListItems(editor, {
169+
within: { kind: 'block', nodeType: 'listItem', nodeId: 'li-2' },
170+
});
171+
172+
const unscopedSecond = unscoped.items.find((item) => item.id === 'li-2');
173+
const scopedSecond = scoped.items.find((item) => item.id === 'li-2');
174+
175+
expect(unscopedSecond?.listId).toBe('1:li-1');
176+
expect(scopedSecond?.listId).toBe('1:li-1');
177+
});
178+
145179
it('throws TARGET_NOT_FOUND when resolving a stale list address', () => {
146180
const editor = makeEditor([
147181
makeParagraph({ id: 'li-1', numId: 1, markerText: '1.', path: [1], numberingType: 'decimal' }),

packages/super-editor/src/document-api-adapters/helpers/list-item-resolver.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,22 @@ export function listListItems(editor: Editor, query?: ListsListQuery): ListsList
192192

193193
const index = getBlockIndex(editor);
194194
const scope = resolveBlockScopeRange(index, query?.within as BlockNodeAddress | undefined);
195-
const candidates = listItemCandidatesInScope(index, scope);
195+
const allCandidates = index.candidates.filter((candidate) => candidate.nodeType === 'listItem');
196+
const allProjections = allCandidates.map((candidate) => projectListItemCandidate(editor, candidate));
197+
const projections = allProjections.filter((projection) => isWithinScope(projection.candidate, scope));
196198
const safeOffset = query?.offset ?? 0;
197199
const safeLimit = query?.limit ?? Number.POSITIVE_INFINITY;
198200
const pageEnd = safeOffset + safeLimit;
199201
const evaluatedRevision = getRevision(editor);
200202

201-
// Project all items first so sequence IDs can be computed in a single pass.
202-
const allProjections = candidates.map((c) => projectListItemCandidate(editor, c));
203+
// Compute sequence IDs from document-wide projections so list identity is
204+
// stable across scoped queries.
203205
const sequenceIds = computeSequenceIdMap(allProjections);
204206

205207
let total = 0;
206208
const items: ListsListResult['items'] = [];
207209

208-
for (const projection of allProjections) {
210+
for (const projection of projections) {
209211
if (!matchesListQuery(projection, query)) continue;
210212

211213
const currentIndex = total;

packages/super-editor/src/document-api-adapters/plan-engine/lists-wrappers.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,12 +637,28 @@ describe('lists-wrappers', () => {
637637
it('removes override when value is null', () => {
638638
const proj = makeProjection({ numId: 1, level: 0 });
639639
vi.mocked(resolveListItem).mockReturnValueOnce(proj);
640+
editor.converter.numbering.definitions[1] = {
641+
elements: [{ name: 'w:lvlOverride', attributes: { 'w:ilvl': '0' } }],
642+
};
640643

641644
const result = listsSetValueWrapper(editor, { target: proj.address, value: null });
642645
expect(result.success).toBe(true);
643646
expect(ListHelpers.removeLvlOverride).toHaveBeenCalledWith(editor, 1, 0);
644647
});
645648

649+
it('returns NO_OP when removing an absent override', () => {
650+
const proj = makeProjection({ numId: 1, level: 0 });
651+
vi.mocked(resolveListItem).mockReturnValueOnce(proj);
652+
editor.converter.numbering.definitions[1] = {
653+
elements: [{ name: 'w:abstractNumId', attributes: { 'w:val': '10' } }],
654+
};
655+
656+
const result = listsSetValueWrapper(editor, { target: proj.address, value: null });
657+
expect(result.success).toBe(false);
658+
expect((result as any).failure.code).toBe('NO_OP');
659+
expect(ListHelpers.removeLvlOverride).not.toHaveBeenCalled();
660+
});
661+
646662
it('returns dry-run result', () => {
647663
const proj = makeProjection({ numId: 1 });
648664
vi.mocked(resolveListItem).mockReturnValueOnce(proj);

packages/super-editor/src/document-api-adapters/plan-engine/lists-wrappers.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,23 @@ function withListTarget(editor: Editor, input: ListTargetInput): ListItemProject
132132
return resolveListItem(editor, input.target);
133133
}
134134

135+
function hasLevelOverride(editor: Editor, numId: number, level: number): boolean {
136+
const converter = editor as unknown as {
137+
converter?: {
138+
numbering?: {
139+
definitions?: Record<number, { elements?: Array<{ name?: string; attributes?: Record<string, unknown> }> }>;
140+
};
141+
};
142+
};
143+
const definition = converter.converter?.numbering?.definitions?.[numId];
144+
const ilvl = String(level);
145+
return (
146+
definition?.elements?.some(
147+
(element) => element.name === 'w:lvlOverride' && element.attributes?.['w:ilvl'] === ilvl,
148+
) ?? false
149+
);
150+
}
151+
135152
/**
136153
* Shared core of setLevel, indent, and outdent.
137154
* Validates preconditions and performs the level change.
@@ -723,6 +740,10 @@ export function listsSetValueWrapper(
723740

724741
// Remove override
725742
if (input.value === null) {
743+
if (!hasLevelOverride(editor, target.numId, level)) {
744+
return toListsFailure('NO_OP', 'No startOverride to remove.', { target: input.target });
745+
}
746+
726747
const receipt = executeDomainCommand(
727748
editor,
728749
() => {

0 commit comments

Comments
 (0)