Commit d84bcddf authored by Enrique Alcantara's avatar Enrique Alcantara

Render frontmatter codeblock in the Content Editor

Render and allow editing frontmatter code blocks
in the Content Editor

Changelog: added
parent 1bd14b95
<script>
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
import { __ } from '~/locale';
export default {
name: 'FrontMatter',
components: {
NodeViewWrapper,
NodeViewContent,
},
props: {
node: {
type: Object,
required: true,
},
},
i18n: {
frontmatter: __('frontmatter'),
},
};
</script>
<template>
<node-view-wrapper class="gl-relative code highlight" as="pre">
<span
data-testid="frontmatter-label"
class="gl-absolute gl-top-0 gl-right-3"
contenteditable="false"
>{{ $options.i18n.frontmatter }}:{{ node.attrs.language }}</span
>
<node-view-content as="code" />
</node-view-wrapper>
</template>
......@@ -11,7 +11,8 @@ export default CodeBlockLowlight.extend({
parseHTML: (element) => extractLanguage(element),
},
class: {
default: 'code highlight js-syntax-highlight',
// eslint-disable-next-line @gitlab/require-i18n-strings
default: 'code highlight',
},
};
},
......
import { VueNodeViewRenderer } from '@tiptap/vue-2';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
import FrontmatterWrapper from '../components/wrappers/frontmatter.vue';
import CodeBlockHighlight from './code_block_highlight';
export default CodeBlockHighlight.extend({
name: 'frontmatter',
parseHTML() {
return [
{
tag: 'pre[data-lang-params="frontmatter"]',
preserveWhitespace: 'full',
priority: PARSE_HTML_PRIORITY_HIGHEST,
},
];
},
addNodeView() {
return new VueNodeViewRenderer(FrontmatterWrapper);
},
});
......@@ -16,6 +16,7 @@ import Dropcursor from '../extensions/dropcursor';
import Emoji from '../extensions/emoji';
import Figure from '../extensions/figure';
import FigureCaption from '../extensions/figure_caption';
import Frontmatter from '../extensions/frontmatter';
import Gapcursor from '../extensions/gapcursor';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
......@@ -86,6 +87,7 @@ export const createContentEditor = ({
Emoji,
Figure,
FigureCaption,
Frontmatter,
Gapcursor,
HardBreak,
Heading,
......
......@@ -15,6 +15,7 @@ import Division from '../extensions/division';
import Emoji from '../extensions/emoji';
import Figure from '../extensions/figure';
import FigureCaption from '../extensions/figure_caption';
import Frontmatter from '../extensions/frontmatter';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
import HorizontalRule from '../extensions/horizontal_rule';
......@@ -137,6 +138,20 @@ const defaultSerializerConfig = {
state.write(`:${name}:`);
},
[Frontmatter.name]: (state, node) => {
const { language } = node.attrs;
const syntax = {
toml: '+++',
json: ';;;',
yaml: '---',
}[language];
state.write(`${syntax}\n`);
state.text(node.textContent, false);
state.ensureNewLine();
state.write(syntax);
state.closeBlock(node);
},
[Figure.name]: renderHTMLNode('figure'),
[FigureCaption.name]: renderHTMLNode('figcaption'),
[HardBreak.name]: renderHardBreak,
......
......@@ -39992,6 +39992,9 @@ msgid_plural "from %d jobs"
msgstr[0] ""
msgstr[1] ""
msgid "frontmatter"
msgstr ""
msgid "group"
msgstr ""
......
import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2';
import { shallowMount } from '@vue/test-utils';
import FrontmatterWrapper from '~/content_editor/components/wrappers/frontmatter.vue';
describe('content/components/wrappers/frontmatter', () => {
let wrapper;
const createWrapper = async (nodeAttrs = { language: 'yaml' }) => {
wrapper = shallowMount(FrontmatterWrapper, {
propsData: {
node: {
attrs: nodeAttrs,
},
},
});
};
afterEach(() => {
wrapper.destroy();
});
it('renders a node-view-wrapper as a pre element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewWrapper).props().as).toBe('pre');
expect(wrapper.findComponent(NodeViewWrapper).classes()).toContain('gl-relative');
});
it('renders a node-view-content as a code element', () => {
createWrapper();
expect(wrapper.findComponent(NodeViewContent).props().as).toBe('code');
});
it('renders label indicating that code block is frontmatter', () => {
createWrapper();
const label = wrapper.find('[data-testid="frontmatter-label"]');
expect(label.text()).toEqual('frontmatter:yaml');
expect(label.classes()).toEqual(['gl-absolute', 'gl-top-0', 'gl-right-3']);
});
});
......@@ -221,3 +221,20 @@
### I don't know
- name: word_break
markdown: Fernstraßen<wbr>bau<wbr>privat<wbr>finanzierungs<wbr>gesetz
- name: frontmatter_yaml
markdown: |-
---
title: Page title
---
- name: frontmatter_toml
markdown: |-
+++
title = "Page title"
+++
- name: frontmatter_json
markdown: |-
;;;
{
"title": "Page title"
}
;;;
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