import { store } from ".." ; import { inorderTreeTraversal } from "../page/tree/tree" ; import { StructureNode , TagType } from "../page/structureTree/structureModel" ; import { PageContent } from "../page/pageModel" ; import { createElement , STRUCTURE_NODE_INDEX , StackItem , getLastStartItem , CONTENT_NODE_END_INDEX , CONTENT_NODE_END_OFFSET , CONTENT_NODE_START_INDEX , CONTENT_NODE_START_OFFSET , } from "./render" ; import { getContentBetweenOffsets } from "../page/contentTree/tree" ; import getEditorSelection from "./selection" ; declare global { namespace JSX { interface IntrinsicElements { "markdown-editor" : { } ; } } } interface MarkdownLastStartNode { stackIndex : number ; node : StructureNode ; index : number ; contentOffset : number ; } export class MarkdownEditor extends HTMLElement { public constructor ( ) { super ( ) ; const parent = document . createElement ( "div" ) ; parent . setAttribute ( "contenteditable" , "true" ) ; parent . setAttribute ( "style" , `border-right: solid rgb(127, 127, 127) 1px;
       padding-left: 4px;
       white-space: pre-wrap;
       height: 100%;
    ` ) ; const state = store . getState ( ) ; const page = state . pages [ state . selectedPage ] ; for ( const item of this . render ( page ) ) { parent . appendChild ( item ) ; } const shadow = this . attachShadow ( { mode : "open" } ) ; shadow . appendChild ( parent ) ; } public connectedCallback ( ) { this . addEventListener ( "beforeinput" , this . onBeforeInput as ( e : Event ) => void ) ; } private onBeforeInput ( e ) { switch ( e . inputType ) { case "insertText" : this . insertText ( e ) ; break ; default : { if ( e . cancelable ) { e . preventDefault ( ) ; } break ; } } } private insertText ( e ) { console . log ( e ) ; if ( this . shadowRoot ) { const selection = getEditorSelection ( this . shadowRoot ) ; console . log ( selection ) ; } } private render ( page ) { let stack = [ ] ; let contentOffset = 0 ; for ( const { index , node } of inorderTreeTraversal ( page . structure ) ) { switch ( node . tagType ) { case TagType . StartTag : { stack . push ( { contentOffset , index , node } ) ; contentOffset += node . length ; break ; } case TagType . EndTag : { stack = this . updateStack ( page , stack ) ; break ; } case TagType . StartEndTag : { this . addStartEndTag ( stack , node , index ) ; break ; } default : { break ; } } } return stack as Element [ ] ; } /**
     * Adds an element for a `StartEnd` tag.
     */ private addStartEndTag ( stack , node , index ) { switch ( node . tag ) { case "br" : { stack . push ( createElement ( "br" , undefined , { [ STRUCTURE_NODE_INDEX ] : index } ) ) ; break ; } default : break ; } } /**
     * Returns the updated stack. The last `StackItem` which has a
     * `tagType === TagType.StartTag` is compiled, with all the elements on the
     * stack after the last start tag being children.
     */ private updateStack ( page , stack ) { const lastStartStackItem = getLastStartItem ( stack ) ; if ( lastStartStackItem ) { return this . updateItem ( page , stack , lastStartStackItem ) ; } else { throw new Error ( "There's a mismatch between the number of start and end tags" ) ; } } param stack param lastStartNode private updateItem ( page , stack , { contentOffset , index , node , stackIndex } ) { const newStack = stack . slice ( 0 , stackIndex ) ; let children ; let contentBoundaries = { } ; if ( stackIndex === stack . length - 1 ) { const startOffset = contentOffset ; const endOffset = contentOffset + node . length ; const content = getContentBetweenOffsets ( page , startOffset , endOffset ) ; children = content . content ; contentBoundaries = { [ CONTENT_NODE_END_INDEX ] : content . end . nodeIndex , [ CONTENT_NODE_END_OFFSET ] : content . end . nodeLocalOffset , [ CONTENT_NODE_START_INDEX ] : content . start . nodeIndex , [ CONTENT_NODE_START_OFFSET ] : content . start . nodeLocalOffset , } ; } else { children = stack . slice ( stackIndex ) as Element [ ] ; } const element = createElement ( "p" , { margin : "0" , padding : "0" } , { ... contentBoundaries , ... node . attributes , [ STRUCTURE_NODE_INDEX ] : index , } , children ) ; newStack . push ( element ) ; return newStack ; } } customElements . define ( "markdown-editor" , MarkdownEditor ) ;