|
remarkNode
|
Type : Node
|
|
Required : true
|
|
|
|
Readonly
children
|
Type : unknown
|
Default value : computed(() => (this.node() as Parent).children)
|
|
|
|
templateService
|
Type : unknown
|
Default value : inject(RemarkTemplatesService)
|
|
|
import { ChangeDetectionStrategy, Component, computed, inject, input } from '@angular/core';
import { Node, Parent } from 'mdast';
import { RemarkTemplatesService } from './remark-templates.service';
@Component({
selector: 'remark-node, [remarkNode]',
templateUrl: './remark-node.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class RemarkNodeComponent {
templateService = inject(RemarkTemplatesService);
readonly node = input.required<Node>({ alias: 'remarkNode' });
readonly children = computed(() => (this.node() as Parent).children);
get templates() {
return this.templateService.templates;
}
}
<!-- eslint-disable @angular-eslint/template/elements-content -->
<!--
For each child, display its custom template or default one.
Tracking by index means that DOM elements are reused even when the node type and content is different.
Changes should still be reflected because of the @Input() node binding.
-->
@for (child of children(); track $index) {
<ng-container
*ngTemplateOutlet="templates[child.type] || defaultTpl; context: { $implicit: child }"
>
</ng-container>
}
<ng-template #defaultTpl let-child>
@switch (child.type) {
<!-- Parents -->
@case ('paragraph') {
<p [remarkNode]="child"></p>
} @case ('heading') { @switch (child.depth) { @case (1) {
<h1 [remarkNode]="child"></h1>
} @case (2) {
<h2 [remarkNode]="child"></h2>
} @case (3) {
<h3 [remarkNode]="child"></h3>
} @case (4) {
<h4 [remarkNode]="child"></h4>
} @case (5) {
<h5 [remarkNode]="child"></h5>
} @case (6) {
<h6 [remarkNode]="child"></h6>
} } } @case ('blockquote') {
<blockquote [remarkNode]="child"></blockquote>
} @case ('list') { @if (!child.ordered) {
<ul [remarkNode]="child"></ul>
} @if (child.ordered) {
<ol [remarkNode]="child" [start]="child.start"></ol>
} } @case ('listItem') {
<li
[remarkNode]="
!$any(node()).loose && child.children.length === 1 && child.children[0].children
? child.children[0]
: child
"
></li>
} @case ('link') {
<a [href]="child.url" [title]="child.title ?? ''" [remarkNode]="child"></a>
} @case ('emphasis') {
<em [remarkNode]="child"></em>
} @case ('strong') {
<strong [remarkNode]="child"></strong>
} @case ('delete') {
<del [remarkNode]="child"></del>
} @case ('textDirective') { @if (child.name === 'u') {
<u [remarkNode]="child"></u>
} } @case ('footnotes') {
<section class="footnotes" [remarkNode]="child"></section>
} @case ('table') {
<table>
<thead>
@if (child.children[0]; as row) {
<tr>
@for (cell of row.children; track $index; let i = $index) {
<th [remarkNode]="cell" [ngStyle]="{'text-align': child.align?.[i]}"></th>
}
</tr>
}
</thead>
<tbody>
@for (row of child.children.slice(1); track $index) {
<tr>
@for (cell of $any(row).children; track $index; let i = $index) {
<td [remarkNode]="cell" [ngStyle]="{'text-align': child.align?.[i]}"></td>
}
</tr>
}
</tbody>
</table>
}
<!-- These node types should not occur outside of a table, but just in case... -->
@case ('tableRow') {
<tr [remarkNode]="child"></tr>
} @case ('tableCell') {
<td [remarkNode]="child"></td>
}
<!-- Literals -->
@case ('inlineCode') {
<code>{{ child.value }}</code>
} @case ('code') {
<pre [ngClass]="child.lang"><code>{{ child.value }}</code></pre>
} @case ('html') {
<div [innerHTML]="child.value"></div>
} @case ('text') {
{{ child.value }}
}
<!-- Nodes -->
@case ('footnoteReference') {
<sup class="footnote-ref"
><a [href]="'#' + child.identifier">{{ child.label }}</a></sup
>
} @case ('image') {
<img [src]="child.url" [alt]="child.alt ?? ''" [title]="child.title ?? ''" />
} @case ('thematicBreak') {
<hr />
} @case ('break') {
<br />
} @default {
<b>Unknown node type: {{ child.type }}</b>
} }
</ng-template>
Legend
Html element with directive