Skip to content

Commit 9cf9f6e

Browse files
authored
Merge pull request #634 from streamich/block-improvements
`Inline` attribute improvements
2 parents bc8eb9b + d1ee62c commit 9cf9f6e

File tree

8 files changed

+454
-164
lines changed

8 files changed

+454
-164
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
"json-logic-js": "^2.0.2",
146146
"rxjs": "^7.8.1",
147147
"ts-jest": "^29.1.2",
148+
"ts-node": "^10.9.2",
148149
"tslib": "^2.6.2",
149150
"tslint": "^6.1.3",
150151
"tslint-config-common": "^1.6.2",

src/json-crdt-extensions/peritext/block/Inline.ts

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
import {printTree} from 'tree-dump/lib/printTree';
22
import {OverlayPoint} from '../overlay/OverlayPoint';
33
import {stringify} from '../../../json-text/stringify';
4-
import {SliceBehavior} from '../slice/constants';
4+
import {SliceBehavior, SliceTypes} from '../slice/constants';
55
import {Range} from '../rga/Range';
66
import {ChunkSlice} from '../util/ChunkSlice';
77
import {updateNum} from '../../../json-hash';
8+
import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint';
89
import type {AbstractRga} from '../../../json-crdt/nodes/rga';
910
import type {Printable} from 'tree-dump/lib/types';
1011
import type {PathStep} from '../../../json-pointer';
11-
import type {Slice} from '../slice/types';
1212
import type {Peritext} from '../Peritext';
1313

14-
export type InlineAttributes = Record<string | number, unknown>;
14+
export const enum InlineAttrPos {
15+
/** The attribute started before this inline and ends after this inline. */
16+
Passing = 0,
17+
/** The attribute starts at the beginning of this inline. */
18+
Start = 1,
19+
/** The attribute ends at the end of this inline. */
20+
End = 2,
21+
/** The attribute starts and ends in this inline. */
22+
Contained = 3,
23+
/** The attribute is collapsed at start of this inline. */
24+
Collapsed = 4,
25+
}
26+
27+
export type InlineAttr<T> = [value: T, position: InlineAttrPos];
28+
export type InlineAttrStack = InlineAttr<unknown[]>;
29+
export type InlineAttrs = Record<string | number, InlineAttr<unknown>>;
1530

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

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

85+
protected getAttrPos(range: Range<any>): InlineAttrPos {
86+
return !range.start.cmp(range.end)
87+
? InlineAttrPos.Collapsed
88+
: !this.start.cmp(range.start)
89+
? !this.end.cmp(range.end)
90+
? InlineAttrPos.Contained
91+
: InlineAttrPos.Start
92+
: !this.end.cmp(range.end)
93+
? InlineAttrPos.End
94+
: InlineAttrPos.Passing;
95+
}
96+
97+
protected stackAttr(attr: InlineAttrs, type: string | number, data: unknown, slice: Range<any>): void {
98+
let item: InlineAttrStack | undefined = attr[type] as InlineAttrStack | undefined;
99+
if (!item) attr[type] = item = [[], this.getAttrPos(slice)];
100+
const dataList: unknown[] = item[0] instanceof Array ? (item[0] as unknown[]) : [];
101+
dataList.push(data);
102+
}
103+
70104
/**
71105
* @returns Returns the attributes of the inline, which are the slice
72106
* annotations and formatting applied to the inline.
73107
*/
74-
public attr(): InlineAttributes {
75-
const attr: InlineAttributes = {};
108+
public attr(): InlineAttrs {
109+
const attr: InlineAttrs = {};
76110
const point = this.start as OverlayPoint;
77-
const slices: Slice[] = this.texts.length ? point.layers : point.markers;
78-
const length = slices.length;
79-
for (let i = 0; i < length; i++) {
80-
const slice = slices[i];
81-
const type = slice.type as PathStep;
82-
switch (slice.behavior) {
83-
case SliceBehavior.Cursor:
84-
case SliceBehavior.Stack: {
85-
let dataList: unknown[] = (attr[type] as unknown[]) || (attr[type] = []);
86-
if (!Array.isArray(dataList)) dataList = attr[type] = [dataList];
87-
let data = slice.data();
88-
if (data === undefined) data = 1;
89-
dataList.push(data);
90-
break;
91-
}
92-
case SliceBehavior.Overwrite: {
93-
let data = slice.data();
94-
if (data === undefined) data = 1;
95-
attr[type] = data;
96-
break;
97-
}
98-
case SliceBehavior.Erase: {
99-
delete attr[type];
100-
break;
111+
const slices1 = point.layers;
112+
const slices2 = point.markers;
113+
const length1 = slices1.length;
114+
const length2 = slices2.length;
115+
const length3 = length1 + length2;
116+
for (let i = 0; i < length3; i++) {
117+
const slice = i >= length1 ? slices2[i - length1] : slices1[i];
118+
if (slice instanceof Range) {
119+
const type = slice.type as PathStep;
120+
switch (slice.behavior) {
121+
case SliceBehavior.Cursor: {
122+
this.stackAttr(attr, SliceTypes.Cursor, [type, slice.data()], slice);
123+
break;
124+
}
125+
case SliceBehavior.Stack: {
126+
this.stackAttr(attr, type, slice.data(), slice);
127+
break;
128+
}
129+
case SliceBehavior.Overwrite: {
130+
let data = slice.data();
131+
if (data === undefined) data = 1;
132+
attr[type] = [data, this.getAttrPos(slice)];
133+
break;
134+
}
135+
case SliceBehavior.Erase: {
136+
delete attr[type];
137+
break;
138+
}
101139
}
102140
}
103141
}
104-
// TODO: Iterate over the markers...
105142
return attr;
106143
}
107144

145+
public text(): string {
146+
const str = super.text();
147+
return this.start instanceof MarkerOverlayPoint ? str.slice(1) : str;
148+
}
149+
108150
// ---------------------------------------------------------------- Printable
109151

110152
public toString(tab: string = ''): string {

src/json-crdt-extensions/peritext/block/__tests__/Block.iteration.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe('tuples', () => {
115115
const text1 = tuples1.map(([p1, p2]) => Inline.create(peritext, p1, p2).text()).join('');
116116
const text2 = tuples2.map(([p1, p2]) => Inline.create(peritext, p1, p2).text()).join('');
117117
expect(text1).toBe('hello ');
118-
expect(text2).toBe('\nworld');
118+
expect(text2).toBe('world');
119119
});
120120
});
121121

@@ -137,6 +137,6 @@ describe('texts', () => {
137137
const text1 = [...block1.texts()].map((inline) => inline.text()).join('');
138138
const text2 = [...block2.texts()].map((inline) => inline.text()).join('');
139139
expect(text1).toBe('hello ');
140-
expect(text2).toBe('\nworld');
140+
expect(text2).toBe('world');
141141
});
142142
});

src/json-crdt-extensions/peritext/block/__tests__/Blocks.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,15 @@ test('can construct a two-paragraph document', () => {
3131
expect(paragraph1.marker).toBe(undefined);
3232
expect(paragraph2.marker instanceof MarkerOverlayPoint).toBe(true);
3333
});
34+
35+
test('first inline element does not contain marker text', () => {
36+
const {peritext} = setupHelloWorldKit();
37+
peritext.editor.cursor.setAt(6);
38+
peritext.editor.saved.insMarker('p');
39+
peritext.editor.delCursors();
40+
peritext.refresh();
41+
expect(peritext.strApi().view()).toBe('hello \nworld');
42+
const [block1, block2] = peritext.blocks.root.children;
43+
expect([...block1.texts()][0].text()).toBe('hello ');
44+
expect([...block2.texts()][0].text()).toBe('world');
45+
});

0 commit comments

Comments
 (0)