private updateInnerEditor()

in src/plugin/fieldViews/ProseMirrorFieldView.ts [140:266]


  private updateInnerEditor(
    node: Node,
    decorations: DecorationSource,
    elementOffset: number,
    selection?: Selection,
    storedMarks?: readonly Mark[] | null
  ) {
    if (!this.innerEditorView) {
      return;
    }

    this.offset = elementOffset;
    this.node = node;
    this.applyDecorationsFromOuterEditor(decorations, node);

    const state = this.innerEditorView.state;
    let shouldDispatchTransaction = false;
    let tr = state.tr;

    // Check if the inner selection needs to be updated

    if (selection) {
      // Absolute positions of the incoming selection
      const incomingAnchorPos = selection.$anchor.pos;
      const incomingHeadPos = selection.$head.pos;

      // Relative positions of the current selection in the inner editor
      const currentAnchorPos = this.innerEditorView.state.selection.$anchor.pos;
      const currentHeadPos = this.innerEditorView.state.selection.$head.pos;

      // Absolute position of the field in the document
      // Note: we must offset to account for a few things:
      //  - getPos() returns the position directly before the parent node (+1)
      //  - the node we will be altering is a child of its parent (+1)
      const contentOffset = 2;
      const fieldStart = this.offset + this.getPos() + contentOffset;
      const fieldEnd =
        this.offset + this.getPos() + this.innerEditorView.state.doc.nodeSize;

      const incomingSelectionIsWithinThisField =
        incomingAnchorPos > fieldStart &&
        incomingAnchorPos < fieldEnd &&
        incomingHeadPos > fieldStart &&
        incomingHeadPos < fieldEnd;

      // The inner editor's selection will be offset relative to the start of this field,
      // compared to the incoming selection
      const incomingSelectionDiffersFromCurrentSelection =
        currentAnchorPos !== incomingAnchorPos - fieldStart ||
        currentHeadPos !== incomingHeadPos - fieldStart;

      if (
        incomingSelectionIsWithinThisField &&
        incomingSelectionDiffersFromCurrentSelection
      ) {
        const offsetMap = StepMap.offset(-fieldStart);
        const mappedSelection = selection.map(state.tr.doc, offsetMap);
        shouldDispatchTransaction = true;
        tr = tr.setSelection(mappedSelection);
      }
    }

    // Check if the passed-in Node is different to the existing content,
    // figure out the smallest change to the node content we need to make to
    // successfully update the inner editor, and apply it.

    const diffStart = node.content.findDiffStart(state.doc.content);
    const diffEnd = node.content.findDiffEnd(state.doc.content);

    // Check for null specifically, rather than falsiness, because a diff starting at pos 0 is a valid diff
    if (diffStart !== null && diffEnd !== null) {
      let { a: endOfOuterDiff, b: endOfInnerDiff } = diffEnd;
      // This overlap accounts for a situation where we're diffing nodes where we encounter
      // identical content.
      //
      // For example, if the inner node has content 'a', and the outer node has content 'aa',
      // `diffStart` sees (numbers are positions, ^ denotes the value found by the method)
      //
      // ab    inner node
      // abb   outer node
      // 123
      //   ^
      //
      // `diffEnd` for the inner node is
      //  ab   inner node
      // abb   outer node
      // 123
      //  ^
      //
      // But 2 is not the correct end of the diff. The correct diff is (3, 3).
      //
      // This happens because `findDiffEnd` finds the first point at which the content differs,
      // starting from the end of the nodes. So if we encounter identical content, the diff will
      // be shorter by the length of the identical content we encounter – or the overlap between
      // the two nodes from the point of view of the diff.
      const overlap = diffStart - Math.min(endOfOuterDiff, endOfInnerDiff);
      if (overlap > 0) {
        endOfOuterDiff += overlap;
        endOfInnerDiff += overlap;
      }

      shouldDispatchTransaction = true;

      tr = tr.replace(
        diffStart,
        endOfInnerDiff,
        node.slice(diffStart, endOfOuterDiff)
      );
    }

    const storedMarksHaveChanged = !isEqual(
      storedMarks,
      this.innerEditorView.state.storedMarks
    );

    if (storedMarksHaveChanged && storedMarks !== undefined) {
      shouldDispatchTransaction = true;
      tr = tr.setStoredMarks(storedMarks);
    }

    if (shouldDispatchTransaction) {
      tr = tr.setMeta("fromOutside", true);
      this.innerEditorView.dispatch(tr);
    } else {
      return this.maybeRerenderDecorations();
    }
  }