Skip to content

Commit 44fc8ea

Browse files
authored
Typescriptify Phase II 🐝 (#737)
* Typescriptify attributable + MarkupSection 🐝 * Atom + AtomNode 🐥 * CardNode + Markup 🦏 * Image + ListItem + ListSection 🦀🦀 * LifecycleCallbacks + PostNodeBuilder + fixy fix 🐷 * POST 🦋 * RenderNode + Fixy 🐠
1 parent adabc3e commit 44fc8ea

33 files changed

+656
-570
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ module.exports = {
151151
"no-continue": "off",
152152
"no-div-regex": "error",
153153
"no-duplicate-imports": "off",
154+
"no-dupe-class-members": "off",
154155
"no-else-return": "off",
155156
"no-empty-function": "off",
156157
"no-eq-null": "error",

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@
5252
"@rollup/plugin-commonjs": "^11.1.0",
5353
"@rollup/plugin-node-resolve": "^7.1.3",
5454
"@rollup/plugin-typescript": "^4.1.2",
55-
"@typescript-eslint/eslint-plugin": "^3.0.2",
56-
"@typescript-eslint/parser": "^3.0.2",
55+
"@typescript-eslint/eslint-plugin": "^3.6.1",
56+
"@typescript-eslint/parser": "^3.6.1",
5757
"conventional-changelog-cli": "^2.0.34",
58-
"eslint": "^7.1.0",
58+
"eslint": "^7.4.0",
5959
"eslint-config-prettier": "^6.11.0",
6060
"jquery": "^3.5.1",
6161
"jsdoc": "^3.6.4",
@@ -78,6 +78,6 @@
7878
},
7979
"volta": {
8080
"node": "12.14.1",
81-
"yarn": "1.22.1"
81+
"yarn": "1.22.4"
8282
}
8383
}

src/js/editor/editor.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import Environment from '../utils/environment'
2020
import PostNodeBuilder from '../models/post-node-builder'
2121
import { DEFAULT_TEXT_INPUT_HANDLERS } from './text-input-handlers'
2222
import { DEFAULT_KEY_COMMANDS, buildKeyCommand, findKeyCommands, validateKeyCommand } from './key-commands'
23-
import { CARD_MODES } from '../models/card'
23+
import { CardMode } from '../models/card'
2424
import assert from '../utils/assert'
2525
import MutationHandler from 'mobiledoc-kit/editor/mutation-handler'
2626
import EditHistory from 'mobiledoc-kit/editor/edit-history'
@@ -690,7 +690,7 @@ class Editor {
690690
* @public
691691
*/
692692
editCard(cardSection) {
693-
this._setCardMode(cardSection, CARD_MODES.EDIT)
693+
this._setCardMode(cardSection, CardMode.EDIT)
694694
}
695695

696696
/**
@@ -702,7 +702,7 @@ class Editor {
702702
* @public
703703
*/
704704
displayCard(cardSection) {
705-
this._setCardMode(cardSection, CARD_MODES.DISPLAY)
705+
this._setCardMode(cardSection, CardMode.DISPLAY)
706706
}
707707

708708
/**

src/js/models/_attributable.js

Lines changed: 0 additions & 28 deletions
This file was deleted.

src/js/models/_attributable.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { entries } from '../utils/object-utils'
2+
import { contains } from '../utils/array-utils'
3+
4+
export const VALID_ATTRIBUTES = ['data-md-text-align']
5+
6+
export interface Attributable {
7+
attributes: { [key: string]: string }
8+
hasAttribute: (key: string) => boolean
9+
setAttribute: (key: string, value: string) => void
10+
removeAttribute: (key: string) => void
11+
getAttribute: (key: string) => string
12+
eachAttribute: (cb: (key: string, value: string) => void) => void
13+
}
14+
15+
type AbstractConstructor<T> = Function & { prototype: T }
16+
type Constructor<T> = new (...args: any[]) => T
17+
18+
/*
19+
* A "mixin" to add section attribute support
20+
* to markup and list sections.
21+
*/
22+
export function attributable<T extends unknown>(Base: AbstractConstructor<T>): Constructor<T & Attributable> {
23+
return class extends (Base as any) {
24+
attributes: { [key: string]: string } = {}
25+
26+
hasAttribute(key: string) {
27+
return key in this.attributes
28+
}
29+
30+
setAttribute(key: string, value: string) {
31+
if (!contains(VALID_ATTRIBUTES, key)) {
32+
throw new Error(`Invalid attribute "${key}" was passed. Constrain attributes to the spec-compliant whitelist.`)
33+
}
34+
this.attributes[key] = value
35+
}
36+
37+
removeAttribute(key: string) {
38+
delete this.attributes[key]
39+
}
40+
41+
getAttribute(key: string) {
42+
return this.attributes[key]
43+
}
44+
45+
eachAttribute(cb: (key: string | number, value: string) => void) {
46+
entries(this.attributes).forEach(([k, v]) => cb(k, v))
47+
}
48+
} as Constructor<T & Attributable>
49+
}

src/js/models/_markerable.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ import assert from '../utils/assert'
66
import Position from '../utils/cursor/position'
77
import Section from './_section'
88
import Marker from './marker'
9+
import { tagNameable } from './_tag-nameable'
10+
import { Type } from './types'
911

10-
export default abstract class Markerable extends Section {
11-
tagName: string
12+
type MarkerableType = Type.LIST_ITEM | Type.MARKUP_SECTION
13+
14+
export default abstract class Markerable extends tagNameable(Section) {
15+
type: MarkerableType
1216
markers: LinkedList<Marker>
13-
builder: any
1417

15-
constructor(type: string, tagName: string, markers = []) {
18+
constructor(type: MarkerableType, tagName: string, markers: Marker[] = []) {
1619
super(type)
20+
this.type = type
1721
this.isMarkerable = true
1822
this.tagName = tagName
1923
this.markers = new LinkedList({
@@ -27,13 +31,13 @@ export default abstract class Markerable extends Section {
2731
markers.forEach(m => this.markers.append(m))
2832
}
2933

30-
canJoin(other: Section) {
34+
canJoin(other: Markerable) {
3135
return other.isMarkerable && other.type === this.type && other.tagName === this.tagName
3236
}
3337

34-
clone() {
38+
clone(): this {
3539
const newMarkers = this.markers.map(m => m.clone())
36-
return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers)
40+
return this.builder.createMarkerableSection(this.type, this.tagName, newMarkers) as any as this
3741
}
3842

3943
get isBlank() {
@@ -87,7 +91,12 @@ export default abstract class Markerable extends Section {
8791
// all markers before the marker/offset split go in beforeSection, and all
8892
// after the marker/offset split go in afterSection
8993
// @return {Array} [beforeSection, afterSection], two new sections
90-
_redistributeMarkers(beforeSection: Markerable, afterSection: Markerable, marker: Marker, offset = 0) {
94+
_redistributeMarkers(
95+
beforeSection: Markerable,
96+
afterSection: Markerable,
97+
marker: Marker,
98+
offset = 0
99+
): [Section, Section] {
91100
let currentSection = beforeSection
92101
forEach(this.markers, m => {
93102
if (m === marker) {
@@ -280,3 +289,7 @@ export default abstract class Markerable extends Section {
280289
return { beforeMarker, afterMarker }
281290
}
282291
}
292+
293+
export function isMarkerable(section: Section): section is Markerable {
294+
return section.isMarkerable
295+
}

src/js/models/_section.ts

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,58 @@
1-
import { normalizeTagName } from '../utils/dom-utils'
21
import LinkedItem from '../utils/linked-item'
32
import assert from '../utils/assert'
43
import Position from '../utils/cursor/position'
5-
import LinkedList from '../utils/linked-list'
4+
import Range from '../utils/cursor/range'
65
import Marker from './marker'
76
import RenderNode from './render-node'
87
import Post from './post'
8+
import { isListSection } from './is-list-section'
9+
import PostNodeBuilder from './post-node-builder'
10+
import { Type } from './types'
911

10-
export default abstract class Section extends LinkedItem<Section> {
11-
type: string
12-
isSection: boolean
13-
isMarkerable: boolean
14-
isNested: boolean
15-
isLeafSection: boolean
12+
export default class Section extends LinkedItem {
13+
type: Type
14+
15+
isSection = true
16+
isMarkerable = false
17+
isNested = false
18+
isListItem = false
19+
isListSection = false
20+
isLeafSection = true
21+
isCardSection = false
1622

1723
post?: Post | null
18-
parent: Section | null = null
1924
renderNode: RenderNode | null = null
20-
_tagName: string | null = null
2125

22-
constructor(type: string) {
26+
parent: Section | null = null
27+
builder!: PostNodeBuilder
28+
29+
constructor(type: Type) {
2330
super()
2431
assert('Cannot create section without type', !!type)
2532
this.type = type
26-
this.isSection = true
27-
this.isMarkerable = false
28-
this.isNested = false
29-
this.isLeafSection = true
30-
}
31-
32-
set tagName(val: string) {
33-
let normalizedTagName = normalizeTagName(val)
34-
assert(`Cannot set section tagName to ${val}`, this.isValidTagName(normalizedTagName))
35-
this._tagName = normalizedTagName
3633
}
3734

38-
get tagName() {
39-
return this._tagName as string
35+
get isBlank() {
36+
return false
4037
}
4138

4239
get length() {
4340
return 0
4441
}
4542

46-
abstract get isBlank(): boolean
47-
abstract isValidTagName(_normalizedTagName: string): boolean
48-
abstract clone(): Section
49-
abstract canJoin(otherSection: Section): boolean
50-
abstract textUntil(position: Position): string
51-
5243
/**
5344
* @return {Position} The position at the start of this section
5445
* @public
5546
*/
56-
headPosition() {
47+
headPosition(): Position {
5748
return this.toPosition(0)
5849
}
5950

6051
/**
6152
* @return {Position} The position at the end of this section
6253
* @public
6354
*/
64-
tailPosition() {
55+
tailPosition(): Position {
6556
return this.toPosition(this.length)
6657
}
6758

@@ -70,7 +61,7 @@ export default abstract class Section extends LinkedItem<Section> {
7061
* @return {Position} The position in this section at the given offset
7162
* @public
7263
*/
73-
toPosition(offset: number) {
64+
toPosition(offset: number): Position {
7465
assert('Must pass number to `toPosition`', typeof offset === 'number')
7566
assert('Cannot call `toPosition` with offset > length', offset <= this.length)
7667

@@ -81,7 +72,7 @@ export default abstract class Section extends LinkedItem<Section> {
8172
* @return {Range} A range from this section's head to tail positions
8273
* @public
8374
*/
84-
toRange() {
75+
toRange(): Range {
8576
return this.headPosition().toRange(this.tailPosition())
8677
}
8778

@@ -136,11 +127,3 @@ export default abstract class Section extends LinkedItem<Section> {
136127
return null
137128
}
138129
}
139-
140-
interface ListSection {
141-
items: LinkedList<Section>
142-
}
143-
144-
function isListSection(item: any): item is ListSection {
145-
return 'items' in item && item.items
146-
}

src/js/models/_tag-nameable.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { normalizeTagName } from '../utils/dom-utils'
2+
import assert from '../utils/assert'
3+
import Section from './_section'
4+
5+
type Constructor<T = {}> = new (...args: any[]) => T
6+
7+
export interface TagNameable {
8+
tagName: string
9+
isValidTagName(normalizedTagName: string): boolean
10+
}
11+
12+
export function tagNameable(Base: Constructor<Section>) {
13+
abstract class TagNameable extends Base {
14+
_tagName: string | null = null
15+
16+
set tagName(val: string) {
17+
let normalizedTagName = normalizeTagName(val)
18+
assert(`Cannot set section tagName to ${val}`, this.isValidTagName(normalizedTagName))
19+
this._tagName = normalizedTagName
20+
}
21+
22+
get tagName() {
23+
return this._tagName as string
24+
}
25+
26+
abstract isValidTagName(normalizedTagName: string): boolean
27+
}
28+
29+
return TagNameable
30+
}

0 commit comments

Comments
 (0)