From 18b6ce1e80c88afeb39695072bb855cdf2066a03 Mon Sep 17 00:00:00 2001
From: Harpal Singh
Date: Tue, 27 Aug 2024 22:24:25 +0530
Subject: [PATCH 1/8] feat: [EuiMarkdownFormat]: Added Opetion to Open Link in
New Tab
---
.../markdown_editor/markdown_format.tsx | 27 ++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/packages/eui/src/components/markdown_editor/markdown_format.tsx b/packages/eui/src/components/markdown_editor/markdown_format.tsx
index c626f7027f1..521713fb77b 100644
--- a/packages/eui/src/components/markdown_editor/markdown_format.tsx
+++ b/packages/eui/src/components/markdown_editor/markdown_format.tsx
@@ -31,8 +31,29 @@ export type EuiMarkdownFormatProps = CommonProps &
* Determines the text size. Choose `relative` to control the `font-size` based on the value of a parent container.
*/
textSize?: EuiTextProps['size'];
+ /**
+ * Links target attribute. Set to `_blank` to open links in a new tab. Other values can be _blank|_self|_parent|_top|framename
+ */
+ linkTarget: string;
};
+const openLinkToNewTab = (target: string) => {
+ return () => {
+ return (tree: any) => {
+ const visit = (node: any) => {
+ if (node.tagName === 'a') {
+ node.properties = node.properties || {};
+ node.properties.target = target;
+ }
+ if (node.children) {
+ node.children.forEach(visit);
+ }
+ };
+ visit(tree);
+ };
+ }
+};
+
export const EuiMarkdownFormat: FunctionComponent = ({
children,
className,
@@ -40,10 +61,14 @@ export const EuiMarkdownFormat: FunctionComponent = ({
processingPluginList = defaultProcessingPlugins,
textSize = 'm',
color = 'default',
+ linkTarget = '',
...rest
}) => {
const processor = useMemo(
- () => unified().use(parsingPluginList).use(processingPluginList),
+ () => {
+ const result = unified().use(parsingPluginList).use(processingPluginList)
+ return (linkTarget === '') ? result : result.use(openLinkToNewTab(linkTarget));
+ },
[parsingPluginList, processingPluginList]
);
const result = useMemo(() => {
From 22f6d020f29095963156ce5a3f7dbfa046efc56e Mon Sep 17 00:00:00 2001
From: Harpal Singh
Date: Wed, 28 Aug 2024 00:56:16 +0530
Subject: [PATCH 2/8] feat(EuiMarkdownFormat): Open links in new tab
---
.../src/views/markdown_editor/markdown_format_links.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
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..0891f243172 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})
@@ -21,5 +21,5 @@ export const markdownContent = `**Links starting with http:, https:, mailto:, an
`;
export default () => {
- return {markdownContent} ;
+ return {markdownContent} ;
};
From 35fdbae545694347e8d18c797c22df4c03e2bb9b Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 08:35:41 -0700
Subject: [PATCH 3/8] Revert changes to EuiMarkdownFormat
---
.../markdown_editor/markdown_format_links.js | 2 +-
.../markdown_editor/markdown_format.tsx | 27 +------------------
2 files changed, 2 insertions(+), 27 deletions(-)
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 0891f243172..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
@@ -21,5 +21,5 @@ export const markdownContent = `**Links starting with http:, https:, mailto:, an
`;
export default () => {
- return {markdownContent} ;
+ return {markdownContent} ;
};
diff --git a/packages/eui/src/components/markdown_editor/markdown_format.tsx b/packages/eui/src/components/markdown_editor/markdown_format.tsx
index 521713fb77b..c626f7027f1 100644
--- a/packages/eui/src/components/markdown_editor/markdown_format.tsx
+++ b/packages/eui/src/components/markdown_editor/markdown_format.tsx
@@ -31,29 +31,8 @@ export type EuiMarkdownFormatProps = CommonProps &
* Determines the text size. Choose `relative` to control the `font-size` based on the value of a parent container.
*/
textSize?: EuiTextProps['size'];
- /**
- * Links target attribute. Set to `_blank` to open links in a new tab. Other values can be _blank|_self|_parent|_top|framename
- */
- linkTarget: string;
};
-const openLinkToNewTab = (target: string) => {
- return () => {
- return (tree: any) => {
- const visit = (node: any) => {
- if (node.tagName === 'a') {
- node.properties = node.properties || {};
- node.properties.target = target;
- }
- if (node.children) {
- node.children.forEach(visit);
- }
- };
- visit(tree);
- };
- }
-};
-
export const EuiMarkdownFormat: FunctionComponent = ({
children,
className,
@@ -61,14 +40,10 @@ export const EuiMarkdownFormat: FunctionComponent = ({
processingPluginList = defaultProcessingPlugins,
textSize = 'm',
color = 'default',
- linkTarget = '',
...rest
}) => {
const processor = useMemo(
- () => {
- const result = unified().use(parsingPluginList).use(processingPluginList)
- return (linkTarget === '') ? result : result.use(openLinkToNewTab(linkTarget));
- },
+ () => unified().use(parsingPluginList).use(processingPluginList),
[parsingPluginList, processingPluginList]
);
const result = useMemo(() => {
From b5c96b33a9e0c334fc753492a2e55baa0d7711e0 Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 11:09:36 -0700
Subject: [PATCH 4/8] [setup] Add optional config objs for default processing,
parsing, and UI plugins
+ Add missing EuiMarkdownFormat unit tests
+ bogart test file for testing rendered output of `getDefaultEuiMarkdownPlugins`
---
.../markdown_format.test.tsx.snap | 15 ++
.../markdown_editor/markdown_format.test.tsx | 151 ++++++++++++++++++
.../markdown_default_plugins/plugins.ts | 27 +++-
3 files changed, 187 insertions(+), 6 deletions(-)
create mode 100644 packages/eui/src/components/markdown_editor/__snapshots__/markdown_format.test.tsx.snap
create mode 100644 packages/eui/src/components/markdown_editor/markdown_format.test.tsx
diff --git a/packages/eui/src/components/markdown_editor/__snapshots__/markdown_format.test.tsx.snap b/packages/eui/src/components/markdown_editor/__snapshots__/markdown_format.test.tsx.snap
new file mode 100644
index 00000000000..161cf4eb8c1
--- /dev/null
+++ b/packages/eui/src/components/markdown_editor/__snapshots__/markdown_format.test.tsx.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EuiMarkdownFormat renders 1`] = `
+
+`;
diff --git a/packages/eui/src/components/markdown_editor/markdown_format.test.tsx b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
new file mode 100644
index 00000000000..66f21036583
--- /dev/null
+++ b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
@@ -0,0 +1,151 @@
+/*
+ * 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 React from 'react';
+import { render } from '../../test/rtl';
+import { shouldRenderCustomStyles } from '../../test/internal';
+import { requiredProps } from '../../test';
+
+import { EuiMarkdownFormat, getDefaultEuiMarkdownPlugins } from './index';
+
+describe('EuiMarkdownFormat', () => {
+ shouldRenderCustomStyles(test );
+
+ it('renders', () => {
+ const { container } = render(
+ **Hello world**
+ );
+
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ describe('props', () => {
+ test('color', () => {
+ const { getByTestSubject } = render(
+ <>
+
+ _Hello world_
+
+
+ ~Hello world~
+
+ >
+ );
+
+ expect(getByTestSubject('first')).toHaveStyle({
+ color: 'rgb(189, 39, 30)',
+ });
+ expect(getByTestSubject('second')).toHaveStyle({
+ color: '#ffffff',
+ });
+ });
+
+ test('textSize', () => {
+ const { getByTestSubject } = render(
+ <>
+
+ _Hello world_
+
+
+ ~Hello world~
+
+ >
+ );
+
+ expect(getByTestSubject('first')).toHaveStyle({
+ 'font-size': '0.8571rem',
+ });
+ expect(getByTestSubject('second')).toHaveStyle({
+ 'font-size': '1em',
+ });
+ });
+ });
+
+ describe('plugins config', () => {
+ // Test utils
+ const getComponent = () => document.querySelector('.euiMarkdownFormat')!;
+ const getLink = () => getComponent().querySelector('.euiLink');
+ const getCheckbox = () => getComponent().querySelector('.euiCheckbox');
+ const getToolTip = () => getComponent().querySelector('.euiToolTipAnchor');
+
+ const assertMarkdownBeforeAndAfter = (args: {
+ markdown: string;
+ config: Parameters[0];
+ before: Function;
+ after: Function;
+ }) => {
+ const { markdown, config, before, after } = args;
+
+ const { rerender } = render(
+ {markdown}
+ );
+ before();
+
+ const { processingPlugins, parsingPlugins } =
+ getDefaultEuiMarkdownPlugins(config);
+ rerender(
+
+ {markdown}
+
+ );
+
+ after();
+ };
+
+ describe('exclude', () => {
+ test('tooltip', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: '!{tooltip[text](help)}',
+ config: { exclude: ['tooltip'] },
+ before: () => expect(getToolTip()).toBeInTheDocument(),
+ after: () => expect(getToolTip()).not.toBeInTheDocument(),
+ });
+ });
+
+ test('checkbox', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: '- [ ] TODO',
+ config: { exclude: ['checkbox'] },
+ before: () => expect(getCheckbox()).toBeInTheDocument(),
+ after: () => expect(getCheckbox()).not.toBeInTheDocument(),
+ });
+ });
+
+ test('emoji', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: ':smile:',
+ config: { exclude: ['emoji'] },
+ before: () => expect(getComponent()).toHaveTextContent('😄'),
+ after: () => expect(getComponent()).toHaveTextContent(':smile:'),
+ });
+ });
+
+ test('linkValidator', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: '[Sus link](file://)',
+ config: { exclude: ['linkValidator'] },
+ before: () => expect(getLink()).not.toBeInTheDocument(),
+ after: () => expect(getLink()).toBeInTheDocument(),
+ });
+ });
+
+ test('lineBreaks', () => {
+ assertMarkdownBeforeAndAfter({
+ markdown: `One
+ Two`,
+ config: { exclude: ['lineBreaks'] },
+ before: () => expect(getComponent().innerHTML).toContain(' '),
+ after: () => expect(getComponent().innerHTML).not.toContain(' '),
+ });
+ });
+ });
+ });
+});
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..c66591e7afb 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
@@ -31,13 +31,28 @@ export type DefaultPluginsConfig =
| { exclude?: ExcludableDefaultPlugins[] };
export const getDefaultEuiMarkdownPlugins = (
- config?: DefaultPluginsConfig
+ config: DefaultPluginsConfig & {
+ // TODO
+ processingConfig?: {};
+ parsingConfig?: {};
+ uiConfig?: {};
+ } = {}
): {
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 }),
+ };
+};
From 8ef7273c538f359a92794c42450cd23c0ebb7a67 Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 11:40:33 -0700
Subject: [PATCH 5/8] Add `processingConfig.linkProps` config
- will allow passing `target: "_blank"` configs and more (e.g. rel, disabled, color, etc)
---
.../markdown_editor/markdown_format.test.tsx | 13 +++++++++++++
.../markdown_default_plugins/plugins.ts | 4 ++--
.../processing_plugins.tsx | 19 ++++++++++++++++---
3 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/packages/eui/src/components/markdown_editor/markdown_format.test.tsx b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
index 66f21036583..78a33e5ccd6 100644
--- a/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
+++ b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
@@ -147,5 +147,18 @@ describe('EuiMarkdownFormat', () => {
});
});
});
+
+ 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'),
+ });
+ });
+ });
});
});
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 c66591e7afb..46ed10a6739 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
@@ -17,6 +17,7 @@ import {
import {
getDefaultEuiMarkdownProcessingPlugins,
DefaultEuiMarkdownProcessingPlugins,
+ type DefaultProcessingPluginsConfig,
} from './processing_plugins';
export type ExcludableDefaultPlugins =
@@ -32,8 +33,7 @@ export type DefaultPluginsConfig =
export const getDefaultEuiMarkdownPlugins = (
config: DefaultPluginsConfig & {
- // TODO
- processingConfig?: {};
+ processingConfig?: DefaultProcessingPluginsConfig;
parsingConfig?: {};
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;
+};
+
const DEFAULT_COMPONENT_RENDERERS: Partial<
Record>
> = {
@@ -62,7 +71,9 @@ const DEFAULT_COMPONENT_RENDERERS: Partial<
export const getDefaultEuiMarkdownProcessingPlugins = ({
exclude,
-}: DefaultPluginsConfig = {}): DefaultEuiMarkdownProcessingPlugins => {
+ linkProps,
+}: DefaultPluginsConfig &
+ DefaultProcessingPluginsConfig = {}): DefaultEuiMarkdownProcessingPlugins => {
const componentPluginsWithExclusions: Rehype2ReactOptions['components'] = {};
Object.entries(DEFAULT_COMPONENT_RENDERERS).forEach(
@@ -89,7 +100,9 @@ export const getDefaultEuiMarkdownProcessingPlugins = ({
createElement,
Fragment,
components: {
- a: EuiLink,
+ a: (props: any) => {
+ return ;
+ },
code: (props: any) =>
// If there are linebreaks use codeblock, otherwise code
/\r|\n/.exec(props.children) ||
From 037874eeef8fa62acd49b32ca780fdc2f5c13c75 Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 13:28:30 -0700
Subject: [PATCH 6/8] Add `parsingConfig` & allow overriding parsing plugin
defaults
---
.../markdown_editor/markdown_format.test.tsx | 26 +++++++++++++++
.../parsing_plugins.ts | 33 ++++++++++++++-----
.../markdown_default_plugins/plugins.ts | 5 +--
.../plugins/markdown_link_validator.tsx | 14 ++++++--
4 files changed, 64 insertions(+), 14 deletions(-)
diff --git a/packages/eui/src/components/markdown_editor/markdown_format.test.tsx b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
index 78a33e5ccd6..354534a84fa 100644
--- a/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
+++ b/packages/eui/src/components/markdown_editor/markdown_format.test.tsx
@@ -160,5 +160,31 @@ describe('EuiMarkdownFormat', () => {
});
});
});
+
+ 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..d0e5e8598c1 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,54 @@ 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 = Partial<
+ // We may eventually add more optional configuration options for more default plugins
+ Record
+> & {
+ // But for now, these are the ones that have typed configurations
+ emoji?: { emoticon?: boolean };
+ 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 46ed10a6739..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,6 +13,7 @@ import {
import {
getDefaultEuiMarkdownParsingPlugins,
DefaultEuiMarkdownParsingPlugins,
+ type DefaultParsingPluginsConfig,
} from './parsing_plugins';
import {
getDefaultEuiMarkdownProcessingPlugins,
@@ -34,8 +35,8 @@ export type DefaultPluginsConfig =
export const getDefaultEuiMarkdownPlugins = (
config: DefaultPluginsConfig & {
processingConfig?: DefaultProcessingPluginsConfig;
- parsingConfig?: {};
- uiConfig?: {};
+ parsingConfig?: DefaultParsingPluginsConfig;
+ uiConfig?: {}; // No customizations currently supported, but we may add this in the future
} = {}
): {
parsingPlugins: DefaultEuiMarkdownParsingPlugins;
diff --git a/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx b/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
index 4f23051fd13..59bd92716a0 100644
--- a/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
+++ b/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
@@ -17,10 +17,15 @@ interface LinkOrTextNode {
}
export interface EuiMarkdownLinkValidatorOptions {
- allowRelative: boolean;
- allowProtocols: string[];
+ allowRelative?: boolean;
+ allowProtocols?: string[];
}
+export const DEFAULT_OPTIONS = {
+ allowRelative: true,
+ allowProtocols: ['https:', 'http:', 'mailto:'],
+};
+
export function euiMarkdownLinkValidator(
options: EuiMarkdownLinkValidatorOptions
) {
@@ -57,7 +62,10 @@ export function mutateLinkToText(node: LinkOrTextNode) {
export function validateUrl(
url: string,
- { allowRelative, allowProtocols }: EuiMarkdownLinkValidatorOptions
+ {
+ allowRelative = DEFAULT_OPTIONS.allowRelative,
+ allowProtocols = DEFAULT_OPTIONS.allowProtocols,
+ }: EuiMarkdownLinkValidatorOptions
) {
// relative captures both relative paths `/` and protocols `//`
const isRelative = url.startsWith('/');
From 30fdb8a70ff0523c12a0c9a9a2cb0a8d17f59bea Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 14:52:52 -0700
Subject: [PATCH 7/8] [docs] Documentation + prop docs pass
- replace link validation example with this one, since it's now a better way of configuring the linkValidator plugin
- add more defaults to props docs
- clean up sidebar links/headings for Markdown plugins page
---
.../markdown_link_validation.js | 37 ------
.../markdown_plugin_config.tsx | 37 ++++++
.../markdown_plugin_example.js | 123 ++++++++++--------
.../markdown_editor/markdown_plugin_props.tsx | 27 ++++
.../parsing_plugins.ts | 14 +-
.../plugins/markdown_link_validator.tsx | 12 +-
6 files changed, 154 insertions(+), 96 deletions(-)
delete mode 100644 packages/eui/src-docs/src/views/markdown_editor/markdown_link_validation.js
create mode 100644 packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_config.tsx
create mode 100644 packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_props.tsx
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 () => (
-
- {markdown}
-
-);
diff --git a/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_config.tsx b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_config.tsx
new file mode 100644
index 00000000000..713887aef24
--- /dev/null
+++ b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_config.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+
+import {
+ EuiMarkdownFormat,
+ getDefaultEuiMarkdownPlugins,
+} from '../../../../src';
+
+export const markdownContent = `
+- :cry: Automatic emoji formatting has been excluded from this markdown.
+- In the example below, only \`https:\` and \`mailto:\` protocols should turn into links.
+- Links should open in a new tab.
+
+https://elastic.co
+http://elastic.co
+someone@elastic.co
+`;
+
+export default () => {
+ const { processingPlugins, parsingPlugins } = getDefaultEuiMarkdownPlugins({
+ exclude: ['emoji'],
+ processingConfig: {
+ linkProps: { target: '_blank' },
+ },
+ parsingConfig: {
+ linkValidator: { allowProtocols: ['https:', 'mailto:'] },
+ },
+ });
+
+ return (
+
+ {markdownContent}
+
+ );
+};
diff --git a/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_example.js
index b839bfbdb34..eb362e4e53e 100644
--- a/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_example.js
+++ b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_example.js
@@ -1,4 +1,5 @@
-import React, { Fragment } from 'react';
+import React from 'react';
+import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';
@@ -14,13 +15,18 @@ import {
EuiLink,
} from '../../../../src/components';
-import { Link } from 'react-router-dom';
+import {
+ DefaultPluginsConfig,
+ DefaultParsingPluginsConfig,
+ DefaultProcessingPluginsConfig,
+ EuiMarkdownLinkValidatorOptions,
+} from './markdown_plugin_props';
import MarkdownEditorWithPlugins from './markdown_editor_with_plugins';
const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins');
-const linkValidationSource = require('!!raw-loader!./markdown_link_validation');
-import LinkValidation from './markdown_link_validation';
+const pluginConfigSource = require('!!raw-loader!./markdown_plugin_config');
+import PluginConfig from './markdown_plugin_config';
const pluginSnippet = `getDefaultEuiMarkdownParsingPlugins,{' '}
getDefaultEuiMarkdownProcessingPlugins , and{' '}
- getDefaultEuiMarkdownUiPlugins respectively. Each
- of these three functions take an optional configuration object with
- an exclude key, an array of EUI-defaulted plugins
- to disable. Currently the only option this configuration can take is{' '}
- 'tooltip' .
+ getDefaultEuiMarkdownUiPlugins respectively.
>
),
@@ -306,56 +308,73 @@ export const MarkdownPluginExample = {
source: [
{
type: GuideSectionTypes.JS,
- code: linkValidationSource,
+ code: pluginConfigSource,
},
],
- title: 'Link validation & security',
+ title: 'Configuring the default plugins',
text: (
-
+ <>
- To enhance user and application security, the default behavior
- removes links to URLs that aren't relative (beginning with{' '}
- / ) and don't use the{' '}
- https: , http: , or{' '}
- mailto: protocols. This validation can be further
- configured or removed altogether.
+ The above plugin utils, as well as{' '}
+ getDefaultEuiMarkdownPlugins , accept an optional
+ configuration object of:
+
+
+ exclude : an array of default plugins to{' '}
+
+ unregister
+
+
+
+ parsingConfig : allows overriding the
+ configuration of any default parsing plugin
+
+
+ processingConfig : currently only accepts a{' '}
+ linkProps key, which accepts any prop that{' '}
+ EuiLink accepts
+
+
- In this example only https: and{' '}
- mailto: links are allowed.
+ The below example has the emoji plugin excluded,
+ and custom configuration on the link validator parsing plugin and
+ link processing plugin. See the Props table for all
+ plugin config options.
-
+ >
),
- snippet: [
- `// customize what link protocols are allowed
-const parsingPlugins = [
- ...getDefaultEuiMarkdownParsingPlugins({
- // Exclude the default validation plugin - we're configuring our own
- exclude: ['linkValidator'],
- }),
- [
- euiMarkdownLinkValidator,
- {
- // Customize what link protocols are allowed
- allowProtocols: ['https:', 'mailto:'],
- },
- ]
-];
+ snippet: `const { parsingPlugins, processingPlugins } = getDefaultEuiMarkdownPlugins({
+ // Exclude plugins as necessary
+ exclude: ['emoji'],
+ parsingConfig: {
+ // Customize what link protocols are allowed
+ linkValidator: { allowProtocols: ['https:', 'mailto:'] },
+ },
+ processingConfig: {
+ // Configure all links to open in new tabs/windows
+ linkProps: { target: '_blank' },
+ },
+});
-// Pass the customized parsing plugins to your markdown component
-
-`,
- ],
- demo: ,
+// Pass the customized plugins to your markdown component
+ `,
+ demo: ,
+ props: {
+ DefaultPluginsConfig,
+ DefaultParsingPluginsConfig,
+ DefaultProcessingPluginsConfig,
+ EuiMarkdownLinkValidatorOptions,
+ },
},
{
+ title: 'Plugin development',
wrapText: false,
text: (
<>
-
- Plugin development
-
-
An EuiMarkdown plugin is comprised of three major
@@ -374,7 +393,7 @@ const parsingPlugins = [
/>
- uiPlugin
+ uiPlugin
@@ -388,11 +407,11 @@ const parsingPlugins = [
/>
- parsingPluginList
+ parsingPluginList
-
+ <>
-
+ >
- processingPluginList
+ processingPluginList
@@ -533,7 +552,7 @@ processingList[1][1].components.emojiPlugin = EmojiMarkdownRenderer;`}
],
title: 'Putting it all together: a simple chart plugin',
text: (
-
+ <>
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{' '}
uiPlugins prop.
-
+ >
),
props: {
EuiMarkdownEditor,
diff --git a/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_props.tsx b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_props.tsx
new file mode 100644
index 00000000000..5f15f38ab5d
--- /dev/null
+++ b/packages/eui/src-docs/src/views/markdown_editor/markdown_plugin_props.tsx
@@ -0,0 +1,27 @@
+import { FunctionComponent } from 'react';
+
+import type {
+ ExcludableDefaultPlugins,
+ DefaultParsingPluginsConfig as DefaultParsingPluginsConfigProps,
+ DefaultProcessingPluginsConfig as DefaultProcessingPluginsConfigProps,
+} from '../../../../src/components/markdown_editor/plugins/markdown_default_plugins';
+
+import type { EuiMarkdownLinkValidatorOptions as EuiMarkdownLinkValidatorOptionsProps } from '../../../../src/components/markdown_editor';
+
+export const DefaultPluginsConfig: FunctionComponent<{
+ exclude?: ExcludableDefaultPlugins;
+ parsingConfig?: DefaultParsingPluginsConfigProps;
+ processingConfig?: DefaultProcessingPluginsConfigProps;
+}> = () => null;
+
+export const DefaultParsingPluginsConfig: FunctionComponent<
+ DefaultParsingPluginsConfigProps
+> = () => null;
+
+export const DefaultProcessingPluginsConfig: FunctionComponent<
+ DefaultProcessingPluginsConfigProps
+> = () => null;
+
+export const EuiMarkdownLinkValidatorOptions: FunctionComponent<
+ EuiMarkdownLinkValidatorOptionsProps
+> = () => null;
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 d0e5e8598c1..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
@@ -37,12 +37,16 @@ import type { ExcludableDefaultPlugins, DefaultPluginsConfig } from './plugins';
export type DefaultEuiMarkdownParsingPlugins = PluggableList;
-export type DefaultParsingPluginsConfig = Partial<
- // We may eventually add more optional configuration options for more default plugins
- Record
-> & {
- // But for now, these are the ones that have typed configurations
+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;
};
diff --git a/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx b/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
index 59bd92716a0..d621fd488c5 100644
--- a/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
+++ b/packages/eui/src/components/markdown_editor/plugins/markdown_link_validator.tsx
@@ -16,10 +16,18 @@ interface LinkOrTextNode {
children?: Array<{ value: string }>;
}
-export interface EuiMarkdownLinkValidatorOptions {
+export type EuiMarkdownLinkValidatorOptions = {
+ /**
+ * Allow or disallow relative links (links that begin with a `/`)
+ * @default true
+ */
allowRelative?: boolean;
+ /**
+ * Allow or disallow specific [URL protocols or schemes](https://developer.mozilla.org/en-US/docs/Web/URI/Schemes)
+ * @default ['https:', 'http:', 'mailto:']
+ */
allowProtocols?: string[];
-}
+};
export const DEFAULT_OPTIONS = {
allowRelative: true,
From 186393fd4e6747d526e269e4a8a62b718dcfe5b9 Mon Sep 17 00:00:00 2001
From: Cee Chen
Date: Wed, 28 Aug 2024 14:58:14 -0700
Subject: [PATCH 8/8] changelog
---
packages/eui/changelogs/upcoming/7985.md | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 packages/eui/changelogs/upcoming/7985.md
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