From d86dc13d4260a154d99ecb289a77088df25bde50 Mon Sep 17 00:00:00 2001 From: Greg Thompson Date: Tue, 4 May 2021 11:47:44 -0500 Subject: [PATCH] [EuiCodeBlock] Replace highlight.js with prism.js via refractor (#4638) * replace highlight.js with prism.js via refractor * merge fix: file renamed * group lines * update tests * i18n block * remove regex lookbehind not supported in safari * CL * js -> jsx * token color parity * update line grouping method * use jsx language in docs * reduce newlines * docs * docs * headings * Update src-docs/src/views/code/code_example.js Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> * playground updates * os newlines Co-authored-by: Caroline Horn <549577+cchaos@users.noreply.github.com> --- CHANGELOG.md | 6 + package.json | 5 +- .../guide_section_code.tsx | 2 +- src-docs/src/services/playground/knobs.js | 18 +- .../src/services/playground/playground.js | 2 +- src-docs/src/views/code/code_block.js | 2 +- src-docs/src/views/code/code_block_pre.js | 2 +- src-docs/src/views/code/code_example.js | 38 ++- src-docs/src/views/code/playground.js | 12 - src-docs/src/views/package/i18n_tokens.js | 2 +- .../__snapshots__/_code_block.test.tsx.snap | 18 +- .../code/__snapshots__/code.test.tsx.snap | 2 +- .../__snapshots__/code_block.test.tsx.snap | 172 ++++++++----- src/components/code/_code_block.scss | 106 ++++---- src/components/code/_code_block.test.tsx | 40 ++- src/components/code/_code_block.tsx | 234 ++++++++++++------ src/components/code/code.test.tsx | 14 +- src/components/code/code_block.test.tsx | 38 ++- .../plugins/markdown_default_plugins.tsx | 16 +- .../plugins/remark/remark_prismjs.ts | 48 ++++ .../markdown_editor/unified-plugins.d.ts | 6 - yarn.lock | 125 ++++++---- 22 files changed, 570 insertions(+), 338 deletions(-) create mode 100644 src/components/markdown_editor/plugins/remark/remark_prismjs.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b1838a37a33..0c85b332e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,18 @@ - Updated `EuiBetaBadge, EuiBadge, EuiButtonIcon, EuiButtonContent, EuiCallOut, EuiContextMenuItem, EuiListGroupItem` icon usage to inherit their parent's color ([#4760](https://github.com/elastic/eui/pull/4760)) - Added `iconProps` prop to `EuiListGroupItem` ([#4760](https://github.com/elastic/eui/pull/4760)) - Added `i18ntokens.json` to published package ([#4771](https://github.com/elastic/eui/pull/4771)) +- Replaced `highlight.js` with `prism.js`/`refractor` for code syntax highlighting in `EuiCodeBlock` ([#4638](https://github.com/elastic/eui/pull/4638)) **Bug fixes** - Fixed `initialFocus` prop functionality in `EuiPopover` ([#4768](https://github.com/elastic/eui/pull/4768)) - Fixed `description` prop in `EuiTable`([#4754](https://github.com/elastic/eui/pull/4754)) +**Breaking changes** + +- Changed some language syntax references in `EuiCodeBlock`, such as `jsx` ([#4638](https://github.com/elastic/eui/pull/4638)) +- Removed ability to parse non-string content in `EuiCodeBlock` ([#4638](https://github.com/elastic/eui/pull/4638)) + ## [`32.3.0`](https://github.com/elastic/eui/tree/v32.3.0) - Reduced icon size in `EuiButtonEmpty` of `size` xs. ([#4759](https://github.com/elastic/eui/pull/4759)) diff --git a/package.json b/package.json index 8246f190033..1fe33887b33 100644 --- a/package.json +++ b/package.json @@ -53,11 +53,11 @@ "@types/react-input-autosize": "^2.2.0", "@types/react-virtualized-auto-sizer": "^1.0.0", "@types/react-window": "^1.8.2", + "@types/refractor": "^3.0.0", "@types/resize-observer-browser": "^0.1.5", "@types/vfile-message": "^2.0.0", "chroma-js": "^2.1.0", "classnames": "^2.2.6", - "highlight.js": "^9.18.5", "lodash": "^4.17.21", "numeral": "^2.0.6", "prop-types": "^15.6.0", @@ -69,11 +69,11 @@ "react-is": "~16.3.0", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.8.5", + "refractor": "^3.3.1", "rehype-raw": "^5.0.0", "rehype-react": "^6.0.0", "rehype-stringify": "^8.0.0", "remark-emoji": "^2.1.0", - "remark-highlight.js": "^6.0.0", "remark-parse": "^8.0.3", "remark-rehype": "^8.0.0", "tabbable": "^3.0.0", @@ -106,7 +106,6 @@ "@svgr/plugin-svgo": "^4.0.3", "@types/classnames": "^2.2.10", "@types/enzyme": "^3.10.5", - "@types/highlight.js": "^9.12.4", "@types/jest": "^24.0.6", "@types/node": "^10.17.5", "@types/react": "^16.9.34", diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx index b2fc48fa16d..db330c7dcba 100644 --- a/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx @@ -34,7 +34,7 @@ export const GuideSectionExampleCode: FunctionComponent return ( <> - + {codeToRender} {codeSandboxLink} diff --git a/src-docs/src/services/playground/knobs.js b/src-docs/src/services/playground/knobs.js index ca38441a3fc..92b4eac1d49 100644 --- a/src-docs/src/services/playground/knobs.js +++ b/src-docs/src/services/playground/knobs.js @@ -27,7 +27,7 @@ import { } from '../../../../src/components/'; export const markup = (text) => { - const regex = /(#[a-zA-Z]+)|(`[^`]+`)/g; + const regex = /(\B#[a-zA-Z]+)|(`[^`]+`)/g; return text.split('\n').map((token) => { const values = token.split(regex).map((token, index) => { if (!token) { @@ -382,7 +382,7 @@ const KnobColumn = ({ state, knobNames, error, set, isPlayground }) => { if (humanizedType) { typeMarkup = humanizedType && ( - {markup(humanizedType)} + {humanizedType} ); const functionMatches = [ @@ -392,17 +392,19 @@ const KnobColumn = ({ state, knobNames, error, set, isPlayground }) => { const types = humanizedType.split(/\([^=]*\) =>\s\w*\)*/); if (functionMatches.length > 0) { - const elements = []; + let elements = ''; let j = 0; for (let i = 0; i < types.length; i++) { if (functionMatches[j]) { - elements.push(
{types[i]}
); - elements.push( -
{functionMatches[j][0]}
- ); + elements = + `${elements}` + + `${types[i]}` + + '\n' + + `${functionMatches[j][0]}` + + '\n'; j++; } else { - elements.push(
{types[i]}
); + elements = `${elements}` + `${types[i]}` + '\n'; } } typeMarkup = ( diff --git a/src-docs/src/services/playground/playground.js b/src-docs/src/services/playground/playground.js index dfd90dfe97f..545791b1ca0 100644 --- a/src-docs/src/services/playground/playground.js +++ b/src-docs/src/services/playground/playground.js @@ -81,7 +81,7 @@ export default ({ diff --git a/src-docs/src/views/code/code_block.js b/src-docs/src/views/code/code_block.js index 6e4c57df391..0314991a617 100644 --- a/src-docs/src/views/code/code_block.js +++ b/src-docs/src/views/code/code_block.js @@ -15,7 +15,7 @@ export default () => ( (
+ +

+ The EuiCode and EuiCodeBlock{' '} + components support{' '} + + all language syntaxes + {' '} + supported by the + prism{' '} + + library + + . +
+ The language prop can also be omitted to simply + render formatted but unhighlighted code. +

+

+ JSX code (often React) has distinct language syntaxes from the base + JavaScript and TypeScript languages. For these instances, use{' '} + language="jsx" or{' '} + language="tsx". +

+
+ + ), sections: [ { title: 'Inline', @@ -48,6 +81,7 @@ export const CodeExample = { snippet: codeSnippet, props: { EuiCode }, demo: , + playground: codeConfig, }, { title: 'Code block', @@ -72,6 +106,7 @@ export const CodeExample = { snippet: codeBlockSnippet, props: { EuiCodeBlock }, demo: , + playground: codeBlockConfig, }, { title: 'Code block and white-space', @@ -98,5 +133,4 @@ export const CodeExample = { demo: , }, ], - playground: [codeBlockConfig, codeConfig], }; diff --git a/src-docs/src/views/code/playground.js b/src-docs/src/views/code/playground.js index 1d9b7c56fd3..8067db896f0 100644 --- a/src-docs/src/views/code/playground.js +++ b/src-docs/src/views/code/playground.js @@ -20,12 +20,6 @@ export const codeBlockConfig = () => { hidden: false, }; - propsToUse.inline = { - ...propsToUse.inline, - type: PropTypes.Boolean, - value: false, - }; - return { config: { componentName: 'EuiCodeBlock', @@ -56,12 +50,6 @@ export const codeConfig = () => { hidden: false, }; - propsToUse.inline = { - ...propsToUse.inline, - type: PropTypes.Boolean, - value: false, - }; - return { config: { componentName: 'EuiCode', diff --git a/src-docs/src/views/package/i18n_tokens.js b/src-docs/src/views/package/i18n_tokens.js index a3aa941a069..ed7e86da4c2 100644 --- a/src-docs/src/views/package/i18n_tokens.js +++ b/src-docs/src/views/package/i18n_tokens.js @@ -37,7 +37,7 @@ const columns = [ render({ defString, highlighting }) { return ( diff --git a/src/components/code/__snapshots__/_code_block.test.tsx.snap b/src/components/code/__snapshots__/_code_block.test.tsx.snap index d2369076574..47443fe20a0 100644 --- a/src/components/code/__snapshots__/_code_block.test.tsx.snap +++ b/src/components/code/__snapshots__/_code_block.test.tsx.snap @@ -2,14 +2,14 @@ exports[`EuiCodeBlockImpl block highlights javascript code, adding "js" class 1`] = `
     
   
@@ -17,7 +17,7 @@ exports[`EuiCodeBlockImpl block highlights javascript code, adding "js" class 1` exports[`EuiCodeBlockImpl block renders a pre block tag 1`] = `
   
   
   
 
 `;
 
 exports[`EuiCodeBlockImpl inline renders an inline code tag 1`] = `
 
   
   
   
-  
+
-      
-        constvalue =
-        'State 1'
-      
+      const value = 'State 1'
     
" @@ -15,12 +12,9 @@ exports[`EuiCodeBlock dynamic content updates DOM when input changes 1`] = ` exports[`EuiCodeBlock dynamic content updates DOM when input changes 2`] = ` "
-
+
-      
-        constvalue =
-        'State 2'
-      
+      const value = 'State 2'
     
" @@ -28,7 +22,7 @@ exports[`EuiCodeBlock dynamic content updates DOM when input changes 2`] = ` exports[`EuiCodeBlock props fontSize l is rendered 1`] = `
   
   
-  
-    
-      var some = 'code';
-console.log(some);
-    
-  
-
- - - + var some = 'code'; +console.log(some); + +
+
+
+ + + + + + + + + + + +
+
-
-
+ +
`; exports[`EuiCodeBlock props language is rendered 1`] = `
     
-      var some = 'code';
-console.log(some);
+      
+        var some = 'code';
+
+      
+      
+        console.log(some);
+      
     
   
@@ -142,12 +198,12 @@ console.log(some); exports[`EuiCodeBlock props overflowHeight is rendered 1`] = `
     
   
   
   
   
   
   
 *::selection {
+  .prismjs > *::selection {
     // Only change the color if the variable IS a color
     // or else no highlight color shows up at all
     @if type-of($euiCodeBlockSelectedBackgroundColor) == color {
@@ -172,112 +176,130 @@
     }
   }
 
-  .hljs-comment,
-  .hljs-quote {
+  .token.punctuation:not(.interpolation-punctuation):not([class*='attr-']),
+  .token.namespace {
+    opacity: .7;
+  }
+
+  .token.comment,
+  .token.prolog,
+  .token.doctype,
+  .token.cdata,
+  .token.coord,
+  .token.blockquote {
     color: $euiCodeBlockCommentColor;
     font-style: italic;
   }
 
-  .hljs-selector-tag {
+  .token.selector {
     color: $euiCodeBlockSelectorTagColor;
-    font-weight: $euiCodeFontWeightBold;
   }
 
-  .hljs-string,
-  .hljs-subst,
-  .hljs-doctag {
+  .token.string,
+  .token.interpolation,
+  .token.interpolation-punctuation,
+  .token.doc-comment .token.keyword,
+  .token.attr-value,
+  .token.url .token.content {
     color: $euiCodeBlockStringColor;
   }
 
-  .hljs-number,
-  .hljs-literal,
-  .hljs-regexp,
-  .hljs-variable,
-  .hljs-template-variable,
-  .hljs-tag .hljs-attr {
+  .token.number,
+  .token.boolean,
+  .token.keyword.nil,
+  .token.regex,
+  .token.variable,
+  .token.unit,
+  .token.hexcode,
+  .token.attr-name,
+  .token.attr-equals {
     color: $euiCodeBlockNumberColor;
   }
 
-  .hljs-keyword {
+  .token.atrule .token.rule,
+  .token.keyword {
     color: $euiCodeBlockKeywordColor;
   }
 
-  .hljs-function > .hljs-title {
+  .token.function {
     color: $euiCodeBlockFunctionTitleColor;
   }
 
-  .hljs-tag {
+  .token.tag {
     color: $euiCodeBlockTagColor;
   }
 
-  .hljs-name {
-    color: $euiCodeBlockNameColor;
-  }
-
-  .hljs-type,
-  .hljs-class .hljs-title {
+  .token.class-name {
     color: $euiCodeBlockTypeColor;
   }
 
-  .hljs-attribute {
+  .token.property {
     color: $euiCodeBlockAttributeColor;
   }
 
-  .hljs-symbol,
-  .hljs-bullet,
-  .hljs-built_in,
-  .hljs-builtin-name,
-  .hljs-link {
+  .token.console,
+  .token.list-punctuation,
+  .token.url-reference,
+  .token.url .token.url {
     color: $euiCodeBlockSymbolColor;
   }
 
-  .hljs-params {
+  .token.paramater {
     color: $euiCodeBlockParamsColor;
   }
 
-  .hljs-meta {
+  .token.meta,
+  .token.important {
     color: $euiCodeBlockMetaColor;
   }
 
-  .hljs-title {
+  .token.title {
     color: $euiCodeBlockTitleColor;
   }
 
-  .hljs-section {
+  .token.section {
     color: $euiCodeBlockSectionColor;
   }
 
-  .hljs-addition,
-  .hljs-deletion {
+  .token.prefix.inserted,
+  .token.prefix.deleted {
     padding-left: $euiSizeXS;
     margin-left: -$euiSizeXS;
   }
 
-  .hljs-addition {
+  .token.prefix.inserted {
     box-shadow: -$euiSizeXS 0 $euiCodeBlockAdditionColor;
+    color: $euiCodeBlockAdditionColor;
   }
 
-  .hljs-deletion {
+  .token.prefix.deleted {
     box-shadow: -$euiSizeXS 0 $euiCodeBlockDeletionColor;
+    color: $euiCodeBlockDeletionColor;
   }
 
-  .hljs-selector-class {
+  .token.selector .token.class {
     color: $euiCodeBlockSelectorClassColor;
   }
 
-  .hljs-selector-id {
+  .token.selector .token.id {
     color: $euiCodeBlockSelectorIdColor;
   }
 
-  .hljs-emphasis {
+  .token.italic {
     font-style: italic;
   }
 
-  .hljs-strong {
+  .token.important,
+  .token.bold {
     font-weight: $euiCodeFontWeightBold;
   }
 
-  .hljs-link {
+  .token.url-reference,
+  .token.url .token.url {
     text-decoration: underline;
   }
+
+  .token.entity {
+    cursor: help;
+  }
 }
diff --git a/src/components/code/_code_block.test.tsx b/src/components/code/_code_block.test.tsx
index e95bbb2ad49..9961726acd1 100644
--- a/src/components/code/_code_block.test.tsx
+++ b/src/components/code/_code_block.test.tsx
@@ -18,83 +18,77 @@
  */
 
 import React from 'react';
-import { mount, ReactWrapper } from 'enzyme';
+import { render } from 'enzyme';
 import { requiredProps } from '../../test/required_props';
 
 import { EuiCodeBlockImpl } from './_code_block';
 
-function snapshotCodeBlock(component: ReactWrapper) {
-  // Get the Portal's sibling and return its html
-  const renderedHtml = component.find('Portal + *').html();
-  const container = document.createElement('div');
-  container.innerHTML = renderedHtml;
-  return container.firstChild;
-}
-
 const code = `var some = 'code';
 console.log(some);`;
 
 describe('EuiCodeBlockImpl', () => {
   describe('inline', () => {
     test('renders an inline code tag', () => {
-      const component = mount(
+      const component = render(
         
           {code}
         
       );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
 
     test('highlights javascript code, adding "js" class', () => {
-      const component = mount();
+      const component = render(
+        
+      );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
 
     test('renders with transparent background', () => {
-      const component = mount(
+      const component = render(
         
       );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
   });
 
   describe('block', () => {
     test('renders a pre block tag', () => {
-      const component = mount(
+      const component = render(
         
           {code}
         
       );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
 
     test('highlights javascript code, adding "js" class', () => {
-      const component = mount(
+      const component = render(
         
       );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
 
     test('renders with transparent background', () => {
-      const component = mount(
+      const component = render(
         
       );
 
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
 
     test('renders a pre block tag with a css class modifier', () => {
-      const component = mount(
+      const component = render(
         
           {code}
         
       );
-      expect(snapshotCodeBlock(component)).toMatchSnapshot();
+      expect(component).toMatchSnapshot();
     });
   });
 });
diff --git a/src/components/code/_code_block.tsx b/src/components/code/_code_block.tsx
index 131b8d8588e..f5b4493084c 100644
--- a/src/components/code/_code_block.tsx
+++ b/src/components/code/_code_block.tsx
@@ -17,17 +17,17 @@
  * under the License.
  */
 
-import classNames from 'classnames';
-import hljs from 'highlight.js';
 import React, {
   CSSProperties,
   FunctionComponent,
   KeyboardEvent,
+  ReactNode,
   useEffect,
-  useRef,
+  useMemo,
   useState,
 } from 'react';
-import { createPortal } from 'react-dom';
+import classNames from 'classnames';
+import { highlight, AST, RefractorNode } from 'refractor';
 import { keys, useCombinedRefs } from '../../services';
 import { EuiButtonIcon } from '../button';
 import { keysOf } from '../common';
@@ -39,6 +39,115 @@ import { useMutationObserver } from '../observer/mutation_observer';
 import { useResizeObserver } from '../observer/resize_observer';
 import { EuiOverlayMask } from '../overlay_mask';
 
+type ExtendedRefractorNode = RefractorNode & {
+  lineStart?: number;
+  lineEnd?: number;
+};
+
+const isAstElement = (node: RefractorNode): node is AST.Element =>
+  node.hasOwnProperty('type') && node.type === 'element';
+
+const nodeToHtml = (
+  node: RefractorNode,
+  idx: number,
+  nodes: RefractorNode[],
+  depth: number = 0
+): ReactNode => {
+  if (isAstElement(node)) {
+    const { properties, tagName, children } = node;
+
+    return React.createElement(
+      tagName,
+      {
+        ...properties,
+        key: `node-${depth}-${idx}`,
+        className: classNames(properties.className),
+      },
+      children && children.map((el, i) => nodeToHtml(el, i, nodes, depth + 1))
+    );
+  }
+
+  return node.value;
+};
+
+const addLineData = (
+  nodes: ExtendedRefractorNode[],
+  data = { lineNumber: 1 }
+): ExtendedRefractorNode[] => {
+  return nodes.reduce((result, node) => {
+    const lineStart = data.lineNumber;
+    if (node.type === 'text') {
+      if (!node.value.match(/\r\n?|\n/)) {
+        node.lineStart = lineStart;
+        node.lineEnd = lineStart;
+        result.push(node);
+      } else {
+        const lines = node.value.split(/\r\n?|\n/);
+        lines.forEach((line, i) => {
+          const num = i === 0 ? data.lineNumber : ++data.lineNumber;
+          result.push({
+            type: 'text',
+            value: i === lines.length - 1 ? line : `${line}\n`,
+            lineStart: num,
+            lineEnd: num,
+          });
+        });
+      }
+      return result;
+    }
+
+    if (node.children) {
+      const children = addLineData(node.children, data);
+      const first = children[0];
+      const last = children[children.length - 1];
+      const start = first.lineStart ?? lineStart;
+      const end = last.lineEnd ?? lineStart;
+      if (start !== end) {
+        children.forEach((node) => {
+          result.push(node);
+        });
+      } else {
+        node.lineStart = start;
+        node.lineEnd = end;
+        node.children = children;
+        result.push(node);
+      }
+      return result;
+    }
+
+    result.push(node);
+    return result;
+  }, []);
+};
+
+function wrapLines(nodes: ExtendedRefractorNode[]) {
+  const grouped: ExtendedRefractorNode[][] = [];
+  nodes.forEach((node) => {
+    const lineStart = node.lineStart! - 1;
+    if (grouped[lineStart]) {
+      grouped[lineStart].push(node);
+    } else {
+      grouped[lineStart] = [node];
+    }
+  });
+  const wrapped: RefractorNode[] = [];
+  grouped.forEach((node) => {
+    wrapped.push({
+      type: 'element',
+      tagName: 'span',
+      properties: {
+        className: ['euiCodeBlock__line'],
+      },
+      children: node,
+    });
+  });
+  return wrapped;
+}
+
+const highlightByLine = (children: string, language: string) => {
+  return wrapLines(addLineData(highlight(children, language)));
+};
+
 const fontSizeToClassNameMap = {
   s: 'euiCodeBlock--fontSmall',
   m: 'euiCodeBlock--fontMedium',
@@ -75,7 +184,7 @@ export interface EuiCodeBlockImplProps {
 
   /**
    * Sets the syntax highlighting for a specific language
-   * @see http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html#language-names-and-aliases
+   * @see https://github.com/wooorm/refractor#syntaxes
    * for options
    */
   language?: string;
@@ -108,20 +217,28 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
   ...rest
 }) => {
   const [isFullScreen, setIsFullScreen] = useState(false);
-  const [isPortalTargetReady, setIsPortalTargetReady] = useState(false);
-  const codeTarget = useRef(null);
-  const code = useRef(null);
   const [wrapperRef, setWrapperRef] = useState(null);
-  const [innerTextRef, innerText] = useInnerText('');
+  const [innerTextRef, _innerText] = useInnerText('');
+  const innerText = useMemo(
+    () => _innerText?.replace(/[\r\n?]{2}|\n\n/g, '\n'),
+    [_innerText]
+  );
   const [tabIndex, setTabIndex] = useState<-1 | 0>(-1);
   const combinedRef = useCombinedRefs([
     innerTextRef,
     setWrapperRef,
   ]);
   const { width, height } = useResizeObserver(wrapperRef);
-  const [codeFullScreen, setCodeFullScreen] = useState(
-    null
-  );
+
+  const content = useMemo(() => {
+    if (!language || typeof children !== 'string') {
+      return children;
+    }
+    const nodes = inline
+      ? highlight(children, language)
+      : highlightByLine(children, language);
+    return nodes.length === 0 ? children : nodes.map(nodeToHtml);
+  }, [children, language, inline]);
 
   const doesOverflow = () => {
     if (!wrapperRef) return;
@@ -140,42 +257,6 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
 
   useEffect(doesOverflow, [width, height, wrapperRef]);
 
-  useEffect(() => {
-    codeTarget.current = document.createElement('div');
-    setIsPortalTargetReady(true);
-  }, []);
-
-  useEffect(() => {
-    /**
-     * because React maintains a mapping between its Virtual DOM representation and the actual
-     * DOM elements (including text nodes), and hljs modifies the DOM structure which leads
-     * to React updating detached nodes, we render to a document fragment and
-     * copy from that fragment into the target elements
-     * (https://github.com/elastic/eui/issues/2322)
-     */
-    const html = isPortalTargetReady ? codeTarget.current!.innerHTML : '';
-
-    if (code.current) {
-      code.current.innerHTML = html;
-    }
-
-    if (language) {
-      if (code.current) {
-        hljs.highlightBlock(code.current);
-      }
-    }
-  });
-
-  useEffect(() => {
-    if (codeFullScreen) {
-      const html = isPortalTargetReady ? codeTarget.current!.innerHTML : '';
-      codeFullScreen.innerHTML = html;
-      if (language) {
-        hljs.highlightBlock(codeFullScreen);
-      }
-    }
-  }, [isPortalTargetReady, codeFullScreen, language]);
-
   const onKeyDown = (event: KeyboardEvent) => {
     if (event.key === keys.ESCAPE) {
       event.preventDefault();
@@ -201,6 +282,10 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
       'euiCodeBlock--inline': inline,
       'euiCodeBlock--hasControls': isCopyable || overflowHeight,
     },
+    {
+      prismjs: !className?.includes('prismjs'),
+      [`language-${language || 'none'}`]: !className?.includes('language'),
+    },
     className
   );
 
@@ -217,7 +302,11 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
     optionalStyles.maxHeight = overflowHeight;
   }
 
-  const codeSnippet = ;
+  const codeSnippet = (
+    
+      {content}
+    
+  );
 
   const wrapperProps = {
     className: classes,
@@ -225,12 +314,7 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
   };
 
   if (inline) {
-    return isPortalTargetReady ? (
-      <>
-        {createPortal(children, codeTarget.current!)}
-        {codeSnippet}
-      
-    ) : null;
+    return {codeSnippet};
   }
 
   const getCopyButton = (textToCopy?: string) => {
@@ -317,11 +401,9 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
           
             
-                
+                
+                  {content}
+                
               
{codeBlockControls} @@ -335,25 +417,21 @@ export const EuiCodeBlockImpl: FunctionComponent = ({ }; const codeBlockControls = getCodeBlockControls(innerText); - return isPortalTargetReady ? ( - <> - {createPortal(children, codeTarget.current!)} -
-
-          {codeSnippet}
-        
- - {/* + return ( +
+
+        {codeSnippet}
+      
+ {/* If the below fullScreen code renders, it actually attaches to the body because of EuiOverlayMask's React portal usage. */} - {codeBlockControls} - {getFullScreenDisplay(codeBlockControls)} -
- - ) : null; + {codeBlockControls} + {getFullScreenDisplay(codeBlockControls)} +
+ ); }; diff --git a/src/components/code/code.test.tsx b/src/components/code/code.test.tsx index 67a9fbd9a0e..6b25e2bafec 100644 --- a/src/components/code/code.test.tsx +++ b/src/components/code/code.test.tsx @@ -18,26 +18,18 @@ */ import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; +import { render } from 'enzyme'; import { requiredProps } from '../../test/required_props'; import { EuiCode } from './code'; -function snapshotCodeBlock(component: ReactWrapper) { - // Get the Portal's sibling and return its html - const renderedHtml = component.find('Portal + *').html(); - const container = document.createElement('div'); - container.innerHTML = renderedHtml; - return container.firstChild; -} - const code = `var some = 'code'; console.log(some);`; describe('EuiCode', () => { test('renders a code snippet', () => { - const component = mount({code}); + const component = render({code}); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); diff --git a/src/components/code/code_block.test.tsx b/src/components/code/code_block.test.tsx index 2843b61ff16..6015800ddaa 100644 --- a/src/components/code/code_block.test.tsx +++ b/src/components/code/code_block.test.tsx @@ -19,7 +19,7 @@ import React, { useState, useEffect } from 'react'; import ReactDOM from 'react-dom'; -import { mount, ReactWrapper } from 'enzyme'; +import { mount, render } from 'enzyme'; import html from 'html'; import { act } from 'react-dom/test-utils'; import { requiredProps } from '../../test/required_props'; @@ -27,34 +27,26 @@ import { requiredProps } from '../../test/required_props'; import { EuiCodeBlock } from './code_block'; import { FONT_SIZES, PADDING_SIZES } from './_code_block'; -function snapshotCodeBlock(component: ReactWrapper) { - // Get the Portal's sibling and return its html - const renderedHtml = component.find('Portal + *').html(); - const container = document.createElement('div'); - container.innerHTML = renderedHtml; - return container.firstChild; -} - const code = `var some = 'code'; console.log(some);`; describe('EuiCodeBlock', () => { test('renders a code block', () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); describe('props', () => { describe('transparentBackground', () => { it('is rendered', () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); @@ -62,38 +54,38 @@ describe('EuiCodeBlock', () => { it('is rendered', () => { const component = mount({code}); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); describe('overflowHeight', () => { it('is rendered', () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); describe('language', () => { it('is rendered', () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); describe('fontSize', () => { FONT_SIZES.forEach((fontSize) => { test(`${fontSize} is rendered`, () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); }); @@ -101,11 +93,11 @@ describe('EuiCodeBlock', () => { describe('paddingSize', () => { PADDING_SIZES.forEach((paddingSize) => { test(`${paddingSize} is rendered`, () => { - const component = mount( + const component = render( {code} ); - expect(snapshotCodeBlock(component)).toMatchSnapshot(); + expect(component).toMatchSnapshot(); }); }); }); @@ -131,7 +123,7 @@ describe('EuiCodeBlock', () => { const [value, setValue] = useState('State 1'); useEffect(() => { - // Wait a tick for EuiCodeBlock internal state to update on mount + // Wait a tick for EuiCodeBlock internal state to update on render setTimeout(() => { takeSnapshot(); act(() => { diff --git a/src/components/markdown_editor/plugins/markdown_default_plugins.tsx b/src/components/markdown_editor/plugins/markdown_default_plugins.tsx index 61e38eb175c..1303c0b8f04 100644 --- a/src/components/markdown_editor/plugins/markdown_default_plugins.tsx +++ b/src/components/markdown_editor/plugins/markdown_default_plugins.tsx @@ -17,6 +17,7 @@ * under the License. */ +import React, { createElement } from 'react'; // Importing seemingly unused types from `unified` because the definitions // are exported for two versions of TypeScript (3.4, 4.0) and implicit // imports during eui.d.ts generation default to the incorrect version (3.4). @@ -34,20 +35,19 @@ import { // eslint-disable-next-line @typescript-eslint/no-unused-vars Settings, } from 'unified'; -import remark2rehype from 'remark-rehype'; +import { Options as Remark2RehypeOptions, Handler } from 'mdast-util-to-hast'; +import all from 'mdast-util-to-hast/lib/all'; import rehype2react from 'rehype-react'; +import markdown from 'remark-parse'; +import emoji from 'remark-emoji'; +import remark2rehype from 'remark-rehype'; +import highlight from './remark/remark_prismjs'; import * as MarkdownTooltip from './markdown_tooltip'; import * as MarkdownCheckbox from './markdown_checkbox'; import { markdownLinkValidator } from './markdown_link_validator'; -import React, { createElement } from 'react'; +import { EuiMarkdownEditorUiPlugin } from './../markdown_types'; import { EuiLink } from '../../link'; import { EuiCodeBlock, EuiCode } from '../../code'; -import markdown from 'remark-parse'; -import highlight from 'remark-highlight.js'; -import emoji from 'remark-emoji'; -import all from 'mdast-util-to-hast/lib/all'; -import { Options as Remark2RehypeOptions, Handler } from 'mdast-util-to-hast'; -import { EuiMarkdownEditorUiPlugin } from './../markdown_types'; export const getDefaultEuiMarkdownParsingPlugins = (): PluggableList => [ [markdown, {}], diff --git a/src/components/markdown_editor/plugins/remark/remark_prismjs.ts b/src/components/markdown_editor/plugins/remark/remark_prismjs.ts new file mode 100644 index 00000000000..72b400fb0bb --- /dev/null +++ b/src/components/markdown_editor/plugins/remark/remark_prismjs.ts @@ -0,0 +1,48 @@ +/* + * 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 refractor from 'refractor'; +import visit from 'unist-util-visit'; +import { Plugin } from 'unified'; + +const attacher: Plugin = () => { + return (ast) => visit(ast, 'code', visitor); + + function visitor(node: any) { + const { data = {}, lang: language } = node; + + if (!language) { + return; + } + + node.data = data; + data.hChildren = refractor.highlight(node.value, language); + data.hProperties = { + ...data.hProperties, + language, + className: [ + 'prismjs', + ...(data.hProperties?.className || []), + `language-${language}`, + ], + }; + } +}; + +export default attacher; diff --git a/src/components/markdown_editor/unified-plugins.d.ts b/src/components/markdown_editor/unified-plugins.d.ts index 53b661079a4..a1596876d10 100644 --- a/src/components/markdown_editor/unified-plugins.d.ts +++ b/src/components/markdown_editor/unified-plugins.d.ts @@ -24,12 +24,6 @@ declare module 'remark-emoji' { export = RemarkEmoji; } -declare module 'remark-highlight.js' { - import { Plugin } from 'unified'; - const RemarkHighlight: Plugin; - export = RemarkHighlight; -} - declare module 'mdast-util-to-hast/lib/all' { // eslint-disable-next-line import/no-unresolved import { Node } from 'unist'; diff --git a/yarn.lock b/yarn.lock index 66d75609eeb..ba568f64049 100755 --- a/yarn.lock +++ b/yarn.lock @@ -1796,11 +1796,6 @@ dependencies: "@types/unist" "*" -"@types/highlight.js@^9.12.4": - version "9.12.4" - resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.4.tgz#8c3496bd1b50cc04aeefd691140aa571d4dbfa34" - integrity sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww== - "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" @@ -1902,6 +1897,11 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== +"@types/prismjs@*": + version "1.16.3" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.16.3.tgz#73ae78b3e339777a1a1b7a8df89dcd6b8fe750c5" + integrity sha512-7lbX0Odbg9rnzXRdYdgPQZFkjd38QHpD6tvWxbLi6VXGQbXr054doixIS+TwftHP6afffA1zxCZrIcJRS/MkYQ== + "@types/prop-types@*": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" @@ -1979,6 +1979,13 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/refractor@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.0.tgz#c535cfad1c54cf377ae2984f6cf6e9627a36ea66" + integrity sha512-jkCqkTpxMXXfN03Xpzj+mBMxo9IxG616SV2U42iwHkBGq/f8RrX3DCzLayIqUV+MAIBCUvl5xPnjqpUtZRnMqA== + dependencies: + "@types/prismjs" "*" + "@types/resize-observer-browser@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23" @@ -4067,6 +4074,15 @@ cli-width@^3.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== +clipboard@^2.0.0: + version "2.0.7" + resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-2.0.7.tgz#da927f817b1859426df39212ac81feb07dbaeadc" + integrity sha512-8M8WEZcIvs0hgOma+wAPkrUxpv0PMY1L6VsAJh/2DOKARIMpyWe6ZLcEoe1qktl6/ced5ceYHs+oGedSbgZ3sg== + dependencies: + good-listener "^1.2.2" + select "^1.1.2" + tiny-emitter "^2.0.0" + cliui@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" @@ -5212,6 +5228,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegate@^3.1.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166" + integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" @@ -6605,13 +6626,6 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fault@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" - integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== - dependencies: - format "^0.2.0" - faye-websocket@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" @@ -7005,11 +7019,6 @@ form-data@~2.3.2: combined-stream "1.0.6" mime-types "^2.1.12" -format@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" - integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -7621,6 +7630,13 @@ gonzales-pe@^4.0.3, gonzales-pe@^4.2.2: dependencies: minimist "^1.2.5" +good-listener@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50" + integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA= + dependencies: + delegate "^3.1.2" + got@^10.7.0: version "10.7.0" resolved "https://registry.yarnpkg.com/got/-/got-10.7.0.tgz#62889dbcd6cca32cd6a154cc2d0c6895121d091f" @@ -7940,6 +7956,17 @@ hastscript@^5.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -7950,16 +7977,6 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.18.5: - version "9.18.5" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.5.tgz#d18a359867f378c138d6819edfc2a8acd5f29825" - integrity sha512-a5bFyofd/BHCX52/8i8uJkjr9DYwXIPnM/plwI6W7ezItLGqzt7X2G2nXuYSfsIJdkwwj/g9DG1LkcGJI/dDoA== - -highlight.js@~10.1.0: - version "10.1.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.1.2.tgz#c20db951ba1c22c055010648dfffd7b2a968e00c" - integrity sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA== - history@^4.9.0: version "4.10.1" resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" @@ -10216,14 +10233,6 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lowlight@^1.2.0: - version "1.14.0" - resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.14.0.tgz#83ebc143fec0f9e6c0d3deffe01be129ce56b108" - integrity sha512-N2E7zTM7r1CwbzwspPxJvmjAbxljCPThTFawEX2Z7+P3NGrrvY54u8kyU16IY4qWfoVIxY8SYCS8jTkuG7TqYA== - dependencies: - fault "^1.0.0" - highlight.js "~10.1.0" - lru-cache@^4.0.0, lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -12867,9 +12876,16 @@ pretty-hrtime@^1.0.3: integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= prism-react-renderer@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44" - integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug== + version "1.2.0" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz#5ad4f90c3e447069426c8a53a0eafde60909cdf4" + integrity sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg== + +prismjs@~1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.23.0.tgz#d3b3967f7d72440690497652a9d40ff046067f33" + integrity sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA== + optionalDependencies: + clipboard "^2.0.0" process-nextick-args@^1.0.6: version "1.0.7" @@ -13687,6 +13703,15 @@ reflect.ownkeys@^0.2.0: resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" integrity sha1-dJrO7H8/34tj+SegSAnpDFwLNGA= +refractor@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.3.1.tgz#ebbc04b427ea81dc25ad333f7f67a0b5f4f0be3a" + integrity sha512-vaN6R56kLMuBszHSWlwTpcZ8KTMG6aUCok4GrxYDT20UIOXxOc5o6oDc8tNTzSlH3m2sI+Eu9Jo2kVdDcUTWYw== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.23.0" + regenerate-unicode-properties@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz#e5de7111d655e7ba60c057dbe9ff37c87e65cdec" @@ -13826,14 +13851,6 @@ remark-emoji@^2.1.0: node-emoji "^1.10.0" unist-util-visit "^2.0.2" -remark-highlight.js@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/remark-highlight.js/-/remark-highlight.js-6.0.0.tgz#cb05387ddddeb078f0b61ce6299f6323f05098fc" - integrity sha512-eNHP/ezuDKoeh3KV+rWLeBVzU3SYVt0uu1tHdsCB6TUJYHwTNMJWZ5+nU+2fJHQXb+7PtpfGXVtFS9zk/W6qdw== - dependencies: - lowlight "^1.2.0" - unist-util-visit "^2.0.0" - remark-math@1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/remark-math/-/remark-math-1.0.4.tgz#ced46473075ff99e4678a154a1a3d0dde403a9f2" @@ -14490,6 +14507,11 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo= +select@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" + integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= + selfsigned@^1.10.7: version "1.10.8" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" @@ -15871,6 +15893,11 @@ timsort@^0.3.0: resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= +tiny-emitter@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + tiny-invariant@^1.0.2, tiny-invariant@^1.0.6: version "1.1.0" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" @@ -16671,9 +16698,9 @@ vfile-location@^2.0.0: integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== vfile-location@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.1.0.tgz#81cd8a04b0ac935185f4fce16f270503fc2f692f" - integrity sha512-FCZ4AN9xMcjFIG1oGmZKo61PjwJHRVA+0/tPUP2ul4uIwjGGndIxavEMRpWn5p4xwm/ZsdXp9YNygf1ZyE4x8g== + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== vfile-message@*, vfile-message@^2.0.0: version "2.0.4"