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
import { DEFAULT_IMAGE_UPLOAD_PATH } from '../constants';
import imageRepository from '../image_repository';
import formatter from '../services/formatter';
import templater from '../services/templater';
export default {
components: {
......@@ -44,7 +45,7 @@ export default {
data() {
return {
saveable: false,
parsedSource: parseSourceFile(this.content),
parsedSource: parseSourceFile(this.preProcess(true, this.content)),
editorMode: EDITOR_TYPES.wysiwyg,
isModified: false,
};
......@@ -59,22 +60,28 @@ export default {
},
},
methods: {
preProcess(isWrap, value) {
const formattedContent = formatter(value);
const templatedContent = templater(isWrap, formattedContent);
return templatedContent;
},
onInputChange(newVal) {
this.parsedSource.sync(newVal, this.isWysiwygMode);
this.isModified = this.parsedSource.isModified();
},
onModeChange(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 }) {
this.$options.imageRepository.add(file, imageUrl);
},
onSubmit() {
const formattedContent = formatter(this.parsedSource.content());
const preProcessedContent = this.preProcess(false, this.parsedSource.content());
this.$emit('submit', {
content: formattedContent,
content: preProcessedContent,
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';
import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierInstanceText from './renderers/render_identifier_instance_text';
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 renderSoftbreak from './renderers/render_softbreak';
......@@ -11,7 +10,7 @@ const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
const htmlBlockRenderers = [renderBlockHtml];
const listRenderers = [renderKramdownList];
const paragraphRenderers = [renderIdentifierParagraph];
const textRenderers = [renderKramdownText, renderEmbeddedRubyText, renderIdentifierInstanceText];
const textRenderers = [renderKramdownText, renderIdentifierInstanceText];
const softbreakRenderers = [renderSoftbreak];
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 {
returnUrl,
} 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', () => {
let wrapper;
const formattedContent = `${content} formatted`;
const formattedBody = `${body} format-pass`;
const savingChanges = true;
const newBody = `new ${body}`;
......@@ -53,9 +53,9 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
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().props('content')).toBe(body);
expect(findRichContentEditor().props('content')).toBe(formattedBody);
});
it('renders publish toolbar', () => {
......@@ -97,7 +97,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
});
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(() => {
expect(findPublishToolbar().props('saveable')).toBe(false);
......@@ -124,8 +124,8 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it.each`
initialMode | targetMode | resetValue
${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${formattedContent}
${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${`${body} formatted`}
${EDITOR_TYPES.wysiwyg} | ${EDITOR_TYPES.markdown} | ${`${content} format-pass format-pass`}
${EDITOR_TYPES.markdown} | ${EDITOR_TYPES.wysiwyg} | ${`${body} format-pass format-pass`}
`(
'sets editorMode from $initialMode to $targetMode',
({ initialMode, targetMode, resetValue }) => {
......@@ -144,7 +144,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
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', () => {
it('should format the 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