From 78a21a81583b32a06d62c2e0e934115c5cee48e7 Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Wed, 22 Jul 2020 10:01:59 -0400 Subject: [PATCH 1/4] =?UTF-8?q?RENDERER!=200.2.0=20=F0=9F=90=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/cards/{image.js => image.ts} | 8 +- src/js/models/_markerable.ts | 2 +- src/js/models/is-list-section.ts | 2 +- src/js/models/post-node-builder.ts | 10 +- src/js/models/post.ts | 13 ++- src/js/models/types.ts | 2 +- src/js/renderers/mobiledoc/{0-2.js => 0-2.ts} | 106 ++++++++++++------ src/js/utils/array-utils.ts | 16 ++- src/js/utils/compiler.ts | 28 ++++- src/js/utils/cursor/position.ts | 2 +- 10 files changed, 127 insertions(+), 62 deletions(-) rename src/js/cards/{image.js => image.ts} (51%) rename src/js/renderers/mobiledoc/{0-2.js => 0-2.ts} (53%) diff --git a/src/js/cards/image.js b/src/js/cards/image.ts similarity index 51% rename from src/js/cards/image.js rename to src/js/cards/image.ts index 0d10202db..6858d8751 100644 --- a/src/js/cards/image.js +++ b/src/js/cards/image.ts @@ -1,10 +1,14 @@ -import placeholderImageSrc from 'mobiledoc-kit/utils/placeholder-image-src' +import placeholderImageSrc from '../utils/placeholder-image-src' + +interface ImagePayload { + src?: string +} export default { name: 'image', type: 'dom', - render({ payload }) { + render({ payload }: { payload: ImagePayload }) { let img = document.createElement('img') img.src = payload.src || placeholderImageSrc return img diff --git a/src/js/models/_markerable.ts b/src/js/models/_markerable.ts index 0c5346b7a..101812e66 100644 --- a/src/js/models/_markerable.ts +++ b/src/js/models/_markerable.ts @@ -37,7 +37,7 @@ export default abstract class Markerable extends tagNameable(Section) { clone(): this { const newMarkers = this.markers.map(m => m.clone()) - return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) as any as this + return (this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) as any) as this } get isBlank() { diff --git a/src/js/models/is-list-section.ts b/src/js/models/is-list-section.ts index 49d35d3e3..727807744 100644 --- a/src/js/models/is-list-section.ts +++ b/src/js/models/is-list-section.ts @@ -1,4 +1,4 @@ -import ListSection from "./list-section" +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/post-node-builder.ts b/src/js/models/post-node-builder.ts index f4407dda6..f3e39c210 100644 --- a/src/js/models/post-node-builder.ts +++ b/src/js/models/post-node-builder.ts @@ -52,9 +52,13 @@ export default class PostNodeBuilder { return post } - 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.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: diff --git a/src/js/models/post.ts b/src/js/models/post.ts index 9bf3aea14..452238862 100644 --- a/src/js/models/post.ts +++ b/src/js/models/post.ts @@ -27,7 +27,7 @@ type SectionCallback = (section: Section, index: number) => void export default class Post { type = Type.POST builder!: PostNodeBuilder - sections: LinkedList + sections: LinkedList
renderNode!: RenderNode constructor() { @@ -45,7 +45,7 @@ export default class Post { if (this.isBlank) { return Position.blankPosition() } else { - return this.sections.head.headPosition() + return this.sections.head!.headPosition() } } @@ -58,7 +58,7 @@ export default class Post { if (this.isBlank) { return Position.blankPosition() } else { - return this.sections.tail.tailPosition() + return this.sections.tail!.tailPosition() } } @@ -81,7 +81,7 @@ export default class Post { * @public */ get hasContent(): boolean { - if (this.sections.length > 1 || (this.sections.length === 1 && !this.sections.head.isBlank)) { + if (this.sections.length > 1 || (this.sections.length === 1 && !this.sections.head!.isBlank)) { return true } else { return false @@ -235,7 +235,10 @@ export default class Post { (newSection as MarkupSection | ListItem).markers.append(m) ) } else { - newSection = tailNotSelected && tail.section === section ? builder.createMarkupSection('p') : expectCloneable(section).clone() + newSection = + tailNotSelected && tail.section === section + ? builder.createMarkupSection('p') + : expectCloneable(section).clone() sectionParent = post } diff --git a/src/js/models/types.ts b/src/js/models/types.ts index 7b9750f68..3d1df3d48 100644 --- a/src/js/models/types.ts +++ b/src/js/models/types.ts @@ -17,5 +17,5 @@ export const enum Type { LIST_ITEM = 'list-item', CARD = 'card-section', IMAGE_SECTION = 'image-section', - ATOM = 'atom' + ATOM = 'atom', } diff --git a/src/js/renderers/mobiledoc/0-2.js b/src/js/renderers/mobiledoc/0-2.ts similarity index 53% rename from src/js/renderers/mobiledoc/0-2.js rename to src/js/renderers/mobiledoc/0-2.ts index a01c7b9ee..6a013a749 100644 --- a/src/js/renderers/mobiledoc/0-2.js +++ b/src/js/renderers/mobiledoc/0-2.ts @@ -1,15 +1,14 @@ -import { visit, visitArray, compile } from '../../utils/compiler' +import { visit, visitArray, compile, Opcodes } from '../../utils/compiler' import { objectToSortedKVArray } from '../../utils/array-utils' -import { - POST_TYPE, - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKER_TYPE, - MARKUP_TYPE, - IMAGE_SECTION_TYPE, - CARD_TYPE, -} from '../../models/types' +import { Type } from '../../models/types' +import Post from '../../models/post' +import MarkupSection from '../../models/markup-section' +import ListSection from '../../models/list-section' +import ListItem from '../../models/list-item' +import Image from '../../models/image' +import Card from '../../models/card' +import Marker from '../../models/marker' +import Markup from '../../models/markup' export const MOBILEDOC_VERSION = '0.2.0' export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 @@ -18,60 +17,89 @@ export const MOBILEDOC_LIST_SECTION_TYPE = 3 export const MOBILEDOC_CARD_SECTION_TYPE = 10 const visitor = { - [POST_TYPE](node, opcodes) { + [Type.POST](node: Post, opcodes: Opcodes) { opcodes.push(['openPost']) visitArray(visitor, node.sections, opcodes) }, - [MARKUP_SECTION_TYPE](node, opcodes) { + [Type.MARKUP_SECTION](node: MarkupSection, opcodes: Opcodes) { opcodes.push(['openMarkupSection', node.tagName]) visitArray(visitor, node.markers, opcodes) }, - [LIST_SECTION_TYPE](node, opcodes) { + [Type.LIST_SECTION](node: ListSection, opcodes: Opcodes) { opcodes.push(['openListSection', node.tagName]) visitArray(visitor, node.items, opcodes) }, - [LIST_ITEM_TYPE](node, opcodes) { + [Type.LIST_ITEM](node: ListItem, opcodes: Opcodes) { opcodes.push(['openListItem']) visitArray(visitor, node.markers, opcodes) }, - [IMAGE_SECTION_TYPE](node, opcodes) { + [Type.IMAGE_SECTION](node: Image, opcodes: Opcodes) { opcodes.push(['openImageSection', node.src]) }, - [CARD_TYPE](node, opcodes) { + [Type.CARD](node: Card, opcodes: Opcodes) { opcodes.push(['openCardSection', node.name, node.payload]) }, - [MARKER_TYPE](node, opcodes) { + [Type.MARKER](node: Marker, opcodes: Opcodes) { opcodes.push(['openMarker', node.closedMarkups.length, node.value]) visitArray(visitor, node.openedMarkups, opcodes) }, - [MARKUP_TYPE](node, opcodes) { + [Type.MARKUP](node: Markup, opcodes: Opcodes) { opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, } -const postOpcodeCompiler = { - openMarker(closeCount, value) { +type OpcodeCompilerMarker = [number[], number, unknown] +type OpcodeCompilerSection = + | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]] + | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]] + | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] + | [typeof MOBILEDOC_CARD_SECTION_TYPE, string, {}] + +interface PostOpcodeCompilerResult { + version: typeof MOBILEDOC_VERSION + sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]] +} + +type PostOpcodeCompilerMarkerType = [string, string[]?] + +class PostOpcodeCompiler { + markupMarkerIds!: number[] + markers!: OpcodeCompilerMarker[] + sections!: OpcodeCompilerSection[] + items!: OpcodeCompilerMarker[][] + markerTypes!: PostOpcodeCompilerMarkerType[] + result!: PostOpcodeCompilerResult + + _markerTypeCache!: { [key: string]: number } + + openMarker(closeCount: number, value: unknown) { this.markupMarkerIds = [] this.markers.push([this.markupMarkerIds, closeCount, value || '']) - }, - openMarkupSection(tagName) { + } + + openMarkupSection(tagName: string) { this.markers = [] this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) - }, - openListSection(tagName) { + } + + openListSection(tagName: string) { this.items = [] this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) - }, + } + openListItem() { this.markers = [] this.items.push(this.markers) - }, - openImageSection(url) { + } + + openImageSection(url: string) { this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) - }, - openCardSection(name, payload) { + } + + openCardSection(name: string, payload: {}) { this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, name, payload]) - }, + } + openPost() { this.markerTypes = [] this.sections = [] @@ -79,12 +107,14 @@ const postOpcodeCompiler = { version: MOBILEDOC_VERSION, sections: [this.markerTypes, this.sections], } - }, - openMarkup(tagName, attributes) { + } + + openMarkup(tagName: string, attributes: string[]) { const index = this._findOrAddMarkerTypeIndex(tagName, attributes) this.markupMarkerIds.push(index) - }, - _findOrAddMarkerTypeIndex(tagName, attributesArray) { + } + + _findOrAddMarkerTypeIndex(tagName: string, attributesArray: string[]) { if (!this._markerTypeCache) { this._markerTypeCache = {} } @@ -92,7 +122,7 @@ const postOpcodeCompiler = { let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName] + let markerType: PostOpcodeCompilerMarkerType = [tagName] if (attributesArray.length) { markerType.push(attributesArray) } @@ -103,9 +133,11 @@ const postOpcodeCompiler = { } return index - }, + } } +const postOpcodeCompiler = new PostOpcodeCompiler() + /** * Render from post -> mobiledoc */ diff --git a/src/js/utils/array-utils.ts b/src/js/utils/array-utils.ts index 59f033031..0ebc7c74b 100644 --- a/src/js/utils/array-utils.ts +++ b/src/js/utils/array-utils.ts @@ -2,12 +2,16 @@ interface Detectable { detect(cb: (val: T) => boolean): T } -interface HasLength { +export interface HasLength { + length: number +} + +export interface Indexable { [key: number]: T length: number } -export function detect(enumerable: Detectable | HasLength, callback: (val: T) => boolean): T | undefined { +export function detect(enumerable: Detectable | Indexable, callback: (val: T) => boolean): T | undefined { if ('detect' in enumerable) { return enumerable.detect(callback) } else { @@ -23,7 +27,7 @@ interface Anyable { any(cb: (val: T) => boolean): boolean } -export function any(enumerable: Anyable | HasLength, callback: (val: T) => boolean): boolean { +export function any(enumerable: Anyable | Indexable, callback: (val: T) => boolean): boolean { if ('any' in enumerable) { return enumerable.any(callback) } @@ -41,7 +45,7 @@ interface Everyable { every(cb: (val: T) => boolean): boolean } -export function every(enumerable: Everyable | HasLength, callback: (val: T) => boolean): boolean { +export function every(enumerable: Everyable | Indexable, callback: (val: T) => boolean): boolean { if ('every' in enumerable) { return enumerable.every(callback) } @@ -59,7 +63,7 @@ export function toArray(arrayLike: ArrayLike): T[] { return Array.prototype.slice.call(arrayLike) } -interface ForEachable { +export interface ForEachable { forEach(cb: (val: T, idx: number) => void): void } @@ -68,7 +72,7 @@ interface ForEachable { * actually arrays, like NodeList * @private */ -export function forEach(enumerable: ForEachable | HasLength, callback: (val: T, idx: number) => void): void { +export function forEach(enumerable: ForEachable | Indexable, callback: (val: T, idx: number) => void): void { if ('forEach' in enumerable) { enumerable.forEach(callback) } else { diff --git a/src/js/utils/compiler.ts b/src/js/utils/compiler.ts index 9b9296bbe..c895b4642 100644 --- a/src/js/utils/compiler.ts +++ b/src/js/utils/compiler.ts @@ -1,11 +1,27 @@ -import { forEach } from './array-utils' +import { forEach, ForEachable, HasLength } from './array-utils' import assert from './assert' +import { Type } from '../models/types' +import Post from '../models/post' +import Image from '../models/image' +import ListSection from '../models/list-section' +import MarkupSection from '../models/markup-section' +import ListItem from '../models/list-item' +import Card from '../models/card' +import Marker from '../models/marker' +import Markup from '../models/markup' -type Opcode = [string] | [string, unknown] | [string, unknown, unknown] -type Opcodes = Opcode[] +export type Opcode = [string] | [string, unknown] | [string, unknown, unknown] +export type Opcodes = Opcode[] interface Visitor { - [key: string]: (node: CompileNode, opcodes: Opcodes) => void + [Type.POST]: (node: Post, opcodes: Opcodes) => void + [Type.MARKUP_SECTION]: (node: MarkupSection, opcodes: Opcodes) => void + [Type.LIST_SECTION]: (node: ListSection, opcodes: Opcodes) => void + [Type.LIST_ITEM]: (node: ListItem, opcodes: Opcodes) => void + [Type.IMAGE_SECTION]: (node: Image, opcodes: Opcodes) => void + [Type.CARD]: (node: Card, opcodes: Opcodes) => void + [Type.MARKER]: (node: Marker, opcodes: Opcodes) => void + [Type.MARKUP]: (node: Markup, opcodes: Opcodes) => void } interface CompileNode { @@ -38,7 +54,9 @@ export function compile(compiler: Compiler, opcodes: Opcodes) { } } -export function visitArray(visitor: Visitor, nodes: CompileNode[], opcodes: Opcodes) { +type CompileNodes = ForEachable & HasLength + +export function visitArray(visitor: Visitor, nodes: CompileNodes, opcodes: Opcodes) { if (!nodes || nodes.length === 0) { return } diff --git a/src/js/utils/cursor/position.ts b/src/js/utils/cursor/position.ts index d8cc4cd69..2c6c6f0ad 100644 --- a/src/js/utils/cursor/position.ts +++ b/src/js/utils/cursor/position.ts @@ -21,7 +21,7 @@ const WORD_CHAR_REGEX = /[A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͅͰ-ʹͶͷͺ function findParentSectionFromNode(renderTree: RenderTree, node: Node) { let renderNode = renderTree.findRenderNodeFromElement(node, renderNode => (renderNode.postNode as Section).isSection) - return renderNode && renderNode.postNode as Section + return renderNode && (renderNode.postNode as Section) } function findOffsetInMarkerable(markerable: Markerable, node: Node, offset: number) { From 5df728d060f7773d4e6f6ed7cd70c55334133264 Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Tue, 28 Jul 2020 09:44:40 -0400 Subject: [PATCH 2/4] =?UTF-8?q?RENDERER!=200.3.0=E2=80=A60.3.2=20?= =?UTF-8?q?=F0=9F=90=95=E2=80=8D=F0=9F=A6=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/renderers/mobiledoc/0-2.ts | 10 +- .../mobiledoc/{0-3-1.js => 0-3-1.ts} | 139 +++++++++++------ .../mobiledoc/{0-3-2.js => 0-3-2.ts} | 141 ++++++++++++------ src/js/renderers/mobiledoc/{0-3.js => 0-3.ts} | 139 +++++++++++------ src/js/utils/compiler.ts | 19 +-- src/js/utils/types.ts | 2 + 6 files changed, 284 insertions(+), 166 deletions(-) rename src/js/renderers/mobiledoc/{0-3-1.js => 0-3-1.ts} (53%) rename src/js/renderers/mobiledoc/{0-3-2.js => 0-3-2.ts} (53%) rename src/js/renderers/mobiledoc/{0-3.js => 0-3.ts} (53%) diff --git a/src/js/renderers/mobiledoc/0-2.ts b/src/js/renderers/mobiledoc/0-2.ts index 6a013a749..60c693080 100644 --- a/src/js/renderers/mobiledoc/0-2.ts +++ b/src/js/renderers/mobiledoc/0-2.ts @@ -55,7 +55,7 @@ type OpcodeCompilerSection = | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] | [typeof MOBILEDOC_CARD_SECTION_TYPE, string, {}] -interface PostOpcodeCompilerResult { +export interface Mobiledoc0_2 { version: typeof MOBILEDOC_VERSION sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]] } @@ -68,7 +68,7 @@ class PostOpcodeCompiler { sections!: OpcodeCompilerSection[] items!: OpcodeCompilerMarker[][] markerTypes!: PostOpcodeCompilerMarkerType[] - result!: PostOpcodeCompilerResult + result!: Mobiledoc0_2 _markerTypeCache!: { [key: string]: number } @@ -136,8 +136,6 @@ class PostOpcodeCompiler { } } -const postOpcodeCompiler = new PostOpcodeCompiler() - /** * Render from post -> mobiledoc */ @@ -146,10 +144,10 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post) { + render(post: Post): Mobiledoc0_2 { let opcodes = [] visit(visitor, post, opcodes) - let compiler = Object.create(postOpcodeCompiler) + let compiler = new PostOpcodeCompiler() compile(compiler, opcodes) return compiler.result }, diff --git a/src/js/renderers/mobiledoc/0-3-1.js b/src/js/renderers/mobiledoc/0-3-1.ts similarity index 53% rename from src/js/renderers/mobiledoc/0-3-1.js rename to src/js/renderers/mobiledoc/0-3-1.ts index 170a6eb44..7ffa35102 100644 --- a/src/js/renderers/mobiledoc/0-3-1.js +++ b/src/js/renderers/mobiledoc/0-3-1.ts @@ -1,16 +1,16 @@ -import { visit, visitArray, compile } from '../../utils/compiler' +import { visit, visitArray, compile, Opcodes } from '../../utils/compiler' import { objectToSortedKVArray } from '../../utils/array-utils' -import { - POST_TYPE, - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKER_TYPE, - MARKUP_TYPE, - IMAGE_SECTION_TYPE, - CARD_TYPE, - ATOM_TYPE, -} from '../../models/types' +import { Type } from '../../models/types' +import Post from '../../models/post' +import MarkupSection from '../../models/markup-section' +import ListSection from '../../models/list-section' +import ListItem from '../../models/list-item' +import Image from '../../models/image' +import Card from '../../models/card' +import Marker from '../../models/marker' +import Markup from '../../models/markup' +import Atom from '../../models/atom' +import { Dict } from '../../utils/types' export const MOBILEDOC_VERSION = '0.3.1' export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 @@ -22,70 +22,107 @@ export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { - [POST_TYPE](node, opcodes) { + [Type.POST](node: Post, opcodes: Opcodes) { opcodes.push(['openPost']) visitArray(visitor, node.sections, opcodes) }, - [MARKUP_SECTION_TYPE](node, opcodes) { + [Type.MARKUP_SECTION](node: MarkupSection, opcodes: Opcodes) { opcodes.push(['openMarkupSection', node.tagName]) visitArray(visitor, node.markers, opcodes) }, - [LIST_SECTION_TYPE](node, opcodes) { + [Type.LIST_SECTION](node: ListSection, opcodes: Opcodes) { opcodes.push(['openListSection', node.tagName]) visitArray(visitor, node.items, opcodes) }, - [LIST_ITEM_TYPE](node, opcodes) { + [Type.LIST_ITEM](node: ListItem, opcodes: Opcodes) { opcodes.push(['openListItem']) visitArray(visitor, node.markers, opcodes) }, - [IMAGE_SECTION_TYPE](node, opcodes) { + [Type.IMAGE_SECTION](node: Image, opcodes: Opcodes) { opcodes.push(['openImageSection', node.src]) }, - [CARD_TYPE](node, opcodes) { + [Type.CARD](node: Card, opcodes: Opcodes) { opcodes.push(['openCardSection', node.name, node.payload]) }, - [MARKER_TYPE](node, opcodes) { + [Type.MARKER](node: Marker, opcodes: Opcodes) { opcodes.push(['openMarker', node.closedMarkups.length, node.value]) visitArray(visitor, node.openedMarkups, opcodes) }, - [MARKUP_TYPE](node, opcodes) { + [Type.MARKUP](node: Markup, opcodes: Opcodes) { opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, - [ATOM_TYPE](node, opcodes) { + [Type.ATOM](node: Atom, opcodes: Opcodes) { opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) visitArray(visitor, node.openedMarkups, opcodes) }, } -const postOpcodeCompiler = { - openMarker(closeCount, value) { +type OpcodeCompilerMarker = [number, number[], number, unknown] +type OpcodeCompilerSection = + | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]] + | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]] + | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] + | [typeof MOBILEDOC_CARD_SECTION_TYPE, number] + +type OpcodeCompilerAtom = [string, unknown, {}] +type OpcodeCompilerCard = [string, {}] +type OpcodeCompilerMarkerType = [string, string[]?] + +export interface Mobiledoc0_3_1 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} + +class PostOpcodeCompiler { + markupMarkerIds!: number[] + markers!: OpcodeCompilerMarker[] + sections!: OpcodeCompilerSection[] + items!: OpcodeCompilerMarker[][] + markerTypes!: OpcodeCompilerMarkerType[] + atomTypes!: OpcodeCompilerAtom[] + cardTypes!: OpcodeCompilerCard[] + result!: Mobiledoc0_3_1 + + _markerTypeCache!: Dict + + openMarker(closeCount: number, value: unknown) { this.markupMarkerIds = [] this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) - }, - openMarkupSection(tagName) { + } + + openMarkupSection(tagName: string) { this.markers = [] this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) - }, - openListSection(tagName) { + } + + openListSection(tagName: string) { this.items = [] this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) - }, + } + openListItem() { this.markers = [] this.items.push(this.markers) - }, - openImageSection(url) { + } + + openImageSection(url: string) { this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) - }, - openCardSection(name, payload) { + } + + openCardSection(name: string, payload: {}) { const index = this._addCardTypeIndex(name, payload) this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) - }, - openAtom(closeCount, name, value, payload) { + } + + openAtom(closeCount: number, name: string, value: unknown, payload: {}) { const index = this._addAtomTypeIndex(name, value, payload) this.markupMarkerIds = [] this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) - }, + } + openPost() { this.atomTypes = [] this.cardTypes = [] @@ -98,22 +135,26 @@ const postOpcodeCompiler = { markups: this.markerTypes, sections: this.sections, } - }, - openMarkup(tagName, attributes) { + } + + openMarkup(tagName: string, attributes: string[]) { const index = this._findOrAddMarkerTypeIndex(tagName, attributes) this.markupMarkerIds.push(index) - }, - _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload] + } + + _addCardTypeIndex(cardName: string, payload: {}) { + let cardType: OpcodeCompilerCard = [cardName, payload] this.cardTypes.push(cardType) return this.cardTypes.length - 1 - }, - _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload] + } + + _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) { + let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload] this.atomTypes.push(atomType) return this.atomTypes.length - 1 - }, - _findOrAddMarkerTypeIndex(tagName, attributesArray) { + } + + _findOrAddMarkerTypeIndex(tagName: string, attributesArray: string[]) { if (!this._markerTypeCache) { this._markerTypeCache = {} } @@ -121,7 +162,7 @@ const postOpcodeCompiler = { let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName] + let markerType: OpcodeCompilerMarkerType = [tagName] if (attributesArray.length) { markerType.push(attributesArray) } @@ -132,9 +173,11 @@ const postOpcodeCompiler = { } return index - }, + } } +const postOpcodeCompiler = new PostOpcodeCompiler() + /** * Render from post -> mobiledoc */ @@ -143,7 +186,7 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post) { + render(post: Post): Mobiledoc0_3_1 { let opcodes = [] visit(visitor, post, opcodes) let compiler = Object.create(postOpcodeCompiler) diff --git a/src/js/renderers/mobiledoc/0-3-2.js b/src/js/renderers/mobiledoc/0-3-2.ts similarity index 53% rename from src/js/renderers/mobiledoc/0-3-2.js rename to src/js/renderers/mobiledoc/0-3-2.ts index b9f3edae0..fe8d1ff21 100644 --- a/src/js/renderers/mobiledoc/0-3-2.js +++ b/src/js/renderers/mobiledoc/0-3-2.ts @@ -1,16 +1,16 @@ -import { visit, visitArray, compile } from '../../utils/compiler' +import { visit, visitArray, compile, Opcodes } from '../../utils/compiler' import { objectToSortedKVArray } from '../../utils/array-utils' -import { - POST_TYPE, - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKER_TYPE, - MARKUP_TYPE, - IMAGE_SECTION_TYPE, - CARD_TYPE, - ATOM_TYPE, -} from '../../models/types' +import { Type } from '../../models/types' +import Post from '../../models/post' +import MarkupSection from '../../models/markup-section' +import ListSection from '../../models/list-section' +import ListItem from '../../models/list-item' +import Image from '../../models/image' +import Card from '../../models/card' +import Marker from '../../models/marker' +import Markup from '../../models/markup' +import Atom from '../../models/atom' +import { Dict } from '../../utils/types' export const MOBILEDOC_VERSION = '0.3.2' export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 @@ -22,78 +22,115 @@ export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { - [POST_TYPE](node, opcodes) { + [Type.POST](node: Post, opcodes: Opcodes) { opcodes.push(['openPost']) visitArray(visitor, node.sections, opcodes) }, - [MARKUP_SECTION_TYPE](node, opcodes) { - opcodes.push(['openMarkupSection', node.tagName, objectToSortedKVArray(node.attributes)]) + [Type.MARKUP_SECTION](node: MarkupSection, opcodes: Opcodes) { + opcodes.push(['openMarkupSection', node.tagName]) visitArray(visitor, node.markers, opcodes) }, - [LIST_SECTION_TYPE](node, opcodes) { - opcodes.push(['openListSection', node.tagName, objectToSortedKVArray(node.attributes)]) + [Type.LIST_SECTION](node: ListSection, opcodes: Opcodes) { + opcodes.push(['openListSection', node.tagName]) visitArray(visitor, node.items, opcodes) }, - [LIST_ITEM_TYPE](node, opcodes) { + [Type.LIST_ITEM](node: ListItem, opcodes: Opcodes) { opcodes.push(['openListItem']) visitArray(visitor, node.markers, opcodes) }, - [IMAGE_SECTION_TYPE](node, opcodes) { + [Type.IMAGE_SECTION](node: Image, opcodes: Opcodes) { opcodes.push(['openImageSection', node.src]) }, - [CARD_TYPE](node, opcodes) { + [Type.CARD](node: Card, opcodes: Opcodes) { opcodes.push(['openCardSection', node.name, node.payload]) }, - [MARKER_TYPE](node, opcodes) { + [Type.MARKER](node: Marker, opcodes: Opcodes) { opcodes.push(['openMarker', node.closedMarkups.length, node.value]) visitArray(visitor, node.openedMarkups, opcodes) }, - [MARKUP_TYPE](node, opcodes) { + [Type.MARKUP](node: Markup, opcodes: Opcodes) { opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, - [ATOM_TYPE](node, opcodes) { + [Type.ATOM](node: Atom, opcodes: Opcodes) { opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) visitArray(visitor, node.openedMarkups, opcodes) }, } -const postOpcodeCompiler = { - openMarker(closeCount, value) { +type OpcodeCompilerMarker = [number, number[], number, unknown] +type OpcodeCompilerSection = + | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[], string[]?] + | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][], string[]?] + | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] + | [typeof MOBILEDOC_CARD_SECTION_TYPE, number] + +type OpcodeCompilerAtom = [string, unknown, {}] +type OpcodeCompilerCard = [string, {}] +type OpcodeCompilerMarkerType = [string, string[]?] + +export interface Mobiledoc0_3_2 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} + +class PostOpcodeCompiler { + markupMarkerIds!: number[] + markers!: OpcodeCompilerMarker[] + sections!: OpcodeCompilerSection[] + items!: OpcodeCompilerMarker[][] + markerTypes!: OpcodeCompilerMarkerType[] + atomTypes!: OpcodeCompilerAtom[] + cardTypes!: OpcodeCompilerCard[] + result!: Mobiledoc0_3_2 + + _markerTypeCache!: Dict + + openMarker(closeCount: number, value: unknown) { this.markupMarkerIds = [] this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) - }, - openMarkupSection(tagName, attributes) { + } + + openMarkupSection(tagName: string, attributes: string[]) { this.markers = [] if (attributes && attributes.length !== 0) { this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers, attributes]) } else { this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) } - }, - openListSection(tagName, attributes) { + } + + openListSection(tagName: string, attributes: string[]) { this.items = [] if (attributes && attributes.length !== 0) { this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items, attributes]) } else { this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) } - }, + } + openListItem() { this.markers = [] this.items.push(this.markers) - }, - openImageSection(url) { + } + + openImageSection(url: string) { this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) - }, - openCardSection(name, payload) { + } + + openCardSection(name: string, payload: {}) { const index = this._addCardTypeIndex(name, payload) this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) - }, - openAtom(closeCount, name, value, payload) { + } + + openAtom(closeCount: number, name: string, value: unknown, payload: {}) { const index = this._addAtomTypeIndex(name, value, payload) this.markupMarkerIds = [] this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) - }, + } + openPost() { this.atomTypes = [] this.cardTypes = [] @@ -106,22 +143,26 @@ const postOpcodeCompiler = { markups: this.markerTypes, sections: this.sections, } - }, + } + openMarkup(tagName, attributes) { const index = this._findOrAddMarkerTypeIndex(tagName, attributes) this.markupMarkerIds.push(index) - }, - _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload] + } + + _addCardTypeIndex(cardName: string, payload: {}) { + let cardType: OpcodeCompilerCard = [cardName, payload] this.cardTypes.push(cardType) return this.cardTypes.length - 1 - }, - _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload] + } + + _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) { + let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload] this.atomTypes.push(atomType) return this.atomTypes.length - 1 - }, - _findOrAddMarkerTypeIndex(tagName, attributesArray) { + } + + _findOrAddMarkerTypeIndex(tagName: string, attributesArray: string[]) { if (!this._markerTypeCache) { this._markerTypeCache = {} } @@ -129,7 +170,7 @@ const postOpcodeCompiler = { let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName] + let markerType: OpcodeCompilerMarkerType = [tagName] if (attributesArray.length) { markerType.push(attributesArray) } @@ -140,9 +181,11 @@ const postOpcodeCompiler = { } return index - }, + } } +const postOpcodeCompiler = new PostOpcodeCompiler() + /** * Render from post -> mobiledoc */ @@ -151,7 +194,7 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post) { + render(post: Post): Mobiledoc0_3_2 { let opcodes = [] visit(visitor, post, opcodes) let compiler = Object.create(postOpcodeCompiler) diff --git a/src/js/renderers/mobiledoc/0-3.js b/src/js/renderers/mobiledoc/0-3.ts similarity index 53% rename from src/js/renderers/mobiledoc/0-3.js rename to src/js/renderers/mobiledoc/0-3.ts index cdd4a793b..db0024d10 100644 --- a/src/js/renderers/mobiledoc/0-3.js +++ b/src/js/renderers/mobiledoc/0-3.ts @@ -1,16 +1,16 @@ -import { visit, visitArray, compile } from '../../utils/compiler' +import { visit, visitArray, compile, Opcodes } from '../../utils/compiler' import { objectToSortedKVArray } from '../../utils/array-utils' -import { - POST_TYPE, - MARKUP_SECTION_TYPE, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKER_TYPE, - MARKUP_TYPE, - IMAGE_SECTION_TYPE, - CARD_TYPE, - ATOM_TYPE, -} from '../../models/types' +import { Type } from '../../models/types' +import Post from '../../models/post' +import MarkupSection from '../../models/markup-section' +import ListSection from '../../models/list-section' +import ListItem from '../../models/list-item' +import Image from '../../models/image' +import Card from '../../models/card' +import Marker from '../../models/marker' +import Markup from '../../models/markup' +import Atom from '../../models/atom' +import { Dict } from '../../utils/types' export const MOBILEDOC_VERSION = '0.3.0' export const MOBILEDOC_MARKUP_SECTION_TYPE = 1 @@ -22,70 +22,107 @@ export const MOBILEDOC_MARKUP_MARKER_TYPE = 0 export const MOBILEDOC_ATOM_MARKER_TYPE = 1 const visitor = { - [POST_TYPE](node, opcodes) { + [Type.POST](node: Post, opcodes: Opcodes) { opcodes.push(['openPost']) visitArray(visitor, node.sections, opcodes) }, - [MARKUP_SECTION_TYPE](node, opcodes) { + [Type.MARKUP_SECTION](node: MarkupSection, opcodes: Opcodes) { opcodes.push(['openMarkupSection', node.tagName]) visitArray(visitor, node.markers, opcodes) }, - [LIST_SECTION_TYPE](node, opcodes) { + [Type.LIST_SECTION](node: ListSection, opcodes: Opcodes) { opcodes.push(['openListSection', node.tagName]) visitArray(visitor, node.items, opcodes) }, - [LIST_ITEM_TYPE](node, opcodes) { + [Type.LIST_ITEM](node: ListItem, opcodes: Opcodes) { opcodes.push(['openListItem']) visitArray(visitor, node.markers, opcodes) }, - [IMAGE_SECTION_TYPE](node, opcodes) { + [Type.IMAGE_SECTION](node: Image, opcodes: Opcodes) { opcodes.push(['openImageSection', node.src]) }, - [CARD_TYPE](node, opcodes) { + [Type.CARD](node: Card, opcodes: Opcodes) { opcodes.push(['openCardSection', node.name, node.payload]) }, - [MARKER_TYPE](node, opcodes) { + [Type.MARKER](node: Marker, opcodes: Opcodes) { opcodes.push(['openMarker', node.closedMarkups.length, node.value]) visitArray(visitor, node.openedMarkups, opcodes) }, - [MARKUP_TYPE](node, opcodes) { + [Type.MARKUP](node: Markup, opcodes: Opcodes) { opcodes.push(['openMarkup', node.tagName, objectToSortedKVArray(node.attributes)]) }, - [ATOM_TYPE](node, opcodes) { + [Type.ATOM](node: Atom, opcodes: Opcodes) { opcodes.push(['openAtom', node.closedMarkups.length, node.name, node.value, node.payload]) visitArray(visitor, node.openedMarkups, opcodes) }, } -const postOpcodeCompiler = { - openMarker(closeCount, value) { +type OpcodeCompilerMarker = [number, number[], number, unknown] +type OpcodeCompilerSection = + | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]] + | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]] + | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] + | [typeof MOBILEDOC_CARD_SECTION_TYPE, number] + +type OpcodeCompilerAtom = [string, unknown, {}] +type OpcodeCompilerCard = [string, {}] +type OpcodeCompilerMarkerType = [string, string[]?] + +export interface Mobiledoc0_3 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} + +class PostOpcodeCompiler { + markupMarkerIds!: number[] + markers!: OpcodeCompilerMarker[] + sections!: OpcodeCompilerSection[] + items!: OpcodeCompilerMarker[][] + markerTypes!: OpcodeCompilerMarkerType[] + atomTypes!: OpcodeCompilerAtom[] + cardTypes!: OpcodeCompilerCard[] + result!: Mobiledoc0_3 + + _markerTypeCache!: Dict + + openMarker(closeCount: number, value: unknown) { this.markupMarkerIds = [] this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || '']) - }, - openMarkupSection(tagName) { + } + + openMarkupSection(tagName: string) { this.markers = [] this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers]) - }, - openListSection(tagName) { + } + + openListSection(tagName: string) { this.items = [] this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items]) - }, + } + openListItem() { this.markers = [] this.items.push(this.markers) - }, - openImageSection(url) { + } + + openImageSection(url: string) { this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url]) - }, - openCardSection(name, payload) { + } + + openCardSection(name: string, payload: {}) { const index = this._addCardTypeIndex(name, payload) this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index]) - }, - openAtom(closeCount, name, value, payload) { + } + + openAtom(closeCount: number, name: string, value: unknown, payload: {}) { const index = this._addAtomTypeIndex(name, value, payload) this.markupMarkerIds = [] this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index]) - }, + } + openPost() { this.atomTypes = [] this.cardTypes = [] @@ -98,22 +135,26 @@ const postOpcodeCompiler = { markups: this.markerTypes, sections: this.sections, } - }, - openMarkup(tagName, attributes) { + } + + openMarkup(tagName: string, attributes: string[]) { const index = this._findOrAddMarkerTypeIndex(tagName, attributes) this.markupMarkerIds.push(index) - }, - _addCardTypeIndex(cardName, payload) { - let cardType = [cardName, payload] + } + + _addCardTypeIndex(cardName: string, payload: {}) { + let cardType: OpcodeCompilerCard = [cardName, payload] this.cardTypes.push(cardType) return this.cardTypes.length - 1 - }, - _addAtomTypeIndex(atomName, atomValue, payload) { - let atomType = [atomName, atomValue, payload] + } + + _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) { + let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload] this.atomTypes.push(atomType) return this.atomTypes.length - 1 - }, - _findOrAddMarkerTypeIndex(tagName, attributesArray) { + } + + _findOrAddMarkerTypeIndex(tagName: string, attributesArray: string[]) { if (!this._markerTypeCache) { this._markerTypeCache = {} } @@ -121,7 +162,7 @@ const postOpcodeCompiler = { let index = this._markerTypeCache[key] if (index === undefined) { - let markerType = [tagName] + let markerType: OpcodeCompilerMarkerType = [tagName] if (attributesArray.length) { markerType.push(attributesArray) } @@ -132,9 +173,11 @@ const postOpcodeCompiler = { } return index - }, + } } +const postOpcodeCompiler = new PostOpcodeCompiler() + /** * Render from post -> mobiledoc */ @@ -143,7 +186,7 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post) { + render(post: Post): Mobiledoc0_3 { let opcodes = [] visit(visitor, post, opcodes) let compiler = Object.create(postOpcodeCompiler) diff --git a/src/js/utils/compiler.ts b/src/js/utils/compiler.ts index c895b4642..3d88a76ca 100644 --- a/src/js/utils/compiler.ts +++ b/src/js/utils/compiler.ts @@ -10,7 +10,7 @@ import Card from '../models/card' import Marker from '../models/marker' import Markup from '../models/markup' -export type Opcode = [string] | [string, unknown] | [string, unknown, unknown] +export type Opcode = [string, ...unknown[]] export type Opcodes = Opcode[] interface Visitor { @@ -34,23 +34,12 @@ export function visit(visitor: Visitor, node: CompileNode, opcodes: Opcodes) { visitor[method](node, opcodes) } -interface Compiler { - [key: string]: (...args: unknown[]) => void -} +type Compiler = {} export function compile(compiler: Compiler, opcodes: Opcodes) { - for (var i = 0, l = opcodes.length; i < l; i++) { + for (let i = 0, l = opcodes.length; i < l; i++) { let [method, ...params] = opcodes[i] - let length = params.length - if (length === 0) { - compiler[method].call(compiler) - } else if (length === 1) { - compiler[method].call(compiler, params[0]) - } else if (length === 2) { - compiler[method].call(compiler, params[0], params[1]) - } else { - compiler[method].apply(compiler, params) - } + compiler[method].apply(compiler, params) } } diff --git a/src/js/utils/types.ts b/src/js/utils/types.ts index c4904118d..f03f3fd51 100644 --- a/src/js/utils/types.ts +++ b/src/js/utils/types.ts @@ -1,2 +1,4 @@ export type Option = T | null export type Maybe = T | null | undefined + +export type Dict = { [key: string]: T } From 72a3735c8bb580c7ff47bf37d1eccdd01d84c18c Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Wed, 29 Jul 2020 09:27:34 -0400 Subject: [PATCH 3/4] =?UTF-8?q?RENDERER!=20Index=20+=20fix=20what=20I=20br?= =?UTF-8?q?oke=20=F0=9F=A6=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/renderers/mobiledoc/0-2.ts | 15 ++++++------ src/js/renderers/mobiledoc/0-3-1.ts | 22 ++++++++---------- src/js/renderers/mobiledoc/0-3-2.ts | 26 ++++++++++----------- src/js/renderers/mobiledoc/0-3.ts | 22 ++++++++---------- src/js/renderers/mobiledoc/index.js | 26 --------------------- src/js/renderers/mobiledoc/index.ts | 36 +++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 71 deletions(-) delete mode 100644 src/js/renderers/mobiledoc/index.js create mode 100644 src/js/renderers/mobiledoc/index.ts diff --git a/src/js/renderers/mobiledoc/0-2.ts b/src/js/renderers/mobiledoc/0-2.ts index 60c693080..8f7b47903 100644 --- a/src/js/renderers/mobiledoc/0-2.ts +++ b/src/js/renderers/mobiledoc/0-2.ts @@ -55,11 +55,6 @@ type OpcodeCompilerSection = | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string] | [typeof MOBILEDOC_CARD_SECTION_TYPE, string, {}] -export interface Mobiledoc0_2 { - version: typeof MOBILEDOC_VERSION - sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]] -} - type PostOpcodeCompilerMarkerType = [string, string[]?] class PostOpcodeCompiler { @@ -68,7 +63,7 @@ class PostOpcodeCompiler { sections!: OpcodeCompilerSection[] items!: OpcodeCompilerMarker[][] markerTypes!: PostOpcodeCompilerMarkerType[] - result!: Mobiledoc0_2 + result!: MobiledocV0_2 _markerTypeCache!: { [key: string]: number } @@ -136,6 +131,12 @@ class PostOpcodeCompiler { } } +export interface MobiledocV0_2 { + version: typeof MOBILEDOC_VERSION + sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]] +} + + /** * Render from post -> mobiledoc */ @@ -144,7 +145,7 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post: Post): Mobiledoc0_2 { + render(post: Post): MobiledocV0_2 { let opcodes = [] visit(visitor, post, opcodes) let compiler = new PostOpcodeCompiler() diff --git a/src/js/renderers/mobiledoc/0-3-1.ts b/src/js/renderers/mobiledoc/0-3-1.ts index 7ffa35102..42f7b3297 100644 --- a/src/js/renderers/mobiledoc/0-3-1.ts +++ b/src/js/renderers/mobiledoc/0-3-1.ts @@ -68,14 +68,6 @@ type OpcodeCompilerAtom = [string, unknown, {}] type OpcodeCompilerCard = [string, {}] type OpcodeCompilerMarkerType = [string, string[]?] -export interface Mobiledoc0_3_1 { - version: typeof MOBILEDOC_VERSION - atoms: OpcodeCompilerAtom[] - cards: OpcodeCompilerCard[] - markups: OpcodeCompilerMarkerType[] - sections: OpcodeCompilerSection[] -} - class PostOpcodeCompiler { markupMarkerIds!: number[] markers!: OpcodeCompilerMarker[] @@ -84,7 +76,7 @@ class PostOpcodeCompiler { markerTypes!: OpcodeCompilerMarkerType[] atomTypes!: OpcodeCompilerAtom[] cardTypes!: OpcodeCompilerCard[] - result!: Mobiledoc0_3_1 + result!: MobiledocV0_3_1 _markerTypeCache!: Dict @@ -176,7 +168,13 @@ class PostOpcodeCompiler { } } -const postOpcodeCompiler = new PostOpcodeCompiler() +export interface MobiledocV0_3_1 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} /** * Render from post -> mobiledoc @@ -186,10 +184,10 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post: Post): Mobiledoc0_3_1 { + render(post: Post): MobiledocV0_3_1 { let opcodes = [] visit(visitor, post, opcodes) - let compiler = Object.create(postOpcodeCompiler) + let compiler = new PostOpcodeCompiler() compile(compiler, opcodes) return compiler.result }, diff --git a/src/js/renderers/mobiledoc/0-3-2.ts b/src/js/renderers/mobiledoc/0-3-2.ts index fe8d1ff21..882736b05 100644 --- a/src/js/renderers/mobiledoc/0-3-2.ts +++ b/src/js/renderers/mobiledoc/0-3-2.ts @@ -27,11 +27,11 @@ const visitor = { visitArray(visitor, node.sections, opcodes) }, [Type.MARKUP_SECTION](node: MarkupSection, opcodes: Opcodes) { - opcodes.push(['openMarkupSection', node.tagName]) + opcodes.push(['openMarkupSection', node.tagName, objectToSortedKVArray(node.attributes)]) visitArray(visitor, node.markers, opcodes) }, [Type.LIST_SECTION](node: ListSection, opcodes: Opcodes) { - opcodes.push(['openListSection', node.tagName]) + opcodes.push(['openListSection', node.tagName, objectToSortedKVArray(node.attributes)]) visitArray(visitor, node.items, opcodes) }, [Type.LIST_ITEM](node: ListItem, opcodes: Opcodes) { @@ -68,14 +68,6 @@ type OpcodeCompilerAtom = [string, unknown, {}] type OpcodeCompilerCard = [string, {}] type OpcodeCompilerMarkerType = [string, string[]?] -export interface Mobiledoc0_3_2 { - version: typeof MOBILEDOC_VERSION - atoms: OpcodeCompilerAtom[] - cards: OpcodeCompilerCard[] - markups: OpcodeCompilerMarkerType[] - sections: OpcodeCompilerSection[] -} - class PostOpcodeCompiler { markupMarkerIds!: number[] markers!: OpcodeCompilerMarker[] @@ -84,7 +76,7 @@ class PostOpcodeCompiler { markerTypes!: OpcodeCompilerMarkerType[] atomTypes!: OpcodeCompilerAtom[] cardTypes!: OpcodeCompilerCard[] - result!: Mobiledoc0_3_2 + result!: MobiledocV0_3_2 _markerTypeCache!: Dict @@ -184,7 +176,13 @@ class PostOpcodeCompiler { } } -const postOpcodeCompiler = new PostOpcodeCompiler() +export interface MobiledocV0_3_2 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} /** * Render from post -> mobiledoc @@ -194,10 +192,10 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post: Post): Mobiledoc0_3_2 { + render(post: Post): MobiledocV0_3_2 { let opcodes = [] visit(visitor, post, opcodes) - let compiler = Object.create(postOpcodeCompiler) + let compiler = new PostOpcodeCompiler() compile(compiler, opcodes) return compiler.result }, diff --git a/src/js/renderers/mobiledoc/0-3.ts b/src/js/renderers/mobiledoc/0-3.ts index db0024d10..20bb4fef1 100644 --- a/src/js/renderers/mobiledoc/0-3.ts +++ b/src/js/renderers/mobiledoc/0-3.ts @@ -68,14 +68,6 @@ type OpcodeCompilerAtom = [string, unknown, {}] type OpcodeCompilerCard = [string, {}] type OpcodeCompilerMarkerType = [string, string[]?] -export interface Mobiledoc0_3 { - version: typeof MOBILEDOC_VERSION - atoms: OpcodeCompilerAtom[] - cards: OpcodeCompilerCard[] - markups: OpcodeCompilerMarkerType[] - sections: OpcodeCompilerSection[] -} - class PostOpcodeCompiler { markupMarkerIds!: number[] markers!: OpcodeCompilerMarker[] @@ -84,7 +76,7 @@ class PostOpcodeCompiler { markerTypes!: OpcodeCompilerMarkerType[] atomTypes!: OpcodeCompilerAtom[] cardTypes!: OpcodeCompilerCard[] - result!: Mobiledoc0_3 + result!: MobiledocV0_3 _markerTypeCache!: Dict @@ -176,7 +168,13 @@ class PostOpcodeCompiler { } } -const postOpcodeCompiler = new PostOpcodeCompiler() +export interface MobiledocV0_3 { + version: typeof MOBILEDOC_VERSION + atoms: OpcodeCompilerAtom[] + cards: OpcodeCompilerCard[] + markups: OpcodeCompilerMarkerType[] + sections: OpcodeCompilerSection[] +} /** * Render from post -> mobiledoc @@ -186,10 +184,10 @@ export default { * @param {Post} * @return {Mobiledoc} */ - render(post: Post): Mobiledoc0_3 { + render(post: Post): MobiledocV0_3 { let opcodes = [] visit(visitor, post, opcodes) - let compiler = Object.create(postOpcodeCompiler) + let compiler = new PostOpcodeCompiler() compile(compiler, opcodes) return compiler.result }, diff --git a/src/js/renderers/mobiledoc/index.js b/src/js/renderers/mobiledoc/index.js deleted file mode 100644 index a0b86ae8a..000000000 --- a/src/js/renderers/mobiledoc/index.js +++ /dev/null @@ -1,26 +0,0 @@ -import MobiledocRenderer_0_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from './0-2' -import MobiledocRenderer_0_3, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from './0-3' -import MobiledocRenderer_0_3_1, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from './0-3-1' -import MobiledocRenderer_0_3_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from './0-3-2' -import assert from 'mobiledoc-kit/utils/assert' - -export const MOBILEDOC_VERSION = MOBILEDOC_VERSION_0_3_2 - -export default { - render(post, version) { - switch (version) { - case MOBILEDOC_VERSION_0_2: - return MobiledocRenderer_0_2.render(post) - case MOBILEDOC_VERSION_0_3: - return MobiledocRenderer_0_3.render(post) - case MOBILEDOC_VERSION_0_3_1: - return MobiledocRenderer_0_3_1.render(post) - case undefined: - case null: - case MOBILEDOC_VERSION_0_3_2: - return MobiledocRenderer_0_3_2.render(post) - default: - assert(`Unknown version of mobiledoc renderer requested: ${version}`, false) - } - }, -} diff --git a/src/js/renderers/mobiledoc/index.ts b/src/js/renderers/mobiledoc/index.ts new file mode 100644 index 000000000..23ba47fc8 --- /dev/null +++ b/src/js/renderers/mobiledoc/index.ts @@ -0,0 +1,36 @@ +import MobiledocRenderer_0_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2, MobiledocV0_2 } from './0-2' +import MobiledocRenderer_0_3, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3, MobiledocV0_3 } from './0-3' +import MobiledocRenderer_0_3_1, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1, MobiledocV0_3_1 } from './0-3-1' +import MobiledocRenderer_0_3_2, { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2, MobiledocV0_3_2 } from './0-3-2' +import assert from '../../utils/assert' +import Post from '../../models/post' + +export const MOBILEDOC_VERSION = MOBILEDOC_VERSION_0_3_2 + +interface VersionTypes { + [MOBILEDOC_VERSION_0_2]: MobiledocV0_2 + [MOBILEDOC_VERSION_0_3]: MobiledocV0_3 + [MOBILEDOC_VERSION_0_3_1]: MobiledocV0_3_1 + [MOBILEDOC_VERSION_0_3_2]: MobiledocV0_3_2 +} + +type MobiledocVersion = keyof VersionTypes + +export default { + render(post: Post, version?: T): VersionTypes[T] { + switch (version) { + case MOBILEDOC_VERSION_0_2: + return MobiledocRenderer_0_2.render(post) as VersionTypes[T] + case MOBILEDOC_VERSION_0_3: + return MobiledocRenderer_0_3.render(post) as VersionTypes[T] + case MOBILEDOC_VERSION_0_3_1: + return MobiledocRenderer_0_3_1.render(post) as VersionTypes[T] + case undefined: + case null: + case MOBILEDOC_VERSION_0_3_2: + return MobiledocRenderer_0_3_2.render(post) as VersionTypes[T] + default: + assert(`Unknown version of mobiledoc renderer requested: ${version}`, false) + } + } +} From 9a0ad3761cc9d9d8401ec7d7b87fae84530d290e Mon Sep 17 00:00:00 2001 From: Zahra Jabini Date: Wed, 5 Aug 2020 09:44:42 -0400 Subject: [PATCH 4/4] =?UTF-8?q?Migrate=20DOM=20renderer=20to=20TypeScript?= =?UTF-8?q?=20=F0=9F=A7=9F=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/js/cards/image.ts | 3 +- src/js/models/_has-child-sections.ts | 10 + src/js/models/atom-node.ts | 5 +- src/js/models/atom.ts | 6 +- src/js/models/card-node.ts | 10 +- src/js/models/image.ts | 4 +- src/js/models/list-section.ts | 3 +- src/js/models/marker.ts | 4 +- src/js/models/markup.ts | 2 +- src/js/models/post.ts | 3 +- src/js/models/render-node.ts | 14 +- src/js/models/render-tree.ts | 4 +- .../{editor-dom.js => editor-dom.ts} | 263 +++++++++++------- src/js/renderers/mobiledoc/0-2.ts | 1 - src/js/renderers/mobiledoc/index.ts | 2 +- src/js/utils/array-utils.ts | 6 +- src/js/utils/markuperable.ts | 5 + 17 files changed, 216 insertions(+), 129 deletions(-) create mode 100644 src/js/models/_has-child-sections.ts rename src/js/renderers/{editor-dom.js => editor-dom.ts} (66%) diff --git a/src/js/cards/image.ts b/src/js/cards/image.ts index 6858d8751..bed70766e 100644 --- a/src/js/cards/image.ts +++ b/src/js/cards/image.ts @@ -1,4 +1,5 @@ import placeholderImageSrc from '../utils/placeholder-image-src' +import { CardData } from '../models/card-node' interface ImagePayload { src?: string @@ -13,4 +14,4 @@ export default { img.src = payload.src || placeholderImageSrc return img }, -} +} as CardData diff --git a/src/js/models/_has-child-sections.ts b/src/js/models/_has-child-sections.ts new file mode 100644 index 000000000..294b323d4 --- /dev/null +++ b/src/js/models/_has-child-sections.ts @@ -0,0 +1,10 @@ +import LinkedList from '../utils/linked-list' +import Section from './_section' + +export default interface HasChildSections { + sections: LinkedList +} + +export function hasChildSections(section: {}): section is HasChildSections { + return 'sections' in section +} diff --git a/src/js/models/atom-node.ts b/src/js/models/atom-node.ts index 1f14b4cb0..62ac3c5d7 100644 --- a/src/js/models/atom-node.ts +++ b/src/js/models/atom-node.ts @@ -11,9 +11,12 @@ export interface AtomRenderOptions { payload: {} } +export type AtomRenderHook = (options: AtomRenderOptions) => Element + export type AtomData = { name: string - render(options: AtomRenderOptions): Element + type: 'dom' + render: AtomRenderHook } export default class AtomNode { diff --git a/src/js/models/atom.ts b/src/js/models/atom.ts index 10242c575..41101a26f 100644 --- a/src/js/models/atom.ts +++ b/src/js/models/atom.ts @@ -1,4 +1,4 @@ -import { ATOM_TYPE } from './types' +import { Type } from './types' import Markuperable from '../utils/markuperable' import assert from '../utils/assert' import Marker from './marker' @@ -7,7 +7,7 @@ import Markup from './markup' const ATOM_LENGTH = 1 export default class Atom extends Markuperable { - type = ATOM_TYPE + type: Type = Type.ATOM isAtom = true name: string @@ -25,7 +25,7 @@ export default class Atom extends Markuperable { this.text = '' // An atom never has text, but it does have a value assert('Atom must have value', value !== undefined && value !== null) this.payload = payload - this.type = ATOM_TYPE + this.type = Type.ATOM this.isMarker = false this.isAtom = true diff --git a/src/js/models/card-node.ts b/src/js/models/card-node.ts index 19f537d1e..08afad0d7 100644 --- a/src/js/models/card-node.ts +++ b/src/js/models/card-node.ts @@ -3,15 +3,19 @@ import Card, { CardMode } from './card' export interface CardNodeOptions {} +export type CardRenderHook = (...args: any[]) => Element + type DidRenderCallback = null | (() => void) type TeardownCallback = null | (() => void) -type RenderMethod = (...args: any[]) => Element +type CardDataType = 'dom' export interface CardData { name: string - render: RenderMethod - edit: RenderMethod + type?: CardDataType + + render: CardRenderHook + edit?: CardRenderHook } type CardRenderMethodName = 'render' | 'edit' diff --git a/src/js/models/image.ts b/src/js/models/image.ts index 78dcecda1..3911ce66c 100644 --- a/src/js/models/image.ts +++ b/src/js/models/image.ts @@ -1,12 +1,12 @@ import { Type } from './types' import Section from './_section' +import { Option } from '../utils/types' export default class Image extends Section { - src: string | null = null + src: Option = null constructor() { super(Type.IMAGE_SECTION) - this.src = null } canJoin() { diff --git a/src/js/models/list-section.ts b/src/js/models/list-section.ts index c4b21258f..b05667cd3 100644 --- a/src/js/models/list-section.ts +++ b/src/js/models/list-section.ts @@ -8,12 +8,13 @@ import assert from '../utils/assert' import { entries } from '../utils/object-utils' import ListItem from './list-item' import { tagNameable } from './_tag-nameable' +import HasChildSections from './_has-child-sections' 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 attributable(tagNameable(Section)) { +export default class ListSection extends attributable(tagNameable(Section)) implements HasChildSections { isListSection = true isLeafSection = false diff --git a/src/js/models/marker.ts b/src/js/models/marker.ts index ed3a7e20f..6b626248f 100644 --- a/src/js/models/marker.ts +++ b/src/js/models/marker.ts @@ -1,7 +1,7 @@ import { isArrayEqual } from '../utils/array-utils' import Markuperable from '../utils/markuperable' import assert from '../utils/assert' -import { MARKER_TYPE } from './types' +import { Type } from './types' import Markup from './markup' import Markerable from './_markerable' import RenderNode from './render-node' @@ -17,7 +17,7 @@ export const HIGH_SURROGATE_RANGE = [0xd800, 0xdbff] export const LOW_SURROGATE_RANGE = [0xdc00, 0xdfff] export default class Marker extends Markuperable { - type = MARKER_TYPE + type: Type = Type.MARKER isMarker = true value: string diff --git a/src/js/models/markup.ts b/src/js/models/markup.ts index 224f5d55b..12f0abb4a 100644 --- a/src/js/models/markup.ts +++ b/src/js/models/markup.ts @@ -27,7 +27,7 @@ export const VALID_ATTRIBUTES = ['href', 'rel'] */ export default class Markup { tagName: string - attributes: { [key: string]: unknown } + attributes: { [key: string]: string } type = MARKUP_TYPE diff --git a/src/js/models/post.ts b/src/js/models/post.ts index 452238862..ccfe31c28 100644 --- a/src/js/models/post.ts +++ b/src/js/models/post.ts @@ -13,6 +13,7 @@ import ListSection, { isListSection } from './list-section' import ListItem, { isListItem } from './list-item' import MarkupSection from './markup-section' import RenderNode from './render-node' +import HasChildSections from './_has-child-sections' type SectionCallback = (section: Section, index: number) => void @@ -24,7 +25,7 @@ type SectionCallback = (section: Section, index: number) => void * When persisting a post, it must first be serialized (loss-lessly) into * mobiledoc using {@link Editor#serialize}. */ -export default class Post { +export default class Post implements HasChildSections { type = Type.POST builder!: PostNodeBuilder sections: LinkedList
diff --git a/src/js/models/render-node.ts b/src/js/models/render-node.ts index fc173296f..d856513fc 100644 --- a/src/js/models/render-node.ts +++ b/src/js/models/render-node.ts @@ -9,9 +9,9 @@ import AtomNode from './atom-node' import Section from './_section' import Markuperable from '../utils/markuperable' -type PostNode = Section | Markuperable +export type PostNode = Section | Markuperable -export default class RenderNode extends LinkedItem { +export default class RenderNode extends LinkedItem { parent: Option = null isDirty = true isRemoved = false @@ -20,21 +20,21 @@ export default class RenderNode extends LinkedItem { renderTree: Option // RenderNodes for Markers keep track of their markupElement - markupElement = null + markupElement: Option = null // RenderNodes for Atoms use these properties - headTextNode = null - tailTextNode = null + headTextNode: Option = null + tailTextNode: Option = null atomNode: Option = null // RenderNodes for cards use this property cardNode: Option = null _childNodes: Option> = null - _element: Option = null + _element: Option = null _cursorElement: Option = null // blank render nodes need a cursor element - constructor(postNode: Section, renderTree: RenderTree) { + constructor(postNode: PostNode, renderTree: RenderTree) { super() this.postNode = postNode this.renderTree = renderTree diff --git a/src/js/models/render-tree.ts b/src/js/models/render-tree.ts index 05699dbef..dcc575eaa 100644 --- a/src/js/models/render-tree.ts +++ b/src/js/models/render-tree.ts @@ -1,4 +1,4 @@ -import RenderNode from '../models/render-node' +import RenderNode, { PostNode } from '../models/render-node' import ElementMap from '../utils/element-map' import Section from './_section' @@ -69,7 +69,7 @@ export default class RenderTree { } } - buildRenderNode(postNode: Section) { + buildRenderNode(postNode: PostNode) { const renderNode = new RenderNode(postNode, this) postNode.renderNode = renderNode return renderNode diff --git a/src/js/renderers/editor-dom.js b/src/js/renderers/editor-dom.ts similarity index 66% rename from src/js/renderers/editor-dom.js rename to src/js/renderers/editor-dom.ts index d9f0097a6..84758d595 100644 --- a/src/js/renderers/editor-dom.js +++ b/src/js/renderers/editor-dom.ts @@ -1,21 +1,28 @@ -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, - LIST_SECTION_TYPE, - LIST_ITEM_TYPE, - MARKER_TYPE, - IMAGE_SECTION_TYPE, - CARD_TYPE, - ATOM_TYPE, -} from '../models/types' +import CardNode, { CardData, CardRenderHook } from '../models/card-node' +import { detect, forEach, ForEachable } from '../utils/array-utils' +import AtomNode, { AtomData, AtomRenderHook } from '../models/atom-node' +import { Type } from '../models/types' import { startsWith, endsWith } from '../utils/string-utils' import { addClassName, removeClassName } from '../utils/dom-utils' -import { MARKUP_SECTION_ELEMENT_NAMES } from '../models/markup-section' -import assert from '../utils/assert' -import { TAB } from 'mobiledoc-kit/utils/characters' +import MarkupSection, { MARKUP_SECTION_ELEMENT_NAMES } from '../models/markup-section' +import assert, { unwrap, assertNotNull } from '../utils/assert' +import { TAB } from '../utils/characters' +import Markup from '../models/markup' +import Marker from '../models/marker' +import Section from '../models/_section' +import { Attributable } from '../models/_attributable' +import { TagNameable } from '../models/_tag-nameable' +import ListSection from '../models/list-section' +import RenderNode, { PostNode } from '../models/render-node' +import { Option, Maybe } from '../utils/types' +import Atom from '../models/atom' +import Editor from '../editor/editor' +import { hasChildSections } from '../models/_has-child-sections' +import Post from '../models/post' +import ListItem from '../models/list-item' +import Image from '../models/image' +import Card from '../models/card' +import RenderTree from '../models/render-tree' export const CARD_ELEMENT_CLASS_NAME = '__mobiledoc-card' export const NO_BREAK_SPACE = '\u00A0' @@ -26,7 +33,7 @@ export const ATOM_CLASS_NAME = '-mobiledoc-kit__atom' export const EDITOR_HAS_NO_CONTENT_CLASS_NAME = '__has-no-content' export const EDITOR_ELEMENT_CLASS_NAME = '__mobiledoc-editor' -function createElementFromMarkup(doc, markup) { +function createElementFromMarkup(doc: Document, markup: Markup) { let element = doc.createElement(markup.tagName) Object.keys(markup.attributes).forEach(k => { element.setAttribute(k, markup.attributes[k]) @@ -38,16 +45,16 @@ const TWO_SPACES = `${SPACE}${SPACE}` const SPACE_AND_NO_BREAK = `${SPACE}${NO_BREAK_SPACE}` const SPACES_REGEX = new RegExp(TWO_SPACES, 'g') const TAB_REGEX = new RegExp(TAB, 'g') -const endsWithSpace = function (text) { +const endsWithSpace = function (text: string) { return endsWith(text, SPACE) } -const startsWithSpace = function (text) { +const startsWithSpace = function (text: string) { return startsWith(text, SPACE) } // FIXME: This can be done more efficiently with a single pass // building a correct string based on the original. -function renderHTMLText(marker) { +function renderHTMLText(marker: Marker) { let text = marker.value text = text.replace(SPACES_REGEX, SPACE_AND_NO_BREAK).replace(TAB_REGEX, TAB_CHARACTER) @@ -71,25 +78,26 @@ function renderHTMLText(marker) { // ascends from element upward, returning the last parent node that is not // parentElement -function penultimateParentOf(element, parentElement) { +function penultimateParentOf(element: Node, parentElement: Node) { while ( parentElement && element.parentNode !== parentElement && element.parentNode !== document.body // ensure the while loop stops ) { - element = element.parentNode + element = element.parentNode as Node } return element } -function setSectionAttributesOnElement(section, element) { +function setSectionAttributesOnElement(section: Attributable, element: HTMLElement) { section.eachAttribute((key, value) => { element.setAttribute(key, value) }) } -function renderMarkupSection(section) { - let element +function renderMarkupSection(section: TagNameable & Attributable) { + let element: HTMLElement + if (MARKUP_SECTION_ELEMENT_NAMES.indexOf(section.tagName) !== -1) { element = document.createElement(section.tagName) } else { @@ -102,7 +110,7 @@ function renderMarkupSection(section) { return element } -function renderListSection(section) { +function renderListSection(section: ListSection) { let element = document.createElement(section.tagName) setSectionAttributesOnElement(section, element) @@ -125,7 +133,7 @@ function renderInlineCursorPlaceholder() { function renderCard() { let wrapper = document.createElement('div') let cardElement = document.createElement('div') - cardElement.contentEditable = false + cardElement.contentEditable = 'false' addClassName(cardElement, CARD_ELEMENT_CLASS_NAME) wrapper.appendChild(renderInlineCursorPlaceholder()) wrapper.appendChild(cardElement) @@ -138,7 +146,7 @@ function renderCard() { * @return {DOMElement} the wrapped element * @private */ -function wrapElement(element, openedMarkups) { +function wrapElement(element: Node, openedMarkups: Markup[]): Node { let wrappedElement = element for (let i = openedMarkups.length - 1; i >= 0; i--) { @@ -153,9 +161,9 @@ function wrapElement(element, openedMarkups) { // Attach the element to its parent element at the correct position based on the // previousRenderNode -function attachElementToParent(element, parentElement, previousRenderNode = null) { +function attachElementToParent(element: Node, parentElement: Node, previousRenderNode: Option = null) { if (previousRenderNode) { - let previousSibling = previousRenderNode.element + let previousSibling = previousRenderNode.element! let previousSiblingPenultimate = penultimateParentOf(previousSibling, parentElement) parentElement.insertBefore(element, previousSiblingPenultimate.nextSibling) } else { @@ -163,9 +171,9 @@ function attachElementToParent(element, parentElement, previousRenderNode = null } } -function renderAtom(atom, element, previousRenderNode) { +function renderAtom(atom: Atom, element: HTMLElement, previousRenderNode: Option) { let atomElement = document.createElement('span') - atomElement.contentEditable = false + atomElement.contentEditable = 'false' let wrapper = document.createElement('span') addClassName(wrapper, ATOM_CLASS_NAME) @@ -188,17 +196,22 @@ function renderAtom(atom, element, previousRenderNode) { } } -function getNextMarkerElement(renderNode) { - let element = renderNode.element.parentNode - let marker = renderNode.postNode +function getNextMarkerElement(renderNode: RenderNode) { + let element = renderNode.element!.parentNode + let marker = renderNode.postNode! as Marker let closedCount = marker.closedMarkups.length while (closedCount--) { - element = element.parentNode + element = element!.parentNode } return element } +interface RenderMarkerResult { + element: Node + markupElement: Node +} + /** * Render the marker * @param {Marker} marker the marker to render @@ -206,12 +219,12 @@ function getNextMarkerElement(renderNode) { * @param {RenderNode} [previousRenderNode] The render node before this one, which * affects the determination of where to insert this rendered marker. * @return {Object} With properties `element` and `markupElement`. - * The element (textNode) that has the text for + * The node (textNode) that has the text for * this marker, and the outermost rendered element. If the marker has no * markups, element and markupElement will be the same textNode * @private */ -function renderMarker(marker, parentElement, previousRenderNode) { +function renderMarker(marker: Marker, parentElement: Node, previousRenderNode: Option): RenderMarkerResult { let text = renderHTMLText(marker) let element = document.createTextNode(text) @@ -223,29 +236,39 @@ function renderMarker(marker, parentElement, previousRenderNode) { // Attach the render node's element to the DOM, // replacing the originalElement if it exists -function attachRenderNodeElementToDOM(renderNode, originalElement = null) { - const element = renderNode.element - const hasRendered = !!originalElement +function attachRenderNodeElementToDOM(renderNode: RenderNode, originalElement: Option = null) { + const element = unwrap(renderNode.element) + + assertNotNull('expected RenderNode to have a parent', renderNode.parent) - if (hasRendered) { - let parentElement = renderNode.parent.element + if (originalElement) { + // RenderNode has already rendered + let parentElement = renderNode.parent!.element! parentElement.replaceChild(element, originalElement) } else { - let parentElement, nextSiblingElement + // RenderNode has not yet been rendered + let parentElement: Node + let nextSiblingElement: Option + if (renderNode.prev) { - let previousElement = renderNode.prev.element - parentElement = previousElement.parentNode + let previousElement = unwrap(renderNode.prev.element) + parentElement = unwrap(previousElement.parentNode) nextSiblingElement = previousElement.nextSibling } else { - parentElement = renderNode.parent.element + parentElement = renderNode.parent.element! nextSiblingElement = parentElement.firstChild } parentElement.insertBefore(element, nextSiblingElement) } } -function removeRenderNodeSectionFromParent(renderNode, section) { +function removeRenderNodeSectionFromParent(renderNode: RenderNode, section: Section) { + assertNotNull('expected RenderNode to have a parent', renderNode.parent) + assertNotNull('expected parent RenderNode to have a PostNode', renderNode.parent.postNode) + const parent = renderNode.parent.postNode + assert('expected PostNode to have sections', hasChildSections(parent)) + parent.sections.remove(section) } @@ -255,7 +278,7 @@ function removeRenderNodeElementFromParent(renderNode) { } } -function validateCards(cards = []) { +function validateCards(cards: CardData[] = []) { forEach(cards, card => { assert(`Card "${card.name}" must define type "dom", has: "${card.type}"`, card.type === 'dom') assert(`Card "${card.name}" must define \`render\` method`, !!card.render) @@ -263,7 +286,7 @@ function validateCards(cards = []) { return cards } -function validateAtoms(atoms = []) { +function validateAtoms(atoms: AtomData[] = []) { forEach(atoms, atom => { assert(`Atom "${atom.name}" must define type "dom", has: "${atom.type}"`, atom.type === 'dom') assert(`Atom "${atom.name}" must define \`render\` method`, !!atom.render) @@ -271,8 +294,27 @@ function validateAtoms(atoms = []) { return atoms } +type VisitArgs = [RenderNode, ForEachable, boolean?] +type VisitFn = (...args: VisitArgs) => void + class Visitor { - constructor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) { + editor: Editor + cards: CardData[] + atoms: AtomData[] + + unknownCardHandler: CardRenderHook + unknownAtomHandler: AtomRenderHook + + options: {} + + constructor( + editor: Editor, + cards: CardData[], + atoms: AtomData[], + unknownCardHandler: CardRenderHook, + unknownAtomHandler: AtomRenderHook, + options: {} + ) { this.editor = editor this.cards = validateCards(cards) this.atoms = validateAtoms(atoms) @@ -281,12 +323,12 @@ class Visitor { this.options = options } - _findCard(cardName) { + _findCard(cardName: string) { let card = detect(this.cards, card => card.name === cardName) return card || this._createUnknownCard(cardName) } - _createUnknownCard(cardName) { + _createUnknownCard(cardName: string): CardData { assert(`Unknown card "${cardName}" found, but no unknownCardHandler is defined`, !!this.unknownCardHandler) return { @@ -297,12 +339,12 @@ class Visitor { } } - _findAtom(atomName) { + _findAtom(atomName: string) { let atom = detect(this.atoms, atom => atom.name === atomName) return atom || this._createUnknownAtom(atomName) } - _createUnknownAtom(atomName) { + _createUnknownAtom(atomName: string): AtomData { assert(`Unknown atom "${atomName}" found, but no unknownAtomHandler is defined`, !!this.unknownAtomHandler) return { @@ -312,20 +354,24 @@ class Visitor { } } - [POST_TYPE](renderNode, post, visit) { + [Type.POST](renderNode: RenderNode, post: Post, visit: VisitFn) { if (!renderNode.element) { renderNode.element = document.createElement('div') } - addClassName(renderNode.element, EDITOR_ELEMENT_CLASS_NAME) + + let element = renderNode.element as Element + addClassName(element, EDITOR_ELEMENT_CLASS_NAME) + if (post.hasContent) { - removeClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) + removeClassName(element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) } else { - addClassName(renderNode.element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) + addClassName(element, EDITOR_HAS_NO_CONTENT_CLASS_NAME) } + visit(renderNode, post.sections) } - [MARKUP_SECTION_TYPE](renderNode, section, visit) { + [Type.MARKUP_SECTION](renderNode: RenderNode, section: MarkupSection, visit: VisitFn) { const originalElement = renderNode.element // Always rerender the section -- its tag name or attributes may have changed. @@ -344,7 +390,7 @@ class Visitor { } } - [LIST_SECTION_TYPE](renderNode, section, visit) { + [Type.LIST_SECTION](renderNode: RenderNode, section: ListSection, visit: VisitFn) { const originalElement = renderNode.element renderNode.element = renderListSection(section) @@ -354,7 +400,7 @@ class Visitor { visit(renderNode, section.items, visitAll) } - [LIST_ITEM_TYPE](renderNode, item, visit) { + [Type.LIST_ITEM](renderNode: RenderNode, item: ListItem, visit: VisitFn) { // FIXME do we need to do anything special for rerenders? renderNode.element = renderListItem() renderNode.cursorElement = null @@ -370,13 +416,13 @@ class Visitor { } } - [MARKER_TYPE](renderNode, marker) { - let parentElement + [Type.MARKER](renderNode: RenderNode, marker: Marker) { + let parentElement: Node if (renderNode.prev) { - parentElement = getNextMarkerElement(renderNode.prev) + parentElement = getNextMarkerElement(renderNode.prev)! } else { - parentElement = renderNode.parent.element + parentElement = renderNode.parent!.element! } let { element, markupElement } = renderMarker(marker, parentElement, renderNode.prev) @@ -385,29 +431,29 @@ class Visitor { renderNode.markupElement = markupElement } - [IMAGE_SECTION_TYPE](renderNode, section) { + [Type.IMAGE_SECTION](renderNode: RenderNode, section: Image) { if (renderNode.element) { if (renderNode.element.src !== section.src) { - renderNode.element.src = section.src + renderNode.element.src = section.src || '' } } else { let element = document.createElement('img') - element.src = section.src + element.src = section.src || '' if (renderNode.prev) { - let previousElement = renderNode.prev.element + let previousElement = renderNode.prev.element! let nextElement = previousElement.nextSibling if (nextElement) { - nextElement.parentNode.insertBefore(element, nextElement) + nextElement.parentNode!.insertBefore(element, nextElement) } } if (!element.parentNode) { - renderNode.parent.element.appendChild(element) + renderNode.parent!.element!.appendChild(element) } renderNode.element = element } } - [CARD_TYPE](renderNode, section) { + [Type.CARD](renderNode: RenderNode, section: Card) { const originalElement = renderNode.element const { editor, options } = this @@ -424,19 +470,19 @@ class Visitor { cardNode[initialMode]() } - [ATOM_TYPE](renderNode, atomModel) { - let parentElement + [Type.ATOM](renderNode: RenderNode, atomModel: Atom) { + let parentElement: Node if (renderNode.prev) { - parentElement = getNextMarkerElement(renderNode.prev) + parentElement = getNextMarkerElement(renderNode.prev)! } else { - parentElement = renderNode.parent.element + parentElement = renderNode.parent!.element! } const { editor, options } = this const { wrapper, markupElement, atomElement, headTextNode, tailTextNode } = renderAtom( atomModel, - parentElement, + parentElement as HTMLElement, renderNode.prev ) const atom = this._findAtom(atomModel.name) @@ -461,26 +507,26 @@ class Visitor { } let destroyHooks = { - [POST_TYPE](/*renderNode, post*/) { + [Type.POST](/*renderNode, post*/) { assert('post destruction is not supported by the renderer', false) }, - [MARKUP_SECTION_TYPE](renderNode, section) { + [Type.MARKUP_SECTION](renderNode: RenderNode, section: MarkupSection) { removeRenderNodeSectionFromParent(renderNode, section) removeRenderNodeElementFromParent(renderNode) }, - [LIST_SECTION_TYPE](renderNode, section) { + [Type.LIST_SECTION](renderNode: RenderNode, section: ListSection) { removeRenderNodeSectionFromParent(renderNode, section) removeRenderNodeElementFromParent(renderNode) }, - [LIST_ITEM_TYPE](renderNode, li) { + [Type.LIST_ITEM](renderNode: RenderNode, li: ListItem) { removeRenderNodeSectionFromParent(renderNode, li) removeRenderNodeElementFromParent(renderNode) }, - [MARKER_TYPE](renderNode, marker) { + [Type.MARKER](renderNode: RenderNode, marker: Marker) { // FIXME before we render marker, should delete previous renderNode's element // and up until the next marker element @@ -495,18 +541,18 @@ let destroyHooks = { marker.section.markers.remove(marker) } - if (markupElement.parentNode) { + if (markupElement!.parentNode) { // if no parentNode, the browser already removed this element - markupElement.parentNode.removeChild(markupElement) + markupElement!.parentNode.removeChild(markupElement!) } }, - [IMAGE_SECTION_TYPE](renderNode, section) { + [Type.IMAGE_SECTION](renderNode: RenderNode, section: Image) { removeRenderNodeSectionFromParent(renderNode, section) removeRenderNodeElementFromParent(renderNode) }, - [CARD_TYPE](renderNode, section) { + [Type.CARD](renderNode: RenderNode, section: Card) { if (renderNode.cardNode) { renderNode.cardNode.teardown() } @@ -514,20 +560,20 @@ let destroyHooks = { removeRenderNodeElementFromParent(renderNode) }, - [ATOM_TYPE](renderNode, atom) { + [Type.ATOM](renderNode: RenderNode, atom: Atom) { if (renderNode.atomNode) { renderNode.atomNode.teardown() } // an atom is a kind of marker so just call its destroy hook vs copying here - destroyHooks[MARKER_TYPE](renderNode, atom) + destroyHooks[Type.MARKER](renderNode, (atom as unknown) as Marker) }, } // removes children from parentNode (a RenderNode) that are scheduled for removal -function removeDestroyedChildren(parentNode, forceRemoval = false) { +function removeDestroyedChildren(parentNode: RenderNode, forceRemoval = false) { let child = parentNode.childNodes.head - let nextChild, method + let nextChild: Option, method: Type while (child) { nextChild = child.next if (child.isRemoved || forceRemoval) { @@ -543,7 +589,7 @@ function removeDestroyedChildren(parentNode, forceRemoval = false) { // Find an existing render node for the given postNode, or // create one, insert it into the tree, and return it -function lookupNode(renderTree, parentNode, postNode, previousNode) { +function lookupNode(renderTree: RenderTree, parentNode: RenderNode, postNode: PostNode, previousNode) { if (postNode.renderNode) { return postNode.renderNode } else { @@ -554,7 +600,21 @@ function lookupNode(renderTree, parentNode, postNode, previousNode) { } export default class Renderer { - constructor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) { + editor: Editor + visitor: Visitor + nodes: RenderNode[] + hasRendered: boolean + + renderTree: Option = null + + constructor( + editor: Editor, + cards: CardData[], + atoms: AtomData[], + unknownCardHandler: CardRenderHook, + unknownAtomHandler: AtomRenderHook, + options: {} + ) { this.editor = editor this.visitor = new Visitor(editor, cards, atoms, unknownCardHandler, unknownAtomHandler, options) this.nodes = [] @@ -565,13 +625,13 @@ export default class Renderer { if (!this.hasRendered) { return } - let renderNode = this.renderTree.rootNode + let renderNode = unwrap(this.renderTree).rootNode let force = true removeDestroyedChildren(renderNode, force) } - visit(renderTree, parentNode, postNodes, visitAll = false) { - let previousNode + visit(renderTree: RenderTree, parentNode: RenderNode, postNodes: ForEachable, visitAll = false) { + let previousNode: RenderNode postNodes.forEach(postNode => { let node = lookupNode(renderTree, parentNode, postNode, previousNode) if (node.isDirty || visitAll) { @@ -581,19 +641,20 @@ export default class Renderer { }) } - render(renderTree) { + render(renderTree: RenderTree) { this.hasRendered = true this.renderTree = renderTree - let renderNode = renderTree.rootNode - let method, postNode + let renderNode: Maybe = renderTree.rootNode + let method: Type + let postNode: PostNode while (renderNode) { removeDestroyedChildren(renderNode) - postNode = renderNode.postNode + postNode = renderNode.postNode! method = postNode.type assert(`EditorDom visitor cannot handle type ${method}`, !!this.visitor[method]) - this.visitor[method](renderNode, postNode, (...args) => this.visit(renderTree, ...args)) + this.visitor[method](renderNode, postNode, (...args: VisitArgs) => this.visit(renderTree, ...args)) renderNode.markClean() renderNode = this.nodes.shift() } diff --git a/src/js/renderers/mobiledoc/0-2.ts b/src/js/renderers/mobiledoc/0-2.ts index 8f7b47903..aa450fbb4 100644 --- a/src/js/renderers/mobiledoc/0-2.ts +++ b/src/js/renderers/mobiledoc/0-2.ts @@ -136,7 +136,6 @@ export interface MobiledocV0_2 { sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]] } - /** * Render from post -> mobiledoc */ diff --git a/src/js/renderers/mobiledoc/index.ts b/src/js/renderers/mobiledoc/index.ts index 23ba47fc8..6a64b4588 100644 --- a/src/js/renderers/mobiledoc/index.ts +++ b/src/js/renderers/mobiledoc/index.ts @@ -32,5 +32,5 @@ export default { default: assert(`Unknown version of mobiledoc renderer requested: ${version}`, false) } - } + }, } diff --git a/src/js/utils/array-utils.ts b/src/js/utils/array-utils.ts index 0ebc7c74b..7a204c07a 100644 --- a/src/js/utils/array-utils.ts +++ b/src/js/utils/array-utils.ts @@ -1,3 +1,5 @@ +import { Dict } from './types' + interface Detectable { detect(cb: (val: T) => boolean): T } @@ -194,8 +196,8 @@ export function isArrayEqual(arr1: ArrayLike, arr2: ArrayLike): boolean } // return an object with only the valid keys -export function filterObject(object: T, validKeys: string[] = []) { - let result: { [key: string]: unknown } = {} +export function filterObject(object: Dict, validKeys: string[] = []) { + let result: Dict = {} forEach( filter(Object.keys(object), key => validKeys.indexOf(key) !== -1), diff --git a/src/js/utils/markuperable.ts b/src/js/utils/markuperable.ts index 627c29c7d..c88200ffb 100644 --- a/src/js/utils/markuperable.ts +++ b/src/js/utils/markuperable.ts @@ -1,6 +1,8 @@ import { normalizeTagName } from './dom-utils' import { detect, commonItemLength, forEach, filter } from './array-utils' import Markup from '../models/markup' +import RenderNode from '../models/render-node' +import { Type } from '../models/types' type MarkupCallback = (markup: Markup) => boolean type MarkupOrMarkupCallback = Markup | MarkupCallback @@ -14,6 +16,9 @@ export default abstract class Markuperable { isAtom = false isMarker = false + renderNode: RenderNode | null = null + + abstract type: Type abstract length: number clearMarkups() {