Skip to content

Commit e2fb502

Browse files
committed
wip
1 parent 531a1ee commit e2fb502

File tree

5 files changed

+117
-0
lines changed

5 files changed

+117
-0
lines changed

packages/quill/src/blots/cursor.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,14 @@ class Cursor extends EmbedBlot {
7272
this.parent = null;
7373
}
7474

75+
resetText() {
76+
const newText = this.textNode.data.split(Cursor.CONTENTS).join('');
77+
this.textNode.data = Cursor.CONTENTS;
78+
return newText;
79+
}
80+
7581
restore(): EmbedContextRange | null {
82+
return;
7683
if (this.selection.composing || this.parent == null) return null;
7784
const range = this.selection.getNativeRange();
7885
// Browser may push down styles/nodes inside the cursor blot.

packages/quill/src/blots/embed.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class Embed extends EmbedBlot {
3737
}
3838

3939
restore(node: Text): EmbedContextRange | null {
40+
return;
4041
let range: EmbedContextRange | null = null;
4142
let textNode: Text;
4243
const text = node.data.split(GUARD_TEXT).join('');

packages/quill/src/core.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Keyboard from './modules/keyboard.js';
2222
import Uploader from './modules/uploader.js';
2323
import Delta, { Op, OpIterator, AttributeMap } from 'quill-delta';
2424
import Input from './modules/input.js';
25+
import CursorModule from './modules/cursor.js';
2526
import UINode from './modules/uiNode.js';
2627

2728
export { default as Module } from './core/module.js';
@@ -50,6 +51,7 @@ Quill.register({
5051
'modules/keyboard': Keyboard,
5152
'modules/uploader': Uploader,
5253
'modules/input': Input,
54+
'modules/cursor': CursorModule,
5355
'modules/uiNode': UINode,
5456
});
5557

packages/quill/src/core/quill.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ class Quill {
228228
this.history = this.theme.addModule('history');
229229
this.uploader = this.theme.addModule('uploader');
230230
this.theme.addModule('input');
231+
// this.theme.addModule('cursor');
231232
this.theme.addModule('uiNode');
232233
this.theme.init();
233234
this.emitter.on(Emitter.events.EDITOR_CHANGE, (type) => {

packages/quill/src/modules/cursor.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import Delta from 'quill-delta';
2+
import Module from '../core/module.js';
3+
import Quill from '../core/quill.js';
4+
import CursorBlot from '../blots/cursor.js';
5+
import type { Range } from '../core/selection.js';
6+
import { deleteRange } from './keyboard.js';
7+
import Embed from '../blots/embed.js';
8+
9+
const INSERT_TYPES = ['insertText', 'insertReplacementText'];
10+
11+
class Cursor extends Module {
12+
constructor(quill: Quill, options: Record<string, never>) {
13+
super(quill, options);
14+
15+
quill.root.addEventListener('beforeinput', (event) => {
16+
this.handleBeforeInput(event);
17+
});
18+
19+
quill.emitter.on(Quill.events.COMPOSITION_END, () => {
20+
this.quill.update(Quill.sources.USER);
21+
22+
const selection = document.getSelection();
23+
if (selection == null || selection.rangeCount <= 0) return null;
24+
const node = selection.getRangeAt(0).startContainer;
25+
const blot = quill.scroll.find(node, true);
26+
if (blot instanceof CursorBlot) {
27+
this.restoreCursor(blot, blot.resetText());
28+
}
29+
});
30+
}
31+
32+
private restoreCursor(cursor: CursorBlot, text: string) {
33+
const offset = cursor.offset(this.quill.scroll);
34+
const formats = this.quill.getFormat(offset);
35+
this.quill.insertText(offset, text, formats, Quill.sources.USER);
36+
cursor.remove();
37+
this.quill.update(Quill.sources.SILENT);
38+
this.quill.setSelection(offset + text.length, 0, Quill.sources.SILENT);
39+
}
40+
41+
private restoreEmbed(embed: Embed, side: 'left' | 'right', text: string) {
42+
const offset = embed.offset(this.quill.scroll) + (side === 'left' ? 0 : 1);
43+
const formats = this.quill.getFormat(offset);
44+
this.quill.insertText(offset, text, formats, Quill.sources.USER);
45+
this.quill.update(Quill.sources.SILENT);
46+
this.quill.setSelection(offset + text.length, 0, Quill.sources.SILENT);
47+
}
48+
49+
private handleBeforeInput(event: InputEvent) {
50+
if (this.quill.composition.isComposing || event.defaultPrevented) {
51+
return;
52+
}
53+
54+
if (event.inputType !== 'insertText') {
55+
return;
56+
}
57+
58+
const staticRange = event.getTargetRanges
59+
? event.getTargetRanges()[0]
60+
: null;
61+
if (!staticRange || !staticRange.collapsed) {
62+
return;
63+
}
64+
65+
const text = getPlainTextFromInputEvent(event);
66+
if (text == null) {
67+
return;
68+
}
69+
70+
const blot = this.quill.scroll.find(staticRange.startContainer, true);
71+
console.log(blot);
72+
73+
this.quill.update(Quill.sources.USER);
74+
75+
if (blot instanceof CursorBlot) {
76+
this.restoreCursor(blot, text);
77+
event.preventDefault();
78+
} else if (blot instanceof Embed) {
79+
this.restoreEmbed(
80+
blot,
81+
blot.leftGuard === staticRange.startContainer ? 'left' : 'right',
82+
text,
83+
);
84+
event.preventDefault();
85+
}
86+
}
87+
}
88+
89+
function getPlainTextFromInputEvent(event: InputEvent) {
90+
// When `inputType` is "insertText":
91+
// - `event.data` should be string (Safari uses `event.dataTransfer`).
92+
// - `event.dataTransfer` should be null.
93+
// When `inputType` is "insertReplacementText":
94+
// - `event.data` should be null.
95+
// - `event.dataTransfer` should contain "text/plain" data.
96+
97+
if (typeof event.data === 'string') {
98+
return event.data;
99+
}
100+
if (event.dataTransfer?.types.includes('text/plain')) {
101+
return event.dataTransfer.getData('text/plain');
102+
}
103+
return null;
104+
}
105+
106+
export default Cursor;

0 commit comments

Comments
 (0)