diff --git a/src/app/components/creator/_dialogs/room-description-settings/room-description-settings.component.html b/src/app/components/creator/_dialogs/room-description-settings/room-description-settings.component.html index 50b4fdab337336a77344cd04acd6f9525fb3cc0b..fc31a5c890e9b44f6863592a9fc50e05c9d69a54 100644 --- a/src/app/components/creator/_dialogs/room-description-settings/room-description-settings.component.html +++ b/src/app/components/creator/_dialogs/room-description-settings/room-description-settings.component.html @@ -29,8 +29,7 @@ </mat-tab> <mat-tab label="{{'session.preview' | translate}}" [disabled]="!editRoom.description"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="editRoom.description"></markdown> + <app-custom-markdown class="images" [data]="editRoom.description"></app-custom-markdown> </mat-tab> </mat-tab-group> </div> diff --git a/src/app/components/creator/_dialogs/room-edit/room-edit.component.html b/src/app/components/creator/_dialogs/room-edit/room-edit.component.html index 9c14d1e86e2614090971036c598aaeeddc03afd7..f05eb018dd58f7fc88fc8b3eb081890b180259ba 100644 --- a/src/app/components/creator/_dialogs/room-edit/room-edit.component.html +++ b/src/app/components/creator/_dialogs/room-edit/room-edit.component.html @@ -51,8 +51,7 @@ </mat-tab> <mat-tab label="{{'session.preview' | translate}}" [disabled]="!editRoom.description"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="editRoom.description"></markdown> + <app-custom-markdown class="images" [data]="editRoom.description"></app-custom-markdown> </mat-tab> </mat-tab-group> <div fxLayout="column"> diff --git a/src/app/components/creator/room-creator-page/room-creator-page.component.html b/src/app/components/creator/room-creator-page/room-creator-page.component.html index 0253e5e8cbbe8ddb423370e56d17f1061239fa71..b4d745512dc7295038fc4f211966c104a1fe46da 100644 --- a/src/app/components/creator/room-creator-page/room-creator-page.component.html +++ b/src/app/components/creator/room-creator-page/room-creator-page.component.html @@ -65,12 +65,7 @@ </div> <mat-card-content *ngIf="room.description" fxLayoutAlign="center"> - <markdown class="images" - katex - emoji - lineNumbers - lineHighlight - [data]="room.description.trim()"></markdown> + <app-custom-markdown class="images" [data]="room.description.trim()"></app-custom-markdown> </mat-card-content> <div fxLayout="column" fxLayoutAlign="center" diff --git a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html index 2c2a037b2ac070b6bfcd6eb83fc1908e65764e7e..61a847b46dc6ed9dac012768edb59457c3e5256f 100644 --- a/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html +++ b/src/app/components/moderator/room-moderator-page/room-moderator-page.component.html @@ -34,12 +34,7 @@ <mat-card-content *ngIf="room.description" fxLayoutAlign="center"> - <markdown class="images" - katex - emoji - lineNumbers - lineHighlight - [data]="room.description.trim()"></markdown> + <app-custom-markdown class="images" [data]="room.description.trim()"></app-custom-markdown> </mat-card-content> <div fxLayout="column" fxLayoutAlign="center" diff --git a/src/app/components/participant/room-participant-page/room-participant-page.component.html b/src/app/components/participant/room-participant-page/room-participant-page.component.html index 37304ea64cecd70d8eb214d52e0514c4933161c2..5da0230922f98d2fa9238d034ffd97aea4cbaa39 100644 --- a/src/app/components/participant/room-participant-page/room-participant-page.component.html +++ b/src/app/components/participant/room-participant-page/room-participant-page.component.html @@ -21,8 +21,7 @@ </div> <mat-card-content *ngIf="room.description" fxLayoutAlign="center"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="room.description.trim()"></markdown> + <app-custom-markdown class="images" [data]="room.description.trim()"></app-custom-markdown> </mat-card-content> <mat-grid-list cols="1" rowHeight="2:1"> <mat-grid-tile> diff --git a/src/app/components/shared/_dialogs/motd-dialog/motd-message/motd-message.component.html b/src/app/components/shared/_dialogs/motd-dialog/motd-message/motd-message.component.html index ce2cca0095942e574f84e25adeda7733ea24086d..be7e211bf926e7f86d5e095f36c56cf0440da210 100644 --- a/src/app/components/shared/_dialogs/motd-dialog/motd-message/motd-message.component.html +++ b/src/app/components/shared/_dialogs/motd-dialog/motd-message/motd-message.component.html @@ -13,6 +13,6 @@ </ars-col> </ars-row> <ars-row> - <markdown class="images" #markdown [data]="translatedMessage"></markdown> + <app-custom-markdown class="images" #markdown [data]="translatedMessage"></app-custom-markdown> </ars-row> </ars-row> diff --git a/src/app/components/shared/_dialogs/motd-temp-dialog/motd-temp-dialog.component.html b/src/app/components/shared/_dialogs/motd-temp-dialog/motd-temp-dialog.component.html index 158ee47c32f3c08bd5a2e30229d91e8014427999..47c5bdba7353245f940677ec2d91f282f881e789 100644 --- a/src/app/components/shared/_dialogs/motd-temp-dialog/motd-temp-dialog.component.html +++ b/src/app/components/shared/_dialogs/motd-temp-dialog/motd-temp-dialog.component.html @@ -7,8 +7,7 @@ </ars-row> <mat-dialog-content class="container" ars-flex-box> <ars-row ars-flex-box> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="content"></markdown> + <app-custom-markdown class="images" [data]="content"></app-custom-markdown> </ars-row> <ars-row [height]="32"></ars-row> <ars-fill></ars-fill> diff --git a/src/app/components/shared/_dialogs/present-comment/present-comment.component.html b/src/app/components/shared/_dialogs/present-comment/present-comment.component.html index fceed936fc7842843239ba6c76b467093db82514..cf4217111f40b7f7b6b7345310dccb992d4be044 100644 --- a/src/app/components/shared/_dialogs/present-comment/present-comment.component.html +++ b/src/app/components/shared/_dialogs/present-comment/present-comment.component.html @@ -12,7 +12,7 @@ <mat-icon >close</mat-icon> </button> <div id="comment"> - <markdown [data]="body"></markdown> + <app-custom-markdown [data]="body"></app-custom-markdown> </div> <div class="visually-hidden"> diff --git a/src/app/components/shared/comment-answer/comment-answer.component.html b/src/app/components/shared/comment-answer/comment-answer.component.html index 3f55e448d913b9a160454c159ff27044beff917a..8d133d0083abd17604e4bf9a2321dae7c941ea58 100644 --- a/src/app/components/shared/comment-answer/comment-answer.component.html +++ b/src/app/components/shared/comment-answer/comment-answer.component.html @@ -25,8 +25,7 @@ <ng-template #editAnswer> <div *ngIf="(isStudent || !edit) && answer"> - <markdown class="imborder-answerages" katex emoji lineNumbers lineHighlight - [data]="answer"></markdown> + <app-custom-markdown class="imborder-answerages" [data]="answer"></app-custom-markdown> <div fxLayout="row" fxLayoutAlign="end"> <button mat-raised-button diff --git a/src/app/components/shared/comment/comment.component.html b/src/app/components/shared/comment/comment.component.html index 1e7139434735eeec40b98ef61076bd1ea165b4b6..bd009b1e970a3438c0d77feb16a6287ee4112155 100644 --- a/src/app/components/shared/comment/comment.component.html +++ b/src/app/components/shared/comment/comment.component.html @@ -264,8 +264,7 @@ tabindex="0"> <ars-row #commentBody> <ars-row #commentBodyInner> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="comment.body"></markdown> + <app-custom-markdown class="images" [data]="comment.body"></app-custom-markdown> </ars-row> </ars-row> <span id="comment-{{ comment.id }}" diff --git a/src/app/components/shared/custom-markdown/custom-markdown.component.scss b/src/app/components/shared/custom-markdown/custom-markdown.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/app/components/shared/custom-markdown/custom-markdown.component.spec.ts b/src/app/components/shared/custom-markdown/custom-markdown.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..be6dc6386da6215d8964594f9bb561d2c028e3de --- /dev/null +++ b/src/app/components/shared/custom-markdown/custom-markdown.component.spec.ts @@ -0,0 +1,26 @@ +/*import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CustomMarkdownComponent } from './custom-markdown.component'; + +describe('CustomMarkdownComponent', () => { + let component: CustomMarkdownComponent; + let fixture: ComponentFixture<CustomMarkdownComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ CustomMarkdownComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CustomMarkdownComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); + */ diff --git a/src/app/components/shared/custom-markdown/custom-markdown.component.ts b/src/app/components/shared/custom-markdown/custom-markdown.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9b00e52d5a73d8c688ad723a29a30399cd68032e --- /dev/null +++ b/src/app/components/shared/custom-markdown/custom-markdown.component.ts @@ -0,0 +1,131 @@ +import { Component, ElementRef, Input, OnChanges, SimpleChanges } from '@angular/core'; +import { KatexOptions, MarkdownService, PrismPlugin } from 'ngx-markdown'; + +@Component({ + selector: 'app-custom-markdown', + template: '<ng-content></ng-content>', + styleUrls: ['./custom-markdown.component.scss'] +}) +export class CustomMarkdownComponent implements OnChanges { + @Input() katex = true; + @Input() emoji = true; + @Input() lineNumbers = true; + @Input() lineHighlight = true; + @Input() start: number | undefined; + @Input() line: string | string[] | undefined; + @Input() lineOffset: number | undefined; + @Input() katexOptions: KatexOptions = { + throwOnError: true + }; + @Input() data: string; + + constructor(public element: ElementRef<HTMLElement>, + public markdownService: MarkdownService) { + } + + private static setPluginClass(element: HTMLElement, plugin: string | string[]): void { + const preElements = element.querySelectorAll('pre'); + for (let i = 0; i < preElements.length; i++) { + const classes = plugin instanceof Array ? plugin : [plugin]; + preElements.item(i).classList.add(...classes); + } + } + + private static setPluginOptions(element: HTMLElement, options: { [key: string]: number | string | string[] | undefined }): void { + const preElements = element.querySelectorAll('pre'); + for (let i = 0; i < preElements.length; i++) { + Object.keys(options).forEach(option => { + const attributeValue = options[option]; + if (attributeValue) { + const attributeName = this.toLispCase(option); + preElements.item(i).setAttribute(attributeName, attributeValue.toString()); + } + }); + } + } + + private static toLispCase(value: string): string { + const upperChars = value.match(/([A-Z])/g); + if (!upperChars) { + return value; + } + let str = value.toString(); + for (let i = 0, n = upperChars.length; i < n; i++) { + str = str.replace(new RegExp(upperChars[i]), '-' + upperChars[i].toLowerCase()); + } + if (str.slice(0, 1) === '-') { + str = str.slice(1); + } + return str; + } + + private static fixKatex(markdown: string): string { + const regexp = /\${1,2}[^$]+\${1,2}/g; + let newStr = ''; + let lastIndex = 0; + let match: RegExpExecArray; + while ((match = regexp.exec(markdown)) !== null) { + if (match.index === regexp.lastIndex) { + regexp.lastIndex++; + } + newStr += markdown.substring(lastIndex, match.index) + match[0].replace('\\\\', '\\\\\\\\'); + lastIndex = match.index + match[0].length; + } + newStr += markdown.substring(lastIndex); + return newStr; + } + + private static decodeHTML(encodedHTML: string) { + const elem = document.createElement('textarea'); + elem.innerHTML = encodedHTML; + return elem.value; + } + + ngOnChanges(changes: SimpleChanges) { + if (this.data != null) { + this.render(this.data); + } + } + + private render(markdown: string, decodeHtml = false): void { + if (this.katex) { + markdown = CustomMarkdownComponent.fixKatex(markdown); + } + const compiled = this.markdownService.compile(markdown, decodeHtml, this.emoji); + this.element.nativeElement.innerHTML = this.katex ? this.renderKatex(compiled) : compiled; + if (this.lineHighlight) { + CustomMarkdownComponent.setPluginOptions(this.element.nativeElement, { + dataLine: this.line, + dataLineOffset: this.lineOffset + }); + } + if (this.lineNumbers) { + CustomMarkdownComponent.setPluginClass(this.element.nativeElement, PrismPlugin.LineNumbers); + CustomMarkdownComponent.setPluginOptions(this.element.nativeElement, { dataStart: this.start }); + } + this.markdownService.highlight(this.element.nativeElement); + } + + private renderKatex(compiledMarkdown: string): string { + const regexp = /\${1,2}[^$]+\${1,2}/g; + let newStr = ''; + let lastIndex = 0; + let match: RegExpExecArray; + while ((match = regexp.exec(compiledMarkdown)) !== null) { + if (match.index === regexp.lastIndex) { + regexp.lastIndex++; + } + newStr += compiledMarkdown.substring(lastIndex, match.index); + const katex = match[0]; + lastIndex = match.index + katex.length; + this.katexOptions.displayMode = katex.startsWith('$$') && katex.endsWith('$$'); + const offset = this.katexOptions.displayMode ? 2 : 1; + const innerKatex = CustomMarkdownComponent.decodeHTML(katex.substring(offset, katex.length - offset)) + .replace(/(^\s*)|(\s+$)/g, ''); + newStr += this.markdownService.renderKatex('$' + innerKatex + '$', this.katexOptions); + } + newStr += compiledMarkdown.substring(lastIndex); + return newStr; + } + +} diff --git a/src/app/components/shared/questionwall/question-wall/question-wall.component.html b/src/app/components/shared/questionwall/question-wall/question-wall.component.html index 6161296e31747039d2026a4d252ea2f5908d3377..b5c65f32b432ea53cd8a028ddbf6eef46a96fe10 100644 --- a/src/app/components/shared/questionwall/question-wall/question-wall.component.html +++ b/src/app/components/shared/questionwall/question-wall/question-wall.component.html @@ -123,9 +123,9 @@ </ars-row> <ars-row> <ars-row class="bound" style="padding:16px 32px 16px 32px;box-sizing:border-box;max-width:100%;"> - <markdown [ngStyle]="{'font-size':fontSize+'%'}" class="questionwall-present-markdown" class="images" - katex emoji lineNumbers lineHighlight - [data]="commentFocus.comment.body"></markdown> + <app-custom-markdown [ngStyle]="{'font-size':fontSize+'%'}" + class="questionwall-present-markdown images" + [data]="commentFocus.comment.body"></app-custom-markdown> </ars-row> </ars-row> <ars-row [height]="50"> @@ -231,8 +231,7 @@ (click)="focusComment(comment)" style="box-sizing:border-box;padding:0 16px;cursor:pointer"> <p class="questionwall-comment-body"> - <markdown class="images" katex emoji lineNumbers lineHighlight - [data]="comment.comment.body"></markdown> + <app-custom-markdown class="images" [data]="comment.comment.body"></app-custom-markdown> </p> </ars-row> <ars-row [height]="50"> diff --git a/src/app/components/shared/shared.module.ts b/src/app/components/shared/shared.module.ts index 1256415f0910cc9056e05b37c37c20863d062c14..40b9193a271a0f44d648ba93272e06a8a1b24f19 100644 --- a/src/app/components/shared/shared.module.ts +++ b/src/app/components/shared/shared.module.ts @@ -47,6 +47,7 @@ import { JoyrideTemplateComponent } from './_dialogs/joyride-template/joyride-te import { JoyrideTemplateDirective } from '../../directives/joyride-template.directive'; import { MatSpinnerOverlayComponent } from './mat-spinner-overlay/mat-spinner-overlay.component'; import { WriteCommentComponent } from './write-comment/write-comment.component'; +import { CustomMarkdownComponent } from './custom-markdown/custom-markdown.component'; @NgModule({ imports: [ @@ -99,7 +100,8 @@ import { WriteCommentComponent } from './write-comment/write-comment.component'; JoyrideTemplateComponent, JoyrideTemplateDirective, MatSpinnerOverlayComponent, - WriteCommentComponent + WriteCommentComponent, + CustomMarkdownComponent ], exports: [ RoomJoinComponent, @@ -120,7 +122,8 @@ import { WriteCommentComponent } from './write-comment/write-comment.component'; ActiveUserComponent, MatSpinnerOverlayComponent, JoyrideTemplateDirective, - AutofocusDirective + AutofocusDirective, + CustomMarkdownComponent ] }) export class SharedModule { diff --git a/src/app/components/shared/write-comment/write-comment.component.html b/src/app/components/shared/write-comment/write-comment.component.html index 38596d969fe9a33f47086d3fffe971670aee7d9c..a5fd579b1e1fcb5762c72a6b698cd53aee18e1f8 100644 --- a/src/app/components/shared/write-comment/write-comment.component.html +++ b/src/app/components/shared/write-comment/write-comment.component.html @@ -47,7 +47,7 @@ </mat-form-field> </div> </div> - <mat-tab-group (selectedTabChange)="tempEditView = commentBody.innerText" *ngIf="enabled"> + <mat-tab-group (selectedTabChange)="onTabChange()" *ngIf="enabled"> <mat-tab label="{{ 'comment-page.write-comment' | translate }}"> <ars-row [height]="12"></ars-row> <ars-row> @@ -100,11 +100,7 @@ </ars-row> <ars-row [height]="12"></ars-row> <ars-row> - <markdown katex - emoji - lineNumbers - lineHighlight - [data]="tempEditView"></markdown> + <app-custom-markdown [data]="tempEditView"></app-custom-markdown> </ars-row> </mat-tab> </mat-tab-group> diff --git a/src/app/components/shared/write-comment/write-comment.component.ts b/src/app/components/shared/write-comment/write-comment.component.ts index ccdfb87a2ea07117ea0a693d8a6ba163a939cdee..3198b0b73e321b547d01b58264c694c103721cf4 100644 --- a/src/app/components/shared/write-comment/write-comment.component.ts +++ b/src/app/components/shared/write-comment/write-comment.component.ts @@ -31,7 +31,6 @@ export class WriteCommentComponent implements OnInit, AfterViewInit { grammarChecker: GrammarChecker; tempEditView: string; - constructor(private notification: NotificationService, private translateService: TranslateService, public eventService: EventService, @@ -65,6 +64,10 @@ export class WriteCommentComponent implements OnInit, AfterViewInit { }; } + onTabChange() { + this.tempEditView = this.commentBody.nativeElement.innerText; + } + private checkInputData(body: string): boolean { body = body.trim(); if (!body) {