All files / modules/10-common/components/MarkdownViewer MarkdownViewer.tsx

93.33% Statements 28/30
88.89% Branches 16/18
100% Functions 5/5
93.1% Lines 27/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98              2x 2x 2x   2x 2x 2x 2x               2x 2x 2x 2x   2x 28x         28x   4x   2x             2x       24x 8x     16x             28x     2x     18x   9x   9x                       2x   7x                            
/*
 * Copyright 2021 Harness Inc. All rights reserved.
 * Use of this source code is governed by the PolyForm Shield 1.0.0 license
 * that can be found in the licenses directory at the root of this repository, also available at
 * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt.
 */
 
import React from 'react'
import { CodeBlock, Container, Text } from '@wings-software/uicore'
import { StringKeys, useStrings } from 'framework/strings'
 
enum BlockType {
  HEADING = 'HEADING',
  TEXT = 'TEXT',
  CODE = 'CODE'
}
 
export interface MarkdownViewerProps extends React.ComponentProps<typeof Container> {
  stringId: StringKeys
  vars?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
}
 
export const MarkdownViewer: React.FC<MarkdownViewerProps> = ({ stringId, vars, ...props }) => {
  const { getString } = useStrings()
  const markdown = getString(stringId, vars)
  const blocks: Array<{ type: BlockType; text: string; opened?: boolean }> = []
 
  markdown.split(/\n/).reduce((_blocks, line) => {
    Iif (line.startsWith('#')) {
      _blocks.push({
        type: BlockType.HEADING,
        text: line.substring(1).trim() // remove '#' and trim heading
      })
    } else if (line.trim().startsWith('```')) {
      // start or end a code block
      if (_blocks[_blocks.length - 1].type !== BlockType.CODE) {
        // start
        _blocks.push({
          type: BlockType.CODE,
          text: '',
          opened: true
        })
      } else {
        // end
        delete _blocks[_blocks.length - 1].opened
      }
    } else {
      // last block in _blocks is an opened code block, push lines into it
      if (_blocks.length && _blocks[_blocks.length - 1].type === BlockType.CODE && _blocks[_blocks.length - 1].opened) {
        _blocks[_blocks.length - 1].text += (_blocks[_blocks.length - 1].text.length ? '\n' : '') + line
      } else {
        // text block
        _blocks.push({
          type: BlockType.TEXT,
          text: line
        })
      }
    }
 
    return _blocks
  }, blocks)
 
  return (
    <Container {...props}>
      {blocks
        .filter(({ text }) => text.trim().length)
        .map(({ type, text }, index) => {
          const key = type + index
 
          switch (type) {
            case BlockType.HEADING:
              return (
                <Text
                  key={key}
                  style={{ color: '#333333', fontWeight: 600, lineHeight: '16px' }}
                  margin={{ top: 'xlarge', bottom: 'medium' }}
                >
                  <span dangerouslySetInnerHTML={{ __html: text }} />
                </Text>
              )
            case BlockType.CODE:
              return <CodeBlock key={key} allowCopy format="pre" snippet={text} />
            case BlockType.TEXT:
              return (
                <Text
                  key={key}
                  style={{ color: '#333333', lineHeight: '18px' }}
                  margin={{ top: 'xlarge', bottom: 'medium' }}
                >
                  <span dangerouslySetInnerHTML={{ __html: text }} />
                </Text>
              )
          }
        })}
    </Container>
  )
}