88 RawContext ,
99 validateMetaLines ,
1010 validSnippetRegex ,
11+ MetaLine ,
1112} from "./common" ;
1213import { Node as ProseMirrorNode , NodeSpec } from "prosemirror-model" ;
1314
@@ -86,6 +87,9 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = (
8687 }
8788
8889 let rawMetaLines : RawContext [ ] = [ ] ;
90+ let inSnippet = false ;
91+ let snippetBegin : MetaLine | null = null ;
92+ let currentLangLines : RawContext [ ] = [ ] ;
8993
9094 //Next up, we want to find and test all the <!-- --> blocks we find.
9195 for ( let i = startLine ; i < endLine ; i ++ ) {
@@ -97,59 +101,99 @@ const parseSnippetBlockForMarkdownIt: MarkdownIt.ParserBlock.RuleBlock = (
97101 if ( ! validSnippetRegex . test ( line ) ) {
98102 continue ;
99103 }
100- rawMetaLines = [ ...rawMetaLines , { line, index : i } ] ;
101- }
102104
103- const metaLines = rawMetaLines . map ( mapMetaLine ) . filter ( ( m ) => m != null ) ;
104- const validationResult = validateMetaLines ( metaLines ) ;
105+ const metaLine = mapMetaLine ( { line, index : i } ) ;
106+ if ( ! metaLine ) {
107+ continue ;
108+ }
109+
110+ if ( metaLine . type === "begin" ) {
111+ if ( inSnippet ) {
112+ // Found a new begin while still in a snippet - invalid state
113+ state . line = i + 1 ;
114+ return false ;
115+ }
116+ inSnippet = true ;
117+ snippetBegin = metaLine ;
118+ rawMetaLines = [ { line, index : i } ] ;
119+ currentLangLines = [ ] ;
120+ } else if ( metaLine . type === "lang" ) {
121+ if ( ! inSnippet ) {
122+ state . line = i + 1 ;
123+ return false ;
124+ }
125+ currentLangLines . push ( { line, index : i } ) ;
126+ rawMetaLines . push ( { line, index : i } ) ;
127+ } else if ( metaLine . type === "end" && inSnippet ) {
128+ rawMetaLines . push ( { line, index : i } ) ;
129+
130+ const metaLines = rawMetaLines
131+ . map ( mapMetaLine )
132+ . filter ( ( m ) => m != null ) ;
133+ const validationResult = validateMetaLines ( metaLines ) ;
134+
135+ //We now know this is a valid snippet. Last call before we start processing
136+ if ( silent || ! validationResult . valid ) {
137+ state . line = i + 1 ;
138+ return validationResult . valid ;
139+ }
140+
141+ // Create the snippet tokens
142+ const openToken = state . push ( "stack_snippet_open" , "code" , 1 ) ;
143+ // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch)
144+ openToken . attrSet ( "id" , Utils . generateRandomId ( ) ) ;
145+ if ( ! snippetBegin || snippetBegin . type !== "begin" ) {
146+ state . line = i + 1 ;
147+ return false ;
148+ }
149+ openToken . attrSet ( "hide" , snippetBegin . hide ) ;
150+ openToken . attrSet ( "console" , snippetBegin . console ) ;
151+ openToken . attrSet ( "babel" , snippetBegin . babel ) ;
152+ openToken . attrSet (
153+ "babelPresetReact" ,
154+ snippetBegin . babelPresetReact
155+ ) ;
156+ openToken . attrSet ( "babelPresetTS" , snippetBegin . babelPresetTS ) ;
157+
158+ // Sort and process language blocks
159+ const langSort = currentLangLines . sort ( ( a , b ) => a . index - b . index ) ;
160+
161+ for ( let j = 0 ; j < langSort . length ; j ++ ) {
162+ const langMeta = mapMetaLine ( langSort [ j ] ) ;
163+ if ( ! langMeta || langMeta . type !== "lang" ) continue ;
164+
165+ //Use the beginning of the next block to establish the end of this one, or the end of the snippet
166+ const langEnd =
167+ j + 1 == langSort . length ? i : langSort [ j + 1 ] . index ;
168+ //Start after the header of the lang block (+1) and the following empty line (+1)
169+ //End on the beginning of the next metaLine, less the preceding empty line (-1)
170+ //All lang blocks are forcefully indented 4 spaces, so cleave those away.
171+ const langBlock = state . getLines (
172+ langSort [ j ] . index + 2 ,
173+ langEnd - 1 ,
174+ 4 ,
175+ false
176+ ) ;
177+ const langToken = state . push ( "stack_snippet_lang" , "code" , 1 ) ;
178+ langToken . content = langBlock ;
179+ langToken . map = [ langSort [ j ] . index , langEnd ] ;
180+ langToken . attrSet ( "language" , langMeta . language ) ;
181+ }
105182
106- //We now know this is a valid snippet. Last call before we start processing
107- if ( silent || ! validationResult . valid ) {
108- return validationResult . valid ;
183+ state . push ( "stack_snippet_close" , "code" , - 1 ) ;
184+ state . line = i + 1 ;
185+
186+ return true ;
187+ }
109188 }
110189
111- //A valid block must start with a begin and end, so cleave the opening and closing from the lines
112- const begin = metaLines . shift ( ) ;
113- if ( begin . type !== "begin" ) return false ;
114- const end = metaLines . pop ( ) ;
115- if ( end . type !== "end" ) return false ;
116-
117- //The rest must be langs, sort them by index
118- const langSort = metaLines
119- . filter ( ( m ) => m . type == "lang" ) //Not strictly necessary, but useful for typing
120- . sort ( ( a , b ) => a . index - b . index ) ;
121- if ( ! langSort . every ( ( l ) => l . type === "lang" ) ) return false ;
122-
123- const openToken = state . push ( "stack_snippet_open" , "code" , 1 ) ;
124- // This value is not serialized, and so is different on every new session of Rich Text (i.e. every mode switch)
125- openToken . attrSet ( "id" , Utils . generateRandomId ( ) ) ;
126- openToken . attrSet ( "hide" , begin . hide ) ;
127- openToken . attrSet ( "console" , begin . console ) ;
128- openToken . attrSet ( "babel" , begin . babel ) ;
129- openToken . attrSet ( "babelPresetReact" , begin . babelPresetReact ) ;
130- openToken . attrSet ( "babelPresetTS" , begin . babelPresetTS ) ;
131-
132- for ( let i = 0 ; i < langSort . length ; i ++ ) {
133- //Use the beginning of the next block to establish the end of this one, or the end of the snippet
134- const langEnd =
135- i + 1 == langSort . length ? end . index : langSort [ i + 1 ] . index ;
136- //Start after the header of the lang block (+1) and the following empty line (+1)
137- //End on the beginning of the next metaLine, less the preceding empty line (-1)
138- //All lang blocks are forcefully indented 4 spaces, so cleave those away.
139- const langBlock = state . getLines (
140- langSort [ i ] . index + 2 ,
141- langEnd - 1 ,
142- 4 ,
143- false
144- ) ;
145- const langToken = state . push ( "stack_snippet_lang" , "code" , 1 ) ;
146- langToken . content = langBlock ;
147- langToken . map = [ langSort [ i ] . index , langEnd ] ;
148- langToken . attrSet ( "language" , langSort [ i ] . language ) ;
190+ // If we're still in a snippet at the end, it means we never found an end marker
191+ if ( inSnippet ) {
192+ state . line = endLine ;
193+ return false ;
149194 }
150- state . push ( "stack_snippet_close" , "code" , - 1 ) ;
151- state . line = end . index + 1 ;
152- return true ;
195+
196+ return false ;
153197} ;
154198
155199export const stackSnippetRichTextNodeSpec : { [ name : string ] : NodeSpec } = {
0 commit comments