import classnames from 'classnames' ; import { isArray , isUndefined } from 'lodash' ; import isNull from 'lodash/isNull' ; import { ElementType , memo , useCallback , useEffect , useRef , useState } from 'react' ; import ReactMarkdown from 'react-markdown' ; import SyntaxHighlighter from 'react-syntax-highlighter' ; import { github } from 'react-syntax-highlighter/dist/cjs/styles/hljs' ; import gfm from 'remark-gfm' ; import useBreakpointDetect from '../../../hooks/useBreakpointDetect' ; import AnchorHeader from '../../common/AnchorHeader' ; import styles from './Readme.module.css' ; interface Props { readme : string ; scrollIntoView : ( id ? : string ) => void ; } interface CodeProps { className : string ; inline : boolean ; children : any ; } interface ImageProps { alt : string ; src : string ; } interface LinkProps { href : string ; target : string ; children : any ; } interface BasicProps { children : JSX . Element | JSX . Element [ ] ; } const AVAILABLE_LANGUAGES = [ 'oneC' , 'abnf' , 'accesslog' , 'actionscript' , 'ada' , 'angelscript' , 'apache' , 'applescript' , 'arcade' , 'arduino' , 'armasm' , 'asciidoc' , 'aspectj' , 'autohotkey' , 'autoit' , 'avrasm' , 'awk' , 'axapta' , 'bash' , 'basic' , 'bnf' , 'brainfuck' , 'cLike (c-like)' , 'c' , 'cal' , 'capnproto' , 'ceylon' , 'clean' , 'clojureRepl (clojure-repl)' , 'clojure' , 'cmake' , 'coffeescript' , 'coq' , 'cos' , 'cpp' , 'crmsh' , 'crystal' , 'csharp' , 'csp' , 'css' , 'd' , 'dart' , 'delphi' , 'diff' , 'django' , 'dns' , 'dockerfile' , 'dos' , 'dsconfig' , 'dts' , 'dust' , 'ebnf' , 'elixir' , 'elm' , 'erb' , 'erlangRepl' , 'erlang' , 'excel' , 'fix' , 'flix' , 'fortran' , 'fsharp' , 'gams' , 'gauss' , 'gcode' , 'gherkin' , 'glsl' , 'gml' , 'go' , 'golo' , 'gradle' , 'groovy' , 'haml' , 'handlebars' , 'haskell' , 'haxe' , 'hsp' , 'htmlbars' , 'http' , 'hy' , 'inform7' , 'ini' , 'irpf90' , 'isbl' , 'java' , 'javascript' , 'jbossCli (jboss-cli)' , 'json' , 'juliaRepl (julia-repl)' , 'julia' , 'kotlin' , 'lasso' , 'latex' , 'ldif' , 'leaf' , 'less' , 'lisp' , 'livecodeserver' , 'livescript' , 'llvm' , 'lsl' , 'lua' , 'makefile' , 'markdown' , 'mathematica' , 'matlab' , 'maxima' , 'mel' , 'mercury' , 'mipsasm' , 'mizar' , 'mojolicious' , 'monkey' , 'moonscript' , 'n1ql' , 'nginx' , 'nim' , 'nix' , 'nodeRepl (node-repl)' , 'nsis' , 'objectivec' , 'ocaml' , 'openscad' , 'oxygene' , 'parser3' , 'perl' , 'pf' , 'pgsql' , 'phpTemplate (php-template)' , 'php' , 'plaintext' , 'pony' , 'powershell' , 'processing' , 'profile' , 'prolog' , 'properties' , 'protobuf' , 'puppet' , 'purebasic' , 'pythonRepl' , 'python' , 'q' , 'qml' , 'r' , 'reasonml' , 'rib' , 'roboconf' , 'routeros' , 'rsl' , 'ruby' , 'ruleslanguage' , 'rust' , 'sas' , 'scala' , 'scheme' , 'scilab' , 'scss' , 'shell' , 'smali' , 'smalltalk' , 'sml' , 'sqf' , 'sql' , 'stan' , 'stata' , 'step21' , 'stylus' , 'subunit' , 'swift' , 'taggerscript' , 'tap' , 'tcl' , 'thrift' , 'tp' , 'twig' , 'typescript' , 'vala' , 'vbnet' , 'vbscriptHtml' , 'vbscript' , 'verilog' , 'vhdl' , 'vim' , 'x86asm' , 'xl' , 'xml' , 'xquery' , 'yaml' , 'zephir' , ] ; const checkCodeLanguage = ( language ) : string => { let lang = 'text' ; if ( language ) { if ( AVAILABLE_LANGUAGES . includes ( language ) ) { lang = language ; } else if ( language === 'console' ) { lang = 'bash' ; } } return lang ; } ; const Readme = ( props ) => { const Code = ( { inline , className , children } ) => { const match = /language-(\w+)/ . exec ( className || '' ) ; if ( inline ) { return < code className = { className } > { children } < /code>; } else { return ( < SyntaxHighlighter language = { checkCodeLanguage ( match ? match [ 1 ] : 'bash' ) } style = { github } > { String ( children ) . replace ( /\n$/ , '' ) } < /SyntaxHighlighter>); } } ; const Image = ( data ) => { const img = useRef < HTMLImageElement > ( null ) ; const [ error , setError ] = useState < boolean > ( false ) ; const [ isBigImage , setIsBigImage ] = useState < boolean > ( false ) ; const point = useBreakpointDetect ( ) ; const checkImageInBreakpoint = useCallback ( ( ) => { if ( ! isNull ( img ) && img . current && point && [ 'md' , 'lg' , 'xl' ] . includes ( point ) && img . current ! . width > 410 ) { setIsBigImage ( true ) ; } } , [ point ] ) ; useEffect ( ( ) => { checkImageInBreakpoint ( ) ; } , [ point , checkImageInBreakpoint ] ) ; return /^https?:/ . test ( data . src ) ? ( < span className = { classnames ( { 'overflow-hidden d-table-cell' : isBigImage } ) } > < img ref = { img } src = { data . src } alt = { data . alt || '' } className = { classnames ( { 'd-none' : error } ) } onError = { ( ) => setError ( true ) } onLoad = { checkImageInBreakpoint } / > < /span>) : null; } ; const Link = ( data ) => { const isContentImage = data . children && isArray ( data . children ) && data . children . length > 0 && ! isUndefined ( data . children [ 0 ] . props ) ? ! isUndefined ( data . children [ 0 ] . props . src ) : false ; if ( /^https?:/ . test ( data . href ) ) { return ( < a href = { data . href } target = { data . target } rel = "noopener noreferrer" className = { classnames ( 'text-primary' , { 'd-inline' : ! isContentImage } ) } > { data . children } < /a>); } else if ( data . href . startsWith ( '#' ) && isElementInView ( data . href ) ) { return ( < button className = { classnames ( 'btn btn-link text-primary text-start border-0 p-0' , styles . btnLink ) } onClick = { ( ) => props . scrollIntoView ( data . href ) } aria - label = "Go to element" > { data . children } < /button>); } else { return < > { data . children } < />; } } ; const Table = ( data ) => ( < div className = "mw-100 overflow-auto" > < table > { data . children } < /table> < /div>); const Paragraph = ( data ) => { const isOneChild = data . children && isArray ( data . children ) && data . children . length === 1 ; if ( isUndefined ( data . children ) ) return null ; return < p className = { classnames ( { 'd-block w-100 h-100' : isOneChild } , styles . paragraph ) } > { data . children } < /p>; } ; const Blockquote = ( data ) => { return < blockquote className = { `text-muted position-relative ${ styles . quote }` } > { data . children } < /blockquote>; } ; const Heading = ( data ) => < AnchorHeader { ... data } scrollIntoView = { props . scrollIntoView } / > ; const isElementInView = ( id ) => { try { const item = document . querySelector ( id ) ; return ! isNull ( item ) ; } catch { return false ; } } ; const Pre = ( props ) => { return < > { props . children } < />; } ; return ( < ReactMarkdown className = { `mt-3 mb-5 position-relative ${ styles . md }` } children = { props . readme } linkTarget = "_blank" skipHtml remarkPlugins = { [ [ gfm , { tableCellPadding : false } ] ] } components = { { pre : Pre , code : Code , image : Image , img : Image , a : Link , table : Table , h1 : Heading , h2 : Heading , h3 : Heading , h4 : Heading , h5 : Heading , h6 : Heading , p : Paragraph , blockquote : Blockquote , } } />); } ; export default memo ( Readme , ( prevProps , nextProps ) => { if ( prevProps . readme !== nextProps . readme ) { return false ; } return true ; } ) ;