src/plugin/helpers/decorations.ts (47 lines of code) (raw):

import type { Node } from "prosemirror-model"; import { Mapping, StepMap } from "prosemirror-transform"; import type { DecorationSource } from "prosemirror-view"; import { DecorationSet } from "prosemirror-view"; export const getMappedDecorationsFromSet = ( // A decoration set received, positioned relative to the outer editor. It may contain irrelevant decorations. decorationSet: DecorationSet, // The offset of the field from its containing element. fieldOffsetFromElement: number, // The node representing the current field in the ProseMirror document. fieldNode: Node, // The node representing the outer ProseMirror document containing the field. document: Node ) => { // This field may receive decorations that will not apply to its range. // Find the decorations in the context of the original document that should apply to this field, based on the position of // the decorations in the original field. // We must filter the decorations before we offset them via a 'map', otherwise there may be errors due to decorations // being mapped outside of a valid range. const relevantDecosFromOriginalDoc = decorationSet.find( fieldOffsetFromElement, fieldOffsetFromElement + fieldNode.nodeSize ); // We must recombine the Decoration[] into a DecorationSet because we can only 'map' a DecorationSet, not a Decoration[], // and we want to reposition the decorations relative to the current field. // We must do this in the context of the original document, because ProseMirror uses the structure of the document to structure // the decorations - in particular Widget and Node decorations rely on the document structure for a correct DecorationSet to be // formed. const offsetMap = new Mapping([StepMap.offset(-fieldOffsetFromElement)]); const relevantDecosInOriginalDoc = DecorationSet.create( document, relevantDecosFromOriginalDoc ); // We now 'map' the decorations based on the offset, so that they are positioned relative to the current field, rather // than the outer doc. const mappedDecorations = relevantDecosInOriginalDoc.map( offsetMap, fieldNode ); return mappedDecorations; }; export const getMappedDecorationsFromSource = ( decorations: DecorationSource, fieldOffsetFromElement: number, fieldNode: Node, document: Node ): DecorationSet => { const relevantDecorationSets: DecorationSet[] = []; // Map each member DecorationSet, combine them into a single DecorationSet, // then set them as the field's decorations. decorations.forEachSet((set) => { const mappedDecos = getMappedDecorationsFromSet( set, fieldOffsetFromElement, fieldNode, document ); relevantDecorationSets.push(mappedDecos); }); const relevantDecorations = relevantDecorationSets.flatMap((set) => set.find() ); // Decorations may be lost if we don't recreate the DecorationSet in the context of the innerEditor const decorationsAsSet = DecorationSet.create(fieldNode, relevantDecorations); return decorationsAsSet; };