Skip to content

Commit

Permalink
feat(content): add support for mermaid in markdown (#555)
Browse files Browse the repository at this point in the history
  • Loading branch information
JeanMeche authored Jul 22, 2023
1 parent b93a772 commit 28f2c20
Show file tree
Hide file tree
Showing 10 changed files with 530 additions and 57 deletions.
8 changes: 4 additions & 4 deletions apps/blog-app/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { provideContent, withMarkdownRenderer } from '@analogjs/content';
import { provideFileRouter } from '@analogjs/router';
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import {
withEnabledBlockingInitialNavigation,
withInMemoryScrolling,
} from '@angular/router';
import { provideFileRouter } from '@analogjs/router';
import { provideContent, withMarkdownRenderer } from '@analogjs/content';

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
provideClientHydration(),
provideContent(withMarkdownRenderer()),
provideContent(withMarkdownRenderer({ enableMermaid: true })),
provideFileRouter(
withInMemoryScrolling({ anchorScrolling: 'enabled' }),
withEnabledBlockingInitialNavigation()
Expand Down
20 changes: 19 additions & 1 deletion apps/blog-app/src/content/2022-12-27-my-first-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,22 @@ description: My First Post Description
coverImage: https://images.unsplash.com/photo-1493612276216-ee3925520721?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=464&q=80
---

Hello
Hello, do you know Mermaid ?

You can quickly create and edit diagrams with in markdown !

This following raw code block

````
```mermaid
graph TD
A[Before] -->|Playing with AnalogJS| B(Now Yes !)
```
````

Will be rendered as:

```mermaid
graph TD
A[Before] -->|Playing with AnalogJS| B(Now Yes !)
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"marked": "^5.0.2",
"marked-gfm-heading-id": "^3.0.4",
"marked-highlight": "^2.0.1",
"mermaid": "^10.2.4",
"react": "^18.2.0",
"react-dom": "^18.0.0",
"rxjs": "7.8.0",
Expand Down
10 changes: 10 additions & 0 deletions packages/content/migrations/migration.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@
"alwaysAddToPackageJson": true
}
}
},
"0.2.0-beta.26": {
"version": "0.2.0-beta.25",
"description": "Adds the new peer dependencies for mermaid",
"packages": {
"mermaid": {
"version": "^10.2.4",
"alwaysAddToPackageJson": true
}
}
}
}
}
1 change: 1 addition & 0 deletions packages/content/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"marked": "^5.0.2",
"marked-gfm-heading-id": "^3.0.4",
"marked-highlight": "^2.0.1",
"mermaid": "^10.2.4",
"prismjs": "^1.29.0",
"front-matter": "^4.0.2"
},
Expand Down
10 changes: 5 additions & 5 deletions packages/content/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export { AnchorNavigationDirective } from './lib/anchor-navigation.directive';
export { injectContent } from './lib/content';
export { injectContentFiles } from './lib/inject-content-files';
export { ContentFile } from './lib/content-file';
export { ContentRenderer } from './lib/content-renderer';
export { default as MarkdownComponent } from './lib/markdown.component';
export { default as MarkdownRouteComponent } from './lib/markdown-route.component';
export { MarkdownContentRendererService } from './lib/markdown-content-renderer.service';
export { injectContentFiles } from './lib/inject-content-files';
export {
MarkdownContentRendererService,
provideContent,
withMarkdownRenderer,
} from './lib/markdown-content-renderer.service';
export { default as MarkdownRouteComponent } from './lib/markdown-route.component';
export { default as MarkdownComponent } from './lib/markdown.component';
export { parseRawContentFile } from './lib/parse-raw-content-file';
export { AnchorNavigationDirective } from './lib/anchor-navigation.directive';
38 changes: 31 additions & 7 deletions packages/content/src/lib/markdown-content-renderer.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { inject, Injectable, PLATFORM_ID, Provider } from '@angular/core';
import {
inject,
Injectable,
InjectionToken,
PLATFORM_ID,
Provider,
} from '@angular/core';

import { ContentRenderer } from './content-renderer';
import { MarkedSetupService } from './marked-setup.service';
Expand All @@ -16,14 +22,32 @@ export class MarkdownContentRendererService implements ContentRenderer {
enhance() {}
}

export function withMarkdownRenderer(): Provider {
return {
provide: ContentRenderer,
useClass: MarkdownContentRendererService,
deps: [MarkedSetupService],
};
export interface MarkdownRendererOptions {
enableMermaid: boolean;
}

export function withMarkdownRenderer(
options?: MarkdownRendererOptions
): Provider[] {
return [
{
provide: ContentRenderer,
useFactory: () => new MarkdownContentRendererService(),
deps: [MarkedSetupService],
},
...(options?.enableMermaid
? [
{
provide: USE_MERMAID_TOKEN,
useValue: true,
},
]
: []),
];
}

export function provideContent(...features: Provider[]) {
return [...features, MarkedSetupService];
}

export const USE_MERMAID_TOKEN = new InjectionToken<boolean>('use_mermaid');
33 changes: 29 additions & 4 deletions packages/content/src/lib/markdown.component.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { AsyncPipe } from '@angular/common';
import { AsyncPipe, isPlatformBrowser } from '@angular/common';
import {
AfterViewChecked,
Component,
inject,
Input,
OnInit,
NgZone,
OnChanges,
OnInit,
PLATFORM_ID,
ViewEncapsulation,
inject,
} from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ActivatedRoute, Data } from '@angular/router';
import { Observable, of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';

import { ContentRenderer } from './content-renderer';
import { AnchorNavigationDirective } from './anchor-navigation.directive';
import { ContentRenderer } from './content-renderer';
import { USE_MERMAID_TOKEN } from './markdown-content-renderer.service';

@Component({
selector: 'analog-markdown',
Expand All @@ -30,13 +33,34 @@ export default class AnalogMarkdownComponent
{
private sanitizer = inject(DomSanitizer);
private route = inject(ActivatedRoute);
private zone = inject(NgZone);
private readonly platformId = inject(PLATFORM_ID);
private readonly useMermaid =
inject(USE_MERMAID_TOKEN, { optional: true }) ?? false;
private mermaid: typeof import('mermaid') | undefined;

public content$: Observable<SafeHtml> = of('');

@Input() content!: string | undefined | null;
@Input() classes = 'analog-markdown';

contentRenderer = inject(ContentRenderer);

constructor() {
if (isPlatformBrowser(this.platformId) && this.useMermaid) {
// Mermaid can only be loaded on client side
this.loadMermaid();
}
}

async loadMermaid() {
this.mermaid = await import('mermaid');
this.mermaid.default.initialize({ startOnLoad: false });
// Explicitly running mermaid as ngAfterViewChecked
// has probably already been called
this.zone.runOutsideAngular(() => this.mermaid?.default.run());
}

ngOnInit() {
this.updateContent();
}
Expand All @@ -60,5 +84,6 @@ export default class AnalogMarkdownComponent

ngAfterViewChecked() {
this.contentRenderer.enhance();
this.zone.runOutsideAngular(() => this.mermaid?.default.run());
}
}
14 changes: 11 additions & 3 deletions packages/content/src/lib/marked-setup.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { gfmHeadingId } from 'marked-gfm-heading-id';
import { markedHighlight } from 'marked-highlight';

import 'prismjs';
import 'prismjs/plugins/toolbar/prism-toolbar';
import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard';
import 'prismjs/components/prism-bash';
import 'prismjs/components/prism-css';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-json';
import 'prismjs/components/prism-markup';
import 'prismjs/components/prism-typescript';
import 'prismjs/plugins/copy-to-clipboard/prism-copy-to-clipboard';
import 'prismjs/plugins/toolbar/prism-toolbar';

declare const Prism: typeof import('prismjs');

Expand All @@ -26,6 +26,12 @@ export class MarkedSetupService {
constructor() {
const renderer = new marked.Renderer();
renderer.code = (code, lang) => {
// Let's do a language based detection like on GitHub
// So we can still have non-interpreted mermaid code
if (lang === 'mermaid') {
return '<pre class="mermaid">' + code + '</pre>';
}

if (!lang) {
return '<pre><code>' + code + '</code></pre>';
}
Expand All @@ -48,13 +54,15 @@ export class MarkedSetupService {
highlight: (code, lang) => {
lang = lang || 'typescript';
if (!Prism.languages[lang]) {
console.warn(`Notice:
if (lang !== 'mermaid') {
console.warn(`Notice:
---------------------------------------------------------------------------------------
The requested language '${lang}' is not available with the provided setup.
To enable, import your main.ts as:
import 'prismjs/components/prism-${lang}';
---------------------------------------------------------------------------------------
`);
}
return code;
}
return Prism.highlight(code, Prism.languages[lang], lang);
Expand Down
Loading

0 comments on commit 28f2c20

Please sign in to comment.