markdown.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import ReactMarkdown from 'react-markdown'
  2. import 'katex/dist/katex.min.css'
  3. import RemarkMath from 'remark-math'
  4. import RemarkBreaks from 'remark-breaks'
  5. import RehypeKatex from 'rehype-katex'
  6. import RemarkGfm from 'remark-gfm'
  7. import SyntaxHighlighter from 'react-syntax-highlighter'
  8. import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
  9. import type { RefObject } from 'react'
  10. import { useEffect, useRef, useState } from 'react'
  11. // import { copyToClipboard } from "../utils";
  12. // https://txtfiddle.com/~hlshwya/extract-urls-from-text
  13. // const urlRegex = /\b((https?|ftp|file):\/\/|(www|ftp)\.)[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/ig
  14. // function highlightURL(content: string) {
  15. // return content.replace(urlRegex, (url) => {
  16. // // fix http:// in [] will be parsed to link agin
  17. // const res = `[${url.replace('://', '://')}](${url})`
  18. // return res
  19. // })
  20. // }
  21. export function PreCode(props: { children: any }) {
  22. const ref = useRef<HTMLPreElement>(null)
  23. return (
  24. <pre ref={ref}>
  25. <span
  26. className="copy-code-button"
  27. onClick={() => {
  28. if (ref.current) {
  29. const code = ref.current.innerText
  30. // copyToClipboard(code);
  31. }
  32. }}
  33. ></span>
  34. {props.children}
  35. </pre>
  36. )
  37. }
  38. const useLazyLoad = (ref: RefObject<Element>): boolean => {
  39. const [isIntersecting, setIntersecting] = useState<boolean>(false)
  40. useEffect(() => {
  41. const observer = new IntersectionObserver(([entry]) => {
  42. if (entry.isIntersecting) {
  43. setIntersecting(true)
  44. observer.disconnect()
  45. }
  46. })
  47. if (ref.current)
  48. observer.observe(ref.current)
  49. return () => {
  50. observer.disconnect()
  51. }
  52. }, [ref])
  53. return isIntersecting
  54. }
  55. export function Markdown(props: { content: string }) {
  56. return (
  57. <div className="markdown-body">
  58. <ReactMarkdown
  59. remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
  60. rehypePlugins={[
  61. RehypeKatex,
  62. ]}
  63. components={{
  64. code({ node, inline, className, children, ...props }) {
  65. const match = /language-(\w+)/.exec(className || '')
  66. return (!inline && match)
  67. ? (
  68. <SyntaxHighlighter
  69. {...props}
  70. children={String(children).replace(/\n$/, '')}
  71. style={atelierHeathLight}
  72. language={match[1]}
  73. showLineNumbers
  74. PreTag="div"
  75. />
  76. )
  77. : (
  78. <code {...props} className={className}>
  79. {children}
  80. </code>
  81. )
  82. },
  83. }}
  84. linkTarget={'_blank'}
  85. >
  86. {/* Markdown detect has problem. */}
  87. {props.content}
  88. </ReactMarkdown>
  89. </div>
  90. )
  91. }