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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 | 57x 57x 57x 57x 57x 57x 57x 57x 57x 84x 57x 246x 57x 246x 57x 313x 57x 138x 57x 4x 57x 3261x 3261x 3261x 3261x 228x 228x 516x 516x 516x 516x 516x 1031x 1031x 1031x 1526x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1420x 1035x 1035x 1035x 1035x 349x 349x 349x 97x 97x 97x 585x 585x 4x 4x 1420x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 16x 8x 516x 516x 516x 516x 516x 516x 516x 516x 516x 516x 649x 649x 649x 438x 438x 252x 252x 252x 252x 252x 252x 252x 252x 649x | import {printTree} from 'tree-dump/lib/printTree'; import {stringify} from '../../../json-text/stringify'; import {SliceBehavior, SliceTypeName} from '../slice/constants'; import {Range} from '../rga/Range'; import {ChunkSlice} from '../util/ChunkSlice'; import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint'; import {Cursor} from '../editor/Cursor'; import {hashId} from '../../../json-crdt/hash'; import {formatType} from '../slice/util'; import type {Point} from '../rga/Point'; import type {OverlayPoint} from '../overlay/OverlayPoint'; import type {Printable} from 'tree-dump/lib/types'; import type {PathStep} from '@jsonjoy.com/json-pointer'; import type {Peritext} from '../Peritext'; import type {Slice} from '../slice/types'; import type {PeritextMlAttributes, PeritextMlNode} from './types'; /** The attribute started before this inline and ends after this inline. */ export class InlineAttrPassing { constructor(public slice: Slice) {} } /** The attribute starts at the beginning of this inline. */ export class InlineAttrStart { constructor(public slice: Slice) {} } /** The attribute ends at the end of this inline. */ export class InlineAttrEnd { constructor(public slice: Slice) {} } /** The attribute starts and ends in this inline, exactly contains it. */ export class InlineAttrContained { constructor(public slice: Slice) {} } /** The attribute is collapsed at start of this inline. */ export class InlineAttrStartPoint { constructor(public slice: Slice) {} } /** The attribute is collapsed at end of this inline. */ export class InlineAttrEndPoint { constructor(public slice: Slice) {} } export type InlineAttr = | InlineAttrPassing | InlineAttrStart | InlineAttrEnd | InlineAttrContained | InlineAttrStartPoint | InlineAttrEndPoint; export type InlineAttrStack = InlineAttr[]; export type InlineAttrs = Record<string | number, InlineAttrStack>; /** * The `Inline` class represents a range of inline text within a block, which * has the same annotations and formatting for all of its text contents, i.e. * its text contents can be rendered as a single (`<span>`) element. However, * the text contents might still be composed of multiple {@link ChunkSlice}s, * which are the smallest units of text and need to be concatenated to get the * full text content of the inline. */ export class Inline extends Range implements Printable { constructor( public readonly txt: Peritext, public readonly p1: OverlayPoint, public readonly p2: OverlayPoint, start: Point, end: Point, ) { super(txt.str, start, end); } /** * @returns A stable unique identifier of this *inline* within a list of other * inlines of the parent block. Can be used for UI libraries to track the * identity of the inline across renders. */ public key(): number { const start = this.start; return hashId(start.id) + (start.anchor ? 0 : 1); } /** * @returns The position of the inline within the text. */ public pos(): number { const chunkSlice = this.texts(1)[0]; Iif (!chunkSlice) return -1; const chunk = chunkSlice.chunk; const pos = this.rga.pos(chunk); return pos + chunkSlice.off; } protected createAttr(slice: Slice): InlineAttr { const p1 = this.p1; const p2 = this.p2; return !slice.start.cmp(slice.end) ? !slice.start.cmp(p1) ? new InlineAttrStartPoint(slice) : new InlineAttrEndPoint(slice) : !p1.cmp(slice.start) ? !p2.cmp(slice.end) ? new InlineAttrContained(slice) : new InlineAttrStart(slice) : !p2.cmp(slice.end) ? new InlineAttrEnd(slice) : new InlineAttrPassing(slice); } private _attr: InlineAttrs | undefined; /** * @returns Returns the attributes of the inline, which are the slice * annotations and formatting applied to the inline. * * @todo Rename to `.stat()`. * @todo Create a more efficient way to compute inline stats, separate: (1) * boolean flags, (2) cursor, (3) other attributes. */ public attr(): InlineAttrs { if (this._attr) return this._attr; const attr: InlineAttrs = (this._attr = {}); const p1 = this.p1 as OverlayPoint; const p2 = this.p2 as OverlayPoint; const slices1 = p1.layers; const slices2 = p1.markers; const slices3 = p2.isAbsEnd() ? p2.markers : []; const length1 = slices1.length; const length2 = slices2.length; const length3 = slices3.length; const length12 = length1 + length2; const length123 = length12 + length3; for (let i = 0; i < length123; i++) { const slice = i >= length12 ? slices3[i - length12] : i >= length1 ? slices2[i - length1] : slices1[i]; if (slice instanceof Range) { const type = slice.type as PathStep; switch (slice.behavior) { case SliceBehavior.Cursor: { const stack: InlineAttrStack = attr[SliceTypeName.Cursor] ?? (attr[SliceTypeName.Cursor] = []); stack.push(this.createAttr(slice)); break; } case SliceBehavior.Many: { const stack: InlineAttrStack = attr[type] ?? (attr[type] = []); stack.push(this.createAttr(slice)); break; } case SliceBehavior.One: { attr[type] = [this.createAttr(slice)]; break; } case SliceBehavior.Erase: { delete attr[type]; break; } } } } return attr; } public hasCursor(): boolean { return !!this.attr()[SliceTypeName.Cursor]; } /** @todo Make this return a list of cursors. */ public cursorStart(): Cursor | undefined { const attributes = this.attr(); const stack = attributes[SliceTypeName.Cursor]; Iif (!stack) return; const attribute = stack[0]; if ( attribute instanceof InlineAttrStart || attribute instanceof InlineAttrContained || attribute instanceof InlineAttrStartPoint ) { const slice = attribute.slice; return slice instanceof Cursor ? slice : void 0; } return; } public cursorEnd(): Cursor | undefined { const attributes = this.attr(); const stack = attributes[SliceTypeName.Cursor]; Iif (!stack) return; const attribute = stack[0]; if ( attribute instanceof InlineAttrEnd || attribute instanceof InlineAttrContained || attribute instanceof InlineAttrEndPoint ) { const slice = attribute.slice; return slice instanceof Cursor ? slice : void 0; } return; } /** * Returns a 2-tuple if this inline is part of a selection. The 2-tuple sides * specify how selection ends on each side. Empty string means the selection * continues past that edge, `focus` and `anchor` specify that the edge * is either a focus caret or an anchor, respectively. * * @returns Selection state of this inline. */ public selection(): undefined | [left: 'anchor' | 'focus' | '', right: 'anchor' | 'focus' | ''] { const attributes = this.attr(); const stack = attributes[SliceTypeName.Cursor]; Iif (!stack) return; const attribute = stack[0]; const cursor = attribute.slice; Iif (!(cursor instanceof Cursor)) return; Iif (attribute instanceof InlineAttrPassing) return ['', '']; Iif (attribute instanceof InlineAttrStart) return [cursor.isStartFocused() ? 'focus' : 'anchor', '']; Iif (attribute instanceof InlineAttrEnd) return ['', cursor.isEndFocused() ? 'focus' : 'anchor']; if (attribute instanceof InlineAttrContained) return cursor.isStartFocused() ? ['focus', 'anchor'] : ['anchor', 'focus']; return; } public texts(limit: number = 1e6): ChunkSlice[] { const texts: ChunkSlice[] = []; const txt = this.txt; const overlay = txt.overlay; let cnt = 0; overlay.chunkSlices0(this.start.chunk(), this.start, this.end, (chunk, off, len): boolean | void => { Iif (overlay.isMarker(chunk.id)) return; cnt++; texts.push(new ChunkSlice(chunk, off, len)); if (cnt === limit) return true; }); return texts; } // ------------------------------------------------------------------- export public toJson(): PeritextMlNode { let node: PeritextMlNode = this.text(); const attrs = this.attr(); for (const key in attrs) { const keyNum = Number(key); if (keyNum === SliceTypeName.Cursor || keyNum === SliceTypeName.RemoteCursor) continue; const attr = attrs[key]; Iif (!attr.length) node = [key, {inline: true}, node]; else { const length = attr.length; for (let i = 0; i < length; i++) { const slice = attr[i].slice; const data = slice.data(); const attributes: PeritextMlAttributes = data === void 0 ? {inline: true} : {inline: true, data}; node = [key === keyNum + '' ? keyNum : key, attributes, node]; } } } return node; } // ---------------------------------------------------------------- Printable public toStringName(): string { return 'Inline'; } public toString(tab: string = ''): string { const header = `${super.toString(tab)}`; const attr = this.attr(); const attrKeys = Object.keys(attr); const texts = this.texts(); return ( header + printTree(tab, [ !attrKeys.length ? null : (tab) => 'attributes' + printTree( tab, attrKeys.map((key) => () => { return ( formatType(key) + ' = ' + stringify( attr[key].map((attr) => attr.slice instanceof Cursor ? [attr.slice.type, attr.slice.data()] : attr.slice.data(), ), ) ); }), ), !texts.length ? null : (tab) => 'texts' + printTree( tab, this.texts().map((text) => (tab) => text.toString(tab)), ), ]) ); } } |