CodeBlock.tsx 2.8 KB

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