diff --git a/demo/src/app/cheat-sheet/cheat-sheet.component.html b/demo/src/app/cheat-sheet/cheat-sheet.component.html index 8becb04c..67e57da1 100644 --- a/demo/src/app/cheat-sheet/cheat-sheet.component.html +++ b/demo/src/app/cheat-sheet/cheat-sheet.component.html @@ -29,7 +29,20 @@

Lists

{{ links$ | async }}
- +
@@ -61,4 +74,4 @@

Horizontal Rule

{{ horizontalRule$ | async }}
- \ No newline at end of file + diff --git a/demo/src/app/cheat-sheet/remote/links.md b/demo/src/app/cheat-sheet/remote/links.md index 4d6cd1f5..ede829cb 100644 --- a/demo/src/app/cheat-sheet/remote/links.md +++ b/demo/src/app/cheat-sheet/remote/links.md @@ -10,6 +10,21 @@ There are two ways to create links. [You can use numbers for reference-style link definitions][1] +[I'm a router link](routerLink:/get-started) + +```html + +``` + Or leave it empty and use the [link text itself]. URLs and URLs in angle brackets will automatically get turned into links. @@ -20,4 +35,4 @@ Some text to show that the reference links can follow later. [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org -[link text itself]: http://www.reddit.com \ No newline at end of file +[link text itself]: http://www.reddit.com diff --git a/lib/src/markdown.component.ts b/lib/src/markdown.component.ts index 3a275dcc..d5f284ee 100644 --- a/lib/src/markdown.component.ts +++ b/lib/src/markdown.component.ts @@ -1,27 +1,40 @@ +import { CommonModule } from '@angular/common'; import { AfterViewInit, Component, ElementRef, EventEmitter, + HostListener, Input, OnChanges, OnDestroy, + Optional, Output, TemplateRef, Type, ViewContainerRef, } from '@angular/core'; -import { Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { NavigationExtras, Router } from '@angular/router'; +import { from, merge, Subject } from 'rxjs'; +import { filter, map, switchMap, takeUntil, tap } from 'rxjs/operators'; import { KatexOptions } from './katex-options'; import { MarkdownService, ParseOptions, RenderOptions } from './markdown.service'; import { MermaidAPI } from './mermaid-options'; import { PrismPlugin } from './prism-plugin'; +export interface MarkdownRouterLinkOptions { + global?: NavigationExtras; + paths?: { [path: string]: NavigationExtras | undefined }; +} + @Component({ // eslint-disable-next-line @angular-eslint/component-selector selector: 'markdown, [markdown]', - template: '', + template: ` + + + `, + imports: [CommonModule], standalone: true, }) export class MarkdownComponent implements OnChanges, AfterViewInit, OnDestroy { @@ -97,12 +110,57 @@ export class MarkdownComponent implements OnChanges, AfterViewInit, OnDestroy { @Input() prompt: string | undefined; @Input() output: string | undefined; @Input() user: string | undefined; + @Input() routerLinkOptions: MarkdownRouterLinkOptions | undefined; // Event emitters @Output() error = new EventEmitter(); @Output() load = new EventEmitter(); @Output() ready = new EventEmitter(); + private changed = new Subject(); + /** + * When the markdown content is ready, or when the markdown content changes, this observable emits. + * - Get all the anchor tags in the markdown content. + * - Filter the anchor tags that have a `href` attribute that starts with `/routerLink:`. + * - Set the `data-routerLink` attribute to the `href` attribute without the `/routerLink:` prefix. + * - Remove the `/routerLink:` prefix from the `href` attribute. + */ + protected changed$ = merge(this.ready, this.changed).pipe( + map(() => this.element.nativeElement.querySelectorAll('a')), + switchMap(links => from(links)), + filter(link => link.getAttribute('href')?.startsWith('/routerLink:') === true), + tap(link => link.setAttribute('data-routerLink', link.getAttribute('href')!.replace('/routerLink:', ''))), + tap(link => link.setAttribute('href', link.getAttribute('href')!.replace('/routerLink:', ''))), + ); + + @HostListener('click', ['$event']) + onDocumentClick(event: MouseEvent) { + const target = event.target as HTMLElement; + const anchor = target.nodeName.toLowerCase() === 'a' ? target : target.closest('a'); + const path = anchor?.getAttribute('href'); + if (path && anchor) { + // Stop the browser from navigating + event.preventDefault(); + event.stopPropagation(); + + // Get the routerLink commands to navigate to + const commands = anchor.getAttribute('data-routerLink')!.split('/').filter(String); + + let extras: NavigationExtras | undefined; + // Find the path in the routerLinkOptions + if (this.routerLinkOptions?.paths) { + extras = this.routerLinkOptions.paths[path]; + } + // Get the global options if no path was found + if (!extras && this.routerLinkOptions?.global) { + extras = this.routerLinkOptions.global; + } + + // Navigate to the path using the router service + this.router?.navigate(commands, extras); + } + } + private _clipboard = false; private _commandLine = false; private _disableSanitizer = false; @@ -119,10 +177,12 @@ export class MarkdownComponent implements OnChanges, AfterViewInit, OnDestroy { public element: ElementRef, public markdownService: MarkdownService, public viewContainerRef: ViewContainerRef, + @Optional() public router?: Router, ) { } ngOnChanges(): void { this.loadContent(); + this.changed.next(); } loadContent(): void {