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

Use gray-matter vs. prev approach at MR noise tradeoff

We will iterate on reducing this noise. The eof
noise has been solved for.
parent 8f2f7442
...@@ -68,7 +68,7 @@ export default { ...@@ -68,7 +68,7 @@ export default {
return templatedContent; return templatedContent;
}, },
onInputChange(newVal) { onInputChange(newVal) {
this.parsedSource.sync(newVal, this.isWysiwygMode); this.parsedSource.syncContent(newVal, this.isWysiwygMode);
this.isModified = this.parsedSource.isModified(); this.isModified = this.parsedSource.isModified();
}, },
onModeChange(mode) { onModeChange(mode) {
......
import getFrontMatterLanguageDefinition from './parse_source_file_language_support'; import grayMatter from 'gray-matter';
const parseSourceFile = (raw, options = { frontMatterLanguage: 'yaml' }) => { const parseSourceFile = raw => {
const { open, close } = getFrontMatterLanguageDefinition(options.frontMatterLanguage); const remake = source => grayMatter(source, {});
const anyChar = '[\\s\\S]';
const frontMatterBlock = `^${open}$${anyChar}*?^${close}$`;
const frontMatterRegex = new RegExp(`${frontMatterBlock}`, 'm');
const preGroupedRegex = new RegExp(`(${anyChar}*?)(${frontMatterBlock})(\\s*)(${anyChar}*)`, 'm'); // preFrontMatter, frontMatter, spacing, and content
let initial;
let editable;
const hasFrontMatter = source => frontMatterRegex.test(source); let editable = remake(raw);
const buildPayload = (source, header, spacing, body) => { const syncContent = (newVal, isBody) => {
return { raw: source, header, spacing, body }; if (isBody) {
}; editable.content = newVal;
} else {
const parse = source => { editable = remake(newVal);
if (hasFrontMatter(source)) {
const match = source.match(preGroupedRegex);
const [, preFrontMatter, frontMatter, spacing, content] = match;
const header = preFrontMatter + frontMatter;
return buildPayload(source, header, spacing, content);
} }
return buildPayload(source, '', '', source);
}; };
const syncEditable = () => { const trimmedEditable = () => grayMatter.stringify(editable).trim();
/*
We re-parse as markdown editing could have added non-body changes (preFrontMatter, frontMatter, or spacing).
Re-parsing additionally gets us the desired body that was extracted from the potentially mutated editable.raw
*/
editable = parse(editable.raw);
};
const refreshEditableRaw = () => { const content = (isBody = false) => (isBody ? editable.content.trim() : trimmedEditable()); // gray-matter internally adds an eof newline so we trim to bypass, open issue: https://github.com/jonschlinkert/gray-matter/issues/96
editable.raw = `${editable.header}${editable.spacing}${editable.body}`;
};
const sync = (newVal, isBodyToRaw) => { const matter = () => editable.matter;
const editableKey = isBodyToRaw ? 'body' : 'raw';
editable[editableKey] = newVal;
if (isBodyToRaw) { const syncMatter = newMatter => {
refreshEditableRaw(); const targetMatter = newMatter.replace(/---/gm, ''); // TODO dynamic delimiter removal vs. hard code
} const currentMatter = matter();
const currentContent = content();
syncEditable(); const newSource = currentContent.replace(currentMatter, targetMatter);
syncContent(newSource);
editable.matter = newMatter;
}; };
const frontMatter = () => editable.header; const matterObject = () => editable.data;
const setFrontMatter = val => { const syncMatterObject = obj => {
editable.header = val; editable.data = obj;
refreshEditableRaw();
}; };
const content = (isBody = false) => { const isModified = () => trimmedEditable() !== raw;
const editableKey = isBody ? 'body' : 'raw';
return editable[editableKey];
};
const isModified = () => initial.raw !== editable.raw;
initial = parse(raw);
editable = parse(raw);
return { return {
frontMatter, matter,
setFrontMatter, syncMatter,
matterObject,
syncMatterObject,
content, content,
syncContent,
isModified, isModified,
sync,
}; };
}; };
......
const frontMatterLanguageDefinitions = [
{ name: 'yaml', open: '---', close: '---' },
{ name: 'toml', open: '\\+\\+\\+', close: '\\+\\+\\+' },
{ name: 'json', open: '{', close: '}' },
];
const getFrontMatterLanguageDefinition = name => {
const languageDefinition = frontMatterLanguageDefinitions.find(def => def.name === name);
if (!languageDefinition) {
throw new Error(`Unsupported front matter language: ${name}`);
}
return languageDefinition;
};
export default getFrontMatterLanguageDefinition;
...@@ -92,6 +92,7 @@ ...@@ -92,6 +92,7 @@
"glob": "^7.1.6", "glob": "^7.1.6",
"graphql": "^14.7.0", "graphql": "^14.7.0",
"graphql-tag": "^2.10.1", "graphql-tag": "^2.10.1",
"gray-matter": "^4.0.2",
"immer": "^7.0.7", "immer": "^7.0.7",
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
"ipaddr.js": "^1.9.1", "ipaddr.js": "^1.9.1",
......
...@@ -81,7 +81,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { ...@@ -81,7 +81,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => {
it('updates parsedSource with new content', () => { it('updates parsedSource with new content', () => {
const newContent = 'New content'; const newContent = 'New content';
const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'sync'); const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'syncContent');
findRichContentEditor().vm.$emit('input', newContent); findRichContentEditor().vm.$emit('input', newContent);
......
export const sourceContentHeaderYAML = `--- export const sourceContentHeaderYAML = `---
layout: handbook-page-toc layout: handbook-page-toc
title: Handbook title: Handbook
twitter_image: '/images/tweets/handbook-gitlab.png' twitter_image: /images/tweets/handbook-gitlab.png
---`; ---`;
export const sourceContentHeaderTOML = `+++ export const sourceContentHeaderObjYAML = {
layout: "handbook-page-toc" layout: 'handbook-page-toc',
title: "Handbook" title: 'Handbook',
twitter_image: "/images/tweets/handbook-gitlab.png" twitter_image: '/images/tweets/handbook-gitlab.png',
+++`; };
export const sourceContentHeaderJSON = `{ export const sourceContentSpacing = `\n`;
"layout": "handbook-page-toc",
"title": "Handbook",
"twitter_image": "/images/tweets/handbook-gitlab.png",
}`;
export const sourceContentSpacing = `
`;
export const sourceContentBody = `## On this page export const sourceContentBody = `## On this page
{:.no_toc .hidden-md .hidden-lg} {:.no_toc .hidden-md .hidden-lg}
- TOC - TOC
{:toc .hidden-md .hidden-lg} {:toc .hidden-md .hidden-lg}
![image](path/to/image1.png) ![image](path/to/image1.png)`;
`;
export const sourceContentYAML = `${sourceContentHeaderYAML}${sourceContentSpacing}${sourceContentBody}`; export const sourceContentYAML = `${sourceContentHeaderYAML}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTOML = `${sourceContentHeaderTOML}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentJSON = `${sourceContentHeaderJSON}${sourceContentSpacing}${sourceContentBody}`;
export const sourceContentTitle = 'Handbook'; export const sourceContentTitle = 'Handbook';
export const username = 'gitlabuser'; export const username = 'gitlabuser';
......
import getFrontMatterLanguageDefinition from '~/static_site_editor/services/parse_source_file_language_support';
describe('static_site_editor/services/parse_source_file_language_support', () => {
describe('getFrontMatterLanguageDefinition', () => {
it.each`
languageName
${'yaml'}
${'toml'}
${'json'}
${'abcd'}
`('returns $hasMatch when provided $languageName', ({ languageName }) => {
try {
const definition = getFrontMatterLanguageDefinition(languageName);
expect(definition.name).toBe(languageName);
} catch (error) {
expect(error.message).toBe(`Unsupported front matter language: ${languageName}`);
}
});
});
});
import { import {
sourceContentYAML as content, sourceContentYAML as content,
sourceContentTOML as tomlContent,
sourceContentJSON as jsonContent,
sourceContentHeaderYAML as yamlFrontMatter, sourceContentHeaderYAML as yamlFrontMatter,
sourceContentHeaderTOML as tomlFrontMatter, sourceContentHeaderObjYAML as yamlFrontMatterObj,
sourceContentHeaderJSON as jsonFrontMatter,
sourceContentBody as body, sourceContentBody as body,
} from '../mock_data'; } from '../mock_data';
...@@ -18,20 +15,15 @@ describe('static_site_editor/services/parse_source_file', () => { ...@@ -18,20 +15,15 @@ describe('static_site_editor/services/parse_source_file', () => {
const newContentComplex = `${contentComplex} ${edit}`; const newContentComplex = `${contentComplex} ${edit}`;
describe('unmodified front matter', () => { describe('unmodified front matter', () => {
const yamlOptions = { frontMatterLanguage: 'yaml' };
it.each` it.each`
parsedSource | targetFrontMatter parsedSource | targetFrontMatter
${parseSourceFile(content)} | ${yamlFrontMatter} ${parseSourceFile(content)} | ${yamlFrontMatter}
${parseSourceFile(contentComplex)} | ${yamlFrontMatter} ${parseSourceFile(contentComplex)} | ${yamlFrontMatter}
${parseSourceFile(content, yamlOptions)} | ${yamlFrontMatter}
${parseSourceFile(contentComplex, yamlOptions)} | ${yamlFrontMatter}
${parseSourceFile(tomlContent, { frontMatterLanguage: 'toml' })} | ${tomlFrontMatter}
${parseSourceFile(jsonContent, { frontMatterLanguage: 'json' })} | ${jsonFrontMatter}
`( `(
'returns $targetFrontMatter when frontMatter queried', 'returns $targetFrontMatter when frontMatter queried',
({ parsedSource, targetFrontMatter }) => { ({ parsedSource, targetFrontMatter }) => {
expect(parsedSource.frontMatter()).toBe(targetFrontMatter); expect(targetFrontMatter).toContain(parsedSource.matter());
expect(parsedSource.matterObject()).toEqual(yamlFrontMatterObj);
}, },
); );
}); });
...@@ -63,6 +55,7 @@ describe('static_site_editor/services/parse_source_file', () => { ...@@ -63,6 +55,7 @@ describe('static_site_editor/services/parse_source_file', () => {
describe('modified front matter', () => { describe('modified front matter', () => {
const newYamlFrontMatter = '---\nnewKey: newVal\n---'; const newYamlFrontMatter = '---\nnewKey: newVal\n---';
const newYamlFrontMatterObj = { newKey: 'newVal' };
const contentWithNewFrontMatter = content.replace(yamlFrontMatter, newYamlFrontMatter); const contentWithNewFrontMatter = content.replace(yamlFrontMatter, newYamlFrontMatter);
const contentComplexWithNewFrontMatter = contentComplex.replace( const contentComplexWithNewFrontMatter = contentComplex.replace(
yamlFrontMatter, yamlFrontMatter,
...@@ -76,11 +69,12 @@ describe('static_site_editor/services/parse_source_file', () => { ...@@ -76,11 +69,12 @@ describe('static_site_editor/services/parse_source_file', () => {
`( `(
'returns the correct front matter and modified content', 'returns the correct front matter and modified content',
({ parsedSource, targetContent }) => { ({ parsedSource, targetContent }) => {
expect(parsedSource.frontMatter()).toBe(yamlFrontMatter); expect(yamlFrontMatter).toContain(parsedSource.matter());
parsedSource.setFrontMatter(newYamlFrontMatter); parsedSource.syncMatter(newYamlFrontMatter);
expect(parsedSource.frontMatter()).toBe(newYamlFrontMatter); expect(parsedSource.matter()).toBe(newYamlFrontMatter);
expect(parsedSource.matterObject()).toEqual(newYamlFrontMatterObj);
expect(parsedSource.content()).toBe(targetContent); expect(parsedSource.content()).toBe(targetContent);
}, },
); );
...@@ -99,7 +93,7 @@ describe('static_site_editor/services/parse_source_file', () => { ...@@ -99,7 +93,7 @@ describe('static_site_editor/services/parse_source_file', () => {
`( `(
'returns $isModified after a $targetRaw sync', 'returns $isModified after a $targetRaw sync',
({ parsedSource, isModified, targetRaw, targetBody }) => { ({ parsedSource, isModified, targetRaw, targetBody }) => {
parsedSource.sync(targetRaw); parsedSource.syncContent(targetRaw);
expect(parsedSource.isModified()).toBe(isModified); expect(parsedSource.isModified()).toBe(isModified);
expect(parsedSource.content()).toBe(targetRaw); expect(parsedSource.content()).toBe(targetRaw);
......
...@@ -3813,13 +3813,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: ...@@ -3813,13 +3813,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
debug@=3.1.0, debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: debug@^3.1.1, debug@^3.2.5, debug@^3.2.6:
version "3.2.6" version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
...@@ -3834,6 +3827,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: ...@@ -3834,6 +3827,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@~3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
decamelize-keys@^1.0.0: decamelize-keys@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
...@@ -5138,14 +5138,7 @@ flush-write-stream@^1.0.0: ...@@ -5138,14 +5138,7 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3" inherits "^2.0.3"
readable-stream "^2.3.6" readable-stream "^2.3.6"
follow-redirects@^1.0.0: follow-redirects@^1.0.0, follow-redirects@^1.10.0:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
follow-redirects@^1.10.0:
version "1.13.0" version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
...@@ -5588,6 +5581,16 @@ graphql@^14.7.0: ...@@ -5588,6 +5581,16 @@ graphql@^14.7.0:
dependencies: dependencies:
iterall "^1.2.2" iterall "^1.2.2"
gray-matter@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/gray-matter/-/gray-matter-4.0.2.tgz#9aa379e3acaf421193fce7d2a28cebd4518ac454"
integrity sha512-7hB/+LxrOjq/dd8APlK0r24uL/67w7SkYnfwhNFwg/VDIGWGmduTDYf3WNstLW2fbbmRwrDGCVSJ2isuf2+4Hw==
dependencies:
js-yaml "^3.11.0"
kind-of "^6.0.2"
section-matter "^1.0.0"
strip-bom-string "^1.0.0"
growly@^1.3.0: growly@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
...@@ -7113,7 +7116,7 @@ js-cookie@^2.2.1: ...@@ -7113,7 +7116,7 @@ js-cookie@^2.2.1:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@~3.13.1: js-yaml@^3.11.0, js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@~3.13.1:
version "3.13.1" version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
...@@ -10499,6 +10502,14 @@ scss-tokenizer@^0.2.3: ...@@ -10499,6 +10502,14 @@ scss-tokenizer@^0.2.3:
js-base64 "^2.1.8" js-base64 "^2.1.8"
source-map "^0.4.2" source-map "^0.4.2"
section-matter@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"
integrity sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==
dependencies:
extend-shallow "^2.0.1"
kind-of "^6.0.0"
select-hose@^2.0.0: select-hose@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
...@@ -11161,6 +11172,11 @@ strip-ansi@^6.0.0: ...@@ -11161,6 +11172,11 @@ strip-ansi@^6.0.0:
dependencies: dependencies:
ansi-regex "^5.0.0" ansi-regex "^5.0.0"
strip-bom-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92"
integrity sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=
strip-bom@^2.0.0: strip-bom@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
......
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