Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 9x 9x 9x 1782x 9x 9x 9x 778x 778x 778x 1777x 3789x 3789x 3789x 2002x 2002x 2002x 2002x 467x 467x 673x 673x 673x 673x 673x 673x 673x 1787x 1787x 1787x 5x 5x 5x 5x 5x 5x 5x 1782x 1782x 1782x 1782x 1782x 1782x 1581x 1581x 1581x 1581x 1782x 2560x 2560x 2560x 2560x 3789x 3789x 3789x 3789x 3789x 778x 778x 778x 778x | import {Anchor} from 'json-joy/lib/json-crdt-extensions/peritext/rga/constants';
import {SliceHeaderShift, SliceStacking} from 'json-joy/lib/json-crdt-extensions/peritext/slice/constants';
import type {ViewRange, ViewSlice} from 'json-joy/lib/json-crdt-extensions/peritext/editor/types';
import type {SlateDocument, SlateDescendantNode, SlateTextNode, SlateElementNode} from '../types';
import type {SliceTypeStep, SliceTypeSteps} from 'json-joy/lib/json-crdt-extensions/peritext';
const isText = (node: unknown): node is SlateTextNode =>
typeof node === 'object' && !!node && typeof (node as SlateTextNode).text === 'string';
const INLINE_ATOMIC_PLACEHOLDER = '\uFFFC';
export interface FromSlateOptions {
/**
* Predicate that returns `true` for Slate elements that should be treated
* as inline (rather than block) when serializing. Inline elements are
* emitted as an `Atomic` slice covering a single placeholder character,
* letting them roundtrip without splitting the surrounding paragraph.
*/
isInline?: (element: SlateElementNode) => boolean;
}
/**
* Converts Slate.js state to a {@link ViewRange} flat string with
* annotation ranges, which is the natural view format for a Peritext model.
*
* Usage:
*
* ```typescript
* FromSlate.convert(node);
* FromSlate.convert(node, {isInline: (el) => editor.isInline(el)});
* ```
*/
export class FromSlate {
static readonly convert = (doc: SlateDocument, options?: FromSlateOptions): ViewRange =>
new FromSlate(options).convert(doc);
private text = '';
private slices: ViewSlice[] = [];
private readonly isInlineEl: (element: SlateElementNode) => boolean;
constructor(options: FromSlateOptions = {}) {
this.isInlineEl = options.isInline ?? (() => false);
}
private conv(node: SlateDescendantNode, path: SliceTypeSteps, nodeDiscriminator: number): void {
Iif (!node || typeof node !== 'object') return;
const start = this.text.length;
if ('text' in node) {
const {text, ...tagMap} = node as SlateTextNode;
this.text += text;
const tags = Object.keys(tagMap);
if (tags.length) {
const end = start + text.length;
for (const tag of tags) {
const data = tagMap[tag];
const dataEmpty = !data || data === true;
const stacking: SliceStacking = dataEmpty ? SliceStacking.One : SliceStacking.Many;
const header =
(stacking << SliceHeaderShift.Stacking) +
(Anchor.Before << SliceHeaderShift.X1Anchor) +
(Anchor.After << SliceHeaderShift.X2Anchor);
const slice: ViewSlice = [header, start, end, tag];
if (!dataEmpty) slice.push(data);
this.slices.push(slice);
}
}
} else {
const element = node as SlateElementNode;
const {type, children, ...data} = element;
// Inline elements: emit as an `Atomic` slice over a single placeholder character.
if (this.isInlineEl(element)) {
this.text += INLINE_ATOMIC_PLACEHOLDER;
const end = start + INLINE_ATOMIC_PLACEHOLDER.length;
const header =
(SliceStacking.Atomic << SliceHeaderShift.Stacking) +
(Anchor.Before << SliceHeaderShift.X1Anchor) +
(Anchor.After << SliceHeaderShift.X2Anchor);
const hasData = Object.keys(data).length > 0;
const slice: ViewSlice = hasData ? [header, start, end, type, data] : [header, start, end, type];
this.slices.push(slice);
return;
}
const step: SliceTypeStep = nodeDiscriminator || data ? [type, nodeDiscriminator, data] : type;
const length = children?.length ?? 0;
const hasNoChildren = length === 0;
const isFirstChildInline = isText((children as SlateElementNode['children'])?.[0]);
const doEmitSplitMarker = hasNoChildren || isFirstChildInline;
if (doEmitSplitMarker) {
this.text += '\n';
const header =
(SliceStacking.Marker << SliceHeaderShift.Stacking) +
(Anchor.Before << SliceHeaderShift.X1Anchor) +
(Anchor.Before << SliceHeaderShift.X2Anchor);
const slice: ViewSlice = [header, start, start, [...path, step]];
this.slices.push(slice);
}
Eif (length > 0) this.cont([...path, step], children!);
}
}
private cont(path: SliceTypeSteps, content: SlateDescendantNode[]): void {
let prevTag: string = '';
let discriminator: number = 0;
const length = content.length;
for (let i = 0; i < length; i++) {
const child = content[i];
const tag = child.type as string;
discriminator = tag === prevTag ? discriminator + 1 : 0;
this.conv(child, path, discriminator);
prevTag = tag;
}
}
public convert(node: SlateDocument): ViewRange {
let length = 0;
Eif (node && (length = node.length) > 0) this.cont([], node);
const viewRange = [this.text, 0, this.slices] as ViewRange;
return viewRange;
}
}
|