CodeBlock.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import type { ReactNode, JSX } from 'react';
  2. import { PrismAsyncLight } from 'react-syntax-highlighter';
  3. import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
  4. import styles from './CodeBlock.module.scss';
  5. // remove font-family
  6. Object.entries<object>(oneDark).forEach(([key, value]) => {
  7. if ('fontFamily' in value) {
  8. delete oneDark[key].fontFamily;
  9. }
  10. });
  11. type InlineCodeBlockProps = {
  12. children: ReactNode,
  13. className?: string,
  14. }
  15. const InlineCodeBlockSubstance = (props: InlineCodeBlockProps): JSX.Element => {
  16. const { children, className, ...rest } = props;
  17. return <code className={`code-inline ${className ?? ''}`} {...rest}>{children}</code>;
  18. };
  19. function extractChildrenToIgnoreReactNode(children: ReactNode): ReactNode {
  20. if (children == null) {
  21. return children;
  22. }
  23. // Single element array
  24. if (Array.isArray(children) && children.length === 1) {
  25. return extractChildrenToIgnoreReactNode(children[0]);
  26. }
  27. // Multiple element array
  28. if (Array.isArray(children) && children.length > 1) {
  29. return children.map(node => extractChildrenToIgnoreReactNode(node)).join('');
  30. }
  31. // object
  32. if (typeof children === 'object') {
  33. const grandChildren = (children as any).children ?? (children as any).props.children;
  34. return extractChildrenToIgnoreReactNode(grandChildren);
  35. }
  36. return String(children).replace(/\n$/, '');
  37. }
  38. function CodeBlockSubstance({ lang, children }: { lang: string, children: ReactNode }): JSX.Element {
  39. // return alternative element
  40. // in order to fix "CodeBlock string is be [object Object] if searched"
  41. // see: https://github.com/growilabs/growi/pull/7484
  42. //
  43. // Note: You can also remove this code if the user requests to see the code highlighted in Prism as-is.
  44. const isSimpleString = typeof children === 'string' || (Array.isArray(children) && children.length === 1 && typeof children[0] === 'string');
  45. if (!isSimpleString) {
  46. return (
  47. <div style={oneDark['pre[class*="language-"]']}>
  48. <code className={`language-${lang}`} style={oneDark['code[class*="language-"]']}>
  49. {children}
  50. </code>
  51. </div>
  52. );
  53. }
  54. return (
  55. <PrismAsyncLight
  56. PreTag="div"
  57. style={oneDark}
  58. language={lang}
  59. >
  60. {extractChildrenToIgnoreReactNode(children)}
  61. </PrismAsyncLight>
  62. );
  63. }
  64. type CodeBlockProps = {
  65. children: ReactNode,
  66. className?: string,
  67. inline?: true,
  68. }
  69. export const CodeBlock = (props: CodeBlockProps): JSX.Element => {
  70. // TODO: set border according to the value of 'customize:highlightJsStyleBorder'
  71. const { className, children, inline } = props;
  72. if (inline) {
  73. return <InlineCodeBlockSubstance className={`code-inline ${className ?? ''}`}>{children}</InlineCodeBlockSubstance>;
  74. }
  75. const match = /language-(\w+)(:?.+)?/.exec(className || '');
  76. const lang = match && match[1] ? match[1] : '';
  77. const name = match && match[2] ? match[2].slice(1) : null;
  78. return (
  79. <>
  80. {name != null && (
  81. <cite className={`code-highlighted-title ${styles['code-highlighted-title']}`}>{name}</cite>
  82. )}
  83. <CodeBlockSubstance lang={lang}>{children}</CodeBlockSubstance>
  84. </>
  85. );
  86. };