Skip to content

Commit

Permalink
Migrate keyboard to TypeScript
Browse files Browse the repository at this point in the history
  • Loading branch information
luin committed Aug 15, 2022
1 parent 0cc8755 commit 18d531c
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 34 deletions.
47 changes: 44 additions & 3 deletions core/quill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,14 @@ class Quill {
this.selection.setRange(null);
}

deleteText(index: number, length: number, source) {
deleteText(range: Range, source?: EmitterSource): Delta;
deleteText(index: number, length: number, source?: EmitterSource): Delta;
deleteText(
index: number | Range,
length?: number | EmitterSource,
source?: EmitterSource,
): Delta {
// @ts-expect-error
[index, length, , source] = overload(index, length, source);
return modify.call(
this,
Expand Down Expand Up @@ -310,12 +317,32 @@ class Quill {
);
}

formatLine(index, length, name, value, source) {
formatLine(
index: number,
length: number,
formats: Record<string, unknown>,
source?: EmitterSource,
);
formatLine(
index: number,
length: number,
name: string,
value?: unknown,
source?: EmitterSource,
);
formatLine(
index: number,
length: number,
name: string | Record<string, unknown>,
value?: unknown | EmitterSource,
source?: EmitterSource,
) {
let formats;
// eslint-disable-next-line prefer-const
[index, length, formats, source] = overload(
index,
length,
// @ts-expect-error
name,
value,
source,
Expand Down Expand Up @@ -469,7 +496,21 @@ class Quill {
);
}

insertText(index, text, name, value, source) {
insertText(index: number, text: string, source: EmitterSource): Delta;
insertText(
index: number,
text: string,
name: string,
value: unknown,
source: EmitterSource,
): Delta;
insertText(
index: number,
text: string,
name: string | EmitterSource,
value?: unknown,
source?: EmitterSource,
): Delta {
let formats;
// eslint-disable-next-line prefer-const
[index, , formats, source] = overload(index, 0, name, value, source);
Expand Down
130 changes: 101 additions & 29 deletions modules/keyboard.js → modules/keyboard.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,64 @@
import cloneDeep from 'lodash.clonedeep';
import isEqual from 'lodash.isequal';
import Delta, { AttributeMap } from 'quill-delta';
import { EmbedBlot, Scope, TextBlot } from 'parchment';
import { BlockBlot, EmbedBlot, Scope, TextBlot } from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
import { BlockEmbed } from '../blots/block';
import { Range } from '../core/selection';

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

const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';

class Keyboard extends Module {
static match(evt, binding) {
interface Context {
collapsed: boolean;
empty: boolean;
offset: number;
prefix: string;
suffix: string;
format: Record<string, unknown>;
event: KeyboardEvent;
line: BlockEmbed | BlockBlot;
}

interface BindingObject
extends Partial<Omit<Context, 'prefix' | 'suffix' | 'format'>> {
key: number | string | string[];
shortKey?: boolean | null;
shiftKey?: boolean | null;
altKey?: boolean | null;
metaKey?: boolean | null;
ctrlKey?: boolean | null;
prefix?: RegExp;
suffix?: RegExp;
format?: Record<string, unknown> | string[];
handler?: (
this: { quill: Quill },
range: Range,
curContext: Context,
// eslint-disable-next-line no-use-before-define
binding: NormalizedBinding,
) => boolean | void;
}

type Binding = BindingObject | string | number;

interface NormalizedBinding extends Omit<BindingObject, 'key' | 'shortKey'> {
key: string | number;
}

interface KeyboardOptions {
bindings: Record<string, Binding>;
}

interface KeyboardOptions {
bindings: Record<string, Binding>;
}

class Keyboard extends Module<KeyboardOptions> {
static match(evt: KeyboardEvent, binding) {
if (
['altKey', 'ctrlKey', 'metaKey', 'shiftKey'].some(key => {
return !!binding[key] !== evt[key] && binding[key] !== null;
Expand All @@ -22,7 +69,9 @@ class Keyboard extends Module {
return binding.key === evt.key || binding.key === evt.which;
}

constructor(quill, options) {
bindings: Record<string, NormalizedBinding[]> = {};

constructor(quill: Quill, options: Partial<KeyboardOptions>) {
super(quill, options);
this.bindings = {};
Object.keys(this.options.bindings).forEach(name => {
Expand Down Expand Up @@ -83,7 +132,15 @@ class Keyboard extends Module {
this.listen();
}

addBinding(keyBinding, context = {}, handler = {}) {
addBinding(
keyBinding: Binding,
context:
| Required<BindingObject['handler']>
| Partial<Omit<BindingObject, 'key' | 'handler'>> = {},
handler:
| Required<BindingObject['handler']>
| Partial<Omit<BindingObject, 'key' | 'handler'>> = {},
) {
const binding = normalize(keyBinding);
if (binding == null) {
debug.warn('Attempted to add invalid keyboard binding', binding);
Expand Down Expand Up @@ -116,6 +173,7 @@ class Keyboard extends Module {
);
const matches = bindings.filter(binding => Keyboard.match(evt, binding));
if (matches.length === 0) return;
// @ts-expect-error
const blot = Quill.find(evt.target, true);
if (blot && blot.scroll !== this.quill.scroll) return;
const range = this.quill.getSelection();
Expand Down Expand Up @@ -188,7 +246,7 @@ class Keyboard extends Module {
});
}

handleBackspace(range, context) {
handleBackspace(range: Range, context: Context) {
// Check for astral symbols
const length = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test(context.prefix)
? 2
Expand Down Expand Up @@ -221,7 +279,7 @@ class Keyboard extends Module {
this.quill.focus();
}

handleDelete(range, context) {
handleDelete(range: Range, context: Context) {
// Check for astral symbols
const length = /^[\uD800-\uDBFF][\uDC00-\uDFFF]/.test(context.suffix)
? 2
Expand All @@ -245,12 +303,12 @@ class Keyboard extends Module {
this.quill.focus();
}

handleDeleteRange(range) {
handleDeleteRange(range: Range) {
deleteRange({ range, quill: this.quill });
this.quill.focus();
}

handleEnter(range, context) {
handleEnter(range: Range, context: Context) {
const lineFormats = Object.keys(context.format).reduce(
(formats, format) => {
if (
Expand All @@ -273,7 +331,7 @@ class Keyboard extends Module {
}
}

Keyboard.DEFAULTS = {
const defaultOptions: KeyboardOptions = {
bindings: {
bold: makeFormatHandler('bold'),
italic: makeFormatHandler('italic'),
Expand Down Expand Up @@ -357,7 +415,7 @@ Keyboard.DEFAULTS = {
format: ['list'],
empty: true,
handler(range, context) {
const formats = { list: false };
const formats: Record<string, unknown> = { list: false };
if (context.format.indent) {
formats.indent = false;
}
Expand Down Expand Up @@ -427,6 +485,7 @@ Keyboard.DEFAULTS = {
handler(range) {
const module = this.quill.getModule('table');
if (module) {
// @ts-expect-error
const [table, row, cell, offset] = module.getTable(range);
const shift = tableSide(table, row, cell, offset);
if (shift == null) return;
Expand Down Expand Up @@ -521,6 +580,7 @@ Keyboard.DEFAULTS = {
cur.length() <= 1 &&
cur.formats()['code-block']
) {
// @ts-expect-error
cur = cur.prev;
numLines -= 1;
// Requisite prev lines are empty
Expand All @@ -546,19 +606,20 @@ Keyboard.DEFAULTS = {
},
};

function makeCodeBlockHandler(indent) {
Keyboard.DEFAULTS = defaultOptions;

function makeCodeBlockHandler(indent: boolean): BindingObject {
return {
key: 'Tab',
shiftKey: !indent,
format: { 'code-block': true },
handler(range, { event }) {
const CodeBlock = this.quill.scroll.query('code-block');
// @ts-expect-error
const { TAB } = CodeBlock;
if (range.length === 0 && !event.shiftKey) {
this.quill.insertText(range.index, CodeBlock.TAB, Quill.sources.USER);
this.quill.setSelection(
range.index + CodeBlock.TAB.length,
Quill.sources.SILENT,
);
this.quill.insertText(range.index, TAB, Quill.sources.USER);
this.quill.setSelection(range.index + TAB.length, Quill.sources.SILENT);
return;
}

Expand All @@ -569,18 +630,19 @@ function makeCodeBlockHandler(indent) {
let { index, length } = range;
lines.forEach((line, i) => {
if (indent) {
line.insertAt(0, CodeBlock.TAB);
// @ts-expect-error
line.insertAt(0, TAB);
if (i === 0) {
index += CodeBlock.TAB.length;
index += TAB.length;
} else {
length += CodeBlock.TAB.length;
length += TAB.length;
}
} else if (line.domNode.textContent.startsWith(CodeBlock.TAB)) {
line.deleteAt(0, CodeBlock.TAB.length);
} else if (line.domNode.textContent.startsWith(TAB)) {
line.deleteAt(0, TAB.length);
if (i === 0) {
index -= CodeBlock.TAB.length;
index -= TAB.length;
} else {
length -= CodeBlock.TAB.length;
length -= TAB.length;
}
}
});
Expand All @@ -590,7 +652,10 @@ function makeCodeBlockHandler(indent) {
};
}

function makeEmbedArrowHandler(key, shiftKey) {
function makeEmbedArrowHandler(
key: string,
shiftKey: boolean | null,
): BindingObject {
const where = key === 'ArrowLeft' ? 'prefix' : 'suffix';
return {
key,
Expand Down Expand Up @@ -631,7 +696,7 @@ function makeEmbedArrowHandler(key, shiftKey) {
};
}

function makeFormatHandler(format) {
function makeFormatHandler(format: string): BindingObject {
return {
key: format[0],
shortKey: true,
Expand All @@ -641,7 +706,7 @@ function makeFormatHandler(format) {
};
}

function makeTableArrowHandler(up) {
function makeTableArrowHandler(up: boolean): BindingObject {
return {
key: up ? 'ArrowUp' : 'ArrowDown',
collapsed: true,
Expand All @@ -653,9 +718,11 @@ function makeTableArrowHandler(up) {
const targetRow = cell.parent[key];
if (targetRow != null) {
if (targetRow.statics.blotName === 'table-row') {
// @ts-expect-error
let targetCell = targetRow.children.head;
let cur = cell;
while (cur.prev != null) {
// @ts-expect-error
cur = cur.prev;
targetCell = targetCell.next;
}
Expand All @@ -665,6 +732,7 @@ function makeTableArrowHandler(up) {
this.quill.setSelection(index, 0, Quill.sources.USER);
}
} else {
// @ts-expect-error
const targetLine = cell.table()[key];
if (targetLine != null) {
if (up) {
Expand All @@ -687,23 +755,27 @@ function makeTableArrowHandler(up) {
};
}

function normalize(binding) {
function normalize(binding: Binding): BindingObject {
if (typeof binding === 'string' || typeof binding === 'number') {
binding = { key: binding };
} else if (typeof binding === 'object') {
binding = cloneDeep(binding);
} else {
return null;
}
// @ts-expect-error
if (binding.shortKey) {
// @ts-expect-error
binding[SHORTKEY] = binding.shortKey;
// @ts-expect-error
delete binding.shortKey;
}
// @ts-expect-error
return binding;
}

// TODO: Move into quill.ts or editor.ts
function deleteRange({ quill, range }) {
function deleteRange({ quill, range }: { quill: Quill; range: Range }) {
const lines = quill.getLines(range);
let formats = {};
if (lines.length > 1) {
Expand Down
4 changes: 2 additions & 2 deletions modules/uploader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Range } from '../core/selection';

interface UploaderOptions {
mimetypes: string[];
handler: (range: Range, files: File[]) => void;
handler: (this: { quill: Quill }, range: Range, files: File[]) => void;
}

class Uploader extends Module<UploaderOptions> {
Expand All @@ -33,7 +33,7 @@ class Uploader extends Module<UploaderOptions> {
});
}

upload(range: Range, files: FileList) {
upload(range: Range, files: FileList | File[]) {
const uploads = [];
Array.from(files).forEach(file => {
if (file && this.options.mimetypes.includes(file.type)) {
Expand Down

0 comments on commit 18d531c

Please sign in to comment.