Skip to content

Commit

Permalink
feat(json-crdt-extensions): 🎸 add inter-block iteration using point t…
Browse files Browse the repository at this point in the history
…uples
  • Loading branch information
streamich committed Jun 7, 2024
1 parent 0e66aa6 commit edf19ac
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 18 deletions.
18 changes: 18 additions & 0 deletions src/json-crdt-extensions/peritext/block/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {Path} from '../../../json-pointer';
import type {Printable} from 'tree-dump';
import type {Peritext} from '../Peritext';
import type {Stateful} from '../types';
import type {OverlayTuple} from '../overlay/types';

export interface IBlock {
readonly path: Path;
Expand Down Expand Up @@ -75,6 +76,23 @@ export class Block<Attr = unknown> implements IBlock, Printable, Stateful {
return new UndefEndIter(this.points0(withMarker));
}

public tuples0(): UndefIterator<OverlayTuple<T>> {
const overlay = this.txt.overlay;
const iterator = overlay.tuples0(this.marker);
let closed = false;
return () => {
if (closed) return;
const pair = iterator();
if (!pair) return;
if (!pair[1] || pair[1] instanceof MarkerOverlayPoint) closed = true;
return pair;
};
}

public tuples(): IterableIterator<OverlayTuple<T>> {
return new UndefEndIter(this.tuples0());
}

// ----------------------------------------------------------------- Stateful

public hash: number = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import {setupHelloWorldKit} from '../../__tests__/setup';
import {MarkerOverlayPoint} from '../../overlay/MarkerOverlayPoint';
import {OverlayPoint} from '../../overlay/OverlayPoint';
import {Inline} from '../Inline';

const setupTwoBlockDocument = () => {
const kit = setupHelloWorldKit();
const {peritext} = kit;
peritext.editor.cursor.setAt(1, 2);
peritext.editor.saved.insStack('bold');
peritext.editor.cursor.setAt(7, 2);
peritext.editor.saved.insStack('italic');
peritext.editor.cursor.setAt(8, 2);
peritext.editor.saved.insStack('underline');
peritext.editor.cursor.setAt(6);
peritext.editor.saved.insMarker('p');
peritext.editor.delCursors();
peritext.refresh();
return kit;
};

describe('points', () => {
test('no iteration in empty document', () => {
Expand Down Expand Up @@ -50,22 +67,6 @@ describe('points', () => {
expect(iterator()).toBe(undefined);
});

const setupTwoBlockDocument = () => {
const kit = setupHelloWorldKit();
const {peritext} = kit;
peritext.editor.cursor.setAt(1, 2);
peritext.editor.saved.insStack('bold');
peritext.editor.cursor.setAt(7, 2);
peritext.editor.saved.insStack('italic');
peritext.editor.cursor.setAt(8, 2);
peritext.editor.saved.insStack('underline');
peritext.editor.cursor.setAt(6);
peritext.editor.saved.insMarker('p');
peritext.editor.delCursors();
peritext.refresh();
return kit;
};

test('returns only points within that block, in two-block document', () => {
const {peritext} = setupTwoBlockDocument();
expect(peritext.blocks.root.children.length).toBe(2);
Expand All @@ -91,3 +92,33 @@ describe('points', () => {
expect(points2[0]).toBeInstanceOf(MarkerOverlayPoint);
});
});

describe('tuples', () => {
test('in markup-less document, returns a single pair', () => {
const {peritext} = setupHelloWorldKit();
peritext.refresh();
const blocks = peritext.blocks;
const block = blocks.root.children[0]!;
const pairs = [...block.tuples()];
expect(pairs.length).toBe(1);
expect(pairs[0]).toEqual([peritext.overlay.START, peritext.overlay.END]);
});

test('can iterate through all text chunks in two-block documents', () => {
const {peritext} = setupTwoBlockDocument();
expect(peritext.blocks.root.children.length).toBe(2);
const block1 = peritext.blocks.root.children[0]!;
const block2 = peritext.blocks.root.children[1]!;
const tuples1 = [...block1.tuples()];
const tuples2 = [...block2.tuples()];
expect(tuples1.length).toBe(3);
const text1 = tuples1
.map(([p1, p2]) => Inline.create(peritext, p1, p2).text())
.join('');
const text2 = tuples2
.map(([p1, p2]) => Inline.create(peritext, p1, p2).text())
.join('');
expect(text1).toBe('hello ');
expect(text2).toBe('\nworld');
});
});
4 changes: 2 additions & 2 deletions src/json-crdt-extensions/peritext/overlay/Overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,8 @@ export class Overlay<T = string> implements Printable, Stateful {
return () => {
const pair = iterator();
if (!pair) return;
if (pair[0] === undefined) pair[0] = this.START;
if (pair[1] === undefined) pair[1] = this.END;
pair[0] ??= this.START;
pair[1] ??= this.END;
return pair as OverlayTuple<T>;
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const runPairsTests = (setup: () => Kit) => {
test('returns [START, END] single tuple for an empty overlay', () => {
const {peritext} = setup();
const overlay = peritext.overlay;
peritext.editor.delCursors();
overlay.refresh();
const list = [...overlay.tuples()];
expect(list).toEqual([[overlay.START, overlay.END]]);
Expand Down

0 comments on commit edf19ac

Please sign in to comment.