prosemirror-model

repository·master·Indexed 43 days ago

https://github.com/prosemirror/prosemirror-model

A core module of ProseMirror that implements the document model and mechanisms for custom document schemas. It provides the fundamental building blocks for document structure, including Nodes, Fragments, Marks, and Slices, as well as tools for schema definition, content validation via ContentMatch, and DOM parsing and serialization using DOMParser and DOMSerializer. Version 1.25.4.

Tokens
10K
Snippets
36
Records
54
Agent score
55%

What's inside ProseMirror Model

  1. Define a document schema

    master

    Every ProseMirror document must conform to a Schema. The schema defines the rules for what the document can contain, including:

    • The set of allowed Nodes and Marks.
    • The relationships between them (e.g., which nodes are permitted to be children of other nodes).

    To build a schema, you use specification objects:

    • SchemaSpec: The base configuration for a schema.
    • NodeSpec: Defines the properties and rules for a specific node type.
    • MarkSpec: Defines the properties and rules for a specific mark type.
    • AttributeSpec: Defines how attributes are handled for nodes or marks.
    • NodeType and MarkType: The resulting types after the schema is instantiated.
  2. Use resolved positions for document navigation

    master

    While document positions can be represented as simple integer offsets, ProseMirror provides more convenient abstractions for navigating and referencing parts of the document:

    • ResolvedPos: A more robust representation of a position within the document tree.
    • NodeRange: Represents a range of content within a specific node.
  3. Understand the ProseMirror document structure

    master

    A ProseMirror document is structured as a tree. The hierarchy is built using two primary components:

    1. Nodes (Node): These represent the different types of content at each level of the tree.
    2. Fragments (Fragment): Each node holds a Fragment which contains its children.

    Other key data structures involved in document manipulation include Mark (for text styling), Slice (for moving parts of a document), and Attrs (for node attributes).

  4. Parse and serialize documents to/from the DOM

    master

    ProseMirror integrates DOM parsing and serialization directly into its model to bridge the gap between the document tree and the browser's DOM.

    • DOMParser: Used to convert DOM nodes into a ProseMirror document. You can customize parsing using ParseRules (including GenericParseRule, TagParseRule, and StyleParseRule) and ParseOptions.
    • DOMSerializer: Used to convert a ProseMirror document back into DOM nodes. You can define how nodes are rendered using a DOMOutputSpec.

    Note: You do not need a DOM implementation (like a browser environment) loaded to use this module, as the logic is decoupled from the actual DOM implementation.

  5. What is a Mark and how to use it

    master

    A Mark is a piece of information attached to a node, such as emphasis (italics), code font, or a link. Each mark has a type (defined by the Schema) and optional attrs (attributes) that provide extra data, like the URL for a link.

    Marks are managed in sets (arrays of Mark objects). When manipulating these sets, you should use the methods provided by the Mark class to ensure proper sorting, exclusivity, and immutability.

  6. What is a Slice and how to use it

    master

    A Slice represents a portion of a document cut out from a larger one. It contains a Fragment (the content) and two depth values, openStart and openEnd, which indicate how many levels of nodes are 'open' (cut through) at the beginning and end of the fragment.

    This allows a slice to represent not just a flat list of nodes, but a piece of a document that maintains its structural relationship with the surrounding nodes (e.g., a slice that starts inside a paragraph and ends inside a paragraph).

    Key Properties

    • content: The Fragment contained in the slice.
    • openStart: The depth at which the start of the fragment is open.
    • openEnd: The depth at which the end of the fragment is open.
    • size: The number of positions the slice adds when inserted into a document (this.content.size - this.openStart - this.openEnd).

    Common Creation Methods

    • Slice.empty: An empty slice with no content and zero open depth.
    • Slice.maxOpen(fragment, [openIsolating]): Creates a slice from a Fragment by calculating the maximum possible open depth on both sides. Use this when you want the slice to automatically 'fit' into the surrounding structure of the fragment's nodes.
    import {Slice} from "@prosemirror/model";
    
    // Create an empty slice
    const emptySlice = Slice.empty;
    
    // Create a slice from a fragment with maximum open depth
    const slice = Slice.maxOpen(myFragment);
    
    // Check equality
    const isSame = slice.eq(otherSlice);
    
    // Convert to JSON
    const json = slice.toJSON();
    const restoredSlice = Slice.fromJSON(schema, json);
  7. How the Fragment class works

    master

    A Fragment represents a collection of child nodes belonging to a parent node. It is a persistent data structure, meaning you should never mutate a fragment directly. Instead, all operations that modify content (like append, cut, replaceChild, addToStart, or addToEnd) return a new Fragment instance. This ensures immutability and predictable state management.

    Key properties:

    • size: The total nodeSize of all nodes within the fragment.
    • childCount: The number of direct child nodes.
    • firstChild / lastChild: Access to the boundary nodes.
  8. How the Node class works in ProseMirror

    master

    A Node represents an element in the ProseMirror document tree. A document is itself a Node containing child Node instances.

    Key Characteristics:

    • Persistent Data Structures: Nodes are immutable. Instead of mutating a node, you must create a new one using methods like copy(), mark(), or cut(). This allows for efficient structure sharing between old and new versions of the document.
    • Markup: Nodes carry marks (e.g., emphasis, links) and attrs (attributes defined by the node's type).
    • Content: Non-leaf nodes contain a Fragment of child nodes. Text nodes contain a text string.
    • Indexing: Nodes use an integer-based indexing scheme. The nodeSize of a text node is its character length; a leaf node is 1; a non-leaf node is 2 + content.size (accounting for start and end tokens).
    import { Node } from "prosemirror-model"
    // Note: You typically interact with Nodes via a Schema, 
    // rather than instantiating them directly with the constructor.
  9. Use NodeRange to represent a range within a node

    master

    A NodeRange represents a range of content that starts and ends within the same parent node. It is typically created using the blockRange method on a ResolvedPos.

    Properties

    • start: The absolute position at the start of the range.
    • end: The absolute position at the end of the range.
    • parent: The node that contains the range.
    • startIndex: The index of the first child in the parent node.
    • endIndex: The index after the last child in the parent node.
  10. Define a Schema with NodeSpec and MarkSpec

    master

    A Schema defines the structure of a document by specifying which nodes and marks are allowed. You create a schema by providing a SchemaSpec object containing nodes (mapping names to NodeSpec) and marks (mapping names to MarkSpec).

    NodeSpec Options

    • content: A string describing the allowed content (e.g., "block+", "inline*").
    • marks: A string specifying allowed marks. "_" allows all marks, "" disallows all marks, or a space-separated list of mark names/groups.
    • group: A space-separated list of groups this node belongs to.
    • inline: Boolean; if true, the node is an inline type.
    • attrs: An object defining the attributes for this node type using AttributeSpec.
    • toDOM: A function to serialize the node to DOM/HTML.
    • parseDOM: Rules for parsing the node from the DOM.

    MarkSpec Options

    • attrs: Attributes for the mark.
    • excludes: A space-separated list of marks that this mark cannot coexist with. Use "_" to exclude all marks.
    • group: The group(s) this mark belongs to.
    • toDOM: A function to serialize the mark to DOM/HTML.
    import {Schema} from "prosemirror-model"
    
    const mySchema = new Schema({
      nodes: {
        doc: { content: "block+" },
        paragraph: { group: "block", content: "inline*", marks: true },
        text: {}}},
      marks: {
        strong: { "text": "bold" }
      }
    })
  11. How to resolve a position using ResolvedPos

    master

    In ProseMirror, a raw numeric position is just an offset in a flat document. To access the context of that position (like which node it is in, its depth, or the marks applied to it), you must "resolve" it. You do this by calling doc.resolve(pos), which returns a ResolvedPos object.

    A ResolvedPos (often denoted as $pos in documentation) provides access to the hierarchy of nodes surrounding that position, allowing you to navigate up to the root or down into specific children.

    // Assuming 'doc' is a Node and 'pos' is a number
    const $pos = doc.resolve(pos);
    
    // Now you can access context:
    console.log($pos.depth);
    console.log($pos.parent);
    console.log($pos.marks());
  12. Use ContentMatch to validate and navigate content expressions

    master

    The ContentMatch class represents the state of a node type's content expression (the rules defining what children a node can have). It is used to determine if a specific sequence of nodes is valid according to the schema and to find valid positions for new nodes.

    Key capabilities:

    • Matching Types: Check if a specific NodeType can follow the current position using matchType(type).
    • Matching Fragments: Validate an entire Fragment of nodes against the current match state using matchFragment(frag, start, end).
    • Finding Wrappers: Determine which parent nodes must wrap a target node to make it valid at the current position using findWrapping(target).
    • Filling Content: Automatically generate the necessary nodes to make a fragment valid at a specific position using fillBefore(after, toEnd, startIndex).
    • Default Nodes: Identify the first valid node type that can be generated at the current position using the defaultType getter.
    import { ContentMatch } from "prosemirror-model"
    
    // Assuming you have access to a NodeType from your schema
    // matchType returns the next ContentMatch state if the type matches
    const nextMatch = currentMatch.matchType(myNodeType);
    
    if (nextMatch) {
      // The node type matched, we are now in the state represented by nextMatch
    }
    
    // findWrapping returns an array of node types that would wrap the target
    const wrappers = currentMatch.findWrapping(targetNodeType);
    // returns e.g., [paragraphNodeType, blockquoteNodeType]