Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Typescriptify Phase IV 💃🕺 #742

Merged
merged 12 commits into from
Sep 2, 2020
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ module.exports = {
"no-unneeded-ternary": "off",
"no-unused-expressions": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error"],
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"no-useless-backreference": "error",
"no-useless-call": "off",
"no-useless-computed-key": "error",
Expand Down
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function commonPlugins() {
},
],
}),
typescript(),
typescript({ noEmitOnError: false }),
]
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import assert from 'mobiledoc-kit/utils/assert'
import {
MARKUP_SECTION_TYPE,
LIST_SECTION_TYPE,
POST_TYPE,
CARD_TYPE,
IMAGE_SECTION_TYPE,
LIST_ITEM_TYPE,
} from 'mobiledoc-kit/models/types'

const MARKERABLE = 'markerable',
NESTED_MARKERABLE = 'nested_markerable',
NON_MARKERABLE = 'non_markerable'
import assert from '../../utils/assert'
import { Option } from '../../utils/types'
import { Type } from '../../models/types'
import Post from '../../models/post'
import Editor from '../editor'
import PostNodeBuilder from '../../models/post-node-builder'
import { Position } from '../../utils/cursor'
import Section, { WithParent } from '../../models/_section'
import MarkupSection from '../../models/markup-section'
import ListSection from '../../models/list-section'
import ListItem from '../../models/list-item'
import Card from '../../models/card'
import Image from '../../models/image'
import Markerable from '../../models/_markerable'
import { Cloneable } from '../../models/_cloneable'
import HasChildSections, { hasChildSections } from '../../models/_has-child-sections'

const MARKERABLE = 'markerable'
const NESTED_MARKERABLE = 'nested_markerable'
const NON_MARKERABLE = 'non_markerable'

class Visitor {
constructor(inserter, cursorPosition) {
let { postEditor, post } = inserter
postEditor: Editor
builder: PostNodeBuilder
_post: Post
_hasInsertedFirstLeafSection: boolean
_cursorPosition!: Position

constructor({ postEditor, post }: Inserter, cursorPosition: Position) {
this.postEditor = postEditor
this._post = post
this.cursorPosition = cursorPosition
Expand All @@ -32,7 +44,7 @@ class Visitor {
this.postEditor.setRange(position)
}

visit(node) {
visit(node: Post | Section) {
let method = node.type
assert(`Cannot visit node of type ${node.type}`, !!this[method])
this[method](node)
Expand All @@ -51,7 +63,7 @@ class Visitor {
}

get cursorSection() {
return this.cursorPosition.section
return this.cursorPosition.section!
}

get cursorOffset() {
Expand All @@ -62,21 +74,22 @@ class Visitor {
return this.cursorSection.isNested
}

[POST_TYPE](node) {
if (this.cursorSection.isBlank && !this._isNested) {
[Type.POST](node: Post) {
let { cursorSection } = this
if (cursorSection.isBlank && !cursorSection.isNested) {
// replace blank section with entire post
let newSections = node.sections.map(s => s.clone())
this._replaceSection(this.cursorSection, newSections)
this._replaceSection(cursorSection as Section & WithParent<HasChildSections>, newSections)
} else {
node.sections.forEach(section => this.visit(section))
}
}

[MARKUP_SECTION_TYPE](node) {
[Type.MARKUP_SECTION](node: MarkupSection) {
this[MARKERABLE](node)
}

[LIST_SECTION_TYPE](node) {
[Type.LIST_SECTION](node: ListSection) {
let hasNext = !!node.next
node.items.forEach(item => this.visit(item))

Expand All @@ -85,19 +98,19 @@ class Visitor {
}
}

[LIST_ITEM_TYPE](node) {
[Type.LIST_ITEM](node: ListItem) {
this[NESTED_MARKERABLE](node)
}

[CARD_TYPE](node) {
[Type.CARD](node: Card) {
this[NON_MARKERABLE](node)
}

[IMAGE_SECTION_TYPE](node) {
[Type.IMAGE_SECTION](node: Image) {
this[NON_MARKERABLE](node)
}

[NON_MARKERABLE](section) {
[NON_MARKERABLE](section: Cloneable<Section>) {
if (this._isNested) {
this._breakNestedAtCursor()
} else if (!this.cursorSection.isBlank) {
Expand All @@ -107,7 +120,7 @@ class Visitor {
this._insertLeafSection(section)
}

[MARKERABLE](section) {
[MARKERABLE](section: Markerable) {
if (this._canMergeSection(section)) {
this._mergeSection(section)
} else if (this._isNested && this._isMarkerable) {
Expand All @@ -116,7 +129,7 @@ class Visitor {
this._breakAtCursor()

// Advance the cursor to the head of the blank list item
let nextPosition = this.cursorSection.next.headPosition()
let nextPosition = this.cursorSection.next!.headPosition()
this.cursorPosition = nextPosition

// Merge this section onto the list item
Expand All @@ -127,22 +140,22 @@ class Visitor {
}
}

[NESTED_MARKERABLE](section) {
[NESTED_MARKERABLE](section: Markerable) {
if (this._canMergeSection(section)) {
this._mergeSection(section)
return
}

section = this._isNested ? section : this._wrapNestedSection(section)
let insertedSection = this._isNested ? section : this._wrapNestedSection(section as ListItem)
this._breakAtCursor()
this._insertLeafSection(section)
this._insertLeafSection(insertedSection)
}

// break out of a nested cursor position
_breakNestedAtCursor() {
assert('Cannot call _breakNestedAtCursor if not nested', this._isNested)

let parent = this.cursorSection.parent
let parent = this.cursorSection.parent!
let cursorAtEndOfList = this.cursorPosition.isEqual(parent.tailPosition())

if (cursorAtEndOfList) {
Expand All @@ -160,6 +173,7 @@ class Visitor {
let list = this.cursorSection.parent,
position = this.cursorPosition,
blank = this.builder.createMarkupSection()

let [pre, post] = this.postEditor._splitListAtPosition(list, position)

let collection = this._post.sections,
Expand All @@ -168,14 +182,14 @@ class Visitor {
return [pre, blank, post]
}

_wrapNestedSection(section) {
let tagName = section.parent.tagName
_wrapNestedSection(section: ListItem) {
let tagName = section.parent!.tagName
let parent = this.builder.createListSection(tagName)
parent.items.append(section.clone())
return parent
}

_mergeSection(section) {
_mergeSection(section: Markerable) {
assert('Can only merge markerable sections', this._isMarkerable && section.isMarkerable)
this._hasInsertedFirstLeafSection = true

Expand Down Expand Up @@ -213,8 +227,8 @@ class Visitor {
this.cursorPosition = pre.tailPosition()
}

_replaceSection(section, newSections) {
assert('Cannot replace section that does not have parent.sections', section.parent && section.parent.sections)
_replaceSection(section: Section, newSections: Section[]) {
assert('Cannot replace section that does not have parent.sections', hasChildSections(section.parent!))
assert('Must pass enumerable to _replaceSection', !!newSections.forEach)

let collection = section.parent.sections
Expand All @@ -228,7 +242,11 @@ class Visitor {
this.cursorPosition = lastSection.tailPosition()
}

_insertSectionBefore(section, reference) {
_insertSectionBefore(section: Section, reference?: Option<Section>) {
assert(
'Cannot insert into section that does not have parent.sections',
hasChildSections(this.cursorSection.parent!)
)
let collection = this.cursorSection.parent.sections
this.postEditor.insertSectionBefore(collection, section, reference)

Expand All @@ -237,15 +255,15 @@ class Visitor {

// Insert a section after the parent section.
// E.g., add a markup section after a list section
_insertSectionAfter(section, parent) {
_insertSectionAfter(section: Section, parent: Section) {
assert('Cannot _insertSectionAfter nested section', !parent.isNested)
let reference = parent.next
let collection = this._post.sections
this.postEditor.insertSectionBefore(collection, section, reference)
this.cursorPosition = section.tailPosition()
}

_insertLeafSection(section) {
_insertLeafSection(section: Cloneable<Section>) {
assert('Can only _insertLeafSection when cursor is at end of section', this.cursorPosition.isTail())

this._hasInsertedFirstLeafSection = true
Expand All @@ -267,12 +285,15 @@ class Visitor {
}

export default class Inserter {
constructor(postEditor, post) {
postEditor: Editor
post: Post

constructor(postEditor: Editor, post: Post) {
this.postEditor = postEditor
this.post = post
}

insert(cursorPosition, newPost) {
insert(cursorPosition: Position, newPost: Post) {
let visitor = new Visitor(this, cursorPosition)
if (!newPost.isBlank) {
visitor.visit(newPost)
Expand Down
13 changes: 13 additions & 0 deletions src/js/models/_cloneable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Section from './_section'

export type Cloneable<T> = T & {
clone(): Cloneable<T>
}

export function expectCloneable<T extends Section>(section: T): Cloneable<T> {
if (!('clone' in section)) {
throw new Error('Expected section to be cloneable')
}

return section as Cloneable<T>
}
22 changes: 12 additions & 10 deletions src/js/models/_markerable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import Section from './_section'
import Marker from './marker'
import { tagNameable } from './_tag-nameable'
import { Type } from './types'
import { Cloneable } from './_cloneable'
import Markuperable from '../utils/markuperable'

type MarkerableType = Type.LIST_ITEM | Type.MARKUP_SECTION

export default abstract class Markerable extends tagNameable(Section) {
export default abstract class Markerable extends tagNameable(Section) implements Cloneable<Markerable> {
type: MarkerableType
markers: LinkedList<Marker>
markers: LinkedList<Markuperable>

constructor(type: MarkerableType, tagName: string, markers: Marker[] = []) {
constructor(type: MarkerableType, tagName: string, markers: Markuperable[] = []) {
super(type)
this.type = type
this.isMarkerable = true
Expand Down Expand Up @@ -70,7 +72,7 @@ export default abstract class Markerable extends tagNameable(Section) {
*
* @return {Number} The offset relative to the start of this section
*/
offsetOfMarker(marker: Marker, markerOffset = 0) {
offsetOfMarker(marker: Markuperable, markerOffset = 0) {
assert(`Cannot get offsetOfMarker for marker that is not child of this`, marker.section === this)

// FIXME it is possible, when we get a cursor position before having finished reparsing,
Expand Down Expand Up @@ -112,7 +114,7 @@ export default abstract class Markerable extends tagNameable(Section) {
return [beforeSection, afterSection]
}

abstract splitAtMarker(marker: Marker, offset: number): [Section, Section]
abstract splitAtMarker(marker: Markuperable, offset: number): [Section, Section]

/**
* Split this section's marker (if any) at the given offset, so that
Expand All @@ -128,7 +130,7 @@ export default abstract class Markerable extends tagNameable(Section) {
let markerOffset
let len = 0
let currentMarker = this.markers.head
let edit: { added: Marker[]; removed: Marker[] } = { added: [], removed: [] }
let edit: { added: Markuperable[]; removed: Markuperable[] } = { added: [], removed: [] }

if (!currentMarker) {
let blankMarker = this.builder.createMarker()
Expand Down Expand Up @@ -181,7 +183,7 @@ export default abstract class Markerable extends tagNameable(Section) {

markerPositionAtOffset(offset: number) {
let currentOffset = 0
let currentMarker: Marker | null = null
let currentMarker: Markuperable | null = null
let remaining = offset
this.markers.detect(marker => {
currentOffset = Math.min(remaining, marker.length)
Expand Down Expand Up @@ -211,7 +213,7 @@ export default abstract class Markerable extends tagNameable(Section) {
markersFor(headOffset: number, tailOffset: number) {
const range = Range.create(this, headOffset, this, tailOffset)

let markers: Marker[] = []
let markers: Markuperable[] = []
this._markersInRange(range, (marker, { markerHead, markerTail, isContained }) => {
const cloned = marker.clone()
if (!isContained) {
Expand All @@ -237,7 +239,7 @@ export default abstract class Markerable extends tagNameable(Section) {
// for each marker that is wholly or partially contained in the range.
_markersInRange(
range: Range,
callback: (marker: Marker, info: { markerHead: number; markerTail: number; isContained: boolean }) => void
callback: (marker: Markuperable, info: { markerHead: number; markerTail: number; isContained: boolean }) => void
) {
const { head, tail } = range
assert(
Expand Down Expand Up @@ -274,7 +276,7 @@ export default abstract class Markerable extends tagNameable(Section) {
// mutates this by appending the other section's (cloned) markers to it
join(otherSection: Markerable) {
let beforeMarker = this.markers.tail
let afterMarker: Marker | null = null
let afterMarker: Markuperable | null = null

otherSection.markers.forEach(m => {
if (!m.isBlank) {
Expand Down
Loading