From fb45ba71f150766900572fd5fa05a5a6282843c3 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Tue, 26 Nov 2024 10:37:22 +0100 Subject: [PATCH] =?UTF-8?q?feat(json-crdt-extensions):=20=F0=9F=8E=B8=20im?= =?UTF-8?q?prove=20JSOM-ML=20generation=20for=20block=20nodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../peritext/block/Block.ts | 30 ++++++++++++++++++- .../peritext/block/Fragment.ts | 6 ++++ .../peritext/block/LeafBlock.ts | 2 +- ...JsonMl.spec.ts => Fragment-export.spec.ts} | 3 +- .../peritext/slice/constants.ts | 22 ++++++++------ src/json-ml/types.ts | 3 +- 6 files changed, 53 insertions(+), 13 deletions(-) rename src/json-crdt-extensions/peritext/block/__tests__/{Fragment-toJsonMl.spec.ts => Fragment-export.spec.ts} (90%) diff --git a/src/json-crdt-extensions/peritext/block/Block.ts b/src/json-crdt-extensions/peritext/block/Block.ts index ce55fa4c1a..19c9775586 100644 --- a/src/json-crdt-extensions/peritext/block/Block.ts +++ b/src/json-crdt-extensions/peritext/block/Block.ts @@ -5,6 +5,7 @@ import {UndefEndIter, type UndefIterator} from '../../../util/iterator'; import {Inline} from './Inline'; import {formatType} from '../slice/util'; import {Range} from '../rga/Range'; +import {SliceTypeName} from '../slice/constants'; import type {Point} from '../rga/Point'; import type {OverlayPoint} from '../overlay/OverlayPoint'; import type {Path} from '@jsonjoy.com/json-pointer'; @@ -12,7 +13,7 @@ import type {Printable} from 'tree-dump'; import type {Peritext} from '../Peritext'; import type {Stateful} from '../types'; import type {OverlayTuple} from '../overlay/types'; -import type {JsonMlNode} from '../../../json-ml'; +import type {JsonMlElement, JsonMlNode} from '../../../json-ml'; export interface IBlock { readonly path: Path; @@ -52,6 +53,33 @@ export class Block extends Range implements IBlock, Printable, S return length ? path[length - 1] : ''; } + public htmlTag(): string { + const tag = this.tag(); + switch (typeof tag) { + case 'string': return tag.toLowerCase(); + case 'number': return SliceTypeName[tag] || 'div'; + default: return 'div'; + } + } + + public jsonMlNode(): JsonMlElement { + const props: Record = {}; + const node: JsonMlElement = ['div', props]; + const tag = this.tag(); + switch (typeof tag) { + case 'string': + node[0] = tag; + break; + case 'number': + const tag0 = SliceTypeName[tag]; + if (tag0) node[0] = tag0; else props['data-tag'] = tag + ''; + break; + } + const attr = this.attr(); + if (attr !== undefined) props['data-attr'] = JSON.stringify(attr); + return node; + } + public attr(): Attr | undefined { return this.marker?.data() as Attr | undefined; } diff --git a/src/json-crdt-extensions/peritext/block/Fragment.ts b/src/json-crdt-extensions/peritext/block/Fragment.ts index 8f86984c24..de72d8eb73 100644 --- a/src/json-crdt-extensions/peritext/block/Fragment.ts +++ b/src/json-crdt-extensions/peritext/block/Fragment.ts @@ -11,6 +11,7 @@ import type {Printable} from 'tree-dump/lib/types'; import type {Peritext} from '../Peritext'; import type {Point} from '../rga/Point'; import type {JsonMlNode} from '../../../json-ml/types'; +import {toHtml} from '../../../json-ml'; /** * A *fragment* represents a structural slice of a rich-text document. A @@ -36,6 +37,11 @@ export class Fragment extends Range implements Printable, Stateful { return this.root.toJsonMl(); } + toHtml(): string { + const json = this.root.toJsonMl(); + return toHtml(json); + } + // ---------------------------------------------------------------- Printable public toString(tab: string = ''): string { diff --git a/src/json-crdt-extensions/peritext/block/LeafBlock.ts b/src/json-crdt-extensions/peritext/block/LeafBlock.ts index 95b70cfcb0..5ad23d95d5 100644 --- a/src/json-crdt-extensions/peritext/block/LeafBlock.ts +++ b/src/json-crdt-extensions/peritext/block/LeafBlock.ts @@ -14,7 +14,7 @@ export class LeafBlock extends Block { // ------------------------------------------------------------------- export toJsonMl(): JsonMlNode { - let node: JsonMlNode = ['div', {}]; + const node = this.jsonMlNode(); for (const inline of this.texts()) { const span = inline.toJsonMl(); if (span) node.push(span); diff --git a/src/json-crdt-extensions/peritext/block/__tests__/Fragment-toJsonMl.spec.ts b/src/json-crdt-extensions/peritext/block/__tests__/Fragment-export.spec.ts similarity index 90% rename from src/json-crdt-extensions/peritext/block/__tests__/Fragment-toJsonMl.spec.ts rename to src/json-crdt-extensions/peritext/block/__tests__/Fragment-export.spec.ts index 98ac6d7272..b9096b8165 100644 --- a/src/json-crdt-extensions/peritext/block/__tests__/Fragment-toJsonMl.spec.ts +++ b/src/json-crdt-extensions/peritext/block/__tests__/Fragment-export.spec.ts @@ -2,12 +2,13 @@ import { type Kit, setupAlphabetKit, } from '../../__tests__/setup'; +import {CommonSliceType} from '../../slice'; const runTests = (setup: () => Kit) => { test('...', () => { const {editor, peritext} = setup(); editor.cursor.setAt(10); - editor.saved.insMarker(['p'], 'p1'); + editor.saved.insMarker(CommonSliceType.p); peritext.refresh(); const fragment = peritext.fragment(peritext.rangeAt(4, 10)); fragment.refresh(); diff --git a/src/json-crdt-extensions/peritext/slice/constants.ts b/src/json-crdt-extensions/peritext/slice/constants.ts index b0eff56020..f779bda965 100644 --- a/src/json-crdt-extensions/peritext/slice/constants.ts +++ b/src/json-crdt-extensions/peritext/slice/constants.ts @@ -9,7 +9,7 @@ export enum CursorAnchor { End = 1, } -export enum SliceTypeCon { +export const enum SliceTypeCon { // ---------------------------------------------------- block slices (0 to 64) p = 0, //

blockquote = 1, //

@@ -17,7 +17,7 @@ export enum SliceTypeCon { pre = 3, //
   ul = 4, // 
    ol = 5, //
      - TaskList = 6, // - [ ] Task list + tasklist = 6, // - [ ] Task list h1 = 7, //

      h2 = 8, //

      h3 = 9, //

      @@ -37,9 +37,9 @@ export enum SliceTypeCon { table = 23, // row = 24, // Table row cell = 25, // Table cell - CollapseList = 26, // Collapsible list - > List item - Collapse = 27, // Collapsible block - Note = 28, // Note block + collapselist = 26, // Collapsible list - > List item + collapse = 27, // Collapsible block + note = 28, // Note block // ------------------------------------------------ inline slices (-64 to -1) Cursor = -1, @@ -69,6 +69,10 @@ export enum SliceTypeCon { bookmark = -25, // UI for creating a link to this slice } +/** + * All type name must be fully lowercase, as HTML custom element tag names must + * be lowercase. + */ export enum SliceTypeName { p = SliceTypeCon.p, blockquote = SliceTypeCon.blockquote, @@ -76,7 +80,7 @@ export enum SliceTypeName { pre = SliceTypeCon.pre, ul = SliceTypeCon.ul, ol = SliceTypeCon.ol, - TaskList = SliceTypeCon.TaskList, + tasklist = SliceTypeCon.tasklist, h1 = SliceTypeCon.h1, h2 = SliceTypeCon.h2, h3 = SliceTypeCon.h3, @@ -96,9 +100,9 @@ export enum SliceTypeName { table = SliceTypeCon.table, row = SliceTypeCon.row, cell = SliceTypeCon.cell, - CollapseList = SliceTypeCon.CollapseList, - Collapse = SliceTypeCon.Collapse, - Note = SliceTypeCon.Note, + collapselist = SliceTypeCon.collapselist, + collapse = SliceTypeCon.collapse, + note = SliceTypeCon.note, Cursor = SliceTypeCon.Cursor, RemoteCursor = SliceTypeCon.RemoteCursor, diff --git a/src/json-ml/types.ts b/src/json-ml/types.ts index 8c2564bd11..6e6b43af11 100644 --- a/src/json-ml/types.ts +++ b/src/json-ml/types.ts @@ -1 +1,2 @@ -export type JsonMlNode = string | [tag: string, attrs: null | Record, ...children: JsonMlNode[]]; +export type JsonMlNode = string | JsonMlElement; +export type JsonMlElement = [tag: string, attrs: null | Record, ...children: JsonMlNode[]];