Commit de4645a8 authored by Phil Hughes's avatar Phil Hughes

Merge branch '221083-custom-renderer-font-awesome' into 'master'

Add font awesome custom renderer

Closes #221083

See merge request gitlab-org/gitlab!36361
parents 4529939f 434e1847
...@@ -3,7 +3,9 @@ import renderKramdownList from './renderers/render_kramdown_list'; ...@@ -3,7 +3,9 @@ import renderKramdownList from './renderers/render_kramdown_list';
import renderKramdownText from './renderers/render_kramdown_text'; import renderKramdownText from './renderers/render_kramdown_text';
import renderIdentifierParagraph from './renderers/render_identifier_paragraph'; import renderIdentifierParagraph from './renderers/render_identifier_paragraph';
import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text'; import renderEmbeddedRubyText from './renderers/render_embedded_ruby_text';
import renderFontAwesomeHtmlInline from './renderers/render_font_awesome_html_inline';
const htmlInlineRenderers = [renderFontAwesomeHtmlInline];
const htmlRenderers = [renderHtml]; const htmlRenderers = [renderHtml];
const listRenderers = [renderKramdownList]; const listRenderers = [renderKramdownList];
const paragraphRenderers = [renderIdentifierParagraph]; const paragraphRenderers = [renderIdentifierParagraph];
...@@ -26,7 +28,7 @@ const buildCustomRendererFunctions = (customRenderers, defaults) => { ...@@ -26,7 +28,7 @@ const buildCustomRendererFunctions = (customRenderers, defaults) => {
}; };
const buildCustomHTMLRenderer = ( const buildCustomHTMLRenderer = (
customRenderers = { htmlBlock: [], list: [], paragraph: [], text: [] }, customRenderers = { htmlBlock: [], htmlInline: [], list: [], paragraph: [], text: [] },
) => { ) => {
const defaults = { const defaults = {
htmlBlock(node, context) { htmlBlock(node, context) {
...@@ -34,6 +36,11 @@ const buildCustomHTMLRenderer = ( ...@@ -34,6 +36,11 @@ const buildCustomHTMLRenderer = (
return executeRenderer(allHtmlRenderers, node, context); return executeRenderer(allHtmlRenderers, node, context);
}, },
htmlInline(node, context) {
const allHtmlInlineRenderers = [...customRenderers.htmlInline, ...htmlInlineRenderers];
return executeRenderer(allHtmlInlineRenderers, node, context);
},
list(node, context) { list(node, context) {
const allListRenderers = [...customRenderers.list, ...listRenderers]; const allListRenderers = [...customRenderers.list, ...listRenderers];
......
...@@ -2,9 +2,14 @@ const buildToken = (type, tagName, props) => { ...@@ -2,9 +2,14 @@ const buildToken = (type, tagName, props) => {
return { type, tagName, ...props }; return { type, tagName, ...props };
}; };
export const buildUneditableOpenTokens = token => { const TAG_TYPES = {
block: 'div',
inline: 'span',
};
export const buildUneditableOpenTokens = (token, type = TAG_TYPES.block) => {
return [ return [
buildToken('openTag', 'div', { buildToken('openTag', type, {
attributes: { contenteditable: false }, attributes: { contenteditable: false },
classNames: [ classNames: [
'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed', 'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
...@@ -14,10 +19,17 @@ export const buildUneditableOpenTokens = token => { ...@@ -14,10 +19,17 @@ export const buildUneditableOpenTokens = token => {
]; ];
}; };
export const buildUneditableCloseToken = () => buildToken('closeTag', 'div'); export const buildUneditableCloseToken = (type = TAG_TYPES.block) => buildToken('closeTag', type);
export const buildUneditableCloseTokens = (token, type = TAG_TYPES.block) => {
return [token, buildUneditableCloseToken(type)];
};
export const buildUneditableCloseTokens = token => { export const buildUneditableInlineTokens = token => {
return [token, buildToken('closeTag', 'div')]; return [
...buildUneditableOpenTokens(token, TAG_TYPES.inline),
buildUneditableCloseToken(TAG_TYPES.inline),
];
}; };
export const buildUneditableTokens = token => { export const buildUneditableTokens = token => {
......
import { buildUneditableInlineTokens } from './build_uneditable_token';
const fontAwesomeRegexOpen = /<i class="fa.+>/;
const canRender = ({ literal }) => {
return fontAwesomeRegexOpen.test(literal);
};
const render = (_, { origin }) => buildUneditableInlineTokens(origin());
export default { canRender, render };
---
title: Add a custom HTML renderer to the Static Site Editor for font awesome inline HTML syntax
merge_request: 36361
author:
type: added
...@@ -2,14 +2,17 @@ import { ...@@ -2,14 +2,17 @@ import {
buildUneditableOpenTokens, buildUneditableOpenTokens,
buildUneditableCloseToken, buildUneditableCloseToken,
buildUneditableCloseTokens, buildUneditableCloseTokens,
buildUneditableInlineTokens,
buildUneditableTokens, buildUneditableTokens,
} from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token'; } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
import { import {
originInlineToken,
originToken, originToken,
uneditableOpenTokens, uneditableOpenTokens,
uneditableCloseToken, uneditableCloseToken,
uneditableCloseTokens, uneditableCloseTokens,
uneditableInlineTokens,
uneditableTokens, uneditableTokens,
} from './mock_data'; } from './mock_data';
...@@ -38,8 +41,17 @@ describe('Build Uneditable Token renderer helper', () => { ...@@ -38,8 +41,17 @@ describe('Build Uneditable Token renderer helper', () => {
}); });
}); });
describe('buildUneditableInlineTokens', () => {
it('returns a 3-item array of tokens with the originInlineToken wrapped in the middle of inline tokens', () => {
const result = buildUneditableInlineTokens(originInlineToken);
expect(result).toHaveLength(3);
expect(result).toStrictEqual(uneditableInlineTokens);
});
});
describe('buildUneditableTokens', () => { describe('buildUneditableTokens', () => {
it('returns a 3-item array of tokens with the originToken wrapped in the middle', () => { it('returns a 3-item array of tokens with the originToken wrapped in the middle of block tokens', () => {
const result = buildUneditableTokens(originToken); const result = buildUneditableTokens(originToken);
expect(result).toHaveLength(3); expect(result).toHaveLength(3);
......
...@@ -12,20 +12,36 @@ export const normalTextNode = buildMockTextNode('This is just normal text.'); ...@@ -12,20 +12,36 @@ export const normalTextNode = buildMockTextNode('This is just normal text.');
// Token spec helpers // Token spec helpers
const uneditableOpenToken = { const buildUneditableOpenToken = type => {
return {
type: 'openTag', type: 'openTag',
tagName: 'div', tagName: type,
attributes: { contenteditable: false }, attributes: { contenteditable: false },
classNames: [ classNames: [
'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed', 'gl-px-4 gl-py-2 gl-opacity-5 gl-bg-gray-100 gl-user-select-none gl-cursor-not-allowed',
], ],
};
};
const buildUneditableCloseToken = type => {
return { type: 'closeTag', tagName: type };
}; };
export const uneditableCloseToken = { type: 'closeTag', tagName: 'div' };
export const originToken = { export const originToken = {
type: 'text', type: 'text',
content: '{:.no_toc .hidden-md .hidden-lg}', content: '{:.no_toc .hidden-md .hidden-lg}',
}; };
export const uneditableOpenTokens = [uneditableOpenToken, originToken]; export const uneditableCloseToken = buildUneditableCloseToken('div');
export const uneditableOpenTokens = [buildUneditableOpenToken('div'), originToken];
export const uneditableCloseTokens = [originToken, uneditableCloseToken]; export const uneditableCloseTokens = [originToken, uneditableCloseToken];
export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken]; export const uneditableTokens = [...uneditableOpenTokens, uneditableCloseToken];
export const originInlineToken = {
type: 'text',
content: '<i>Inline</i> content',
};
export const uneditableInlineTokens = [
buildUneditableOpenToken('span'),
originInlineToken,
buildUneditableCloseToken('span'),
];
import renderer from '~/vue_shared/components/rich_content_editor/services/renderers/render_font_awesome_html_inline';
import { buildUneditableInlineTokens } from '~/vue_shared/components/rich_content_editor/services/renderers/build_uneditable_token';
import { normalTextNode } from './mock_data';
const fontAwesomeInlineHtmlNode = {
firstChild: null,
literal: '<i class="far fa-paper-plane" id="biz-tech-icons">',
type: 'html',
};
describe('Render Font Awesome Inline HTML renderer', () => {
describe('canRender', () => {
it('should return true when the argument `literal` has font awesome inline html syntax', () => {
expect(renderer.canRender(fontAwesomeInlineHtmlNode)).toBe(true);
});
it('should return false when the argument `literal` lacks font awesome inline html syntax', () => {
expect(renderer.canRender(normalTextNode)).toBe(false);
});
});
describe('render', () => {
it('should return uneditable inline tokens', () => {
const token = { type: 'text', tagName: null, content: fontAwesomeInlineHtmlNode.literal };
const context = { origin: () => token };
expect(renderer.render(fontAwesomeInlineHtmlNode, context)).toStrictEqual(
buildUneditableInlineTokens(token),
);
});
});
});
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