From 4a4eaeabc3dcd93042f5082357c4af6bee9e354f Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 3 Jul 2020 15:01:39 -0700 Subject: [PATCH 01/17] docs skeleton --- .../markdown_editor/mardown_editor_example.js | 92 ++++++++++++++++++- .../views/markdown_editor/markdown_editor.js | 12 --- .../markdown_editor_with_plugins.js | 67 ++++++++++++++ .../views/markdown_editor/markdown_format.js | 30 ++++++ src/components/index.js | 5 +- src/components/markdown_editor/_index.scss | 3 +- .../markdown_editor/_markdown_format.scss | 48 ++++------ src/components/markdown_editor/index.ts | 10 +- .../markdown_default_plugins.tsx | 72 +++++++++++++++ .../markdown_editor/markdown_editor.tsx | 60 ++---------- .../markdown_editor/markdown_format.tsx | 15 ++- .../plugins/markdown_tooltip.scss | 4 + .../plugins/markdown_tooltip.tsx | 15 ++- 13 files changed, 319 insertions(+), 114 deletions(-) create mode 100644 src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js create mode 100644 src-docs/src/views/markdown_editor/markdown_format.js create mode 100644 src/components/markdown_editor/markdown_default_plugins.tsx create mode 100644 src/components/markdown_editor/plugins/markdown_tooltip.scss diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index c6744de6bb0..0cc3849e23c 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -1,18 +1,73 @@ -import React from 'react'; +import React, { Fragment } from 'react'; import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; -import { EuiMarkdownEditor } from '../../../../src/components'; +import { + EuiMarkdownEditor, + EuiMarkdownFormat, + EuiText, + EuiSpacer, +} from '../../../../src/components'; + +import { Link } from 'react-router-dom'; import MarkdownEditor from './markdown_editor'; const markdownEditorSource = require('!!raw-loader!./markdown_editor'); const markdownEditorHtml = renderToHtml(MarkdownEditor); +import MarkdownFormat from './markdown_format'; +const markdownFormatSource = require('!!raw-loader!./markdown_format'); +const markdownFormatHtml = renderToHtml(MarkdownFormat); + +import MarkdownEditorWithPlugins from './markdown_editor_with_plugins'; +const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins'); +const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); + export const MarkdownEditorExample = { - title: 'Markdown Editor', + title: 'Markdown', + intro: ( + + +

+ EUI provides components to both edit and render markdown-like content + with dynamic previews. The components, built on top of the{' '} + Unified{' '} + framework, are extendible through an optional plugin layer that allows + for translating additional string syntax into React renders. +

+
+ +
+ ), sections: [ + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownFormatSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownFormatHtml, + }, + ], + title: 'Markdown format', + text: ( +

+ EuiMarkdownFormat is a wrapper that will render + Markdown provided. EuiMarkdownFormat uses{' '} + Remark by + default, though you could replace it with your own processor if you + are feeling adventurous. +

+ ), + props: { + EuiMarkdownFormat, + }, + demo: , + }, { source: [ { @@ -24,10 +79,13 @@ export const MarkdownEditorExample = { code: markdownEditorHtml, }, ], + title: 'Markdown editor', text: (

- This component renders a markdown editor, including buttons for - quickly inserting common markdown elements and a preview mode. + EuiMarkdownEditor provides a markdown authoring + experience for the user. This component consists of a toolbar, text + area, and optionally a drag-and-drop zone to accept files. It can be + toggled by the user between editing and preview modes

), props: { @@ -35,5 +93,29 @@ export const MarkdownEditorExample = { }, demo: , }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownEditorWithPluginsSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownEditorWithPluginsHtml, + }, + ], + title: 'Markdown plugins', + text: ( +

+ Both EuiMarkdownEditor and{' '} + EuiMarkdownFormat can utilize additional plugins to a + syntax to react render pipeline. +

+ ), + props: { + EuiMarkdownEditor, + }, + demo: , + }, ], }; diff --git a/src-docs/src/views/markdown_editor/markdown_editor.js b/src-docs/src/views/markdown_editor/markdown_editor.js index 2dd2ce8ffc7..5b19d27c06a 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor.js +++ b/src-docs/src/views/markdown_editor/markdown_editor.js @@ -1,23 +1,14 @@ import React, { useCallback, useState } from 'react'; import { - defaultParsingPlugins, - defaultProcessingPlugins, EuiMarkdownEditor, EuiSpacer, EuiCodeBlock, EuiButtonToggle, } from '../../../../src'; -import * as MarkdownChart from './plugins/markdown_chart'; const markdownExample = require('!!raw-loader!./markdown-example.md'); -const exampleParsingList = [...defaultParsingPlugins, MarkdownChart.parser]; - -const exampleProcessingList = [...defaultProcessingPlugins]; // pretend mutation doesn't happen immediately next 😅 -exampleProcessingList[0][1].handlers.chartDemoPlugin = MarkdownChart.handler; -exampleProcessingList[1][1].components.chartDemoPlugin = MarkdownChart.renderer; - export default () => { const [value, setValue] = useState(markdownExample); const [messages, setMessages] = useState([]); @@ -34,9 +25,6 @@ export default () => { value={value} onChange={setValue} height={400} - uiPlugins={[MarkdownChart.plugin]} - parsingPluginList={exampleParsingList} - processingPluginList={exampleProcessingList} onParse={onParse} errors={messages} /> diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js new file mode 100644 index 00000000000..b689f53b66a --- /dev/null +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -0,0 +1,67 @@ +import React, { useCallback, useState } from 'react'; + +import { + EuiMarkdownDefaultParsingPlugins, + EuiMarkdownDefaultProcessingPlugins, + EuiMarkdownEditor, + EuiSpacer, + EuiCodeBlock, + EuiButtonToggle, +} from '../../../../src'; +import * as MarkdownChart from './plugins/markdown_chart'; + +const exampleParsingList = [ + ...EuiMarkdownDefaultParsingPlugins, + MarkdownChart.parser, +]; + +const exampleProcessingList = [...EuiMarkdownDefaultProcessingPlugins]; // pretend mutation doesn't happen immediately next 😅 +exampleProcessingList[0][1].handlers.chartDemoPlugin = MarkdownChart.handler; +exampleProcessingList[1][1].components.chartDemoPlugin = MarkdownChart.renderer; + +const initialExample = `## Chart plugin + +Notice the toolbar above has a new chart button. Click it to add a chart. + +Once you finish, it'll add some syntax that looks like the below. + +!{chart{"palette":4,"height":300}} +`; + +export default () => { + const [value, setValue] = useState(initialExample); + const [messages, setMessages] = useState([]); + const [ast, setAst] = useState(null); + const [isAstShowing, setIsAstShowing] = useState(false); + const onParse = useCallback((err, { messages, ast }) => { + setMessages(err ? [err] : messages); + setAst(JSON.stringify(ast, null, 2)); + }, []); + return ( + <> + + +
+ setIsAstShowing(!isAstShowing)} + isSelected={isAstShowing} + /> +
+ {isAstShowing && {ast}} + + ); +}; diff --git a/src-docs/src/views/markdown_editor/markdown_format.js b/src-docs/src/views/markdown_editor/markdown_format.js new file mode 100644 index 00000000000..a5b58e00a42 --- /dev/null +++ b/src-docs/src/views/markdown_editor/markdown_format.js @@ -0,0 +1,30 @@ +import React from 'react'; + +import { EuiMarkdownFormat } from '../../../../src'; + +const markdownContent = `Beyond Remark's base syntax, **EuiMarkdownFormat** bundles these abilities by default: + +\`:smile:\` we support emojis :smile:! + +\`!{tooltip[anchor text](Tooltip content)}\` syntax can render !{tooltip[tooltips like this](I am Jack's helpful tooltip content)} + +We also support checkboxes so that + +\`\`\` +- [ ] Checkboxes +- [x] Can be filled +- [ ] Or empty +\`\`\` + +turns into + +- [ ] Checkboxes +- [x] Can be filled +- [ ] Or empty + +Note that you'll need to use *EuiMarkdownEditor* to make those checkboxes dynamic. +`; + +export default () => { + return {markdownContent}; +}; diff --git a/src/components/index.js b/src/components/index.js index 4add95e006d..34556c5f1c9 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -217,8 +217,9 @@ export { export { EuiMarkdownEditor, EuiMarkdownContext, - defaultProcessingPlugins, - defaultParsingPlugins, + EuiMarkdownFormat, + EuiMarkdownDefaultParsingPlugins, + EuiMarkdownDefaultProcessingPlugins, } from './markdown_editor'; export { EuiMark } from './mark'; diff --git a/src/components/markdown_editor/_index.scss b/src/components/markdown_editor/_index.scss index 194529e7f04..dd5dfac5390 100644 --- a/src/components/markdown_editor/_index.scss +++ b/src/components/markdown_editor/_index.scss @@ -4,4 +4,5 @@ @import 'markdown_editor_footer'; @import 'markdown_editor_preview'; @import 'markdown_editor_text_area'; -@import 'markdown_editor_toolbar'; \ No newline at end of file +@import 'markdown_editor_toolbar'; +@import 'plugins/markdown_tooltip'; diff --git a/src/components/markdown_editor/_markdown_format.scss b/src/components/markdown_editor/_markdown_format.scss index 51a3cd25244..8aecdb7d956 100644 --- a/src/components/markdown_editor/_markdown_format.scss +++ b/src/components/markdown_editor/_markdown_format.scss @@ -12,8 +12,8 @@ $browserDefaultFontSize: 16px; -// We're setting a function o transform px in em -// because it's easier to think in px +// We're setting a function o transform px in em +// because it's easier to think in px @function em($pixels, $context: $browserDefaultFontSize) { @return #{$pixels/$context}em; } @@ -22,7 +22,7 @@ $browserDefaultFontSize: 16px; @include euiFont; @include euiText; - // We're using `em` values to support apps where consumers might adjust sizes + // We're using `em` values to support apps where consumers might adjust sizes // and consequently the markdown needs to adjust to these changes // Font size variables @@ -41,7 +41,7 @@ $browserDefaultFontSize: 16px; $euiMarkdownSize: em(16px); $euiMarkdownSizeL: em(24px); - // We're using alpha values to support apps that + // We're using alpha values to support apps that // display markdown on backgrounds of various colors // Grayscale variables @@ -58,23 +58,33 @@ $browserDefaultFontSize: 16px; color: $euiColorLightestShade; } - > *:first-child { + > div > *:first-child { // sass-lint:disable-block no-important margin-top: 0 !important; } - > *:last-child { + > div > * { + margin-top: 0; + margin-bottom: $euiMarkdownSize; + } + + > div > *:last-child, + .euiCheckbox { // sass-lint:disable-block no-important margin-bottom: 0 !important; } + .euiCheckbox + *:not(.euiCheckbox) { + margin-top: $euiMarkdownSize; + } + p, blockquote, ul, ol, dl, - table, - pre { + pre, + table { margin-top: 0; margin-bottom: $euiMarkdownSize; line-height: 1.5em; @@ -263,26 +273,4 @@ $browserDefaultFontSize: 16px; background-color: transparent; border-top: 1px solid $euiMarkdownAlphaLightShade; } - - // 8. Code - // the markdown editor adds a EuiCodeBlock when consumers specify the language - // when no language is specified it gets the .euiMarkdownFormat__code styles - &__code { - @include euiCodeFont; - color: $euiCodeBlockColor; - font-size: $euiMarkdownFontSizeXS; - padding: $euiMarkdownSizeXXS 0; - margin-bottom: $euiMarkdownSizeXS; - background-color: $euiMarkdownAlphaLightestShade; - } - - // default styles for code blocks - pre &__code { - display: block; - padding: $euiMarkdownFontSizeL; - overflow: visible; - line-height: inherit; - word-wrap: normal; - white-space: pre; - } } diff --git a/src/components/markdown_editor/index.ts b/src/components/markdown_editor/index.ts index 49cda3bc240..6e35d3a0713 100644 --- a/src/components/markdown_editor/index.ts +++ b/src/components/markdown_editor/index.ts @@ -17,10 +17,10 @@ * under the License. */ +export { EuiMarkdownEditor, EuiMarkdownEditorProps } from './markdown_editor'; export { - EuiMarkdownEditor, - EuiMarkdownEditorProps, - defaultParsingPlugins, - defaultProcessingPlugins, -} from './markdown_editor'; + EuiMarkdownDefaultParsingPlugins, + EuiMarkdownDefaultProcessingPlugins, +} from './markdown_default_plugins'; export { EuiMarkdownContext } from './markdown_context'; +export { EuiMarkdownFormat } from './markdown_format'; diff --git a/src/components/markdown_editor/markdown_default_plugins.tsx b/src/components/markdown_editor/markdown_default_plugins.tsx new file mode 100644 index 00000000000..07f31ec6ad9 --- /dev/null +++ b/src/components/markdown_editor/markdown_default_plugins.tsx @@ -0,0 +1,72 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluggableList } from 'unified'; +// @ts-ignore TODO +import remark2rehype from 'remark-rehype'; +import rehype2react from 'rehype-react'; +import * as MarkdownTooltip from './plugins/markdown_tooltip'; +import * as MarkdownCheckbox from './plugins/markdown_checkbox'; +import React, { createElement } from 'react'; +import { EuiLink } from '../link'; +import { EuiCodeBlock, EuiCode } from '../code'; +import markdown from 'remark-parse'; +// @ts-ignore TODO +import highlight from 'remark-highlight.js'; +// @ts-ignore TODO +import emoji from 'remark-emoji'; + +export const EuiMarkdownDefaultParsingPlugins: PluggableList = [ + [markdown, {}], + [highlight, {}], + [emoji, { emoticon: true }], + [MarkdownTooltip.parser, {}], + [MarkdownCheckbox.parser, {}], +]; + +export const EuiMarkdownDefaultProcessingPlugins: PluggableList = [ + [ + remark2rehype, + { + allowDangerousHtml: true, + handlers: { + tooltipPlugin: MarkdownTooltip.handler, + checkboxPlugin: MarkdownCheckbox.handler, + }, + }, + ], + [ + rehype2react, + { + createElement: createElement, + components: { + a: EuiLink, + code: (props: any) => + // If there are linebreaks use codeblock, otherwise code + /\r|\n/.exec(props.children) ? ( + + ) : ( + + ), + tooltipPlugin: MarkdownTooltip.renderer, + checkboxPlugin: MarkdownCheckbox.renderer, + }, + }, + ], +]; diff --git a/src/components/markdown_editor/markdown_editor.tsx b/src/components/markdown_editor/markdown_editor.tsx index 4318934ad70..ae3a0bd0e01 100644 --- a/src/components/markdown_editor/markdown_editor.tsx +++ b/src/components/markdown_editor/markdown_editor.tsx @@ -32,15 +32,6 @@ import React, { import unified, { PluggableList, Processor } from 'unified'; import { VFileMessage } from 'vfile-message'; import classNames from 'classnames'; -// @ts-ignore TODO -import emoji from 'remark-emoji'; -import markdown from 'remark-parse'; -// @ts-ignore TODO -import remark2rehype from 'remark-rehype'; -// @ts-ignore TODO -import highlight from 'remark-highlight.js'; -import rehype2react from 'rehype-react'; - import { CommonProps, OneOf } from '../common'; import MarkdownActions, { insertText } from './markdown_actions'; import { EuiMarkdownEditorToolbar } from './markdown_editor_toolbar'; @@ -48,54 +39,16 @@ import { EuiMarkdownEditorTextArea } from './markdown_editor_text_area'; import { EuiMarkdownFormat } from './markdown_format'; import { EuiMarkdownEditorDropZone } from './markdown_editor_drop_zone'; import { htmlIdGenerator } from '../../services/accessibility'; -import { EuiLink } from '../link'; -import { EuiCodeBlock } from '../code'; import { MARKDOWN_MODE, MODE_EDITING, MODE_VIEWING } from './markdown_modes'; import { EuiMarkdownEditorUiPlugin } from './markdown_types'; import { EuiOverlayMask } from '../overlay_mask'; import { EuiModal } from '../modal'; import { ContextShape, EuiMarkdownContext } from './markdown_context'; import * as MarkdownTooltip from './plugins/markdown_tooltip'; -import * as MarkdownCheckbox from './plugins/markdown_checkbox'; - -export const defaultParsingPlugins: PluggableList = [ - [markdown, {}], - [highlight, {}], - [emoji, { emoticon: true }], - [MarkdownTooltip.parser, {}], - [MarkdownCheckbox.parser, {}], -]; - -export const defaultProcessingPlugins: PluggableList = [ - [ - remark2rehype, - { - allowDangerousHtml: true, - handlers: { - tooltipPlugin: MarkdownTooltip.handler, - checkboxPlugin: MarkdownCheckbox.handler, - }, - }, - ], - [ - rehype2react, - { - createElement: createElement, - components: { - a: EuiLink, - code: (props: any) => - // if has classNames is a codeBlock using highlight js - props.className ? ( - - ) : ( - - ), - tooltipPlugin: MarkdownTooltip.renderer, - checkboxPlugin: MarkdownCheckbox.renderer, - }, - }, - ], -]; +import { + EuiMarkdownDefaultParsingPlugins, + EuiMarkdownDefaultProcessingPlugins, +} from './markdown_default_plugins'; type CommonMarkdownEditorProps = HTMLAttributes & CommonProps & { @@ -148,8 +101,8 @@ export const EuiMarkdownEditor: FunctionComponent< value, onChange, height = 150, - parsingPluginList = defaultParsingPlugins, - processingPluginList = defaultProcessingPlugins, + parsingPluginList = EuiMarkdownDefaultParsingPlugins, + processingPluginList = EuiMarkdownDefaultProcessingPlugins, uiPlugins = [], onParse, errors = [], @@ -288,6 +241,7 @@ export const EuiMarkdownEditor: FunctionComponent< () => ({ textarea: textareaRef.current, replaceNode }), [replaceNode] ); + // console.log('editor', processor); return ( diff --git a/src/components/markdown_editor/markdown_format.tsx b/src/components/markdown_editor/markdown_format.tsx index decdac83182..bfe5bc02631 100644 --- a/src/components/markdown_editor/markdown_format.tsx +++ b/src/components/markdown_editor/markdown_format.tsx @@ -18,16 +18,25 @@ */ import React, { FunctionComponent, useMemo } from 'react'; -import { Processor } from 'unified'; +import unified, { Processor } from 'unified'; +import { + EuiMarkdownDefaultProcessingPlugins, + EuiMarkdownDefaultParsingPlugins, +} from './markdown_default_plugins'; interface EuiMarkdownFormatProps { children: string; - processor: Processor; + /** A function to process the text. Will use unified/remark with EUI's additional default plugins when not provided */ + processor?: Processor; } +const defaultProcessor = unified() + .use(EuiMarkdownDefaultParsingPlugins) + .use(EuiMarkdownDefaultProcessingPlugins); + export const EuiMarkdownFormat: FunctionComponent = ({ children, - processor, + processor = defaultProcessor, }) => { const result = useMemo(() => { try { diff --git a/src/components/markdown_editor/plugins/markdown_tooltip.scss b/src/components/markdown_editor/plugins/markdown_tooltip.scss new file mode 100644 index 00000000000..2fe1a191c83 --- /dev/null +++ b/src/components/markdown_editor/plugins/markdown_tooltip.scss @@ -0,0 +1,4 @@ +// This is to offset the tooltip icon, which isn't perfectly centered +.euiMarkdownTooltip__icon { + transform: translateY(-1px); +} diff --git a/src/components/markdown_editor/plugins/markdown_tooltip.tsx b/src/components/markdown_editor/plugins/markdown_tooltip.tsx index 7eceaaa40d2..9d15273d4c7 100644 --- a/src/components/markdown_editor/plugins/markdown_tooltip.tsx +++ b/src/components/markdown_editor/plugins/markdown_tooltip.tsx @@ -27,6 +27,7 @@ import { RemarkTokenizer, } from '../markdown_types'; import { EuiToolTip } from '../../tool_tip'; +import { EuiIcon } from '../../icon'; import { EuiCodeBlock } from '../../code'; interface TooltipNodeDetails { @@ -144,9 +145,17 @@ const tooltipMarkdownRenderer: FunctionComponent< TooltipNodeDetails & { position: AstNodePosition } > = ({ content, children }) => { return ( - - {children} - + + + + {children} + + + + ); }; From c7389eb9982ae3fe002b73533875bdaf90365d7e Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 3 Jul 2020 16:53:05 -0700 Subject: [PATCH 02/17] rework plugin example so that it doesn't import hidden files --- .../markdown_editor/mardown_editor_example.js | 2 +- .../markdown_editor_with_plugins.js | 238 +++++++++++++++++- .../markdown_editor/plugins/markdown_chart.js | 237 ----------------- 3 files changed, 234 insertions(+), 243 deletions(-) delete mode 100644 src-docs/src/views/markdown_editor/plugins/markdown_chart.js diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index 0cc3849e23c..964ea129a79 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -104,7 +104,7 @@ export const MarkdownEditorExample = { code: markdownEditorWithPluginsHtml, }, ], - title: 'Markdown plugins', + title: 'Adding custom plugins', text: (

Both EuiMarkdownEditor and{' '} diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index b689f53b66a..a8e9789c734 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -1,5 +1,15 @@ import React, { useCallback, useState } from 'react'; +import { + Chart, + Settings, + Axis, + BarSeries, + DataGenerator, +} from '@elastic/charts'; + +import { EUI_CHARTS_THEME_LIGHT } from '../../../../src/themes/charts/themes'; + import { EuiMarkdownDefaultParsingPlugins, EuiMarkdownDefaultProcessingPlugins, @@ -7,17 +17,235 @@ import { EuiSpacer, EuiCodeBlock, EuiButtonToggle, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButton, + EuiButtonEmpty, + EuiForm, + EuiFormRow, + EuiSelect, + EuiRange, + EuiText, } from '../../../../src'; -import * as MarkdownChart from './plugins/markdown_chart'; + +import { + euiPaletteColorBlind, + euiPaletteComplimentary, + euiPaletteCool, + euiPaletteForStatus, + euiPaletteForTemperature, + euiPaletteGray, + euiPaletteNegative, + euiPalettePositive, + euiPaletteWarm, +} from '../../../../src/services/color'; + +const paletteData = { + euiPaletteColorBlind, + euiPaletteForStatus, + euiPaletteForTemperature, + euiPaletteComplimentary, + euiPaletteNegative, + euiPalettePositive, + euiPaletteCool, + euiPaletteWarm, + euiPaletteGray, +}; + +const paletteNames = Object.keys(paletteData); + +const dg = new DataGenerator(); +const data = dg.generateGroupedSeries(20, 5); + +const chartDemoPlugin = { + name: 'chartDemoPlugin', + button: { + label: 'Chart', + iconType: 'visArea', + }, + helpText: ( +

+ + {'!{chart{options}}'} + + + +

Where options can contain:

+
    +
  • + palette: A number between 1-9 for each palette. +
  • +
  • + height: + The height of the chart +
  • +
+
+
+ ), + editor: function ChartEditor({ node, onSave, onCancel }) { + const [palette, setPalette] = useState((node && node.palette) || 4); + const [height, setHeight] = useState((node && node.height) || 300); + + return ( + <> + + Chart data + + + + <> + + + setPalette(parseInt(e.target.value, 10))} + /> + + + + setHeight(parseInt(e.target.value, 10))} + /> + + + + + + + + Cancel + + + onSave(`!{chart${JSON.stringify({ palette, height })}}`) + } + fill> + Save + + + + ); + }, +}; + +function ChartMarkdownParser() { + const Parser = this.Parser; + const tokenizers = Parser.prototype.blockTokenizers; + const methods = Parser.prototype.blockMethods; + + function tokenizeChart(eat, value, silent) { + if (value.startsWith('!{chart') === false) return false; + + const nextChar = value[7]; + + if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a chart + + if (silent) { + return true; + } + + // is there a configuration? + const hasConfiguration = nextChar === '{'; + + let match = '!{chart'; + let configuration = {}; + + if (hasConfiguration) { + let configurationString = ''; + + let openObjects = 0; + + for (let i = 7; i < value.length; i++) { + const char = value[i]; + if (char === '{') { + openObjects++; + configurationString += char; + } else if (char === '}') { + openObjects--; + if (openObjects === -1) { + break; + } + configurationString += char; + } else { + configurationString += char; + } + } + + match += configurationString; + try { + configuration = JSON.parse(configurationString); + } catch (e) { + const now = eat.now(); + this.file.fail(`Unable to parse chart JSON configuration: ${e}`, { + line: now.line, + column: now.column + 7, + }); + } + } + + match += '}'; + return eat(match)({ + type: 'chartDemoPlugin', + ...configuration, + }); + } + + tokenizers.chart = tokenizeChart; + methods.splice(methods.indexOf('text'), 0, 'chart'); +} + +const chartMarkdownHandler = (h, node) => { + return h(node.position, 'chartDemoPlugin', node, []); +}; +const ChartMarkdownRenderer = ({ height = 200, palette = 5 }) => { + const customColors = { + colors: { + vizColors: paletteData[paletteNames[palette]](5), + }, + }; + return ( + + + + + + + ); +}; const exampleParsingList = [ ...EuiMarkdownDefaultParsingPlugins, - MarkdownChart.parser, + ChartMarkdownParser, ]; const exampleProcessingList = [...EuiMarkdownDefaultProcessingPlugins]; // pretend mutation doesn't happen immediately next 😅 -exampleProcessingList[0][1].handlers.chartDemoPlugin = MarkdownChart.handler; -exampleProcessingList[1][1].components.chartDemoPlugin = MarkdownChart.renderer; +exampleProcessingList[0][1].handlers.chartDemoPlugin = chartMarkdownHandler; +exampleProcessingList[1][1].components.chartDemoPlugin = ChartMarkdownRenderer; const initialExample = `## Chart plugin @@ -44,7 +272,7 @@ export default () => { value={value} onChange={setValue} height={400} - uiPlugins={[MarkdownChart.plugin]} + uiPlugins={[chartDemoPlugin]} parsingPluginList={exampleParsingList} processingPluginList={exampleProcessingList} onParse={onParse} diff --git a/src-docs/src/views/markdown_editor/plugins/markdown_chart.js b/src-docs/src/views/markdown_editor/plugins/markdown_chart.js deleted file mode 100644 index 081555924b7..00000000000 --- a/src-docs/src/views/markdown_editor/plugins/markdown_chart.js +++ /dev/null @@ -1,237 +0,0 @@ -import React, { useState } from 'react'; -import { - Chart, - Settings, - Axis, - BarSeries, - DataGenerator, -} from '@elastic/charts'; -import { EUI_CHARTS_THEME_LIGHT } from '../../../../../src/themes/charts/themes'; -import { - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, - EuiButton, - EuiButtonEmpty, - EuiForm, - EuiFormRow, - EuiSelect, - EuiRange, - EuiCodeBlock, - EuiText, - EuiSpacer, -} from '../../../../../src/components'; -import { - euiPaletteColorBlind, - euiPaletteComplimentary, - euiPaletteCool, - euiPaletteForStatus, - euiPaletteForTemperature, - euiPaletteGray, - euiPaletteNegative, - euiPalettePositive, - euiPaletteWarm, -} from '../../../../../src/services/color'; - -const paletteData = { - euiPaletteColorBlind, - euiPaletteForStatus, - euiPaletteForTemperature, - euiPaletteComplimentary, - euiPaletteNegative, - euiPalettePositive, - euiPaletteCool, - euiPaletteWarm, - euiPaletteGray, -}; -const paletteNames = Object.keys(paletteData); - -const dg = new DataGenerator(); -const data = dg.generateGroupedSeries(20, 5); - -const chartDemoPlugin = { - name: 'chartDemoPlugin', - button: { - label: 'Chart', - iconType: 'visArea', - }, - helpText: ( -
- - {'!{chart{options}}'} - - - -

Where options can contain:

-
    -
  • - palette: A number between 1-9 for each palette. -
  • -
  • - height: - The height of the chart -
  • -
-
-
- ), - editor: function ChartEditor({ node, onSave, onCancel }) { - const [palette, setPalette] = useState((node && node.palette) || 4); - const [height, setHeight] = useState((node && node.height) || 300); - - return ( - <> - - Chart data - - - - <> - - - setPalette(parseInt(e.target.value, 10))} - /> - - - - setHeight(parseInt(e.target.value, 10))} - /> - - - - - - - - Cancel - - - onSave(`!{chart${JSON.stringify({ palette, height })}}`) - } - fill> - Save - - - - ); - }, -}; - -function ChartParser() { - const Parser = this.Parser; - const tokenizers = Parser.prototype.blockTokenizers; - const methods = Parser.prototype.blockMethods; - - function tokenizeChart(eat, value, silent) { - if (value.startsWith('!{chart') === false) return false; - - const nextChar = value[7]; - - if (nextChar !== '{' && nextChar !== '}') return false; // this isn't actually a chart - - if (silent) { - return true; - } - - // is there a configuration? - const hasConfiguration = nextChar === '{'; - - let match = '!{chart'; - let configuration = {}; - - if (hasConfiguration) { - let configurationString = ''; - - let openObjects = 0; - - for (let i = 7; i < value.length; i++) { - const char = value[i]; - if (char === '{') { - openObjects++; - configurationString += char; - } else if (char === '}') { - openObjects--; - if (openObjects === -1) { - break; - } - configurationString += char; - } else { - configurationString += char; - } - } - - match += configurationString; - try { - configuration = JSON.parse(configurationString); - } catch (e) { - const now = eat.now(); - this.file.fail(`Unable to parse chart JSON configuration: ${e}`, { - line: now.line, - column: now.column + 7, - }); - } - } - - match += '}'; - return eat(match)({ - type: 'chartDemoPlugin', - ...configuration, - }); - } - - tokenizers.chart = tokenizeChart; - methods.splice(methods.indexOf('text'), 0, 'chart'); -} - -const chartMarkdownHandler = (h, node) => { - return h(node.position, 'chartDemoPlugin', node, []); -}; -const ChartMarkdownRenderer = ({ height = 200, palette = 5 }) => { - const customColors = { - colors: { - vizColors: paletteData[paletteNames[palette]](5), - }, - }; - return ( - - - - - - - ); -}; - -export { - chartDemoPlugin as plugin, - ChartParser as parser, - chartMarkdownHandler as handler, - ChartMarkdownRenderer as renderer, -}; From a50cb0e7cba125a8a7470e64d062da6bebf30142 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Fri, 3 Jul 2020 23:30:12 -0700 Subject: [PATCH 03/17] moving stuff around, still rough --- src-docs/src/routes.js | 14 +++- .../markdown_editor/mardown_editor_example.js | 32 +-------- .../markdown_editor/mardown_format_example.js | 65 +++++++++++++++++++ 3 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 src-docs/src/views/markdown_editor/mardown_format_example.js diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 5aaa6f626e0..764f78b8a66 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -143,6 +143,8 @@ import { LoadingExample } from './views/loading/loading_example'; import { MarkdownEditorExample } from './views/markdown_editor/mardown_editor_example'; +import { MarkdownFormatExample } from './views/markdown_editor/mardown_format_example'; + import { ModalExample } from './views/modal/modal_example'; import { MutationObserverExample } from './views/mutation_observer/mutation_observer_example'; @@ -375,7 +377,6 @@ const navigation = [ BadgeExample, CallOutExample, CardExample, - CodeExample, CommentListExample, DescriptionListExample, DragAndDropExample, @@ -405,11 +406,9 @@ const navigation = [ SuperSelectExample, ComboBoxExample, ColorPickerExample, - CodeEditorExample, DatePickerExample, ExpressionExample, FilterGroupExample, - MarkdownEditorExample, RangeControlExample, SearchBarExample, SelectableExample, @@ -417,6 +416,15 @@ const navigation = [ SuperDatePickerExample, ].map(example => createExample(example)), }, + { + name: 'Editors & syntax', + items: [ + MarkdownEditorExample, + MarkdownFormatExample, + CodeEditorExample, + CodeExample, + ].map(example => createExample(example)), + }, { name: 'Elastic Charts', items: [ diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index 964ea129a79..badc13acacd 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -17,16 +17,12 @@ import MarkdownEditor from './markdown_editor'; const markdownEditorSource = require('!!raw-loader!./markdown_editor'); const markdownEditorHtml = renderToHtml(MarkdownEditor); -import MarkdownFormat from './markdown_format'; -const markdownFormatSource = require('!!raw-loader!./markdown_format'); -const markdownFormatHtml = renderToHtml(MarkdownFormat); - import MarkdownEditorWithPlugins from './markdown_editor_with_plugins'; const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins'); const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); export const MarkdownEditorExample = { - title: 'Markdown', + title: 'Markdown editor', intro: ( @@ -42,32 +38,6 @@ export const MarkdownEditorExample = { ), sections: [ - { - source: [ - { - type: GuideSectionTypes.JS, - code: markdownFormatSource, - }, - { - type: GuideSectionTypes.HTML, - code: markdownFormatHtml, - }, - ], - title: 'Markdown format', - text: ( -

- EuiMarkdownFormat is a wrapper that will render - Markdown provided. EuiMarkdownFormat uses{' '} - Remark by - default, though you could replace it with your own processor if you - are feeling adventurous. -

- ), - props: { - EuiMarkdownFormat, - }, - demo: , - }, { source: [ { diff --git a/src-docs/src/views/markdown_editor/mardown_format_example.js b/src-docs/src/views/markdown_editor/mardown_format_example.js new file mode 100644 index 00000000000..44041a8b1d0 --- /dev/null +++ b/src-docs/src/views/markdown_editor/mardown_format_example.js @@ -0,0 +1,65 @@ +import React, { Fragment } from 'react'; + +import { renderToHtml } from '../../services'; + +import { GuideSectionTypes } from '../../components'; + +import { + EuiMarkdownFormat, + EuiText, + EuiSpacer, +} from '../../../../src/components'; + +import { Link } from 'react-router-dom'; + +import MarkdownFormat from './markdown_format'; +const markdownFormatSource = require('!!raw-loader!./markdown_format'); +const markdownFormatHtml = renderToHtml(MarkdownFormat); + +export const MarkdownFormatExample = { + title: 'Markdown format', + intro: ( + + +

+ EUI provides components to both edit and render markdown-like content + with dynamic previews. The components, built on top of the{' '} + Unified{' '} + framework, are extendible through an optional plugin layer that allows + for translating additional string syntax into React renders. +

+
+ +
+ ), + sections: [ + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownFormatSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownFormatHtml, + }, + ], + title: 'Markdown format', + text: ( +

+ EuiMarkdownFormat is a wrapper that will render + Markdown provided. EuiMarkdownFormat uses{' '} + Remark by + default. The translation layer automatically substitutes raw HTML + output with their EUI equivilant. This means anchor and code blocks + will become EuiLink and EuiCodeBlock{' '} + components respectively). +

+ ), + props: { + EuiMarkdownFormat, + }, + demo: , + }, + ], +}; From a2b289a207ea2627745ea541a47c3ad7de3e002b Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sat, 4 Jul 2020 08:31:17 -0700 Subject: [PATCH 04/17] clean up markdown format pages, make a kitchen sink example --- .../markdown_editor/mardown_format_example.js | 43 ++++- .../markdown_editor/markdown_format_sink.js | 150 ++++++++++++++++++ 2 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 src-docs/src/views/markdown_editor/markdown_format_sink.js diff --git a/src-docs/src/views/markdown_editor/mardown_format_example.js b/src-docs/src/views/markdown_editor/mardown_format_example.js index 44041a8b1d0..de0116eafd8 100644 --- a/src-docs/src/views/markdown_editor/mardown_format_example.js +++ b/src-docs/src/views/markdown_editor/mardown_format_example.js @@ -16,17 +16,23 @@ import MarkdownFormat from './markdown_format'; const markdownFormatSource = require('!!raw-loader!./markdown_format'); const markdownFormatHtml = renderToHtml(MarkdownFormat); +import MarkdownFormatSink from './markdown_format_sink'; +const markdownFormatSinkSource = require('!!raw-loader!./markdown_format_sink'); +const markdownFormatSinkHtml = renderToHtml(MarkdownFormatSink); + export const MarkdownFormatExample = { title: 'Markdown format', intro: (

- EUI provides components to both edit and render markdown-like content - with dynamic previews. The components, built on top of the{' '} - Unified{' '} - framework, are extendible through an optional plugin layer that allows - for translating additional string syntax into React renders. + EuiMarkdownFormat is a read-only way to render + markdown-style content in a page. It is a peer component to{' '} + + EuiMarkdownEditor + {' '} + and has the ability to be modified by additional{' '} + markdown plugins.

@@ -44,7 +50,7 @@ export const MarkdownFormatExample = { code: markdownFormatHtml, }, ], - title: 'Markdown format', + title: 'Built in plugins', text: (

EuiMarkdownFormat is a wrapper that will render @@ -53,7 +59,7 @@ export const MarkdownFormatExample = { default. The translation layer automatically substitutes raw HTML output with their EUI equivilant. This means anchor and code blocks will become EuiLink and EuiCodeBlock{' '} - components respectively). + components respectively.

), props: { @@ -61,5 +67,28 @@ export const MarkdownFormatExample = { }, demo: , }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownFormatSinkSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownFormatSinkHtml, + }, + ], + title: 'Kitchen sink', + text: ( +

+ This example shows of all the styling and markup possibilities. It is + mostly used for testing. +

+ ), + props: { + EuiMarkdownFormat, + }, + demo: , + }, ], }; diff --git a/src-docs/src/views/markdown_editor/markdown_format_sink.js b/src-docs/src/views/markdown_editor/markdown_format_sink.js new file mode 100644 index 00000000000..9cb5d06af7f --- /dev/null +++ b/src-docs/src/views/markdown_editor/markdown_format_sink.js @@ -0,0 +1,150 @@ +import React from 'react'; + +import { EuiMarkdownFormat } from '../../../../src'; + +const markdownContent = `# h1 Heading +## h2 Heading +### h3 Heading +#### h4 Heading +##### h5 Heading +###### h6 Heading + + +### Emphasis + +**This is bold text** + +__This is bold text__ + +*This is italic text* + +_This is italic text_ + +~~Strikethrough~~ + + +### Horizontal Rules + +___ + +--- + +*** + + +## Blockquotes + + +> Blockquotes can also be nested... +>> ...by using additional greater-than signs right next to each other... +> > > ...or with spaces between arrows. + + +## Lists + +Unordered + +* Item 1 +* Item 2 + * Item 2a + * Item 2b + +Ordered + +1. Item 1 +1. Item 2 +1. Item 3 + 1. Item 3a + 1. Item 3b + + +## Task Lists + +- [x] Create a new project +- [x] Give your project a name +- [ ] Add a new column + +## Another Task Lists + +* [x] Create a new project +* [x] Give your project a name +* [ ] Add a new column + + +## Code + +Inline \`\` is awesome! + +Block code "fences" + +\`\`\` +Sample text here... +\`\`\` + +Syntax highlighting JS + +\`\`\` js +var foo = function (bar) { + return bar++; +}; + +console.log(foo(5)); +\`\`\` + +Syntax highlighting Java + +\`\`\` java +package l2f.gameserver.model; + +public abstract class L2Char extends L2Object { + public static final Short ERROR = 0x0001; + + public void moveTo(int x, int y, int z) { + _ai = null; + log("Should not be called"); + if (1 > 5) { // wtf!? + return; + } + } +} +\`\`\` + +## Tables + +| Option | Description | +| ------ | ----------- | +| data | path to data files to supply the data that will be passed into templates. | +| engine | engine to be used for processing templates. Handlebars is the default. | +| ext | extension to be used for dest files. | + +Right aligned columns + +| Option | Description | +| ------:| -----------:| +| data | path to data files to supply the data that will be passed into templates. | +| engine | engine to be used for processing templates. Handlebars is the default. | +| ext | extension to be used for dest files. | + + +## Links + +[link text](http://dev.nodeca.com) + +[link with title](http://nodeca.github.io/pica/demo/ "title text!") + +Autoconverted link https://github.com/nodeca/pica (enable linkify to see) + + +## Images + +![Kibana](https://user-images.githubusercontent.com/2750668/74490344-2f271800-4ec0-11ea-8614-8651cd47eab1.png) + + +### [Emojies](https://github.com/markdown-it/markdown-it-emoji) + +> Classic markup: :wink: :cry: :laughing: :yum: +`; + +export default () => { + return {markdownContent}; +}; From bcbe6a401d184502a3ea4e27cd78f25194794d53 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sat, 4 Jul 2020 10:16:23 -0700 Subject: [PATCH 05/17] add editor errors example, skeleton for plugins --- src-docs/src/routes.js | 5 +- .../markdown_editor/mardown_editor_example.js | 68 ++++++++++++++---- .../views/markdown_editor/markdown_editor.js | 15 +++- .../markdown_editor/markdown_editor_errors.js | 63 ++++++++++++++++ .../markdown_editor_with_plugins.js | 2 +- .../markdown_plugin_example.js | 71 +++++++++++++++++++ 6 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 src-docs/src/views/markdown_editor/markdown_editor_errors.js create mode 100644 src-docs/src/views/markdown_editor/markdown_plugin_example.js diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 764f78b8a66..c7e8c151591 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -145,6 +145,8 @@ import { MarkdownEditorExample } from './views/markdown_editor/mardown_editor_ex import { MarkdownFormatExample } from './views/markdown_editor/mardown_format_example'; +import { MarkdownPluginExample } from './views/markdown_editor/markdown_plugin_example'; + import { ModalExample } from './views/modal/modal_example'; import { MutationObserverExample } from './views/mutation_observer/mutation_observer_example'; @@ -419,8 +421,9 @@ const navigation = [ { name: 'Editors & syntax', items: [ - MarkdownEditorExample, MarkdownFormatExample, + MarkdownEditorExample, + MarkdownPluginExample, CodeEditorExample, CodeExample, ].map(example => createExample(example)), diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index badc13acacd..86118ee514d 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -6,9 +6,9 @@ import { GuideSectionTypes } from '../../components'; import { EuiMarkdownEditor, - EuiMarkdownFormat, EuiText, EuiSpacer, + EuiCode, } from '../../../../src/components'; import { Link } from 'react-router-dom'; @@ -21,17 +21,26 @@ import MarkdownEditorWithPlugins from './markdown_editor_with_plugins'; const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins'); const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); +import MarkdownEditorErrors from './markdown_editor_errors'; +const markdownEditorErrorsSource = require('!!raw-loader!./markdown_editor_errors'); +const markdownEditorErrorsHtml = renderToHtml(MarkdownEditorErrors); + export const MarkdownEditorExample = { title: 'Markdown editor', intro: (

- EUI provides components to both edit and render markdown-like content - with dynamic previews. The components, built on top of the{' '} - Unified{' '} - framework, are extendible through an optional plugin layer that allows - for translating additional string syntax into React renders. + EuiMarkdownEditor provides a markdown authoring + experience for the user. The component consists of a toolbar, text + area, and a drag-and-drop zone to accept files. There are two modes: a + textarea that keeps track of cursor position, and a rendered preview + mode that is powered by{' '} + + EuiMarkdownFormat + + . State is maintained between the two and it is possible to pass + changes from the preview area to the text area and vice versa.

@@ -49,13 +58,11 @@ export const MarkdownEditorExample = { code: markdownEditorHtml, }, ], - title: 'Markdown editor', + title: 'Base editor', text: (

- EuiMarkdownEditor provides a markdown authoring - experience for the user. This component consists of a toolbar, text - area, and optionally a drag-and-drop zone to accept files. It can be - toggled by the user between editing and preview modes + The base editor can render basic markdown along with some built-in + plugins.

), props: { @@ -63,6 +70,33 @@ export const MarkdownEditorExample = { }, demo: , }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownEditorErrorsSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownEditorErrorsHtml, + }, + ], + title: 'Error handling and feedback', + text: ( +

+ The errors prop allows you to pass an array of + errors if syntax is malformed. Below the tooltip plugin is able to + provide this message by default. These errors are meant to be + emphemeral and part of the editing experience. They should not be a + substitute for{' '} + form validation. +

+ ), + props: { + EuiMarkdownEditor, + }, + demo: , + }, { source: [ { @@ -77,9 +111,15 @@ export const MarkdownEditorExample = { title: 'Adding custom plugins', text: (

- Both EuiMarkdownEditor and{' '} - EuiMarkdownFormat can utilize additional plugins to a - syntax to react render pipeline. + EuiMarkdownEditor can extend its functionality with + additional plugins. These allow you to add additional toolbar items, + syntax and rendering abilities. For a more technical overview check + out the{' '} + + plugin documentation + + . The below example shows how to embed charts that can be added + through a modal GUI and then modified afterwards through syntax.

), props: { diff --git a/src-docs/src/views/markdown_editor/markdown_editor.js b/src-docs/src/views/markdown_editor/markdown_editor.js index 5b19d27c06a..c2398c2328f 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor.js +++ b/src-docs/src/views/markdown_editor/markdown_editor.js @@ -5,12 +5,21 @@ import { EuiSpacer, EuiCodeBlock, EuiButtonToggle, -} from '../../../../src'; +} from '../../../../src/components'; -const markdownExample = require('!!raw-loader!./markdown-example.md'); +const initialContent = `## Hello world! + +Basic "github flavored" markdown will work as you'd expect. + +The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode. + +- [ ] Checkboxes +- [x] Can be filled +- [ ] Or empty +`; export default () => { - const [value, setValue] = useState(markdownExample); + const [value, setValue] = useState(initialContent); const [messages, setMessages] = useState([]); const [ast, setAst] = useState(null); const [isAstShowing, setIsAstShowing] = useState(false); diff --git a/src-docs/src/views/markdown_editor/markdown_editor_errors.js b/src-docs/src/views/markdown_editor/markdown_editor_errors.js new file mode 100644 index 00000000000..ef6c3546739 --- /dev/null +++ b/src-docs/src/views/markdown_editor/markdown_editor_errors.js @@ -0,0 +1,63 @@ +import React, { useCallback, useState } from 'react'; + +import { Link } from 'react-router-dom'; + +import { + EuiMarkdownEditor, + EuiSpacer, + EuiCodeBlock, + EuiButtonToggle, + EuiFormErrorText, +} from '../../../../src/components'; + +const initialContent = `## Errors + +The tooltip is empty and will error + +!{tooltip[]()} +`; + +export default () => { + const [value, setValue] = useState(initialContent); + const [messages, setMessages] = useState([]); + const [ast, setAst] = useState(null); + const [isAstShowing, setIsAstShowing] = useState(false); + const onParse = useCallback((err, { messages, ast }) => { + setMessages(err ? [err] : messages); + setAst(JSON.stringify(ast, null, 2)); + }, []); + return ( + <> + + + + + Utilize error text or{' '} + + EuiFormRow + {' '} + for more permanent error feedback + + +
+ setIsAstShowing(!isAstShowing)} + isSelected={isAstShowing} + /> +
+ + {isAstShowing && {ast}} + + ); +}; diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index a8e9789c734..f23fd49cf56 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -28,7 +28,7 @@ import { EuiSelect, EuiRange, EuiText, -} from '../../../../src'; +} from '../../../../src/components'; import { euiPaletteColorBlind, diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js new file mode 100644 index 00000000000..0cd938991cc --- /dev/null +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -0,0 +1,71 @@ +import React, { Fragment } from 'react'; + +import { renderToHtml } from '../../services'; + +import { GuideSectionTypes } from '../../components'; + +import { + EuiMarkdownFormat, + EuiText, + EuiSpacer, +} from '../../../../src/components'; + +import { Link } from 'react-router-dom'; + +import MarkdownFormat from './markdown_format'; +const markdownFormatSource = require('!!raw-loader!./markdown_format'); +const markdownFormatHtml = renderToHtml(MarkdownFormat); + +import MarkdownFormatSink from './markdown_format_sink'; +const markdownFormatSinkSource = require('!!raw-loader!./markdown_format_sink'); +const markdownFormatSinkHtml = renderToHtml(MarkdownFormatSink); + +export const MarkdownPluginExample = { + title: 'Markdown plugins', + intro: ( + + +

+ EuiMarkdownFormat is a read-only way to render + markdown-style content in a page. It is a peer component to{' '} + + EuiMarkdownEditor + {' '} + and has the ability to be modified by additional{' '} + markdown plugins. +

+
+ +
+ ), + sections: [ + { + source: [ + { + type: GuideSectionTypes.JS, + code: markdownFormatSource, + }, + { + type: GuideSectionTypes.HTML, + code: markdownFormatHtml, + }, + ], + title: 'Built in plugins', + text: ( +

+ EuiMarkdownFormat is a wrapper that will render + Markdown provided. EuiMarkdownFormat uses{' '} + Remark by + default. The translation layer automatically substitutes raw HTML + output with their EUI equivilant. This means anchor and code blocks + will become EuiLink and EuiCodeBlock{' '} + components respectively. +

+ ), + props: { + EuiMarkdownFormat, + }, + demo: , + }, + ], +}; From 1eb2ebf73f30800be48f2c5b1c31887513d24046 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sat, 4 Jul 2020 12:36:47 -0700 Subject: [PATCH 06/17] basic intro fo plugin page, needs more content and an example still --- .../markdown_plugin_example.js | 144 +++++++++++++++--- 1 file changed, 127 insertions(+), 17 deletions(-) diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index 0cd938991cc..cc8aaf9271a 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -8,6 +8,7 @@ import { EuiMarkdownFormat, EuiText, EuiSpacer, + EuiDescriptionList, } from '../../../../src/components'; import { Link } from 'react-router-dom'; @@ -16,9 +17,88 @@ import MarkdownFormat from './markdown_format'; const markdownFormatSource = require('!!raw-loader!./markdown_format'); const markdownFormatHtml = renderToHtml(MarkdownFormat); -import MarkdownFormatSink from './markdown_format_sink'; -const markdownFormatSinkSource = require('!!raw-loader!./markdown_format_sink'); -const markdownFormatSinkHtml = renderToHtml(MarkdownFormatSink); +const pluginConcepts = [ + { + title: 'uiPlugin', + description: ( + + Provides the UI for the button in the toolbar as well + as any modals or extra UI that provides content to the editor. + + ), + }, + { + title: 'parsingPlugin', + description: ( + + Provides the logic to identify the new syntax and parse it into an{' '} + AST node. + + ), + }, + { + title: 'processingPlugin', + description: ( + + Provides the logic to process the new AST node into a{' '} + React node. + + ), + }, +]; + +const uiPluginConcepts = [ + { + title: 'name', + description: ( + + The name of your plugin. Use the button.label listed + below if you need a more friendly display name. The button can be + ommitted if you wish the user to only utilize syntax to author the + content. + + ), + }, + { + title: 'button', + description: ( + + Takes a label and an icon type. This + forms the button that appear in the toolbar. Clicking the button will + trigger either the editor or formatter + . + + ), + }, + { + title: 'editor', + description: ( + + Provides UI controls (like an interactive modal) for how to build the + inital content. Must exist if formatting does not. + + ), + }, + { + title: 'formatter', + description: ( + + If no editor is provided, a React component can be + providing to handle formatting. + + ), + }, + { + title: 'helpText', + description: ( + + Contains a React node. Should contain some information and an example + for how to utlize the syntax. Appears when the markdown icon is clicked + on the bottom of the editor. + + ), + }, +]; export const MarkdownPluginExample = { title: 'Markdown plugins', @@ -26,13 +106,31 @@ export const MarkdownPluginExample = {

- EuiMarkdownFormat is a read-only way to render - markdown-style content in a page. It is a peer component to{' '} + Both{' '} EuiMarkdownEditor {' '} - and has the ability to be modified by additional{' '} - markdown plugins. + and{' '} + + EuiMarkdownFormat + {' '} + utilize the same underlying plugin architecture to transform string + based syntax into React components. At a high level{' '} + Unified JS is used in comination with{' '} + Remark to provide EUI's markdown components, + which are separated into a parsing and{' '} + processing layer. These two concepts are kept + distinct in EUI components to provide concrete locations for your + plugins to be injected, be it editing or rendering. Finally you + provide UI to the component to handle interactions + with the editor. +

+

+ In addition to running the full pipeline,{' '} + EuiMarkdownEditor uses just the parsing configuration + to determine the input's validity, provide messages back to the + application, and allow the toolbar buttons to interact with existing + markdown tags.

@@ -50,17 +148,29 @@ export const MarkdownPluginExample = { code: markdownFormatHtml, }, ], - title: 'Built in plugins', + title: 'Plugin development', text: ( -

- EuiMarkdownFormat is a wrapper that will render - Markdown provided. EuiMarkdownFormat uses{' '} - Remark by - default. The translation layer automatically substitutes raw HTML - output with their EUI equivilant. This means anchor and code blocks - will become EuiLink and EuiCodeBlock{' '} - components respectively. -

+ +

+ An EuiMarkdown plugin is comprised of three major + pieces, which are passed searpately into the editor component. +

+ +

uiPlugin

+ +
), props: { EuiMarkdownFormat, From bee355d290d5682376a20e1710d38a6794aa3b2f Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sat, 4 Jul 2020 18:18:56 -0700 Subject: [PATCH 07/17] so much html --- .../markdown_editor/mardown_editor_example.js | 34 ---- .../markdown_editor_with_plugins.js | 2 +- .../markdown_plugin_example.js | 172 +++++++++++++++--- 3 files changed, 143 insertions(+), 65 deletions(-) diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index 86118ee514d..69017d2e1d1 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -17,10 +17,6 @@ import MarkdownEditor from './markdown_editor'; const markdownEditorSource = require('!!raw-loader!./markdown_editor'); const markdownEditorHtml = renderToHtml(MarkdownEditor); -import MarkdownEditorWithPlugins from './markdown_editor_with_plugins'; -const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins'); -const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); - import MarkdownEditorErrors from './markdown_editor_errors'; const markdownEditorErrorsSource = require('!!raw-loader!./markdown_editor_errors'); const markdownEditorErrorsHtml = renderToHtml(MarkdownEditorErrors); @@ -97,35 +93,5 @@ export const MarkdownEditorExample = { }, demo: , }, - { - source: [ - { - type: GuideSectionTypes.JS, - code: markdownEditorWithPluginsSource, - }, - { - type: GuideSectionTypes.HTML, - code: markdownEditorWithPluginsHtml, - }, - ], - title: 'Adding custom plugins', - text: ( -

- EuiMarkdownEditor can extend its functionality with - additional plugins. These allow you to add additional toolbar items, - syntax and rendering abilities. For a more technical overview check - out the{' '} - - plugin documentation - - . The below example shows how to embed charts that can be added - through a modal GUI and then modified afterwards through syntax. -

- ), - props: { - EuiMarkdownEditor, - }, - demo: , - }, ], }; diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index f23fd49cf56..323764554ac 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -251,7 +251,7 @@ const initialExample = `## Chart plugin Notice the toolbar above has a new chart button. Click it to add a chart. -Once you finish, it'll add some syntax that looks like the below. +Once you finish it'll add some syntax that looks like the below. !{chart{"palette":4,"height":300}} `; diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index cc8aaf9271a..03a62a3cf43 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -5,17 +5,38 @@ import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; import { - EuiMarkdownFormat, + EuiMarkdownEditor, EuiText, + EuiTitle, EuiSpacer, EuiDescriptionList, + EuiHorizontalRule, + EuiCodeBlock, + EuiCode, } from '../../../../src/components'; import { Link } from 'react-router-dom'; -import MarkdownFormat from './markdown_format'; -const markdownFormatSource = require('!!raw-loader!./markdown_format'); -const markdownFormatHtml = renderToHtml(MarkdownFormat); +import MarkdownEditorWithPlugins from './markdown_editor_with_plugins'; +const markdownEditorWithPluginsSource = require('!!raw-loader!./markdown_editor_with_plugins'); +const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); + +const pluginSnippet = ``; + +const uiPluginSnippet = `const myPluginUI = { + name: 'myPlugin', + button: { + label: 'Chart', + iconType: 'visArea', + }, + helpText: (
), + editor: function editor({ node, onSave, onCancel }) { return ('something'); }, +}; `; const pluginConcepts = [ { @@ -133,6 +154,114 @@ export const MarkdownPluginExample = { markdown tags.

+ + +

Plugin development

+
+ + +

+ An EuiMarkdown plugin is comprised of three major + pieces, which are passed searpately into the editor component. +

+
+ + + {pluginSnippet} + + + + + + +

uiPlugin

+
+ + + {uiPluginSnippet} + + + + + + +

parsingPlugin

+
+ + + +

+ + Remark-parse + {' '} + is used to parse the input text into markdown AST nodes. Its + documentation for{' '} + + writing parsers + {' '} + is under the Extending the Parser section, but highlights are + included below. +

+ +

+ A parser is comprised of three pieces. There is a wrapping function + which is provided to remark-parse and injects the parser, the parser + method itself, and a locator function if the markdown tag is inline. +

+ +

+ The parsing method is called at locations where its markdown down + might be found at. The method is responsible for determining if the + location is a valid tag, process the tag, and mark report the + result. +

+ +

Inline vs block

+

+ Inline tags are allowed at any point in text, and will be rendered + somewhere within a {'

'} element. For better + performance, inline parsers must provide a locate method which + reports the location where their next tag might be found. They are + not allowed to span multiple lines of the input. +

+ +

+ Block tags are rendered inside {''}{' '} + elements, and do not have a locate method. They can consume as much + input text as desired, across multiple lines. +

+
+
+ + + + Chandler, a simple parser example here maybe? + + + + +

processingPlugin

+
+ + +

This needs some explanation

+
+ + + Chandler, a simple processingPluginList example + ), @@ -141,41 +270,24 @@ export const MarkdownPluginExample = { source: [ { type: GuideSectionTypes.JS, - code: markdownFormatSource, + code: markdownEditorWithPluginsSource, }, { type: GuideSectionTypes.HTML, - code: markdownFormatHtml, + code: markdownEditorWithPluginsHtml, }, ], - title: 'Plugin development', + title: 'Putting it all together: a simple chart plugin', text: ( - -

- An EuiMarkdown plugin is comprised of three major - pieces, which are passed searpately into the editor component. -

- -

uiPlugin

- -
+

+ The below example takes the concepts from above to construct a simple + chart embed that is initiated from a new button in the editor toolbar. +

), props: { - EuiMarkdownFormat, + EuiMarkdownEditor, }, - demo: , + demo: , }, ], }; From c179da5379fdac9c944fba4d18bf96661b621406 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sat, 4 Jul 2020 18:46:31 -0700 Subject: [PATCH 08/17] rework markdownformat so that it can utilize the same plugin layer as the editor --- .../markdown_editor/mardown_format_example.js | 2 +- .../markdown_editor_with_plugins.js | 7 ++++++ .../markdown_plugin_example.js | 23 +++++++++++++------ .../markdown_editor/markdown_editor.tsx | 12 +++------- .../markdown_editor/markdown_format.tsx | 20 ++++++++-------- 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/src-docs/src/views/markdown_editor/mardown_format_example.js b/src-docs/src/views/markdown_editor/mardown_format_example.js index de0116eafd8..352d45e844b 100644 --- a/src-docs/src/views/markdown_editor/mardown_format_example.js +++ b/src-docs/src/views/markdown_editor/mardown_format_example.js @@ -32,7 +32,7 @@ export const MarkdownFormatExample = { EuiMarkdownEditor {' '} and has the ability to be modified by additional{' '} - markdown plugins. + markdown plugins.

diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index 323764554ac..4f01e279cda 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -14,6 +14,7 @@ import { EuiMarkdownDefaultParsingPlugins, EuiMarkdownDefaultProcessingPlugins, EuiMarkdownEditor, + EuiMarkdownFormat, EuiSpacer, EuiCodeBlock, EuiButtonToggle, @@ -290,6 +291,12 @@ export default () => { />
{isAstShowing && {ast}} + + + {value} + ); }; diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index 03a62a3cf43..55e8d909f97 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -24,7 +24,7 @@ const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); const pluginSnippet = ``; @@ -58,7 +58,7 @@ const pluginConcepts = [ ), }, { - title: 'processingPlugin', + title: 'processingPluginList', description: ( Provides the logic to process the new AST node into a{' '} @@ -252,7 +252,7 @@ export const MarkdownPluginExample = { -

processingPlugin

+

processingPluginList

@@ -279,10 +279,19 @@ export const MarkdownPluginExample = { ], 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 toolbar. -

+ +

+ The below example takes the concepts from above to construct a + simple chart embed that is initiated from a new button in the editor + toolbar. +

+

+ Note that the EuiMarkdownEditor and{' '} + EuiMarkdownFormat examples utilize the same prop + list. The editor manages additional controls through the{' '} + uiPlugins prop. +

+
), props: { EuiMarkdownEditor, diff --git a/src/components/markdown_editor/markdown_editor.tsx b/src/components/markdown_editor/markdown_editor.tsx index ae3a0bd0e01..9e9bd8ab89c 100644 --- a/src/components/markdown_editor/markdown_editor.tsx +++ b/src/components/markdown_editor/markdown_editor.tsx @@ -156,14 +156,6 @@ export const EuiMarkdownEditor: FunctionComponent< } }, [parser, value]); - const processor = useMemo( - () => - unified() - .use(parsingPluginList) - .use(processingPluginList), - [parsingPluginList, processingPluginList] - ); - const isPreviewing = viewMode === MODE_VIEWING; const replaceNode = useCallback( @@ -260,7 +252,9 @@ export const EuiMarkdownEditor: FunctionComponent<
- + {value}
diff --git a/src/components/markdown_editor/markdown_format.tsx b/src/components/markdown_editor/markdown_format.tsx index bfe5bc02631..1b9ddbb17c1 100644 --- a/src/components/markdown_editor/markdown_format.tsx +++ b/src/components/markdown_editor/markdown_format.tsx @@ -18,7 +18,7 @@ */ import React, { FunctionComponent, useMemo } from 'react'; -import unified, { Processor } from 'unified'; +import unified, { PluggableList } from 'unified'; import { EuiMarkdownDefaultProcessingPlugins, EuiMarkdownDefaultParsingPlugins, @@ -26,24 +26,26 @@ import { interface EuiMarkdownFormatProps { children: string; - /** A function to process the text. Will use unified/remark with EUI's additional default plugins when not provided */ - processor?: Processor; + /** array of unified plugins to parse content into an AST */ + parsingPluginList?: PluggableList; + /** array of unified plugins to convert the AST into a ReactNode */ + processingPluginList?: PluggableList; } -const defaultProcessor = unified() - .use(EuiMarkdownDefaultParsingPlugins) - .use(EuiMarkdownDefaultProcessingPlugins); - export const EuiMarkdownFormat: FunctionComponent = ({ children, - processor = defaultProcessor, + parsingPluginList, + processingPluginList, }) => { + const processor = unified() + .use(parsingPluginList || EuiMarkdownDefaultParsingPlugins) + .use(processingPluginList || EuiMarkdownDefaultProcessingPlugins); const result = useMemo(() => { try { return processor.processSync(children).contents; } catch (e) { return children; } - }, [processor, children]); + }, [children, processor]); return
{result}
; }; From 6ae4f513fb1942b1572eb671c66c05781d1aa9a4 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sun, 5 Jul 2020 09:55:21 -0700 Subject: [PATCH 09/17] some typos --- .../markdown_plugin_example.js | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index 55e8d909f97..03ead7d7df4 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -23,10 +23,17 @@ const markdownEditorWithPluginsHtml = renderToHtml(MarkdownEditorWithPlugins); const pluginSnippet = ``; +/> + + + +`; const uiPluginSnippet = `const myPluginUI = { name: 'myPlugin', @@ -34,7 +41,7 @@ const uiPluginSnippet = `const myPluginUI = { label: 'Chart', iconType: 'visArea', }, - helpText: (
), + helpText: (
A node that explains how the syntax works
), editor: function editor({ node, onSave, onCancel }) { return ('something'); }, }; `; @@ -49,7 +56,7 @@ const pluginConcepts = [ ), }, { - title: 'parsingPlugin', + title: 'parsingPluginList', description: ( Provides the logic to identify the new syntax and parse it into an{' '} @@ -162,7 +169,7 @@ export const MarkdownPluginExample = {

An EuiMarkdown plugin is comprised of three major - pieces, which are passed searpately into the editor component. + pieces, which are passed searpately as props.

@@ -197,20 +204,24 @@ export const MarkdownPluginExample = { -

parsingPlugin

+

parsingPluginList

- + Remark-parse - {' '} + {' '} is used to parse the input text into markdown AST nodes. Its documentation for{' '} - + writing parsers - {' '} + {' '} is under the Extending the Parser section, but highlights are included below.

From 5b070228618ebb800108e5e64a73c6fad717831b Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Wed, 22 Jul 2020 12:34:36 -0700 Subject: [PATCH 10/17] Update src-docs/src/views/markdown_editor/mardown_editor_example.js Co-authored-by: Chandler Prall --- src-docs/src/views/markdown_editor/mardown_editor_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index 69017d2e1d1..ae1efbde86d 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -83,7 +83,7 @@ export const MarkdownEditorExample = { The errors prop allows you to pass an array of errors if syntax is malformed. Below the tooltip plugin is able to provide this message by default. These errors are meant to be - emphemeral and part of the editing experience. They should not be a + ephemeral and part of the editing experience. They should not be a substitute for{' '} form validation.

From 34cf801b9b7780a80d795cc150d3164fe19d6869 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Wed, 22 Jul 2020 12:34:59 -0700 Subject: [PATCH 11/17] Update src-docs/src/views/markdown_editor/markdown_plugin_example.js Co-authored-by: Chandler Prall --- src-docs/src/views/markdown_editor/markdown_plugin_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index 03ead7d7df4..c366ffab81a 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -144,7 +144,7 @@ export const MarkdownPluginExample = { {' '} utilize the same underlying plugin architecture to transform string based syntax into React components. At a high level{' '} - Unified JS is used in comination with{' '} + Unified JS is used in combination with{' '} Remark to provide EUI's markdown components, which are separated into a parsing and{' '} processing layer. These two concepts are kept From f761d6fca402d1a04507f2bd8b2ec6201a249765 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Wed, 22 Jul 2020 12:35:34 -0700 Subject: [PATCH 12/17] Update src-docs/src/views/markdown_editor/mardown_editor_example.js Co-authored-by: Chandler Prall --- src-docs/src/views/markdown_editor/mardown_editor_example.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index ae1efbde86d..ae001b049bd 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -36,7 +36,7 @@ export const MarkdownEditorExample = { EuiMarkdownFormat . State is maintained between the two and it is possible to pass - changes from the preview area to the text area and vice versa. + changes from the preview area to the textarea and vice versa.

From bc908bcf3d762509858eabc021b4a2efc7428f74 Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Sun, 26 Jul 2020 19:14:30 -0700 Subject: [PATCH 13/17] feedback --- .../markdown_editor/mardown_editor_example.js | 14 +++++++------- .../markdown_editor_with_plugins.js | 11 ++++++++++- .../markdown_editor/markdown_plugin_example.js | 4 ++-- src/components/markdown_editor/index.ts | 2 +- .../markdown_editor/markdown_editor.tsx | 3 +-- .../markdown_editor/markdown_format.tsx | 16 ++++++++++------ .../{ => plugins}/markdown_default_plugins.tsx | 11 ++++------- 7 files changed, 35 insertions(+), 26 deletions(-) rename src/components/markdown_editor/{ => plugins}/markdown_default_plugins.tsx (88%) diff --git a/src-docs/src/views/markdown_editor/mardown_editor_example.js b/src-docs/src/views/markdown_editor/mardown_editor_example.js index ae001b049bd..02656a92af1 100644 --- a/src-docs/src/views/markdown_editor/mardown_editor_example.js +++ b/src-docs/src/views/markdown_editor/mardown_editor_example.js @@ -29,9 +29,9 @@ export const MarkdownEditorExample = {

EuiMarkdownEditor provides a markdown authoring experience for the user. The component consists of a toolbar, text - area, and a drag-and-drop zone to accept files. There are two modes: a - textarea that keeps track of cursor position, and a rendered preview - mode that is powered by{' '} + area, and a drag-and-drop zone to accept files (if configured to do + so). There are two modes: a textarea that keeps track of cursor + position, and a rendered preview mode that is powered by{' '} EuiMarkdownFormat @@ -81,10 +81,10 @@ export const MarkdownEditorExample = { text: (

The errors prop allows you to pass an array of - errors if syntax is malformed. Below the tooltip plugin is able to - provide this message by default. These errors are meant to be - ephemeral and part of the editing experience. They should not be a - substitute for{' '} + errors if syntax is malformed. The below example starts with an + incomplete tooltip tag, showing this error message by default. These + errors are meant to be ephemeral and part of the editing experience. + They should not be a substitute for{' '} form validation.

), diff --git a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js index 4f01e279cda..7d86dfc9c64 100644 --- a/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js +++ b/src-docs/src/views/markdown_editor/markdown_editor_with_plugins.js @@ -122,7 +122,16 @@ const chartDemoPlugin = { /> - +
+ +
diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index c366ffab81a..e993937580a 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -111,8 +111,8 @@ const uiPluginConcepts = [ title: 'formatter', description: ( - If no editor is provided, a React component can be - providing to handle formatting. + If no editor is provided, this is an object defining + how the plugins markdown tag is styled. ), }, diff --git a/src/components/markdown_editor/index.ts b/src/components/markdown_editor/index.ts index bf75c307cb9..299fb9f9357 100644 --- a/src/components/markdown_editor/index.ts +++ b/src/components/markdown_editor/index.ts @@ -21,7 +21,7 @@ export { EuiMarkdownEditor, EuiMarkdownEditorProps } from './markdown_editor'; export { EuiMarkdownDefaultParsingPlugins, EuiMarkdownDefaultProcessingPlugins, -} from './markdown_default_plugins'; +} from './plugins/markdown_default_plugins'; export { EuiMarkdownContext } from './markdown_context'; export { EuiMarkdownFormat } from './markdown_format'; export { diff --git a/src/components/markdown_editor/markdown_editor.tsx b/src/components/markdown_editor/markdown_editor.tsx index 6e3c67de336..b0771cf4af4 100644 --- a/src/components/markdown_editor/markdown_editor.tsx +++ b/src/components/markdown_editor/markdown_editor.tsx @@ -55,7 +55,7 @@ import * as MarkdownTooltip from './plugins/markdown_tooltip'; import { EuiMarkdownDefaultParsingPlugins, EuiMarkdownDefaultProcessingPlugins, -} from './markdown_default_plugins'; +} from './plugins/markdown_default_plugins'; type CommonMarkdownEditorProps = HTMLAttributes & CommonProps & { @@ -240,7 +240,6 @@ export const EuiMarkdownEditor: FunctionComponent< () => ({ textarea: textareaRef.current, replaceNode }), [replaceNode] ); - // console.log('editor', processor); return ( diff --git a/src/components/markdown_editor/markdown_format.tsx b/src/components/markdown_editor/markdown_format.tsx index 1b9ddbb17c1..4f942527cbc 100644 --- a/src/components/markdown_editor/markdown_format.tsx +++ b/src/components/markdown_editor/markdown_format.tsx @@ -22,7 +22,7 @@ import unified, { PluggableList } from 'unified'; import { EuiMarkdownDefaultProcessingPlugins, EuiMarkdownDefaultParsingPlugins, -} from './markdown_default_plugins'; +} from './plugins/markdown_default_plugins'; interface EuiMarkdownFormatProps { children: string; @@ -34,12 +34,16 @@ interface EuiMarkdownFormatProps { export const EuiMarkdownFormat: FunctionComponent = ({ children, - parsingPluginList, - processingPluginList, + parsingPluginList = EuiMarkdownDefaultParsingPlugins, + processingPluginList = EuiMarkdownDefaultProcessingPlugins, }) => { - const processor = unified() - .use(parsingPluginList || EuiMarkdownDefaultParsingPlugins) - .use(processingPluginList || EuiMarkdownDefaultProcessingPlugins); + const processor = useMemo( + () => + unified() + .use(parsingPluginList) + .use(processingPluginList), + [parsingPluginList, processingPluginList] + ); const result = useMemo(() => { try { return processor.processSync(children).contents; diff --git a/src/components/markdown_editor/markdown_default_plugins.tsx b/src/components/markdown_editor/plugins/markdown_default_plugins.tsx similarity index 88% rename from src/components/markdown_editor/markdown_default_plugins.tsx rename to src/components/markdown_editor/plugins/markdown_default_plugins.tsx index 07f31ec6ad9..174bfce7f31 100644 --- a/src/components/markdown_editor/markdown_default_plugins.tsx +++ b/src/components/markdown_editor/plugins/markdown_default_plugins.tsx @@ -18,18 +18,15 @@ */ import { PluggableList } from 'unified'; -// @ts-ignore TODO import remark2rehype from 'remark-rehype'; import rehype2react from 'rehype-react'; -import * as MarkdownTooltip from './plugins/markdown_tooltip'; -import * as MarkdownCheckbox from './plugins/markdown_checkbox'; +import * as MarkdownTooltip from './markdown_tooltip'; +import * as MarkdownCheckbox from './markdown_checkbox'; import React, { createElement } from 'react'; -import { EuiLink } from '../link'; -import { EuiCodeBlock, EuiCode } from '../code'; +import { EuiLink } from '../../link'; +import { EuiCodeBlock, EuiCode } from '../../code'; import markdown from 'remark-parse'; -// @ts-ignore TODO import highlight from 'remark-highlight.js'; -// @ts-ignore TODO import emoji from 'remark-emoji'; export const EuiMarkdownDefaultParsingPlugins: PluggableList = [ From 11dbc6a096cd53f9bc19befe3c5200a729532fea Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Mon, 27 Jul 2020 16:42:09 -0700 Subject: [PATCH 14/17] ignore markdown format page from a11y testing because it munges headings --- scripts/a11y-testing.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/a11y-testing.js b/scripts/a11y-testing.js index 39c9f5378b1..519da11072e 100644 --- a/scripts/a11y-testing.js +++ b/scripts/a11y-testing.js @@ -40,6 +40,7 @@ const docsPages = async (root, page) => { `${root}#/forms/date-picker`, `${root}#/forms/suggest`, `${root}#/forms/super-date-picker`, + `${root}#/editors-syntax/markdown-format`, `${root}#/elastic-charts/creating-charts`, `${root}#/elastic-charts/part-to-whole-comparisons`, `${root}#/utilities/css-utility-classes`, From befa0d05548146e8b334082e3f80a49302cf377a Mon Sep 17 00:00:00 2001 From: Dave Snider Date: Tue, 28 Jul 2020 00:32:04 -0700 Subject: [PATCH 15/17] ignore plugins format page from a11y testing because it munges headings --- scripts/a11y-testing.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/a11y-testing.js b/scripts/a11y-testing.js index 519da11072e..dc02dc645ca 100644 --- a/scripts/a11y-testing.js +++ b/scripts/a11y-testing.js @@ -41,6 +41,7 @@ const docsPages = async (root, page) => { `${root}#/forms/suggest`, `${root}#/forms/super-date-picker`, `${root}#/editors-syntax/markdown-format`, + `${root}#/editors-syntax/markdown-plugins`, `${root}#/elastic-charts/creating-charts`, `${root}#/elastic-charts/part-to-whole-comparisons`, `${root}#/utilities/css-utility-classes`, From 99b83475c77c84f0d44361e1b4fcc20b271ca37c Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Tue, 28 Jul 2020 09:56:50 -0600 Subject: [PATCH 16/17] Filled out the docs a bit more --- .../markdown_plugin_example.js | 102 ++++++++++++++---- 1 file changed, 84 insertions(+), 18 deletions(-) diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index e993937580a..0eae03692fd 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -13,6 +13,7 @@ import { EuiHorizontalRule, EuiCodeBlock, EuiCode, + EuiLink, } from '../../../../src/components'; import { Link } from 'react-router-dom'; @@ -144,14 +145,21 @@ export const MarkdownPluginExample = { {' '} utilize the same underlying plugin architecture to transform string based syntax into React components. At a high level{' '} - Unified JS is used in combination with{' '} - Remark to provide EUI's markdown components, - which are separated into a parsing and{' '} - processing layer. These two concepts are kept - distinct in EUI components to provide concrete locations for your - plugins to be injected, be it editing or rendering. Finally you - provide UI to the component to handle interactions - with the editor. + + Unified JS + {' '} + is used in combination with{' '} + + Remark + {' '} + to provide EUI's markdown components, which are separated into a{' '} + parsing and processing layer. These + two concepts are kept distinct in EUI components to provide concrete + locations for your plugins to be injected, be it editing or rendering. + Finally you provide UI to the component to handle + interactions with the editor.

In addition to running the full pipeline,{' '} @@ -185,7 +193,7 @@ export const MarkdownPluginExample = { descriptionProps={{ style: { width: '80%' } }} /> - +

uiPlugin

@@ -202,7 +210,7 @@ export const MarkdownPluginExample = { descriptionProps={{ style: { width: '80%' } }} /> - +

parsingPluginList

@@ -257,22 +265,80 @@ export const MarkdownPluginExample = { - - Chandler, a simple parser example here maybe? - + {`// example plugin parser +function EmojiMarkdownParser() { + const Parser = this.Parser; + const tokenizers = Parser.prototype.inlineTokenizers; + const methods = Parser.prototype.inlineMethods; + + const emojiMap = { + wave: '👋', + smile: '😀', + plane: '🛩', + }; + const emojiNames = Object.keys(emojiMap); + + // function to parse a matching string + function tokenizeEmoji(eat, value, silent) { + const tokenMatch = value.match(/^:(.*?):/); + + if (!tokenMatch) return false; // no match + const [, emojiName] = tokenMatch; + + // ensure we know this one + if (emojiNames.indexOf(emojiName) === -1) return false; + + if (silent) { + return true; + } + + // must consume the exact & entire match string + return eat(\`:\${emojiName}:\`)({ + type: 'emojiPlugin', + emoji: emojiMap[emojiName], // configuration is passed to the renderer + }); + } + + // function to detect where the next emoji match might be found + tokenizeEmoji.locator = (value, fromIndex) => { + return value.indexOf(':', fromIndex); + }; + + // define the emoji plugin and inject it just before the existing text plugin + tokenizers.emoji = tokenizeEmoji; + methods.splice(methods.indexOf('text'), 0, 'emoji'); +}`} - +

processingPluginList

-

This needs some explanation

+

+ After parsing the input into an AST, the nodes need to be transformed + into React elements. This is performed by a list of processors, the + default set converts remark AST into rehype and then into React. + Plugins need to define themselves within this transformation process, + identifying with the same type its parser uses in its{' '} + eat call. +

- - Chandler, a simple processingPluginList example - + {`// example plugin processor + +// convert remark nodes to rehype, basically a pass through +const emojiMarkdownHandler = (h, node) => { + return h(node.position, 'emojiPlugin', node, []); +}; +// receives the configuration from the parser and renders +const EmojiMarkdownRenderer = ({ emoji }) => { + return {emoji}; +}; + +// add the handler & renderer for \`emojiPlugin\` +processingList[0][1].handlers.emojiPlugin = emojiMarkdownHandler; +processingList[1][1].components.emojiPlugin = EmojiMarkdownRenderer;`} ), From 160b852cf69c3001c928ef16a73e05b98e16528c Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Tue, 28 Jul 2020 11:01:13 -0600 Subject: [PATCH 17/17] Clean up accessibility issues --- scripts/a11y-testing.js | 2 - .../views/markdown_editor/markdown-example.md | 141 ------------------ .../markdown_plugin_example.js | 2 +- .../plugins/markdown_checkbox.tsx | 4 +- 4 files changed, 3 insertions(+), 146 deletions(-) delete mode 100644 src-docs/src/views/markdown_editor/markdown-example.md diff --git a/scripts/a11y-testing.js b/scripts/a11y-testing.js index dc02dc645ca..39c9f5378b1 100644 --- a/scripts/a11y-testing.js +++ b/scripts/a11y-testing.js @@ -40,8 +40,6 @@ const docsPages = async (root, page) => { `${root}#/forms/date-picker`, `${root}#/forms/suggest`, `${root}#/forms/super-date-picker`, - `${root}#/editors-syntax/markdown-format`, - `${root}#/editors-syntax/markdown-plugins`, `${root}#/elastic-charts/creating-charts`, `${root}#/elastic-charts/part-to-whole-comparisons`, `${root}#/utilities/css-utility-classes`, diff --git a/src-docs/src/views/markdown_editor/markdown-example.md b/src-docs/src/views/markdown_editor/markdown-example.md deleted file mode 100644 index 56f9fbdd166..00000000000 --- a/src-docs/src/views/markdown_editor/markdown-example.md +++ /dev/null @@ -1,141 +0,0 @@ -# h1 Heading -## h2 Heading -### h3 Heading -#### h4 Heading -##### h5 Heading -###### h6 Heading - - -### Emphasis - -**This is bold text** - -__This is bold text__ - -*This is italic text* - -_This is italic text_ - -~~Strikethrough~~ - - -### Horizontal Rules - -___ - ---- - -*** - - -## Blockquotes - - -> Blockquotes can also be nested... ->> ...by using additional greater-than signs right next to each other... -> > > ...or with spaces between arrows. - - -## Lists - -Unordered - -* Item 1 -* Item 2 - * Item 2a - * Item 2b - -Ordered - -1. Item 1 -1. Item 2 -1. Item 3 - 1. Item 3a - 1. Item 3b - - -## Task Lists - -- [x] Create a new project -- [x] Give your project a name -- [ ] Add a new column - -## Another Task Lists - -* [x] Create a new project -* [x] Give your project a name -* [ ] Add a new column - - -## Code - -Inline `` is awesome! - -Block code "fences" - -``` -Sample text here... -``` - -Syntax highlighting JS - -``` js -var foo = function (bar) { - return bar++; -}; - -console.log(foo(5)); -``` - -Syntax highlighting Java - -``` java -package l2f.gameserver.model; - -public abstract class L2Char extends L2Object { - public static final Short ERROR = 0x0001; - - public void moveTo(int x, int y, int z) { - _ai = null; - log("Should not be called"); - if (1 > 5) { // wtf!? - return; - } - } -} -``` - -## Tables - -| Option | Description | -| ------ | ----------- | -| data | path to data files to supply the data that will be passed into templates. | -| engine | engine to be used for processing templates. Handlebars is the default. | -| ext | extension to be used for dest files. | - -Right aligned columns - -| Option | Description | -| ------:| -----------:| -| data | path to data files to supply the data that will be passed into templates. | -| engine | engine to be used for processing templates. Handlebars is the default. | -| ext | extension to be used for dest files. | - - -## Links - -[link text](http://dev.nodeca.com) - -[link with title](http://nodeca.github.io/pica/demo/ "title text!") - -Autoconverted link https://github.com/nodeca/pica (enable linkify to see) - - -## Images - -![Kibana](https://user-images.githubusercontent.com/2750668/74490344-2f271800-4ec0-11ea-8614-8651cd47eab1.png) - - -### [Emojies](https://github.com/markdown-it/markdown-it-emoji) - -> Classic markup: :wink: :cry: :laughing: :yum: diff --git a/src-docs/src/views/markdown_editor/markdown_plugin_example.js b/src-docs/src/views/markdown_editor/markdown_plugin_example.js index 0eae03692fd..627c34d78a3 100644 --- a/src-docs/src/views/markdown_editor/markdown_plugin_example.js +++ b/src-docs/src/views/markdown_editor/markdown_plugin_example.js @@ -171,7 +171,7 @@ export const MarkdownPluginExample = { -

Plugin development

+

Plugin development

diff --git a/src/components/markdown_editor/plugins/markdown_checkbox.tsx b/src/components/markdown_editor/plugins/markdown_checkbox.tsx index 339fb702838..f1ec36d7e10 100644 --- a/src/components/markdown_editor/plugins/markdown_checkbox.tsx +++ b/src/components/markdown_editor/plugins/markdown_checkbox.tsx @@ -47,12 +47,12 @@ const CheckboxParser: Plugin = function CheckboxParser() { silent ) { /** - * optional leading whitespace & single dash mix + * optional leading whitespace & single (dash or asterisk) mix * square brackets, optionally containing whitespace and `x` * optional whitespace * remainder of the line is consumed as the textbox label */ - const checkboxMatch = value.match(/^(\s*-\s*)?\[([\sx]*)\](.+)/); + const checkboxMatch = value.match(/^(\s*[-*]\s*)?\[([\sx]*)\](.+)/); if (checkboxMatch == null) return false; if (silent) {