From 00a6831fb1419d4b256db6f450701da075c44688 Mon Sep 17 00:00:00 2001 From: Marc Stammerjohann <8985933+marcjulian@users.noreply.github.com> Date: Fri, 17 Nov 2023 12:43:38 +0100 Subject: [PATCH] add provideMarkdocOptions --- README.md | 53 ++++++++++++++++-- projects/ngx-markdoc/src/lib/config.ts | 8 --- .../ngx-markdoc/src/lib/extensions/index.ts | 1 + .../ngx-markdoc/src/lib/markdoc.component.ts | 25 +++++++-- .../src/lib/provide-markdoc-options.ts | 29 ++++++++++ projects/ngx-markdoc/src/public-api.ts | 2 +- src/app/app.config.ts | 14 +++++ src/app/markdoc-example.ts | 13 +++++ src/app/pages/landing.component.ts | 14 ++--- src/assets/md/docs/installation.md | 56 ++++++++++++++++++- 10 files changed, 187 insertions(+), 28 deletions(-) delete mode 100644 projects/ngx-markdoc/src/lib/config.ts create mode 100644 projects/ngx-markdoc/src/lib/provide-markdoc-options.ts create mode 100644 src/app/markdoc-example.ts diff --git a/README.md b/README.md index 7dd7ab2..37491e0 100644 --- a/README.md +++ b/README.md @@ -28,16 +28,13 @@ Import `Markdoc` into your component and use `` in your templ ```ts import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; import { Markdoc } from '@notiz/ngx-markdoc'; @Component({ selector: 'app-docs', standalone: true, imports: [Markdoc], - template: ` - - `, + template: ` `, }) export class DocsComponent {} ``` @@ -59,3 +56,51 @@ export class DocsComponent {} ```html ``` + +## Options + +Use `provideMarkdocOptions` to optionally pass a Markdoc configuration options. + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; +import { provideHttpClient } from '@angular/common/http'; + +import { provideMarkdocOptions } from '@notiz/ngx-markdoc'; +import { Config, Node, Tag } from '@markdoc/markdoc'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter(routes), + provideHttpClient(), + provideMarkdocOptions({ + config: { + tags: { + figure: { + selfClosing: true, + attributes: { + src: { type: String, required: true }, + alt: { type: String, required: true }, + caption: { type: String, required: true }, + }, + transform: (node: Node, config: Config) => { + const { src, alt, caption } = node.transformAttributes(config); + const imageTag = new Tag('img', { src, alt }); + const captionTag = new Tag('figcaption', {}, [caption]); + return new Tag('figure', {}, [imageTag, captionTag]); + }, + }, + }, + }, + }), + ], +}; +``` + +Now you can use `{% figure %}` tag in your Markdown file + +```md +{% figure src="https://images.unsplash.com/photo-1610296669228-602fa827fc1f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1675&q=80" alt="Pelican nebulae mosaic" caption="Pelican nebulae mosaic" /%} +``` diff --git a/projects/ngx-markdoc/src/lib/config.ts b/projects/ngx-markdoc/src/lib/config.ts deleted file mode 100644 index 3fa428b..0000000 --- a/projects/ngx-markdoc/src/lib/config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { figure } from './extensions/figure.markdoc'; -import { Config } from '@markdoc/markdoc'; -import { heading, image } from './extensions'; - -export const defaultConfig: Config = { - nodes: { heading, image }, - tags: { figure }, -}; diff --git a/projects/ngx-markdoc/src/lib/extensions/index.ts b/projects/ngx-markdoc/src/lib/extensions/index.ts index a170cf1..b9c7810 100644 --- a/projects/ngx-markdoc/src/lib/extensions/index.ts +++ b/projects/ngx-markdoc/src/lib/extensions/index.ts @@ -1,3 +1,4 @@ +export * from './figure.markdoc'; export * from './heading.markdoc'; export * from './image.markdoc'; export * from './toc.markdoc'; diff --git a/projects/ngx-markdoc/src/lib/markdoc.component.ts b/projects/ngx-markdoc/src/lib/markdoc.component.ts index 459e8c3..10c54e0 100644 --- a/projects/ngx-markdoc/src/lib/markdoc.component.ts +++ b/projects/ngx-markdoc/src/lib/markdoc.component.ts @@ -21,7 +21,7 @@ import MarkdocRenderer, { Tag, } from '@markdoc/markdoc'; import * as yaml from 'js-yaml'; -import { defaultConfig } from './config'; +import { MARKDOC_CONFIG } from './provide-markdoc-options'; @Component({ selector: 'markdoc, [markdoc]', @@ -31,11 +31,25 @@ import { defaultConfig } from './config'; export class Markdoc implements OnChanges, AfterViewInit { private element = inject(ElementRef); private http = inject(HttpClient); + private _defaultConfig = inject(MARKDOC_CONFIG, { optional: true }); + + private get defaultConfig(): Config { + return this._defaultConfig ?? {}; + } @Input() content: string | undefined; @Input() src: string | undefined; - @Input() config: Config | undefined; + private _config: Config | undefined; + @Input() + public get config(): Config | undefined { + return this._config; + } + public set config(value: Config | undefined) { + this._config = value + ? { ...this.defaultConfig, ...value } + : this.defaultConfig; + } private _contentNode: RenderableTreeNode | undefined; /** @@ -80,6 +94,10 @@ export class Markdoc implements OnChanges, AfterViewInit { } @Output() frontmatterChange = new EventEmitter>(); + constructor() { + this.config = this.defaultConfig; + } + ngOnChanges(changes: SimpleChanges): void { if (this.content != undefined) { this.render(this.content); @@ -142,10 +160,9 @@ export class Markdoc implements OnChanges, AfterViewInit { const variables = { ...(config?.variables || {}), markdoc }; const nodes = { - ...defaultConfig.nodes, ...(config?.nodes || {}), }; - return { ...config, tags: { ...defaultConfig.tags }, nodes, variables }; + return { ...config, tags: { ...config?.tags }, nodes, variables }; } private loadFrontmatter(ast: Node) { diff --git a/projects/ngx-markdoc/src/lib/provide-markdoc-options.ts b/projects/ngx-markdoc/src/lib/provide-markdoc-options.ts new file mode 100644 index 0000000..ec6bcc2 --- /dev/null +++ b/projects/ngx-markdoc/src/lib/provide-markdoc-options.ts @@ -0,0 +1,29 @@ +import { + EnvironmentProviders, + InjectionToken, + makeEnvironmentProviders, +} from '@angular/core'; +import { Config } from '@markdoc/markdoc'; +import { figure, heading, image } from './extensions'; + +export const MARKDOC_CONFIG = new InjectionToken('MARKDOC_CONFIG'); + +export interface MarkdocOptions { + config?: Config; +} + +const defaultConfig: Config = { + nodes: { heading, image }, + tags: { figure }, +}; + +export function provideMarkdocOptions( + markdocOptions?: MarkdocOptions, +): EnvironmentProviders { + return makeEnvironmentProviders([ + { + provide: MARKDOC_CONFIG, + useValue: markdocOptions?.config ?? defaultConfig, + }, + ]); +} diff --git a/projects/ngx-markdoc/src/public-api.ts b/projects/ngx-markdoc/src/public-api.ts index 0615e8a..0231227 100644 --- a/projects/ngx-markdoc/src/public-api.ts +++ b/projects/ngx-markdoc/src/public-api.ts @@ -1,3 +1,3 @@ export * from './lib/extensions'; -export * from './lib/config'; export * from './lib/markdoc.component'; +export * from './lib/provide-markdoc-options'; diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 63cbbca..89671fb 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -4,6 +4,14 @@ import { provideRouter, withInMemoryScrolling } from '@angular/router'; import { routes } from './app.routes'; import { provideHttpClient, withFetch } from '@angular/common/http'; import { provideClientHydration } from '@angular/platform-browser'; +import { + figure, + heading, + image, + provideMarkdocOptions, +} from '@notiz/ngx-markdoc'; +import { Config, Node, Tag } from '@markdoc/markdoc'; +import { markdocExample } from './markdoc-example'; export const appConfig: ApplicationConfig = { providers: [ @@ -15,5 +23,11 @@ export const appConfig: ApplicationConfig = { ), provideHttpClient(withFetch()), provideClientHydration(), + provideMarkdocOptions({ + config: { + nodes: { heading, image }, + tags: { figure, markdocExample }, + }, + }), ], }; diff --git a/src/app/markdoc-example.ts b/src/app/markdoc-example.ts new file mode 100644 index 0000000..c48dabe --- /dev/null +++ b/src/app/markdoc-example.ts @@ -0,0 +1,13 @@ +import { Schema, Tag } from '@markdoc/markdoc'; + +export const markdocExample: Schema = { + render: 'pre', + attributes: {}, + transform(node, config) { + console.log('test'); + const attributes = node.transformAttributes(config); + const { content, language } = node.children[0].attributes; + + return new Tag('pre', { ...attributes, language }, [content]); + }, +}; diff --git a/src/app/pages/landing.component.ts b/src/app/pages/landing.component.ts index 3f8162f..b947bda 100644 --- a/src/app/pages/landing.component.ts +++ b/src/app/pages/landing.component.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { Component, OnInit } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { AsyncPipe } from '@angular/common'; import { Markdoc } from '@notiz/ngx-markdoc'; import { Prose } from '../components/prose.component'; @@ -7,6 +7,8 @@ import { Hero } from '../components/hero.component'; @Component({ selector: 'app-landing', + standalone: true, + imports: [Hero, Prose, Markdoc, AsyncPipe], template: ` @@ -34,13 +36,9 @@ import { Hero } from '../components/hero.component'; `, - styles: [], - standalone: true, - imports: [Hero, Prose, Markdoc, AsyncPipe], }) -export class LandingComponent implements OnInit { - example$ = this.http.get('assets/md/example.md', { responseType: 'text' }); - constructor(private http: HttpClient) {} +export class LandingComponent { + private http = inject(HttpClient); - ngOnInit(): void {} + example$ = this.http.get('assets/md/example.md', { responseType: 'text' }); } diff --git a/src/assets/md/docs/installation.md b/src/assets/md/docs/installation.md index 54280d2..0fedbee 100644 --- a/src/assets/md/docs/installation.md +++ b/src/assets/md/docs/installation.md @@ -39,9 +39,7 @@ import { Markdoc } from '@notiz/ngx-markdoc'; selector: 'app-docs', standalone: true, imports: [Markdoc], - template: ` - - `, + template: ` `, }) export class DocsComponent {} ``` @@ -63,3 +61,55 @@ export class DocsComponent {} ```html ``` + +## Options + +Use `provideMarkdocOptions` to optionally pass a Markdoc configuration options. + +```ts +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; +import { provideHttpClient } from '@angular/common/http'; + +import { provideMarkdocOptions } from '@notiz/ngx-markdoc'; +import { Config, Node, Tag } from '@markdoc/markdoc'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideRouter(routes), + provideHttpClient(), + provideMarkdocOptions({ + config: { + tags: { + figure: { + selfClosing: true, + attributes: { + src: { type: String, required: true }, + alt: { type: String, required: true }, + caption: { type: String, required: true }, + }, + transform: (node: Node, config: Config) => { + const { src, alt, caption } = node.transformAttributes(config); + const imageTag = new Tag('img', { src, alt }); + const captionTag = new Tag('figcaption', {}, [caption]); + return new Tag('figure', {}, [imageTag, captionTag]); + }, + }, + }, + }, + }), + ], +}; +``` + +Now you can use `{% figure %}` tag in your Markdown file + +{% markdocExample %} + +```md +{% figure src="https://images.unsplash.com/photo-1610296669228-602fa827fc1f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1675&q=80" alt="Pelican nebulae mosaic" caption="Pelican nebulae mosaic" /%} +``` + +{% /markdocExample %}