Skip to content

Commit 008e1c8

Browse files
committed
temp 3
1 parent 707f987 commit 008e1c8

File tree

1 file changed

+83
-76
lines changed

1 file changed

+83
-76
lines changed

src/components/text-editor/prosemirror-adapter/menu/menu-commands.ts

Lines changed: 83 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
/* eslint-disable no-console */
22
import { toggleMark, setBlockType, wrapIn, lift } from 'prosemirror-commands';
3-
import {
4-
Schema,
5-
MarkType,
6-
NodeType,
7-
Attrs,
8-
Node,
9-
Fragment,
10-
} from 'prosemirror-model';
11-
3+
import { Schema, MarkType, NodeType, Attrs, Fragment } from 'prosemirror-model';
4+
import { wrapInList } from 'prosemirror-schema-list';
125
import { findWrapping, liftTarget } from 'prosemirror-transform';
136
import {
147
Command,
@@ -340,40 +333,6 @@ const removeListNodes = (state, targetType, schema, dispatch) => {
340333
return changed;
341334
};
342335

343-
const toggleList = (listType) => {
344-
return (state, dispatch) => {
345-
const { $from, $to } = state.selection;
346-
const range = $from.blockRange($to);
347-
348-
if (!range) {
349-
return false;
350-
}
351-
352-
const wrapping = range && findWrapping(range, listType);
353-
354-
if (wrapping) {
355-
// Wrap the selection in a list
356-
if (dispatch) {
357-
dispatch(state.tr.wrap(range, wrapping).scrollIntoView());
358-
}
359-
360-
return true;
361-
} else {
362-
// Check if we are in a list item and lift out of the list
363-
const liftRange = range && liftTarget(range);
364-
if (liftRange !== null) {
365-
if (dispatch) {
366-
dispatch(state.tr.lift(range, liftRange).scrollIntoView());
367-
}
368-
369-
return true;
370-
}
371-
372-
return false;
373-
}
374-
};
375-
};
376-
377336
const isInListOfType = (state: EditorState, listType: NodeType): boolean => {
378337
const { $from } = state.selection;
379338
for (let depth = $from.depth; depth > 0; depth--) {
@@ -395,12 +354,10 @@ const LIST_TYPES = [
395354
type ListType = (typeof LIST_TYPES)[number];
396355

397356
const getOtherListType = (schema: Schema, currentType: string): NodeType => {
398-
// Validate current type is a valid list type
399357
if (!LIST_TYPES.includes(currentType as ListType)) {
400358
console.error(`Invalid list type: ${currentType}`);
401359
}
402360

403-
// Find the other list type
404361
const otherType = LIST_TYPES.find((type) => type !== currentType);
405362

406363
if (!otherType || !schema.nodes[otherType]) {
@@ -410,13 +367,48 @@ const getOtherListType = (schema: Schema, currentType: string): NodeType => {
410367
return schema.nodes[otherType];
411368
};
412369

370+
const fromOrderedToBulletList = (fromType: NodeType, toType: NodeType) => {
371+
return (
372+
fromType.name === EditorMenuTypes.OrderedList &&
373+
toType.name === EditorMenuTypes.BulletList
374+
);
375+
};
376+
377+
const fromBulletToOrderedList = (fromType: NodeType, toType: NodeType) => {
378+
return (
379+
fromType.name === EditorMenuTypes.BulletList &&
380+
toType.name === EditorMenuTypes.OrderedList
381+
);
382+
};
383+
384+
/**
385+
* Returns the converted attributes for a list node when converting from one type to another.
386+
*
387+
* @param NodeType - fromType - The current list type.
388+
* @param NodeType - toType - The target list type.
389+
* @param Object - attrs - The current attributes.
390+
* @returns Object - The updated attributes.
391+
*/
392+
const convertAttributes = (fromType, toType, attrs) => {
393+
const newAttrs = { ...attrs };
394+
if (fromOrderedToBulletList(fromType, toType)) {
395+
// Bullet lists generally do not need an "order" attribute.
396+
delete newAttrs.order;
397+
} else if (fromBulletToOrderedList(fromType, toType)) {
398+
// For ordered lists, set a default start if not present.
399+
newAttrs.order = newAttrs.order || 1;
400+
}
401+
402+
return newAttrs;
403+
};
404+
413405
/**
414406
* Iterates through all list nodes (including nested ones) in the selection
415407
* and converts each node from one type to another.
416408
*
417409
* This helper also handles attribute conversion:
418-
* - When converting from an ordered list to a bullet list, attributes like `order` are removed.
419-
* - When converting from a bullet list to an ordered list, you can set a default start (e.g. 1).
410+
* - When converting from an ordered list to a bullet list, attributes like "order" are removed.
411+
* - When converting from a bullet list to an ordered list, a default start (1) is set if not present.
420412
*
421413
* @param EditorState - state - The current editor state.
422414
* @param NodeType - fromType - The list node type to convert from.
@@ -433,26 +425,11 @@ const convertAllListNodes = (state, fromType, toType, dispatch) => {
433425
state.selection.to,
434426
(node, pos) => {
435427
if (node.type === fromType) {
436-
// Create new attributes by copying the current ones
437-
const newAttrs = { ...node.attrs };
438-
439-
// Handle attribute differences:
440-
if (
441-
fromType.name === 'ordered_list' &&
442-
toType.name === 'bullet_list'
443-
) {
444-
// Bullet lists generally do not need an "order" attribute
445-
delete newAttrs.order;
446-
} else if (
447-
fromType.name === 'bullet_list' &&
448-
toType.name === 'ordered_list'
449-
) {
450-
// For ordered lists, set a default start if not present
451-
newAttrs.order = newAttrs.order || 1;
452-
}
453-
// You can add more attribute merging logic here if needed.
454-
455-
// Replace the current list node with one of the target type
428+
const newAttrs = convertAttributes(
429+
fromType,
430+
toType,
431+
node.attrs,
432+
);
456433
const newNode = toType.create(
457434
newAttrs,
458435
node.content,
@@ -461,8 +438,7 @@ const convertAllListNodes = (state, fromType, toType, dispatch) => {
461438
tr = tr.replaceWith(pos, pos + node.nodeSize, newNode);
462439
converted = true;
463440

464-
// Skip the subtree to avoid reprocessing nested nodes already converted.
465-
return false;
441+
return false; // Skip the subtree.
466442
}
467443

468444
return true;
@@ -476,26 +452,57 @@ const convertAllListNodes = (state, fromType, toType, dispatch) => {
476452
return converted;
477453
};
478454

455+
/**
456+
* Adjusts the current selection to include only fully selected blocks.
457+
*
458+
* @param EditorState - state - The current editor state.
459+
* @returns Object - An object with properties "from" and "to" representing
460+
* the new selection boundaries.
461+
*/
462+
const adjustSelectionToFullBlocks = (state) => {
463+
const { $from, $to } = state.selection;
464+
// If the selection start is at the beginning of its block, keep it.
465+
// Otherwise, move to the end of that block (i.e. skip the partial block).
466+
const from = $from.pos === $from.start() ? $from.pos : $from.end();
467+
// Similarly, if the selection end is at the end of its block, keep it.
468+
// Otherwise, use the start of that block.
469+
const to = $to.pos === $to.end() ? $to.pos : $to.start();
470+
471+
return { from: from, to: to };
472+
};
473+
479474
export const createListCommand = (schema, listTypeName) => {
480475
const type = schema.nodes[listTypeName];
481476
if (!type) {
482477
throw new Error(`List type "${listTypeName}" not found in schema`);
483478
}
484479

485480
const command = (state, dispatch) => {
486-
// (a) If selection is already in the target list type, remove the list.
481+
// If selection is already in the target list type, remove the list.
487482
if (isInListOfType(state, type)) {
488483
return removeListNodes(state, type, schema, dispatch);
489484
}
490485

491-
// (b) If the selection is in the "other" list type, convert it.
492-
const otherType = getOtherListType(schema, listTypeName);
493-
if (otherType && isInListOfType(state, otherType)) {
494-
return convertAllListNodes(state, otherType, type, dispatch);
486+
// If the selection is in another list type, convert it.
487+
const isOtherListType = getOtherListType(schema, listTypeName);
488+
if (isOtherListType && isInListOfType(state, isOtherListType)) {
489+
return convertAllListNodes(state, isOtherListType, type, dispatch);
490+
}
491+
492+
// Adjust the selection to include only fully selected blocks.
493+
const { from, to } = adjustSelectionToFullBlocks(state);
494+
if (from >= to) {
495+
return false;
495496
}
496497

497-
// (c) Otherwise, wrap the selection in the target list type.
498-
return toggleList(type)(state, dispatch);
498+
// Create a new transaction with the adjusted selection.
499+
const adjustedTr = state.tr.setSelection(
500+
new TextSelection(state.doc.resolve(from), state.doc.resolve(to)),
501+
);
502+
// Apply the transaction to get a new state.
503+
const newState = state.apply(adjustedTr);
504+
505+
return wrapInList(type)(newState, dispatch);
499506
};
500507

501508
command.active = (state) => {

0 commit comments

Comments
 (0)