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 1796x 9x 9x 9x 789x 789x 789x 1791x 3777x 3777x 3777x 1976x 1976x 1976x 1976x 414x 414x 663x 663x 663x 663x 663x 663x 663x 1801x 1801x 1801x 5x 5x 5x 5x 5x 5x 5x 1796x 1796x 1796x 1796x 1796x 1796x 1595x 1595x 1595x 1595x 1796x 2585x 2585x 2585x 2585x 3777x 3777x 3777x 3777x 3777x 789x 789x 789x 789x | 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;
}
}
|