|
1 |
| -import {SliceBehavior} from '../slice/constants'; |
| 1 | +import {SliceBehavior, type SliceTypeCon} from '../slice/constants'; |
2 | 2 | import {CommonSliceType} from '../slice';
|
3 | 3 | import type {PeritextMlElement} from '../block/types';
|
4 | 4 | import type {NodeBuilder} from '../../../json-crdt-patch';
|
5 | 5 | import type {JsonMlElement} from 'very-small-parser/lib/html/json-ml/types';
|
6 |
| -import type {FromHtmlConverter, SliceTypeDefinition, ToHtmlConverter} from './types'; |
| 6 | +import type {FromHtmlConverter, ToHtmlConverter} from './types'; |
| 7 | +import type {JsonNodeView} from '../../../json-crdt/nodes'; |
| 8 | +import type {SchemaToJsonNode} from '../../../json-crdt/schema/types'; |
| 9 | + |
| 10 | +export type TagType = SliceTypeCon | number | string; |
| 11 | + |
| 12 | +export class SliceRegistryEntry< |
| 13 | + Behavior extends SliceBehavior = SliceBehavior, |
| 14 | + Tag extends TagType = TagType, |
| 15 | + Schema extends NodeBuilder = NodeBuilder, |
| 16 | +> { |
| 17 | + public isInline(): boolean { |
| 18 | + return this.behavior !== SliceBehavior.Marker; |
| 19 | + } |
| 20 | + |
| 21 | + constructor( |
| 22 | + public readonly behavior: Behavior, |
| 23 | + |
| 24 | + /** |
| 25 | + * The tag name of this slice. The tag is one step in the type path of the |
| 26 | + * slice. For example, below is a type path composed of three steps: |
| 27 | + * |
| 28 | + * ```js |
| 29 | + * ['ul', 'li', 'p'] |
| 30 | + * ``` |
| 31 | + * |
| 32 | + * Tag types are normally numbers of type {@link SliceTypeCon}, however, |
| 33 | + * they can also be any arbitrary strings or numbers. |
| 34 | + */ |
| 35 | + public readonly tag: Tag, |
| 36 | + |
| 37 | + /** |
| 38 | + * Default expected schema of the slice data. |
| 39 | + */ |
| 40 | + public readonly schema: Schema, |
| 41 | + |
| 42 | + /** |
| 43 | + * This property is relevant only for block split markers. It specifies |
| 44 | + * whether the block split marker is a container for other block elements. |
| 45 | + * |
| 46 | + * For example, a `blockquote` is a container for `paragraph` elements, |
| 47 | + * however, a `paragraph` is not a container (it can only contain inline |
| 48 | + * elements). |
| 49 | + * |
| 50 | + * If the marker slice is of the container sort, they tag can appear in the |
| 51 | + * path steps of the type: |
| 52 | + * |
| 53 | + * ``` |
| 54 | + * |
| 55 | + * ``` |
| 56 | + */ |
| 57 | + public readonly container: boolean = false, |
| 58 | + |
| 59 | + /** |
| 60 | + * Converts a node of this type to HTML representation: returns the HTML tag |
| 61 | + * and attributes. The method receives {@link PeritextMlElement} as an |
| 62 | + * argument, which is a tuple of internal HTML-like representation of the |
| 63 | + * node. |
| 64 | + */ |
| 65 | + public readonly toHtml: |
| 66 | + | ToHtmlConverter< |
| 67 | + PeritextMlElement< |
| 68 | + Tag, |
| 69 | + JsonNodeView<SchemaToJsonNode<Schema>>, |
| 70 | + Behavior extends SliceBehavior.Marker ? false : true |
| 71 | + > |
| 72 | + > |
| 73 | + | undefined = void 0, |
| 74 | + |
| 75 | + /** |
| 76 | + * Specifies a mapping of converters from HTML {@link JsonMlElement} to |
| 77 | + * {@link PeritextMlElement}. This way a slice type can specify multiple |
| 78 | + * HTML tags that are converted to the same slice type. |
| 79 | + * |
| 80 | + * For example, both, `<b>` and `<strong>` tags can be converted to the |
| 81 | + * {@link SliceTypeCon.b} slice type. |
| 82 | + */ |
| 83 | + public readonly fromHtml?: { |
| 84 | + [htmlTag: string]: FromHtmlConverter< |
| 85 | + PeritextMlElement< |
| 86 | + Tag, |
| 87 | + JsonNodeView<SchemaToJsonNode<Schema>>, |
| 88 | + Behavior extends SliceBehavior.Marker ? false : true |
| 89 | + > |
| 90 | + >; |
| 91 | + }, |
| 92 | + ) {} |
| 93 | +} |
7 | 94 |
|
8 | 95 | /**
|
9 |
| - * @todo Consider moving the registry under the `/transfer` directory. |
| 96 | + * @todo Consider moving the registry under the `/transfer` directory. Or maybe |
| 97 | + * `/slices` directory. |
10 | 98 | */
|
11 | 99 | export class SliceRegistry {
|
12 |
| - private map: Map<string | number, SliceTypeDefinition<any, any, any>> = new Map(); |
13 |
| - private toHtmlMap: Map<string | number, ToHtmlConverter<any>> = new Map(); |
14 |
| - private fromHtmlMap: Map<string, [def: SliceTypeDefinition<any, any, any>, converter: FromHtmlConverter][]> = |
15 |
| - new Map(); |
| 100 | + private map: Map<TagType, SliceRegistryEntry> = new Map(); |
| 101 | + private _fromHtml: Map<string, [entry: SliceRegistryEntry, converter: FromHtmlConverter][]> = new Map(); |
16 | 102 |
|
17 |
| - public add<Type extends number | string, Schema extends NodeBuilder, Inline extends boolean = true>( |
18 |
| - def: SliceTypeDefinition<Type, Schema, Inline>, |
19 |
| - ): void { |
20 |
| - const {type, toHtml, fromHtml} = def; |
21 |
| - const fromHtmlMap = this.fromHtmlMap; |
22 |
| - if (toHtml) this.toHtmlMap.set(type, toHtml); |
| 103 | + public add(entry: SliceRegistryEntry<any, any, any>): void { |
| 104 | + const {tag, fromHtml} = entry; |
| 105 | + const _fromHtml = this._fromHtml; |
23 | 106 | if (fromHtml) {
|
24 | 107 | for (const htmlTag in fromHtml) {
|
25 | 108 | const converter = fromHtml[htmlTag];
|
26 |
| - const converters = fromHtmlMap.get(htmlTag) ?? []; |
27 |
| - converters.push([def, converter]); |
28 |
| - fromHtmlMap.set(htmlTag, converters); |
| 109 | + const converters = _fromHtml.get(htmlTag) ?? []; |
| 110 | + converters.push([entry, converter]); |
| 111 | + _fromHtml.set(htmlTag, converters); |
29 | 112 | }
|
30 | 113 | }
|
31 |
| - const tag = CommonSliceType[type as any]; |
32 |
| - if (tag && typeof tag === 'string') { |
33 |
| - fromHtmlMap.set(tag, [[def, () => [type, null]]]); |
34 |
| - } |
35 |
| - } |
36 |
| - |
37 |
| - public def<Type extends number | string, Schema extends NodeBuilder, Inline extends boolean = true>( |
38 |
| - type: Type, |
39 |
| - schema: Schema, |
40 |
| - behavior: SliceBehavior, |
41 |
| - inline: boolean, |
42 |
| - rest: Omit<SliceTypeDefinition<Type, Schema, Inline>, 'type' | 'schema' | 'behavior' | 'inline'> = {}, |
43 |
| - ): void { |
44 |
| - this.add({type, schema, behavior, inline, ...rest}); |
| 114 | + const tagStr = CommonSliceType[tag as SliceTypeCon]; |
| 115 | + if (tagStr && typeof tagStr === 'string') _fromHtml.set(tagStr, [[entry, () => [tag, null]]]); |
45 | 116 | }
|
46 | 117 |
|
47 | 118 | public toHtml(el: PeritextMlElement): ReturnType<ToHtmlConverter<any>> | undefined {
|
48 |
| - const converter = this.toHtmlMap.get(el[0]); |
49 |
| - return converter ? converter(el) : undefined; |
| 119 | + const entry = this.map.get(el[0]); |
| 120 | + return entry?.toHtml ? entry?.toHtml(el) : void 0; |
50 | 121 | }
|
51 | 122 |
|
52 | 123 | public fromHtml(el: JsonMlElement): PeritextMlElement | undefined {
|
53 | 124 | const tag = el[0] + '';
|
54 |
| - const converters = this.fromHtmlMap.get(tag); |
| 125 | + const converters = this._fromHtml.get(tag); |
55 | 126 | if (converters) {
|
56 |
| - for (const [def, converter] of converters) { |
| 127 | + for (const [entry, converter] of converters) { |
57 | 128 | const result = converter(el);
|
58 | 129 | if (result) {
|
59 |
| - const attr = result[1] ?? (result[1] = {}); |
60 |
| - attr.inline = def.inline ?? def.type < 0; |
61 |
| - attr.behavior = !attr.inline ? SliceBehavior.Marker : (def.behavior ?? SliceBehavior.Many); |
| 130 | + if (entry.isInline()) { |
| 131 | + const attr = result[1] ?? (result[1] = {}); |
| 132 | + attr.inline = entry.isInline(); |
| 133 | + attr.behavior = !attr.inline ? SliceBehavior.Marker : (entry.behavior ?? SliceBehavior.Many); |
| 134 | + } |
62 | 135 | return result;
|
63 | 136 | }
|
64 | 137 | }
|
|
0 commit comments