All files / lib/internal/text-formatter text-formatter.component.ts

93.47% Statements 129/138
95.45% Branches 21/22
75% Functions 6/8
93.47% Lines 129/138

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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 1391x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x           1x 1x 1x 1x 1x 1x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x       9x 9x 11x 11x 11x 9x 9x 9x 9x 9x 9x 1x 1x 1x 1x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 76x 53x 76x 76x 76x 19x 76x 76x 76x 4x 4x 12x 12x 12x 4x 4x 4x 76x   19x 10x 9x 9x 73x 73x 73x 23x 23x 23x 15x 76x 76x 76x 76x 1x  
/*******************************************************************************
 * Copyright bei
 * Entwicklungs- und Pflegeverbund für das gemeinsame Fachverfahren gefa
 *
 *******************************************************************************/
import { Component, Directive, Input, TemplateRef } from '@angular/core';
 
import { Typography } from '../../typography/typography.directive';
 
type FormattedText = (
  | { kind: 'text'; text: string }
  | { kind: 'bold'; text: FormattedText }
  | { kind: 'italic'; text: FormattedText }
  | { kind: 'br' }
  | { kind: 'ul'; liArray: FormattedText[] }
)[];
 
const MDList = /(?<list>^(?:\* .*\n*)+)/;
const MDBold = /\*\*(?<bold>\*.+?\*|.+?)\*\*/;
const MDItalic = /\*(?<italic>.+?)\*/;
const MDEmptyLine = /(?<br>\n\s*\n)/;
 
interface FormattedTextContext {
  $implicit: FormattedText;
}
 
/**
 * Typeguard for the formatted text data structure
 */
@Directive({
  selector: 'ng-template[data-gc-formatted-text]',
})
export class TextFormatterDirective {
  constructor(public readonly template: TemplateRef<FormattedTextContext>) {}
 
  /**
   * @ignore
   */
  static ngTemplateContextGuard(
    _dir: TextFormatterDirective,
    ctx: unknown,
  ): ctx is FormattedTextContext {
    return true;
  }
}
 
/**
 * Text formatter component that is used to convert a markdown text into HTML text
 * Supported formats: bold text, italic text, unordered list (one level only)
 */
@Component({
  selector: 'gc-text-formatter',
  templateUrl: './text-formatter.component.html',
  styleUrls: [
    '../../utils/styles/reset-list.css',
    './text-formatter.component.css',
  ],
})
export class TextFormatterComponent {
  /**
   * Sets data-gc-typography
   * Default: 'label'
   */
  @Input()
  public typography: Typography = 'label';
 
  /**
   * Input text in markdown format that will get converted and rendered
   */
  @Input()
  public get markdownText(): string {
    return this._markdownText;
  }
 
  public set markdownText(newText: string) {
    this._markdownText = newText;
    this.formattedText = this.convert(newText);
  }
 
  /** @ignore */
  protected formattedText: FormattedText = [];
 
  /** @ignore */
  private _markdownText = '';
 
  /** @ignore */
 
  private convert(text: string): FormattedText {
    let parts: FormattedText = [];
 
    const mdReg = new RegExp(
      MDList.source +
        '|' +
        MDBold.source +
        '|' +
        MDItalic.source +
        '|' +
        MDEmptyLine.source,
      'm',
    );
    const match: ReturnType<typeof mdReg.exec> = mdReg.exec(text);
 
    if (match === null) {
      parts.push({ kind: 'text', text: text });
      return parts;
    } else {
      // store text before the first match
      if (match.index > 0) {
        parts = [...parts, ...this.convert(text.slice(0, match.index))];
      }
 
      if (match.groups?.list) {
        const enumList: FormattedText[] = [];
        match[0].split('\n').forEach(line => {
          const textonly = line.replace('* ', '').trim();
          if (textonly.length > 0) {
            enumList.push(this.convert(textonly));
          }
        });
        parts.push({ kind: 'ul', liArray: enumList });
      } else if (match.groups?.br) {
        parts.push({ kind: 'br' });
      } else if (match.groups?.bold) {
        parts.push({ kind: 'bold', text: this.convert(match.groups.bold) });
      } else if (match.groups?.italic) {
        parts.push({ kind: 'italic', text: this.convert(match.groups.italic) });
      }
 
      text = text.slice(match.index + match[0].length);
 
      // store text following after the last match
      if (text.length > 0) {
        parts = [...parts, ...this.convert(text)];
      }
    }
    return parts;
  }
}