Skip to content

Commit

Permalink
Merge pull request #634 from streamich/block-improvements
Browse files Browse the repository at this point in the history
`Inline` attribute improvements
  • Loading branch information
streamich authored Jun 8, 2024
2 parents bc8eb9b + d1ee62c commit 9cf9f6e
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 164 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"json-logic-js": "^2.0.2",
"rxjs": "^7.8.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"tslint": "^6.1.3",
"tslint-config-common": "^1.6.2",
Expand Down
104 changes: 73 additions & 31 deletions src/json-crdt-extensions/peritext/block/Inline.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
import {printTree} from 'tree-dump/lib/printTree';
import {OverlayPoint} from '../overlay/OverlayPoint';
import {stringify} from '../../../json-text/stringify';
import {SliceBehavior} from '../slice/constants';
import {SliceBehavior, SliceTypes} from '../slice/constants';
import {Range} from '../rga/Range';
import {ChunkSlice} from '../util/ChunkSlice';
import {updateNum} from '../../../json-hash';
import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
import type {AbstractRga} from '../../../json-crdt/nodes/rga';
import type {Printable} from 'tree-dump/lib/types';
import type {PathStep} from '../../../json-pointer';
import type {Slice} from '../slice/types';
import type {Peritext} from '../Peritext';

export type InlineAttributes = Record<string | number, unknown>;
export const enum InlineAttrPos {
/** The attribute started before this inline and ends after this inline. */
Passing = 0,
/** The attribute starts at the beginning of this inline. */
Start = 1,
/** The attribute ends at the end of this inline. */
End = 2,
/** The attribute starts and ends in this inline. */
Contained = 3,
/** The attribute is collapsed at start of this inline. */
Collapsed = 4,
}

export type InlineAttr<T> = [value: T, position: InlineAttrPos];
export type InlineAttrStack = InlineAttr<unknown[]>;
export type InlineAttrs = Record<string | number, InlineAttr<unknown>>;

/**
* The `Inline` class represents a range of inline text within a block, which
Expand Down Expand Up @@ -57,7 +72,7 @@ export class Inline extends Range implements Printable {
}

/**
* @returns The position of the inline withing the text.
* @returns The position of the inline within the text.
*/
public pos(): number {
const chunkSlice = this.texts[0];
Expand All @@ -67,44 +82,71 @@ export class Inline extends Range implements Printable {
return pos + chunkSlice.off;
}

protected getAttrPos(range: Range<any>): InlineAttrPos {
return !range.start.cmp(range.end)
? InlineAttrPos.Collapsed
: !this.start.cmp(range.start)
? !this.end.cmp(range.end)
? InlineAttrPos.Contained
: InlineAttrPos.Start
: !this.end.cmp(range.end)
? InlineAttrPos.End
: InlineAttrPos.Passing;
}

protected stackAttr(attr: InlineAttrs, type: string | number, data: unknown, slice: Range<any>): void {
let item: InlineAttrStack | undefined = attr[type] as InlineAttrStack | undefined;
if (!item) attr[type] = item = [[], this.getAttrPos(slice)];
const dataList: unknown[] = item[0] instanceof Array ? (item[0] as unknown[]) : [];
dataList.push(data);
}

/**
* @returns Returns the attributes of the inline, which are the slice
* annotations and formatting applied to the inline.
*/
public attr(): InlineAttributes {
const attr: InlineAttributes = {};
public attr(): InlineAttrs {
const attr: InlineAttrs = {};
const point = this.start as OverlayPoint;
const slices: Slice[] = this.texts.length ? point.layers : point.markers;
const length = slices.length;
for (let i = 0; i < length; i++) {
const slice = slices[i];
const type = slice.type as PathStep;
switch (slice.behavior) {
case SliceBehavior.Cursor:
case SliceBehavior.Stack: {
let dataList: unknown[] = (attr[type] as unknown[]) || (attr[type] = []);
if (!Array.isArray(dataList)) dataList = attr[type] = [dataList];
let data = slice.data();
if (data === undefined) data = 1;
dataList.push(data);
break;
}
case SliceBehavior.Overwrite: {
let data = slice.data();
if (data === undefined) data = 1;
attr[type] = data;
break;
}
case SliceBehavior.Erase: {
delete attr[type];
break;
const slices1 = point.layers;
const slices2 = point.markers;
const length1 = slices1.length;
const length2 = slices2.length;
const length3 = length1 + length2;
for (let i = 0; i < length3; i++) {
const slice = i >= length1 ? slices2[i - length1] : slices1[i];
if (slice instanceof Range) {
const type = slice.type as PathStep;
switch (slice.behavior) {
case SliceBehavior.Cursor: {
this.stackAttr(attr, SliceTypes.Cursor, [type, slice.data()], slice);
break;
}
case SliceBehavior.Stack: {
this.stackAttr(attr, type, slice.data(), slice);
break;
}
case SliceBehavior.Overwrite: {
let data = slice.data();
if (data === undefined) data = 1;
attr[type] = [data, this.getAttrPos(slice)];
break;
}
case SliceBehavior.Erase: {
delete attr[type];
break;
}
}
}
}
// TODO: Iterate over the markers...
return attr;
}

public text(): string {
const str = super.text();
return this.start instanceof MarkerOverlayPoint ? str.slice(1) : str;
}

// ---------------------------------------------------------------- Printable

public toString(tab: string = ''): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe('tuples', () => {
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');
expect(text2).toBe('world');
});
});

Expand All @@ -137,6 +137,6 @@ describe('texts', () => {
const text1 = [...block1.texts()].map((inline) => inline.text()).join('');
const text2 = [...block2.texts()].map((inline) => inline.text()).join('');
expect(text1).toBe('hello ');
expect(text2).toBe('\nworld');
expect(text2).toBe('world');
});
});
12 changes: 12 additions & 0 deletions src/json-crdt-extensions/peritext/block/__tests__/Blocks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ test('can construct a two-paragraph document', () => {
expect(paragraph1.marker).toBe(undefined);
expect(paragraph2.marker instanceof MarkerOverlayPoint).toBe(true);
});

test('first inline element does not contain marker text', () => {
const {peritext} = setupHelloWorldKit();
peritext.editor.cursor.setAt(6);
peritext.editor.saved.insMarker('p');
peritext.editor.delCursors();
peritext.refresh();
expect(peritext.strApi().view()).toBe('hello \nworld');
const [block1, block2] = peritext.blocks.root.children;
expect([...block1.texts()][0].text()).toBe('hello ');
expect([...block2.texts()][0].text()).toBe('world');
});
Loading

0 comments on commit 9cf9f6e

Please sign in to comment.