Commit cf42fd33 authored by derek-knox's avatar derek-knox

Initial erb parse via templater approach

Update the static site editor to pre-process
content such that templated code (ERB) gets
processed as code vs. content in the downstream
WYSIWYG and Markdown editor (Toast UI)
parent 8449665f
...@@ -8,6 +8,7 @@ import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/consta ...@@ -8,6 +8,7 @@ import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/consta
import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants'; import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants';
import imageRepository from '../image_repository'; import imageRepository from '../image_repository';
import formatter from '../services/formatter'; import formatter from '../services/formatter';
import templater from '../services/templater';
export default { export default {
components: { components: {
...@@ -44,7 +45,7 @@ export default { ...@@ -44,7 +45,7 @@ export default {
data() { data() {
return { return {
saveable: false, saveable: false,
parsedSource: parseSourceFile(this.content), parsedSource: parseSourceFile(this.preProcess(true, this.content)),
editorMode: EDITOR_TYPES.wysiwyg, editorMode: EDITOR_TYPES.wysiwyg,
isModified: false, isModified: false,
}; };
...@@ -59,22 +60,28 @@ export default { ...@@ -59,22 +60,28 @@ export default {
}, },
}, },
methods: { methods: {
preProcess(isWrap, value) {
const formattedContent = formatter(value);
const templatedContent = templater(isWrap, formattedContent);
return templatedContent;
},
onInputChange(newVal) { onInputChange(newVal) {
this.parsedSource.sync(newVal, this.isWysiwygMode); this.parsedSource.sync(newVal, this.isWysiwygMode);
this.isModified = this.parsedSource.isModified(); this.isModified = this.parsedSource.isModified();
}, },
onModeChange(mode) { onModeChange(mode) {
this.editorMode = mode; this.editorMode = mode;
const formattedContent = formatter(this.editableContent);
this.$refs.editor.resetInitialValue(formattedContent); const preProcessedContent = this.preProcess(this.isWysiwygMode, this.editableContent);
this.$refs.editor.resetInitialValue(preProcessedContent);
}, },
onUploadImage({ file, imageUrl }) { onUploadImage({ file, imageUrl }) {
this.$options.imageRepository.add(file, imageUrl); this.$options.imageRepository.add(file, imageUrl);
}, },
onSubmit() { onSubmit() {
const formattedContent = formatter(this.parsedSource.content()); const preProcessedContent = this.preProcess(false, this.parsedSource.content());
this.$emit('submit', { this.$emit('submit', {
content: formattedContent, content: preProcessedContent,
images: this.$options.imageRepository.getAll(), images: this.$options.imageRepository.getAll(),
}); });
}, },
......
// eslint-disable-next-line @gitlab/require-i18n-strings
const marker = ' sse';
const ticks = '```';
const prefix = `${ticks}${marker}\n`;
const postfix = `\n${ticks}`;
const code = '.| |\\t|\\n(?!\\n)';
const templatedRegex = new RegExp(`(^${prefix}(${code})+${postfix}$)`, 'gm');
const embeddedRubyRegex = new RegExp(`(^<%(${code})+%>$)`, 'gm');
const unwrap = source => {
let text = source;
const matches = text.match(templatedRegex);
if (matches) {
matches.forEach(match => {
const initial = match.replace(prefix, '').replace(postfix, '');
text = text.replace(match, initial);
});
}
return text;
};
const wrap = source => {
let text = unwrap(source);
const matches = text.match(embeddedRubyRegex);
if (matches) {
matches.forEach(match => {
text = text.replace(match, `${prefix}${match}${postfix}`);
});
}
return text;
};
const template = (isWrap, source) => (isWrap ? wrap(source) : unwrap(source));
export default template;
...@@ -3,7 +3,6 @@ import renderKramdownList from './renderers/render_kramdown_list'; ...@@ -3,7 +3,6 @@ import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text'; import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierInstanceText from './renderers/render_identifier_instance_text'; import renderIdentifierInstanceText from './renderers/render_identifier_instance_text';
import renderIdentifierParagraph from './renderers/render_identifier_paragraph'; import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline'; import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline';
import renderSoftbreak from './renderers/render_softbreak'; import renderSoftbreak from './renderers/render_softbreak';
...@@ -11,7 +10,7 @@ const htmlInlineRenderers = [renderFontAwesomeHtmlInline]; ...@@ -11,7 +10,7 @@ const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
const htmlBlockRenderers = [renderBlockHtml]; const htmlBlockRenderers = [renderBlockHtml];
const listRenderers = [renderKramdownList]; const listRenderers = [renderKramdownList];
const paragraphRenderers = [renderIdentifierParagraph]; const paragraphRenderers = [renderIdentifierParagraph];
const textRenderers = [renderKramdownText, renderEmbeddedRubyText, renderIdentifierInstanceText]; const textRenderers = [renderKramdownText, renderIdentifierInstanceText];
const softbreakRenderers = [renderSoftbreak]; const softbreakRenderers = [renderSoftbreak];
const executeRenderer = (renderers, node, context) => { const executeRenderer = (renderers, node, context) => {
......
---
title: Added pre-processing step to the Static Site Editor so code templates (ERB) are interpreted as code not content
merge_request: 38694
author:
type: added
...@@ -15,11 +15,11 @@ import { ...@@ -15,11 +15,11 @@ import {
returnUrl, returnUrl,
} from '../mock_data'; } from '../mock_data';
jest.mock('~/static_site_editor/services/formatter', () => jest.fn(str => `${str} formatted`)); jest.mock('~/static_site_editor/services/formatter', () => jest.fn(str => `${str} format-pass`));
describe('~/static_site_editor/components/edit_area.vue', () => { describe('~/static_site_editor/components/edit_area.vue', () => {
let wrapper; let wrapper;
const formattedContent = `${content} formatted`; const formattedBody = `${body} format-pass`;
const savingChanges = true; const savingChanges = true;
const newBody = `new ${body}`; const newBody = `new ${body}`;
...@@ -53,9 +53,9 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -53,9 +53,9 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
expect(findEditHeader().props('title')).toBe(title); expect(findEditHeader().props('title')).toBe(title);
}); });
it('renders rich content editor', () => { it('renders rich content editor with a format pass', () => {
expect(findRichContentEditor().exists()).toBe(true); expect(findRichContentEditor().exists()).toBe(true);
expect(findRichContentEditor().props('content')).toBe(body); expect(findRichContentEditor().props('content')).toBe(formattedBody);
}); });
it('renders publish toolbar', () => { it('renders publish toolbar', () => {
...@@ -97,7 +97,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -97,7 +97,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
}); });
it('sets publish toolbar as not saveable when content changes are rollback', () => { it('sets publish toolbar as not saveable when content changes are rollback', () => {
findRichContentEditor().vm.$emit('input', body); findRichContentEditor().vm.$emit('input', formattedBody);
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(findPublishToolbar().props('saveable')).toBe(false); expect(findPublishToolbar().props('saveable')).toBe(false);
...@@ -124,8 +124,8 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -124,8 +124,8 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it.each` it.each`
initialMode | targetMode | resetValue initialMode | targetMode | resetValue
${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${formattedContent} ${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${`${content} format-pass format-pass`}
${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${`${body} formatted`} ${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${`${body} format-pass format-pass`}
`( `(
'sets editorMode from $initialMode to $targetMode', 'sets editorMode from $initialMode to $targetMode',
({ initialMode, targetMode, resetValue }) => { ({ initialMode, targetMode, resetValue }) => {
...@@ -144,7 +144,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -144,7 +144,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
findRichContentEditor().vm.$emit('modeChange', EDITOR_TYPES.markdown); findRichContentEditor().vm.$emit('modeChange', EDITOR_TYPES.markdown);
expect(resetInitialValue).toHaveBeenCalledWith(formattedContent); expect(resetInitialValue).toHaveBeenCalledWith(`${content} format-pass format-pass`);
}); });
}); });
...@@ -152,7 +152,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -152,7 +152,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('should format the content', () => { it('should format the content', () => {
findPublishToolbar().vm.$emit('submit', content); findPublishToolbar().vm.$emit('submit', content);
expect(wrapper.emitted('submit')[0][0].content).toBe(formattedContent); expect(wrapper.emitted('submit')[0][0].content).toBe(`${content} format-pass format-pass`);
}); });
}); });
}); });
import templater from '~/static_site_editor/services/templater';
describe('templater', () => {
const source = `Some text
<% some erb code %>
Some more text
`;
const sourceTemplated = `Some text
\`\`\` sse
<% some erb code %>
\`\`\`
Some more text
`;
it.each`
isWrap | initial | target
${true} | ${source} | ${sourceTemplated}
${true} | ${sourceTemplated} | ${sourceTemplated}
${false} | ${sourceTemplated} | ${source}
${false} | ${source} | ${source}
`(
'wraps $initial in a templated sse codeblock when $isWrap and unwraps otherwise',
({ isWrap, initial, target }) => {
expect(templater(isWrap, initial)).toMatch(target);
},
);
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment