From 7c0f57e51230a8af8f5f340cfc9e628e96aefe59 Mon Sep 17 00:00:00 2001 From: Nick Graziano Date: Mon, 11 Nov 2024 14:44:05 -0700 Subject: [PATCH 1/4] POC for implementing "bold" rich text functionality into document producer --- .../DocumentProducer/EditableSection/Edit.tsx | 25 +++++++ .../EditableSection/TextChunk.tsx | 16 +++- .../DocumentProducer/EditableSection/types.ts | 74 +++++++++++++++++++ 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/components/DocumentProducer/EditableSection/types.ts diff --git a/src/components/DocumentProducer/EditableSection/Edit.tsx b/src/components/DocumentProducer/EditableSection/Edit.tsx index d33e2f81615..f8142765696 100644 --- a/src/components/DocumentProducer/EditableSection/Edit.tsx +++ b/src/components/DocumentProducer/EditableSection/Edit.tsx @@ -157,16 +157,41 @@ const SectionEdit = ({ Transforms.move(editor, { unit: 'offset', reverse: true }); return; } + if (isKeyHotkey('right', nativeEvent)) { event.preventDefault(); Transforms.move(editor, { unit: 'offset' }); return; } + if (isKeyHotkey('enter', nativeEvent)) { event.preventDefault(); Transforms.insertNodes(editor, { text: '\n' }); return; } + + if (isKeyHotkey('cmd+b', nativeEvent)) { + // TODO We would need to figure out how to "unbold" the text. + // This sets properties onto the text that we need to use when rendering to achieve the desired effect + const range = Editor.unhangRange(editor, editor.selection, { voids: true }); + event.preventDefault(); + Transforms.setNodes( + editor, + { bold: true }, + { + at: range, + match: () => true, + split: true, + } + ); + return; + } + + if (isKeyHotkey('cmd+i', nativeEvent)) { + event.preventDefault(); + // TODO + return; + } } }, [editor] diff --git a/src/components/DocumentProducer/EditableSection/TextChunk.tsx b/src/components/DocumentProducer/EditableSection/TextChunk.tsx index c7c7abc3e40..7acf53db013 100644 --- a/src/components/DocumentProducer/EditableSection/TextChunk.tsx +++ b/src/components/DocumentProducer/EditableSection/TextChunk.tsx @@ -3,5 +3,19 @@ import React from 'react'; import { RenderElementProps } from 'slate-react'; export default function TextChunk(props: RenderElementProps): React.ReactElement { - return {props.children}; + console.log(props.children); + // I'm not sure what this type is, it seems to be `any` in the slate types unfortunately + return props.children.map((child: any, index: number) => { + const style: React.CSSProperties = {}; + + if (child?.props?.text?.bold) { + style.fontWeight = 600; + } + + return ( + + {child} + + ); + }); } diff --git a/src/components/DocumentProducer/EditableSection/types.ts b/src/components/DocumentProducer/EditableSection/types.ts new file mode 100644 index 00000000000..5a173d38062 --- /dev/null +++ b/src/components/DocumentProducer/EditableSection/types.ts @@ -0,0 +1,74 @@ +import { BaseEditor, Descendant, Element as SlateElement } from 'slate'; +import { ReactEditor } from 'slate-react'; + +// Required type definitions for slatejs (https://docs.slatejs.org/concepts/12-typescript): +export type CustomText = { text: string }; + +export type VariableElement = { + // This is unused, but required by Slate + children: CustomText[]; + docId?: number; + reference: boolean; + type: 'variable'; + variableId?: number; +}; + +export type TextElement = { + children: CustomText[]; + type: 'text'; + bold?: boolean; +}; + +export type SectionElement = { + children: (VariableElement | TextElement)[]; + type: 'section'; +}; + +export type ElementUnion = SectionElement | VariableElement | TextElement; + +// For some reason the typeguards in Slate do not actually narrow the type +export const isVariableElement = (input: unknown): input is VariableElement => + SlateElement.isElementType(input, 'variable'); +export const isTextElement = (input: unknown): input is TextElement => SlateElement.isElementType(input, 'text'); +export const isSectionElement = (input: unknown): input is SectionElement => + SlateElement.isElementType(input, 'section'); +export const isCustomText = (input: unknown): input is CustomText => (input as CustomText).text !== undefined; + +// Slate editor will return everything as a Descendant, which is a union of our custom elements and CustomText +// This is used to disambiguate the two, since most of the operations going from Slate to variable values are in the +// context of the custom elements (ElementUnion) +// export const isSlateElementWithChildren = (input: unknown): input is ElementUnion => +// isArray((input as ElementUnion).children); + +declare module 'slate' { + interface CustomTypes { + Editor: BaseEditor & ReactEditor; + Element: ElementUnion; + Text: CustomText; + } +} + +// An "empty" descendant is either a TextElement with no children that have text, or an empty CustomText +// A VariableElement is never considered "empty" +export const isEmptyDescendant = (value: Descendant): boolean => { + // If https://tc39.es/proposal-pattern-matching/ ever lands, I can stop using this pattern! + switch (true) { + case isVariableElement(value): + return false; + + case isSectionElement(value): + case isTextElement(value): + if (value.children.length > 1) { + return false; + } + + const onlyChild = value.children[0]; + return isEmptyDescendant(onlyChild); + + case isCustomText(value): + return value.text === ''; + + default: + return true; + } +}; From ee6964c8cab668a83aca5993873479b36135f710 Mon Sep 17 00:00:00 2001 From: Nick Graziano Date: Mon, 11 Nov 2024 14:55:10 -0700 Subject: [PATCH 2/4] Implement unbold, use correct signature for span returned by text chunk renderer --- .../DocumentProducer/EditableSection/Edit.tsx | 2 +- .../EditableSection/TextChunk.tsx | 29 ++++++++++--------- .../DocumentProducer/EditableSection/types.ts | 5 +++- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/components/DocumentProducer/EditableSection/Edit.tsx b/src/components/DocumentProducer/EditableSection/Edit.tsx index f8142765696..c338b7e8b26 100644 --- a/src/components/DocumentProducer/EditableSection/Edit.tsx +++ b/src/components/DocumentProducer/EditableSection/Edit.tsx @@ -177,7 +177,7 @@ const SectionEdit = ({ event.preventDefault(); Transforms.setNodes( editor, - { bold: true }, + { bold: !Editor.marks(editor)?.bold }, { at: range, match: () => true, diff --git a/src/components/DocumentProducer/EditableSection/TextChunk.tsx b/src/components/DocumentProducer/EditableSection/TextChunk.tsx index 7acf53db013..9859dea1610 100644 --- a/src/components/DocumentProducer/EditableSection/TextChunk.tsx +++ b/src/components/DocumentProducer/EditableSection/TextChunk.tsx @@ -3,19 +3,22 @@ import React from 'react'; import { RenderElementProps } from 'slate-react'; export default function TextChunk(props: RenderElementProps): React.ReactElement { - console.log(props.children); - // I'm not sure what this type is, it seems to be `any` in the slate types unfortunately - return props.children.map((child: any, index: number) => { - const style: React.CSSProperties = {}; + // I'm not sure what this type is for `child`, it seems to be `any` in the slate types unfortunately + return ( + + {props.children.map((child: any, index: number) => { + const style: React.CSSProperties = {}; - if (child?.props?.text?.bold) { - style.fontWeight = 600; - } + if (child?.props?.text?.bold) { + style.fontWeight = 600; + } - return ( - - {child} - - ); - }); + return ( + + {child} + + ); + })} + + ); } diff --git a/src/components/DocumentProducer/EditableSection/types.ts b/src/components/DocumentProducer/EditableSection/types.ts index 5a173d38062..3d65d221023 100644 --- a/src/components/DocumentProducer/EditableSection/types.ts +++ b/src/components/DocumentProducer/EditableSection/types.ts @@ -2,7 +2,10 @@ import { BaseEditor, Descendant, Element as SlateElement } from 'slate'; import { ReactEditor } from 'slate-react'; // Required type definitions for slatejs (https://docs.slatejs.org/concepts/12-typescript): -export type CustomText = { text: string }; +export type CustomText = { + text: string; + bold?: boolean; +}; export type VariableElement = { // This is unused, but required by Slate From f412e13834437b01fdbbc69bc9e9dff0598f003d Mon Sep 17 00:00:00 2001 From: Nick Graziano Date: Wed, 27 Nov 2024 09:53:42 -0700 Subject: [PATCH 3/4] Clean up comments --- src/components/DocumentProducer/EditableSection/Edit.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/DocumentProducer/EditableSection/Edit.tsx b/src/components/DocumentProducer/EditableSection/Edit.tsx index c338b7e8b26..d99615ddb42 100644 --- a/src/components/DocumentProducer/EditableSection/Edit.tsx +++ b/src/components/DocumentProducer/EditableSection/Edit.tsx @@ -171,12 +171,11 @@ const SectionEdit = ({ } if (isKeyHotkey('cmd+b', nativeEvent)) { - // TODO We would need to figure out how to "unbold" the text. - // This sets properties onto the text that we need to use when rendering to achieve the desired effect const range = Editor.unhangRange(editor, editor.selection, { voids: true }); event.preventDefault(); Transforms.setNodes( editor, + // This property must be considered in rendering or this does nothing { bold: !Editor.marks(editor)?.bold }, { at: range, @@ -189,7 +188,7 @@ const SectionEdit = ({ if (isKeyHotkey('cmd+i', nativeEvent)) { event.preventDefault(); - // TODO + // TODO implement italics? return; } } From 3b5a7438ffec2e42dc8467de431712ff50ec5024 Mon Sep 17 00:00:00 2001 From: Nick Graziano Date: Mon, 2 Dec 2024 10:13:01 -0700 Subject: [PATCH 4/4] Use PrismJS for markdown bold / unbold, use editor decorator to do it according to their docs --- package.json | 2 + .../EditableSection/Display.tsx | 1 + .../DocumentProducer/EditableSection/Edit.tsx | 179 ++++++++++++------ .../EditableSection/TextChunk.tsx | 19 +- .../EditableSection/helpers.ts | 95 +++++++++- .../DocumentProducer/EditableSection/types.ts | 1 - yarn.lock | 10 + 7 files changed, 221 insertions(+), 86 deletions(-) diff --git a/package.json b/package.json index fc654958a38..8be0eaa422f 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "lodash": "^4.17.21", "luxon": "^3.3.0", "mapbox-gl": "^3.0.0", + "prismjs": "^1.29.0", "re-reselect": "^4.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -161,6 +162,7 @@ "@types/luxon": "^3.2.0", "@types/mapbox__mapbox-gl-draw": "^1.4.0", "@types/node": "^18.15.11", + "@types/prismjs": "^1.26.5", "@types/react": "^18.2.38", "@types/react-dom": "^18.2.17", "@typescript-eslint/eslint-plugin": "^7.7.0", diff --git a/src/components/DocumentProducer/EditableSection/Display.tsx b/src/components/DocumentProducer/EditableSection/Display.tsx index 9cb4647a7b1..76f13c1c41c 100644 --- a/src/components/DocumentProducer/EditableSection/Display.tsx +++ b/src/components/DocumentProducer/EditableSection/Display.tsx @@ -50,6 +50,7 @@ const SectionValue = ({ }: SectionValueProps): React.ReactElement | null => { switch (value.type) { case 'SectionText': + // Need to implement markdown view here return {value.textValue}; case 'SectionVariable': const reference = value.usageType === 'Reference'; diff --git a/src/components/DocumentProducer/EditableSection/Edit.tsx b/src/components/DocumentProducer/EditableSection/Edit.tsx index d99615ddb42..2f8f77f195b 100644 --- a/src/components/DocumentProducer/EditableSection/Edit.tsx +++ b/src/components/DocumentProducer/EditableSection/Edit.tsx @@ -1,11 +1,11 @@ import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { Box, Typography, useTheme } from '@mui/material'; +import { Box, Typography, styled, useTheme } from '@mui/material'; import { Autocomplete, Button, DropdownItem } from '@terraware/web-components'; import { useDeviceInfo } from '@terraware/web-components/utils'; import { isKeyHotkey } from 'is-hotkey'; -import { BaseEditor, Descendant, Element as SlateElement, Transforms, createEditor } from 'slate'; -import { Editable, ReactEditor, Slate, withReact } from 'slate-react'; +import { Descendant, Editor, Transforms, createEditor } from 'slate'; +import { Editable, RenderElementProps, RenderLeafProps, Slate, withReact } from 'slate-react'; import strings from 'src/strings'; import { Section, VariableWithValues } from 'src/types/documentProducer/Variable'; @@ -14,24 +14,8 @@ import { VariableValueValue } from 'src/types/documentProducer/VariableValue'; import InsertOptionsDropdown from './InsertOptionsDropdown'; import TextChunk from './TextChunk'; import TextVariable from './TextVariable'; -import { editorValueFromVariableValue, variableValueFromEditorValue } from './helpers'; - -// Required type definitions for slatejs (https://docs.slatejs.org/concepts/12-typescript): -export type CustomElement = { - type?: 'text' | 'variable'; - children: CustomText[]; - variableId?: number; - docId: number; - reference?: boolean; -}; -export type CustomText = { text: string }; -declare module 'slate' { - interface CustomTypes { - Editor: BaseEditor & ReactEditor; - Element: CustomElement; - Text: CustomText; - } -} +import { decorateMarkdown, editorValueFromVariableValue, scrubMarkdown, variableValueFromEditorValue } from './helpers'; +import { isEmptyDescendant, isSectionElement, isTextElement, isVariableElement } from './types'; type EditableSectionEditProps = { section: Section; @@ -59,27 +43,32 @@ const SectionEdit = ({ const [variableToBeInserted, setVariableToBeInserted] = useState(); const initialValue: Descendant[] = useMemo(() => { - const editorValue = - sectionValues !== undefined && sectionValues.length > 0 - ? [ - { - children: [ - { text: '' }, - ...sectionValues.map((value) => editorValueFromVariableValue(value, allVariables)), - { text: '' }, - ], - }, - ] - : [{ children: [{ text: '' }] }]; - return editorValue as Descendant[]; + const editorValue: Descendant = { + type: 'section', + children: [], + }; + + if (sectionValues !== undefined && sectionValues.length > 0) { + editorValue.children = sectionValues.map((value) => editorValueFromVariableValue(value, allVariables)); + } + + return [editorValue]; }, [sectionValues, allVariables]); const onChange = useCallback( - (value: Descendant[]) => { + (values: Descendant[]) => { const newVariableValues: VariableValueValue[] = []; - value.forEach((v) => { - const children = (v as SlateElement).children; - if (children.length === 1 && children[0].text === '') { + + values.forEach((value: Descendant) => { + if (!isSectionElement(value)) { + // We shouldn't ever hit here, for now we are only using SectionElement as top level elements + return; + } + + const children = value.children; + + // This is an "empty" value + if (isEmptyDescendant(value)) { newVariableValues.push({ id: -1, listPosition: newVariableValues.length, @@ -87,9 +76,13 @@ const SectionEdit = ({ textValue: '\n', }); } else { - children.forEach((c) => { - if (c.text === undefined || c.text !== '') { - newVariableValues.push(variableValueFromEditorValue(c, allVariables, newVariableValues.length)); + children.forEach((child) => { + if (isEmptyDescendant(child)) { + return; + } + + if (isVariableElement(child) || isTextElement(child)) { + newVariableValues.push(variableValueFromEditorValue(child, newVariableValues.length)); } }); } @@ -105,27 +98,29 @@ const SectionEdit = ({ ); const renderElement = useCallback( - (props: any) => { - switch (props.element.type) { - case 'variable': - const variable = allVariables.find((v) => v.id === props.element.variableId); - return ( - onEditVariableValue(variable)} - reference={props.element.reference} - variable={variable} - {...props} - /> - ); - default: - return ; + (props: RenderElementProps) => { + const { element } = props; + if (isVariableElement(element)) { + const variable = allVariables.find((v) => v.id === element.variableId); + return ( + onEditVariableValue(variable)} + reference={element.reference} + variable={variable} + {...props} + /> + ); } + + return ; }, [allVariables] ); + const renderLeaf = useCallback((props: RenderLeafProps) => , []); + const insertVariable = useCallback( (variable: VariableWithValues, reference?: boolean) => { Transforms.insertNodes(editor, { @@ -278,7 +273,9 @@ const SectionEdit = ({ ( @@ -315,4 +312,72 @@ const withInlines = (editor: any) => { return editor; }; +const Leaf = styled(({ attributes, children, className, leaf }: RenderLeafProps & { className?: string }) => { + // Scrub the markdown for the editor view + if (leaf.bold) { + leaf.text = scrubMarkdown(leaf.text); + } + + return ( + + {children} + + ); +})(({ leaf }) => ({ + ...(leaf.bold + ? { + fontWeight: 'bold', + } + : {}), + // TODO need to implement these as CustomText properties if we want to use them + // ...(leaf.italic + // ? { + // fontStyle: 'italic', + // } + // : {}), + // ...(leaf.underlined + // ? { + // textDecoration: 'underline', + // } + // : {}), + // ...(leaf.title + // ? { + // display: 'inline-block', + // fontWeight: 'bold', + // fontSize: '20px', + // margin: '20px 0 10px 0', + // } + // : {}), + // ...(leaf.list + // ? { + // paddingLeft: '10px', + // fontSize: '20px', + // lineHeight: '10px', + // } + // : {}), + // ...(leaf.hr + // ? { + // display: 'block', + // textAlign: 'center', + // borderBottom: '2px solid #ddd', + // } + // : {}), + // ...(leaf.blockquote + // ? { + // display: 'inline-block', + // borderLeft: '2px solid #ddd', + // paddingLeft: '10px', + // color: '#aaa', + // fontStyle: 'italic', + // } + // : {}), + // ...(leaf.code + // ? { + // fontFamily: 'monospace', + // backgroundColor: '#eee', + // padding: '3px', + // } + // : {}), +})); + export default SectionEdit; diff --git a/src/components/DocumentProducer/EditableSection/TextChunk.tsx b/src/components/DocumentProducer/EditableSection/TextChunk.tsx index 9859dea1610..c7c7abc3e40 100644 --- a/src/components/DocumentProducer/EditableSection/TextChunk.tsx +++ b/src/components/DocumentProducer/EditableSection/TextChunk.tsx @@ -3,22 +3,5 @@ import React from 'react'; import { RenderElementProps } from 'slate-react'; export default function TextChunk(props: RenderElementProps): React.ReactElement { - // I'm not sure what this type is for `child`, it seems to be `any` in the slate types unfortunately - return ( - - {props.children.map((child: any, index: number) => { - const style: React.CSSProperties = {}; - - if (child?.props?.text?.bold) { - style.fontWeight = 600; - } - - return ( - - {child} - - ); - })} - - ); + return {props.children}; } diff --git a/src/components/DocumentProducer/EditableSection/helpers.ts b/src/components/DocumentProducer/EditableSection/helpers.ts index 23c6cf26105..d3d0465064a 100644 --- a/src/components/DocumentProducer/EditableSection/helpers.ts +++ b/src/components/DocumentProducer/EditableSection/helpers.ts @@ -1,10 +1,13 @@ -import { Descendant } from 'slate'; +import Prism, { TokenStream } from 'prismjs'; +import 'prismjs/components/prism-markdown'; +import { Descendant, NodeEntry, Range } from 'slate'; -import { CustomElement, CustomText } from 'src/components/DocumentProducer/EditableSection/Edit'; import strings from 'src/strings'; import { VariableWithValues } from 'src/types/documentProducer/Variable'; import { VariableValueSelectValue, VariableValueValue } from 'src/types/documentProducer/VariableValue'; +import { CustomText, TextElement, VariableElement, isCustomText, isVariableElement } from './types'; + export const editorDisplayVariableWithValues = ( variable: VariableWithValues, separator: string, @@ -54,6 +57,65 @@ export const displayValue = (value: VariableValueValue, placeholder?: string): s } }; +// From https://github.com/ianstormtaylor/slate/blob/main/site/examples/ts/markdown-preview.tsx +export const decorateMarkdown = ([node, path]: NodeEntry): Range[] => { + const ranges: Range[] = []; + + if (!isCustomText(node)) { + return ranges; + } + + const getLength = (token: TokenStream): number => { + if (token.length || typeof token === 'string') { + return token.length; + } else if (Array.isArray(token)) { + return token.reduce((length, _token) => length + getLength(_token), 0); + } else if (typeof token.content === 'string') { + return token.content.length; + } + return 0; + }; + + const tokens = Prism.tokenize(node.text, Prism.languages.markdown); + let start = 0; + + for (const token of tokens) { + const length = getLength(token); + const end = start + length; + + if (typeof token !== 'string') { + ranges.push({ + [token.type]: true, + anchor: { path, offset: start }, + focus: { path, offset: end }, + }); + } + + start = end; + } + + return ranges; +}; + +const getTokenContent = (token: TokenStream): string => { + if (typeof token === 'string') { + return token; + } else if (Array.isArray(token)) { + return token.map(getTokenContent).join(''); + } else { + if (token.type && token.type === 'punctuation') { + return ''; + } + return getTokenContent(token.content); + } +}; + +export const scrubMarkdown = (input: string): string => { + const tokens = Prism.tokenize(input, Prism.languages.markdown); + // We want to remove any tokens that are markdown punctuation + return tokens.flatMap(getTokenContent).join(''); +}; + export const editorValueFromVariableValue = ( variableValue: VariableValueValue, allValues: VariableWithValues[] @@ -68,31 +130,44 @@ export const editorValueFromVariableValue = ( variableId: value?.id, children: [{ text: '' }], reference: variableValue.usageType === 'Reference', - } as CustomElement; + }; default: return { text: '' } as CustomText; } }; +// This is the function used to format editor text into a format that can be embedded into the section text on the server +// For now, we are going to use Markdown https://www.markdownguide.org/basic-syntax/ +const formatEditorChildren = (children: CustomText[]): string => + children.reduce((acc: string, textNode: CustomText) => { + let text = textNode.text; + if (textNode.bold) { + // Going with asterisk bold + // https://www.markdownguide.org/basic-syntax/#bold + text = `**${textNode.text}**`; + } + + return `${acc}${text}`; + }, ''); + export const variableValueFromEditorValue = ( - editorValue: Descendant, - allValues: VariableWithValues[], + editorValue: VariableElement | TextElement, listPosition: number ): VariableValueValue => { - if ((editorValue as CustomElement).variableId !== undefined) { + if (isVariableElement(editorValue)) { return { id: -1, listPosition, type: 'SectionVariable', - variableId: (editorValue as CustomElement).variableId!, - displayStyle: (editorValue as CustomElement).reference ? undefined : 'Inline', - usageType: (editorValue as CustomElement).reference ? 'Reference' : 'Injection', + variableId: editorValue.variableId!, + displayStyle: editorValue.reference ? undefined : 'Inline', + usageType: editorValue.reference ? 'Reference' : 'Injection', }; } return { id: -1, listPosition, type: 'SectionText', - textValue: (editorValue as CustomText).text, + textValue: formatEditorChildren(editorValue.children), }; }; diff --git a/src/components/DocumentProducer/EditableSection/types.ts b/src/components/DocumentProducer/EditableSection/types.ts index 3d65d221023..cc7168e787f 100644 --- a/src/components/DocumentProducer/EditableSection/types.ts +++ b/src/components/DocumentProducer/EditableSection/types.ts @@ -19,7 +19,6 @@ export type VariableElement = { export type TextElement = { children: CustomText[]; type: 'text'; - bold?: boolean; }; export type SectionElement = { diff --git a/yarn.lock b/yarn.lock index ae1e09328ee..89bd32236f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4562,6 +4562,11 @@ resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#ee1bd8c9f7a01b3445786aad0ef23aba5f511a44" integrity sha512-nj39q0wAIdhwn7DGUyT9irmsKK1tV0bd5WFEhgpqNTMFZ8cE+jieuTphCW0tfdm47S2zVT5mr09B28b1chmQMA== +"@types/prismjs@^1.26.5": + version "1.26.5" + resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6" + integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ== + "@types/prop-types@*", "@types/prop-types@^15.7.11": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -12995,6 +13000,11 @@ pretty-hrtime@^1.0.3: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A== +prismjs@^1.29.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" + integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"