From 0b96a0b915ea9020f01ee2e6f5545e2d9fdebcec Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Fri, 8 Nov 2024 08:51:26 +0100 Subject: [PATCH 01/22] =?UTF-8?q?refactor(json-crdt-peritext-ui):=20?= =?UTF-8?q?=F0=9F=92=A1=20rename=20plugin=20related=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-peritext-ui/__demos__/components/App.tsx | 4 ++-- .../{renderers => plugins}/debug/RenderBlock.tsx | 0 .../{renderers => plugins}/debug/RenderInline.tsx | 0 .../{renderers => plugins}/debug/RenderPeritext.tsx | 0 .../{renderers => plugins}/debug/context.ts | 0 .../{renderers => plugins}/debug/index.tsx | 4 ++-- .../{renderers/default => plugins/minimal}/Button/index.tsx | 0 .../{renderers/default => plugins/minimal}/Chrome/index.tsx | 0 .../{renderers/default => plugins/minimal}/RenderAnchor.tsx | 0 .../{renderers/default => plugins/minimal}/RenderCaret.tsx | 0 .../{renderers/default => plugins/minimal}/RenderFocus.tsx | 0 .../{renderers/default => plugins/minimal}/RenderInline.tsx | 0 .../default => plugins/minimal}/RenderPeritext.tsx | 0 .../default => plugins/minimal}/TopToolbar/index.tsx | 0 .../{renderers/default => plugins/minimal}/constants.ts | 0 .../{renderers/default => plugins/minimal}/context.ts | 0 .../{renderers/default => plugins/minimal}/index.ts | 4 ++-- src/json-crdt-peritext-ui/react/PeritextView.tsx | 6 +++--- src/json-crdt-peritext-ui/react/context.ts | 4 ++-- src/json-crdt-peritext-ui/react/types.ts | 2 +- 20 files changed, 12 insertions(+), 12 deletions(-) rename src/json-crdt-peritext-ui/{renderers => plugins}/debug/RenderBlock.tsx (100%) rename src/json-crdt-peritext-ui/{renderers => plugins}/debug/RenderInline.tsx (100%) rename src/json-crdt-peritext-ui/{renderers => plugins}/debug/RenderPeritext.tsx (100%) rename src/json-crdt-peritext-ui/{renderers => plugins}/debug/context.ts (100%) rename src/json-crdt-peritext-ui/{renderers => plugins}/debug/index.tsx (86%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/Button/index.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/Chrome/index.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/RenderAnchor.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/RenderCaret.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/RenderFocus.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/RenderInline.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/RenderPeritext.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/TopToolbar/index.tsx (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/constants.ts (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/context.ts (100%) rename src/json-crdt-peritext-ui/{renderers/default => plugins/minimal}/index.ts (93%) diff --git a/src/json-crdt-peritext-ui/__demos__/components/App.tsx b/src/json-crdt-peritext-ui/__demos__/components/App.tsx index fd93dd004e..a79f36ba4a 100644 --- a/src/json-crdt-peritext-ui/__demos__/components/App.tsx +++ b/src/json-crdt-peritext-ui/__demos__/components/App.tsx @@ -2,8 +2,8 @@ import * as React from 'react'; import {Provider, GlobalCss} from 'nano-theme'; import {ModelWithExt, ext} from '../../../json-crdt-extensions'; import {PeritextView} from '../../react'; -import {renderers} from '../../renderers/default'; -import {renderers as debugRenderers} from '../../renderers/debug'; +import {renderers} from '../../plugins/minimal'; +import {renderers as debugRenderers} from '../../plugins/debug'; export const App: React.FC = () => { const [[model, peritext]] = React.useState(() => { diff --git a/src/json-crdt-peritext-ui/renderers/debug/RenderBlock.tsx b/src/json-crdt-peritext-ui/plugins/debug/RenderBlock.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/debug/RenderBlock.tsx rename to src/json-crdt-peritext-ui/plugins/debug/RenderBlock.tsx diff --git a/src/json-crdt-peritext-ui/renderers/debug/RenderInline.tsx b/src/json-crdt-peritext-ui/plugins/debug/RenderInline.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/debug/RenderInline.tsx rename to src/json-crdt-peritext-ui/plugins/debug/RenderInline.tsx diff --git a/src/json-crdt-peritext-ui/renderers/debug/RenderPeritext.tsx b/src/json-crdt-peritext-ui/plugins/debug/RenderPeritext.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/debug/RenderPeritext.tsx rename to src/json-crdt-peritext-ui/plugins/debug/RenderPeritext.tsx diff --git a/src/json-crdt-peritext-ui/renderers/debug/context.ts b/src/json-crdt-peritext-ui/plugins/debug/context.ts similarity index 100% rename from src/json-crdt-peritext-ui/renderers/debug/context.ts rename to src/json-crdt-peritext-ui/plugins/debug/context.ts diff --git a/src/json-crdt-peritext-ui/renderers/debug/index.tsx b/src/json-crdt-peritext-ui/plugins/debug/index.tsx similarity index 86% rename from src/json-crdt-peritext-ui/renderers/debug/index.tsx rename to src/json-crdt-peritext-ui/plugins/debug/index.tsx index 7fc232b485..69308820ee 100644 --- a/src/json-crdt-peritext-ui/renderers/debug/index.tsx +++ b/src/json-crdt-peritext-ui/plugins/debug/index.tsx @@ -2,9 +2,9 @@ import * as React from 'react'; import {RenderInline} from './RenderInline'; import {RenderBlock} from './RenderBlock'; import {RenderPeritext, type RenderPeritextProps} from './RenderPeritext'; -import type {RendererMap} from '../../react/types'; +import type {PeritextPlugin} from '../../react/types'; -export const renderers = (options?: Pick): RendererMap => ({ +export const renderers = (options?: Pick): PeritextPlugin => ({ inline: (props, children) => {children}, block: (props, children) => {children}, peritext: (props, children, ctx) => ( diff --git a/src/json-crdt-peritext-ui/renderers/default/Button/index.tsx b/src/json-crdt-peritext-ui/plugins/minimal/Button/index.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/Button/index.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/Button/index.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/Chrome/index.tsx b/src/json-crdt-peritext-ui/plugins/minimal/Chrome/index.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/Chrome/index.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/Chrome/index.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/RenderAnchor.tsx b/src/json-crdt-peritext-ui/plugins/minimal/RenderAnchor.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/RenderAnchor.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/RenderAnchor.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/RenderCaret.tsx b/src/json-crdt-peritext-ui/plugins/minimal/RenderCaret.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/RenderCaret.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/RenderCaret.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/RenderFocus.tsx b/src/json-crdt-peritext-ui/plugins/minimal/RenderFocus.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/RenderFocus.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/RenderFocus.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/RenderInline.tsx b/src/json-crdt-peritext-ui/plugins/minimal/RenderInline.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/RenderInline.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/RenderInline.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/RenderPeritext.tsx b/src/json-crdt-peritext-ui/plugins/minimal/RenderPeritext.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/RenderPeritext.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/RenderPeritext.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/TopToolbar/index.tsx b/src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/TopToolbar/index.tsx rename to src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx diff --git a/src/json-crdt-peritext-ui/renderers/default/constants.ts b/src/json-crdt-peritext-ui/plugins/minimal/constants.ts similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/constants.ts rename to src/json-crdt-peritext-ui/plugins/minimal/constants.ts diff --git a/src/json-crdt-peritext-ui/renderers/default/context.ts b/src/json-crdt-peritext-ui/plugins/minimal/context.ts similarity index 100% rename from src/json-crdt-peritext-ui/renderers/default/context.ts rename to src/json-crdt-peritext-ui/plugins/minimal/context.ts diff --git a/src/json-crdt-peritext-ui/renderers/default/index.ts b/src/json-crdt-peritext-ui/plugins/minimal/index.ts similarity index 93% rename from src/json-crdt-peritext-ui/renderers/default/index.ts rename to src/json-crdt-peritext-ui/plugins/minimal/index.ts index 35edd63bea..b3299dfddc 100644 --- a/src/json-crdt-peritext-ui/renderers/default/index.ts +++ b/src/json-crdt-peritext-ui/plugins/minimal/index.ts @@ -5,11 +5,11 @@ import {RenderAnchor} from './RenderAnchor'; import {RenderInline} from './RenderInline'; import {RenderPeritext} from './RenderPeritext'; import {CommonSliceType} from '../../../json-crdt-extensions'; -import type {RendererMap} from '../../react/types'; +import type {PeritextPlugin} from '../../react/types'; const h = React.createElement; -export const renderers: RendererMap = { +export const renderers: PeritextPlugin = { text: (props, inline) => { const style = (props.style || (props.style = {})) as React.CSSProperties; const attr = inline.attr(); diff --git a/src/json-crdt-peritext-ui/react/PeritextView.tsx b/src/json-crdt-peritext-ui/react/PeritextView.tsx index 6e90880493..bf71d407f0 100644 --- a/src/json-crdt-peritext-ui/react/PeritextView.tsx +++ b/src/json-crdt-peritext-ui/react/PeritextView.tsx @@ -4,9 +4,9 @@ import {context, type PeritextSurfaceContextValue} from './context'; import {CssClass} from '../constants'; import {BlockView} from './BlockView'; import {DomController} from '../dom/DomController'; -import {renderers as defaultRenderers} from '../renderers/default'; +import {renderers as defaultRenderers} from '../plugins/minimal'; import type {Peritext} from '../../json-crdt-extensions/peritext/Peritext'; -import type {RendererMap} from './types'; +import type {PeritextPlugin} from './types'; put('.' + CssClass.Editor, { out: 0, @@ -29,7 +29,7 @@ put('.' + CssClass.Editor, { */ export interface PeritextViewProps { peritext: Peritext; - renderers?: RendererMap[]; + renderers?: PeritextPlugin[]; onRender?: () => void; } diff --git a/src/json-crdt-peritext-ui/react/context.ts b/src/json-crdt-peritext-ui/react/context.ts index dfff404c26..e3569c47e8 100644 --- a/src/json-crdt-peritext-ui/react/context.ts +++ b/src/json-crdt-peritext-ui/react/context.ts @@ -1,11 +1,11 @@ import * as React from 'react'; -import type {RendererMap} from './types'; +import type {PeritextPlugin} from './types'; import type {Peritext} from '../../json-crdt-extensions/peritext/Peritext'; import type {DomController} from '../dom/DomController'; export interface PeritextSurfaceContextValue { peritext: Peritext; - renderers: RendererMap[]; + renderers: PeritextPlugin[]; dom: DomController; rerender: () => void; } diff --git a/src/json-crdt-peritext-ui/react/types.ts b/src/json-crdt-peritext-ui/react/types.ts index c0fc283bb5..b0ef43c1b2 100644 --- a/src/json-crdt-peritext-ui/react/types.ts +++ b/src/json-crdt-peritext-ui/react/types.ts @@ -8,7 +8,7 @@ import type {PeritextViewProps} from './PeritextView'; import type {PeritextSurfaceContextValue} from './context'; import type {Inline} from '../../json-crdt-extensions/peritext/block/Inline'; -export interface RendererMap { +export interface PeritextPlugin { focus?: (props: FocusViewProps, children: React.ReactNode) => React.ReactNode; caret?: (props: CaretViewProps, children: React.ReactNode) => React.ReactNode; anchor?: (props: AnchorViewProps, children: React.ReactNode) => React.ReactNode; From 71859ec7220dc938352603e5d6813c7649222e7a Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 8 Nov 2024 19:13:15 +0100 Subject: [PATCH 02/22] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20move=20TypedEv?= =?UTF-8?q?entTarget=20to=20utils=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/json-crdt-peritext-ui/dom/InputController.ts | 2 +- src/json-crdt-peritext-ui/events/PeritextEventTarget.ts | 2 +- src/{json-crdt-peritext-ui => util}/events/TypedEventTarget.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{json-crdt-peritext-ui => util}/events/TypedEventTarget.ts (100%) diff --git a/src/json-crdt-peritext-ui/dom/InputController.ts b/src/json-crdt-peritext-ui/dom/InputController.ts index 1947d5aa4b..2ce3177fa1 100644 --- a/src/json-crdt-peritext-ui/dom/InputController.ts +++ b/src/json-crdt-peritext-ui/dom/InputController.ts @@ -1,7 +1,7 @@ import {unit} from './util'; import type {Peritext} from '../../json-crdt-extensions/peritext'; import type {PeritextEventTarget} from '../events/PeritextEventTarget'; -import type {TypedEventTarget} from '../events/TypedEventTarget'; +import type {TypedEventTarget} from '../../util/events/TypedEventTarget'; import type {CompositionController} from './CompositionController'; import type {UiLifeCycles} from './types'; diff --git a/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts b/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts index 0df4a0628e..d0a40ae817 100644 --- a/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts +++ b/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts @@ -1,4 +1,4 @@ -import {TypedEventTarget} from './TypedEventTarget'; +import {TypedEventTarget} from '../../util/events/TypedEventTarget'; import type {PeritextEventMap, CursorDetail, InlineDetail, DeleteDetail} from './types'; export type PeritextEventHandlerMap = { diff --git a/src/json-crdt-peritext-ui/events/TypedEventTarget.ts b/src/util/events/TypedEventTarget.ts similarity index 100% rename from src/json-crdt-peritext-ui/events/TypedEventTarget.ts rename to src/util/events/TypedEventTarget.ts From 08823fc3459073d55b0a65f4f94da64a033a8926 Mon Sep 17 00:00:00 2001 From: streamich Date: Fri, 8 Nov 2024 20:01:35 +0100 Subject: [PATCH 03/22] =?UTF-8?q?feat(json-crdt-peritext-ui):=20?= =?UTF-8?q?=F0=9F=8E=B8=20improve=20boolean=20slice=20insertion=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../peritext/editor/Editor.ts | 35 +++++++++++++++++++ .../events/PeritextEventDefaults.ts | 24 +++++-------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/json-crdt-extensions/peritext/editor/Editor.ts b/src/json-crdt-extensions/peritext/editor/Editor.ts index 3d699c4513..47c51111c9 100644 --- a/src/json-crdt-extensions/peritext/editor/Editor.ts +++ b/src/json-crdt-extensions/peritext/editor/Editor.ts @@ -6,12 +6,31 @@ import {isLetter, isPunctuation, isWhitespace} from './util'; import {Anchor} from '../rga/constants'; import {MarkerOverlayPoint} from '../overlay/MarkerOverlayPoint'; import {UndefEndIter, type UndefIterator} from '../../../util/iterator'; +import type {SliceType} from '../slice'; import type {ChunkSlice} from '../util/ChunkSlice'; import type {Peritext} from '../Peritext'; import type {Point} from '../rga/Point'; import type {Range} from '../rga/Range'; import type {CharIterator, CharPredicate, Position, TextRangeUnit} from './types'; +/** + * For inline boolean ("Overwrite") slices, both range endpoints should be + * attached to {@link Anchor.Before} as per the Peritext paper. This way, say + * bold text, automatically extends to include the next character typed as + * user types. + * + * @param range The range to be adjusted. + */ +const makeRangeExtendable = (range: Range): void => { + if (range.end.anchor !== Anchor.Before || range.start.anchor !== Anchor.Before) { + const start = range.start.clone(); + const end = range.end.clone(); + start.refBefore(); + end.refBefore(); + range.set(start, end); + } +}; + export class Editor { public readonly saved: EditorSlices; public readonly extra: EditorSlices; @@ -461,6 +480,22 @@ export class Editor { if (unit) this.select(unit); } + // --------------------------------------------------------------- formatting + + public formatExclusive(type: SliceType, data?: unknown, slices: EditorSlices = this.saved): void { + // TODO: handle mutually exclusive slices (, ) + const overlay = this.txt.overlay; + overlay.refresh(); // TODO: Refresh for `overlay.stat()` calls. Is it actually needed? + for (let i = this.cursors0(), cursor = i(); cursor; cursor = i()) { + const [complete] = overlay.stat(cursor, 1e6); + const alreadyFormatted = complete.has(type); + if (alreadyFormatted) continue; + makeRangeExtendable(cursor); + if (cursor.start.isAbs() || cursor.end.isAbs()) continue; + slices.insOverwrite(type, data); + } + } + // ------------------------------------------------------------------ various public point(at: Position): Point { diff --git a/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts b/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts index 01ff7d6b95..eae3fdf41a 100644 --- a/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts +++ b/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts @@ -104,26 +104,18 @@ export class PeritextEventDefaults implements PeritextEventHandlerMap { const editor = this.txt.editor; const slices: EditorSlices = store === 'saved' ? editor.saved : store === 'extra' ? editor.extra : editor.local; switch (behavior) { - case 'stack': + case 'stack': { slices.insStack(type, data); break; - case 'erase': + } + case 'overwrite': { + editor.formatExclusive(type, data, slices); + break; + } + case 'erase': { slices.insErase(type, data); break; - default: - for (let i = editor.cursors0(), cursor = i(); cursor; cursor = i()) { - // For inline boolean slices, ref endpoint "before" the next character - // as per the Peritext paper, so that, say, bold text automatically - // includes the next character typed. - if (cursor.end.anchor !== Anchor.Before || cursor.start.anchor !== Anchor.Before) { - const start = cursor.start.clone(); - const end = cursor.end.clone(); - start.refBefore(); - end.refBefore(); - cursor.set(start, end); - } - } - slices.insOverwrite(type, data); + } } }; From 453e0e8d7554ad364795f2df82fe77898753b7fb Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 9 Nov 2024 16:34:00 +0100 Subject: [PATCH 04/22] =?UTF-8?q?feat(json-crdt-peritext-ui):=20?= =?UTF-8?q?=F0=9F=8E=B8=20improve=20inline=20"format"=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dom/RichTextController.ts | 8 +-- .../events/PeritextEventDefaults.ts | 7 +-- .../events/PeritextEventTarget.ts | 20 +++---- .../events/__tests__/inline.spec.ts | 2 +- src/json-crdt-peritext-ui/events/types.ts | 55 +++++++++++++++++-- .../plugins/minimal/TopToolbar/index.tsx | 2 +- 6 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/json-crdt-peritext-ui/dom/RichTextController.ts b/src/json-crdt-peritext-ui/dom/RichTextController.ts index 514ac29fb1..b805ebd6f8 100644 --- a/src/json-crdt-peritext-ui/dom/RichTextController.ts +++ b/src/json-crdt-peritext-ui/dom/RichTextController.ts @@ -31,15 +31,15 @@ export class RichTextController implements UiLifeCycles { switch (key) { case 'b': event.preventDefault(); - et.inline(CommonSliceType.b); + et.format(CommonSliceType.b); return; case 'i': event.preventDefault(); - et.inline(CommonSliceType.i); + et.format(CommonSliceType.i); return; case 'u': event.preventDefault(); - et.inline(CommonSliceType.u); + et.format(CommonSliceType.u); return; } } @@ -47,7 +47,7 @@ export class RichTextController implements UiLifeCycles { switch (key) { case 'x': event.preventDefault(); - et.inline(CommonSliceType.s); + et.format(CommonSliceType.s); return; } } diff --git a/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts b/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts index eae3fdf41a..95c012f165 100644 --- a/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts +++ b/src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts @@ -1,6 +1,5 @@ import type {Peritext} from '../../json-crdt-extensions/peritext'; import type {EditorSlices} from '../../json-crdt-extensions/peritext/editor/EditorSlices'; -import {Anchor} from '../../json-crdt-extensions/peritext/rga/constants'; import {CursorAnchor} from '../../json-crdt-extensions/peritext/slice/constants'; import type {PeritextEventHandlerMap, PeritextEventTarget} from './PeritextEventTarget'; import type * as events from './types'; @@ -99,16 +98,16 @@ export class PeritextEventDefaults implements PeritextEventHandlerMap { } }; - public readonly inline = (event: CustomEvent) => { + public readonly format = (event: CustomEvent) => { const {type, store = 'saved', behavior = 'overwrite', data} = event.detail; const editor = this.txt.editor; const slices: EditorSlices = store === 'saved' ? editor.saved : store === 'extra' ? editor.extra : editor.local; switch (behavior) { - case 'stack': { + case 'many': { slices.insStack(type, data); break; } - case 'overwrite': { + case 'one': { editor.formatExclusive(type, data, slices); break; } diff --git a/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts b/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts index d0a40ae817..4b172502c6 100644 --- a/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts +++ b/src/json-crdt-peritext-ui/events/PeritextEventTarget.ts @@ -1,5 +1,5 @@ import {TypedEventTarget} from '../../util/events/TypedEventTarget'; -import type {PeritextEventMap, CursorDetail, InlineDetail, DeleteDetail} from './types'; +import type {PeritextEventMap, CursorDetail, FormatDetail, DeleteDetail} from './types'; export type PeritextEventHandlerMap = { [K in keyof PeritextEventMap]: (event: CustomEvent) => void; @@ -51,15 +51,15 @@ export class PeritextEventTarget extends TypedEventTarget { this.cursor({len, unit, edge}); } - public inline(type: InlineDetail['type'], behavior?: InlineDetail['behavior'], data?: InlineDetail['data']): void; - public inline(detail: InlineDetail): void; - public inline( - a: InlineDetail | InlineDetail['type'], - behavior?: InlineDetail['behavior'], - data?: InlineDetail['data'], + public format(type: FormatDetail['type'], behavior?: FormatDetail['behavior'], data?: FormatDetail['data']): void; + public format(detail: FormatDetail): void; + public format( + a: FormatDetail | FormatDetail['type'], + behavior?: FormatDetail['behavior'], + data?: FormatDetail['data'], ): void { - const detail: InlineDetail = - typeof a === 'object' && !Array.isArray(a) ? (a as InlineDetail) : ({type: a, behavior, data} as InlineDetail); - this.dispatch('inline', detail); + const detail: FormatDetail = + typeof a === 'object' && !Array.isArray(a) ? (a as FormatDetail) : ({type: a, behavior, data} as FormatDetail); + this.dispatch('format', detail); } } diff --git a/src/json-crdt-peritext-ui/events/__tests__/inline.spec.ts b/src/json-crdt-peritext-ui/events/__tests__/inline.spec.ts index 7cba41189b..b569f4b076 100644 --- a/src/json-crdt-peritext-ui/events/__tests__/inline.spec.ts +++ b/src/json-crdt-peritext-ui/events/__tests__/inline.spec.ts @@ -14,7 +14,7 @@ const testSuite = (getKit: () => Kit) => { test('can add annotation to the current selection', async () => { const kit = setup(); kit.et.cursor({at: 3, len: 3}); - kit.et.inline({type: 'bold'}); + kit.et.format({type: 'bold'}); kit.peritext.refresh(); const slices = kit.peritext.overlay.findOverlapping(kit.peritext.rangeAt(5)); console.log([...slices]); diff --git a/src/json-crdt-peritext-ui/events/types.ts b/src/json-crdt-peritext-ui/events/types.ts index 34a44a29ff..316f85e149 100644 --- a/src/json-crdt-peritext-ui/events/types.ts +++ b/src/json-crdt-peritext-ui/events/types.ts @@ -6,7 +6,7 @@ import type {SliceType} from '../../json-crdt-extensions/peritext/slice/types'; * Dispatched every time any other event is dispatched. */ export interface ChangeDetail { - ev?: CustomEvent; + ev?: CustomEvent; } /** @@ -174,11 +174,56 @@ export interface CursorDetail { /** * Event dispatched to insert an inline rich-text annotation into the document. */ -export interface InlineDetail { - type: SliceType; +export interface FormatDetail { + /** + * Type of the annotation. The type is used to determine the visual style of + * the annotation, for example, the type `'bold'` may render the text in bold. + * + * For common formatting use the {@link CommonSliceType} enum. It contains + * a unique numeric value for each common formatting types. Numeric values + * are best for performance and memory usage. Values in the rage -64 to 64 are + * reserved for common formatting types. + * + * For custom formatting, you can use a string value, for example, + * `'highlight'`. Or use an integer with absolute value greater than 64. + * + * Inline formatting types are restricted to a single string or integer value. + * Nester formatting, say `['p', 'blockquote', 'h1']` is reserved for block + * formatting, in which case a nested structure like + * `

text

` is created. + */ + type: number | string; + + /** + * Arbitrary data associated with the formatting. Usually, stored with + * annotations of "stack" behavior, for example, an "" tag annotation may + * store the href attribute in this field. + */ data?: unknown; - behavior?: 'stack' | 'overwrite' | 'erase'; + + /** + * Specifies the behavior of the annotation. If `'many'`, the annotation of + * this type will be stacked on top of each other, and all of them will be + * applied to the text, with the last annotation on top. If `'one'`, + * the annotation is not stacked, only one such annotation can be applied per + * character. The `'erase'` behavior is used to remove the `'many`' or + * `'one'` annotation from the the given range. + */ + behavior?: 'many' | 'one' | 'erase'; + + /** + * The slice set where the annotation will be stored. `'saved'` is the main + * document, which is persisted and replicated across all clients. `'extra'` + * is an ephemeral document, which is not persisted but can be replicated + * across clients. `'local'` is a local document, which is accessible only to + * the local client, for example, for storing cursor or selection information. + */ store?: 'saved' | 'extra' | 'local'; + + /** + * Specifies the range of the annotation. If not specified, the annotation + * is applied to all cursors in the document at their current positions. + */ pos?: [start: Position, end: Position][]; } @@ -205,6 +250,6 @@ export type PeritextEventMap = { insert: InsertDetail; delete: DeleteDetail; cursor: CursorDetail; - inline: InlineDetail; + format: FormatDetail; marker: MarkerDetail; }; diff --git a/src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx b/src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx index 845e06bc43..68bb8aa00c 100644 --- a/src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx +++ b/src/json-crdt-peritext-ui/plugins/minimal/TopToolbar/index.tsx @@ -30,7 +30,7 @@ export const TopToolbar: React.FC = () => { const button = (type: string | number, name: React.ReactNode) => (