Skip to content

Commit

Permalink
Handle text replacements explicitly
Browse files Browse the repository at this point in the history
  • Loading branch information
luin authored and jhchen committed Jun 22, 2023
1 parent 382bf48 commit 1c2521e
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 41 deletions.
2 changes: 2 additions & 0 deletions core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import History from './modules/history';
import Keyboard from './modules/keyboard';
import Uploader from './modules/uploader';
import Delta, { Op, OpIterator, AttributeMap } from 'quill-delta';
import Input from './modules/input';

export { Delta, Op, OpIterator, AttributeMap };

Expand All @@ -32,6 +33,7 @@ Quill.register({
'modules/history': History,
'modules/keyboard': Keyboard,
'modules/uploader': Uploader,
'modules/input': Input,
});

export default Quill;
44 changes: 44 additions & 0 deletions core/composition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Embed from '../blots/embed';
import Scroll from '../blots/scroll';
import Emitter from './emitter';

class Composition {
isComposing = false;

constructor(private scroll: Scroll, private emitter: Emitter) {
scroll.domNode.addEventListener('compositionstart', event => {
if (!this.isComposing) {
this.handleCompositionStart(event);
}
});

scroll.domNode.addEventListener('compositionend', event => {
if (this.isComposing) {
this.handleCompositionEnd(event);
}
});
}

private handleCompositionStart(event: CompositionEvent) {
const blot =
event.target instanceof Node
? this.scroll.find(event.target, true)
: null;

if (blot && !(blot instanceof Embed)) {
this.emitter.emit(Emitter.events.COMPOSITION_BEFORE_START, event);
this.scroll.batchStart();
this.emitter.emit(Emitter.events.COMPOSITION_START, event);
this.isComposing = true;
}
}

private handleCompositionEnd(event: CompositionEvent) {
this.emitter.emit(Emitter.events.COMPOSITION_BEFORE_END, event);
this.scroll.batchEnd();
this.emitter.emit(Emitter.events.COMPOSITION_END, event);
this.isComposing = false;
}
}

export default Composition;
4 changes: 4 additions & 0 deletions core/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ class Emitter extends EventEmitter<string> {
SCROLL_EMBED_UPDATE: 'scroll-embed-update',
SELECTION_CHANGE: 'selection-change',
TEXT_CHANGE: 'text-change',
COMPOSITION_BEFORE_START: 'composition-before-start',
COMPOSITION_START: 'composition-start',
COMPOSITION_BEFORE_END: 'composition-before-end',
COMPOSITION_END: 'composition-end',
} as const;

static sources = {
Expand Down
13 changes: 12 additions & 1 deletion core/quill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import instances from './instances';
import logger, { DebugLevel } from './logger';
import Module from './module';
import Selection, { Range } from './selection';
import Composition from './composition';
import Theme, { ThemeConstructor } from './theme';

const debug = logger('quill');
Expand Down Expand Up @@ -135,6 +136,7 @@ class Quill {
emitter: Emitter;
allowReadOnlyEdits: boolean;
editor: Editor;
composition: Composition;
selection: Selection;

theme: Theme;
Expand Down Expand Up @@ -172,11 +174,13 @@ class Quill {
});
this.editor = new Editor(this.scroll);
this.selection = new Selection(this.scroll, this.emitter);
this.composition = new Composition(this.scroll, this.emitter);
this.theme = new this.options.theme(this, this.options); // eslint-disable-line new-cap
this.keyboard = this.theme.addModule('keyboard');
this.clipboard = this.theme.addModule('clipboard');
this.history = this.theme.addModule('history');
this.uploader = this.theme.addModule('uploader');
this.theme.addModule('input');
this.theme.init();
this.emitter.on(Emitter.events.EDITOR_CHANGE, type => {
if (type === Emitter.events.TEXT_CHANGE) {
Expand Down Expand Up @@ -528,6 +532,12 @@ class Quill {
}

insertText(index: number, text: string, source: EmitterSource): Delta;
insertText(
index: number,
text: string,
formats: Record<string, unknown>,
source: EmitterSource,
): Delta;
insertText(
index: number,
text: string,
Expand All @@ -538,12 +548,13 @@ class Quill {
insertText(
index: number,
text: string,
name: string | EmitterSource,
name: string | Record<string, unknown> | EmitterSource,
value?: unknown,
source?: EmitterSource,
): Delta {
let formats;
// eslint-disable-next-line prefer-const
// @ts-expect-error
[index, , formats, source] = overload(index, 0, name, value, source);
return modify.call(
this,
Expand Down
8 changes: 3 additions & 5 deletions core/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Scroll from '../blots/scroll';

const debug = logger('quill:selection');

type NativeRange = ReturnType<Document['createRange']>;
type NativeRange = AbstractRange;

interface NormalizedRange {
start: {
Expand Down Expand Up @@ -95,12 +95,10 @@ class Selection {
}

handleComposition() {
this.root.addEventListener('compositionstart', () => {
this.emitter.on(Emitter.events.COMPOSITION_BEFORE_START, () => {
this.composing = true;
this.scroll.batchStart();
});
this.root.addEventListener('compositionend', () => {
this.scroll.batchEnd();
this.emitter.on(Emitter.events.COMPOSITION_END, () => {
this.composing = false;
if (this.cursor.parent) {
const range = this.cursor.restore();
Expand Down
12 changes: 12 additions & 0 deletions e2e/utils/fixtures.ts → e2e/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
import { test as base } from '@playwright/test';
import EditorPage from '../pageobjects/EditorPage';

export const test = base.extend<{
editorPage: EditorPage;
clipboard: Clipboard;
}>({
editorPage: ({ page }, use) => {
use(new EditorPage(page));
},
});

export const CHAPTER = 'Chapter 1. Loomings.';
export const P1 =
'Call me Ishmael. Some years ago—never mind how long precisely-having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet; and especially whenever my hypos get such an upper hand of me, that it requires a strong moral principle to prevent me from deliberately stepping into the street, and methodically knocking people’s hats off—then, I account it high time to get to sea as soon as I can. This is my substitute for pistol and ball. With a philosophical flourish Cato throws himself upon his sword; I quietly take to the ship. There is nothing surprising in this. If they but knew it, almost all men in their degree, some time or other, cherish very nearly the same feelings towards the ocean with me.';
Expand Down
39 changes: 19 additions & 20 deletions e2e/full.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { test, expect } from '@playwright/test';
import { expect } from '@playwright/test';
import { getSelectionInTextNode, SHORTKEY } from './utils';
import { CHAPTER, P1, P2 } from './utils/fixtures';
import QuillPage from './utils/QuillPage';
import { test, CHAPTER, P1, P2 } from './fixtures';

test('compose an epic', async ({ page }) => {
await page.goto('http://localhost:9000/standalone/full');
const quillPage = new QuillPage(page);
await page.waitForSelector('.ql-editor', { timeout: 10000 });
test('compose an epic', async ({ page, editorPage }) => {
await editorPage.open();
await expect(page).toHaveTitle('Full Editor - Quill Rich Text Editor');

await page.type('.ql-editor', 'The Whale');
expect(await quillPage.editorHTML()).toEqual('<p>The Whale</p>');
expect(await editorPage.root.innerHTML()).toEqual('<p>The Whale</p>');

await page.keyboard.press('Enter');
expect(await quillPage.editorHTML()).toEqual('<p>The Whale</p><p><br></p>');
expect(await editorPage.root.innerHTML()).toEqual(
'<p>The Whale</p><p><br></p>',
);

await page.keyboard.press('Enter');
await page.keyboard.press('Tab');
await page.type('.ql-editor', P1);
await page.keyboard.press('Enter');
await page.keyboard.press('Enter');
await page.type('.ql-editor', P2);
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p>The Whale</p>',
'<p><br></p>',
Expand All @@ -41,7 +40,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('Enter');
await page.type('.ql-editor', CHAPTER);
await page.keyboard.press('Enter');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p>The Whale</p>',
'<p><br></p>',
Expand All @@ -67,7 +66,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
await page.keyboard.press('Backspace');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p>Whale</p>',
'<p><br></p>',
Expand All @@ -84,7 +83,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('Delete');
await page.keyboard.press('Delete');
await page.keyboard.press('Delete');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p><br></p>',
'<p><br></p>',
Expand All @@ -97,7 +96,7 @@ test('compose an epic', async ({ page }) => {
);

await page.keyboard.press('Delete');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p><br></p>',
`<p>${CHAPTER}</p>`,
Expand All @@ -110,7 +109,7 @@ test('compose an epic', async ({ page }) => {

await page.click('.ql-toolbar .ql-bold');
await page.click('.ql-toolbar .ql-italic');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p><strong><em><span class="ql-cursor">\uFEFF</span></em></strong></p>',
`<p>${CHAPTER}</p>`,
Expand All @@ -126,7 +125,7 @@ test('compose an epic', async ({ page }) => {
expect(italic).not.toBe(null);

await page.type('.ql-editor', 'Moby Dick');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p><strong><em>Moby Dick</em></strong></p>',
`<p>${CHAPTER}</p>`,
Expand Down Expand Up @@ -159,7 +158,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.up(SHORTKEY);
bold = await page.$('.ql-toolbar .ql-bold.ql-active');
expect(bold).not.toBe(null);
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<p><strong><em>Moby Dick</em></strong></p>',
`<p><strong>${CHAPTER}</strong></p>`,
Expand All @@ -173,7 +172,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowUp');
await page.click('.ql-toolbar .ql-header[value="1"]');
expect(await quillPage.editorHTML()).toEqual(
expect(await editorPage.root.innerHTML()).toEqual(
[
'<h1><strong><em>Moby Dick</em></strong></h1>',
`<p><strong>${CHAPTER}</strong></p>`,
Expand All @@ -198,7 +197,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('b');
await page.keyboard.up(SHORTKEY);
await page.type('.ql-editor', 'B');
expect(await quillPage.root.locator('p').nth(2).innerHTML()).toBe('ABA');
expect(await editorPage.root.locator('p').nth(2).innerHTML()).toBe('ABA');
await page.keyboard.down(SHORTKEY);
await page.keyboard.press('b');
await page.keyboard.up(SHORTKEY);
Expand All @@ -207,7 +206,7 @@ test('compose an epic', async ({ page }) => {
await page.keyboard.press('b');
await page.keyboard.up(SHORTKEY);
await page.type('.ql-editor', 'D');
expect(await quillPage.root.locator('p').nth(2).innerHTML()).toBe(
expect(await editorPage.root.locator('p').nth(2).innerHTML()).toBe(
'AB<strong>C</strong>DA',
);
const selection = await page.evaluate(getSelectionInTextNode);
Expand Down
Loading

0 comments on commit 1c2521e

Please sign in to comment.