All files / collaborative-prosemirror/src util.ts

94.11% Statements 64/68
64.28% Branches 18/28
83.33% Functions 5/6
96.22% Lines 51/53

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            12x         54x 54x     54x             48x                       12x 64x 64x 64x 64x 66x 64x 64x 64x 64x           12x 464x 464x 582x 464x 464x 464x 464x 464x     12x 286x 286x 286x 286x 396x 396x 396x 396x 572x 572x 572x 366x 366x 366x 366x 366x 366x     396x 30x 30x 30x 30x 30x     286x 286x 286x    
import type {Block, LeafBlock} from 'json-joy/lib/json-crdt-extensions';
import type {Peritext} from 'json-joy/lib/json-crdt-extensions';
import type {Point} from 'json-joy/lib/json-crdt-extensions/peritext/rga/Point';
import type * as pmm from 'prosemirror-model';
import type {PmJsonTextNode, PmJsonNode} from './types';
 
export const fromJSON = (
  schema: pmm.Schema,
  json: PmJsonNode | PmJsonTextNode,
  createFragment: (nodes?: pmm.Node[]) => pmm.Fragment,
): pmm.Node => {
  const {type, attrs, content, marks, text} = json as PmJsonNode & PmJsonTextNode;
  const _marks: pmm.Mark[] | undefined = Array.isArray(marks)
    ? marks.map((mark) => schema.mark(mark.type, mark.attrs))
    : void 0;
  return type === 'text'
    ? schema.text(typeof text === 'string' ? text : '', _marks)
    : (schema as any)
        .nodeType(type)
        .create(
          attrs,
          Array.isArray(content)
            ? createFragment(content.map((val) => fromJSON(schema, val, createFragment)))
            : createFragment(),
          _marks,
        );
};
 
/**
 * Convert a flat ProseMirror position to a Peritext gap position (the integer
 * coordinate system used by `Peritext.insAt` / `Peritext.delAt`).
 *
 * Returns `-1` if the position cannot be resolved (e.g. structural mismatch).
 */
export const pmPosToGap = (txt: Peritext, doc: pmm.Node, pmPos: number): number => {
  try {
    const resolved = doc.resolve(pmPos);
    const leafDepth = resolved.depth;
    let block: Block<string> | LeafBlock<string> = txt.blocks.root;
    for (let d = 0; d < leafDepth && block; d++) block = block.children[resolved.index(d)];
    Iif (!block) return -1;
    const textOffset = resolved.parentOffset;
    const hasMarker = !!(block as LeafBlock<string>).marker;
    return hasMarker ? block.start.viewPos() + 1 + textOffset : textOffset;
  } catch {
    return -1;
  }
};
 
export const pmPosToPoint = (txt: Peritext, resolved: pmm.ResolvedPos): Point<string> => {
  const leafDepth = resolved.depth;
  let block: Block<string> | LeafBlock<string> = txt.blocks.root;
  for (let d = 0; d < leafDepth && block; d++) block = block.children[resolved.index(d)];
  Iif (!block) return txt.pointStart() ?? txt.pointAbsStart();
  const textOffset = resolved.parentOffset;
  const hasMarker = !!(block as LeafBlock<string>).marker;
  const peritextGap = hasMarker ? block.start.viewPos() + 1 + textOffset : textOffset;
  return txt.pointIn(peritextGap);
};
 
export const pointToPmPos = (block: Block<string> | LeafBlock<string>, point: Point<string>, doc: pmm.Node): number => {
  const viewPos = point.viewPos();
  let pmNode: pmm.Node = doc;
  let pmPos = 0;
  while (!block.isLeaf()) {
    const children = block.children;
    const len = children.length;
    let found = false;
    for (let i = 0; i < len; i++) {
      const child = children[i];
      const childEndView = child.end.viewPos();
      if (viewPos <= childEndView) {
        for (let j = 0; j < i; j++) pmPos += pmNode.child(j).nodeSize;
        pmPos += 1; // open tag
        block = child;
        pmNode = pmNode.child(i);
        found = true;
        break;
      }
    }
    if (!found) {
      const lastIdx = len - 1;
      for (let j = 0; j < lastIdx; j++) pmPos += pmNode.child(j).nodeSize;
      pmPos += 1; // open tag
      block = children[lastIdx];
      pmNode = pmNode.child(lastIdx);
    }
  }
  const hasMarker = !!(block as LeafBlock<string>).marker;
  const textOffset = hasMarker ? viewPos - (block.start.viewPos() + 1) : viewPos;
  return pmPos + Math.max(0, textOffset);
};