diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-10 18:09:49 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-10 18:09:49 +0300 |
commit | 70732753863e569f95ed954ca3c41421292f912b (patch) | |
tree | f642d44c83ab951fd4581e3c46491784376b9c18 /app/assets/javascripts/static_site_editor | |
parent | bf593ae68b7135bf633484aa3442b7592126b1d2 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/static_site_editor')
-rw-r--r-- | app/assets/javascripts/static_site_editor/services/templater.js | 107 |
1 files changed, 67 insertions, 40 deletions
diff --git a/app/assets/javascripts/static_site_editor/services/templater.js b/app/assets/javascripts/static_site_editor/services/templater.js index 081db60b601..a1c1bb6b8d6 100644 --- a/app/assets/javascripts/static_site_editor/services/templater.js +++ b/app/assets/javascripts/static_site_editor/services/templater.js @@ -1,62 +1,89 @@ +/** + * 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 prefix = `${ticks} ${marker}\n`; // Space intentional due to https://github.com/nhn/tui.editor/blob/6bcec75c69028570d93d973aa7533090257eaae0/libs/to-mark/src/renderer.gfm.js#L26 -const postfix = `\n${ticks}`; -const flagPrefix = `${marker}-${Date.now()}`; -const template = `.| |\\t|\\n(?!(\\n|${flagPrefix}))`; -const templatedRegex = new RegExp(`(^${prefix}(${template})+?${postfix}$)`, 'gm'); - -const nonErbMarkupRegex = new RegExp(`^((<(?!%).+>){1}(${template})+(</.+>){1})$`, 'gm'); -const embeddedRubyBlockRegex = new RegExp(`(^<%(${template})+%>$)`, 'gm'); -const embeddedRubyInlineRegex = new RegExp(`(^.*[<|<]%(${template})+$)`, 'gm'); - -// Order is intentional (general to specific) where HTML markup is flagged first, then ERB blocks, then inline ERB -// Order in combo with the `flag()` algorithm is used to mitigate potential duplicate pattern matches (ERB nested in HTML for example) -const orderedPatterns = [nonErbMarkupRegex, embeddedRubyBlockRegex, embeddedRubyInlineRegex]; - -const unwrap = source => { - let text = source; - const matches = text.match(templatedRegex); +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()}`; - if (matches) { - matches.forEach(match => { - const initial = match.replace(`${prefix}`, '').replace(`${postfix}`, ''); - text = text.replace(match, initial); - }); - } +const reHelpers = { + template: `.| |\\t|\\n(?!(\\n|${markPrefix}))`, + openTag: '<[a-zA-Z]+.*?>', + 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'); - return text; +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 flag = (source, patterns) => { +const mark = (source, groups) => { let text = source; let id = 0; const hash = {}; - patterns.forEach(pattern => { - const matches = text.match(pattern); - if (matches) { - matches.forEach(match => { - const key = `${flagPrefix}${id}`; - text = text.replace(match, key); - hash[key] = match; - id += 1; - }); - } + Object.entries(groups).forEach(([groupKey, group]) => { + group.forEach(pattern => { + const matches = text.match(pattern); + if (matches) { + matches.forEach(match => { + const key = `${markPrefix}-${groupKey}-${id}`; + text = text.replace(match, key); + hash[key] = match; + id += 1; + }); + } + }); }); return { text, hash }; }; -const wrap = source => { - const { text, hash } = flag(unwrap(source), orderedPatterns); +const unmark = (text, hash) => { + let source = text; - let wrappedSource = text; Object.entries(hash).forEach(([key, value]) => { - wrappedSource = wrappedSource.replace(key, `${prefix}${value}${postfix}`); + const newVal = key.includes('ignore') ? value : `${wrapPrefix}${value}${wrapPostfix}`; + source = source.replace(key, newVal); }); - return wrappedSource; + return source; +}; + +const unwrap = source => { + let text = source; + const matches = text.match(reTemplated); + + if (matches) { + matches.forEach(match => { + const initial = match.replace(`${wrapPrefix}`, '').replace(`${wrapPostfix}`, ''); + text = text.replace(match, initial); + }); + } + + return text; +}; + +const wrap = source => { + const { text, hash } = mark(unwrap(source), patternGroups); + return unmark(text, hash); }; export default { wrap, unwrap }; |