diff --git a/packages/eui/changelogs/upcoming/7985.md b/packages/eui/changelogs/upcoming/7985.md
new file mode 100644
index 00000000000..d8d6636d747
--- /dev/null
+++ b/packages/eui/changelogs/upcoming/7985.md
@@ -0,0 +1,5 @@
+- Updated `getDefaultEuiMarkdownPlugins` to support the following new default plugin configurations:
+ - `parsingConfig.linkValidator`, which allows configuring `allowRelative` and `allowProtocols`
+ - `parsingConfig.emoji`, which allows configuring emoticon parsing
+ - `processingConfig.linkProps`, which allows configuring rendered links with any props that `EuiLink` accepts
+ - See our **Markdown plugins** documentation for example `EuiMarkdownFormat` and `EuiMarkdownEditor` usage
diff --git a/packages/eui/src-docs/src/views/markdown_editor/markdown_format_links.js b/packages/eui/src-docs/src/views/markdown_editor/markdown_format_links.js
index 2b28d6bd188..3e8969b8f1a 100644
--- a/packages/eui/src-docs/src/views/markdown_editor/markdown_format_links.js
+++ b/packages/eui/src-docs/src/views/markdown_editor/markdown_format_links.js
@@ -7,8 +7,8 @@ const locationPathname = location.pathname;
export const markdownContent = `**Links starting with http:, https:, mailto:, and / are valid:**
-* https://elastic.com
-* http://elastic.com
+* https://elastic.co
+* http://elastic.co
* https link to [elastic.co](https://elastic.co)
* http link to [elastic.co](http://elastic.co)
* relative link to [eui doc's homepage](${locationPathname})
diff --git a/packages/eui/src-docs/src/views/markdown_editor/markdown_link_validation.js b/packages/eui/src-docs/src/views/markdown_editor/markdown_link_validation.js
deleted file mode 100644
index 0d7863e6ce5..00000000000
--- a/packages/eui/src-docs/src/views/markdown_editor/markdown_link_validation.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import React from 'react';
-
-import {
- getDefaultEuiMarkdownParsingPlugins,
- euiMarkdownLinkValidator,
- EuiMarkdownFormat,
-} from '../../../../src/components';
-
-const parsingPlugins = [
- // Exclude the default validation plugin, we're configuring our own that excludes `http` as a protocol
- ...getDefaultEuiMarkdownParsingPlugins({
- exclude: ['linkValidator'],
- }),
- [
- euiMarkdownLinkValidator,
- {
- allowProtocols: ['https:', 'mailto:'],
- },
- ],
-];
-
-const markdown = `**Standalone links**
-https://example.com
-http://example.com
-someone@example.com
-
-**As markdown syntax**
-[example.com, https](https://example.com)
-[example.com, http](http://example.com)
-[email someone@example.com](mailto:someone@example.com)
-`;
-
-export default () => (
-
- To enhance user and application security, the default behavior
- removes links to URLs that aren't relative (beginning with{' '}
-
- In this example only
An EuiMarkdown plugin is comprised of three major
@@ -374,7 +393,7 @@ const parsingPlugins = [
/>
The below example takes the concepts from above to construct a
simple chart embed that is initiated from a new button in the editor
@@ -545,7 +564,7 @@ processingList[1][1].components.emojiPlugin = EmojiMarkdownRenderer;`}
list. The editor manages additional controls through the{' '}
+
+ Hello world
+
+ uiPlugin
+ uiPlugin
parsingPluginList
+ parsingPluginList
processingPluginList
+ processingPluginList
'),
+ after: () => expect(getComponent().innerHTML).not.toContain('
'),
+ });
+ });
+ });
+
+ describe('processingConfig', () => {
+ test('linkProps', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: '[link](https://elastic.co)',
+ config: {
+ processingConfig: { linkProps: { target: '_blank' } },
+ },
+ before: () => expect(getLink()).not.toHaveAttribute('target'),
+ after: () => expect(getLink()).toHaveAttribute('target', '_blank'),
+ });
+ });
+ });
+
+ describe('parsingConfig', () => {
+ it('emoji', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: ':)',
+ config: {
+ parsingConfig: { emoji: { emoticon: true } },
+ },
+ before: () => expect(getComponent()).toHaveTextContent(':)'),
+ after: () => expect(getComponent()).toHaveTextContent('😃'),
+ });
+ });
+
+ it('linkValidator', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: '[relative](/), [protocol](ftp://test)',
+ config: {
+ parsingConfig: {
+ linkValidator: { allowRelative: false, allowProtocols: ['ftp:'] },
+ },
+ },
+ before: () => expect(getLink()).toHaveTextContent('relative'),
+ after: () => expect(getLink()).toHaveTextContent('protocol'),
+ });
+ });
+ });
+ });
+});
diff --git a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts
index d0ebcb12d82..ab936b3fd08 100644
--- a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts
+++ b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/parsing_plugins.ts
@@ -31,39 +31,58 @@ import * as MarkdownCheckbox from '../markdown_checkbox';
import {
euiMarkdownLinkValidator,
EuiMarkdownLinkValidatorOptions,
+ DEFAULT_OPTIONS as LINK_VALIDATOR_DEFAULTS,
} from '../markdown_link_validator';
import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins';
export type DefaultEuiMarkdownParsingPlugins = PluggableList;
+export type DefaultParsingPluginsConfig = {
+ /**
+ * Allows enabling emoji rendering for emoticons such as :) and :(
+ * @default { emoticon: false }
+ */
+ emoji?: { emoticon?: boolean };
+ /**
+ * Allows configuring the `allowRelative` and `allowProtocols` of
+ * #EuiMarkdownLinkValidatorOptions
+ */
+ linkValidator?: EuiMarkdownLinkValidatorOptions;
+};
+
const DEFAULT_PARSING_PLUGINS: Record<
ExcludableDefaultPlugins,
DefaultEuiMarkdownParsingPlugins[0]
> = {
emoji: [emoji, { emoticon: false }],
lineBreaks: [breaks, {}],
- linkValidator: [
- euiMarkdownLinkValidator,
- {
- allowRelative: true,
- allowProtocols: ['https:', 'http:', 'mailto:'],
- } as EuiMarkdownLinkValidatorOptions,
- ],
+ linkValidator: [euiMarkdownLinkValidator, LINK_VALIDATOR_DEFAULTS],
checkbox: [MarkdownCheckbox.parser, {}],
tooltip: [MarkdownTooltip.parser, {}],
};
export const getDefaultEuiMarkdownParsingPlugins = ({
exclude,
-}: DefaultPluginsConfig = {}): DefaultEuiMarkdownParsingPlugins => {
+ ...parsingConfig
+}: DefaultPluginsConfig &
+ DefaultParsingPluginsConfig = {}): DefaultEuiMarkdownParsingPlugins => {
const parsingPlugins: PluggableList = [
[markdown, {}],
[highlight, {}],
];
Object.entries(DEFAULT_PARSING_PLUGINS).forEach(([pluginName, plugin]) => {
+ // Check for plugin exclusions
if (!exclude?.includes(pluginName as ExcludableDefaultPlugins)) {
- parsingPlugins.push(plugin);
+ // Check for plugin configuration overrides
+ if (pluginName in parsingConfig) {
+ parsingPlugins.push([
+ (plugin as any[])[0],
+ parsingConfig[pluginName as keyof DefaultParsingPluginsConfig],
+ ]);
+ } else {
+ parsingPlugins.push(plugin);
+ }
}
});
diff --git a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts
index 9a8c3001d61..d53e85c8b27 100644
--- a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts
+++ b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/plugins.ts
@@ -13,10 +13,12 @@ import {
import {
getDefaultEuiMarkdownParsingPlugins,
DefaultEuiMarkdownParsingPlugins,
+ type DefaultParsingPluginsConfig,
} from './parsing_plugins';
import {
getDefaultEuiMarkdownProcessingPlugins,
DefaultEuiMarkdownProcessingPlugins,
+ type DefaultProcessingPluginsConfig,
} from './processing_plugins';
export type ExcludableDefaultPlugins =
@@ -31,13 +33,27 @@ export type DefaultPluginsConfig =
| { exclude?: ExcludableDefaultPlugins[] };
export const getDefaultEuiMarkdownPlugins = (
- config?: DefaultPluginsConfig
+ config: DefaultPluginsConfig & {
+ processingConfig?: DefaultProcessingPluginsConfig;
+ parsingConfig?: DefaultParsingPluginsConfig;
+ uiConfig?: {}; // No customizations currently supported, but we may add this in the future
+ } = {}
): {
parsingPlugins: DefaultEuiMarkdownParsingPlugins;
processingPlugins: DefaultEuiMarkdownProcessingPlugins;
uiPlugins: DefaultEuiMarkdownUiPlugins;
-} => ({
- parsingPlugins: getDefaultEuiMarkdownParsingPlugins(config),
- processingPlugins: getDefaultEuiMarkdownProcessingPlugins(config),
- uiPlugins: getDefaultEuiMarkdownUiPlugins(config),
-});
+} => {
+ const { exclude, processingConfig, parsingConfig, uiConfig } = config;
+
+ return {
+ parsingPlugins: getDefaultEuiMarkdownParsingPlugins({
+ exclude,
+ ...parsingConfig,
+ }),
+ processingPlugins: getDefaultEuiMarkdownProcessingPlugins({
+ exclude,
+ ...processingConfig,
+ }),
+ uiPlugins: getDefaultEuiMarkdownUiPlugins({ exclude, ...uiConfig }),
+ };
+};
diff --git a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx
index 04c575d826a..e4aba601352 100644
--- a/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx
+++ b/packages/eui/src/components/markdown_editor/plugins/markdown_default_plugins/processing_plugins.tsx
@@ -29,7 +29,7 @@ import all from 'mdast-util-to-hast/lib/all';
import rehype2react from 'rehype-react';
import remark2rehype from 'remark-rehype';
-import { EuiLink } from '../../../link';
+import { EuiLink, EuiLinkProps } from '../../../link';
import { EuiCodeBlock, EuiCode } from '../../../code';
import { EuiHorizontalRule } from '../../../horizontal_rule';
@@ -53,6 +53,15 @@ export type DefaultEuiMarkdownProcessingPlugins = [
...PluggableList // any additional are generic
];
+export type DefaultProcessingPluginsConfig = {
+ /**
+ * Allows customizing all formatted links.
+ * Accepts any prop that [EuiLink](/#/navigation/link) or any anchor link tag accepts.
+ * Useful for, e.g. setting `target="_blank"` on all links
+ */
+ linkProps?: Partial