All files / collaborative-prosemirror/src/sync FromPm.ts

100% Statements 62/62
87.5% Branches 28/32
100% Functions 5/5
100% Lines 56/56

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 9512x 12x                             12x 1009x   1009x 1009x     24210x 24210x 24210x 24210x 24210x 11396x   12814x 12814x 12814x 12814x 12814x 12814x 12814x 12814x 8460x   8460x     8460x 8460x   12814x   24210x 24210x 24210x 5525x 5525x 10186x 10186x 10186x 10186x 10186x 3540x 3540x   10186x   10186x     10186x 10186x 10186x           11565x 11565x 11565x 11565x 24210x 24210x 24210x 24210x 24210x         1009x 1009x 1009x 1009x 1009x      
import {Anchor} from 'json-joy/lib/json-crdt-extensions/peritext/rga/constants';
import {SliceHeaderShift, SliceStacking} from 'json-joy/lib/json-crdt-extensions/peritext/slice/constants';
import type {ViewRange, ViewSlice} from 'json-joy/lib/json-crdt-extensions/peritext/editor/types';
import type {SliceTypeStep, SliceTypeSteps} from 'json-joy/lib/json-crdt-extensions/peritext';
import type {PmFragment, PmNode, PmTextNode} from '../types';
 
/**
 * Converts ProseMirror raw nodes to a {@link ViewRange} flat string with
 * annotation ranges, which is the natural view format for a Peritext model.
 *
 * Usage:
 *
 * ```typescript
 * FromPm.convert(node);
 * ```
 */
export class FromPm {
  static readonly convert = (node: PmNode): ViewRange => new FromPm().convert(node);
 
  private text = '';
  private slices: ViewSlice[] = [];
 
  private conv(node: PmNode, path: SliceTypeSteps, nodeDiscriminator: number): void {
    const text = this.text;
    const start = text.length;
    let inlineText: string = '';
    const type = node.type.name;
    if (type === 'text' && (inlineText = (node as PmTextNode).text || '')) {
      this.text += inlineText;
    } else {
      const content = node.content?.content;
      const data = node.attrs;
      const step: SliceTypeStep = nodeDiscriminator || data ? [type, nodeDiscriminator, data] : type;
      const length = content?.length ?? 0;
      const hasNoChildren = length === 0;
      const isFirstChildInline = content?.[0]?.type.name === 'text';
      const doEmitSplitMarker = hasNoChildren || isFirstChildInline;
      if (doEmitSplitMarker) {
        this.text += '\n';
        const header =
          (SliceStacking.Marker << SliceHeaderShift.Stacking) +
          (Anchor.Before << SliceHeaderShift.X1Anchor) +
          (Anchor.Before << SliceHeaderShift.X2Anchor);
        const slice: ViewSlice = [header, start, start, [...path, step]];
        this.slices.push(slice);
      }
      if (length > 0) this.cont([...path, step], content!);
    }
    const marks = node.marks;
    let length = 0;
    if (marks && (length = marks.length) > 0) {
      const end = start + inlineText.length;
      for (let i = 0; i < length; i++) {
        const mark = marks[i];
        const type = mark.type.name;
        const data = mark.attrs;
        let dataEmpty = true;
        for (const _ in data) {
          dataEmpty = false;
          break;
        }
        const stacking: SliceStacking = dataEmpty ? SliceStacking.One : SliceStacking.Many;
        const header =
          (stacking << SliceHeaderShift.Stacking) +
          (Anchor.Before << SliceHeaderShift.X1Anchor) +
          (Anchor.After << SliceHeaderShift.X2Anchor);
        const slice: ViewSlice = [header, start, end, type];
        if (!dataEmpty) slice.push(data);
        this.slices.push(slice);
      }
    }
  }
 
  private cont(path: SliceTypeSteps, content: PmFragment['content']): void {
    let prevTag: string = '';
    let discriminator: number = 0;
    const length = content.length;
    for (let i = 0; i < length; i++) {
      const child = content[i];
      const tag = child.type.name;
      discriminator = tag === prevTag ? discriminator + 1 : 0;
      this.conv(child, path, discriminator);
      prevTag = tag;
    }
  }
 
  public convert(node: PmNode): ViewRange {
    const content = node.content?.content;
    let length = 0;
    Eif (content && (length = content.length) > 0) this.cont([], content);
    const view: ViewRange = [this.text, 0, this.slices];
    return view;
  }
}