From 44fc8ea9aa5d6c86a7eeb455ed423227bb45caf0 Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Wed, 22 Jul 2020 08:57:49 -0400 Subject: [PATCH] =?UTF-8?q?Typescriptify=20Phase=20II=20=F0=9F=90=9D=20(#7?= =?UTF-8?q?37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Typescriptify attributable + MarkupSection 🐝 * Atom + AtomNode 🐥 * CardNode + Markup 🦏 * Image + ListItem + ListSection 🦀🦀 * LifecycleCallbacks + PostNodeBuilder + fixy fix 🐷 * POST 🦋 * RenderNode + Fixy 🐠 --- .eslintrc.js | 1 + package.json | 8 +- src/js/editor/editor.js | 6 +- src/js/models/_attributable.js | 28 -- src/js/models/_attributable.ts | 49 ++++ src/js/models/_markerable.ts | 29 +- src/js/models/_section.ts | 65 ++--- src/js/models/_tag-nameable.ts | 30 ++ src/js/models/{atom-node.js => atom-node.ts} | 38 ++- src/js/models/{atom.js => atom.ts} | 31 ++- src/js/models/{card-node.js => card-node.ts} | 56 ++-- src/js/models/card.ts | 27 +- src/js/models/{image.js => image.ts} | 10 +- src/js/models/is-list-section.ts | 5 + ...le-callbacks.js => lifecycle-callbacks.ts} | 24 +- src/js/models/{list-item.js => list-item.ts} | 27 +- .../{list-section.js => list-section.ts} | 24 +- src/js/models/marker.ts | 8 +- .../{markup-section.js => markup-section.ts} | 19 +- src/js/models/{markup.js => markup.ts} | 22 +- ...t-node-builder.js => post-node-builder.ts} | 61 +++-- src/js/models/{post.js => post.ts} | 105 ++++--- src/js/models/render-node.js | 95 ------- src/js/models/render-node.ts | 120 ++++++++ src/js/models/render-tree.ts | 11 +- src/js/models/types.ts | 12 + src/js/renderers/editor-dom.js | 6 +- src/js/utils/cursor.ts | 9 +- src/js/utils/cursor/position.ts | 19 +- src/js/utils/linked-item.ts | 6 +- src/js/utils/markuperable.ts | 13 +- tests/acceptance/editor-cards-test.js | 4 +- yarn.lock | 258 ++++++------------ 33 files changed, 656 insertions(+), 570 deletions(-) delete mode 100644 src/js/models/_attributable.js create mode 100644 src/js/models/_attributable.ts create mode 100644 src/js/models/_tag-nameable.ts rename src/js/models/{atom-node.js => atom-node.ts} (58%) rename src/js/models/{atom.js => atom.ts} (78%) rename src/js/models/{card-node.js => card-node.ts} (52%) rename src/js/models/{image.js => image.ts} (63%) create mode 100644 src/js/models/is-list-section.ts rename src/js/models/{lifecycle-callbacks.js => lifecycle-callbacks.ts} (64%) rename src/js/models/{list-item.js => list-item.ts} (56%) rename src/js/models/{list-section.js => list-section.ts} (77%) rename src/js/models/{markup-section.js => markup-section.ts} (79%) rename src/js/models/{markup.js => markup.ts} (84%) rename src/js/models/{post-node-builder.js => post-node-builder.ts} (70%) rename src/js/models/{post.js => post.ts} (68%) delete mode 100644 src/js/models/render-node.js create mode 100644 src/js/models/render-node.ts diff --git a/.eslintrc.js b/.eslintrc.js index e5e79529e..11e409fdd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -151,6 +151,7 @@ module.exports = { "no-continue": "off", "no-div-regex": "error", "no-duplicate-imports": "off", + "no-dupe-class-members": "off", "no-else-return": "off", "no-empty-function": "off", "no-eq-null": "error", diff --git a/package.json b/package.json index 65cca863f..583d639c9 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,10 @@ "@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-node-resolve": "^7.1.3", "@rollup/plugin-typescript": "^4.1.2", - "@typescript-eslint/eslint-plugin": "^3.0.2", - "@typescript-eslint/parser": "^3.0.2", + "@typescript-eslint/eslint-plugin": "^3.6.1", + "@typescript-eslint/parser": "^3.6.1", "conventional-changelog-cli": "^2.0.34", - "eslint": "^7.1.0", + "eslint": "^7.4.0", "eslint-config-prettier": "^6.11.0", "jquery": "^3.5.1", "jsdoc": "^3.6.4", @@ -78,6 +78,6 @@ }, "volta": { "node": "12.14.1", - "yarn": "1.22.1" + "yarn": "1.22.4" } } diff --git a/src/js/editor/editor.js b/src/js/editor/editor.js index b64598e60..d35401357 100644 --- a/src/js/editor/editor.js +++ b/src/js/editor/editor.js @@ -20,7 +20,7 @@ import Environment from '../utils/environment' import PostNodeBuilder from '../models/post-node-builder' import { DEFAULT_TEXT_INPUT_HANDLERS } from './text-input-handlers' import { DEFAULT_KEY_COMMANDS, buildKeyCommand, findKeyCommands, validateKeyCommand } from './key-commands' -import { CARD_MODES } from '../models/card' +import { CardMode } from '../models/card' import assert from '../utils/assert' import MutationHandler from 'mobiledoc-kit/editor/mutation-handler' import EditHistory from 'mobiledoc-kit/editor/edit-history' @@ -690,7 +690,7 @@ class Editor { * @public */ editCard(cardSection) { - this._setCardMode(cardSection, CARD_MODES.EDIT) + this._setCardMode(cardSection, CardMode.EDIT) } /** @@ -702,7 +702,7 @@ class Editor { * @public */ displayCard(cardSection) { - this._setCardMode(cardSection, CARD_MODES.DISPLAY) + this._setCardMode(cardSection, CardMode.DISPLAY) } /** diff --git a/src/js/models/_attributable.js b/src/js/models/_attributable.js deleted file mode 100644 index b1adc8c13..000000000 --- a/src/js/models/_attributable.js +++ /dev/null @@ -1,28 +0,0 @@ -import { entries } from '../utils/object-utils' -import { contains } from '../utils/array-utils' - -export const VALID_ATTRIBUTES = ['data-md-text-align'] - -/* - * A "mixin" to add section attribute support - * to markup and list sections. - */ -export function attributable(ctx) { - ctx.attributes = {} - - ctx.hasAttribute = key => key in ctx.attributes - - ctx.setAttribute = (key, value) => { - if (!contains(VALID_ATTRIBUTES, key)) { - throw new Error(`Invalid attribute "${key}" was passed. Constrain attributes to the spec-compliant whitelist.`) - } - ctx.attributes[key] = value - } - ctx.removeAttribute = key => { - delete ctx.attributes[key] - } - ctx.getAttribute = key => ctx.attributes[key] - ctx.eachAttribute = cb => { - entries(ctx.attributes).forEach(([k, v]) => cb(k, v)) - } -} diff --git a/src/js/models/_attributable.ts b/src/js/models/_attributable.ts new file mode 100644 index 000000000..1445bc21d --- /dev/null +++ b/src/js/models/_attributable.ts @@ -0,0 +1,49 @@ +import { entries } from '../utils/object-utils' +import { contains } from '../utils/array-utils' + +export const VALID_ATTRIBUTES = ['data-md-text-align'] + +export interface Attributable { + attributes: { [key: string]: string } + hasAttribute: (key: string) => boolean + setAttribute: (key: string, value: string) => void + removeAttribute: (key: string) => void + getAttribute: (key: string) => string + eachAttribute: (cb: (key: string, value: string) => void) => void +} + +type AbstractConstructor = Function & { prototype: T } +type Constructor = new (...args: any[]) => T + +/* + * A "mixin" to add section attribute support + * to markup and list sections. + */ +export function attributable(Base: AbstractConstructor): Constructor { + return class extends (Base as any) { + attributes: { [key: string]: string } = {} + + hasAttribute(key: string) { + return key in this.attributes + } + + setAttribute(key: string, value: string) { + if (!contains(VALID_ATTRIBUTES, key)) { + throw new Error(`Invalid attribute "${key}" was passed. Constrain attributes to the spec-compliant whitelist.`) + } + this.attributes[key] = value + } + + removeAttribute(key: string) { + delete this.attributes[key] + } + + getAttribute(key: string) { + return this.attributes[key] + } + + eachAttribute(cb: (key: string | number, value: string) => void) { + entries(this.attributes).forEach(([k, v]) => cb(k, v)) + } + } as Constructor +} diff --git a/src/js/models/_markerable.ts b/src/js/models/_markerable.ts index 560870bd6..0c5346b7a 100644 --- a/src/js/models/_markerable.ts +++ b/src/js/models/_markerable.ts @@ -6,14 +6,18 @@ import assert from '../utils/assert' import Position from '../utils/cursor/position' import Section from './_section' import Marker from './marker' +import { tagNameable } from './_tag-nameable' +import { Type } from './types' -export default abstract class Markerable extends Section { - tagName: string +type MarkerableType = Type.LIST_ITEM | Type.MARKUP_SECTION + +export default abstract class Markerable extends tagNameable(Section) { + type: MarkerableType markers: LinkedList - builder: any - constructor(type: string, tagName: string, markers = []) { + constructor(type: MarkerableType, tagName: string, markers: Marker[] = []) { super(type) + this.type = type this.isMarkerable = true this.tagName = tagName this.markers = new LinkedList({ @@ -27,13 +31,13 @@ export default abstract class Markerable extends Section { markers.forEach(m => this.markers.append(m)) } - canJoin(other: Section) { + canJoin(other: Markerable) { return other.isMarkerable && other.type === this.type && other.tagName === this.tagName } - clone() { + clone(): this { const newMarkers = this.markers.map(m => m.clone()) - return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) + return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) as any as this } get isBlank() { @@ -87,7 +91,12 @@ export default abstract class Markerable extends Section { // all markers before the marker/offset split go in beforeSection, and all // after the marker/offset split go in afterSection // @return {Array} [beforeSection, afterSection], two new sections - _redistributeMarkers(beforeSection: Markerable, afterSection: Markerable, marker: Marker, offset = 0) { + _redistributeMarkers( + beforeSection: Markerable, + afterSection: Markerable, + marker: Marker, + offset = 0 + ): [Section, Section] { let currentSection = beforeSection forEach(this.markers, m => { if (m === marker) { @@ -280,3 +289,7 @@ export default abstract class Markerable extends Section { return { beforeMarker, afterMarker } } } + +export function isMarkerable(section: Section): section is Markerable { + return section.isMarkerable +} diff --git a/src/js/models/_section.ts b/src/js/models/_section.ts index 8a7eeb7e1..accc29576 100644 --- a/src/js/models/_section.ts +++ b/src/js/models/_section.ts @@ -1,59 +1,50 @@ -import { normalizeTagName } from '../utils/dom-utils' import LinkedItem from '../utils/linked-item' import assert from '../utils/assert' import Position from '../utils/cursor/position' -import LinkedList from '../utils/linked-list' +import Range from '../utils/cursor/range' import Marker from './marker' import RenderNode from './render-node' import Post from './post' +import { isListSection } from './is-list-section' +import PostNodeBuilder from './post-node-builder' +import { Type } from './types' -export default abstract class Section extends LinkedItem
{ - type: string - isSection: boolean - isMarkerable: boolean - isNested: boolean - isLeafSection: boolean +export default class Section extends LinkedItem { + type: Type + + isSection = true + isMarkerable = false + isNested = false + isListItem = false + isListSection = false + isLeafSection = true + isCardSection = false post?: Post | null - parent: Section | null = null renderNode: RenderNode | null = null - _tagName: string | null = null - constructor(type: string) { + parent: Section | null = null + builder!: PostNodeBuilder + + constructor(type: Type) { super() assert('Cannot create section without type', !!type) this.type = type - this.isSection = true - this.isMarkerable = false - this.isNested = false - this.isLeafSection = true - } - - set tagName(val: string) { - let normalizedTagName = normalizeTagName(val) - assert(`Cannot set section tagName to ${val}`, this.isValidTagName(normalizedTagName)) - this._tagName = normalizedTagName } - get tagName() { - return this._tagName as string + get isBlank() { + return false } get length() { return 0 } - abstract get isBlank(): boolean - abstract isValidTagName(_normalizedTagName: string): boolean - abstract clone(): Section - abstract canJoin(otherSection: Section): boolean - abstract textUntil(position: Position): string - /** * @return {Position} The position at the start of this section * @public */ - headPosition() { + headPosition(): Position { return this.toPosition(0) } @@ -61,7 +52,7 @@ export default abstract class Section extends LinkedItem
{ * @return {Position} The position at the end of this section * @public */ - tailPosition() { + tailPosition(): Position { return this.toPosition(this.length) } @@ -70,7 +61,7 @@ export default abstract class Section extends LinkedItem
{ * @return {Position} The position in this section at the given offset * @public */ - toPosition(offset: number) { + toPosition(offset: number): Position { assert('Must pass number to `toPosition`', typeof offset === 'number') assert('Cannot call `toPosition` with offset > length', offset <= this.length) @@ -81,7 +72,7 @@ export default abstract class Section extends LinkedItem
{ * @return {Range} A range from this section's head to tail positions * @public */ - toRange() { + toRange(): Range { return this.headPosition().toRange(this.tailPosition()) } @@ -136,11 +127,3 @@ export default abstract class Section extends LinkedItem
{ return null } } - -interface ListSection { - items: LinkedList
-} - -function isListSection(item: any): item is ListSection { - return 'items' in item && item.items -} diff --git a/src/js/models/_tag-nameable.ts b/src/js/models/_tag-nameable.ts new file mode 100644 index 000000000..d6d153f9a --- /dev/null +++ b/src/js/models/_tag-nameable.ts @@ -0,0 +1,30 @@ +import { normalizeTagName } from '../utils/dom-utils' +import assert from '../utils/assert' +import Section from './_section' + +type Constructor = new (...args: any[]) => T + +export interface TagNameable { + tagName: string + isValidTagName(normalizedTagName: string): boolean +} + +export function tagNameable(Base: Constructor
) { + abstract class TagNameable extends Base { + _tagName: string | null = null + + set tagName(val: string) { + let normalizedTagName = normalizeTagName(val) + assert(`Cannot set section tagName to ${val}`, this.isValidTagName(normalizedTagName)) + this._tagName = normalizedTagName + } + + get tagName() { + return this._tagName as string + } + + abstract isValidTagName(normalizedTagName: string): boolean + } + + return TagNameable +} diff --git a/src/js/models/atom-node.js b/src/js/models/atom-node.ts similarity index 58% rename from src/js/models/atom-node.js rename to src/js/models/atom-node.ts index ff6a8ce3d..1f14b4cb0 100644 --- a/src/js/models/atom-node.js +++ b/src/js/models/atom-node.ts @@ -1,15 +1,37 @@ import assert from '../utils/assert' +import Atom from './atom' + +export interface AtomOptions {} + +export type TeardownCallback = () => void +export interface AtomRenderOptions { + options: AtomOptions + env: any + value: unknown + payload: {} +} + +export type AtomData = { + name: string + render(options: AtomRenderOptions): Element +} export default class AtomNode { - constructor(editor, atom, model, element, atomOptions) { + editor: any + atom: AtomData + model: Atom + element: Element + atomOptions: AtomOptions + + _teardownCallback: TeardownCallback | null = null + _rendered: Node | null = null + + constructor(editor: any, atom: AtomData, model: Atom, element: Element, atomOptions: AtomOptions) { this.editor = editor this.atom = atom this.model = model this.atomOptions = atomOptions this.element = element - - this._teardownCallback = null - this._rendered = null } render() { @@ -23,14 +45,14 @@ export default class AtomNode { this._rendered = this.atom.render({ options, env, value, payload }) } - this._validateAndAppendRenderResult(this._rendered) + this._validateAndAppendRenderResult(this._rendered!) } get env() { return { name: this.atom.name, - onTeardown: callback => (this._teardownCallback = callback), - save: (value, payload = {}) => { + onTeardown: (callback: TeardownCallback) => (this._teardownCallback = callback), + save: (value: unknown, payload = {}) => { this.model.value = value this.model.payload = payload @@ -52,7 +74,7 @@ export default class AtomNode { } } - _validateAndAppendRenderResult(rendered) { + _validateAndAppendRenderResult(rendered: Node) { if (!rendered) { return } diff --git a/src/js/models/atom.js b/src/js/models/atom.ts similarity index 78% rename from src/js/models/atom.js rename to src/js/models/atom.ts index 972012012..10242c575 100644 --- a/src/js/models/atom.js +++ b/src/js/models/atom.ts @@ -1,13 +1,24 @@ import { ATOM_TYPE } from './types' -import mixin from '../utils/mixin' -import MarkuperableMixin from '../utils/markuperable' -import LinkedItem from '../utils/linked-item' +import Markuperable from '../utils/markuperable' import assert from '../utils/assert' +import Marker from './marker' +import Markup from './markup' const ATOM_LENGTH = 1 -class Atom extends LinkedItem { - constructor(name, value, payload, markups = []) { +export default class Atom extends Markuperable { + type = ATOM_TYPE + isAtom = true + + name: string + value: unknown + text: string + payload: {} + + markups: Markup[] + builder: any + + constructor(name: string, value: unknown, payload: {}, markups: Markup[] = []) { super() this.name = name this.value = value @@ -44,7 +55,7 @@ class Atom extends LinkedItem { } split(offset = 0, endOffset = offset) { - let markers = [] + let markers: Marker[] = [] if (endOffset === 0) { markers.push(this.builder.createMarker('', this.markups.slice())) @@ -59,13 +70,13 @@ class Atom extends LinkedItem { return markers } - splitAtOffset(offset) { + splitAtOffset(offset: number) { assert('Cannot split a marker at an offset > its length', offset <= this.length) let { builder } = this let clone = this.clone() let blankMarker = builder.createMarker('') - let pre, post + let pre: Marker, post: Marker if (offset === 0) { ;[pre, post] = [blankMarker, clone] @@ -82,7 +93,3 @@ class Atom extends LinkedItem { return [pre, post] } } - -mixin(Atom, MarkuperableMixin) - -export default Atom diff --git a/src/js/models/card-node.js b/src/js/models/card-node.ts similarity index 52% rename from src/js/models/card-node.js rename to src/js/models/card-node.ts index d3470c112..19f537d1e 100644 --- a/src/js/models/card-node.js +++ b/src/js/models/card-node.ts @@ -1,20 +1,42 @@ import assert from '../utils/assert' +import Card, { CardMode } from './card' + +export interface CardNodeOptions {} + +type DidRenderCallback = null | (() => void) +type TeardownCallback = null | (() => void) + +type RenderMethod = (...args: any[]) => Element + +export interface CardData { + name: string + render: RenderMethod + edit: RenderMethod +} + +type CardRenderMethodName = 'render' | 'edit' export default class CardNode { - constructor(editor, card, section, element, options) { + editor: any + card: CardData + section: Card + element: Element + options: CardNodeOptions + + mode!: CardMode + _rendered: Element | null = null + _teardownCallback: TeardownCallback = null + _didRenderCallback: DidRenderCallback = null + + constructor(editor: any, card: CardData, section: Card, element: Element, options: CardNodeOptions) { this.editor = editor this.card = card this.section = section this.element = element this.options = options - - this.mode = null - - this._teardownCallback = null - this._rendered = null } - render(mode) { + render(mode: CardMode) { if (this.mode === mode) { return } @@ -23,10 +45,10 @@ export default class CardNode { this.mode = mode - let method = mode === 'display' ? 'render' : 'edit' - method = this.card[method] + let methodName: CardRenderMethodName = mode === 'display' ? 'render' : 'edit' + let method = this.card[methodName] - assert(`Card is missing "${method}" (tried to render mode: "${mode}")`, !!method) + assert(`Card is missing "${methodName}" (tried to render mode: "${mode}")`, !!method) let rendered = method({ env: this.env, options: this.options, @@ -57,10 +79,10 @@ export default class CardNode { return { name: this.card.name, isInEditor: true, - onTeardown: callback => (this._teardownCallback = callback), - didRender: callback => (this._didRenderCallback = callback), + onTeardown: (callback: TeardownCallback) => (this._teardownCallback = callback), + didRender: (callback: DidRenderCallback) => (this._didRenderCallback = callback), edit: () => this.edit(), - save: (payload, transition = true) => { + save: (payload: {}, transition = true) => { this.section.payload = payload this.editor._postDidChange() @@ -75,18 +97,18 @@ export default class CardNode { } display() { - this.render('display') + this.render(CardMode.DISPLAY) } edit() { - this.render('edit') + this.render(CardMode.EDIT) } remove() { - this.editor.run(postEditor => postEditor.removeSection(this.section)) + this.editor.run((postEditor: any) => postEditor.removeSection(this.section)) } - _validateAndAppendRenderResult(rendered) { + _validateAndAppendRenderResult(rendered: Element | null) { if (!rendered) { return } diff --git a/src/js/models/card.ts b/src/js/models/card.ts index 54cb35d0e..e67888d2a 100644 --- a/src/js/models/card.ts +++ b/src/js/models/card.ts @@ -1,45 +1,38 @@ import Section from './_section' -import { CARD_TYPE } from './types' +import { Type } from './types' import { shallowCopyObject } from '../utils/copy' +import PostNodeBuilder from './post-node-builder' -export enum CARD_MODES { +export enum CardMode { DISPLAY = 'display', EDIT = 'edit', } const CARD_LENGTH = 1 -export function isCardSection(section: Section | Card): section is Card { +export function isCardSection(section: {}): section is Card { return (section as Card).isCardSection } export default class Card extends Section { name: string payload: T - isCardSection: true - builder: any - _initialMode: CARD_MODES = CARD_MODES.DISPLAY + builder!: PostNodeBuilder + _initialMode: CardMode = CardMode.DISPLAY + + isCardSection = true constructor(name: string, payload: T) { - super(CARD_TYPE) + super(Type.CARD) this.name = name this.payload = payload this.isCardSection = true } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - isValidTagName(_normalizedTagName: string): boolean { - throw new Error('Method not implemented.') - } - textUntil(): string { return '' } - get isBlank() { - return false - } - canJoin() { return false } @@ -65,7 +58,7 @@ export default class Card extends Section { * set the mode that this will be rendered into initially * @private */ - setInitialMode(initialMode: CARD_MODES) { + setInitialMode(initialMode: CardMode) { // TODO validate initialMode this._initialMode = initialMode } diff --git a/src/js/models/image.js b/src/js/models/image.ts similarity index 63% rename from src/js/models/image.js rename to src/js/models/image.ts index a18fdfd7d..78dcecda1 100644 --- a/src/js/models/image.js +++ b/src/js/models/image.ts @@ -1,9 +1,11 @@ -import { IMAGE_SECTION_TYPE } from './types' +import { Type } from './types' import Section from './_section' export default class Image extends Section { + src: string | null = null + constructor() { - super(IMAGE_SECTION_TYPE) + super(Type.IMAGE_SECTION) this.src = null } @@ -11,10 +13,6 @@ export default class Image extends Section { return false } - get isBlank() { - return false - } - get length() { return 1 } diff --git a/src/js/models/is-list-section.ts b/src/js/models/is-list-section.ts new file mode 100644 index 000000000..49d35d3e3 --- /dev/null +++ b/src/js/models/is-list-section.ts @@ -0,0 +1,5 @@ +import ListSection from "./list-section" + +export function isListSection(item: any): item is ListSection { + return 'items' in item && item.items +} diff --git a/src/js/models/lifecycle-callbacks.js b/src/js/models/lifecycle-callbacks.ts similarity index 64% rename from src/js/models/lifecycle-callbacks.js rename to src/js/models/lifecycle-callbacks.ts index 4cd972349..c5b7aa8ec 100644 --- a/src/js/models/lifecycle-callbacks.js +++ b/src/js/models/lifecycle-callbacks.ts @@ -1,17 +1,23 @@ -import assert from 'mobiledoc-kit/utils/assert' +import assert from '../utils/assert' + +interface Queue { + [name: string]: LifecycleCallback[] +} + +export type LifecycleCallback = (...args: any[]) => void export default class LifecycleCallbacks { - constructor(queueNames = []) { - this.callbackQueues = {} - this.removalQueues = {} + callbackQueues: Queue = {} + removalQueues: Queue = {} + constructor(queueNames = []) { queueNames.forEach(name => { this.callbackQueues[name] = [] this.removalQueues[name] = [] }) } - runCallbacks(queueName, args = []) { + runCallbacks(queueName: string, args: unknown[] = []) { let queue = this._getQueue(queueName) queue.forEach(cb => cb(...args)) @@ -26,15 +32,15 @@ export default class LifecycleCallbacks { this.removalQueues[queueName] = [] } - addCallback(queueName, callback) { + addCallback(queueName: string, callback: LifecycleCallback) { this._getQueue(queueName).push(callback) } - _scheduleCallbackForRemoval(queueName, callback) { + _scheduleCallbackForRemoval(queueName: string, callback: LifecycleCallback) { this.removalQueues[queueName].push(callback) } - addCallbackOnce(queueName, callback) { + addCallbackOnce(queueName: string, callback: LifecycleCallback) { let queue = this._getQueue(queueName) if (queue.indexOf(callback) === -1) { queue.push(callback) @@ -42,7 +48,7 @@ export default class LifecycleCallbacks { } } - _getQueue(queueName) { + _getQueue(queueName: string) { let queue = this.callbackQueues[queueName] assert(`No queue found for "${queueName}"`, !!queue) return queue diff --git a/src/js/models/list-item.js b/src/js/models/list-item.ts similarity index 56% rename from src/js/models/list-item.js rename to src/js/models/list-item.ts index 282b71951..bfd1674a0 100644 --- a/src/js/models/list-item.js +++ b/src/js/models/list-item.ts @@ -1,22 +1,27 @@ import Markerable from './_markerable' -import { LIST_ITEM_TYPE } from './types' +import { Type } from './types' import { normalizeTagName } from '../utils/dom-utils' -import { contains } from 'mobiledoc-kit/utils/array-utils' +import { contains } from '../utils/array-utils' +import Section from './_section' +import { expect } from '../utils/assert' +import Marker from './marker' export const VALID_LIST_ITEM_TAGNAMES = ['li'].map(normalizeTagName) export default class ListItem extends Markerable { - constructor(tagName, markers = []) { - super(LIST_ITEM_TYPE, tagName, markers) - this.isListItem = true - this.isNested = true + isListItem = true + isNested = true + section: Section | null = null + + constructor(tagName: string, markers: Marker[] = []) { + super(Type.LIST_ITEM, tagName, markers) } - isValidTagName(normalizedTagName) { + isValidTagName(normalizedTagName: string) { return contains(VALID_LIST_ITEM_TAGNAMES, normalizedTagName) } - splitAtMarker(marker, offset = 0) { + splitAtMarker(marker: Marker, offset = 0) { // FIXME need to check if we are going to split into two list items // or a list item and a new markup section: const isLastItem = !this.next @@ -31,6 +36,10 @@ export default class ListItem extends Markerable { } get post() { - return this.section.post + return expect(this.section, 'expected list item to have section').post } } + +export function isListItem(section: Section): section is ListItem { + return section.isListItem +} diff --git a/src/js/models/list-section.js b/src/js/models/list-section.ts similarity index 77% rename from src/js/models/list-section.js rename to src/js/models/list-section.ts index 2e160e33f..c4b21258f 100644 --- a/src/js/models/list-section.js +++ b/src/js/models/list-section.ts @@ -1,28 +1,32 @@ import { LIST_SECTION_TYPE } from './types' import Section from './_section' import { attributable } from './_attributable' - import LinkedList from '../utils/linked-list' import { forEach, contains } from '../utils/array-utils' import { normalizeTagName } from '../utils/dom-utils' import assert from '../utils/assert' import { entries } from '../utils/object-utils' +import ListItem from './list-item' +import { tagNameable } from './_tag-nameable' export const VALID_LIST_SECTION_TAGNAMES = ['ul', 'ol'].map(normalizeTagName) export const DEFAULT_TAG_NAME = VALID_LIST_SECTION_TAGNAMES[0] -export default class ListSection extends Section { +export default class ListSection extends attributable(tagNameable(Section)) { + isListSection = true + isLeafSection = false + + items: LinkedList + sections: LinkedList + constructor(tagName = DEFAULT_TAG_NAME, items = [], attributes = {}) { super(LIST_SECTION_TYPE) this.tagName = tagName - this.isListSection = true - this.isLeafSection = false - attributable(this) entries(attributes).forEach(([k, v]) => this.setAttribute(k, v)) - this.items = new LinkedList({ + this.items = new LinkedList({ adoptItem: i => { assert(`Cannot insert non-list-item to list (is: ${i.type})`, i.isListItem) i.section = i.parent = this @@ -43,11 +47,11 @@ export default class ListSection extends Section { } headPosition() { - return this.items.head.headPosition() + return this.items.head!.headPosition() } tailPosition() { - return this.items.tail.tailPosition() + return this.items.tail!.tailPosition() } get isBlank() { @@ -75,3 +79,7 @@ export default class ListSection extends Section { } } } + +export function isListSection(section: Section): section is ListSection { + return section.isListSection +} diff --git a/src/js/models/marker.ts b/src/js/models/marker.ts index f2f2c86e0..ed3a7e20f 100644 --- a/src/js/models/marker.ts +++ b/src/js/models/marker.ts @@ -17,10 +17,10 @@ export const HIGH_SURROGATE_RANGE = [0xd800, 0xdbff] export const LOW_SURROGATE_RANGE = [0xdc00, 0xdfff] export default class Marker extends Markuperable { + type = MARKER_TYPE + isMarker = true + value: string - type: string = MARKER_TYPE - isMarker: boolean = true - isAtom: boolean = false builder: any markups: Markup[] = [] @@ -28,7 +28,7 @@ export default class Marker extends Markuperable { parent: Markerable | null = null renderNode: RenderNode | null = null - constructor(value = '', markups = []) { + constructor(value = '', markups: Markup[] = []) { super() this.value = value assert('Marker must have value', value !== undefined && value !== null) diff --git a/src/js/models/markup-section.js b/src/js/models/markup-section.ts similarity index 79% rename from src/js/models/markup-section.js rename to src/js/models/markup-section.ts index 700815d3b..832084ff1 100644 --- a/src/js/models/markup-section.js +++ b/src/js/models/markup-section.ts @@ -1,10 +1,10 @@ import Markerable from './_markerable' import { attributable } from './_attributable' import { MARKUP_SECTION_TYPE } from './types' - import { normalizeTagName } from '../utils/dom-utils' import { contains } from '../utils/array-utils' import { entries } from '../utils/object-utils' +import Marker from './marker' // valid values of `tagName` for a MarkupSection export const VALID_MARKUP_SECTION_TAGNAMES = ['aside', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].map( @@ -19,21 +19,20 @@ export const MARKUP_SECTION_ELEMENT_NAMES = ['aside', 'blockquote', 'h1', 'h2', ) export const DEFAULT_TAG_NAME = VALID_MARKUP_SECTION_TAGNAMES[8] -const MarkupSection = class MarkupSection extends Markerable { - constructor(tagName = DEFAULT_TAG_NAME, markers = [], attributes = {}) { - super(MARKUP_SECTION_TYPE, tagName, markers) +export default class MarkupSection extends attributable(Markerable) { + isMarkupSection = true + isGenerated = false - attributable(this) + constructor(tagName = DEFAULT_TAG_NAME, markers: Marker[] = [], attributes = {}) { + super(MARKUP_SECTION_TYPE, tagName, markers) entries(attributes).forEach(([k, v]) => this.setAttribute(k, v)) - - this.isMarkupSection = true } - isValidTagName(normalizedTagName) { + isValidTagName(normalizedTagName: string) { return contains(VALID_MARKUP_SECTION_TAGNAMES, normalizedTagName) } - splitAtMarker(marker, offset = 0) { + splitAtMarker(marker: Marker, offset = 0) { let [beforeSection, afterSection] = [ this.builder.createMarkupSection(this.tagName, [], false, this.attributes), this.builder.createMarkupSection(), @@ -42,5 +41,3 @@ const MarkupSection = class MarkupSection extends Markerable { return this._redistributeMarkers(beforeSection, afterSection, marker, offset) } } - -export default MarkupSection diff --git a/src/js/models/markup.js b/src/js/models/markup.ts similarity index 84% rename from src/js/models/markup.js rename to src/js/models/markup.ts index ecece259e..224f5d55b 100644 --- a/src/js/models/markup.js +++ b/src/js/models/markup.ts @@ -25,19 +25,21 @@ export const VALID_ATTRIBUTES = ['href', 'rel'] * that could be added are bold ('b'), italic ('i'), strikethrough ('s'), and `a` tags (links). * @property {String} tagName */ -class Markup { +export default class Markup { + tagName: string + attributes: { [key: string]: unknown } + + type = MARKUP_TYPE + /* * @param {Object} attributes key-values */ - constructor(tagName, attributes = {}) { + constructor(tagName: string, attributes = {}) { + assert(`Cannot create markup of tagName ${tagName}`, VALID_MARKUP_TAGNAMES.indexOf(tagName) !== -1) this.tagName = normalizeTagName(tagName) assert('Must use attributes object param (not array) for Markup', !Array.isArray(attributes)) - this.attributes = filterObject(attributes, VALID_ATTRIBUTES) - this.type = MARKUP_TYPE - - assert(`Cannot create markup of tagName ${tagName}`, VALID_MARKUP_TAGNAMES.indexOf(this.tagName) !== -1) } /** @@ -53,7 +55,7 @@ class Markup { return false } - hasTag(tagName) { + hasTag(tagName: string) { return this.tagName === normalizeTagName(tagName) } @@ -61,14 +63,12 @@ class Markup { * Returns the attribute value * @param {String} name, e.g. "href" */ - getAttribute(name) { + getAttribute(name: string) { return this.attributes[name] } - static isValidElement(element) { + static isValidElement(element: Element) { const tagName = normalizeTagName(element.tagName) return VALID_MARKUP_TAGNAMES.indexOf(tagName) !== -1 } } - -export default Markup diff --git a/src/js/models/post-node-builder.js b/src/js/models/post-node-builder.ts similarity index 70% rename from src/js/models/post-node-builder.js rename to src/js/models/post-node-builder.ts index 0690ecc0c..f4407dda6 100644 --- a/src/js/models/post-node-builder.js +++ b/src/js/models/post-node-builder.ts @@ -1,17 +1,19 @@ -import Atom from '../models/atom' -import Post from '../models/post' -import MarkupSection from '../models/markup-section' -import ListSection from '../models/list-section' -import ListItem from '../models/list-item' -import ImageSection from '../models/image' -import Marker from '../models/marker' -import Markup from '../models/markup' -import Card from '../models/card' +import Atom from './atom' +import Post from './post' +import MarkupSection from './markup-section' +import ListSection from './list-section' +import ListItem from './list-item' +import ImageSection from './image' +import Marker from './marker' +import Markup from './markup' +import Card from './card' + +import { LIST_ITEM_TYPE, MARKUP_SECTION_TYPE, Type } from './types' +import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from './markup-section' +import { DEFAULT_TAG_NAME as DEFAULT_LIST_SECTION_TAG_NAME } from './list-section' + import { normalizeTagName } from '../utils/dom-utils' import { objectToSortedKVArray } from '../utils/array-utils' -import { LIST_ITEM_TYPE, MARKUP_SECTION_TYPE } from '../models/types' -import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from '../models/markup-section' -import { DEFAULT_TAG_NAME as DEFAULT_LIST_SECTION_TAG_NAME } from '../models/list-section' import assert from '../utils/assert' function cacheKey(tagName, attributes) { @@ -35,18 +37,13 @@ function findMarkupInCache(cache, tagName, attributes) { * Post primitives to insert into the document. * A PostNodeBuilder should be read from the Editor, *not* instantiated on its own. */ -class PostNodeBuilder { - /** - * @private - */ - constructor() { - this.markupCache = {} - } +export default class PostNodeBuilder { + markupCache: { [key: string]: unknown } = {} /** * @return {Post} A new, blank post */ - createPost(sections = []) { + createPost(sections = []): Post { const post = new Post() post.builder = this @@ -55,7 +52,10 @@ class PostNodeBuilder { return post } - createMarkerableSection(type, tagName, markers = []) { + createMarkerableSection(type: Type.LIST_ITEM, tagName: string, markers: Marker[]): ListItem; + createMarkerableSection(type: Type.MARKUP_SECTION, tagName: string, markers: Marker[]): MarkupSection; + createMarkerableSection(type: Exclude, tagName: string, markers: Marker[]): never; + createMarkerableSection(type: Type, tagName: string, markers: Marker[] = []) { switch (type) { case LIST_ITEM_TYPE: return this.createListItem(markers) @@ -71,7 +71,12 @@ class PostNodeBuilder { * @param {Marker[]} [markers=[]] * @return {MarkupSection} */ - createMarkupSection(tagName = DEFAULT_MARKUP_SECTION_TAG_NAME, markers = [], isGenerated = false, attributes = {}) { + createMarkupSection( + tagName: string = DEFAULT_MARKUP_SECTION_TAG_NAME, + markers: Marker[] = [], + isGenerated = false, + attributes = {} + ): MarkupSection { tagName = normalizeTagName(tagName) const section = new MarkupSection(tagName, markers, attributes) if (isGenerated) { @@ -88,7 +93,7 @@ class PostNodeBuilder { return section } - createListItem(markers = []) { + createListItem(markers: Marker[] = []) { const tagName = normalizeTagName('li') const item = new ListItem(tagName, markers) item.builder = this @@ -108,7 +113,7 @@ class PostNodeBuilder { * @param {Object} [payload={}] * @return {CardSection} */ - createCardSection(name, payload = {}) { + createCardSection(name: string, payload: {} = {}): Card { const card = new Card(name, payload) card.builder = this return card @@ -119,7 +124,7 @@ class PostNodeBuilder { * @param {Markup[]} [markups=[]] * @return {Marker} */ - createMarker(value, markups = []) { + createMarker(value?: string, markups: Markup[] = []): Marker { const marker = new Marker(value, markups) marker.builder = this return marker @@ -132,7 +137,7 @@ class PostNodeBuilder { * @param {Markup[]} [markups=[]] * @return {Atom} */ - createAtom(name, value = '', payload = {}, markups = []) { + createAtom(name: string, value: string = '', payload: object = {}, markups: Markup[] = []): Atom { const atom = new Atom(name, value, payload, markups) atom.builder = this return atom @@ -143,7 +148,7 @@ class PostNodeBuilder { * @param {Object} attributes Key-value pairs of attributes for the markup * @return {Markup} */ - createMarkup(tagName, attributes = {}) { + createMarkup(tagName: string, attributes: object = {}): Markup { tagName = normalizeTagName(tagName) let markup = findMarkupInCache(this.markupCache, tagName, attributes) @@ -156,5 +161,3 @@ class PostNodeBuilder { return markup } } - -export default PostNodeBuilder diff --git a/src/js/models/post.js b/src/js/models/post.ts similarity index 68% rename from src/js/models/post.js rename to src/js/models/post.ts index 4117cfc48..9bf3aea14 100644 --- a/src/js/models/post.js +++ b/src/js/models/post.ts @@ -1,9 +1,20 @@ -import { POST_TYPE } from './types' -import LinkedList from 'mobiledoc-kit/utils/linked-list' -import { forEach } from 'mobiledoc-kit/utils/array-utils' -import Set from 'mobiledoc-kit/utils/set' -import Position from 'mobiledoc-kit/utils/cursor/position' -import assert from 'mobiledoc-kit/utils/assert' +import { Type } from './types' +import LinkedList from '../utils/linked-list' +import { forEach } from '../utils/array-utils' +import Set from '../utils/set' +import Position from '../utils/cursor/position' +import Range from '../utils/cursor/range' +import assert from '../utils/assert' +import Marker from './marker' +import Markerable, { isMarkerable } from './_markerable' +import Section from './_section' +import PostNodeBuilder from './post-node-builder' +import ListSection, { isListSection } from './list-section' +import ListItem, { isListItem } from './list-item' +import MarkupSection from './markup-section' +import RenderNode from './render-node' + +type SectionCallback = (section: Section, index: number) => void /** * The Post is an in-memory representation of an editor's document. @@ -13,24 +24,24 @@ import assert from 'mobiledoc-kit/utils/assert' * When persisting a post, it must first be serialized (loss-lessly) into * mobiledoc using {@link Editor#serialize}. */ -class Post { - /** - * @private - */ +export default class Post { + type = Type.POST + builder!: PostNodeBuilder + sections: LinkedList + renderNode!: RenderNode + constructor() { - this.type = POST_TYPE - this.sections = new LinkedList({ + this.sections = new LinkedList({ adoptItem: s => (s.post = s.parent = this), freeItem: s => (s.post = s.parent = null), }) } - /** * @return {Position} The position at the start of the post (will be a {@link BlankPosition} * if the post is blank) * @public */ - headPosition() { + headPosition(): Position { if (this.isBlank) { return Position.blankPosition() } else { @@ -43,7 +54,7 @@ class Post { * if the post is blank) * @public */ - tailPosition() { + tailPosition(): Position { if (this.isBlank) { return Position.blankPosition() } else { @@ -55,7 +66,7 @@ class Post { * @return {Range} A range encompassing the entire post * @public */ - toRange() { + toRange(): Range { return this.headPosition().toRange(this.tailPosition()) } @@ -69,7 +80,7 @@ class Post { * @return {Boolean} * @public */ - get hasContent() { + get hasContent(): boolean { if (this.sections.length > 1 || (this.sections.length === 1 && !this.sections.head.isBlank)) { return true } else { @@ -81,10 +92,10 @@ class Post { * @param {Range} range * @return {Array} markers that are completely contained by the range */ - markersContainedByRange(range) { - const markers = [] + markersContainedByRange(range: Range): Array { + const markers: Marker[] = [] - this.walkMarkerableSections(range, section => { + this.walkMarkerableSections(range, (section: Markerable) => { section._markersInRange(range.trimTo(section), (m, { isContained }) => { if (isContained) { markers.push(m) @@ -95,7 +106,7 @@ class Post { return markers } - markupsInRange(range) { + markupsInRange(range: Range) { const markups = new Set() if (range.isCollapsed) { @@ -126,16 +137,17 @@ class Post { return markups.toArray() } - walkAllLeafSections(callback) { + walkAllLeafSections(callback: SectionCallback) { let range = this.headPosition().toRange(this.tailPosition()) return this.walkLeafSections(range, callback) } - walkLeafSections(range, callback) { + walkLeafSections(range: Range, callback: SectionCallback) { const { head, tail } = range let index = 0 - let nextSection, shouldStop + let nextSection: Section + let shouldStop: boolean let currentSection = head.section while (currentSection) { @@ -153,9 +165,9 @@ class Post { } } - walkMarkerableSections(range, callback) { + walkMarkerableSections(range: Range, callback: (section: Markerable) => void) { this.walkLeafSections(range, section => { - if (section.isMarkerable) { + if (isMarkerable(section)) { callback(section) } }) @@ -163,7 +175,7 @@ class Post { // return the next section that has markers after this one, // possibly skipping non-markerable sections - _nextLeafSection(section) { + _nextLeafSection(section: Section) { if (!section) { return null } @@ -172,7 +184,7 @@ class Post { if (next) { if (next.isLeafSection) { return next - } else if (next.items) { + } else if (isListSection(next)) { return next.items.head } else { assert('Cannot determine next section from non-leaf-section', false) @@ -181,7 +193,7 @@ class Post { // if there is no section after this, but this section is a child // (e.g. a ListItem inside a ListSection), check for a markerable // section after its parent - return this._nextLeafSection(section.parent) + return this._nextLeafSection(section.parent!) } } @@ -189,27 +201,28 @@ class Post { * @param {Range} range * @return {Post} A new post, constrained to {range} */ - trimTo(range) { - const post = this.builder.createPost() + trimTo(range: Range): Post { const { builder } = this + const post = builder.createPost() const { head, tail } = range const tailNotSelected = tail.offset === 0 && head.section !== tail.section - let sectionParent = post, - listParent = null + let sectionParent: Post | null = post, + listParent: ListSection | null = null + this.walkLeafSections(range, section => { - let newSection - if (section.isMarkerable) { - if (section.isListItem) { + let newSection: Section + if (isMarkerable(section)) { + if (isListItem(section)) { if (listParent) { sectionParent = null } else { - listParent = builder.createListSection(section.parent.tagName) + listParent = builder.createListSection((section.parent! as ListSection).tagName) post.sections.append(listParent) sectionParent = null } newSection = builder.createListItem() - listParent.items.append(newSection) + listParent.items.append(newSection as ListItem) } else { listParent = null sectionParent = post @@ -219,10 +232,10 @@ class Post { let currentRange = range.trimTo(section) forEach(section.markersFor(currentRange.headSectionOffset, currentRange.tailSectionOffset), m => - newSection.markers.append(m) + (newSection as MarkupSection | ListItem).markers.append(m) ) } else { - newSection = tailNotSelected && tail.section === section ? builder.createMarkupSection('p') : section.clone() + newSection = tailNotSelected && tail.section === section ? builder.createMarkupSection('p') : expectCloneable(section).clone() sectionParent = post } @@ -234,4 +247,14 @@ class Post { } } -export default Post +interface Cloneable { + clone(): T +} + +function expectCloneable(section: T): T & Cloneable { + if (!('clone' in section)) { + throw new Error('Expected section to be cloneable') + } + + return section as T & Cloneable +} diff --git a/src/js/models/render-node.js b/src/js/models/render-node.js deleted file mode 100644 index 2996f8100..000000000 --- a/src/js/models/render-node.js +++ /dev/null @@ -1,95 +0,0 @@ -import LinkedItem from 'mobiledoc-kit/utils/linked-item' -import LinkedList from 'mobiledoc-kit/utils/linked-list' -import { containsNode } from '../utils/dom-utils' -import assert from 'mobiledoc-kit/utils/assert' - -export default class RenderNode extends LinkedItem { - constructor(postNode, renderTree) { - super() - this.parent = null - this.isDirty = true - this.isRemoved = false - this.postNode = postNode - this._childNodes = null - this._element = null - this._cursorElement = null // blank render nodes need a cursor element - this.renderTree = renderTree - - // RenderNodes for Markers keep track of their markupElement - this.markupElement = null - - // RenderNodes for Atoms use these properties - this.headTextNode = null - this.tailTextNode = null - this.atomNode = null - - // RenderNodes for cards use this property - this.cardNode = null - } - isAttached() { - assert('Cannot check if a renderNode is attached without an element.', !!this.element) - return containsNode(this.renderTree.rootElement, this.element) - } - get childNodes() { - if (!this._childNodes) { - this._childNodes = new LinkedList({ - adoptItem: item => (item.parent = this), - freeItem: item => item.destroy(), - }) - } - return this._childNodes - } - scheduleForRemoval() { - this.isRemoved = true - if (this.parent) { - this.parent.markDirty() - } - } - markDirty() { - this.isDirty = true - if (this.parent) { - this.parent.markDirty() - } - } - get isRendered() { - return !!this.element - } - markClean() { - this.isDirty = false - } - set element(element) { - const currentElement = this._element - this._element = element - - if (currentElement) { - this.renderTree.removeElementRenderNode(currentElement) - } - - if (element) { - this.renderTree.setElementRenderNode(element, this) - } - } - get element() { - return this._element - } - set cursorElement(cursorElement) { - this._cursorElement = cursorElement - } - get cursorElement() { - return this._cursorElement || this.element - } - destroy() { - this.element = null - this.parent = null - this.postNode = null - this.renderTree = null - } - reparsesMutationOfChildNode(node) { - if (this.postNode.isCardSection) { - return !containsNode(this.cardNode.element, node) - } else if (this.postNode.isAtom) { - return !containsNode(this.atomNode.element, node) - } - return true - } -} diff --git a/src/js/models/render-node.ts b/src/js/models/render-node.ts new file mode 100644 index 000000000..fc173296f --- /dev/null +++ b/src/js/models/render-node.ts @@ -0,0 +1,120 @@ +import LinkedItem from '../utils/linked-item' +import LinkedList from '../utils/linked-list' +import { containsNode } from '../utils/dom-utils' +import assert, { unwrap } from '../utils/assert' +import RenderTree from './render-tree' +import { Option } from '../utils/types' +import CardNode from './card-node' +import AtomNode from './atom-node' +import Section from './_section' +import Markuperable from '../utils/markuperable' + +type PostNode = Section | Markuperable + +export default class RenderNode extends LinkedItem { + parent: Option = null + isDirty = true + isRemoved = false + + postNode: Option + renderTree: Option + + // RenderNodes for Markers keep track of their markupElement + markupElement = null + + // RenderNodes for Atoms use these properties + headTextNode = null + tailTextNode = null + atomNode: Option = null + + // RenderNodes for cards use this property + cardNode: Option = null + + _childNodes: Option> = null + _element: Option = null + _cursorElement: Option = null // blank render nodes need a cursor element + + constructor(postNode: Section, renderTree: RenderTree) { + super() + this.postNode = postNode + this.renderTree = renderTree + } + + isAttached() { + assert('Cannot check if a renderNode is attached without an element.', !!this.element) + return containsNode(unwrap(unwrap(this.renderTree).rootElement), this.element) + } + + get childNodes() { + if (!this._childNodes) { + this._childNodes = new LinkedList({ + adoptItem: item => (item.parent = this), + freeItem: item => item.destroy(), + }) + } + return this._childNodes + } + + scheduleForRemoval() { + this.isRemoved = true + if (this.parent) { + this.parent.markDirty() + } + } + + markDirty() { + this.isDirty = true + if (this.parent) { + this.parent.markDirty() + } + } + + get isRendered() { + return !!this.element + } + + markClean() { + this.isDirty = false + } + + get element() { + return this._element + } + + set element(element) { + const currentElement = this._element + this._element = element + + if (currentElement) { + this.renderTree!.removeElementRenderNode(currentElement) + } + + if (element) { + this.renderTree!.setElementRenderNode(element, this) + } + } + + set cursorElement(cursorElement: Node | null) { + this._cursorElement = cursorElement + } + + get cursorElement() { + return this._cursorElement || this.element + } + + destroy() { + this.element = null + this.parent = null + this.postNode = null + this.renderTree = null + } + + reparsesMutationOfChildNode(node) { + if ((this.postNode as Section).isCardSection) { + return !containsNode(this.cardNode!.element, node) + } else if ((this.postNode as Markuperable).isAtom) { + return !containsNode(this.atomNode!.element, node) + } + return true + } +} diff --git a/src/js/models/render-tree.ts b/src/js/models/render-tree.ts index bb9b09289..05699dbef 100644 --- a/src/js/models/render-tree.ts +++ b/src/js/models/render-tree.ts @@ -1,15 +1,12 @@ import RenderNode from '../models/render-node' import ElementMap from '../utils/element-map' - -interface Post { - renderNode: RenderNode -} +import Section from './_section' export default class RenderTree { _rootNode: RenderNode _elements: ElementMap - constructor(rootPostNode: Post) { + constructor(rootPostNode: Section) { this._rootNode = this.buildRenderNode(rootPostNode) this._elements = new ElementMap() } @@ -22,7 +19,7 @@ export default class RenderTree { /** * @return {Boolean} */ - get isDirty() { + get isDirty(): boolean { return this.rootNode && this.rootNode.isDirty } /* @@ -72,7 +69,7 @@ export default class RenderTree { } } - buildRenderNode(postNode: Post) { + buildRenderNode(postNode: Section) { const renderNode = new RenderNode(postNode, this) postNode.renderNode = renderNode return renderNode diff --git a/src/js/models/types.ts b/src/js/models/types.ts index 4105d6297..7b9750f68 100644 --- a/src/js/models/types.ts +++ b/src/js/models/types.ts @@ -7,3 +7,15 @@ export const LIST_ITEM_TYPE = 'list-item' export const CARD_TYPE = 'card-section' export const IMAGE_SECTION_TYPE = 'image-section' export const ATOM_TYPE = 'atom' + +export const enum Type { + MARKUP_SECTION = 'markup-section', + LIST_SECTION = 'list-section', + MARKUP = 'markup', + MARKER = 'marker', + POST = 'post', + LIST_ITEM = 'list-item', + CARD = 'card-section', + IMAGE_SECTION = 'image-section', + ATOM = 'atom' +} diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.js index 95f001dbf..d9f0097a6 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.js @@ -1,6 +1,6 @@ -import CardNode from 'mobiledoc-kit/models/card-node' -import { detect, forEach } from 'mobiledoc-kit/utils/array-utils' -import AtomNode from 'mobiledoc-kit/models/atom-node' +import CardNode from '../models/card-node' +import { detect, forEach } from '../utils/array-utils' +import AtomNode from '../models/atom-node' import { POST_TYPE, MARKUP_SECTION_TYPE, diff --git a/src/js/utils/cursor.ts b/src/js/utils/cursor.ts index afb2b9323..08220178e 100644 --- a/src/js/utils/cursor.ts +++ b/src/js/utils/cursor.ts @@ -9,6 +9,7 @@ import RenderTree from '../models/render-tree' import Post from '../models/post' import { unwrap, assertNotNull, expect } from './assert' import { isCardSection } from '../models/card' +import Section from '../models/_section' export { Position, Range } @@ -45,8 +46,8 @@ class Cursor { isAddressable(element: Element) { let { renderTree } = this let renderNode = renderTree.findRenderNodeFromElement(element) - if (renderNode && renderNode.postNode.isCardSection) { - let renderedElement = renderNode.element + if (renderNode && (renderNode.postNode as Section).isCardSection) { + let renderedElement = renderNode.element! // card sections have addressable text nodes containing ‌ // as their first and last child @@ -90,9 +91,9 @@ class Cursor { if (isCardSection(section)) { offset = 0 if (position.offset === 0) { - node = section.renderNode.element.firstChild + node = section.renderNode.element!.firstChild } else { - node = section.renderNode.element.lastChild + node = section.renderNode.element!.lastChild } } else if (section.isBlank) { node = section.renderNode.cursorElement diff --git a/src/js/utils/cursor/position.ts b/src/js/utils/cursor/position.ts index 941888143..d8cc4cd69 100644 --- a/src/js/utils/cursor/position.ts +++ b/src/js/utils/cursor/position.ts @@ -8,7 +8,8 @@ import Range from './range' import Markerable from '../../models/_markerable' import Section from '../../models/_section' import RenderNode from '../../models/render-node' -import Card from '../../models/card' +import Card, { isCardSection } from '../../models/card' +import Markuperable from '../markuperable' const { FORWARD, BACKWARD } = DIRECTION @@ -18,9 +19,9 @@ const { FORWARD, BACKWARD } = DIRECTION const WORD_CHAR_REGEX = /[A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͅͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևְ-ׇֽֿׁׂׅׄא-תװ-ײؐ-ؚؠ-ٗٙ-ٟٮ-ۓە-ۜۡ-ۭۨ-ۯۺ-ۼۿܐ-ܿݍ-ޱߊ-ߪߴߵߺࠀ-ࠗࠚ-ࠬࡀ-ࡘࢠ-ࢴࣣ-ࣰࣩ-ऻऽ-ौॎ-ॐॕ-ॣॱ-ঃঅ-ঌএঐও-নপ-রলশ-হঽ-ৄেৈোৌৎৗড়ঢ়য়-ৣৰৱਁ-ਃਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਾ-ੂੇੈੋੌੑਖ਼-ੜਫ਼ੰ-ੵઁ-ઃઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽ-ૅે-ૉોૌૐૠ-ૣૹଁ-ଃଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽ-ୄେୈୋୌୖୗଡ଼ଢ଼ୟ-ୣୱஂஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹா-ூெ-ைொ-ௌௐௗఀ-ఃఅ-ఌఎ-ఐఒ-నప-హఽ-ౄె-ైొ-ౌౕౖౘ-ౚౠ-ౣಁ-ಃಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽ-ೄೆ-ೈೊ-ೌೕೖೞೠ-ೣೱೲഁ-ഃഅ-ഌഎ-ഐഒ-ഺഽ-ൄെ-ൈൊ-ൌൎൗൟ-ൣൺ-ൿංඃඅ-ඖක-නඳ-රලව-ෆා-ුූෘ-ෟෲෳก-ฺเ-ๆํກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ູົ-ຽເ-ໄໆໍໜ-ໟༀཀ-ཇཉ-ཬཱ-ཱྀྈ-ྗྙ-ྼက-ံးျ-ဿၐ-ၢၥ-ၨၮ-ႆႎႜႝႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚ፟ᎀ-ᎏᎠ-Ᏽᏸ-ᏽᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜓᜠ-ᜳᝀ-ᝓᝠ-ᝬᝮ-ᝰᝲᝳក-ឳា-ៈៗៜᠠ-ᡷᢀ-ᢪᢰ-ᣵᤀ-ᤞᤠ-ᤫᤰ-ᤸᥐ-ᥭᥰ-ᥴᦀ-ᦫᦰ-ᧉᨀ-ᨛᨠ-ᩞᩡ-ᩴᪧᬀ-ᬳᬵ-ᭃᭅ-ᭋᮀ-ᮩᮬ-ᮯᮺ-ᯥᯧ-ᯱᰀ-ᰵᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳳᳵᳶᴀ-ᶿᷧ-ᷴḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⒶ-ⓩⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⷠ-ⷿⸯ々-〇〡-〩〱-〵〸-〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿕ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙴ-ꙻꙿ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞭꞰ-ꞷꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠧꡀ-ꡳꢀ-ꣃꣲ-ꣷꣻꣽꤊ-ꤪꤰ-ꥒꥠ-ꥼꦀ-ꦲꦴ-ꦿꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨶꩀ-ꩍꩠ-ꩶꩺꩾ-ꪾꫀꫂꫛ-ꫝꫠ-ꫯꫲ-ꫵꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭥꭰ-ꯪ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ]|[0-9]|_|:/ function findParentSectionFromNode(renderTree: RenderTree, node: Node) { - let renderNode = renderTree.findRenderNodeFromElement(node, renderNode => renderNode.postNode.isSection) + let renderNode = renderTree.findRenderNodeFromElement(node, renderNode => (renderNode.postNode as Section).isSection) - return renderNode && renderNode.postNode + return renderNode && renderNode.postNode as Section } function findOffsetInMarkerable(markerable: Markerable, node: Node, offset: number) { @@ -58,7 +59,7 @@ function findOffsetInSection(section: Section, node: Node, offset: number) { } else { assertIsCard(section) assertHasRenderNode(section.renderNode) - let wrapperNode = section.renderNode.element + let wrapperNode = section.renderNode.element! let endTextNode = wrapperNode.lastChild if (node === endTextNode) { return 1 @@ -375,7 +376,7 @@ export default class Position { let section, offsetInSection if (renderNode) { - const marker = renderNode.postNode + const marker = renderNode.postNode as Marker section = marker.section assert(`Could not find parent section for mapped text node "${textNode.textContent}"`, !!section) @@ -401,13 +402,13 @@ export default class Position { // element if the user clicks an element that is immediately removed, // which can happen when clicking to remove a card. if (elementNode === renderTree.rootElement) { - let post = renderTree.rootNode.postNode + let post = renderTree.rootNode.postNode as Section position = offset === 0 ? post.headPosition() : post.tailPosition() } else { let section = findParentSectionFromNode(renderTree, elementNode) assert('Could not find parent section from element node', !!section) - if (section.isCardSection) { + if (isCardSection(section)) { // Selections in cards are usually made on a text node // containing a ‌ on one side or the other of the card but // some scenarios (Firefox) will result in selecting the @@ -424,8 +425,8 @@ export default class Position { // to be on the wrapper element itself let renderNode = renderTree.getElementRenderNode(elementNode) let postNode = renderNode && renderNode.postNode - if (postNode && postNode.isAtom) { - let sectionOffset = section.offsetOfMarker(postNode) + if (postNode && (postNode as Markuperable).isAtom) { + let sectionOffset = (section as Markerable).offsetOfMarker(postNode as Marker) if (offset > 1) { // we are on the tail side of the atom sectionOffset += postNode.length diff --git a/src/js/utils/linked-item.ts b/src/js/utils/linked-item.ts index c7b616a8d..d1fbb9922 100644 --- a/src/js/utils/linked-item.ts +++ b/src/js/utils/linked-item.ts @@ -1,4 +1,4 @@ -export default class LinkedItem { - next: T | null = null - prev: T | null = null +export default class LinkedItem { + next: this | null = null + prev: this | null = null } diff --git a/src/js/utils/markuperable.ts b/src/js/utils/markuperable.ts index 2376723c8..627c29c7d 100644 --- a/src/js/utils/markuperable.ts +++ b/src/js/utils/markuperable.ts @@ -1,20 +1,21 @@ import { normalizeTagName } from './dom-utils' import { detect, commonItemLength, forEach, filter } from './array-utils' - -interface Markup { - tagName: string -} +import Markup from '../models/markup' type MarkupCallback = (markup: Markup) => boolean - type MarkupOrMarkupCallback = Markup | MarkupCallback -export default class Markerupable { +export default abstract class Markuperable { markups: Markup[] = [] prev: this | null = null next: this | null = null + isAtom = false + isMarker = false + + abstract length: number + clearMarkups() { this.markups = [] } diff --git a/tests/acceptance/editor-cards-test.js b/tests/acceptance/editor-cards-test.js index 1346f24a2..f11cdaf2a 100644 --- a/tests/acceptance/editor-cards-test.js +++ b/tests/acceptance/editor-cards-test.js @@ -1,6 +1,6 @@ import { DIRECTION } from 'mobiledoc-kit/utils/key'; import Helpers from '../test-helpers'; -import { CARD_MODES } from 'mobiledoc-kit/models/card'; +import { CardMode } from 'mobiledoc-kit/models/card'; const { test, module } = Helpers; const { editor: { buildFromText } } = Helpers; @@ -250,7 +250,7 @@ test('editor ignores events when focus is inside a card', (assert) => { test('a moved card retains its inital editing mode', (assert) => { editorOpts.beforeRender = (editor) => { - editor.post.sections.tail.setInitialMode(CARD_MODES.EDIT); + editor.post.sections.tail.setInitialMode(CardMode.EDIT); }; editor = buildFromText(['','[simple]'], editorOpts); diff --git a/yarn.lock b/yarn.lock index f0c1a24e5..0f1522336 100644 --- a/yarn.lock +++ b/yarn.lock @@ -165,50 +165,66 @@ dependencies: "@types/node" "*" -"@typescript-eslint/eslint-plugin@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.0.2.tgz#4a114a066e2f9659b25682ee59d4866e15a17ec3" - integrity sha512-ER3bSS/A/pKQT/hjMGCK8UQzlL0yLjuCZ/G8CDFJFVTfl3X65fvq2lNYqOG8JPTfrPa2RULCdwfOyFjZEMNExQ== +"@typescript-eslint/eslint-plugin@^3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.1.tgz#5ced8fd2087fbb83a76973dea4a0d39d9cb4a642" + integrity sha512-06lfjo76naNeOMDl+mWG9Fh/a0UHKLGhin+mGaIw72FUMbMGBkdi/FEJmgEDzh4eE73KIYzHWvOCYJ0ak7nrJQ== dependencies: - "@typescript-eslint/experimental-utils" "3.0.2" + "@typescript-eslint/experimental-utils" "3.6.1" + debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.0.2.tgz#bb2131baede8df28ec5eacfa540308ca895e5fee" - integrity sha512-4Wc4EczvoY183SSEnKgqAfkj1eLtRgBQ04AAeG+m4RhTVyaazxc1uI8IHf0qLmu7xXe9j1nn+UoDJjbmGmuqXQ== +"@typescript-eslint/experimental-utils@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.6.1.tgz#b5a2738ebbceb3fa90c5b07d50bb1225403c4a54" + integrity sha512-oS+hihzQE5M84ewXrTlVx7eTgc52eu+sVmG7ayLfOhyZmJ8Unvf3osyFQNADHP26yoThFfbxcibbO0d2FjnYhg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "3.0.2" + "@typescript-eslint/types" "3.6.1" + "@typescript-eslint/typescript-estree" "3.6.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.0.2.tgz#a92ef339added9bf7fb92605ac99c93ef243e834" - integrity sha512-80Z7s83e8QXHNUspqVlWwb4t5gdz/1bBBmafElbK1wwAwiD/yvJsFyHRxlEpNrt4rdK6eB3p+2WEFkEDHAKk9w== +"@typescript-eslint/parser@^3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.6.1.tgz#216e8adf4ee9c629f77c985476a2ea07fb80e1dc" + integrity sha512-SLihQU8RMe77YJ/jGTqOt0lMq7k3hlPVfp7v/cxMnXA9T0bQYoMDfTsNgHXpwSJM1Iq2aAJ8WqekxUwGv5F67Q== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "3.0.2" - "@typescript-eslint/typescript-estree" "3.0.2" + "@typescript-eslint/experimental-utils" "3.6.1" + "@typescript-eslint/types" "3.6.1" + "@typescript-eslint/typescript-estree" "3.6.1" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.0.2.tgz#67a1ce4307ebaea43443fbf3f3be7e2627157293" - integrity sha512-cs84mxgC9zQ6viV8MEcigfIKQmKtBkZNDYf8Gru2M+MhnA6z9q0NFMZm2IEzKqAwN8lY5mFVd1Z8DiHj6zQ3Tw== +"@typescript-eslint/types@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.6.1.tgz#87600fe79a1874235d3cc1cf5c7e1a12eea69eee" + integrity sha512-NPxd5yXG63gx57WDTW1rp0cF3XlNuuFFB5G+Kc48zZ+51ZnQn9yjDEsjTPQ+aWM+V+Z0I4kuTFKjKvgcT1F7xQ== + +"@typescript-eslint/typescript-estree@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.6.1.tgz#a5c91fcc5497cce7922ff86bc37d5e5891dcdefa" + integrity sha512-G4XRe/ZbCZkL1fy09DPN3U0mR6SayIv1zSeBNquRFRk7CnVLgkC2ZPj8llEMJg5Y8dJ3T76SvTGtceytniaztQ== dependencies: + "@typescript-eslint/types" "3.6.1" + "@typescript-eslint/visitor-keys" "3.6.1" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/visitor-keys@3.6.1": + version "3.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.6.1.tgz#5c57a7772f4dd623cfeacc219303e7d46f963b37" + integrity sha512-qC8Olwz5ZyMTZrh4Wl3K4U6tfms0R/mzU4/5W3XeUZptVraGVmbptJbn6h2Ey6Rb3hOs3zWoAUebZk8t47KGiQ== + dependencies: + eslint-visitor-keys "^1.1.0" + JSONStream@^1.0.4: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -230,10 +246,10 @@ acorn-jsx@^5.2.0: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== -acorn@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.2.0.tgz#17ea7e40d7c8640ff54a694c889c26f31704effe" - integrity sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ== +acorn@^7.2.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" + integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== add-stream@^1.0.0: version "1.0.0" @@ -277,12 +293,10 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-escapes@^4.2.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== - dependencies: - type-fest "^0.11.0" +ansi-colors@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-regex@^2.0.0: version "2.1.1" @@ -651,14 +665,6 @@ chalk@^2.0.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" @@ -667,11 +673,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - charm@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/charm/-/charm-1.0.2.tgz#8add367153a6d9a581331052c4090991da995e35" @@ -679,18 +680,6 @@ charm@^1.0.0: dependencies: inherits "^2.0.1" -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" @@ -1173,11 +1162,6 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1230,6 +1214,13 @@ engine.io@~3.4.0: engine.io-parser "~2.2.0" ws "^7.1.2" +enquirer@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== + dependencies: + ansi-colors "^4.1.1" + entities@~2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436" @@ -1284,6 +1275,14 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" +eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-utils@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" @@ -1296,10 +1295,15 @@ eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.1.0.tgz#d9a1df25e5b7859b0a3d86bb05f0940ab676a851" - integrity sha512-DfS3b8iHMK5z/YLSme8K5cge168I8j8o1uiVmFCgnnjxZQbCGyraF8bMl7Ju4yfBmCuxD7shOF7eqGkcuIHfsA== +eslint-visitor-keys@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.4.0.tgz#4e35a2697e6c1972f9d6ef2b690ad319f80f206f" + integrity sha512-gU+lxhlPHu45H3JkEGgYhWhkR9wLHHEXC9FbWFnTlEkbKyZKWgWRLgf61E8zWmBuI6g5xKBph9ltg3NtZMVF8g== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -1307,10 +1311,11 @@ eslint@^7.1.0: cross-spawn "^7.0.2" debug "^4.0.1" doctrine "^3.0.0" - eslint-scope "^5.0.0" + enquirer "^2.3.5" + eslint-scope "^5.1.0" eslint-utils "^2.0.0" - eslint-visitor-keys "^1.1.0" - espree "^7.0.0" + eslint-visitor-keys "^1.2.0" + espree "^7.1.0" esquery "^1.2.0" esutils "^2.0.2" file-entry-cache "^5.0.1" @@ -1320,7 +1325,6 @@ eslint@^7.1.0: ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" @@ -1338,14 +1342,14 @@ eslint@^7.1.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-7.0.0.tgz#8a7a60f218e69f120a842dc24c5a88aa7748a74e" - integrity sha512-/r2XEx5Mw4pgKdyb7GNLQNsu++asx/dltf/CI8RFi9oGHxmQFgvLbc5Op4U6i8Oaj+kdslhJtVlEZeAqH5qOTw== +espree@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" + integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== dependencies: - acorn "^7.1.1" + acorn "^7.2.0" acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + eslint-visitor-keys "^1.2.0" esprima@^4.0.0: version "4.0.1" @@ -1460,15 +1464,6 @@ extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -1513,13 +1508,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.0" -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" @@ -1905,7 +1893,7 @@ https-proxy-agent@^3.0.0: agent-base "^4.3.0" debug "^3.1.0" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -1985,25 +1973,6 @@ ini@^1.3.2: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== -inquirer@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" - integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - interpret@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" @@ -2043,11 +2012,6 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" @@ -2612,11 +2576,6 @@ mime@>=2.0.3: resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5" integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - min-indent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" @@ -2716,11 +2675,6 @@ mustache@^3.0.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-3.2.1.tgz#89e78a9d207d78f2799b1e95764a25bf71a28322" integrity sha512-RERvMFdLpaFfSRIEe632yDm5nsd0SDKn8hGmcUwswnyiE5mtdZLDybtHAz6hjJhawokF0hXvGLtx9mrQfm6FkA== -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -2828,13 +2782,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - opener@1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" @@ -3361,14 +3308,6 @@ resolve@^1.14.2: dependencies: path-parse "^1.0.6" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - reusify@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -3429,23 +3368,11 @@ rollup@^2.10.3: optionalDependencies: fsevents "~2.1.2" -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - run-parallel@^1.1.9: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== -rxjs@^6.5.3: - version "6.5.5" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" - integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== - dependencies: - tslib "^1.9.0" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -3580,11 +3507,6 @@ signal-exit@^3.0.0: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= -signal-exit@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3760,15 +3682,6 @@ string-width@^3.0.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" -string-width@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" - integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.0" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -3979,12 +3892,12 @@ through2@^3.0.0: dependencies: readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.6: +through@2, "through@>=2.2.7 <3": version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -tmp@0.0.33, tmp@^0.0.33: +tmp@0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== @@ -4036,7 +3949,7 @@ trim-off-newlines@^1.0.0: resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" integrity sha1-n5up2e+odkw4dpi8v+sshI8RrbM= -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.8.1: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== @@ -4072,11 +3985,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934"