diff --git a/docs/src/demos/Examples/Book/index.vue b/docs/src/demos/Examples/Book/index.vue index 5558e0c2..8d0848dc 100644 --- a/docs/src/demos/Examples/Book/index.vue +++ b/docs/src/demos/Examples/Book/index.vue @@ -138,6 +138,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue new file mode 100644 index 00000000..f5095930 --- /dev/null +++ b/docs/src/demos/Examples/CodeBlockLanguage/CodeBlockComponent.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/docs/src/demos/Examples/CodeBlockLanguage/index.vue b/docs/src/demos/Examples/CodeBlockLanguage/index.vue new file mode 100644 index 00000000..a1e8fbfe --- /dev/null +++ b/docs/src/demos/Examples/CodeBlockLanguage/index.vue @@ -0,0 +1,152 @@ + + + + + diff --git a/docs/src/demos/Examples/CollaborativeEditing/index.vue b/docs/src/demos/Examples/CollaborativeEditing/index.vue index 5be639b5..ef1fd0f8 100644 --- a/docs/src/demos/Examples/CollaborativeEditing/index.vue +++ b/docs/src/demos/Examples/CollaborativeEditing/index.vue @@ -285,6 +285,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/Default/React/styles.scss b/docs/src/demos/Examples/Default/React/styles.scss index dc996245..9ba5fc02 100644 --- a/docs/src/demos/Examples/Default/React/styles.scss +++ b/docs/src/demos/Examples/Default/React/styles.scss @@ -32,6 +32,7 @@ code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/Default/Vue/index.vue b/docs/src/demos/Examples/Default/Vue/index.vue index 5800130b..89b7c5b5 100644 --- a/docs/src/demos/Examples/Default/Vue/index.vue +++ b/docs/src/demos/Examples/Default/Vue/index.vue @@ -163,6 +163,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/Formatting/index.vue b/docs/src/demos/Examples/Formatting/index.vue index 81dd2fd5..978a1c62 100644 --- a/docs/src/demos/Examples/Formatting/index.vue +++ b/docs/src/demos/Examples/Formatting/index.vue @@ -138,6 +138,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/MarkdownShortcuts/index.vue b/docs/src/demos/Examples/MarkdownShortcuts/index.vue index 3716dc7c..33d82e8f 100644 --- a/docs/src/demos/Examples/MarkdownShortcuts/index.vue +++ b/docs/src/demos/Examples/MarkdownShortcuts/index.vue @@ -89,6 +89,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Examples/Tables/index.vue b/docs/src/demos/Examples/Tables/index.vue index 90923f63..67531de8 100644 --- a/docs/src/demos/Examples/Tables/index.vue +++ b/docs/src/demos/Examples/Tables/index.vue @@ -176,6 +176,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Experiments/GlobalDragHandle/index.vue b/docs/src/demos/Experiments/GlobalDragHandle/index.vue index b3cbd121..af34c427 100644 --- a/docs/src/demos/Experiments/GlobalDragHandle/index.vue +++ b/docs/src/demos/Experiments/GlobalDragHandle/index.vue @@ -105,6 +105,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Experiments/MultipleEditors/index.vue b/docs/src/demos/Experiments/MultipleEditors/index.vue index ab9454e8..00199714 100644 --- a/docs/src/demos/Experiments/MultipleEditors/index.vue +++ b/docs/src/demos/Experiments/MultipleEditors/index.vue @@ -203,6 +203,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Guide/Content/ExportHTML/index.vue b/docs/src/demos/Guide/Content/ExportHTML/index.vue index c91eb78d..e4e3e8bb 100644 --- a/docs/src/demos/Guide/Content/ExportHTML/index.vue +++ b/docs/src/demos/Guide/Content/ExportHTML/index.vue @@ -145,6 +145,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Guide/Content/ExportJSON/index.vue b/docs/src/demos/Guide/Content/ExportJSON/index.vue index 9c9cf014..1af2d3c6 100644 --- a/docs/src/demos/Guide/Content/ExportJSON/index.vue +++ b/docs/src/demos/Guide/Content/ExportJSON/index.vue @@ -152,6 +152,7 @@ export default { code { color: inherit; + padding: 0; background: none; font-size: 0.8rem; } diff --git a/docs/src/demos/Nodes/CodeBlock/index.vue b/docs/src/demos/Nodes/CodeBlock/index.vue index 54b3c676..9246e4f8 100644 --- a/docs/src/demos/Nodes/CodeBlock/index.vue +++ b/docs/src/demos/Nodes/CodeBlock/index.vue @@ -61,3 +61,27 @@ export default { }, } + + diff --git a/docs/src/demos/Nodes/CodeBlockLowlight/index.vue b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue new file mode 100644 index 00000000..69976deb --- /dev/null +++ b/docs/src/demos/Nodes/CodeBlockLowlight/index.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/docs/src/docPages/api/nodes/code-block-lowlight.md b/docs/src/docPages/api/nodes/code-block-lowlight.md index 97fc8537..c5df20c1 100644 --- a/docs/src/docPages/api/nodes/code-block-lowlight.md +++ b/docs/src/docPages/api/nodes/code-block-lowlight.md @@ -1,7 +1,37 @@ # CodeBlockLowlight +[![Version](https://img.shields.io/npm/v/@tiptap/extension-code-block-lowlight.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight) +[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-code-block-lowlight.svg)](https://npmcharts.com/compare/@tiptap/extension-code-block-lowlight?minimal=true) -:::pro Fund the development ♥ -We need your support to maintain, update, support and develop tiptap 2. If you’re waiting for this extension, [become a sponsor and fund our work](/sponsor). -::: +With the CodeBlock extension you can add fenced code blocks to your documents. It’ll wrap the code in `
` and `` HTML tags.
 
-TODO
+Type ```  (three backticks and a space) or ∼∼∼  (three tildes and a space) and a code block is instantly added for you. You can even specify the language, try writing ```css . That should add a `language-css` class to the ``-tag.
+
+## Installation
+```bash
+# with npm
+npm install @tiptap/extension-code-block-lowlight
+
+# with Yarn
+yarn add @tiptap/extension-code-block-lowlight
+```
+
+## Settings
+| Option              | Type     | Default       | Description                                                           |
+| ------------------- | -------- | ------------- | --------------------------------------------------------------------- |
+| HTMLAttributes      | `Object` | `{}`          | Custom HTML attributes that should be added to the rendered HTML tag. |
+| languageClassPrefix | `String` | `'language-'` | Adds a prefix to language classes that are applied to code tags.      |
+
+## Commands
+| Command   | Parameters | Description                   |
+| --------- | ---------- | ----------------------------- |
+| codeBlock | —          | Wrap content in a code block. |
+
+## Keyboard shortcuts
+* Windows/Linux: `Control` `Alt` `C`
+* macOS: `Cmd` `Alt` `C`
+
+## Source code
+[packages/extension-code-block-lowlight/](https://github.com/ueberdosis/tiptap-next/blob/main/packages/extension-code-block-lowlight/)
+
+## Usage
+
diff --git a/docs/src/docPages/examples/code-block-language.md b/docs/src/docPages/examples/code-block-language.md
new file mode 100644
index 00000000..bdac1b35
--- /dev/null
+++ b/docs/src/docPages/examples/code-block-language.md
@@ -0,0 +1,3 @@
+# Code block language
+
+
diff --git a/docs/src/links.yaml b/docs/src/links.yaml
index c526fac2..33f95d42 100644
--- a/docs/src/links.yaml
+++ b/docs/src/links.yaml
@@ -86,6 +86,9 @@
       link: /examples/savvy
     - title: Interactivity
       link: /examples/interactivity
+    - title: Code block language
+      link: /examples/code-block-language
+      type: new
 
 - title: Guide
   items:
@@ -155,7 +158,7 @@
           link: /api/nodes/code-block
         - title: CodeBlockLowlight
           link: /api/nodes/code-block-lowlight
-          type: draft
+          type: new
         - title: Document
           link: /api/nodes/document
         - title: Emoji
diff --git a/packages/core/src/Editor.ts b/packages/core/src/Editor.ts
index f5ff9f38..0c1f0651 100644
--- a/packages/core/src/Editor.ts
+++ b/packages/core/src/Editor.ts
@@ -57,6 +57,7 @@ export class Editor extends EventEmitter {
     parseOptions: {},
     enableInputRules: true,
     enablePasteRules: true,
+    onBeforeCreate: () => null,
     onCreate: () => null,
     onUpdate: () => null,
     onSelectionUpdate: () => null,
@@ -73,6 +74,8 @@ export class Editor extends EventEmitter {
     this.createExtensionManager()
     this.createCommandManager()
     this.createSchema()
+    this.on('beforeCreate', this.options.onCreate)
+    this.emit('beforeCreate', { editor: this })
     this.createView()
     this.injectCSS()
     this.on('create', this.options.onCreate)
diff --git a/packages/core/src/Extension.ts b/packages/core/src/Extension.ts
index 059b770c..f63ab802 100644
--- a/packages/core/src/Extension.ts
+++ b/packages/core/src/Extension.ts
@@ -99,6 +99,14 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts
index 364623a5..028db8d3 100644
--- a/packages/core/src/ExtensionManager.ts
+++ b/packages/core/src/ExtensionManager.ts
@@ -43,6 +43,10 @@ export default class ExtensionManager {
         }
       }
 
+      if (typeof extension.config.onBeforeCreate === 'function') {
+        this.editor.on('beforeCreate', extension.config.onBeforeCreate.bind(context))
+      }
+
       if (typeof extension.config.onCreate === 'function') {
         this.editor.on('create', extension.config.onCreate.bind(context))
       }
diff --git a/packages/core/src/Mark.ts b/packages/core/src/Mark.ts
index d2b2dbc9..819338f5 100644
--- a/packages/core/src/Mark.ts
+++ b/packages/core/src/Mark.ts
@@ -109,6 +109,15 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+      type: MarkType,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts
index b0facfb8..9b731ceb 100644
--- a/packages/core/src/Node.ts
+++ b/packages/core/src/Node.ts
@@ -114,6 +114,15 @@ declare module '@tiptap/core' {
       [key: string]: any,
     }) | null,
 
+    /**
+     * The editor is not ready yet.
+     */
+     onBeforeCreate?: ((this: {
+      options: Options,
+      editor: Editor,
+      type: NodeType,
+    }) => void) | null,
+
     /**
      * The editor is ready.
      */
diff --git a/packages/core/src/helpers/findChildren.ts b/packages/core/src/helpers/findChildren.ts
new file mode 100644
index 00000000..c580ec49
--- /dev/null
+++ b/packages/core/src/helpers/findChildren.ts
@@ -0,0 +1,22 @@
+import { Node as ProseMirrorNode } from 'prosemirror-model'
+import { Predicate } from '../types'
+
+type NodeWithPos = {
+  node: ProseMirrorNode,
+  pos: number,
+}
+
+export default function findChildren(node: ProseMirrorNode, predicate: Predicate): NodeWithPos[] {
+  const nodesWithPos: NodeWithPos[] = []
+
+  node.descendants((child, pos) => {
+    if (predicate(child)) {
+      nodesWithPos.push({
+        node: child,
+        pos,
+      })
+    }
+  })
+
+  return nodesWithPos
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 78e57616..9e650ac9 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -12,6 +12,9 @@ export { default as markPasteRule } from './pasteRules/markPasteRule'
 export { default as callOrReturn } from './utilities/callOrReturn'
 export { default as mergeAttributes } from './utilities/mergeAttributes'
 
+export { default as findChildren } from './helpers/findChildren'
+export { default as findParentNode } from './helpers/findParentNode'
+export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos'
 export { default as generateHTML } from './helpers/generateHTML'
 export { default as getSchema } from './helpers/getSchema'
 export { default as getHTMLFromFragment } from './helpers/getHTMLFromFragment'
@@ -22,7 +25,6 @@ export { default as isNodeActive } from './helpers/isNodeActive'
 export { default as isNodeEmpty } from './helpers/isNodeEmpty'
 export { default as isNodeSelection } from './helpers/isNodeSelection'
 export { default as isTextSelection } from './helpers/isTextSelection'
-export { default as findParentNodeClosestToPos } from './helpers/findParentNodeClosestToPos'
 
 export interface Commands {}
 
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 8a6e7aa7..6858a11f 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -29,6 +29,7 @@ export interface EditorOptions {
   parseOptions: ParseOptions,
   enableInputRules: boolean,
   enablePasteRules: boolean,
+  onBeforeCreate: (props: { editor: Editor }) => void,
   onCreate: (props: { editor: Editor }) => void,
   onUpdate: (props: { editor: Editor }) => void,
   onSelectionUpdate: (props: { editor: Editor }) => void,
diff --git a/packages/extension-code-block-lowlight/README.md b/packages/extension-code-block-lowlight/README.md
new file mode 100644
index 00000000..d950097e
--- /dev/null
+++ b/packages/extension-code-block-lowlight/README.md
@@ -0,0 +1,14 @@
+# @tiptap/extension-code-block-lowlight
+[![Version](https://img.shields.io/npm/v/@tiptap/extension-code-block-lowlight.svg?label=version)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight)
+[![Downloads](https://img.shields.io/npm/dm/@tiptap/extension-code-block-lowlight.svg)](https://npmcharts.com/compare/tiptap?minimal=true)
+[![License](https://img.shields.io/npm/l/@tiptap/extension-code-block-lowlight.svg)](https://www.npmjs.com/package/@tiptap/extension-code-block-lowlight)
+[![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub)](https://github.com/sponsors/ueberdosis)
+
+## Introduction
+tiptap is a headless wrapper around [ProseMirror](https://ProseMirror.net) – a toolkit for building rich text WYSIWYG editors, which is already in use at many well-known companies such as *New York Times*, *The Guardian* or *Atlassian*.
+
+## Offical Documentation
+Documentation can be found on the [tiptap website](https://tiptap.dev).
+
+## License
+tiptap is open-sourced software licensed under the [MIT license](https://github.com/ueberdosis/tiptap-next/blob/main/LICENSE.md).
diff --git a/packages/extension-code-block-lowlight/package.json b/packages/extension-code-block-lowlight/package.json
new file mode 100644
index 00000000..b1443c5e
--- /dev/null
+++ b/packages/extension-code-block-lowlight/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "@tiptap/extension-code-block-lowlight",
+  "description": "code block extension for tiptap",
+  "version": "2.0.0-beta.0",
+  "homepage": "https://tiptap.dev",
+  "keywords": [
+    "tiptap",
+    "tiptap extension"
+  ],
+  "license": "MIT",
+  "funding": {
+    "type": "github",
+    "url": "https://github.com/sponsors/ueberdosis"
+  },
+  "main": "dist/tiptap-extension-code-block-lowlight.cjs.js",
+  "umd": "dist/tiptap-extension-code-block-lowlight.umd.js",
+  "module": "dist/tiptap-extension-code-block-lowlight.esm.js",
+  "unpkg": "dist/tiptap-extension-code-block-lowlight.bundle.umd.min.js",
+  "types": "dist/packages/extension-code-block-lowlight/src/index.d.ts",
+  "files": [
+    "src",
+    "dist"
+  ],
+  "peerDependencies": {
+    "@tiptap/core": "^2.0.0-beta.1"
+  },
+  "dependencies": {
+    "@tiptap/extension-code-block": "^2.0.0-beta.1",
+    "@types/lowlight": "^0.0.1",
+    "lowlight": "^1.20.0",
+    "prosemirror-model": "^1.14.0",
+    "prosemirror-state": "^1.3.4",
+    "prosemirror-view": "^1.18.2"
+  }
+}
diff --git a/packages/extension-code-block-lowlight/src/code-block-lowlight.ts b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
new file mode 100644
index 00000000..77f82919
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/code-block-lowlight.ts
@@ -0,0 +1,10 @@
+import CodeBlock from '@tiptap/extension-code-block'
+import { LowlightPlugin } from './lowlight-plugin'
+
+export const CodeBlockLowlight = CodeBlock.extend({
+  addProseMirrorPlugins() {
+    return [
+      LowlightPlugin({ name: 'codeBlock' }),
+    ]
+  },
+})
diff --git a/packages/extension-code-block-lowlight/src/index.ts b/packages/extension-code-block-lowlight/src/index.ts
new file mode 100644
index 00000000..257f48ac
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/index.ts
@@ -0,0 +1,5 @@
+import { CodeBlockLowlight } from './code-block-lowlight'
+
+export * from './code-block-lowlight'
+
+export default CodeBlockLowlight
diff --git a/packages/extension-code-block-lowlight/src/lowlight-plugin.ts b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
new file mode 100644
index 00000000..7b099358
--- /dev/null
+++ b/packages/extension-code-block-lowlight/src/lowlight-plugin.ts
@@ -0,0 +1,111 @@
+import { Plugin, PluginKey } from 'prosemirror-state'
+import { Decoration, DecorationSet } from 'prosemirror-view'
+import { Node as ProsemirrorNode } from 'prosemirror-model'
+import { findChildren } from '@tiptap/core'
+import lowlight from 'lowlight/lib/core'
+
+function parseNodes(nodes: any[], className: string[] = []): { text: string, classes: string[] }[] {
+  return nodes
+    .map(node => {
+      const classes = [
+        ...className,
+        ...node.properties
+          ? node.properties.className
+          : [],
+      ]
+
+      if (node.children) {
+        return parseNodes(node.children, classes)
+      }
+
+      return {
+        text: node.value,
+        classes,
+      }
+    })
+    .flat()
+}
+
+function getDecorations({ doc, name }: { doc: ProsemirrorNode, name: string}) {
+  const decorations: Decoration[] = []
+
+  findChildren(doc, node => node.type.name === name)
+    .forEach(block => {
+      let from = block.pos + 1
+      const { language } = block.node.attrs
+      // TODO: add missing type for `listLanguages`
+      // @ts-ignore
+      const languages = lowlight.listLanguages() as string[]
+      const nodes = language && languages.includes(language)
+        ? lowlight.highlight(language, block.node.textContent).value
+        : lowlight.highlightAuto(block.node.textContent).value
+
+      parseNodes(nodes).forEach(node => {
+        const to = from + node.text.length
+
+        if (node.classes.length) {
+          const decoration = Decoration.inline(from, to, {
+            class: node.classes.join(' '),
+          })
+
+          decorations.push(decoration)
+        }
+
+        from = to
+      })
+    })
+
+  return DecorationSet.create(doc, decorations)
+}
+
+export function LowlightPlugin({ name }: { name: string }) {
+  return new Plugin({
+    key: new PluginKey('lowlight'),
+
+    state: {
+      init: (_, { doc }) => getDecorations({ doc, name }),
+      apply: (transaction, decorationSet, oldState, newState) => {
+        const oldNodeName = oldState.selection.$head.parent.type.name
+        const newNodeName = newState.selection.$head.parent.type.name
+        const oldNodes = findChildren(oldState.doc, node => node.type.name === name)
+        const newNodes = findChildren(newState.doc, node => node.type.name === name)
+
+        if (
+          transaction.docChanged
+          // Apply decorations if:
+          && (
+            // selection includes named node,
+            [oldNodeName, newNodeName].includes(name)
+            // OR transaction adds/removes named node,
+            || newNodes.length !== oldNodes.length
+            // OR transaction has changes that completely encapsulte a node
+            // (for example, a transaction that affects the entire document).
+            // Such transactions can happen during collab syncing via y-prosemirror, for example.
+            || transaction.steps.some(step => {
+              // @ts-ignore
+              return step.from !== undefined
+                // @ts-ignore
+                && step.to !== undefined
+                && oldNodes.some(node => {
+                  // @ts-ignore
+                  return node.pos >= step.from
+                    // @ts-ignore
+                    && node.pos + node.node.nodeSize <= step.to
+                })
+            })
+          )
+        ) {
+          return getDecorations({ doc: transaction.doc, name })
+        }
+
+        return decorationSet.map(transaction.mapping, transaction.doc)
+      },
+    },
+
+    props: {
+      decorations(state) {
+        return this.getState(state)
+      },
+    },
+  })
+}
diff --git a/yarn.lock b/yarn.lock
index a25dfbd9..3be799a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2184,6 +2184,11 @@
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
 
+"@types/lowlight@^0.0.1":
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/@types/lowlight/-/lowlight-0.0.1.tgz#221bc67a6c517bae71e6f200fa1cad0feaeeb965"
+  integrity sha512-yPpbpV1KfpFOZ0ZZbsgwWumraiAKoX7/Ng75Ah//w+ZBt4j0xwrQ2aHSlk2kPzQVK4LiPbNFE1LjC00IL4nl/A==
+
 "@types/mdast@^3.0.3":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb"
@@ -6545,6 +6550,13 @@ 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.11.3:
   version "0.11.3"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e"
@@ -6780,6 +6792,11 @@ 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"
@@ -7674,6 +7691,11 @@ 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@~10.7.0:
+  version "10.7.1"
+  resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.1.tgz#a8ec4152db24ea630c90927d6cae2a45f8ecb955"
+  integrity sha512-S6G97tHGqJ/U8DsXcEdnACbirtbx58Bx9CzIVeYli8OuswCfYI/LsXH2EiGcoGio1KAC3x4mmUwulOllJ2ZyRA==
+
 hirestime@^3.2.1:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/hirestime/-/hirestime-3.2.2.tgz#1b5ff4c796b6b70586fa6efa4850952c6e1be484"
@@ -9406,6 +9428,14 @@ lowercase-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479"
   integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==
 
+lowlight@^1.20.0:
+  version "1.20.0"
+  resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
+  integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
+  dependencies:
+    fault "^1.0.0"
+    highlight.js "~10.7.0"
+
 lpad-align@^1.0.1:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/lpad-align/-/lpad-align-1.1.2.tgz#21f600ac1c3095c3c6e497ee67271ee08481fe9e"