|
|
|
@ -32,16 +32,14 @@ import { |
|
|
|
ViewChild, |
|
|
|
ViewContainerRef |
|
|
|
} from '@angular/core'; |
|
|
|
import { HelpService } from '@core/services/help.service'; |
|
|
|
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, guid, isDefinedAndNotNull } from '@core/utils'; |
|
|
|
import { guid, isDefinedAndNotNull } from '@core/utils'; |
|
|
|
import { Observable, of, ReplaySubject } from 'rxjs'; |
|
|
|
import { coerceBoolean } from '@shared/decorators/coercion'; |
|
|
|
|
|
|
|
let defaultMarkdownStyle; |
|
|
|
let defaultMarkdownStyle: string; |
|
|
|
|
|
|
|
@Component({ |
|
|
|
selector: 'tb-markdown', |
|
|
|
@ -70,12 +68,12 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
@Input() additionalStyles: string[]; |
|
|
|
|
|
|
|
@Input() |
|
|
|
get lineNumbers(): boolean { return this.lineNumbersValue; } |
|
|
|
set lineNumbers(value: boolean) { this.lineNumbersValue = coerceBooleanProperty(value); } |
|
|
|
@coerceBoolean() |
|
|
|
lineNumbers = false; |
|
|
|
|
|
|
|
@Input() |
|
|
|
get fallbackToPlainMarkdown(): boolean { return this.fallbackToPlainMarkdownValue; } |
|
|
|
set fallbackToPlainMarkdown(value: boolean) { this.fallbackToPlainMarkdownValue = coerceBooleanProperty(value); } |
|
|
|
@coerceBoolean() |
|
|
|
fallbackToPlainMarkdown = false; |
|
|
|
|
|
|
|
@Input() |
|
|
|
@coerceBoolean() |
|
|
|
@ -83,9 +81,6 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
|
|
|
|
@Output() ready = new EventEmitter<void>(); |
|
|
|
|
|
|
|
private lineNumbersValue = false; |
|
|
|
private fallbackToPlainMarkdownValue = false; |
|
|
|
|
|
|
|
isMarkdownReady = false; |
|
|
|
|
|
|
|
error = null; |
|
|
|
@ -93,8 +88,7 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
private tbMarkdownInstanceComponentRef: ComponentRef<any>; |
|
|
|
private tbMarkdownInstanceComponentType: Type<any>; |
|
|
|
|
|
|
|
constructor(private help: HelpService, |
|
|
|
private cd: ChangeDetectorRef, |
|
|
|
constructor(private cd: ChangeDetectorRef, |
|
|
|
private zone: NgZone, |
|
|
|
public markdownService: MarkdownService, |
|
|
|
@Inject(SHARED_MODULE_TOKEN) private sharedModule: Type<any>, |
|
|
|
@ -102,8 +96,19 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
private renderer: Renderer2) {} |
|
|
|
|
|
|
|
ngOnChanges(changes: SimpleChanges): void { |
|
|
|
if (isDefinedAndNotNull(this.data)) { |
|
|
|
this.zone.run(() => this.render(this.data)); |
|
|
|
for (const propName of Object.keys(changes)) { |
|
|
|
const change = changes[propName]; |
|
|
|
if (propName === 'data' && change.currentValue !== change.previousValue) { |
|
|
|
if (isDefinedAndNotNull(this.data)) { |
|
|
|
this.zone.run(() => this.render(this.data)); |
|
|
|
} |
|
|
|
} else if (propName === 'context' && !change.firstChange) { |
|
|
|
if (this.context && this.tbMarkdownInstanceComponentRef) { |
|
|
|
for (const propName of Object.keys(this.context)) { |
|
|
|
this.tbMarkdownInstanceComponentRef.instance[propName] = this.context[propName]; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -134,8 +139,8 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
if (this.applyDefaultMarkdownStyle) { |
|
|
|
if (!defaultMarkdownStyle) { |
|
|
|
const compDef = this.dynamicComponentFactoryService.getComponentDef(TbMarkdownComponent); |
|
|
|
defaultMarkdownStyle = deepClone(compDef.styles[0]).replace(/\[_nghost\-%COMP%\]/g, '') |
|
|
|
.replace(/\[_ngcontent\-%COMP%\]/g, ''); |
|
|
|
defaultMarkdownStyle = compDef.styles[0].replace(/\[_nghost-%COMP%]/g, '') |
|
|
|
.replace(/\[_ngcontent-%COMP%]/g, ''); |
|
|
|
} |
|
|
|
styles.push(defaultMarkdownStyle); |
|
|
|
} |
|
|
|
@ -149,7 +154,7 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
this.ready.emit(); |
|
|
|
}); |
|
|
|
} else { |
|
|
|
const parent = this; |
|
|
|
const destroyMarkdownInstanceResources = this.destroyMarkdownInstanceResources.bind(this); |
|
|
|
let compileModules = [this.sharedModule]; |
|
|
|
if (this.additionalCompileModules) { |
|
|
|
compileModules = compileModules.concat(this.additionalCompileModules); |
|
|
|
@ -157,13 +162,14 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
this.dynamicComponentFactoryService.createDynamicComponent( |
|
|
|
class TbMarkdownInstance { |
|
|
|
ngOnDestroy(): void { |
|
|
|
parent.destroyMarkdownInstanceResources(); |
|
|
|
destroyMarkdownInstanceResources(); |
|
|
|
} |
|
|
|
}, |
|
|
|
template, |
|
|
|
compileModules, |
|
|
|
true, styles |
|
|
|
).subscribe((componentType) => { |
|
|
|
).subscribe({ |
|
|
|
next: (componentType) => { |
|
|
|
this.tbMarkdownInstanceComponentType = componentType; |
|
|
|
const injector: Injector = Injector.create({providers: [], parent: this.markdownContainer.injector}); |
|
|
|
try { |
|
|
|
@ -187,20 +193,21 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
this.ready.emit(); |
|
|
|
}); |
|
|
|
}, |
|
|
|
(error) => { |
|
|
|
error: (error) => { |
|
|
|
readyObservable = this.handleError(template, error, styles); |
|
|
|
this.cd.detectChanges(); |
|
|
|
readyObservable.subscribe(() => { |
|
|
|
this.ready.emit(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private handleError(template: string, error, styles?: string[]): Observable<void> { |
|
|
|
private handleError(template: string, error: any, styles?: string[]): Observable<void> { |
|
|
|
this.error = (error ? error + '' : 'Failed to render markdown!').replace(/\n/g, '<br>'); |
|
|
|
this.markdownContainer.clear(); |
|
|
|
if (this.fallbackToPlainMarkdownValue) { |
|
|
|
if (this.fallbackToPlainMarkdown) { |
|
|
|
return this.plainMarkdown(template, styles); |
|
|
|
} else { |
|
|
|
return of(null); |
|
|
|
@ -209,7 +216,7 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
|
|
|
|
private plainMarkdown(template: string, styles?: string[]): Observable<void> { |
|
|
|
const element = this.fallbackElement.nativeElement; |
|
|
|
let styleElement; |
|
|
|
let styleElement: any; |
|
|
|
if (styles?.length) { |
|
|
|
const markdownClass = 'tb-markdown-view-' + guid(); |
|
|
|
let innerStyle = styles.join('\n'); |
|
|
|
@ -244,7 +251,7 @@ export class TbMarkdownComponent implements OnChanges { |
|
|
|
if (imgs.length) { |
|
|
|
let totalImages = imgs.length; |
|
|
|
const imagesLoadedSubject = new ReplaySubject<void>(); |
|
|
|
imgs.each((index, img) => { |
|
|
|
imgs.each((_index, img) => { |
|
|
|
$(img).one('load error', () => { |
|
|
|
totalImages--; |
|
|
|
if (totalImages === 0) { |
|
|
|
|