|
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