Skip to content

Commit b6f1119

Browse files
committed
feat(json-crdt-peritext-ui): 🎸 update undo setup
1 parent 31897c8 commit b6f1119

File tree

6 files changed

+78
-80
lines changed

6 files changed

+78
-80
lines changed

src/json-crdt-peritext-ui/dom/DomController.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {printTree, type Printable} from 'tree-dump';
2-
import {InputController} from '../dom/InputController';
3-
import {CursorController} from '../dom/CursorController';
4-
import {RichTextController} from '../dom/RichTextController';
5-
import {KeyController} from '../dom/KeyController';
6-
import {CompositionController} from '../dom/CompositionController';
2+
import {InputController} from './InputController';
3+
import {CursorController} from './CursorController';
4+
import {RichTextController} from './RichTextController';
5+
import {KeyController} from './KeyController';
6+
import {CompositionController} from './CompositionController';
7+
import {UndoRedoController} from './UndoRedoController';
78
import type {PeritextEventDefaults} from '../events/defaults/PeritextEventDefaults';
89
import type {PeritextEventTarget} from '../events/PeritextEventTarget';
910
import type {PeritextRenderingSurfaceApi, UiLifeCycles} from '../dom/types';
@@ -20,6 +21,7 @@ export class DomController implements UiLifeCycles, Printable, PeritextRendering
2021
public readonly input: InputController;
2122
public readonly cursor: CursorController;
2223
public readonly richText: RichTextController;
24+
public readonly undo: UndoRedoController;
2325

2426
constructor(public readonly opts: DomControllerOpts) {
2527
const {source, events} = opts;
@@ -30,6 +32,7 @@ export class DomController implements UiLifeCycles, Printable, PeritextRendering
3032
this.input = new InputController({et, source, txt, comp});
3133
this.cursor = new CursorController({et, source, txt, keys});
3234
this.richText = new RichTextController({et, source, txt});
35+
this.undo = new UndoRedoController();
3336
}
3437

3538
/** -------------------------------------------------- {@link UiLifeCycles} */
@@ -40,6 +43,7 @@ export class DomController implements UiLifeCycles, Printable, PeritextRendering
4043
this.input.start();
4144
this.cursor.start();
4245
this.richText.start();
46+
this.undo.start();
4347
}
4448

4549
public stop(): void {
@@ -48,6 +52,7 @@ export class DomController implements UiLifeCycles, Printable, PeritextRendering
4852
this.input.stop();
4953
this.cursor.stop();
5054
this.richText.stop();
55+
this.undo.stop();
5156
}
5257

5358
/** ----------------------------------- {@link PeritextRenderingSurfaceApi} */

src/json-crdt-peritext-ui/events/undo/DomUndoRedo.ts renamed to src/json-crdt-peritext-ui/dom/UndoRedoController.ts

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,19 @@
11
import type {Printable} from 'tree-dump';
2-
import type {UiLifeCycles} from '../../dom/types';
3-
import type {UndoRedo} from './types';
2+
import type {UiLifeCycles} from './types';
43

5-
export class DomUndoRedo<State> implements UndoRedo<State>, UiLifeCycles, Printable {
4+
export class UndoRedoController implements UiLifeCycles, Printable {
65
private _duringUpdate: boolean = false;
7-
private _stack: State[] = [];
8-
private el: HTMLElement;
6+
private _stack: unknown[] = [];
7+
private el!: HTMLElement;
98

109
constructor (
11-
public onundo?: (state: State) => void,
12-
public onredo?: (state: State) => void,
13-
) {
14-
// nb. Previous versions of this used `input` for browsers other than Firefox (as Firefox
15-
// _only_ supports execCommand on contentEditable)
16-
const el = this.el = document.createElement('div');
17-
el.tabIndex = -1;
18-
el.contentEditable = 'true';
19-
el.setAttribute('aria-hidden', 'true');
20-
const style = el.style;
21-
// style.opacity = '0';
22-
style.position = 'fixed';
23-
// style.top = '-1000px';
24-
style.top = '10px';
25-
style.left = '10px';
26-
style.pointerEvents = 'none';
27-
style.fontSize = '2px';
28-
// style.visibility = 'hidden';
29-
30-
document.body.appendChild(el);
31-
32-
el.addEventListener('focus', () => {
33-
// Timeout, as immediate blur doesn't work in some browsers.
34-
window.setTimeout(() => el.blur(), 0);
35-
});
36-
el.addEventListener('input', (ev) => {
37-
if (!this._duringUpdate) {
38-
// callback(this.data);
39-
}
40-
41-
// clear selection, otherwise user copy gesture will copy value
42-
// nb. this _probably_ won't work inside Shadow DOM
43-
// nb. this is mitigated by the fact that we set visibility: 'hidden'
44-
// const s = window.getSelection();
45-
// if (s.containsNode(this._ctrl, true)) {
46-
// s.removeAllRanges();
47-
// }
48-
});
49-
}
10+
public onundo?: (state: unknown) => void,
11+
public onredo?: (state: unknown) => void,
12+
) {}
5013

5114
/** ------------------------------------------------------ {@link UndoRedo} */
5215

53-
public do(state: State): void {
16+
public do(state: unknown): void {
5417
const activeElement = document.activeElement;
5518
const el = this.el;
5619
const style = el.style;
@@ -70,12 +33,48 @@ export class DomUndoRedo<State> implements UndoRedo<State>, UiLifeCycles, Printa
7033
/** -------------------------------------------------- {@link UiLifeCycles} */
7134

7235
public start(): void {
73-
throw new Error('Method not implemented.');
36+
const el = this.el = document.createElement('div');
37+
el.tabIndex = -1;
38+
el.contentEditable = 'true';
39+
el.setAttribute('aria-hidden', 'true');
40+
const style = el.style;
41+
// style.opacity = '0';
42+
style.position = 'fixed';
43+
// style.top = '-1000px';
44+
style.top = '10px';
45+
style.left = '10px';
46+
style.pointerEvents = 'none';
47+
style.fontSize = '2px';
48+
// style.visibility = 'hidden';
49+
document.body.appendChild(el);
50+
el.addEventListener('focus', this.onFocus);
51+
el.addEventListener('input', this.onInput);
7452
}
7553

7654
public stop(): void {
77-
throw new Error('Method not implemented.');
55+
const el = this.el;
56+
document.body.removeChild(el);
57+
el.removeEventListener('focus', this.onFocus);
58+
el.removeEventListener('input', this.onInput);
7859
}
60+
61+
public readonly onFocus = () => {
62+
window.setTimeout(() => this.el.blur(), 0);
63+
};
64+
65+
public readonly onInput = () => {
66+
if (!this._duringUpdate) {
67+
// callback(this.data);
68+
}
69+
// clear selection, otherwise user copy gesture will copy value
70+
// nb. this _probably_ won't work inside Shadow DOM
71+
// nb. this is mitigated by the fact that we set visibility: 'hidden'
72+
// const s = window.getSelection();
73+
// if (s.containsNode(this._ctrl, true)) {
74+
// s.removeAllRanges();
75+
// }
76+
};
77+
7978
/** ----------------------------------------------------- {@link Printable} */
8079

8180
public toString(tab?: string): string {

src/json-crdt-peritext-ui/events/defaults/PeritextEventDefaults.ts

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ import type {EditorSlices} from '../../../json-crdt-extensions/peritext/editor/E
88
import type * as events from '../types';
99
import type {PeritextClipboard, PeritextClipboardData} from '../clipboard/types';
1010
import type {ITimespanStruct} from '../../../json-crdt-patch';
11-
import type {UndoRedo} from '../undo/types';
11+
import type {Redo, Undo, UndoRedoCollector} from '../../types';
1212

1313
const toText = (buf: Uint8Array) => new TextDecoder().decode(buf);
1414

15+
type InsertUndoState = [text: string, after: Point<any>[], inserts: ITimespanStruct[]];
16+
1517
export interface PeritextEventDefaultsOpts {
1618
clipboard?: PeritextClipboard;
1719
transfer?: PeritextDataTransfer;
18-
undo?: UndoRedo<unknown>;
20+
undo?: UndoRedoCollector;
1921
}
2022

2123
/**
@@ -34,35 +36,28 @@ export class PeritextEventDefaults implements PeritextEventHandlerMap {
3436

3537
public readonly change = (event: CustomEvent<events.ChangeDetail>) => {};
3638

37-
private insertUndo = (text: string, after: Point<any>[], insertions: ITimespanStruct[]) => {
38-
const undo = () => {
39-
// Delete `insertions`.
40-
const redo = this.insertRedo(text, after);
41-
return redo;
42-
};
43-
return undo;
39+
private insertUndo: Undo<InsertUndoState> = ([text, after, inserts]) => {
40+
// TODO: delete `insertions`.
41+
console.log('delete', inserts);
42+
return [[text, after, inserts], this.insertRedo];
4443
};
4544

46-
private insertRedo = (text: string, after: Point<any>[]) => {
47-
const redo = () => {
48-
// Insert text.
49-
const insertions: ITimespanStruct[] = [];
50-
const undo = this.insertUndo(text, after, insertions);
51-
return undo;
52-
};
53-
return redo;
45+
private insertRedo: Redo<InsertUndoState> = ([text, after]) => {
46+
// TODO: insert `text` after `after` locations.
47+
console.log('insert', text, 'after', after);
48+
const inserts: ITimespanStruct[] = [];
49+
return [[text, after, inserts], this.insertUndo];
5450
};
5551

5652
public readonly insert = (event: CustomEvent<events.InsertDetail>) => {
5753
const text = event.detail.text;
5854
const editor = this.txt.editor;
59-
const insertions: ITimespanStruct[] = editor.insert(text);
55+
const inserts: ITimespanStruct[] = editor.insert(text);
6056
const after: Point<any>[] = [];
6157
editor.forCursor(cursor => {
6258
after.push(cursor.start.clone());
6359
});
64-
this.opts.undo?.do([123]);
65-
console.log('insertions', insertions, 'after', after);
60+
this.opts.undo?.do<InsertUndoState>([text, after, inserts], this.insertUndo);
6661
};
6762

6863
public readonly delete = (event: CustomEvent<events.DeleteDetail>) => {

src/json-crdt-peritext-ui/events/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {PeritextEventDefaults, type PeritextEventDefaultsOpts} from './defaults/
22
import {PeritextEventTarget} from './PeritextEventTarget';
33
import {DomClipboard} from './clipboard/DomClipboard';
44
import {create as createDataTransfer} from '../../json-crdt-extensions/peritext/transfer/create';
5-
import {DomUndoRedo} from './undo/DomUndoRedo';
65
import type {Peritext} from '../../json-crdt-extensions';
76

87
export const create = (txt: Peritext) => {
@@ -12,8 +11,7 @@ export const create = (txt: Peritext) => {
1211
? new DomClipboard(navigator.clipboard)
1312
: undefined;
1413
const transfer = createDataTransfer(txt);
15-
const undo = new DomUndoRedo();
16-
const defaults = new PeritextEventDefaults(txt, et, {clipboard, transfer, undo});
14+
const defaults = new PeritextEventDefaults(txt, et, {clipboard, transfer});
1715
et.defaults = defaults;
1816
return defaults;
1917
};

src/json-crdt-peritext-ui/events/undo/types.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.

src/json-crdt-peritext-ui/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface UndoRedoCollector {
2+
do<State>(state: State, undo: Undo<State>): void;
3+
}
4+
5+
export type Undo<State> = (state: State) => [state: State, redo: Redo<State>];
6+
export type Redo<State> = (state: State) => [state: State, undo: Undo<State>];

0 commit comments

Comments
 (0)