Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial TypeScript setup #3598

Merged
merged 6 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist/
docs/
_develop
41 changes: 41 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"extends": ["airbnb-base", "airbnb-typescript/base", "prettier"],
"parserOptions": {
"project": "./tsconfig.json"
},
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"plugins": ["prettier"],
"settings": {
"import/resolver": {
"webpack": {
"config": "_develop/webpack.config.js"
}
}
},
"rules": {
"class-methods-use-this": "off",
"import/no-cycle": "off",
"no-restricted-exports": "off",
"@typescript-eslint/indent": "off",
"@typescript-eslint/lines-between-class-members": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/quotes": "off",
"@typescript-eslint/comma-dangle": "off",
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": ["_develop/*.js", "test/**/*.js"]
}
],
"no-param-reassign": "off",
"no-use-before-define": ["error", { "functions": false, "classes": false }],
"import/named": "error",
"max-classes-per-file": "off",
"prettier/prettier": "error"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
.*
!.eslintrc.json
!.eslintignore

/dist
/node_modules
/selenium
Expand Down
30 changes: 6 additions & 24 deletions _develop/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ const constantPack = new webpack.DefinePlugin({
});

const source = [
'quill.js',
'core.js',
'quill.ts',
'core.ts',
'blots',
'core',
'formats',
Expand Down Expand Up @@ -80,27 +80,15 @@ const stylRules = {

const tsRules = {
test: /\.ts$/,
use: [
{
loader: 'ts-loader',
options: {
compilerOptions: {
module: 'es6',
sourceMap: true,
target: 'es6',
},
transpileOnly: true,
},
},
],
use: [{ loader: 'ts-loader' }],
};

const baseConfig = {
mode: 'development',
context: path.resolve(__dirname, '..'),
entry: {
'quill.js': ['./quill.js'],
'quill.core.js': ['./core.js'],
'quill.js': ['./quill.ts'],
'quill.core.js': ['./core.ts'],
'quill.core': './assets/core.styl',
'quill.bubble': './assets/bubble.styl',
'quill.snow': './assets/snow.styl',
Expand All @@ -114,12 +102,6 @@ const baseConfig = {
path: path.resolve(__dirname, '../dist/'),
},
resolve: {
alias: {
parchment: path.resolve(
__dirname,
'../node_modules/parchment/src/parchment',
),
},
extensions: ['.js', '.styl', '.ts'],
},
module: {
Expand Down Expand Up @@ -152,7 +134,7 @@ module.exports = env => {
return {
...prodConfig,
mode: 'production',
entry: { 'quill.min.js': './quill.js' },
entry: { 'quill.min.js': './quill.ts' },
devtool: 'source-map',
};
}
Expand Down
39 changes: 22 additions & 17 deletions blots/block.js → blots/block.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import Delta from 'quill-delta';
import {
AttributorStore,
BlockBlot,
EmbedBlot,
LeafBlot,
Scope,
} from 'parchment';
import { Blot } from 'parchment/dist/typings/blot/abstract/blot';
import Delta from 'quill-delta';
import Break from './break';
import Inline from './inline';
import TextBlot from './text';

const NEWLINE_LENGTH = 1;

class Block extends BlockBlot {
constructor(scroll, domNode) {
super(scroll, domNode);
this.cache = {};
}
cache: { delta?: Delta | null; length?: number } = {};

delta() {
if (this.cache.delta == null) {
Expand Down Expand Up @@ -47,7 +45,7 @@ class Block extends BlockBlot {
this.cache = {};
}

insertAt(index, value, def) {
insertAt(index, value, def?: unknown) {
if (def != null) {
super.insertAt(index, value, def);
this.cache = {};
Expand All @@ -64,7 +62,7 @@ class Block extends BlockBlot {
}
this.cache = {};
}
let block = this;
let block: Blot | this = this;
lines.reduce((lineIndex, line) => {
block = block.split(lineIndex, true);
block.insertAt(0, line);
Expand Down Expand Up @@ -128,6 +126,9 @@ Block.defaultChild = Break;
Block.allowedChildren = [Break, Inline, EmbedBlot, TextBlot];

class BlockEmbed extends EmbedBlot {
attributes: AttributorStore;
domNode: HTMLElement;

attach() {
super.attach();
this.attributes = new AttributorStore(this.domNode);
Expand All @@ -143,6 +144,7 @@ class BlockEmbed extends EmbedBlot {
format(name, value) {
const attribute = this.scroll.query(name, Scope.BLOCK_ATTRIBUTE);
if (attribute != null) {
// @ts-expect-error TODO: Scroll#query() should return Attributor when scope is attribute
this.attributes.attribute(attribute, value);
}
}
Expand All @@ -164,16 +166,19 @@ class BlockEmbed extends EmbedBlot {
BlockEmbed.scope = Scope.BLOCK_BLOT;
// It is important for cursor behavior BlockEmbeds use tags that are block level elements

function blockDelta(blot, filter = true) {
return blot
.descendants(LeafBlot)
.reduce((delta, leaf) => {
if (leaf.length() === 0) {
return delta;
}
return delta.insert(leaf.value(), bubbleFormats(leaf, {}, filter));
}, new Delta())
.insert('\n', bubbleFormats(blot));
function blockDelta(blot: BlockBlot, filter = true) {
return (
blot
// @ts-expect-error
.descendants(LeafBlot)
.reduce((delta, leaf: LeafBlot) => {
if (leaf.length() === 0) {
return delta;
}
return delta.insert(leaf.value(), bubbleFormats(leaf, {}, filter));
}, new Delta())
.insert('\n', bubbleFormats(blot))
);
}

function bubbleFormats(blot, formats = {}, filter = true) {
Expand Down
File renamed without changes.
File renamed without changes.
28 changes: 19 additions & 9 deletions blots/cursor.js → blots/cursor.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
import { EmbedBlot, Scope } from 'parchment';
import { EmbedBlot, Scope, ScrollBlot } from 'parchment';
import { Parent } from 'parchment/dist/typings/blot/abstract/blot';
import Selection from '../core/selection';
import TextBlot from './text';

class Cursor extends EmbedBlot {
static blotName = 'cursor';
static className = 'ql-cursor';
static tagName = 'span';
static CONTENTS = '\uFEFF'; // Zero width no break space

static value() {
return undefined;
}

constructor(scroll, domNode, selection) {
selection: Selection;
textNode: Text;
savedLength: number;

constructor(scroll: ScrollBlot, domNode, selection: Selection) {
super(scroll, domNode);
this.selection = selection;
this.textNode = document.createTextNode(Cursor.CONTENTS);
Expand All @@ -24,14 +35,15 @@ class Cursor extends EmbedBlot {
super.format(name, value);
return;
}
let target = this;
let target: Parent | this = this;
let index = 0;
while (target != null && target.statics.scope !== Scope.BLOCK_BLOT) {
index += target.offset(target.parent);
target = target.parent;
}
if (target != null) {
this.savedLength = Cursor.CONTENTS.length;
// @ts-expect-error TODO: allow empty context in Parchment
target.optimize();
target.formatAt(index, Cursor.CONTENTS.length, name, value);
this.savedLength = 0;
Expand All @@ -47,7 +59,7 @@ class Cursor extends EmbedBlot {
return this.savedLength;
}

position() {
position(): [Text, number] {
return [this.textNode, this.textNode.data.length];
}

Expand All @@ -74,6 +86,7 @@ class Cursor extends EmbedBlot {
const prevTextBlot = this.prev instanceof TextBlot ? this.prev : null;
const prevTextLength = prevTextBlot ? prevTextBlot.length() : 0;
const nextTextBlot = this.next instanceof TextBlot ? this.next : null;
// @ts-expect-error TODO: make TextBlot.text public
const nextText = nextTextBlot ? nextTextBlot.text : '';
const { textNode } = this;
// take text from inside this blot and reset it
Expand Down Expand Up @@ -155,13 +168,14 @@ class Cursor extends EmbedBlot {
// <span class="ql-cursor"><a>\uFEFF{I}</a></span>
// And then "x" will be inserted after `<a/>`:
// <span class="ql-cursor"><a>\uFEFF</a>d{I}</span>
optimize(context) {
optimize(context?: unknown) {
super.optimize(context);

let { parent } = this;
while (parent) {
if (parent.domNode.tagName === 'A') {
this.savedLength = Cursor.CONTENTS.length;
// @ts-expect-error TODO: make isolate generic
parent.isolate(this.offset(parent), this.length()).unwrap();
this.savedLength = 0;
break;
Expand All @@ -174,9 +188,5 @@ class Cursor extends EmbedBlot {
return '';
}
}
Cursor.blotName = 'cursor';
Cursor.className = 'ql-cursor';
Cursor.tagName = 'span';
Cursor.CONTENTS = '\uFEFF'; // Zero width no break space

export default Cursor;
6 changes: 5 additions & 1 deletion blots/embed.js → blots/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import TextBlot from './text';
const GUARD_TEXT = '\uFEFF';

class Embed extends EmbedBlot {
contentNode: HTMLSpanElement;
leftGuard: Text;
rightGuard: Text;

constructor(scroll, node) {
super(scroll, node);
this.contentNode = document.createElement('span');
this.contentNode.setAttribute('contenteditable', false);
this.contentNode.setAttribute('contenteditable', 'false');
Array.from(this.domNode.childNodes).forEach(childNode => {
this.contentNode.appendChild(childNode);
});
Expand Down
28 changes: 15 additions & 13 deletions blots/inline.js → blots/inline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import Break from './break';
import Text from './text';

class Inline extends InlineBlot {
static allowedChildren = [Inline, Break, EmbedBlot, Text];
// Lower index means deeper in the DOM tree, since not found (-1) is for embeds
static order = [
'cursor',
'inline', // Must be lower
'link', // Chrome wants <a> to be lower
'underline',
'strike',
'italic',
'bold',
'script',
'code', // Must be higher
];

static compare(self, other) {
const selfIndex = Inline.order.indexOf(self);
const otherIndex = Inline.order.indexOf(other);
Expand Down Expand Up @@ -39,23 +53,11 @@ class Inline extends InlineBlot {
Inline.compare(this.statics.blotName, this.parent.statics.blotName) > 0
) {
const parent = this.parent.isolate(this.offset(), this.length());
// @ts-expect-error TODO: make isolate generic
this.moveChildren(parent);
parent.wrap(this);
}
}
}
Inline.allowedChildren = [Inline, Break, EmbedBlot, Text];
// Lower index means deeper in the DOM tree, since not found (-1) is for embeds
Inline.order = [
'cursor',
'inline', // Must be lower
'link', // Chrome wants <a> to be lower
'underline',
'strike',
'italic',
'bold',
'script',
'code', // Must be higher
];

export default Inline;
Loading