Replaces the static markdown and highlight.js packages with ngx-markdown and prism

parent a78d1121
...@@ -44,6 +44,7 @@ ...@@ -44,6 +44,7 @@
}, },
"styles": [ "styles": [
"src/styles/styles.scss", "src/styles/styles.scss",
"node_modules/prism-themes/themes/prism-ghcolors.css",
{ {
"input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss", "input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss",
"bundleName": "theme-arsnova-dot-click-contrast", "bundleName": "theme-arsnova-dot-click-contrast",
...@@ -101,6 +102,9 @@ ...@@ -101,6 +102,9 @@
} }
], ],
"scripts": [ "scripts": [
"node_modules/ngx-markdown/node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js",
"node_modules/prismjs/components.js"
] ]
}, },
"configurations": { "configurations": {
...@@ -129,6 +133,7 @@ ...@@ -129,6 +133,7 @@
], ],
"styles": [ "styles": [
"src/styles/styles.scss", "src/styles/styles.scss",
"node_modules/prism-themes/themes/prism-ghcolors.css",
{ {
"input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss", "input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss",
"bundleName": "theme-arsnova-dot-click-contrast", "bundleName": "theme-arsnova-dot-click-contrast",
...@@ -211,6 +216,7 @@ ...@@ -211,6 +216,7 @@
], ],
"styles": [ "styles": [
"src/styles/styles.scss", "src/styles/styles.scss",
"node_modules/prism-themes/themes/prism-ghcolors.css",
{ {
"input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss", "input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss",
"bundleName": "theme-arsnova-dot-click-contrast", "bundleName": "theme-arsnova-dot-click-contrast",
...@@ -301,6 +307,7 @@ ...@@ -301,6 +307,7 @@
], ],
"styles": [ "styles": [
"src/styles/styles.scss", "src/styles/styles.scss",
"node_modules/prism-themes/themes/prism-ghcolors.css",
{ {
"input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss", "input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss",
"bundleName": "theme-arsnova-dot-click-contrast", "bundleName": "theme-arsnova-dot-click-contrast",
...@@ -391,6 +398,7 @@ ...@@ -391,6 +398,7 @@
], ],
"styles": [ "styles": [
"src/styles/styles.scss", "src/styles/styles.scss",
"node_modules/prism-themes/themes/prism-ghcolors.css",
{ {
"input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss", "input": "src/styles/themes/theme-arsnova-dot-click-contrast.scss",
"bundleName": "theme-arsnova-dot-click-contrast", "bundleName": "theme-arsnova-dot-click-contrast",
...@@ -491,7 +499,11 @@ ...@@ -491,7 +499,11 @@
"src/styles/themes" "src/styles/themes"
] ]
}, },
"scripts": [], "scripts": [
"node_modules/ngx-markdown/node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js",
"node_modules/prismjs/components.js"
],
"assets": [ "assets": [
"src/assets/fonts", "src/assets/fonts",
"src/assets/js", "src/assets/js",
......
import * as highlight from 'highlight.js';
import * as marked from 'marked';
function createElementFromHTML(htmlString): Node {
const div = document.createElement('div');
div.innerHTML = htmlString.trim();
// Change this to div.childNodes to support multiple top-level nodes
return div.firstChild;
}
export function parseGithubFlavoredMarkdown(value: string): string {
const renderer = new marked.Renderer();
renderer.paragraph = (text) => `${text}\n`;
const options = {
renderer: renderer,
gfm: true,
tables: true,
breaks: true,
pedantic: true,
sanitize: false,
smartLists: false,
smartypants: false,
mathDelimiters: [['$', '$'], ['\\(', '\\)'], ['\\[', '\\]'], ['$$', '$$'], 'beginend'],
highlight: (code) => {
return highlight.highlightAuto(code).value;
},
};
marked.setOptions(options);
return postMarkdownRenderer(marked(preMarkdownRenderer(value)));
}
export function emojiRenderer(value: string): string {
const emojiMatch = value.match(/:([a-z0-9_\+\-]+):/g);
if (emojiMatch) {
emojiMatch.forEach(token => {
const emoji = token.replace(/:/g, '');
value = value.replace(token, `![emoji_:${emoji}:](/assets/images/emojis/${emoji}.png)`);
});
}
return value;
}
function preMarkdownRenderer(value: string): string {
return emojiRenderer(value);
}
function postMarkdownRenderer(value: string): string {
const iframeOptions = `frameborder="0" gesture="media" width="100%" webkitallowfullscreen mozallowfullscreen allowfullscreen`;
const youtubeMatch = value.match(/<a href=".*(youtube|youtu).*">.*<\/a>/g);
if (youtubeMatch) {
youtubeMatch.forEach(token => {
const originalToken = token;
if (token.indexOf('embed') === -1) {
// Convert to embed uri. Direct youtube urls are restricted by the sameorigin policy and cannot be embedded in iframes
token = token.replace('watch?v=', 'embed/');
}
const videoTag = token //
.replace('<a', `<iframe`) //
.replace('</a>', `</iframe>`) //
.replace('<iframe', `<iframe ${iframeOptions}`) //
.replace('href', 'src');
value = value.replace(originalToken, videoTag);
});
}
const vimeoMatch = value.match(/<a href=".*(vimeo).*">.*<\/a>/g);
if (vimeoMatch) {
vimeoMatch.forEach(token => {
const id = token.match(/([0-9]+)/);
if (id) {
const videoTag = `<iframe src="https://player.vimeo.com/video/${id[0]}?title=0&byline=0&portrait=0" ${iframeOptions}></iframe>`;
value = value.replace(token, videoTag);
}
});
}
const imgMatch = value.match(/<img (?!src=".*emoji.*").+?(?=>)>/g);
if (imgMatch) {
imgMatch.forEach(token => {
const imgNode: HTMLImageElement = createElementFromHTML(token) as HTMLImageElement;
imgNode.classList.add(...['thumbnail', 'cursor-zoom-in', 'img-fluid']);
const anchorNode = document.createElement<'a'>('a');
anchorNode.href = imgNode.src;
anchorNode.target = null;
anchorNode.classList.add(...['highslide', 'd-flex', 'd-sm-block', 'justify-content-center']);
anchorNode.setAttribute('onclick', 'return hs.expand(this);');
anchorNode.appendChild(imgNode);
value = value.replace(token, anchorNode.outerHTML);
});
}
const linkMatch = value.match(/<a href=".*">/g);
if (linkMatch) {
linkMatch.forEach(token => {
value = value.replace(token, token.replace('<a ', '<a rel=\'noopener noreferrer\' target=\'_blank\' '));
});
}
return value;
}
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MarkdownModule } from 'ngx-markdown';
import { HeaderModule } from '../header/header.module'; import { HeaderModule } from '../header/header.module';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { LivePreviewComponent } from './live-preview/live-preview.component'; import { LivePreviewComponent } from './live-preview/live-preview.component';
@NgModule({ @NgModule({
imports: [ imports: [
SharedModule, SharedModule, HeaderModule, MarkdownModule,
HeaderModule,
], ],
declarations: [LivePreviewComponent], declarations: [LivePreviewComponent],
exports: [LivePreviewComponent], exports: [LivePreviewComponent],
......
...@@ -46,4 +46,4 @@ ...@@ -46,4 +46,4 @@
<h3 class="text-center d-flex align-items-center server-not-available-label mb-0"> <h3 class="text-center d-flex align-items-center server-not-available-label mb-0">
{{'plugins.splashscreen.error.error_messages.no_preview_available' | translate}} {{'plugins.splashscreen.error.error_messages.no_preview_available' | translate}}
</h3> </h3>
</ng-template> </ng-template>
\ No newline at end of file
...@@ -6,6 +6,7 @@ import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; ...@@ -6,6 +6,7 @@ import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators';
import { DEVICE_TYPES, LIVE_PREVIEW_ENVIRONMENT } from '../../../environments/environment'; import { DEVICE_TYPES, LIVE_PREVIEW_ENVIRONMENT } from '../../../environments/environment';
import { AbstractChoiceQuestionEntity } from '../../lib/entities/question/AbstractChoiceQuestionEntity'; import { AbstractChoiceQuestionEntity } from '../../lib/entities/question/AbstractChoiceQuestionEntity';
import { ConnectionService } from '../../service/connection/connection.service'; import { ConnectionService } from '../../service/connection/connection.service';
import { CustomMarkdownService } from '../../service/custom-markdown/custom-markdown.service';
import { QuestionTextService } from '../../service/question-text/question-text.service'; import { QuestionTextService } from '../../service/question-text/question-text.service';
import { QuizService } from '../../service/quiz/quiz.service'; import { QuizService } from '../../service/quiz/quiz.service';
...@@ -53,7 +54,7 @@ export class LivePreviewComponent implements OnInit, OnDestroy { ...@@ -53,7 +54,7 @@ export class LivePreviewComponent implements OnInit, OnDestroy {
public connectionService: ConnectionService, public connectionService: ConnectionService,
private quizService: QuizService, private quizService: QuizService,
private sanitizer: DomSanitizer, private sanitizer: DomSanitizer,
private route: ActivatedRoute, private route: ActivatedRoute, private markdownService: CustomMarkdownService,
) { ) {
} }
...@@ -104,7 +105,7 @@ export class LivePreviewComponent implements OnInit, OnDestroy { ...@@ -104,7 +105,7 @@ export class LivePreviewComponent implements OnInit, OnDestroy {
public ngOnInit(): void { public ngOnInit(): void {
this.questionTextService.eventEmitter.pipe(takeUntil(this._destroy)).subscribe(value => { this.questionTextService.eventEmitter.pipe(takeUntil(this._destroy)).subscribe(value => {
this.dataSource = Array.isArray(value) ? value : [value]; this.dataSource = Array.isArray(value) ? value : [this.markdownService.parseGithubFlavoredMarkdown(value)];
}); });
const questionIndex$ = this.route.paramMap.pipe(map(params => parseInt(params.get('questionIndex'), 10)), distinctUntilChanged(), const questionIndex$ = this.route.paramMap.pipe(map(params => parseInt(params.get('questionIndex'), 10)), distinctUntilChanged(),
takeUntil(this._destroy)); takeUntil(this._destroy));
......
...@@ -9,5 +9,5 @@ import { MarkdownBarComponent } from './markdown-bar/markdown-bar.component'; ...@@ -9,5 +9,5 @@ import { MarkdownBarComponent } from './markdown-bar/markdown-bar.component';
declarations: [MarkdownBarComponent], declarations: [MarkdownBarComponent],
exports: [MarkdownBarComponent], exports: [MarkdownBarComponent],
}) })
export class MarkdownModule { export class MarkdownBarModule {
} }
import { MarkdownModule } from './markdown.module'; import { MarkdownBarModule } from './markdown-bar.module';
describe('MarkdownModule', () => { describe('MarkdownModule', () => {
let markdownModule: MarkdownModule; let markdownModule: MarkdownBarModule;
beforeEach(() => { beforeEach(() => {
markdownModule = new MarkdownModule(); markdownModule = new MarkdownBarModule();
}); });
it('should create an instance', () => { it('should create an instance', () => {
......
...@@ -11,11 +11,11 @@ import { StorageKey } from '../../../lib/enums/enums'; ...@@ -11,11 +11,11 @@ import { StorageKey } from '../../../lib/enums/enums';
import { MessageProtocol } from '../../../lib/enums/Message'; import { MessageProtocol } from '../../../lib/enums/Message';
import { QuizState } from '../../../lib/enums/QuizState'; import { QuizState } from '../../../lib/enums/QuizState';
import { ILeaderBoardItem } from '../../../lib/interfaces/ILeaderboard'; import { ILeaderBoardItem } from '../../../lib/interfaces/ILeaderboard';
import { parseGithubFlavoredMarkdown } from '../../../lib/markdown/markdown';
import { ServerUnavailableModalComponent } from '../../../modals/server-unavailable-modal/server-unavailable-modal.component'; import { ServerUnavailableModalComponent } from '../../../modals/server-unavailable-modal/server-unavailable-modal.component';
import { LeaderboardApiService } from '../../../service/api/leaderboard/leaderboard-api.service'; import { LeaderboardApiService } from '../../../service/api/leaderboard/leaderboard-api.service';
import { AttendeeService } from '../../../service/attendee/attendee.service'; import { AttendeeService } from '../../../service/attendee/attendee.service';
import { ConnectionService } from '../../../service/connection/connection.service'; import { ConnectionService } from '../../../service/connection/connection.service';
import { CustomMarkdownService } from '../../../service/custom-markdown/custom-markdown.service';
import { FooterBarService } from '../../../service/footer-bar/footer-bar.service'; import { FooterBarService } from '../../../service/footer-bar/footer-bar.service';
import { HeaderLabelService } from '../../../service/header-label/header-label.service'; import { HeaderLabelService } from '../../../service/header-label/header-label.service';
import { I18nService } from '../../../service/i18n/i18n.service'; import { I18nService } from '../../../service/i18n/i18n.service';
...@@ -77,7 +77,7 @@ export class LeaderboardComponent implements OnInit, OnDestroy { ...@@ -77,7 +77,7 @@ export class LeaderboardComponent implements OnInit, OnDestroy {
private connectionService: ConnectionService, private connectionService: ConnectionService,
private i18nService: I18nService, private i18nService: I18nService,
private leaderboardApiService: LeaderboardApiService, private leaderboardApiService: LeaderboardApiService,
private ngbModal: NgbModal, private messageQueue: SimpleMQ, private ngbModal: NgbModal, private messageQueue: SimpleMQ, private customMarkdownService: CustomMarkdownService,
) { ) {
this.footerBarService.TYPE_REFERENCE = LeaderboardComponent.TYPE; this.footerBarService.TYPE_REFERENCE = LeaderboardComponent.TYPE;
} }
...@@ -135,7 +135,7 @@ export class LeaderboardComponent implements OnInit, OnDestroy { ...@@ -135,7 +135,7 @@ export class LeaderboardComponent implements OnInit, OnDestroy {
public parseNickname(value: string): string { public parseNickname(value: string): string {
if (value.match(/:[\w\+\-]+:/g)) { if (value.match(/:[\w\+\-]+:/g)) {
return this.sanitizeHTML(parseGithubFlavoredMarkdown(value)); return this.sanitizeHTML(this.customMarkdownService.parseGithubFlavoredMarkdown(value));
} }
return value; return value;
} }
......
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { QRCodeModule } from 'angularx-qrcode'; import { QRCodeModule } from 'angularx-qrcode';
import { MarkdownModule } from '../../markdown/markdown.module'; import { MarkdownModule } from 'ngx-markdown';
import { CasLoginService } from '../../service/login/cas-login.service'; import { CasLoginService } from '../../service/login/cas-login.service';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { ConfidenceRateComponent } from './confidence-rate/confidence-rate.component'; import { ConfidenceRateComponent } from './confidence-rate/confidence-rate.component';
...@@ -59,7 +59,7 @@ export const quizFlowRoutes: Routes = [ ...@@ -59,7 +59,7 @@ export const quizFlowRoutes: Routes = [
@NgModule({ @NgModule({
imports: [ imports: [
MarkdownModule, SharedModule, RouterModule.forChild(quizFlowRoutes), QuizResultsModule, QRCodeModule, QuizFlowSharedModule, SharedModule, RouterModule.forChild(quizFlowRoutes), QuizResultsModule, QRCodeModule, QuizFlowSharedModule, MarkdownModule.forChild(),
], ],
bootstrap: [EditModeConfirmComponent, QrCodeContentComponent], bootstrap: [EditModeConfirmComponent, QrCodeContentComponent],
declarations: [ declarations: [
......
...@@ -14,12 +14,12 @@ import { UserRole } from '../../../lib/enums/UserRole'; ...@@ -14,12 +14,12 @@ import { UserRole } from '../../../lib/enums/UserRole';
import { FooterbarElement } from '../../../lib/footerbar-element/footerbar-element'; import { FooterbarElement } from '../../../lib/footerbar-element/footerbar-element';
import { IMessage } from '../../../lib/interfaces/communication/IMessage'; import { IMessage } from '../../../lib/interfaces/communication/IMessage';
import { IMemberSerialized } from '../../../lib/interfaces/entities/Member/IMemberSerialized'; import { IMemberSerialized } from '../../../lib/interfaces/entities/Member/IMemberSerialized';
import { parseGithubFlavoredMarkdown } from '../../../lib/markdown/markdown';
import { ServerUnavailableModalComponent } from '../../../modals/server-unavailable-modal/server-unavailable-modal.component'; import { ServerUnavailableModalComponent } from '../../../modals/server-unavailable-modal/server-unavailable-modal.component';
import { MemberApiService } from '../../../service/api/member/member-api.service'; import { MemberApiService } from '../../../service/api/member/member-api.service';
import { QuizApiService } from '../../../service/api/quiz/quiz-api.service'; import { QuizApiService } from '../../../service/api/quiz/quiz-api.service';
import { AttendeeService } from '../../../service/attendee/attendee.service'; import { AttendeeService } from '../../../service/attendee/attendee.service';
import { ConnectionService } from '../../../service/connection/connection.service'; import { ConnectionService } from '../../../service/connection/connection.service';
import { CustomMarkdownService } from '../../../service/custom-markdown/custom-markdown.service';
import { FooterBarService } from '../../../service/footer-bar/footer-bar.service'; import { FooterBarService } from '../../../service/footer-bar/footer-bar.service';
import { HeaderLabelService } from '../../../service/header-label/header-label.service'; import { HeaderLabelService } from '../../../service/header-label/header-label.service';
import { QuizService } from '../../../service/quiz/quiz.service'; import { QuizService } from '../../../service/quiz/quiz.service';
...@@ -67,7 +67,7 @@ export class QuizLobbyComponent implements OnInit, OnDestroy { ...@@ -67,7 +67,7 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
private ngbModal: NgbModal, private ngbModal: NgbModal,
private sharedService: SharedService, private sharedService: SharedService,
private userService: UserService, private userService: UserService,
private messageQueue: SimpleMQ, private messageQueue: SimpleMQ, private customMarkdownService: CustomMarkdownService,
) { ) {
sessionStorage.removeItem(StorageKey.CurrentQuestionIndex); sessionStorage.removeItem(StorageKey.CurrentQuestionIndex);
this.footerBarService.TYPE_REFERENCE = QuizLobbyComponent.TYPE; this.footerBarService.TYPE_REFERENCE = QuizLobbyComponent.TYPE;
...@@ -152,7 +152,7 @@ export class QuizLobbyComponent implements OnInit, OnDestroy { ...@@ -152,7 +152,7 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
public parseNickname(value: string): string { public parseNickname(value: string): string {
if (value.match(/:[\w\+\-]+:/g)) { if (value.match(/:[\w\+\-]+:/g)) {
return this.sanitizeHTML(parseGithubFlavoredMarkdown(value)); return this.sanitizeHTML(this.customMarkdownService.parseGithubFlavoredMarkdown(value));
} }
return value; return value;
} }
......
import { ChangeDetectorRef, Component, Input, SecurityContext } from '@angular/core'; import { ChangeDetectorRef, Component, Input, SecurityContext } from '@angular/core';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { parseGithubFlavoredMarkdown } from '../../../../lib/markdown/markdown'; import { CustomMarkdownService } from '../../../../service/custom-markdown/custom-markdown.service';
import { I18nService } from '../../../../service/i18n/i18n.service'; import { I18nService } from '../../../../service/i18n/i18n.service';
@Component({ @Component({
...@@ -45,10 +45,15 @@ export class ConfidenceRateComponent { ...@@ -45,10 +45,15 @@ export class ConfidenceRateComponent {
private _name: string; private _name: string;
@Input() set name(value: string) { @Input() set name(value: string) {
this._name = parseGithubFlavoredMarkdown(value); this._name = this.customMarkdownService.parseGithubFlavoredMarkdown(value);
} }
constructor(private i18nService: I18nService, private sanitizer: DomSanitizer, private cd: ChangeDetectorRef) { constructor(
private i18nService: I18nService,
private sanitizer: DomSanitizer,
private cd: ChangeDetectorRef,
private customMarkdownService: CustomMarkdownService,
) {
} }
public sanitizeStyle(value: string): SafeStyle { public sanitizeStyle(value: string): SafeStyle {
......
import { ChangeDetectorRef, Component, Input, SecurityContext } from '@angular/core'; import { ChangeDetectorRef, Component, Input, SecurityContext } from '@angular/core';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { parseGithubFlavoredMarkdown } from '../../../../lib/markdown/markdown'; import { CustomMarkdownService } from '../../../../service/custom-markdown/custom-markdown.service';
import { I18nService } from '../../../../service/i18n/i18n.service'; import { I18nService } from '../../../../service/i18n/i18n.service';
@Component({ @Component({
...@@ -46,12 +46,17 @@ export class ReadingConfirmationProgressComponent { ...@@ -46,12 +46,17 @@ export class ReadingConfirmationProgressComponent {
private _name: string; private _name: string;
@Input() set name(value: string) { @Input() set name(value: string) {
this._name = parseGithubFlavoredMarkdown(value); this._name = this.customMarkdownService.parseGithubFlavoredMarkdown(value);
} }
private _hasData = false; private _hasData = false;
constructor(private i18nService: I18nService, private sanitizer: DomSanitizer, private cd: ChangeDetectorRef) { constructor(
private i18nService: I18nService,
private sanitizer: DomSanitizer,
private cd: ChangeDetectorRef,
private customMarkdownService: CustomMarkdownService,
) {
} }
public sanitizeStyle(value: string): SafeStyle { public sanitizeStyle(value: string): SafeStyle {
......
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { LivePreviewModule } from '../../../live-preview/live-preview.module'; import { LivePreviewModule } from '../../../live-preview/live-preview.module';
import { MarkdownModule } from '../../../markdown/markdown.module'; import { MarkdownBarModule } from '../../../markdown/markdown-bar.module';
import { SharedModule } from '../../../shared/shared.module'; import { SharedModule } from '../../../shared/shared.module';
import { AnsweroptionsModule } from './answeroptions/answeroptions.module'; import { AnsweroptionsModule } from './answeroptions/answeroptions.module';
import { CountdownComponent } from './countdown/countdown.component'; import { CountdownComponent } from './countdown/countdown.component';
...@@ -10,7 +10,7 @@ import { QuestiontypeComponent } from './questiontype/questiontype.component'; ...@@ -10,7 +10,7 @@ import { QuestiontypeComponent } from './questiontype/questiontype.component';
@NgModule({ @NgModule({
imports: [ imports: [
SharedModule, MarkdownModule, LivePreviewModule, AnsweroptionsModule, SharedModule, MarkdownBarModule, LivePreviewModule, AnsweroptionsModule,
], ],
providers: [], providers: [],
declarations: [QuizManagerDetailsOverviewComponent, CountdownComponent, QuestiontextComponent, QuestiontypeComponent], declarations: [QuizManagerDetailsOverviewComponent, CountdownComponent, QuestiontextComponent, QuestiontypeComponent],
......
...@@ -4,8 +4,8 @@ import { Subject } from 'rxjs'; ...@@ -4,8 +4,8 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { StorageKey } from '../../../lib/enums/enums'; import { StorageKey } from '../../../lib/enums/enums';
import { IAvailableNicks } from '../../../lib/interfaces/IAvailableNicks'; import { IAvailableNicks } from '../../../lib/interfaces/IAvailableNicks';
import { parseGithubFlavoredMarkdown } from '../../../lib/markdown/markdown';
import { NickApiService } from '../../../service/api/nick/nick-api.service'; import { NickApiService