Skip to content

Commit

Permalink
Merge pull request #737 from streamich/peritext-events
Browse files Browse the repository at this point in the history
Peritext events
  • Loading branch information
streamich authored Oct 31, 2024
2 parents 45a5aa7 + 79421dc commit c46ec42
Show file tree
Hide file tree
Showing 5 changed files with 428 additions and 0 deletions.
112 changes: 112 additions & 0 deletions src/json-crdt-peritext-ui/events/PeritextEventDefaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type {Peritext} from '../../json-crdt-extensions/peritext';
import type {EditorSlices} from '../../json-crdt-extensions/peritext/editor/EditorSlices';
import type {PeritextEventHandlerMap, PeritextEventTarget} from './PeritextEventTarget';
import type * as events from './types';

export class PeritextEventDefaults implements PeritextEventHandlerMap {
public constructor(
protected readonly txt: Peritext,
protected readonly et: PeritextEventTarget,
) {}

public readonly change = (event: CustomEvent<events.ChangeDetail>) => {
// console.log('change', event?.detail.ev?.type, event?.detail.ev?.detail);
};

public readonly insert = (event: CustomEvent<events.InsertDetail>) => {
if (event.defaultPrevented) return;
const text = event.detail.text;
this.txt.editor.insert(text);
this.et.change(event);
};

public readonly delete = (event: CustomEvent<events.DeleteDetail>) => {
if (event.defaultPrevented) return;
const {direction = -1, unit = 'char'} = event.detail;
this.txt.editor.delete(direction, unit);
this.et.change(event);
};

public readonly cursor = (event: CustomEvent<events.CursorDetail>) => {
const {at, edge, len = 0, unit} = event.detail;
const txt = this.txt;
const editor = txt.editor;

// If `at` is specified.
if (typeof at === 'number' && at >= 0) {
const point = editor.point(at);
switch (edge) {
case 'focus':
case 'anchor': {
editor.cursor.setEndpoint(point, edge === 'focus' ? 0 : 1);
break;
}
case 'new': {
editor.addCursor(txt.range(point, point.clone()));
break;
}
default: {
// both
if (!!len && typeof len === 'number') {
const point2 = point.clone();
point2.step(len);
const range = txt.rangeFromPoints(point, point2);
editor.cursor.set(range.start, range.end);
} else {
editor.cursor.set(point);
}
}
}
this.et.change(event);
return;
}

// If `edge` is specified.
const isSpecificEdgeSelected = edge === 'focus' || edge === 'anchor';
if (isSpecificEdgeSelected) {
editor.move(len, unit ?? 'char', edge === 'focus' ? 0 : 1, false);
this.et.change(event);
return;
}

// If `len` is specified.
if (len) {
const cursor = editor.cursor;
if (cursor.isCollapsed()) editor.move(len, unit ?? 'char');
else {
if (len > 0) cursor.collapseToEnd();
else cursor.collapseToStart();
}
this.et.change(event);
return;
}

// If `unit` is specified.
if (unit) {
editor.select(unit);
this.et.change(event);
return;
}
};

public readonly inline = (event: CustomEvent<events.InlineDetail>) => {
const {type, store = 'saved', behavior = 'overwrite', data, pos} = event.detail;
const editor = this.txt.editor;
const slices: EditorSlices = store === 'saved' ? editor.saved : store === 'extra' ? editor.extra : editor.local;
switch (behavior) {
case 'stack':
slices.insStack(type, data);
break;
case 'erase':
slices.insErase(type, data);
break;
default:
slices.insOverwrite(type, data);
}
this.et.change(event);
};

public readonly marker = (event: CustomEvent<events.MarkerDetail>) => {
throw new Error('Not implemented');
};
}
44 changes: 44 additions & 0 deletions src/json-crdt-peritext-ui/events/PeritextEventTarget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {TypedEventTarget} from './TypedEventTarget';
import {type PeritextEventMap, CursorDetail, InlineDetail, DeleteDetail} from './types';

export type PeritextEventHandlerMap = {
[K in keyof PeritextEventMap]: (event: CustomEvent<PeritextEventMap[K]>) => void;
};

let id = 0;

export class PeritextEventTarget extends TypedEventTarget<PeritextEventMap> {
public readonly id: number = id++;

public defaults: Partial<PeritextEventHandlerMap> = {};

public dispatch<K extends keyof PeritextEventMap>(type: K, detail: PeritextEventMap[K]): void {
const event = new CustomEvent<PeritextEventMap[K]>(type, {detail});
this.dispatchEvent(event);
this.defaults[type]?.(event);
}

public change(ev?: CustomEvent<any>): void {
this.dispatch('change', {ev});
}

public insert(text: string): void {
this.dispatch('insert', {text});
}

public delete(direction?: -1 | 0 | 1, unit?: DeleteDetail['unit']): void {
this.dispatch('delete', {direction, unit});
}

public cursor(detail: CursorDetail): void {
this.dispatch('cursor', detail);
}

public move(len: number, unit?: CursorDetail['unit'], edge?: CursorDetail['edge']): void {
this.cursor({len, unit, edge});
}

public inline(detail: InlineDetail): void {
this.dispatch('inline', detail);
}
}
75 changes: 75 additions & 0 deletions src/json-crdt-peritext-ui/events/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Peritext Events

## Editing

### The `insert` event

- [ ] Insert a character.
- [ ] Insert a string.

### The `delete` event

- [ ] Delete one character backward.
- [ ] Delete one character forward.
- [ ] Delete one word backward.
- [ ] Delete one word forward.

### Clipboard events

- [ ] Export range.
- [ ] Import range.
- [ ] Insert plain text.
- [ ] Insert HTML.

### Imperative API

- [ ] Delete range.
- [ ] Delete all annotations in range.
- [ ] Find block by position.
- [ ] Find annotations by position.
- [ ] Get block range.

## Overlays

- Events
- [ ] `mark` event
- [ ] Annotate range.
- [ ] Insert block split.
- [ ] `mark-update` event
- [ ] Change annotation metadata.
- [ ] Change annotation range.
- [ ] Change block metadata.
- [ ] `mark-delete` event
- [ ] Delete block split.
- [ ] Delete annotation.

### The `inline` event

### The `marker` event

### Imperative API

- [ ] Insert an overlay.
- [ ] Update overlay metadata.
- [ ] Delete an overlay.
- [ ] Change overlay range.
- [ ] Assign a set of overlays.
- [ ] Remove an overlay set.

## Navigation

### The `cursor` event

- [ ] Move one character left.
- [ ] Move one character right.
- [ ] Move one word left.
- [ ] Move one word right.
- [ ] Move left until end of line.
- [ ] Move right until end of line.
- [ ] Move up one line.
- [ ] Move down one line.

### History

- [ ] Undo.
- [ ] Redo.
59 changes: 59 additions & 0 deletions src/json-crdt-peritext-ui/events/TypedEventTarget.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const buildTypedEventTargetType = () => {
const klass = class TypedEventTarget<EventMap> {
addEventListener<K extends keyof EventMap>(
type: K,
listener: (this: HTMLElement, ev: EventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: AddEventListenerOptions | boolean,
): void;
addEventListener(): void {}
dispatchEvent(event: EventMap[keyof EventMap]): boolean;
dispatchEvent(event: Event): boolean;
dispatchEvent(): boolean {
return true;
}
removeEventListener<K extends keyof EventMap>(
type: K,
listener: (this: HTMLElement, ev: EventMap[K]) => any,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: EventListenerOptions | boolean,
): void;
removeEventListener(): void {}
};
return EventTarget as unknown as typeof klass;
};

export const TypedEventTarget = buildTypedEventTargetType();

export interface TypedEventTarget<EventMap> {
addEventListener<K extends keyof EventMap>(
type: K,
listener: (this: HTMLElement, ev: EventMap[K]) => any,
options?: boolean | AddEventListenerOptions,
): void;
addEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: AddEventListenerOptions | boolean,
): void;
dispatchEvent(event: EventMap[keyof EventMap]): boolean;
dispatchEvent(event: Event): boolean;
removeEventListener<K extends keyof EventMap>(
type: K,
listener: (this: HTMLElement, ev: EventMap[K]) => any,
options?: boolean | EventListenerOptions,
): void;
removeEventListener(
type: string,
callback: EventListenerOrEventListenerObject | null,
options?: EventListenerOptions | boolean,
): void;
}
Loading

0 comments on commit c46ec42

Please sign in to comment.