@cisstech/nge
Version:
NG Essentials is a collection of libraries for Angular developers.
1,219 lines • 52.9 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, Injectable, Optional, Inject, inject, ElementRef, ChangeDetectorRef, EventEmitter, Component, ChangeDetectionStrategy, Input, HostBinding, Output, NgModule } from '@angular/core';
import * as i1 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i2 from '@angular/common';
import { HttpClient } from '@angular/common/http';
import * as i1$1 from '@cisstech/nge/services';
import { ResourceLoaderService } from '@cisstech/nge/services';
import { lastValueFrom, firstValueFrom } from 'rxjs';
import { marked } from 'marked';
/** Inject this token to get the list of contributions to nge-markdown api. */
const NGE_MARKDOWN_CONTRIBUTION = new InjectionToken('NGE_MARKDOWN_CONTRIBUTION');
const OPEN = /^:::(\+?)\s+(\w+)(\s+.+)?/;
const CLOSE = /^:::\s*$/;
/**
* Contribution to add collapsible styled block to markdown syntax.
*/
class NgeMarkdownAdmonitions {
contribute(transformer) {
this.addStyles();
this.autoFixAdmonitionsSyntax(transformer);
this.createAdmonitions(transformer);
}
addStyles() {
const head = document.head;
if (head.querySelector('[nge-markdown-admonitions]')) {
return;
}
const admonitions = {
note: {
bg: 'rgba(68, 138, 255, 0.1)',
border: '#448aff',
},
abstract: {
bg: 'rgba(0, 176, 255, 0.1)',
border: '#00b0ff',
},
info: {
bg: 'rgba(0, 184, 212, 0.1)',
border: '#00b8d4',
},
tip: {
bg: 'rgba(0, 191, 165, 0.1)',
border: '#00bfa5',
},
success: {
bg: 'rgba(0, 200, 83, 0.1)',
border: '#00c853',
},
question: {
bg: 'rgba(100, 221, 23, 0.1)',
border: '#64dd17',
},
warning: {
bg: 'rgba(255, 145, 0, 0.1)',
border: '#ff9100',
},
failure: {
bg: 'rgba(255, 82, 82, 0.1)',
border: '#ff5252',
},
danger: {
bg: 'rgba(255, 23, 68, 0.1)',
border: '#ff1744',
},
bug: {
bg: 'rgba(245, 0, 87, 0.1)',
border: '#f50057',
},
example: {
bg: 'rgba(101, 31, 255, 0.1)',
border: '#651fff',
},
quote: {
bg: 'rgba(158, 158, 158, 0.1)',
border: '#9e9e9e',
},
};
const stylesheet = [
`
/* CONTAINER */
.nge-md-admonition {
margin: 1.5625em 0;
overflow: hidden;
font-size: 0.84rem;
page-break-inside: avoid;
border-radius: 0.1rem;
box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.05rem rgba(0, 0, 0, 0.1);
--admonition--note: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20.71 7.04c.39-.39.39-1.04 0-1.41l-2.34-2.34c-.37-.39-1.02-.39-1.41 0l-1.84 1.83 3.75 3.75M3 17.25V21h3.75L17.81 9.93l-3.75-3.75L3 17.25z"/></svg>');
--admonition--abstract: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 5h16v2H4V5m0 4h16v2H4V9m0 4h16v2H4v-2m0 4h10v2H4v-2z"/></svg>');
--admonition--info: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10A10 10 0 0012 2z"/></svg>');
--admonition--tip: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M17.55 11.2c-.23-.3-.5-.56-.76-.82-.65-.6-1.4-1.03-2.03-1.66C13.3 7.26 13 4.85 13.91 3c-.91.23-1.75.75-2.45 1.32-2.54 2.08-3.54 5.75-2.34 8.9.04.1.08.2.08.33 0 .22-.15.42-.35.5-.22.1-.46.04-.64-.12a.83.83 0 01-.15-.17c-1.1-1.43-1.28-3.48-.53-5.12C5.89 10 5 12.3 5.14 14.47c.04.5.1 1 .27 1.5.14.6.4 1.2.72 1.73 1.04 1.73 2.87 2.97 4.84 3.22 2.1.27 4.35-.12 5.96-1.6 1.8-1.66 2.45-4.32 1.5-6.6l-.13-.26c-.2-.46-.47-.87-.8-1.25l.05-.01m-3.1 6.3c-.28.24-.73.5-1.08.6-1.1.4-2.2-.16-2.87-.82 1.19-.28 1.89-1.16 2.09-2.05.17-.8-.14-1.46-.27-2.23-.12-.74-.1-1.37.18-2.06.17.38.37.76.6 1.06.76 1 1.95 1.44 2.2 2.8.04.14.06.28.06.43.03.82-.32 1.72-.92 2.27h.01z"/></svg>');
--admonition--success: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2m-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>');
--admonition--question: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.07 11.25l-.9.92C13.45 12.89 13 13.5 13 15h-2v-.5c0-1.11.45-2.11 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41a2 2 0 00-2-2 2 2 0 00-2 2H8a4 4 0 014-4 4 4 0 014 4 3.2 3.2 0 01-.93 2.25M13 19h-2v-2h2M12 2A10 10 0 002 12a10 10 0 0010 10 10 10 0 0010-10c0-5.53-4.5-10-10-10z"/></svg>');
--admonition--warning: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 14h-2v-4h2m0 8h-2v-2h2M1 21h22L12 2 1 21z"/></svg>');
--admonition--failure: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2c5.53 0 10 4.47 10 10s-4.47 10-10 10S2 17.53 2 12 6.47 2 12 2m3.59 5L12 10.59 8.41 7 7 8.41 10.59 12 7 15.59 8.41 17 12 13.41 15.59 17 17 15.59 13.41 12 17 8.41 15.59 7z"/></svg>');
--admonition--danger: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M11.5 20l4.86-9.73H13V4l-5 9.73h3.5V20M12 2c2.75 0 5.1 1 7.05 2.95C21 6.9 22 9.25 22 12s-1 5.1-2.95 7.05C17.1 21 14.75 22 12 22s-5.1-1-7.05-2.95C3 17.1 2 14.75 2 12s1-5.1 2.95-7.05C6.9 3 9.25 2 12 2z"/></svg>');
--admonition--bug: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14 12h-4v-2h4m0 6h-4v-2h4m6-6h-2.81a5.985 5.985 0 00-1.82-1.96L17 4.41 15.59 3l-2.17 2.17a6.002 6.002 0 00-2.83 0L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8z"/></svg>');
--admonition--example: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 13v-2h14v2H7m0 6v-2h14v2H7M7 7V5h14v2H7M3 8V5H2V4h2v4H3m-1 9v-1h3v4H2v-1h2v-.5H3v-1h1V17H2m2.25-7a.75.75 0 01.75.75c0 .2-.08.39-.21.52L3.12 13H5v1H2v-.92L4 11H2v-1h2.25z"/></svg>');
--admonition--quote: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M14 17h3l2-4V7h-6v6h3M6 17h3l2-4V7H5v6h3l-2 4z"/></svg>');
--admonition--chevron-right: url('data:image/svg+xml;:charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/></svg>');
--admonition--chevron-down: url('data:image/svg+xml;:charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/></svg>');
}
/* TITLE */
.nge-md-admonition-title {
min-height: 24px;
box-sizing: border-box;
position: relative;
display: flex;
align-items: center;
margin-top: 0 !important;
margin-bottom: 0 !important;
padding: 0.4rem 0.6rem 0.4rem 2rem;
font-weight: 700;
/*REMOVE DETAILS MARKER IN FIREFOX*/
list-style-type: none;
}
.nge-md-admonition-title:focus {
outline: none;
}
/* DETAILS MARKER */
.nge-md-admonition-title::marker,
.nge-md-admonition-title::-webkit-details-marker {
display: none;
}
/* TITLE COLLAPSIBLE */
details .nge-md-admonition-title {
cursor: pointer;
}
details .nge-md-admonition-title:after {
position: absolute;
right: 0.6rem;
width: 1rem;
height: 1rem;
-o-mask-image: var(--admonition--chevron-right);
-webkit-mask-image: var(--admonition--chevron-right);
mask-image: var(--admonition--chevron-right);
content: "";
}
details[open] .nge-md-admonition-title:after {
-o-mask-image: var(--admonition--chevron-down);
-webkit-mask-image: var(--admonition--chevron-down);
mask-image: var(--admonition--chevron-down);
}
/* CONTENT */
.nge-md-admonition-content {
padding: 0 0.6rem;
}
`,
];
Object.keys(admonitions).forEach((type) => {
const map = admonitions[type];
stylesheet.push(`
.nge-md-admonition--${type} {
border-left: 0.2rem solid ${map.border};
}
.nge-md-admonition-title--${type} {
background-color: ${map.bg};
}
.nge-md-admonition-title--${type}:before,
.nge-md-admonition-title--${type}:after {
background-color: ${map.border};
}
.nge-md-admonition-title--${type}:before {
position: absolute;
left: 0.6rem;
width: 1rem;
height: 1rem;
-o-mask-image: var(--admonition--${type});
-webkit-mask-image: var(--admonition--${type});
mask-image: var(--admonition--${type});
content: "";
}
`);
});
const style = document.createElement('style');
style.setAttribute('nge-markdown-admonitions', '');
style.innerHTML = stylesheet.join('\n');
head.appendChild(style);
}
createAdmonitions(transformer) {
transformer.addHtmlTransformer((element) => {
const paragraphs = element.querySelectorAll('p');
paragraphs.forEach((p) => {
const text = p.innerHTML;
const match = text.match(OPEN);
if (match) {
const opener = match[1];
const type = match[2];
const title = (match[3] || '').trim();
const admonition = document.createElement('details');
if (opener.endsWith('+')) {
admonition.open = true;
}
admonition.className = 'nge-md-admonition nge-md-admonition--' + type;
const summary = document.createElement('summary');
summary.className = 'nge-md-admonition-title nge-md-admonition-title--' + type;
summary.innerHTML = title;
const content = [];
admonition.appendChild(summary);
let node = p.nextElementSibling;
let opened = 1;
while (node) {
const innerHTML = node.innerHTML.trim();
if (innerHTML.match(OPEN)) {
opened++;
}
else if (innerHTML.match(CLOSE)) {
opened--;
if (opened === 0) {
node.remove();
break;
}
}
content.push(node);
node = node.nextElementSibling;
}
const div = document.createElement('div');
div.className = 'nge-md-admonition-content';
content.forEach((e) => div.appendChild(e));
admonition.appendChild(div);
p.parentElement?.insertBefore(admonition, p);
p.remove();
}
});
});
}
autoFixAdmonitionsSyntax(transformer) {
transformer.addMarkdownTransformer((markdown) => {
const lines = markdown.split('\n');
const length = lines.length;
let insideCodeBlock = false;
for (let i = 0; i < length; i++) {
const curr = lines[i];
if (curr.startsWith('```')) {
insideCodeBlock = !insideCodeBlock;
}
if (insideCodeBlock) {
continue;
}
const prev = i > 0 ? lines[i - 1] : undefined;
const next = i < length - 1 ? lines[i + 1] : undefined;
if (curr.match(OPEN)) {
if (next?.trim()) {
lines[i] = curr + '\n';
}
}
else if (curr.match(CLOSE)) {
if (prev?.trim()) {
lines[i] = '\n' + curr;
}
}
}
return lines.join('\n');
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownAdmonitions, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownAdmonitions }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownAdmonitions, decorators: [{
type: Injectable
}] });
/**
* Injection token to register `NgeMarkdownAdmonitions` contribution.
*/
const NgeMarkdownAdmonitionsProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownAdmonitions,
};
/** Injection token to pass custom options to `NgeMarkdownEmoji` contribution. */
const NGE_MARKDOWN_EMOJI_OPTIONS = new InjectionToken('NGE_MARKDOWN_EMOJI_OPTIONS');
/**
* Contribution to use emoji in markdown using [emoji-toolkit](https://github.com/joypixels/emoji-toolkit) library.
*/
class NgeMarkdownEmoji {
constructor(options) {
this.options = options;
this.options = options || {};
this.options.url = this.options.url || 'https://cdn.jsdelivr.net/npm/emoji-toolkit@8.0.0/lib/js/joypixels.min.js';
}
dependencies() {
const deps = [];
if (!('joypixels' in window)) {
deps.push(['script', this.options.url]);
}
return deps;
}
contribute(transformer) {
transformer.addMarkdownTransformer((markdown) => {
const { joypixels } = window;
const lines = markdown.split('\n');
const length = lines.length;
let insideCodeBlock = false;
for (let i = 0; i < length; i++) {
const curr = lines[i];
if (curr.startsWith('```')) {
insideCodeBlock = !insideCodeBlock;
}
if (insideCodeBlock) {
continue;
}
if (lines[i].match(/:[a-z0-9_+-]+:/g)) {
lines[i] = joypixels.shortnameToUnicode(lines[i]);
}
}
return lines.join('\n');
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownEmoji, deps: [{ token: NGE_MARKDOWN_EMOJI_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownEmoji }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownEmoji, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [NGE_MARKDOWN_EMOJI_OPTIONS]
}] }] });
/**
* Provider to register `NgeMarkdownEmoji` contribution.
*/
const NgeMarkdownEmojiProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownEmoji,
};
/**
* Provider to pass options to `NgeMarkdownEmoji` contribution.
* @param options `NgeMarkdownEmoji` options.
*/
function NgeMarkdownEmojiOptionsProvider(options) {
return {
provide: NGE_MARKDOWN_EMOJI_OPTIONS,
useValue: options,
};
}
const DATA_LINES = 'data-nge-md-hl-lines';
const DATA_LANGUAGE = 'data-nge-md-hl-language';
const DATA_HIGHLIGHTS = 'data-nge-md-hl-highlights';
const DATA_FILENAME = 'data-nge-md-hl-filename';
/**
* Injection token to register a highlighter service.
*/
const NGE_MARKDOWN_HIGHLIGHTER_SERVICE = new InjectionToken('NGE_MARKDOWN_HIGHLIGHTER_SERVICE');
/**
* Contribution to add an abstract syntax highlighter.
*/
class NgeMarkdownHighlighter {
constructor(injector, options) {
this.injector = injector;
this.options = options;
}
contribute(transformer) {
this.createAttributes(transformer);
this.colorizeCodes(transformer);
}
createAttributes(transformer) {
transformer.addRendererTransformer((renderer) => {
renderer.code = (code, args) => {
args = args || '';
const attributes = new Map();
// LANGUAGE
const language = args.split(' ').slice(0, 1).pop() || 'plaintext';
attributes.set(DATA_LANGUAGE, language);
// LINE NUMBERING
let match = args.match(/lines="(.+?)"/);
if (match) {
attributes.set(DATA_LINES, match[1]);
}
// LINE HIGHLIGHTING
match = args.match(/highlights="(.+?)"/);
if (match) {
attributes.set(DATA_HIGHLIGHTS, match[1]);
}
// FILENAME
match = args.match(/filename="(.+?)"/);
if (match) {
attributes.set(DATA_FILENAME, match[1]);
}
const attribs = Array.from(attributes.entries())
.map(([attributeName, attributeValue]) => {
return `${attributeName}="${attributeValue}"`;
})
.join(' ');
console.log(attribs);
return `<pre ${attribs}><code>${this.escapeHtml(code)}</code></pre>`;
};
return renderer;
});
}
colorizeCodes(transformer) {
if (!this.options?.highligtht) {
return;
}
const highlight = this.options.highligtht;
transformer.addHtmlTransformer(async (element) => {
const preElements = Array.from(element.querySelectorAll(`pre[${DATA_LANGUAGE}]`));
for (const pre of preElements) {
highlight(this.injector, {
lines: pre.getAttribute(DATA_LINES) || '',
element: pre.querySelector('code'),
language: pre.getAttribute(DATA_LANGUAGE) || 'plaintext',
highlights: pre.getAttribute(DATA_HIGHLIGHTS) || '',
filename: pre.getAttribute(DATA_FILENAME) || '',
});
}
});
}
escapeHtml(input) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const map = {
'<': '<',
'>': '>',
};
return input.replace(/[<>]/g, (tag) => map[tag] || tag);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownHighlighter, deps: [{ token: i0.Injector }, { token: NGE_MARKDOWN_HIGHLIGHTER_SERVICE, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownHighlighter }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownHighlighter, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [NGE_MARKDOWN_HIGHLIGHTER_SERVICE]
}] }] });
/**
* Injection token to register `NgeMarkdownHighlighter` contribution.
*/
const NgeMarkdownHighlighterProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownHighlighter,
};
/**
* Provider to register `NgeMonacoColorizerService` as the syntax highlighter.
* @param type A reference to NgeMonacoColorizerService type.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function NgeMarkdownHighlighterMonacoProvider(type) {
return {
provide: NGE_MARKDOWN_HIGHLIGHTER_SERVICE,
useValue: {
highligtht: (injector, options) => {
const colorizer = injector.get(type, null);
const code = options.element;
const pre = code.parentElement;
pre.style.margin = '0.5em 0';
pre.style.overflow = 'auto';
pre.style.border = '1px solid #F2F2F2';
colorizer?.colorizeElement({
element: code,
language: options.language,
code: code.textContent,
lines: options.lines,
filename: options.filename,
highlights: options.highlights,
});
},
},
};
}
/**
* Contribution to use icons in markdown library using https://icongr.am/.
*/
class NgeMarkdownIcons {
contribute(transformer) {
transformer.addMarkdownTransformer((markdown) => {
const pattern = /@(\w+)\s+([\w-]+)((\s+(?:color|size)=[^\s]+)*?)?@/gm;
const lines = markdown.split('\n');
const length = lines.length;
let insideCodeBlock = false;
for (let i = 0; i < length; i++) {
const curr = lines[i];
if (curr.startsWith('```')) {
insideCodeBlock = !insideCodeBlock;
}
if (insideCodeBlock) {
continue;
}
lines[i] = lines[i].replace(pattern, (_, type, name, params) => {
params = (params ?? '')
.trim()
.split(' ')
.filter((e) => e.trim())
.join('&');
params = params ? '?' + params : '';
return `<img src="https://icongr.am/${type.trim()}/${name.trim()}.svg${params}"/>`;
});
}
return lines.join('\n');
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownIcons, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownIcons }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownIcons, decorators: [{
type: Injectable
}] });
/**
* Injection token to register `NgeMarkdownIcons` contribution.
*/
const NgeMarkdownIconsProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownIcons,
};
/** Custom options to pass to NgeMarkdownKatex contribution. */
const NGE_MARKDOWN_KATEX_OPTIONS = new InjectionToken('NGE_MARKDOWN_KATEX_OPTIONS');
/**
* Contribution to render math expressions in markdown using [Katex](https://katex.org) library.
*/
class NgeMarkdownKatex {
constructor(options) {
this.options = options;
this.options = options || {
extensions: {
mhchem: true,
copyTex: true,
},
};
this.options.extensions = this.options.extensions || {};
this.options.extensions.mhchem = this.options.extensions.mhchem ?? true;
this.options.extensions.copyTex = this.options.extensions.copyTex ?? true;
}
dependencies() {
if ('katex' in window) {
return [];
}
let baseUrl = this.options?.baseUrl || 'https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/';
if (!baseUrl.endsWith('/')) {
baseUrl += '/';
}
const deps = [
['style', `${baseUrl}katex.min.css`],
['script', `${baseUrl}katex.js`],
['script', `${baseUrl}contrib/auto-render.js`],
];
if (this.options.extensions?.copyTex) {
deps.push(['script', `${baseUrl}contrib/copy-tex.min.js`]);
}
if (this.options.extensions?.mhchem) {
deps.push(['script', `${baseUrl}contrib/mhchem.js`]);
}
return deps;
}
contribute(transformer) {
// pattern to search multiline latex between $$...$$ or inline latex between $...$
// const pattern = /(\$\$\n((.|\s|\n)+?)\n\$\$)|(\$([^\s][^$\n]+?[^\s])\$)/gm;
transformer.addHtmlTransformer((element) => {
const { renderMathInElement } = window;
try {
renderMathInElement(element, this.options.options || {
delimiters: [
{ left: '$$', right: '$$', display: false },
{ left: '$', right: '$', display: false },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: false },
],
});
}
catch (error) {
console.error(error);
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownKatex, deps: [{ token: NGE_MARKDOWN_KATEX_OPTIONS, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownKatex }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownKatex, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [NGE_MARKDOWN_KATEX_OPTIONS]
}] }] });
/**
* Provider to render math expressions in markdown using [Katex](https://katex.org) library.
*/
const NgeMarkdownKatexProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownKatex,
};
/**
* Provider to pass options to `NgeMarkdownKatex` contribution.
* @param options `NgeMarkdownKatex` options.
*/
function NgeMarkdownKatexOptionsProvider(options) {
return {
provide: NGE_MARKDOWN_KATEX_OPTIONS,
useValue: options,
};
}
/**
* Contribution to handle fragment navigation in anchor elements.
*/
class NgeMarkdownLinkAnchor {
constructor(router, location) {
this.router = router;
this.location = location;
}
contribute(transformer) {
transformer.addRendererTransformer((renderer) => {
renderer.link = (href, _, text) => {
const attributes = new Map();
if (href.startsWith('#')) {
href = this.location.path() + href;
}
attributes.set('href', href);
// open in new tab if its a link external to the application
const routes = (this.router.config || []).map((config) => {
if (!config.path?.startsWith('/')) {
return '/' + config.path;
}
return config.path;
});
if (!routes.find((route) => href.startsWith(route))) {
attributes.set('target', '_blank');
}
return `<a ${Array.from(attributes.entries())
.map((entry) => {
return entry[0] + '=' + entry[1];
})
.join(' ')}>${text}</a>`;
};
return renderer;
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownLinkAnchor, deps: [{ token: i1.Router }, { token: i2.Location }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownLinkAnchor }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownLinkAnchor, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.Router }, { type: i2.Location }] });
/**
* Injection token to register `NgeMarkdownLinkAnchor` contribution.
*/
const NgeMarkdownLinkAnchorProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownLinkAnchor,
};
let TABSET_COUNTER = 0;
/**
* Contribution to render tabs inside markdown files.
*/
class NgeMarkdownTabbedSet {
contribute(transformer) {
this.addStyles();
transformer.addHtmlTransformer((el) => {
const open = /^===\s*(.+)/;
const close = /^===\s*$/;
const processed = [];
const toRemoves = [];
el.querySelectorAll('p').forEach((paragraph) => {
if (processed.indexOf(paragraph) !== -1) {
return;
}
const match = paragraph.innerHTML.match(open);
if (match) {
const tabs = [];
let tab = {
title: match[1],
content: [],
};
let node = paragraph.nextElementSibling;
while (node) {
let push = true;
const innerHTML = node.innerHTML.trim();
if (innerHTML.match(open)) {
tabs.push(tab);
tab = {
title: innerHTML.replace('===', '').trim(),
content: [],
};
push = false;
toRemoves.push(node);
}
else if (innerHTML.match(close)) {
tabs.push(tab);
toRemoves.push(node);
break;
}
if (push) {
tab.content.push(node);
}
processed.push(node);
node = node.nextElementSibling;
}
paragraph.parentElement?.replaceChild(this.createTabs(tabs), paragraph);
paragraph.remove();
toRemoves.forEach((e) => e.remove());
}
});
});
}
createTabs(tabs) {
const tabset = document.createElement('div');
tabset.className = 'nge-md-tabbed-set';
let i = 0;
TABSET_COUNTER++;
tabs.forEach((e) => {
const checkbox = document.createElement('input');
checkbox.type = 'radio';
checkbox.id = 'nge-md-tabbed-' + TABSET_COUNTER + '-' + i;
checkbox.name = 'nge-md-tabbed-' + TABSET_COUNTER;
if (i === 0) {
checkbox.setAttribute('checked', 'checked');
}
const label = document.createElement('label');
label.setAttribute('for', checkbox.id);
label.innerHTML = e.title;
const content = document.createElement('div');
content.className = 'nge-md-tabbed-content';
e.content.forEach((c) => content.appendChild(c));
tabset.append(checkbox, label, content);
i++;
});
return tabset;
}
addStyles() {
const head = document.head;
if (head.querySelector('[nge-markdown-tabbed-set]')) {
return;
}
const style = document.createElement('style');
style.setAttribute('nge-markdown-tabbed-set', '');
style.innerHTML = `
/* TAB SET */
.nge-md-tabbed-set {
display: flex;
position: relative;
flex-wrap: wrap;
margin: 1em 0;
border-radius: .1rem;
}
.nge-md-tabbed-set .highlight {
background: #ddd;
}
.nge-md-tabbed-set .nge-md-tabbed-content {
display: none;
order: 99;
width: 100%;
}
.nge-md-tabbed-set .nge-md-tabbed-content :first-child {
margin: 0;
}
.nge-md-tabbed-set label {
width: auto;
/*padding: 0.25em;*/
padding: .9375em 1.25em .78125em;
font-weight: 700;
font-size: .84rem;
cursor: pointer;
}
.nge-md-tabbed-set input {
position: absolute;
opacity: 0;
}
.nge-md-tabbed-set input:nth-child(n+1) {
color: #333333;
}
.nge-md-tabbed-set input:nth-child(n+1):checked + label {
color: #ff5252;
transition: all 0.3s;
/* background-color: rgba(255, 82, 82, 0.1);*/
border-bottom: .1rem solid;
}
.nge-md-tabbed-set input:nth-child(n+1):checked + label + .nge-md-tabbed-content {
display: block;
border-top: 1px solid #F5F5F5;
}
`;
head.appendChild(style);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownTabbedSet, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownTabbedSet }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownTabbedSet, decorators: [{
type: Injectable
}] });
/** * Injection token to register `NgeMarkdownTabbedSet` contribution. */
const NgeMarkdownTabbedSetProvider = {
provide: NGE_MARKDOWN_CONTRIBUTION,
multi: true,
useClass: NgeMarkdownTabbedSet,
};
/**
* Nge markdown transformer used by the contributions.
*/
class NgeMarkdownTransformer {
constructor(
/** Configuration option */
config) {
this.config = config;
this.astTransformers = [];
this.htmlTransformers = [];
this.markdownTransformers = [];
this.rendererTransformers = [];
this.tokenizerTransformers = [];
}
/**
* Registers a function to call to update the ast generated by the markdown compiler.
* @param transform the function to call.
*/
addAstTransformer(transform) {
if (transform == null) {
throw new ReferenceError('argument "transform" is required');
}
this.astTransformers.push(transform);
}
/**
* Registers a function to call to transform the html generated by the markdown compiler.
* @param transformer the function to call.
*/
addHtmlTransformer(transformer) {
if (transformer == null) {
throw new ReferenceError('argument "transform" is required');
}
this.htmlTransformers.push(transformer);
}
/**
* Registers a function to call to transform the markdown before it's parsed by the markdown compiler.
* @param transform the function to call.
*/
addMarkdownTransformer(transform) {
if (transform == null) {
throw new ReferenceError('argument "transform" is required');
}
this.markdownTransformers.push(transform);
}
/**
* Registers a function to call to update marked library renderer.
* @param transform the function to call.
*/
addRendererTransformer(transform) {
if (transform == null) {
throw new ReferenceError('argument "transform" is required');
}
this.rendererTransformers.push(transform);
}
/**
* Registers a function to call to update marked library tokenizer.
* @param transform the function to call.
*/
addTokenizerTransformer(transform) {
if (transform == null) {
throw new ReferenceError('argument "transform" is required');
}
this.tokenizerTransformers.push(transform);
}
/**
* Apply the registered ast transformer functions to the given ast.
* @param ast the ast to transform.
* @returns the transformed ast.
*/
transformAst(ast) {
if (ast == null) {
throw new ReferenceError('argument "ast" is required');
}
for (const fn of this.astTransformers) {
ast = fn(ast);
}
return ast;
}
/**
* Apply the registered html transformer functions to the given html.
* @param element the html element to transform.
* @returns the transformed html.
*/
transformHTML(element) {
if (element == null) {
throw new ReferenceError('argument "html" is required');
}
for (const fn of this.htmlTransformers) {
fn(element);
}
return element;
}
/**
* Apply the registered markdown transformer functions to the given markdown.
* @param markdown the markdown to transform.
* @returns the transformed markdown.
*/
transformMarkdown(markdown) {
if (markdown == null) {
throw new ReferenceError('argument "markdown" is required');
}
for (const fn of this.markdownTransformers) {
markdown = fn(markdown);
}
return markdown;
}
/**
* Apply the registered renderer transformer functions to the given renderer.
* @param renderer the renderer to transform.
* @returns the transformed renderer.
*/
transformRenderer(renderer) {
if (renderer == null) {
throw new ReferenceError('argument "renderer" is required');
}
for (const fn of this.rendererTransformers) {
renderer = fn(renderer);
}
return renderer;
}
/**
* Apply the registered tokenizer transformer functions to the given tokenizer.
* @param tokenizer the tokenizer to transform.
* @returns the transformed tokenizer.
*/
transformTokenizer(tokenizer) {
if (tokenizer == null) {
throw new ReferenceError('argument "tokenizer" is required');
}
for (const fn of this.tokenizerTransformers) {
tokenizer = fn(tokenizer);
}
return tokenizer;
}
/**
* Add new stylesheet element to the document.
* @param url url to the stylesheet.
* @returns A promise that resolves once the element is loaded.
*/
addStyle(url) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
document.body.appendChild(link);
return new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
}
/**
* Add new script element to the document.
* @param url url to the script.
* @returns A promise that resolves once the element is loaded.
*/
addScript(url) {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
script.defer = true;
document.body.appendChild(script);
return new Promise((resolve, reject) => {
script.onload = resolve;
script.onerror = reject;
});
}
}
const NGE_MARKDOWN_CONFIG = new InjectionToken('NGE_MARKDOWN_CONFIG');
const NGE_MARKDOWN_THEMES = new InjectionToken('NGE_MARKDOWN_THEMES');
const NgeMarkdownConfigProvider = (config) => typeof config === 'function'
? {
provide: NGE_MARKDOWN_CONFIG,
useFactory: config,
}
: {
provide: NGE_MARKDOWN_CONFIG,
useValue: config,
};
// darkThemeClassName: 'dark-theme',
const NgeMarkdownThemeProvider = (...themes) => themes.map((theme) => ({
provide: NGE_MARKDOWN_THEMES,
useValue: theme,
multi: true,
}));
const WINDOW = window;
/**
* Markdown compiler service.
*/
class NgeMarkdownService {
constructor(config, resourceLoader) {
this.config = config;
this.resourceLoader = resourceLoader;
this.config = config || {};
}
/**
* Compiles a markdown string to an html string.
* @param options compilation options.
* @returns A promise that resolve with the AST of the compiled markdown
* (with the modifications of the contributions).
*/
async compile(options) {
let markdown = this.trimIndent(options.markdown);
if (options.isHtmlString) {
markdown = this.decodeHtml(markdown);
}
const transformer = await this.createTransformer(options);
const renderer = this.renderer(transformer);
const tokenizer = this.tokenizer(transformer);
const markedOptions = {
gfm: true,
...this.config,
renderer,
tokenizer,
};
markdown = transformer.transformMarkdown(markdown);
const tokens = transformer.transformAst(marked.lexer(markdown, markedOptions));
options.target.innerHTML = marked.parser(tokens, markedOptions);
transformer.transformHTML(options.target);
return tokens;
}
async createTransformer(options) {
const contributions = [...(options.contributions || [])];
const transformer = new NgeMarkdownTransformer(this.config);
const dependencies = [];
for (const contrib of contributions) {
if (contrib.dependencies) {
dependencies.push(...contrib.dependencies());
}
contrib.contribute(transformer);
}
// Try to fix the issues described here by loading monaco editor
// after all the other scripts.
// https://stackoverflow.com/a/33635881
// https://github.com/microsoft/monaco-editor/issues/662
// https://github.com/microsoft/monaco-editor/issues/1249
const define = WINDOW.define;
WINDOW.define = undefined;
await lastValueFrom(this.resourceLoader.loadAllSync(dependencies));
WINDOW.define = define;
return transformer;
}
renderer(transformer) {
const renderer = transformer.transformRenderer(this.config?.renderer || new marked.Renderer());
return renderer;
}
tokenizer(transformer) {
return transformer.transformTokenizer(this.config?.tokenizer || new marked.Tokenizer());
}
decodeHtml(html) {
const textarea = document.createElement('textarea');
textarea.innerHTML = html;
return textarea.value;
}
trimIndent(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: "18.2.1", ngImport: i0, type: NgeMarkdownService, deps: [{ token: NGE_MARKDOWN_CONFIG, optional: true }, { token: i1$1.ResourceLoaderService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: undefined, decorators: [{
type: Optional
}, {
type: Inject,
args: [NGE_MARKDOWN_CONFIG]
}] }, { type: i1$1.ResourceLoaderService }] });
class NgeMarkdownComponent {
get klass() {
if (!this.theme)
return '';
const classeNames = [`nge-markdown-theme--${this.theme}`];
if (this.isDark) {
classeNames.push(`nge-markdown-theme--${this.theme}--dark`);
}
return classeNames.join(' ');
}
constructor() {
this.el = inject(ElementRef);
this.http = inject(HttpClient, { optional: true });
this.markdownService = inject(NgeMarkdownService);
this.resourceLoader = inject(ResourceLoaderService);
this.changeDetectorRef = inject(ChangeDetectorRef);
this.themes = inject(NGE_MARKDOWN_THEMES, { optional: true });
this.contributions = inject(NGE_MARKDOWN_CONTRIBUTION, {
optional: true,
});
this.isDark = false;
/** Theme to apply to the markdown content. */
this.theme = 'github';
/**
* An event that emit after each rendering pass
* with the list of tokens parsed from the input markdown.
*/
this.render = new EventEmitter();
this.themes = this.themes || [];
}
ngOnInit() {
this.el.nativeElement.style.opacity = '0';
}
async ngOnChanges() {
await this.checkTheme();
this.file ? await this.renderFromFile(this.file) : await this.renderFromString(this.data || '');
this.el.nativeElement.style.opacity = '1';
}
async ngAfterViewInit() {
await this.checkTheme();
if (!this.file && !this.data) {
await this.renderFromString(this.el.nativeElement.innerHTML, true);
}
this.el.nativeElement.style.opacity = '1';
}
async renderFromFile(file) {
if (!this.http) {
throw new Error('[nge-markdown] When using the `file` attribute you *have to* pass the `HttpClient` as a parameter of the `forRoot` method. See README for more information');
}
const markdown = await firstValueFrom(this.http.get(file, { responseType: 'text' }));
await this.renderFromString(markdown);
}
async renderFromString(markdown, isHtmlString = false) {
const tokens = await this.markdownService.compile({
target: this.el.nativeElement,
markdown,
isHtmlString,
contributions: this.contributions,
});
this.render.emit(tokens);
this.changeDetectorRef.markForCheck();
}
async checkTheme() {
if (this.theme) {
const themeInfo = this.themes?.find((theme) => theme.name === this.theme);
if (themeInfo) {
await firstValueFrom(this.resourceLoader.loadAllSync([['style', themeInfo.styleUrl]])).catch();
}
}
const { darkThemeClassName } = this.markdownService.config;
if (darkThemeClassName) {
// TODO: support angular universal
this.isDark =
document.querySelector(darkThemeClassName.startsWith('.') ? darkThemeClassName : `.${darkThemeClassName}`) !=
null;
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.1", type: NgeMarkdownComponent, selector: "nge-markdown, [nge-markdown]", inputs: { file: "file", data: "data", theme: "theme" }, outputs: { render: "render" }, host: { properties: { "class": "this.klass" } }, usesOnChanges: true, ngImport: i0, template: "<ng-content></ng-content>\n", styles: [":host{transition:opacity .3s ease-in-out}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownComponent, decorators: [{
type: Component,
args: [{ selector: 'nge-markdown, [nge-markdown]', changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>\n", styles: [":host{transition:opacity .3s ease-in-out}\n"] }]
}], ctorParameters: () => [], propDecorators: { file: [{
type: Input
}], data: [{
type: Input
}], theme: [{
type: Input
}], klass: [{
type: HostBinding,
args: ['class']
}], render: [{
type: Output
}] } });
class NgeMarkdownModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownModule, declarations: [NgeMarkdownComponent], imports: [RouterModule], exports: [NgeMarkdownComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownModule, imports: [RouterModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.1", ngImport: i0, type: NgeMarkdownModule, decorators: [{
type: NgModule,
args: [{
imports: [RouterModule],
declarations: [NgeMarkdownComponent],
exports: [NgeMarkdownComponent],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NGE_MARKDOWN_CONFIG, NGE_MARKDOWN_CONTRIBUTION, NGE_MARKDOWN_EMOJI_OPTIONS, NGE_MARKDOWN_HIGHLIGHTER_SERVICE, NGE_MARKDOWN_KATEX_OPTIONS, NGE_MARKDOWN_THEMES, NgeMarkdownAdmonitions, NgeMarkdownAdmonitionsProvider, NgeMarkdownComponent, NgeMarkdownConfigProvider, NgeMarkdownEmoji, NgeMarkdownEmojiOptionsProvider, NgeMarkdownEmojiProvider, NgeMarkdownHighlighter, NgeMarkdownHighlighterMonacoProvider, NgeMarkdownHighlighterProvider, NgeMarkdownIcons, NgeMarkdownIconsProvider, NgeMarkdownKatex, NgeMarkdownKatexOptionsProvider, NgeMarkdownKatexProvider, NgeMarkdownLinkAnchor, NgeMarkdownLinkAnchorProvider, NgeMarkdownModule, NgeMarkdownService, NgeMarkdownTabbedSet, NgeMarkdownTabbedSetProvider, NgeMarkdownThemeProvider, NgeMarkdownTransformer };
//# sourceMappingURL=cisstech-nge-markdown.mjs.map