Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/euclidesseg/euclides-workspace/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Euclides Rich Editor supports both ordered and unordered lists through the toggleList method. List functionality is provided by ProseMirror’s prosemirror-schema-list package and integrated into the custom schema.

Using toggleList

The toggleList method creates or converts content to a list:
toggleList(type: list, view: EditorView): boolean {
   return CommandsMethods.switchList(EuclidesEditorSchema.nodes[type])(view.state, view.dispatch);
}
Usage in your component:
import { EditorCommandsService } from 'euclides-rich-editor';
import { list } from 'euclides-rich-editor/core/types';

toggleList(type: list) {
  if (this.editorCommandsService.toggleList(type, this.view)) {
    this.view.focus();
  }
}
Example with buttons:
<button (click)="toggleList('bullet_list')">• Bullet List</button>
<button (click)="toggleList('ordered_list')">1. Ordered List</button>

List Types

The editor defines a list type for supported list formats:
export type list = 'ordered_list' | 'bullet_list' | 'task_list'
Source: ~/workspace/source/projects/euclides-rich-editor/src/lib/core/types/list.type.ts:1
Currently, the editor implements ordered_list and bullet_list. The task_list type is defined but not yet fully implemented.

Ordered Lists

Numbered lists for sequential content:
toggleList('ordered_list');
Generates HTML:
<ol>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ol>

Unordered Lists

Bullet lists for non-sequential content:
toggleList('bullet_list');
Generates HTML:
<ul>
  <li>First item</li>
  <li>Second item</li>
  <li>Third item</li>
</ul>

How switchList Works

The switchList method provides intelligent list toggling:
static switchList(listType: NodeType): Command {
    return (state, dispatch) => {
        const { schema, selection } = state;
        const { $from, from, to } = selection;

        let tr = state.tr;

        // Convert heading to paragraph first
        if ($from.parent.type === schema.nodes['heading']) {
            tr = tr.setBlockType(from, to, schema.nodes['paragraph']);
        }

        const resolved = tr.doc.resolve(tr.selection.from);
        for (let d = resolved.depth; d > 0; d--) {
            const node = resolved.node(d);

            if (
                node.type === schema.nodes['bullet_list'] ||
                node.type === schema.nodes['ordered_list']
            ) {
                // If already this list type, lift it out
                if (node.type === listType) {
                    return liftListItem(schema.nodes['list_item'])(state, dispatch);
                }

                // Convert between list types
                if (dispatch) {
                    dispatch(tr.setNodeMarkup(resolved.before(d), listType));
                }
                return true;
            }
        }

        // Not in a list, wrap in one
        return wrapInList(listType)(state, dispatch);
    };
}
Source: ~/workspace/source/projects/euclides-rich-editor/src/lib/engine/commanmethods/command.methods.ts:60-92

Behavior Patterns

1

Heading to List

If the selection is in a heading, it’s first converted to a paragraph, then wrapped in a list:
if ($from.parent.type === schema.nodes['heading']) {
    tr = tr.setBlockType(from, to, schema.nodes['paragraph']);
}
2

Toggle Same List Type

If already in the same list type, lift the item out of the list:
if (node.type === listType) {
    return liftListItem(schema.nodes['list_item'])(state, dispatch);
}
3

Convert Between Lists

If in a different list type, convert it:
dispatch(tr.setNodeMarkup(resolved.before(d), listType));
4

Wrap in New List

If not in a list, wrap the selection:
return wrapInList(listType)(state, dispatch);

Schema Integration

Lists are added to the schema using addListNodes from prosemirror-schema-list:
import { addListNodes } from 'prosemirror-schema-list';

const nodes = basicSchema.spec.nodes.update("paragraph", paragraph);

export const EuclidesEditorSchema = new Schema({
  nodes: addListNodes(nodes, "paragraph block*", "block"),
  marks: basicSchema.spec.marks.addToEnd("strike", strike),
});
Source: ~/workspace/source/projects/euclides-rich-editor/src/lib/engine/schema/euclides-schema.ts:47-52

Understanding addListNodes

The addListNodes function adds three node types to the schema:
  1. bullet_list - The unordered list container (<ul>)
  2. ordered_list - The ordered list container (<ol>)
  3. list_item - Individual list items (<li>)
Parameters:
addListNodes(
  nodes,              // Existing nodes OrderedMap
  "paragraph block*", // Content expression for list items
  "block"            // Group name for list nodes
)
  • "paragraph block*" - List items can contain a paragraph followed by any number of blocks
  • "block" - Lists are block-level elements

List and Code Block Integration

The toggleCodeBlock method has special handling for lists. When converting a list to a code block, it extracts all the text:
if (
    node.type === schema.nodes['bullet_list'] ||
    node.type === schema.nodes['ordered_list']
) {
    // Extract text from all list items
    let text = "";

    node.forEach((listItem: any) => {
        const para = listItem.firstChild;
        if (para) {
            text += para.textContent + "\n";
        }
    });

    text = text.trimEnd();

    const codeBlock = schema.nodes['code_block'].create(
        null,
        schema.text(text)
    );

    if (dispatch) {
        dispatch(
            tr.replaceWith(
                $from.before(d),
                $from.after(d),
                codeBlock
            )
        );
    }

    return true;
}
Source: ~/workspace/source/projects/euclides-rich-editor/src/lib/engine/commanmethods/command.methods.ts:15-50 This allows seamless conversion from:
• Item one
• Item two
• Item three
To:
Item one
Item two
Item three

Document Structure

Lists create a nested document structure:
{
  "type": "bullet_list",
  "content": [
    {
      "type": "list_item",
      "content": [
        {
          "type": "paragraph",
          "content": [
            { "type": "text", "text": "First item" }
          ]
        }
      ]
    },
    {
      "type": "list_item",
      "content": [
        {
          "type": "paragraph",
          "content": [
            { "type": "text", "text": "Second item" }
          ]
        }
      ]
    }
  ]
}
Each list item contains a paragraph by default, which allows for nested blocks like additional paragraphs or even nested lists within a single item.

Best Practices

1

Use type-safe list types

Import and use the list type for type safety:
import { list } from 'euclides-rich-editor/core/types';

toggleList(type: list) {
  this.editorCommandsService.toggleList(type, this.view);
}
2

Handle toggle behavior

Clicking the same list button twice will remove the list:
// First click: wrap in bullet list
toggleList('bullet_list');

// Second click on same button: unwrap list
toggleList('bullet_list');
3

Understand list conversion

Switching between list types preserves items:
// Start with bullet list
toggleList('bullet_list');

// Convert to ordered list (keeps items)
toggleList('ordered_list');

Advanced: Nested Lists

To create nested lists, users can:
  1. Create a list item
  2. Manually adjust structure through the ProseMirror API
Tab key indentation for nested lists requires additional input rules configuration. You can implement this by adding custom input rules to the editor plugins. See the Custom Schema guide for information on extending the editor.

Next Steps

Build docs developers (and LLMs) love