2020-10-24 23:57:45 +05:30
/ * *
* The purpose of this file is to modify Markdown source such that templated code ( embedded ruby currently ) can be temporarily wrapped and unwrapped in codeblocks :
* 1. ` wrap() ` : temporarily wrap in codeblocks ( useful for a WYSIWYG editing experience )
* 2. ` unwrap() ` : undo the temporarily wrapped codeblocks ( useful for Markdown editing experience and saving edits )
*
* Without this ` templater ` , the templated code is otherwise interpreted as Markdown content resulting in loss of spacing , indentation , escape characters , etc .
*
* /
const ticks = '```' ;
const marker = 'sse' ;
const wrapPrefix = ` ${ ticks } ${ marker } \n ` ; // Space intentional due to https://github.com/nhn/tui.editor/blob/6bcec75c69028570d93d973aa7533090257eaae0/libs/to-mark/src/renderer.gfm.js#L26
const wrapPostfix = ` \n ${ ticks } ` ;
const markPrefix = ` ${ marker } - ${ Date . now ( ) } ` ;
const reHelpers = {
template : ` .| | \\ t| \\ n(?!( \\ n| ${ markPrefix } )) ` ,
2021-01-03 14:25:43 +05:30
openTag : '<(?!figure|iframe)[a-zA-Z]+.*?>' ,
2020-10-24 23:57:45 +05:30
closeTag : '</.+>' ,
} ;
const reTemplated = new RegExp ( ` (^ ${ wrapPrefix } ( ${ reHelpers . template } )+? ${ wrapPostfix } $ ) ` , 'gm' ) ;
const rePreexistingCodeBlocks = new RegExp ( ` (^ ${ ticks } .* \\ n(.| \\ s)+? ${ ticks } $ ) ` , 'gm' ) ;
const reHtmlMarkup = new RegExp (
` ^(( ${ reHelpers . openTag } ){1}( ${ reHelpers . template } )*( ${ reHelpers . closeTag } ){1}) $ ` ,
'gm' ,
) ;
const reEmbeddedRubyBlock = new RegExp ( ` (^<%( ${ reHelpers . template } )+%> $ ) ` , 'gm' ) ;
const reEmbeddedRubyInline = new RegExp ( ` (^.*[<|<]%( ${ reHelpers . template } )+ $ ) ` , 'gm' ) ;
const patternGroups = {
ignore : [ rePreexistingCodeBlocks ] ,
// Order is intentional (general to specific) where HTML markup is marked first, then ERB blocks, then inline ERB
// Order in combo with the `mark()` algorithm is used to mitigate potential duplicate pattern matches (ERB nested in HTML for example)
allow : [ reHtmlMarkup , reEmbeddedRubyBlock , reEmbeddedRubyInline ] ,
} ;
const mark = ( source , groups ) => {
let text = source ;
let id = 0 ;
const hash = { } ;
Object . entries ( groups ) . forEach ( ( [ groupKey , group ] ) => {
2021-03-08 18:12:59 +05:30
group . forEach ( ( pattern ) => {
2020-10-24 23:57:45 +05:30
const matches = text . match ( pattern ) ;
if ( matches ) {
2021-03-08 18:12:59 +05:30
matches . forEach ( ( match ) => {
2020-10-24 23:57:45 +05:30
const key = ` ${ markPrefix } - ${ groupKey } - ${ id } ` ;
text = text . replace ( match , key ) ;
hash [ key ] = match ;
id += 1 ;
} ) ;
}
} ) ;
} ) ;
return { text , hash } ;
} ;
const unmark = ( text , hash ) => {
let source = text ;
Object . entries ( hash ) . forEach ( ( [ key , value ] ) => {
const newVal = key . includes ( 'ignore' ) ? value : ` ${ wrapPrefix } ${ value } ${ wrapPostfix } ` ;
source = source . replace ( key , newVal ) ;
} ) ;
return source ;
} ;
2021-03-08 18:12:59 +05:30
const unwrap = ( source ) => {
2020-10-24 23:57:45 +05:30
let text = source ;
const matches = text . match ( reTemplated ) ;
if ( matches ) {
2021-03-08 18:12:59 +05:30
matches . forEach ( ( match ) => {
2020-10-24 23:57:45 +05:30
const initial = match . replace ( ` ${ wrapPrefix } ` , '' ) . replace ( ` ${ wrapPostfix } ` , '' ) ;
text = text . replace ( match , initial ) ;
} ) ;
}
return text ;
} ;
2021-03-08 18:12:59 +05:30
const wrap = ( source ) => {
2020-10-24 23:57:45 +05:30
const { text , hash } = mark ( unwrap ( source ) , patternGroups ) ;
return unmark ( text , hash ) ;
} ;
export default { wrap , unwrap } ;