Browse Source

UI: markdown component and markdown widget improvements

pull/8203/head
Igor Kulikov 4 years ago
parent
commit
e4671e10a6
  1. 1
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html
  2. 7
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts
  3. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.html
  4. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/cards/markdown-widget-settings.component.ts
  5. 44
      ui-ngx/src/app/shared/components/markdown.component.scss
  6. 56
      ui-ngx/src/app/shared/components/markdown.component.ts
  7. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  8. 49
      ui-ngx/src/styles.scss

1
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.html

@ -18,4 +18,5 @@
<tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule ]" <tb-markdown [data]="markdownText" [additionalCompileModules]="[ homeComponentsModule ]"
[additionalStyles]="additionalStyles" [additionalStyles]="additionalStyles"
[containerClass]="markdownClass" [containerClass]="markdownClass"
[applyDefaultMarkdownStyle]="applyDefaultMarkdownStyle"
[context]="{ ctx: ctx }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown> [context]="{ ctx: ctx }" lineNumbers fallbackToPlainMarkdown (click)="markdownClick($event)"></tb-markdown>

7
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts

@ -40,13 +40,14 @@ interface MarkdownWidgetSettings {
markdownTextPattern: string; markdownTextPattern: string;
useMarkdownTextFunction: boolean; useMarkdownTextFunction: boolean;
markdownTextFunction: string; markdownTextFunction: string;
applyDefaultMarkdownStyle: boolean;
markdownCss: string; markdownCss: string;
} }
type MarkdownTextFunction = (data: FormattedData[], ctx: WidgetContext) => string; type MarkdownTextFunction = (data: FormattedData[], ctx: WidgetContext) => string;
@Component({ @Component({
selector: 'tb-markdown-widget ', selector: 'tb-markdown-widget',
templateUrl: './markdown-widget.component.html' templateUrl: './markdown-widget.component.html'
}) })
export class MarkdownWidgetComponent extends PageComponent implements OnInit { export class MarkdownWidgetComponent extends PageComponent implements OnInit {
@ -63,6 +64,7 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
additionalStyles: string[]; additionalStyles: string[];
applyDefaultMarkdownStyle = true;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private utils: UtilsService, private utils: UtilsService,
@ -86,6 +88,9 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
cssString = cssParser.getCSSForEditor(cssString); cssString = cssParser.getCSSForEditor(cssString);
this.additionalStyles = [cssString]; this.additionalStyles = [cssString];
} }
if (isDefinedAndNotNull(this.settings.applyDefaultMarkdownStyle)) {
this.applyDefaultMarkdownStyle = this.settings.applyDefaultMarkdownStyle;
}
const pageSize = isDefinedAndNotNull(this.ctx.widgetConfig.pageSize) && const pageSize = isDefinedAndNotNull(this.ctx.widgetConfig.pageSize) &&
this.ctx.widgetConfig.pageSize > 0 ? this.ctx.widgetConfig.pageSize : 16384; this.ctx.widgetConfig.pageSize > 0 ? this.ctx.widgetConfig.pageSize : 16384;
const pageLink: EntityDataPageLink = { const pageLink: EntityDataPageLink = {

3
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 }}" label="{{ 'widgets.markdown.markdown-text-pattern' | translate }}"
helpId="widget/editor/widget_js_markdown_pattern"> helpId="widget/editor/widget_js_markdown_pattern">
</tb-markdown-editor> </tb-markdown-editor>
<mat-slide-toggle class="mat-slide" formControlName="applyDefaultMarkdownStyle">
{{ 'widgets.markdown.apply-default-markdown-style' | translate }}
</mat-slide-toggle>
<tb-css formControlName="markdownCss" <tb-css formControlName="markdownCss"
label="{{ 'widgets.markdown.markdown-css' | translate }}"> label="{{ 'widgets.markdown.markdown-css' | translate }}">
</tb-css> </tb-css>

2
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, useMarkdownTextFunction: false,
markdownTextPattern: '# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.', markdownTextPattern: '# Markdown/HTML card \\n - **Current entity**: **${entityName}**. \\n - **Current value**: **${Random}**.',
markdownTextFunction: 'return \'# Some title\\\\n - Entity name: \' + data[0][\'entityName\'];', markdownTextFunction: 'return \'# Some title\\\\n - Entity name: \' + data[0][\'entityName\'];',
applyDefaultMarkdownStyle: true,
markdownCss: '' markdownCss: ''
}; };
} }
@ -52,6 +53,7 @@ export class MarkdownWidgetSettingsComponent extends WidgetSettingsComponent {
useMarkdownTextFunction: [settings.useMarkdownTextFunction, []], useMarkdownTextFunction: [settings.useMarkdownTextFunction, []],
markdownTextPattern: [settings.markdownTextPattern, []], markdownTextPattern: [settings.markdownTextPattern, []],
markdownTextFunction: [settings.markdownTextFunction, []], markdownTextFunction: [settings.markdownTextFunction, []],
applyDefaultMarkdownStyle: [settings.applyDefaultMarkdownStyle, []],
markdownCss: [settings.markdownCss, []] markdownCss: [settings.markdownCss, []]
}); });
} }

44
ui-ngx/src/app/shared/components/markdown.component.scss

@ -357,6 +357,50 @@
background: 0 0; background: 0 0;
padding: 8px 16px; padding: 8px 16px;
color: #212529; 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;
}
}
}
}
} }
} }
} }

56
ui-ngx/src/app/shared/components/markdown.component.ts

@ -26,6 +26,7 @@ import {
Input, Input,
OnChanges, OnChanges,
Output, Output,
Renderer2,
SimpleChanges, SimpleChanges,
Type, Type,
ViewChild, ViewChild,
@ -36,9 +37,11 @@ import { MarkdownService, PrismPlugin } from 'ngx-markdown';
import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service'; import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { SHARED_MODULE_TOKEN } from '@shared/components/tokens'; 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'; import { Observable, of, ReplaySubject } from 'rxjs';
let defaultMarkdownStyle;
@Component({ @Component({
selector: 'tb-markdown', selector: 'tb-markdown',
templateUrl: './markdown.component.html', templateUrl: './markdown.component.html',
@ -61,6 +64,8 @@ export class TbMarkdownComponent implements OnChanges {
@Input() style: { [klass: string]: any } = {}; @Input() style: { [klass: string]: any } = {};
@Input() applyDefaultMarkdownStyle = true;
@Input() additionalStyles: string[]; @Input() additionalStyles: string[];
@Input() @Input()
@ -87,7 +92,8 @@ export class TbMarkdownComponent implements OnChanges {
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
public markdownService: MarkdownService, public markdownService: MarkdownService,
@Inject(SHARED_MODULE_TOKEN) private sharedModule: Type<any>, @Inject(SHARED_MODULE_TOKEN) private sharedModule: Type<any>,
private dynamicComponentFactoryService: DynamicComponentFactoryService) {} private dynamicComponentFactoryService: DynamicComponentFactoryService,
private renderer: Renderer2) {}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (isDefinedAndNotNull(this.data)) { if (isDefinedAndNotNull(this.data)) {
@ -97,15 +103,25 @@ export class TbMarkdownComponent implements OnChanges {
private render(markdown: string) { private render(markdown: string) {
const compiled = this.markdownService.parse(markdown, { decodeHtml: false }); const compiled = this.markdownService.parse(markdown, { decodeHtml: false });
let template = this.sanitizeCurlyBraces(compiled);
let markdownClass = 'tb-markdown-view'; let markdownClass = 'tb-markdown-view';
if (this.markdownClass) { if (this.markdownClass) {
markdownClass += ` ${this.markdownClass}`; markdownClass += ` ${this.markdownClass}`;
} }
template = `<div [ngStyle]="style" class="${markdownClass}">${template}</div>`; let template = `<div [ngStyle]="style" class="${markdownClass}">${compiled}</div>`;
if (this.containerClass) { if (this.containerClass) {
template = `<div class="${this.containerClass}" style="width: 100%; height: 100%;">${template}</div>`; template = `<div class="${this.containerClass}" style="width: 100%; height: 100%;">${template}</div>`;
} }
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[\S\s]+?(?=<\/pre>)<\/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(); this.markdownContainer.clear();
const parent = this; const parent = this;
let readyObservable: Observable<void>; let readyObservable: Observable<void>;
@ -113,8 +129,14 @@ export class TbMarkdownComponent implements OnChanges {
if (this.additionalCompileModules) { if (this.additionalCompileModules) {
compileModules = compileModules.concat(this.additionalCompileModules); compileModules = compileModules.concat(this.additionalCompileModules);
} }
let styles: string[] = deepClone(TbMarkdownComponent['ɵcmp'].styles); let styles: string[] = [];
styles[0] = styles[0].replace(/\[_nghost\-%COMP%\]/g, '').replace(/\[_ngcontent\-%COMP%\]/g, ''); 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) { if (this.additionalStyles) {
styles = styles.concat(this.additionalStyles); styles = styles.concat(this.additionalStyles);
} }
@ -139,20 +161,18 @@ export class TbMarkdownComponent implements OnChanges {
} }
} }
this.tbMarkdownInstanceComponentRef.instance.style = this.style; 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); readyObservable = this.handleImages(this.tbMarkdownInstanceComponentRef.location.nativeElement);
this.cd.detectChanges(); this.cd.detectChanges();
this.error = null; this.error = null;
} catch (error) { } catch (error) {
readyObservable = this.handleError(compiled, error); readyObservable = this.handleError(template, error, styles);
} }
readyObservable.subscribe(() => { readyObservable.subscribe(() => {
this.ready.emit(); this.ready.emit();
}); });
}, },
(error) => { (error) => {
readyObservable = this.handleError(compiled, error); readyObservable = this.handleError(template, error, styles);
this.cd.detectChanges(); this.cd.detectChanges();
readyObservable.subscribe(() => { readyObservable.subscribe(() => {
this.ready.emit(); this.ready.emit();
@ -160,14 +180,24 @@ export class TbMarkdownComponent implements OnChanges {
}); });
} }
private handleError(template: string, error): Observable<void> { private handleError(template: string, error, styles?: string[]): Observable<void> {
this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>'); this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>');
this.markdownContainer.clear(); this.markdownContainer.clear();
if (this.fallbackToPlainMarkdownValue) { if (this.fallbackToPlainMarkdownValue) {
const element = this.fallbackElement.nativeElement; 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; element.innerHTML = template;
this.handlePlugins(element); if (styleElement) {
this.markdownService.highlight(element); this.renderer.appendChild(element, styleElement);
}
return this.handleImages(element); return this.handleImages(element);
} else { } else {
return of(null); return of(null);

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -4678,6 +4678,7 @@
"use-markdown-text-function": "Use markdown/HTML value function", "use-markdown-text-function": "Use markdown/HTML value function",
"markdown-text-function": "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.')", "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" "markdown-css": "Markdown/HTML CSS"
}, },
"simple-card": { "simple-card": {

49
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
.tooltipster-sidetip.tooltipster-tb { .tooltipster-sidetip.tooltipster-tb {

Loading…
Cancel
Save