Skip to content

Commit 6834881

Browse files
committed
feat(json-crdt-extensions): 🎸 implement Printable in slice registry and add TypeTag type
1 parent 56809bd commit 6834881

File tree

4 files changed

+171
-112
lines changed

4 files changed

+171
-112
lines changed

‎src/json-crdt-extensions/peritext/editor/Editor.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {formatType} from '../slice/util';
44
import {EditorSlices} from './EditorSlices';
55
import {next, prev} from 'sonic-forest/lib/util';
66
import {printTree} from 'tree-dump/lib/printTree';
7-
import {createRegistry} from '../registry/registry';
7+
import {SliceRegistry} from '../registry/SliceRegistry';
8+
import {registerCommon} from '../registry/registerCommon';
89
import {PersistedSlice} from '../slice/PersistedSlice';
910
import {stringify} from '../../../json-text/stringify';
1011
import {CommonSliceType, type SliceTypeSteps, type SliceType, type SliceTypeStep} from '../slice';
@@ -21,7 +22,6 @@ import type {Printable} from 'tree-dump';
2122
import type {Peritext} from '../Peritext';
2223
import type {ChunkSlice} from '../util/ChunkSlice';
2324
import type {MarkerSlice} from '../slice/MarkerSlice';
24-
import type {SliceRegistry, SliceRegistryEntry} from '../registry/SliceRegistry';
2525
import type {
2626
CharIterator,
2727
CharPredicate,
@@ -34,6 +34,7 @@ import type {
3434
EditorSelection,
3535
} from './types';
3636
import type {ObjNode} from '../../../json-crdt/nodes';
37+
import type {SliceRegistryEntry} from '../registry/SliceRegistryEntry';
3738

3839
/**
3940
* For inline boolean ("Overwrite") slices, both range endpoints should be
@@ -71,6 +72,11 @@ export class Editor<T = string> implements Printable {
7172
public readonly extra: EditorSlices<T>;
7273
public readonly local: EditorSlices<T>;
7374

75+
/**
76+
* The registry holds definitions of detailed behavior of various slice tags.
77+
*/
78+
public readonly registry: SliceRegistry;
79+
7480
/**
7581
* Formatting basic inline formatting which will be applied to the next
7682
* inserted text. This is a temporary store for formatting which is not yet
@@ -80,11 +86,16 @@ export class Editor<T = string> implements Printable {
8086
*/
8187
public readonly pending = new ValueSyncStore<Map<CommonSliceType | string | number, unknown> | undefined>(void 0);
8288

89+
/**
90+
* New slice configuration. This is used for new slices which are not yet
91+
* applied to the text as they need to be configured first.
92+
*/
8393
public readonly newSliceConfig = new ValueSyncStore<NewSliceConfig | undefined>(void 0);
8494

85-
public registry: SliceRegistry = createRegistry();
86-
8795
constructor(public readonly txt: Peritext<T>) {
96+
const registry = this.registry = new SliceRegistry();
97+
registerCommon(registry); // TODO: figure out a better place to put this
98+
8899
this.saved = new EditorSlices(txt, txt.savedSlices);
89100
this.extra = new EditorSlices(txt, txt.extraSlices);
90101
this.local = new EditorSlices(txt, txt.localSlices);
@@ -1197,7 +1208,7 @@ export class Editor<T = string> implements Printable {
11971208
return txt.pointStart() ?? txt.pointAbsStart();
11981209
}
11991210

1200-
// ---------------------------------------------------------------- Printable
1211+
/** ----------------------------------------------------- {@link Printable} */
12011212

12021213
public toString(tab: string = ''): string {
12031214
const pending = this.pending.value;
@@ -1212,6 +1223,7 @@ export class Editor<T = string> implements Printable {
12121223
tab,
12131224
[...this.cursors()].map((cursor) => (tab) => cursor.toString(tab)),
12141225
),
1226+
(tab) => this.registry.toString(tab),
12151227
pending ? (() => `pending ${stringify(pendingFormatted)}`) : null,
12161228
])
12171229
);
Lines changed: 19 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,13 @@
11
import {SliceBehavior, type SliceTypeCon} from '../slice/constants';
22
import {CommonSliceType} from '../slice';
3+
import {SliceRegistryEntry} from './SliceRegistryEntry';
4+
import {printTree} from 'tree-dump/lib/printTree';
35
import type {PeritextMlElement} from '../block/types';
4-
import type {NodeBuilder} from '../../../json-crdt-patch';
56
import type {JsonMlElement} from 'very-small-parser/lib/html/json-ml/types';
67
import type {FromHtmlConverter, ToHtmlConverter} from './types';
7-
import type {JsonNodeView} from '../../../json-crdt/nodes';
8-
import type {SchemaToJsonNode} from '../../../json-crdt/schema/types';
8+
import type {Printable} from 'tree-dump';
99

10-
export type TagType = SliceTypeCon | number | string;
11-
12-
const sliceCustomData = new WeakMap<SliceRegistryEntry<any, any, any>, Record<string, unknown>>();
13-
14-
export class SliceRegistryEntry<
15-
Behavior extends SliceBehavior = SliceBehavior,
16-
Tag extends TagType = TagType,
17-
Schema extends NodeBuilder = NodeBuilder,
18-
> {
19-
public isInline(): boolean {
20-
return this.behavior !== SliceBehavior.Marker;
21-
}
22-
23-
public data(): Record<string, unknown> {
24-
const data = sliceCustomData.get(this);
25-
if (data) return data;
26-
const newData = {};
27-
sliceCustomData.set(this, newData);
28-
return newData;
29-
}
30-
31-
constructor(
32-
/**
33-
* Specifies whether the slice is an inline or block element. And if it is
34-
* an inline element, whether multiple instances of the same tag are allowed
35-
* to be applied to a range of tex - "Many", or only one instance - "One".
36-
*/
37-
public readonly behavior: Behavior,
38-
39-
/**
40-
* The tag name of this slice. The tag is one step in the type path of the
41-
* slice. For example, below is a type path composed of three steps:
42-
*
43-
* ```js
44-
* ['ul', 'li', 'p']
45-
* ```
46-
*
47-
* Tag types are normally numbers of type {@link SliceTypeCon}, however,
48-
* they can also be any arbitrary strings or numbers.
49-
*/
50-
public readonly tag: Tag,
51-
52-
/**
53-
* Default expected schema of the slice data.
54-
*/
55-
public readonly schema: Schema,
56-
57-
/**
58-
* This property is relevant only for block split markers. It specifies
59-
* whether the block split marker is a container for other block elements.
60-
*
61-
* For example, a `blockquote` is a container for `paragraph` elements,
62-
* however, a `paragraph` is not a container (it can only contain inline
63-
* elements).
64-
*
65-
* If the marker slice is of the container sort, they tag can appear in the
66-
* path steps of the type:
67-
*
68-
* ```
69-
*
70-
* ```
71-
*/
72-
public readonly container: boolean = false,
73-
74-
/**
75-
* Converts a node of this type to HTML representation: returns the HTML tag
76-
* and attributes. The method receives {@link PeritextMlElement} as an
77-
* argument, which is a tuple of internal HTML-like representation of the
78-
* node.
79-
*/
80-
public readonly toHtml:
81-
| ToHtmlConverter<
82-
PeritextMlElement<
83-
Tag,
84-
JsonNodeView<SchemaToJsonNode<Schema>>,
85-
Behavior extends SliceBehavior.Marker ? false : true
86-
>
87-
>
88-
| undefined = void 0,
89-
90-
/**
91-
* Specifies a mapping of converters from HTML {@link JsonMlElement} to
92-
* {@link PeritextMlElement}. This way a slice type can specify multiple
93-
* HTML tags that are converted to the same slice type.
94-
*
95-
* For example, both, `<b>` and `<strong>` tags can be converted to the
96-
* {@link SliceTypeCon.b} slice type.
97-
*/
98-
public readonly fromHtml?: {
99-
[htmlTag: string]: FromHtmlConverter<
100-
PeritextMlElement<
101-
Tag,
102-
JsonNodeView<SchemaToJsonNode<Schema>>,
103-
Behavior extends SliceBehavior.Marker ? false : true
104-
>
105-
>;
106-
},
107-
) {}
108-
}
10+
export type TypeTag = SliceTypeCon | number | string;
10911

11012
/**
11113
* Slice registry contains a record of possible inline an block formatting
@@ -115,10 +17,15 @@ export class SliceRegistryEntry<
11517
* @todo Consider moving the registry under the `/transfer` directory. Or maybe
11618
* `/slices` directory.
11719
*/
118-
export class SliceRegistry {
119-
private map: Map<TagType, SliceRegistryEntry> = new Map();
20+
export class SliceRegistry implements Printable {
21+
private map: Map<TypeTag, SliceRegistryEntry> = new Map();
12022
private _fromHtml: Map<string, [entry: SliceRegistryEntry, converter: FromHtmlConverter][]> = new Map();
12123

24+
public clear(): void {
25+
this.map.clear();
26+
this._fromHtml.clear();
27+
}
28+
12229
public add(entry: SliceRegistryEntry<any, any, any>): void {
12330
const {tag, fromHtml} = entry;
12431
this.map.set(tag, entry);
@@ -135,11 +42,11 @@ export class SliceRegistry {
13542
if (tagStr && typeof tagStr === 'string') _fromHtml.set(tagStr, [[entry, () => [tag, null]]]);
13643
}
13744

138-
public get(tag: TagType): SliceRegistryEntry | undefined {
45+
public get(tag: TypeTag): SliceRegistryEntry | undefined {
13946
return this.map.get(tag);
14047
}
14148

142-
public isContainer(tag: TagType): boolean {
49+
public isContainer(tag: TypeTag): boolean {
14350
const entry = this.map.get(tag);
14451
return entry?.container ?? false;
14552
}
@@ -167,4 +74,10 @@ export class SliceRegistry {
16774
}
16875
return;
16976
}
77+
78+
/** ----------------------------------------------------- {@link Printable} */
79+
80+
public toString(tab: string = ''): string {
81+
return `SliceRegistry` + printTree(tab, [...this.map.values()].map((entry) => tab => entry.toString(tab)));
82+
}
17083
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import {SliceBehavior} from '../slice/constants';
2+
import {formatType} from '../slice/util';
3+
import type {PeritextMlElement} from '../block/types';
4+
import type {NodeBuilder} from '../../../json-crdt-patch';
5+
import type {FromHtmlConverter, ToHtmlConverter} from './types';
6+
import type {JsonNodeView} from '../../../json-crdt/nodes';
7+
import type {SchemaToJsonNode} from '../../../json-crdt/schema/types';
8+
import type {Printable} from 'tree-dump';
9+
import type {TypeTag} from '../slice';
10+
11+
const sliceCustomData = new WeakMap<SliceRegistryEntry<any, any, any>, Record<string, unknown>>();
12+
13+
export class SliceRegistryEntry<
14+
Behavior extends SliceBehavior = SliceBehavior,
15+
Tag extends TypeTag = TypeTag,
16+
Schema extends NodeBuilder = NodeBuilder,
17+
> implements Printable {
18+
public isInline(): boolean {
19+
return this.behavior !== SliceBehavior.Marker;
20+
}
21+
22+
/**
23+
* An opaque object which can be mutated in-place by the rendering layer
24+
* to store custom rendering-specific data. This is useful for storing, for
25+
* example, React component references, which is specific to the rendering
26+
* layer.
27+
*
28+
* Usage:
29+
*
30+
* ```ts
31+
* registry.get(SliceTypeCon.a)?.data().ReactConfig = ReactConfigCompForLink;
32+
* ```
33+
*
34+
* @returns The custom data of the slice.
35+
*/
36+
public data(): Record<string, unknown> {
37+
const data = sliceCustomData.get(this);
38+
if (data) return data;
39+
const newData = {};
40+
sliceCustomData.set(this, newData);
41+
return newData;
42+
}
43+
44+
constructor(
45+
/**
46+
* Specifies whether the slice is an inline or block element. And if it is
47+
* an inline element, whether multiple instances of the same tag are allowed
48+
* to be applied to a range of tex - "Many", or only one instance - "One".
49+
*/
50+
public readonly behavior: Behavior,
51+
52+
/**
53+
* The tag name of this slice. The tag is one step in the type path of the
54+
* slice. For example, below is a type path composed of three steps:
55+
*
56+
* ```js
57+
* ['ul', 'li', 'p']
58+
* ```
59+
*
60+
* Tag types are normally numbers of type {@link SliceTypeCon}, however,
61+
* they can also be any arbitrary strings or numbers.
62+
*/
63+
public readonly tag: Tag,
64+
65+
/**
66+
* Default expected schema of the slice data.
67+
*/
68+
public readonly schema: Schema,
69+
70+
/**
71+
* This property is relevant only for block split markers. It specifies
72+
* whether the block split marker is a container for other block elements.
73+
*
74+
* For example, a `blockquote` is a container for `paragraph` elements,
75+
* however, a `paragraph` is not a container (it can only contain inline
76+
* elements).
77+
*
78+
* If the marker slice is of the container sort, they tag can appear in the
79+
* path steps of the type:
80+
*
81+
* ```
82+
*
83+
* ```
84+
*/
85+
public readonly container: boolean = false,
86+
87+
/**
88+
* Converts a node of this type to HTML representation: returns the HTML tag
89+
* and attributes. The method receives {@link PeritextMlElement} as an
90+
* argument, which is a tuple of internal HTML-like representation of the
91+
* node.
92+
*/
93+
public readonly toHtml:
94+
| ToHtmlConverter<
95+
PeritextMlElement<
96+
Tag,
97+
JsonNodeView<SchemaToJsonNode<Schema>>,
98+
Behavior extends SliceBehavior.Marker ? false : true
99+
>
100+
>
101+
| undefined = void 0,
102+
103+
/**
104+
* Specifies a mapping of converters from HTML {@link JsonMlElement} to
105+
* {@link PeritextMlElement}. This way a slice type can specify multiple
106+
* HTML tags that are converted to the same slice type.
107+
*
108+
* For example, both, `<b>` and `<strong>` tags can be converted to the
109+
* {@link SliceTypeCon.b} slice type.
110+
*/
111+
public readonly fromHtml?: {
112+
[htmlTag: string]: FromHtmlConverter<
113+
PeritextMlElement<
114+
Tag,
115+
JsonNodeView<SchemaToJsonNode<Schema>>,
116+
Behavior extends SliceBehavior.Marker ? false : true
117+
>
118+
>;
119+
},
120+
) {}
121+
122+
/** ----------------------------------------------------- {@link Printable} */
123+
124+
public toString(tab: string = ''): string {
125+
return `${formatType(this.tag)} (${this.behavior}) ${JSON.stringify(Object.keys(this.data))}`;
126+
}
127+
}

‎src/json-crdt-extensions/peritext/slice/types.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {Range} from '../rga/Range';
22
import type {Stateful} from '../types';
33
import type {ITimestampStruct} from '../../../json-crdt-patch/clock';
4-
import type {SliceBehavior} from './constants';
4+
import type {SliceBehavior, SliceTypeCon} from './constants';
55
import type {nodes} from '../../../json-crdt-patch';
66
import type {SchemaToJsonNode} from '../../../json-crdt/schema/types';
77
import type {JsonNodeView} from '../../../json-crdt/nodes';
@@ -45,6 +45,13 @@ export type SliceType = SliceTypeStep | SliceTypeSteps;
4545
export type SliceTypeSteps = SliceTypeStep[];
4646
export type SliceTypeStep = string | number | [tag: string | number, discriminant: number];
4747

48+
/**
49+
* Tag is number or a string, the last type element if type is a list. Tag
50+
* specifies the kind of the leaf block element. For example, if the full type
51+
* is `['ul', 'li', 'p']`, then the tag is `<p>`.
52+
*/
53+
export type TypeTag = SliceTypeCon | number | string;
54+
4855
/**
4956
* The JSON CRDT schema of the stored slices in the document. The slices are
5057
* stored compactly in "vec" nodes, with the first *header* element storing

0 commit comments

Comments
 (0)