Skip to content

Commit

Permalink
[EuiMarkdownEditor] Allow more plugins to be excludable (#7676)
Browse files Browse the repository at this point in the history
Co-authored-by: Cee Chen <constance.chen@elastic.co>
  • Loading branch information
sakurai-youhei and cee-chen authored Apr 13, 2024
1 parent 0cd039a commit 07d5338
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 56 deletions.
5 changes: 5 additions & 0 deletions changelogs/upcoming/7676.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Updated `getDefaultEuiMarkdownPlugins()` to allow excluding the following plugins in addition to `tooltip`:
- `checkbox`
- `linkValidator`
- `lineBreaks`
- `emoji`
1 change: 0 additions & 1 deletion src-docs/src/views/markdown_editor/markdown_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ The editor also ships with some built in plugins. For example it can handle chec
- [ ] Or empty
It can also handle emojis! :smile:
And it can render !{tooltip[tooltips like this](Look! I'm a very helpful tooltip content!)}
`;

Expand Down
24 changes: 16 additions & 8 deletions src-docs/src/views/markdown_editor/markdown_editor_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ const markdownEditorNoPluginsSnippet = `const {
parsingPlugins,
processingPlugins,
uiPlugins,
} = getDefaultEuiMarkdownPlugins({ exclude: ['tooltip'] });
} = getDefaultEuiMarkdownPlugins({
exclude: [
'tooltip',
'checkbox',
'linkValidator',
'lineBreaks',
'emoji',
],
});
<EuiMarkdownEditor
value={value}
Expand Down Expand Up @@ -133,16 +141,16 @@ export const MarkdownEditorExample = {
title: 'Unregistering plugins',
text: (
<p>
The <strong>EuiMarkdownEditor</strong> comes with a default plugin for{' '}
<EuiCode>tooltip</EuiCode> support. However, this may be unfamiliar or
unnecessary in some contexts, and you can unregister this plugin by
excluding it from the
<EuiCode>parsingPlugins</EuiCode>,{' '}
The <strong>EuiMarkdownEditor</strong> comes with several default
plugins, demo'd below. If these defaults are unnecessary for your
use-case or context, you can unregister these plugins by excluding
them from the <EuiCode>parsingPlugins</EuiCode>,{' '}
<EuiCode>processingPlugins</EuiCode> and <EuiCode>uiPlugins</EuiCode>{' '}
options with a single <EuiCode>exclude</EuiCode> parameter passed to{' '}
<EuiCode>getDefaultEuiMarkdownPlugins()</EuiCode>. This will ensure
the syntax won&apos;t be identified or rendered and no additional UI,
like the button and help syntax, will be displayed.
the syntax won't be identified or rendered, and no additional UI (like
toolbar buttons or help syntax) will be displayed by the unregistered
plugins.
</p>
),
props: {
Expand Down
109 changes: 93 additions & 16 deletions src-docs/src/views/markdown_editor/markdown_editor_no_plugins.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,112 @@
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';

import {
EuiMarkdownEditor,
getDefaultEuiMarkdownPlugins,
EuiFlexGroup,
EuiFlexItem,
EuiSwitch,
} from '../../../../src/components';

const initialContent = `## This is how we do it :smile:
const initialContent = `
### tooltip
In this example, we unregistered the built in **tooltip** plugin. So the button in the toolbar and the help syntax won't be displayed.
And the following syntax no longer works.
When disabled, the button in the toolbar and the help syntax won't be displayed, and the following syntax will no longer works.
!{tooltip[anchor text](Tooltip content)}
`;
const { parsingPlugins, processingPlugins, uiPlugins } =
getDefaultEuiMarkdownPlugins({ exclude: ['tooltip'] });
### checkbox
When disabled, a EuiCheckbox will no longer render.
- [ ] TODO: Disable some default plugins
### emoji
When disabled, text will render instead of an emoji.
:smile:
### linkValidator
When disabled, all links will render as links instead of as text.
[This is a sus link](file://)
### lineBreaks
When disabled, these sentence will be in the same line.
When enabled, these sentences will be separated by a line break.
Two blank lines in a row will create a new paragraph as usual.
`;

export default () => {
const [value, setValue] = useState(initialContent);

const [tooltip, excludeTooltips] = useState(false);
const [checkbox, excludeCheckboxes] = useState(true);
const [emoji, excludeEmojis] = useState(true);
const [linkValidator, excludeLinkValidator] = useState(true);
const [lineBreaks, excludeLineBreaks] = useState(false);

const { parsingPlugins, processingPlugins, uiPlugins } = useMemo(() => {
const excludedPlugins = [];
if (!tooltip) excludedPlugins.push('tooltip');
if (!checkbox) excludedPlugins.push('checkbox');
if (!emoji) excludedPlugins.push('emoji');
if (!linkValidator) excludedPlugins.push('linkValidator');
if (!lineBreaks) excludedPlugins.push('lineBreaks');

return getDefaultEuiMarkdownPlugins({
exclude: excludedPlugins,
});
}, [tooltip, checkbox, emoji, linkValidator, lineBreaks]);

return (
<>
<EuiMarkdownEditor
aria-label="EUI markdown editor with no default plugins demo"
value={value}
onChange={setValue}
parsingPluginList={parsingPlugins}
processingPluginList={processingPlugins}
uiPlugins={uiPlugins}
/>
<EuiFlexGroup>
<EuiFlexItem grow={false} css={{ gap: 20 }}>
<EuiSwitch
label="tooltip"
checked={tooltip}
onChange={() => excludeTooltips(!tooltip)}
/>
<EuiSwitch
label="checkbox"
checked={checkbox}
onChange={() => excludeCheckboxes(!checkbox)}
/>
<EuiSwitch
label="emoji"
checked={emoji}
onChange={() => excludeEmojis(!emoji)}
/>
<EuiSwitch
label="linkValidator"
checked={linkValidator}
onChange={() => excludeLinkValidator(!linkValidator)}
/>
<EuiSwitch
label="lineBreaks"
checked={lineBreaks}
onChange={() => excludeLineBreaks(!lineBreaks)}
/>
</EuiFlexItem>
<EuiFlexItem>
<EuiMarkdownEditor
aria-label="Demo with excluded default plugins"
value={value}
onChange={setValue}
parsingPluginList={parsingPlugins}
processingPluginList={processingPlugins}
uiPlugins={uiPlugins}
initialViewMode="viewing"
autoExpandPreview={false}
height={400}
/>
</EuiFlexItem>
</EuiFlexGroup>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,40 @@ import {
euiMarkdownLinkValidator,
EuiMarkdownLinkValidatorOptions,
} from '../markdown_link_validator';
import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins';

export type DefaultEuiMarkdownParsingPlugins = PluggableList;

const DEFAULT_PARSING_PLUGINS: Record<
ExcludableDefaultPlugins,
DefaultEuiMarkdownParsingPlugins[0]
> = {
emoji: [emoji, { emoticon: false }],
lineBreaks: [breaks, {}],
linkValidator: [
euiMarkdownLinkValidator,
{
allowRelative: true,
allowProtocols: ['https:', 'http:', 'mailto:'],
} as EuiMarkdownLinkValidatorOptions,
],
checkbox: [MarkdownCheckbox.parser, {}],
tooltip: [MarkdownTooltip.parser, {}],
};

export const getDefaultEuiMarkdownParsingPlugins = ({
exclude,
}: { exclude?: Array<'tooltip'> } = {}): DefaultEuiMarkdownParsingPlugins => {
const excludeSet = new Set(exclude);
}: DefaultPluginsConfig = {}): DefaultEuiMarkdownParsingPlugins => {
const parsingPlugins: PluggableList = [
[markdown, {}],
[highlight, {}],
[emoji, { emoticon: false }],
[breaks, {}],
[
euiMarkdownLinkValidator,
{
allowRelative: true,
allowProtocols: ['https:', 'http:', 'mailto:'],
} as EuiMarkdownLinkValidatorOptions,
],
[MarkdownCheckbox.parser, {}],
];

if (!excludeSet.has('tooltip'))
parsingPlugins.push([MarkdownTooltip.parser, {}]);
Object.entries(DEFAULT_PARSING_PLUGINS).forEach(([pluginName, plugin]) => {
if (!exclude?.includes(pluginName as ExcludableDefaultPlugins)) {
parsingPlugins.push(plugin);
}
});

return parsingPlugins;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getDefaultEuiMarkdownPlugins } from './plugins';

describe('default plugins', () => {
test('no exclusions', () => {
const { parsingPlugins, processingPlugins, uiPlugins } =
getDefaultEuiMarkdownPlugins();

expect(parsingPlugins).toHaveLength(7);
expect(Object.keys(processingPlugins[1][1].components)).toHaveLength(8);
expect(uiPlugins).toHaveLength(1);

expect(processingPlugins[1][1].components.tooltipPlugin).toBeDefined();
expect(processingPlugins[1][1].components.checkboxPlugin).toBeDefined();
});

test('exclude tooltips', () => {
const { parsingPlugins, processingPlugins, uiPlugins } =
getDefaultEuiMarkdownPlugins({
exclude: ['tooltip'],
});

expect(parsingPlugins).toHaveLength(6);
expect(processingPlugins[1][1].components.tooltipPlugin).toBeUndefined();
expect(uiPlugins).toHaveLength(0);
});

test('exclude checkboxes', () => {
const { parsingPlugins, processingPlugins, uiPlugins } =
getDefaultEuiMarkdownPlugins({
exclude: ['checkbox'],
});

expect(parsingPlugins).toHaveLength(6);
expect(processingPlugins[1][1].components.checkboxPlugin).toBeUndefined();
expect(uiPlugins).toHaveLength(1);
});

test('all exclusions', () => {
const { parsingPlugins, processingPlugins, uiPlugins } =
getDefaultEuiMarkdownPlugins({
exclude: [
'tooltip',
'checkbox',
'lineBreaks',
'linkValidator',
'emoji',
],
});

expect(parsingPlugins).toHaveLength(2);
expect(Object.keys(processingPlugins[1][1].components)).toHaveLength(6);
expect(uiPlugins).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,19 @@ import {
DefaultEuiMarkdownProcessingPlugins,
} from './processing_plugins';

export type ExcludableDefaultPlugins =
| 'emoji'
| 'lineBreaks'
| 'linkValidator'
| 'checkbox'
| 'tooltip';

export type DefaultPluginsConfig =
| undefined
| { exclude?: ExcludableDefaultPlugins[] };

export const getDefaultEuiMarkdownPlugins = (
config: undefined | { exclude?: Array<'tooltip'> }
config?: DefaultPluginsConfig
): {
parsingPlugins: DefaultEuiMarkdownParsingPlugins;
processingPlugins: DefaultEuiMarkdownProcessingPlugins;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ import { Options as Remark2RehypeOptions, Handler } from 'mdast-util-to-hast';
import all from 'mdast-util-to-hast/lib/all';
import rehype2react from 'rehype-react';
import remark2rehype from 'remark-rehype';
import * as MarkdownTooltip from '../markdown_tooltip';
import * as MarkdownCheckbox from '../markdown_checkbox';
import { FENCED_CLASS } from '../remark/remark_prismjs';

import { EuiLink } from '../../../link';
import { EuiCodeBlock, EuiCode } from '../../../code';
import { EuiHorizontalRule } from '../../../horizontal_rule';

import { FENCED_CLASS } from '../remark/remark_prismjs';
import * as MarkdownTooltip from '../markdown_tooltip';
import * as MarkdownCheckbox from '../markdown_checkbox';
import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins';

const unknownHandler: Handler = (h, node) => {
return h(node, node.type, node, all(h, node));
};
Expand All @@ -50,12 +53,26 @@ export type DefaultEuiMarkdownProcessingPlugins = [
...PluggableList // any additional are generic
];

const DEFAULT_COMPONENT_RENDERERS: Partial<
Record<ExcludableDefaultPlugins, React.ComponentType<any>>
> = {
checkbox: MarkdownCheckbox.renderer,
tooltip: MarkdownTooltip.renderer,
};

export const getDefaultEuiMarkdownProcessingPlugins = ({
exclude,
}: {
exclude?: Array<'tooltip'>;
} = {}): DefaultEuiMarkdownProcessingPlugins => {
const excludeSet = new Set(exclude);
}: DefaultPluginsConfig = {}): DefaultEuiMarkdownProcessingPlugins => {
const componentPluginsWithExclusions: Rehype2ReactOptions['components'] = {};

Object.entries(DEFAULT_COMPONENT_RENDERERS).forEach(
([excludeName, renderer]) => {
if (!exclude?.includes(excludeName as ExcludableDefaultPlugins)) {
const pluginName = `${excludeName}Plugin`;
componentPluginsWithExclusions[pluginName] = renderer;
}
}
);

const plugins: DefaultEuiMarkdownProcessingPlugins = [
[
Expand Down Expand Up @@ -99,15 +116,12 @@ export const getDefaultEuiMarkdownProcessingPlugins = ({
<table className="euiMarkdownFormat__table" {...props} />
),
hr: (props) => <EuiHorizontalRule {...props} />,
checkboxPlugin: MarkdownCheckbox.renderer,
...componentPluginsWithExclusions,
},
},
],
];

if (!excludeSet.has('tooltip'))
plugins[1][1].components.tooltipPlugin = MarkdownTooltip.renderer;

return plugins;
};

Expand Down
Loading

0 comments on commit 07d5338

Please sign in to comment.