From e4671e10a6ed88e283ce95703d1ba4c234c71747 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 8 Mar 2023 14:59:49 +0200 Subject: [PATCH] UI: markdown component and markdown widget improvements --- .../widget/lib/markdown-widget.component.html | 1 + .../widget/lib/markdown-widget.component.ts | 7 ++- .../markdown-widget-settings.component.html | 3 + .../markdown-widget-settings.component.ts | 2 + .../shared/components/markdown.component.scss | 44 +++++++++++++++ .../shared/components/markdown.component.ts | 56 ++++++++++++++----- .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/styles.scss | 49 ---------------- 8 files changed, 100 insertions(+), 63 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html index 5f4486dad2..1c3df367c4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html @@ -18,4 +18,5 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts index 3f3f13626e..98c196f393 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts @@ -40,13 +40,14 @@ interface MarkdownWidgetSettings { markdownTextPattern: string; useMarkdownTextFunction: boolean; markdownTextFunction: string; + applyDefaultMarkdownStyle: boolean; markdownCss: string; } type MarkdownTextFunction = (data: FormattedData[], ctx: WidgetContext) => string; @Component({ - selector: 'tb-markdown-widget ', + selector: 'tb-markdown-widget', templateUrl: './markdown-widget.component.html' }) export class MarkdownWidgetComponent extends PageComponent implements OnInit { @@ -63,6 +64,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { additionalStyles: string[]; + applyDefaultMarkdownStyle = true; constructor(protected store: Store, private utils: UtilsService, @@ -86,6 +88,9 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { cssString = cssParser.getCSSForEditor(cssString); this.additionalStyles = [cssString]; } + if (isDefinedAndNotNull(this.settings.applyDefaultMarkdownStyle)) { + this.applyDefaultMarkdownStyle = this.settings.applyDefaultMarkdownStyle; + } const pageSize = isDefinedAndNotNull(this.ctx.widgetConfig.pageSize) && this.ctx.widgetConfig.pageSize > 0 ? this.ctx.widgetConfig.pageSize : 16384; const pageLink: EntityDataPageLink = { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html index 97dcf167f2..cf5415afe0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html @@ -31,6 +31,9 @@ label="{{ 'widgets.markdown.markdown-text-pattern' | translate }}" helpId="widget/editor/widget_js_markdown_pattern"> + + {{ 'widgets.markdown.apply-default-markdown-style' | translate }} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.ts index 0c24a38361..9aac7efb20 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.ts @@ -43,6 +43,7 @@ export class MarkdownWidgetSettingsComponent extends WidgetSettingsComponent { useMarkdownTextFunction: false, markdownTextPattern: '# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.', markdownTextFunction: 'return \'# Some title\\\\n - Entity name: \' + data[0][\'entityName\'];', + applyDefaultMarkdownStyle: true, markdownCss: '' }; } @@ -52,6 +53,7 @@ export class MarkdownWidgetSettingsComponent extends WidgetSettingsComponent { useMarkdownTextFunction: [settings.useMarkdownTextFunction, []], markdownTextPattern: [settings.markdownTextPattern, []], markdownTextFunction: [settings.markdownTextFunction, []], + applyDefaultMarkdownStyle: [settings.applyDefaultMarkdownStyle, []], markdownCss: [settings.markdownCss, []] }); } diff --git a/ui-ngx/src/app/shared/components/markdown.component.scss b/ui-ngx/src/app/shared/components/markdown.component.scss index d30de48ade..757a26c587 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.scss +++ b/ui-ngx/src/app/shared/components/markdown.component.scss @@ -357,6 +357,50 @@ background: 0 0; padding: 8px 16px; color: #212529; + + .token.atrule, .token.attr-value, .token.keyword { + color: #2a7dec; + } + + .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { + color: #eb5757; + } + + .token.punctuation { + color: #212529; + } + + &.line-numbers { + padding-left: 66px; + + & > code { + span.line-numbers-rows { + top: -12px; + bottom: -12px; + left: -66px; + width: 50px; + border: none; + padding: 8px 12px 8px 18px; + text-align: right; + background: #f9fbff; + + & > span:before { + color: rgba(33, 37, 41, .6); + padding-right: 0; + } + } + } + + &.no-line-numbers { + padding-left: 16px; + + & > code { + span.line-numbers-rows { + display: none; + } + } + } + } } } } diff --git a/ui-ngx/src/app/shared/components/markdown.component.ts b/ui-ngx/src/app/shared/components/markdown.component.ts index 24226573e6..4ed77b66d2 100644 --- a/ui-ngx/src/app/shared/components/markdown.component.ts +++ b/ui-ngx/src/app/shared/components/markdown.component.ts @@ -26,6 +26,7 @@ import { Input, OnChanges, Output, + Renderer2, SimpleChanges, Type, ViewChild, @@ -36,9 +37,11 @@ import { MarkdownService, PrismPlugin } from 'ngx-markdown'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { SHARED_MODULE_TOKEN } from '@shared/components/tokens'; -import { deepClone, isDefinedAndNotNull } from '@core/utils'; +import { deepClone, guid, isDefinedAndNotNull } from '@core/utils'; import { Observable, of, ReplaySubject } from 'rxjs'; +let defaultMarkdownStyle; + @Component({ selector: 'tb-markdown', templateUrl: './markdown.component.html', @@ -61,6 +64,8 @@ export class TbMarkdownComponent implements OnChanges { @Input() style: { [klass: string]: any } = {}; + @Input() applyDefaultMarkdownStyle = true; + @Input() additionalStyles: string[]; @Input() @@ -87,7 +92,8 @@ export class TbMarkdownComponent implements OnChanges { private cd: ChangeDetectorRef, public markdownService: MarkdownService, @Inject(SHARED_MODULE_TOKEN) private sharedModule: Type, - private dynamicComponentFactoryService: DynamicComponentFactoryService) {} + private dynamicComponentFactoryService: DynamicComponentFactoryService, + private renderer: Renderer2) {} ngOnChanges(changes: SimpleChanges): void { if (isDefinedAndNotNull(this.data)) { @@ -97,15 +103,25 @@ export class TbMarkdownComponent implements OnChanges { private render(markdown: string) { const compiled = this.markdownService.parse(markdown, { decodeHtml: false }); - let template = this.sanitizeCurlyBraces(compiled); let markdownClass = 'tb-markdown-view'; if (this.markdownClass) { markdownClass += ` ${this.markdownClass}`; } - template = `
${template}
`; + let template = `
${compiled}
`; if (this.containerClass) { template = `
${template}
`; } + const element: HTMLDivElement = this.renderer.createElement('div'); + element.innerHTML = template; + this.handlePlugins(element); + this.markdownService.highlight(element); + const preElements = element.querySelectorAll('pre'); + const matches = Array.from(template.matchAll(/)<\/pre>/g)); + for (let i = 0; i < preElements.length; i++) { + const preHtml = preElements.item(i).outerHTML.replace('ngnonbindable=""', 'ngNonBindable'); + template = template.replace(matches[i][0], preHtml); + } + template = this.sanitizeCurlyBraces(template); this.markdownContainer.clear(); const parent = this; let readyObservable: Observable; @@ -113,8 +129,14 @@ export class TbMarkdownComponent implements OnChanges { if (this.additionalCompileModules) { compileModules = compileModules.concat(this.additionalCompileModules); } - let styles: string[] = deepClone(TbMarkdownComponent['ɵcmp'].styles); - styles[0] = styles[0].replace(/\[_nghost\-%COMP%\]/g, '').replace(/\[_ngcontent\-%COMP%\]/g, ''); + let styles: string[] = []; + if (this.applyDefaultMarkdownStyle) { + if (!defaultMarkdownStyle) { + defaultMarkdownStyle = deepClone(TbMarkdownComponent['ɵcmp'].styles)[0].replace(/\[_nghost\-%COMP%\]/g, '') + .replace(/\[_ngcontent\-%COMP%\]/g, ''); + } + styles.push(defaultMarkdownStyle); + } if (this.additionalStyles) { styles = styles.concat(this.additionalStyles); } @@ -139,20 +161,18 @@ export class TbMarkdownComponent implements OnChanges { } } this.tbMarkdownInstanceComponentRef.instance.style = this.style; - this.handlePlugins(this.tbMarkdownInstanceComponentRef.location.nativeElement); - this.markdownService.highlight(this.tbMarkdownInstanceComponentRef.location.nativeElement); readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement); this.cd.detectChanges(); this.error = null; } catch (error) { - readyObservable = this.handleError(compiled, error); + readyObservable = this.handleError(template, error, styles); } readyObservable.subscribe(() => { this.ready.emit(); }); }, (error) => { - readyObservable = this.handleError(compiled, error); + readyObservable = this.handleError(template, error, styles); this.cd.detectChanges(); readyObservable.subscribe(() => { this.ready.emit(); @@ -160,14 +180,24 @@ export class TbMarkdownComponent implements OnChanges { }); } - private handleError(template: string, error): Observable { + private handleError(template: string, error, styles?: string[]): Observable { this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '
'); this.markdownContainer.clear(); if (this.fallbackToPlainMarkdownValue) { const element = this.fallbackElement.nativeElement; + let styleElement; + if (styles?.length) { + const markdownClass = 'tb-markdown-view-' + guid(); + let innerStyle = styles.join('\n'); + innerStyle = innerStyle.replace(/\.tb-markdown-view/g, '.' + markdownClass); + template = template.replace(/tb-markdown-view/g, markdownClass); + styleElement = this.renderer.createElement('style'); + styleElement.innerHTML = innerStyle; + } element.innerHTML = template; - this.handlePlugins(element); - this.markdownService.highlight(element); + if (styleElement) { + this.renderer.appendChild(element, styleElement); + } return this.handleImages(element); } else { return of(null); diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 370432ca42..45f4371677 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -4678,6 +4678,7 @@ "use-markdown-text-function": "Use markdown/HTML value function", "markdown-text-function": "Markdown/HTML value function", "markdown-text-pattern": "Markdown/HTML pattern (markdown or HTML with variables, for ex. '${entityName} or ${keyName} - some text.')", + "apply-default-markdown-style": "Apply default markdown style", "markdown-css": "Markdown/HTML CSS" }, "simple-card": { diff --git a/ui-ngx/src/styles.scss b/ui-ngx/src/styles.scss index 369b3b9e3d..c975ece53a 100644 --- a/ui-ngx/src/styles.scss +++ b/ui-ngx/src/styles.scss @@ -424,55 +424,6 @@ mat-label { } } -.tb-markdown-view { - pre[class*="language-"] { - - .token.atrule, .token.attr-value, .token.keyword { - color: #2a7dec; - } - - .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { - color: #eb5757; - } - - .token.punctuation { - color: #212529; - } - - &.line-numbers { - padding-left: 66px; - - & > code { - span.line-numbers-rows { - top: -12px; - bottom: -12px; - left: -66px; - width: 50px; - border: none; - padding: 8px 12px 8px 18px; - text-align: right; - background: #f9fbff; - - & > span:before { - color: rgba(33, 37, 41, .6); - padding-right: 0; - } - } - } - - &.no-line-numbers { - padding-left: 16px; - - & > code { - span.line-numbers-rows { - display: none; - } - } - } - } - } -} - // Tooltipster .tooltipster-sidetip.tooltipster-tb {