import { VChild , VNode } from '../rendering/vdom' ; import AttributeMap from '../delta/AttributeMap' ; import Editor , { Shortcuts } from '../Editor' ; const EMPTY_ARR = [ ] ; const lineTypes = { } ; const formatTypes = { } ; const embedTypes = { } ; const shouldCombine = ( prev , next ) => true ; export class Typeset { lines : Types < LineType > ; formats : Types < FormatType > ; embeds : Types < EmbedType > ; static line = line ; static format = format ; static embed = embed ; constructor ( types ) { const lines = types . lines ?. map ( entry => typeof entry === 'string' ? lineTypes [ entry ] : entry ) . filter ( Boolean ) ; const formats = types . formats ?. map ( entry => typeof entry === 'string' ? formatTypes [ entry ] : entry ) . filter ( Boolean ) ; const embeds = types . embeds ?. map ( entry => typeof entry === 'string' ? embedTypes [ entry ] : entry ) . filter ( Boolean ) ; this . lines = new Types < LineType > ( lines || EMPTY_ARR ) ; this . formats = new Types ( formats || EMPTY_ARR ) ; this . embeds = new Types ( embeds || EMPTY_ARR ) ; } } export function line ( type ) { if ( type . renderMultiple && ! type . shouldCombine ) type . shouldCombine = shouldCombine ; return lineTypes [ type . name ] = type ; } export function format ( type ) { return formatTypes [ type . name ] = type ; } export function embed ( type ) { return embedTypes [ type . name ] = type ; } export type FromDom = ( node : Node ) => any ; export type LineData = [ attributes : AttributeMap , children : VChild [ ] , id : string ] ; export type Renderer = ( attributes : AttributeMap , children : VChild [ ] , editor : Editor , forHTML ? : boolean ) => VNode ; export type MultiLineRenderer = ( lines : LineData [ ] , editor : Editor , forHTML ? : boolean ) => VNode ; export type ShouldCombine = ( prev : AttributeMap , next : AttributeMap ) => boolean ; export interface Commands { [ name ] : Function ; } export interface BasicType { name : string ; selector : string ; styleSelector ? : string ; fromDom ? : FromDom | false ; commands ? : ( editor : Editor ) => Commands | Function ; shortcuts ? : Shortcuts | string ; render ? : Renderer ; } export interface FormatType extends BasicType { greedy ? : boolean ; } export interface EmbedType extends BasicType { noFill ? : boolean ; } export interface LineType extends BasicType { indentable ? : boolean ; defaultFollows ? : boolean ; frozen ? : boolean ; contained ? : boolean ; nextLineAttributes ? : ( attributes : AttributeMap ) => AttributeMap ; render ? : Renderer ; renderMultiple ? : MultiLineRenderer ; shouldCombine ? : ShouldCombine ; } export interface TypesetTypes { lines ? : Array < string | LineType > ; formats ? : Array < string | FormatType > ; embeds ? : Array < string | EmbedType > ; } export interface TypeMap < T extends BasicType = BasicType > { [ name ] : T ; } /**
 * A type store to hold types and make it easy to manage them.
 */ export class Types < T extends BasicType = BasicType > { list : T [ ] ; selector ! : string ; types ! : TypeMap < T > ; priorities ! : { [ name ] : number ; } ; constructor ( types ) { this . list = types ; this . init ( ) ; } get default ( ) { return this . list [ 0 ] ; } init ( ) { this . selector = this . list . map ( type => type . selector || '' ) . filter ( Boolean ) . join ( ', ' ) ; this . types = this . list . reduce ( ( types , type ) => { types [ type . name ] = type ; return types ; } , { } ) ; this . priorities = this . list . reduce ( ( priorities , type , i ) => { priorities [ type . name ] = i ; return priorities ; } , { } ) ; } add ( type ) { this . list . push ( type ) ; this . init ( ) ; } remove ( type ) { const name = typeof type === 'string' ? type : type . name ; this . list = this . list . filter ( type => type . name !== name ) ; this . init ( ) ; } get ( name ) { return this . types [ name ] ; } priority ( name ) { const priority = this . priorities [ name ] ; return priority !== undefined ? priority : - 1 ; } matches ( node ) { if ( ! node ) return false ; if ( ! node . nodeType ) throw new Error ( 'Cannot match against ' + node ) ; if ( node . nodeType === Node . ELEMENT_NODE ) { return this . selector ? ( node as Element ) . matches ( this . selector ) : false ; } } findByNode ( node , fallbackToDefault ) ; findByNode ( node , fallbackToDefault ? ) ; findByNode ( node , fallbackToDefault = false ) { if ( node . nodeType !== Node . ELEMENT_NODE ) return ; let i = this . list . length ; while ( i -- ) { let type = this . list [ i ] ; if ( ( node as Element ) . matches ( type . selector ) ) return type ; } if ( fallbackToDefault ) return this . default ; } findByAttributes ( attributes , fallbackToDefault ) ; findByAttributes ( attributes , fallbackToDefault ? ) ; findByAttributes ( attributes , fallbackToDefault = false ) { const keys = attributes && Object . keys ( attributes ) ; let type ; keys && keys . every ( name => ! ( type = this . get ( name ) ) ) ; return type || ( fallbackToDefault ? this . default : undefined ) ; } }