ngx-markdown
Version:
Angular library that uses marked to parse markdown to html combined with Prism.js for synthax highlights
799 lines (783 loc) • 35.2 kB
JavaScript
import { AsyncPipe, isPlatformBrowser, CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Component, ChangeDetectionStrategy, InjectionToken, Pipe, PLATFORM_ID, Injectable, Inject, Optional, EventEmitter, Input, Output, SecurityContext, NgModule } from '@angular/core';
import { Subject, merge, of, timer } from 'rxjs';
import { switchMap, mapTo, distinctUntilChanged, shareReplay, startWith, map, takeUntil, first } from 'rxjs/operators';
import { Renderer, marked } from 'marked';
export { Renderer as MarkedRenderer } from 'marked';
import * as i1 from '@angular/common/http';
import * as i2 from '@angular/platform-browser';
const BUTTON_TEXT_COPY = 'Copy';
const BUTTON_TEXT_COPIED = 'Copied';
class ClipboardButtonComponent {
constructor() {
this._buttonClick$ = new Subject();
this.copied$ = this._buttonClick$.pipe(switchMap(() => merge(of(true), timer(3000).pipe(mapTo(false)))), distinctUntilChanged(), shareReplay(1));
this.copiedText$ = this.copied$.pipe(startWith(false), map(copied => copied
? BUTTON_TEXT_COPIED
: BUTTON_TEXT_COPY));
}
onCopyToClipboardClick() {
this._buttonClick$.next();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: ClipboardButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.0", type: ClipboardButtonComponent, isStandalone: true, selector: "markdown-clipboard", ngImport: i0, template: `
<button
class="markdown-clipboard-button"
[class.copied]="copied$ | async"
(click)="onCopyToClipboardClick()"
>{{ copiedText$ | async }}</button>
`, isInline: true, dependencies: [{ kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: ClipboardButtonComponent, decorators: [{
type: Component,
args: [{
selector: 'markdown-clipboard',
template: `
<button
class="markdown-clipboard-button"
[class.copied]="copied$ | async"
(click)="onCopyToClipboardClick()"
>{{ copiedText$ | async }}</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [AsyncPipe],
}]
}] });
const CLIPBOARD_OPTIONS = new InjectionToken('CLIPBOARD_OPTIONS');
/* eslint-disable */
class KatexSpecificOptions {
}
class LanguagePipe {
transform(value, language) {
if (value == null) {
value = '';
}
if (language == null) {
language = '';
}
if (typeof value !== 'string') {
console.error(`LanguagePipe has been invoked with an invalid value type [${typeof value}]`);
return value;
}
if (typeof language !== 'string') {
console.error(`LanguagePipe has been invoked with an invalid parameter [${typeof language}]`);
return value;
}
return '```' + language + '\n' + value + '\n```';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: LanguagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: LanguagePipe, isStandalone: true, name: "language" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: LanguagePipe, decorators: [{
type: Pipe,
args: [{
name: 'language',
}]
}] });
var PrismPlugin;
(function (PrismPlugin) {
PrismPlugin["CommandLine"] = "command-line";
PrismPlugin["LineHighlight"] = "line-highlight";
PrismPlugin["LineNumbers"] = "line-numbers";
})(PrismPlugin || (PrismPlugin = {}));
const MARKED_EXTENSIONS = new InjectionToken('MARKED_EXTENSIONS');
const MARKED_OPTIONS = new InjectionToken('MARKED_OPTIONS');
const MERMAID_OPTIONS = new InjectionToken('MERMAID_OPTIONS');
const errorJoyPixelsNotLoaded = '[ngx-markdown] When using the `emoji` attribute you *have to* include Emoji-Toolkit files to `angular.json` or use imports. See README for more information';
const errorKatexNotLoaded = '[ngx-markdown] When using the `katex` attribute you *have to* include KaTeX files to `angular.json` or use imports. See README for more information';
const errorMermaidNotLoaded = '[ngx-markdown] When using the `mermaid` attribute you *have to* include Mermaid files to `angular.json` or use imports. See README for more information';
const errorClipboardNotLoaded = '[ngx-markdown] When using the `clipboard` attribute you *have to* include Clipboard files to `angular.json` or use imports. See README for more information';
const errorClipboardViewContainerRequired = '[ngx-markdown] When using the `clipboard` attribute you *have to* provide the `viewContainerRef` parameter to `MarkdownService.render()` function';
const errorSrcWithoutHttpClient = '[ngx-markdown] When using the `src` attribute you *have to* pass the `HttpClient` as a parameter of the `forRoot` method. See README for more information';
const SECURITY_CONTEXT = new InjectionToken('SECURITY_CONTEXT');
class ExtendedRenderer extends Renderer {
constructor() {
super(...arguments);
this.ɵNgxMarkdownRendererExtendedForExtensions = false;
this.ɵNgxMarkdownRendererExtendedForMermaid = false;
}
}
class MarkdownService {
get options() { return this._options; }
set options(value) {
this._options = { ...this.DEFAULT_MARKED_OPTIONS, ...value };
}
get renderer() { return this.options.renderer; }
set renderer(value) {
this.options.renderer = value;
}
constructor(clipboardOptions, extensions, options, mermaidOptions, platform, securityContext, http, sanitizer) {
this.clipboardOptions = clipboardOptions;
this.extensions = extensions;
this.mermaidOptions = mermaidOptions;
this.platform = platform;
this.securityContext = securityContext;
this.http = http;
this.sanitizer = sanitizer;
this.DEFAULT_MARKED_OPTIONS = {
renderer: new Renderer(),
};
this.DEFAULT_KATEX_OPTIONS = {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\begin{equation}', right: '\\end{equation}', display: true },
{ left: '\\begin{align}', right: '\\end{align}', display: true },
{ left: '\\begin{alignat}', right: '\\end{alignat}', display: true },
{ left: '\\begin{gather}', right: '\\end{gather}', display: true },
{ left: '\\begin{CD}', right: '\\end{CD}', display: true },
{ left: '\\[', right: '\\]', display: true },
],
};
this.DEFAULT_MERMAID_OPTIONS = {
startOnLoad: false,
};
this.DEFAULT_CLIPBOARD_OPTIONS = {
buttonComponent: undefined,
};
this.DEFAULT_PARSE_OPTIONS = {
decodeHtml: false,
inline: false,
emoji: false,
mermaid: false,
markedOptions: undefined,
disableSanitizer: false,
};
this.DEFAULT_RENDER_OPTIONS = {
clipboard: false,
clipboardOptions: undefined,
katex: false,
katexOptions: undefined,
mermaid: false,
mermaidOptions: undefined,
};
this._reload$ = new Subject();
this.reload$ = this._reload$.asObservable();
this.options = options;
}
parse(markdown, parseOptions = this.DEFAULT_PARSE_OPTIONS) {
const { decodeHtml, inline, emoji, mermaid, disableSanitizer, } = parseOptions;
const markedOptions = {
...this.options,
...parseOptions.markedOptions,
};
const renderer = markedOptions.renderer || this.renderer || new Renderer();
if (this.extensions) {
this.renderer = this.extendsRendererForExtensions(renderer);
}
if (mermaid) {
this.renderer = this.extendsRendererForMermaid(renderer);
}
const trimmed = this.trimIndentation(markdown);
const decoded = decodeHtml ? this.decodeHtml(trimmed) : trimmed;
const emojified = emoji ? this.parseEmoji(decoded) : decoded;
const marked = this.parseMarked(emojified, markedOptions, inline);
const sanitized = disableSanitizer ? marked : this.sanitizer.sanitize(this.securityContext, marked);
return sanitized || '';
}
render(element, options = this.DEFAULT_RENDER_OPTIONS, viewContainerRef) {
const { clipboard, clipboardOptions, katex, katexOptions, mermaid, mermaidOptions, } = options;
if (katex) {
this.renderKatex(element, {
...this.DEFAULT_KATEX_OPTIONS,
...katexOptions,
});
}
if (mermaid) {
this.renderMermaid(element, {
...this.DEFAULT_MERMAID_OPTIONS,
...this.mermaidOptions,
...mermaidOptions,
});
}
if (clipboard) {
this.renderClipboard(element, viewContainerRef, {
...this.DEFAULT_CLIPBOARD_OPTIONS,
...this.clipboardOptions,
...clipboardOptions,
});
}
this.highlight(element);
}
reload() {
this._reload$.next();
}
getSource(src) {
if (!this.http) {
throw new Error(errorSrcWithoutHttpClient);
}
return this.http
.get(src, { responseType: 'text' })
.pipe(map(markdown => this.handleExtension(src, markdown)));
}
highlight(element) {
if (!isPlatformBrowser(this.platform)) {
return;
}
if (typeof Prism === 'undefined' || typeof Prism.highlightAllUnder === 'undefined') {
return;
}
if (!element) {
element = document;
}
const noLanguageElements = element.querySelectorAll('pre code:not([class*="language-"])');
Array.prototype.forEach.call(noLanguageElements, (x) => x.classList.add('language-none'));
Prism.highlightAllUnder(element);
}
decodeHtml(html) {
if (!isPlatformBrowser(this.platform)) {
return html;
}
const textarea = document.createElement('textarea');
textarea.innerHTML = html;
return textarea.value;
}
extendsRendererForExtensions(renderer) {
const extendedRenderer = renderer;
if (extendedRenderer.ɵNgxMarkdownRendererExtendedForExtensions === true) {
return renderer;
}
if (this.extensions?.length > 0) {
marked.use(...this.extensions);
}
extendedRenderer.ɵNgxMarkdownRendererExtendedForExtensions = true;
return renderer;
}
extendsRendererForMermaid(renderer) {
const extendedRenderer = renderer;
if (extendedRenderer.ɵNgxMarkdownRendererExtendedForMermaid === true) {
return renderer;
}
const defaultCode = renderer.code;
renderer.code = (token) => {
return token.lang === 'mermaid'
? `<div class="mermaid">${token.text}</div>`
: defaultCode(token);
};
extendedRenderer.ɵNgxMarkdownRendererExtendedForMermaid = true;
return renderer;
}
handleExtension(src, markdown) {
const urlProtocolIndex = src.lastIndexOf('://');
const urlWithoutProtocol = urlProtocolIndex > -1
? src.substring(urlProtocolIndex + 4)
: src;
const lastSlashIndex = urlWithoutProtocol.lastIndexOf('/');
const lastUrlSegment = lastSlashIndex > -1
? urlWithoutProtocol.substring(lastSlashIndex + 1).split('?')[0]
: '';
const lastDotIndex = lastUrlSegment.lastIndexOf('.');
const extension = lastDotIndex > -1
? lastUrlSegment.substring(lastDotIndex + 1)
: '';
return !!extension && extension !== 'md'
? '```' + extension + '\n' + markdown + '\n```'
: markdown;
}
parseMarked(html, markedOptions, inline = false) {
if (markedOptions.renderer) {
// clone renderer and remove extended flags otherwise
// marked throws an error thinking it is a renderer prop
const renderer = { ...markedOptions.renderer };
delete renderer.ɵNgxMarkdownRendererExtendedForExtensions;
delete renderer.ɵNgxMarkdownRendererExtendedForMermaid;
// remove renderer from markedOptions because if renderer is
// passed to marked.parse method, it will ignore all extensions
delete markedOptions.renderer;
marked.use({ renderer });
}
return inline
? marked.parseInline(html, markedOptions)
: marked.parse(html, markedOptions);
}
parseEmoji(html) {
if (!isPlatformBrowser(this.platform)) {
return html;
}
if (typeof joypixels === 'undefined' || typeof joypixels.shortnameToUnicode === 'undefined') {
throw new Error(errorJoyPixelsNotLoaded);
}
return joypixels.shortnameToUnicode(html);
}
renderKatex(element, options) {
if (!isPlatformBrowser(this.platform)) {
return;
}
if (typeof katex === 'undefined' || typeof renderMathInElement === 'undefined') {
throw new Error(errorKatexNotLoaded);
}
renderMathInElement(element, options);
}
renderClipboard(element, viewContainerRef, options) {
if (!isPlatformBrowser(this.platform)) {
return;
}
if (typeof ClipboardJS === 'undefined') {
throw new Error(errorClipboardNotLoaded);
}
if (!viewContainerRef) {
throw new Error(errorClipboardViewContainerRequired);
}
const { buttonComponent, buttonTemplate, } = options;
// target every <pre> elements
const preElements = element.querySelectorAll('pre');
for (let i = 0; i < preElements.length; i++) {
const preElement = preElements.item(i);
// create <pre> wrapper element
const preWrapperElement = document.createElement('div');
preWrapperElement.style.position = 'relative';
preElement.parentNode.insertBefore(preWrapperElement, preElement);
preWrapperElement.appendChild(preElement);
// create toolbar element
const toolbarWrapperElement = document.createElement('div');
toolbarWrapperElement.classList.add('markdown-clipboard-toolbar');
toolbarWrapperElement.style.position = 'absolute';
toolbarWrapperElement.style.top = '.5em';
toolbarWrapperElement.style.right = '.5em';
toolbarWrapperElement.style.zIndex = '1';
preWrapperElement.insertAdjacentElement('beforeend', toolbarWrapperElement);
// register listener to show/hide toolbar
preWrapperElement.onmouseenter = () => toolbarWrapperElement.classList.add('hover');
preWrapperElement.onmouseleave = () => toolbarWrapperElement.classList.remove('hover');
// declare embeddedViewRef holding variable
let embeddedViewRef;
// use provided component via input property
// or provided via ClipboardOptions provider
if (buttonComponent) {
const componentRef = viewContainerRef.createComponent(buttonComponent);
embeddedViewRef = componentRef.hostView;
componentRef.changeDetectorRef.markForCheck();
}
// use provided template via input property
else if (buttonTemplate) {
embeddedViewRef = viewContainerRef.createEmbeddedView(buttonTemplate);
}
// use default component
else {
const componentRef = viewContainerRef.createComponent(ClipboardButtonComponent);
embeddedViewRef = componentRef.hostView;
componentRef.changeDetectorRef.markForCheck();
}
// declare clipboard instance variable
let clipboardInstance;
// attach clipboard.js to root node
embeddedViewRef.rootNodes.forEach((node) => {
toolbarWrapperElement.appendChild(node);
clipboardInstance = new ClipboardJS(node, { text: () => preElement.innerText });
});
// destroy clipboard instance when view is destroyed
embeddedViewRef.onDestroy(() => clipboardInstance.destroy());
}
}
renderMermaid(element, options = this.DEFAULT_MERMAID_OPTIONS) {
if (!isPlatformBrowser(this.platform)) {
return;
}
if (typeof mermaid === 'undefined' || typeof mermaid.initialize === 'undefined') {
throw new Error(errorMermaidNotLoaded);
}
const mermaidElements = element.querySelectorAll('.mermaid');
if (mermaidElements.length === 0) {
return;
}
mermaid.initialize(options);
mermaid.run({ nodes: mermaidElements });
}
trimIndentation(markdown) {
if (!markdown) {
return '';
}
let indentStart;
return markdown
.split('\n')
.map(line => {
let lineIdentStart = indentStart;
if (line.length > 0) {
lineIdentStart = isNaN(lineIdentStart)
? line.search(/\S|$/)
: Math.min(line.search(/\S|$/), lineIdentStart);
}
if (isNaN(indentStart)) {
indentStart = lineIdentStart;
}
return lineIdentStart
? line.substring(lineIdentStart)
: line;
}).join('\n');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownService, deps: [{ token: CLIPBOARD_OPTIONS, optional: true }, { token: MARKED_EXTENSIONS, optional: true }, { token: MARKED_OPTIONS, optional: true }, { token: MERMAID_OPTIONS, optional: true }, { token: PLATFORM_ID }, { token: SECURITY_CONTEXT }, { token: i1.HttpClient, optional: true }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Inject,
args: [CLIPBOARD_OPTIONS]
}, {
type: Optional
}] }, { type: undefined, decorators: [{
type: Inject,
args: [MARKED_EXTENSIONS]
}, {
type: Optional
}] }, { type: undefined, decorators: [{
type: Inject,
args: [MARKED_OPTIONS]
}, {
type: Optional
}] }, { type: undefined, decorators: [{
type: Inject,
args: [MERMAID_OPTIONS]
}, {
type: Optional
}] }, { type: Object, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }, { type: i0.SecurityContext, decorators: [{
type: Inject,
args: [SECURITY_CONTEXT]
}] }, { type: i1.HttpClient, decorators: [{
type: Optional
}] }, { type: i2.DomSanitizer }] });
class MarkdownComponent {
get disableSanitizer() { return this._disableSanitizer; }
set disableSanitizer(value) { this._disableSanitizer = this.coerceBooleanProperty(value); }
get inline() { return this._inline; }
set inline(value) { this._inline = this.coerceBooleanProperty(value); }
// Plugin - clipboard
get clipboard() { return this._clipboard; }
set clipboard(value) { this._clipboard = this.coerceBooleanProperty(value); }
// Plugin - emoji
get emoji() { return this._emoji; }
set emoji(value) { this._emoji = this.coerceBooleanProperty(value); }
// Plugin - katex
get katex() { return this._katex; }
set katex(value) { this._katex = this.coerceBooleanProperty(value); }
// Plugin - mermaid
get mermaid() { return this._mermaid; }
set mermaid(value) { this._mermaid = this.coerceBooleanProperty(value); }
// Plugin - lineHighlight
get lineHighlight() { return this._lineHighlight; }
set lineHighlight(value) { this._lineHighlight = this.coerceBooleanProperty(value); }
// Plugin - lineNumbers
get lineNumbers() { return this._lineNumbers; }
set lineNumbers(value) { this._lineNumbers = this.coerceBooleanProperty(value); }
// Plugin - commandLine
get commandLine() { return this._commandLine; }
set commandLine(value) { this._commandLine = this.coerceBooleanProperty(value); }
constructor(element, markdownService, viewContainerRef) {
this.element = element;
this.markdownService = markdownService;
this.viewContainerRef = viewContainerRef;
// Event emitters
this.error = new EventEmitter();
this.load = new EventEmitter();
this.ready = new EventEmitter();
this._clipboard = false;
this._commandLine = false;
this._disableSanitizer = false;
this._emoji = false;
this._inline = false;
this._katex = false;
this._lineHighlight = false;
this._lineNumbers = false;
this._mermaid = false;
this.destroyed$ = new Subject();
}
ngOnChanges() {
this.loadContent();
}
loadContent() {
if (this.data != null) {
this.handleData();
return;
}
if (this.src != null) {
this.handleSrc();
return;
}
}
ngAfterViewInit() {
if (!this.data && !this.src) {
this.handleTransclusion();
}
this.markdownService.reload$
.pipe(takeUntil(this.destroyed$))
.subscribe(() => this.loadContent());
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
async render(markdown, decodeHtml = false) {
const parsedOptions = {
decodeHtml,
inline: this.inline,
emoji: this.emoji,
mermaid: this.mermaid,
disableSanitizer: this.disableSanitizer,
};
const renderOptions = {
clipboard: this.clipboard,
clipboardOptions: this.getClipboardOptions(),
katex: this.katex,
katexOptions: this.katexOptions,
mermaid: this.mermaid,
mermaidOptions: this.mermaidOptions,
};
const parsed = await this.markdownService.parse(markdown, parsedOptions);
this.element.nativeElement.innerHTML = parsed;
this.handlePlugins();
this.markdownService.render(this.element.nativeElement, renderOptions, this.viewContainerRef);
this.ready.emit();
}
coerceBooleanProperty(value) {
return value != null && `${String(value)}` !== 'false';
}
getClipboardOptions() {
if (this.clipboardButtonComponent || this.clipboardButtonTemplate) {
return {
buttonComponent: this.clipboardButtonComponent,
buttonTemplate: this.clipboardButtonTemplate,
};
}
return undefined;
}
handleData() {
this.render(this.data);
}
handleSrc() {
this.markdownService
.getSource(this.src)
.subscribe({
next: markdown => {
this.render(markdown).then(() => {
this.load.emit(markdown);
});
},
error: (error) => this.error.emit(error),
});
}
handleTransclusion() {
this.render(this.element.nativeElement.innerHTML, true);
}
handlePlugins() {
if (this.commandLine) {
this.setPluginClass(this.element.nativeElement, PrismPlugin.CommandLine);
this.setPluginOptions(this.element.nativeElement, {
dataFilterOutput: this.filterOutput,
dataHost: this.host,
dataPrompt: this.prompt,
dataOutput: this.output,
dataUser: this.user,
});
}
if (this.lineHighlight) {
this.setPluginOptions(this.element.nativeElement, { dataLine: this.line, dataLineOffset: this.lineOffset });
}
if (this.lineNumbers) {
this.setPluginClass(this.element.nativeElement, PrismPlugin.LineNumbers);
this.setPluginOptions(this.element.nativeElement, { dataStart: this.start });
}
}
setPluginClass(element, plugin) {
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);
}
}
setPluginOptions(element, options) {
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());
}
});
}
}
toLispCase(value) {
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;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownComponent, deps: [{ token: i0.ElementRef }, { token: MarkdownService }, { token: i0.ViewContainerRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.0", type: MarkdownComponent, isStandalone: true, selector: "markdown, [markdown]", inputs: { data: "data", src: "src", disableSanitizer: "disableSanitizer", inline: "inline", clipboard: "clipboard", clipboardButtonComponent: "clipboardButtonComponent", clipboardButtonTemplate: "clipboardButtonTemplate", emoji: "emoji", katex: "katex", katexOptions: "katexOptions", mermaid: "mermaid", mermaidOptions: "mermaidOptions", lineHighlight: "lineHighlight", line: "line", lineOffset: "lineOffset", lineNumbers: "lineNumbers", start: "start", commandLine: "commandLine", filterOutput: "filterOutput", host: "host", prompt: "prompt", output: "output", user: "user" }, outputs: { error: "error", load: "load", ready: "ready" }, usesOnChanges: true, ngImport: i0, template: '<ng-content></ng-content>', isInline: true }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownComponent, decorators: [{
type: Component,
args: [{
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'markdown, [markdown]',
template: '<ng-content></ng-content>',
}]
}], ctorParameters: () => [{ type: i0.ElementRef }, { type: MarkdownService }, { type: i0.ViewContainerRef }], propDecorators: { data: [{
type: Input
}], src: [{
type: Input
}], disableSanitizer: [{
type: Input
}], inline: [{
type: Input
}], clipboard: [{
type: Input
}], clipboardButtonComponent: [{
type: Input
}], clipboardButtonTemplate: [{
type: Input
}], emoji: [{
type: Input
}], katex: [{
type: Input
}], katexOptions: [{
type: Input
}], mermaid: [{
type: Input
}], mermaidOptions: [{
type: Input
}], lineHighlight: [{
type: Input
}], line: [{
type: Input
}], lineOffset: [{
type: Input
}], lineNumbers: [{
type: Input
}], start: [{
type: Input
}], commandLine: [{
type: Input
}], filterOutput: [{
type: Input
}], host: [{
type: Input
}], prompt: [{
type: Input
}], output: [{
type: Input
}], user: [{
type: Input
}], error: [{
type: Output
}], load: [{
type: Output
}], ready: [{
type: Output
}] } });
class MarkdownPipe {
constructor(domSanitizer, elementRef, markdownService, viewContainerRef, zone) {
this.domSanitizer = domSanitizer;
this.elementRef = elementRef;
this.markdownService = markdownService;
this.viewContainerRef = viewContainerRef;
this.zone = zone;
}
async transform(value, options) {
if (value == null) {
return '';
}
if (typeof value !== 'string') {
console.error(`MarkdownPipe has been invoked with an invalid value type [${typeof value}]`);
return value;
}
const markdown = await this.markdownService.parse(value, options);
this.zone.onStable
.pipe(first())
.subscribe(() => this.markdownService.render(this.elementRef.nativeElement, options, this.viewContainerRef));
return this.domSanitizer.bypassSecurityTrustHtml(markdown);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownPipe, deps: [{ token: i2.DomSanitizer }, { token: i0.ElementRef }, { token: MarkdownService }, { token: i0.ViewContainerRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: MarkdownPipe, isStandalone: true, name: "markdown" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownPipe, decorators: [{
type: Pipe,
args: [{
name: 'markdown',
}]
}], ctorParameters: () => [{ type: i2.DomSanitizer }, { type: i0.ElementRef }, { type: MarkdownService }, { type: i0.ViewContainerRef }, { type: i0.NgZone }] });
function provideMarkdown(markdownModuleConfig) {
return [
MarkdownService,
markdownModuleConfig?.loader ?? [],
markdownModuleConfig?.clipboardOptions ?? [],
markdownModuleConfig?.markedOptions ?? [],
markdownModuleConfig?.mermaidOptions ?? [],
getMarkedExtensionProvider(markdownModuleConfig?.markedExtensions) ?? [],
{
provide: SECURITY_CONTEXT,
useValue: markdownModuleConfig?.sanitize ?? SecurityContext.HTML,
},
];
}
;
;
function isTypedProvider(provider) {
return provider != null && provider.provide != null;
}
function getMarkedExtensionProvider(markedExtensions) {
if (!markedExtensions) {
return undefined;
}
return markedExtensions.reduce((acc, markedExtension) => {
const provider = isTypedProvider(markedExtension)
? { ...markedExtension, multi: true }
: { provide: MARKED_EXTENSIONS, useValue: markedExtension, multi: true };
return [...acc, provider];
}, []);
}
const sharedDeclarations = [
ClipboardButtonComponent,
LanguagePipe,
MarkdownComponent,
MarkdownPipe,
];
class MarkdownModule {
static forRoot(markdownModuleConfig) {
return {
ngModule: MarkdownModule,
providers: [
provideMarkdown(markdownModuleConfig),
],
};
}
static forChild() {
return {
ngModule: MarkdownModule,
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.0.0", ngImport: i0, type: MarkdownModule, imports: [CommonModule, ClipboardButtonComponent,
LanguagePipe,
MarkdownComponent,
MarkdownPipe], exports: [ClipboardButtonComponent,
LanguagePipe,
MarkdownComponent,
MarkdownPipe] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownModule, imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.0", ngImport: i0, type: MarkdownModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, ...sharedDeclarations],
exports: sharedDeclarations,
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { CLIPBOARD_OPTIONS, ClipboardButtonComponent, ExtendedRenderer, KatexSpecificOptions, LanguagePipe, MARKED_EXTENSIONS, MARKED_OPTIONS, MERMAID_OPTIONS, MarkdownComponent, MarkdownModule, MarkdownPipe, MarkdownService, PrismPlugin, SECURITY_CONTEXT, errorClipboardNotLoaded, errorClipboardViewContainerRequired, errorJoyPixelsNotLoaded, errorKatexNotLoaded, errorMermaidNotLoaded, errorSrcWithoutHttpClient, getMarkedExtensionProvider, isTypedProvider, provideMarkdown };
//# sourceMappingURL=ngx-markdown.mjs.map