import { Component, OnInit, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Exercise } from 'app/shared/model/exercise.model'; import { ExerciseService } from '../service/exercise.service'; import { TranslateService } from '@ngx-translate/core'; import { MarkdownService } from 'ngx-markdown'; import { KatexOptions, errorKatexNotLoaded } from 'ngx-markdown'; declare let katex: any; // Magic @Component({ selector: 'jhi-markdown-viewer', templateUrl: './markDownViewer.component.html', styleUrls: ['./markDownViewer.component.scss'], }) export class MarkDownViewerComponent implements OnInit, OnChanges { @Input() exercise: Exercise | undefined; public filename: string; public content: string; public baseURL: string; public katexOptions: KatexOptions = { displayMode: true, throwOnError: false, errorColor: '#cc0000', }; constructor(private exerciseService: ExerciseService, private translate: TranslateService, private markdownService: MarkdownService) { this.filename = 'README.md'; this.content = ''; this.baseURL = ''; } ngOnInit(): void { this.markdownService.renderKatex = (html: string, options?: KatexOptions) => this.renderKatex(html, options); } /** helper function for special latex rendering with katex */ private renderKatex(html: string, options?: KatexOptions): string { if (typeof katex === 'undefined' || typeof katex.renderToString === 'undefined') { throw new Error(errorKatexNotLoaded); } const reDollar = /\$([^\s][^$]*?[^\s])\$/gm; const html2 = html.replace(reDollar, (_, tex) => this.renderSanitizedLatex(tex, options)); const reMath = /<code class="language-math">((.|\n|\r])*?)<\/code>/gi; return html2.replace(reMath, (_, tex) => this.renderSanitizedLatex(tex, options)); } /** wrapper to sanitize latex before rendering with latex */ private renderSanitizedLatex(latex: string, options?: KatexOptions): string { return katex.renderToString(this.replaceConfusingCharKatex(latex), options); } /** these html entities must be reverted, in order to work with katex :-( */ private replaceConfusingCharKatex(html: string): string { return html .replace(/&/gm, '&') .replace(/</gm, '<') .replace(/>/gm, '>') .replace(/·/gm, '·') .replace(/ /gm, '·') .replace(/( \\ )+/gm, ' \\\\ '); } /** This is a hard core workaround, setting the baseUrl before content is rendered */ getContentAndSetBaseURL(): string { this.markdownService.options.baseUrl = this.baseURL; return this.content; } // eslint-disable-next-line ngOnChanges(changes: SimpleChanges): void { if (!this.exercise) return; this.baseURL = this.exercise.originalResult.project.url + '/-/raw/master/README.md'; this.exerciseService.loadExerciseFile(this.exercise.originalResult.project.project_id, 'README.md').subscribe( content => { this.content = content; this.filename = 'README.md'; }, () => { this.exerciseService.loadExerciseFile(this.exercise!.originalResult.project.project_id, 'exercise.md').subscribe( content => { this.content = content; this.filename = 'exercise.md'; }, () => { this.content = this.translate.instant('exercise.details.readmeNotFound'); this.filename = ''; } ); } ); } }