@ngx-docs-markdown-kit/parser-md-code-block
v0.1.0
Published
Extension de @ngx-docs-markdown-kit/parser-md que reconoce bloques de codigo enriquecidos y tarjetas de comando (fences code-block/card-code-block), y expone los componentes de Angular (CodeBlockComponent, CardCodeBlockComponent) que los renderizan.
Maintainers
Readme
@ngx-docs-markdown-kit/parser-md-code-block
Extension de @ngx-docs-markdown-kit/parser-md para bloques de codigo enriquecidos (boton de copiar, resaltado de sintaxis via highlight.js, temas configurables) y tarjetas de comando (comando + resultado esperado + nota). Mismo mecanismo que parser-md-image: un fence normal con un lang especial, reconocido via buildSegment(). Cero cambios al nucleo.
Un fence de Markdown SIN uno de los 2 nombres reconocidos (ver abajo) nunca se enriquece -- cae al render default de marked (sin boton de copiar, sin componente). Este paquete es deliberadamente selectivo: enriquecer todo fence por default sorprende a cualquiera que solo quiera un ```bash normal.
Sintaxis
code-block -- un bloque de codigo enriquecido
4 backticks envolviendo UN fence normal de 3 backticks con el codigo real (asi el fence interior conserva el resaltado de sintaxis nativo de tu editor):
`````code-block src/Program.cs
```csharp
Console.WriteLine("Hola");
```
`src/Program.cs` (segundo token del info string, opcional) es la ruta recomendada donde vive ese codigo -- se muestra como parte de la etiqueta del bloque. El lenguaje para el resaltado de sintaxis lo trae el fence INTERIOR (`csharp` en el ejemplo), no el exterior.
Produce el mismo segmento `type: 'code'` que un fence simple ya enriquecido en versiones anteriores de este paquete.
### `card-code-block` -- comando + resultado esperado + nota
4 backticks con campos nombrados `@campo`, cada uno en su propia linea, hasta el siguiente `@campo` o el cierre del fence:
````md
`````card-code-block
@description
Corre las pruebas del proyecto con **npm**.
@command bash
npm test
@result<Resultado esperado> text
Test Files 1 passed (1)
Tests 3 passed (3)
@note<Nota>
Si falla, revisa que hayas corrido `npm install` primero.
Campos soportados (todos opcionales salvo `@command`):
| Campo | Contenido | Etiqueta visible |
| --- | --- | --- |
| `@description` | Markdown libre (parrafos siguientes hasta el proximo `@campo`) | Sin etiqueta, solo el texto |
| `@command [lang]` | Un fence de codigo (el comando real) | Sin etiqueta -- el lenguaje se muestra en la barra del bloque |
| `@result[<Etiqueta>] [lang]` | Un fence de codigo (la salida esperada) | `<Etiqueta>` si se da, si no "Resultado esperado" |
| `@note[<Etiqueta>]` | Markdown libre | `<Etiqueta>` si se da, si no ninguna (solo el icono) |
`<Etiqueta>` es texto libre entre `<` y `>`, opcional en cualquier campo (aunque solo `@result`/`@note` la muestran visualmente hoy). `lang` (para `@command`/`@result`) tambien es opcional -- si se omite, se usa el lenguaje que ya trae el fence de codigo anidado de ese campo.
Produce un segmento `type: 'card-code-block'` (`CardCodeBlockSegment`).
### Un fence normal, sin enriquecer
```md
```bash
npm install
```
```
Sigue funcionando exactamente igual que en Markdown puro (`marked` lo renderiza por su cuenta) -- sin boton de copiar, sin temas, sin componente Angular de por medio.
## Uso
```ts
import { createParser } from '@ngx-docs-markdown-kit/parser-md';
import { codeBlockExtension } from '@ngx-docs-markdown-kit/parser-md-code-block';
const parser = createParser().use(codeBlockExtension());
```
Y en el template, junto a los demas tipos de segmento (ver el `@switch` en `doc-segments.html` de `create-ngx-docs-site`):
```html
@case ('code') {
<ndmk-code-block [code]="segment.code" [lang]="segment.lang" [path]="segment.path" />
}
@case ('card-code-block') {
<ndmk-card-code-block
[descriptionHtml]="segment.descriptionHtml"
[commandLang]="segment.commandLang"
[command]="segment.command"
[resultLabel]="segment.resultLabel"
[resultLang]="segment.resultLang"
[resultText]="segment.resultText"
[noteLabel]="segment.noteLabel"
[noteHtml]="segment.noteHtml"
/>
}
```
## Temas de codigo
Ver [`parser-md-code-block-themes`](../parser-md-code-block-themes) (extension opcional) para paletas de color adicionales por lenguaje, seleccionables en runtime via `CodeThemeService`.
<!-- parser-md-code-block:doc_start -->
<!-- parser-md-code-block:doc_header_start -->
# parser-md-code-block

  [](https://www.npmjs.com/package/@ngx-docs-markdown-kit/parser-md-code-block)
**Dependencias:**
- `@angular/animations`: ^22.1.0
- `@angular/cdk`: ^22.1.0
- `@angular/common`: ^22.1.0
- `@angular/compiler`: ^22.1.0
- `@angular/core`: ^22.1.0
- `@angular/forms`: ^22.1.0
- `@angular/material`: ^22.1.0
- `@angular/platform-browser`: ^22.1.0
- `@angular/platform-server`: ^22.1.0
- `@angular/router`: ^22.1.0
- `@angular/ssr`: ^22.1.3
- `@ngx-docs-markdown-kit/parser-md`: file:./vendor/parser-md/ngx-docs-markdown-kit-parser-md-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-seo`: file:./vendor/parser-md-seo/ngx-docs-markdown-kit-parser-md-seo-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-code-block`: file:./vendor/parser-md-code-block/ngx-docs-markdown-kit-parser-md-code-block-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-code-block-themes`: file:./vendor/parser-md-code-block-themes/ngx-docs-markdown-kit-parser-md-code-block-themes-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-image`: file:./vendor/parser-md-image/ngx-docs-markdown-kit-parser-md-image-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-card`: file:./vendor/parser-md-card/ngx-docs-markdown-kit-parser-md-card-0.1.0.tgz
- `@ngx-docs-markdown-kit/parser-md-converter`: file:./vendor/parser-md-converter/ngx-docs-markdown-kit-parser-md-converter-0.1.0.tgz
- `@ngx-docs-markdown-kit/ui`: file:./vendor/ui/ngx-docs-markdown-kit-ui-0.1.0.tgz
- `rxjs`: ~7.8.0
- `tslib`: ^2.3.0
<!-- parser-md-code-block:doc_header_end -->
<!-- parser-md-code-block:doc_body_start -->
## Apartados de parser-md-code-block
- [Docs](#docs-de-parser-md-code-block) -- Documentación de @ngx-docs-markdown-kit/parser-md-code-block -- bloques de código enriquecidos y tarjetas de comando.
- [Parser MD Code Block](#parser-md-code-block-de-parser-md-code-block) -- Bloques de codigo enriquecidos y tarjetas de comando para sitios @ngx-docs-markdown-kit.
---
## Docs de parser-md-code-block
[REGRESAR A APARTADOS DE parser-md-code-block](#apartados-de-parser-md-code-block)
---
## Índice Docs de parser-md-code-block
- [Primeros pasos](#primeros-pasos-de-parser-md-code-block) -- Instalación y wiring de la extensión.
- [Instalación](#instalación-de-parser-md-code-block) -- npm install + wiring de la extensión y sus componentes.
- [Uso](#uso-de-parser-md-code-block) -- La sintaxis de los 2 fences -- code-block y card-code-block.
- [Bloque de código enriquecido](#bloque-de-código-enriquecido-de-parser-md-code-block) -- El fence ```code-block -- botón de copiar, resaltado de sintaxis, ruta opcional.
- [Tarjeta de comando](#tarjeta-de-comando-de-parser-md-code-block) -- El fence ```card-code-block -- comando + resultado esperado + nota, con campos @nombre.
- [Ecosistema](#ecosistema-de-parser-md-code-block) -- Relación de @ngx-docs-markdown-kit/parser-md-code-block con el resto del kit -- qué depende de esta librería y por qué.
- [Relación con el ecosistema](#relación-con-el-ecosistema-de-parser-md-code-block) -- Quién depende de @ngx-docs-markdown-kit/parser-md-code-block y por qué, dentro del kit.
---
## Primeros pasos de parser-md-code-block
[< Índice Docs de parser-md-code-block](#índice-docs-de-parser-md-code-block)
---
### Instalación de parser-md-code-block
[< Primeros pasos de parser-md-code-block](#primeros-pasos-de-parser-md-code-block)
---
```bash
npm install @ngx-docs-markdown-kit/parser-md-code-block
```
#### Registrar la extensión
```typescript
import { createParser } from '@ngx-docs-markdown-kit/parser-md';
import { codeBlockExtension } from '@ngx-docs-markdown-kit/parser-md-code-block';
const parser = createParser().use(codeBlockExtension());
```
#### Renderizar los segmentos
En el template, junto a los demás tipos de segmento (mismo `@switch` que usa `create-ngx-docs-site`
en `doc-segments.html`):
```html
@case ('code') {
<ndmk-code-block [code]="segment.code" [lang]="segment.lang" [path]="segment.path" />
}
@case ('card-code-block') {
<ndmk-card-code-block
[descriptionHtml]="segment.descriptionHtml"
[commandLang]="segment.commandLang"
[command]="segment.command"
[resultLabel]="segment.resultLabel"
[resultLang]="segment.resultLang"
[resultText]="segment.resultText"
[noteLabel]="segment.noteLabel"
[noteHtml]="segment.noteHtml"
/>
}
```
Ver [Bloque de código](#bloque-de-código-enriquecido-de-parser-md-code-block) y [Tarjeta de comando](#tarjeta-de-comando-de-parser-md-code-block)
para la sintaxis completa de cada fence.
#### Temas de código (opcional)
[`parser-md-code-block-themes`](https://www.npmjs.com/package/@ngx-docs-markdown-kit/parser-md-code-block-themes)
agrega paletas de color adicionales por lenguaje, seleccionables en runtime.
## Uso de parser-md-code-block
[< Índice Docs de parser-md-code-block](#índice-docs-de-parser-md-code-block)
---
### Bloque de código enriquecido de parser-md-code-block
[< Uso de parser-md-code-block](#uso-de-parser-md-code-block)
---
Un fence de Markdown SIN el `lang` reconocido (` ```bash ` normal, 3 backticks) nunca se enriquece
-- cae al render default de `marked` (sin botón de copiar, sin componente). Deliberadamente
selectivo: enriquecer TODO fence por default sorprende a cualquiera que solo quiera un bloque
normal. Antes de esta librería el comportamiento era el inverso (cualquier fence simple se
enriquecía automático) -- se invirtió a propósito.
` ```code-block ` (4 backticks) envuelve UN fence normal de 3 backticks con el código real -- así
el fence interior conserva el resaltado de sintaxis nativo de tu editor mientras lo escribís:
````code-block src/Program.cs
```csharp
Console.WriteLine("Hola");
```src/Program.cs (segundo token del info string, opcional) es la ruta recomendada donde vive ese
código -- se muestra como parte de la etiqueta del bloque. El lenguaje para el resaltado de sintaxis
lo trae el fence INTERIOR (csharp en el ejemplo), no el exterior.
Produce un segmento { type: 'code', code, lang, path } (CodeSegment), renderizado por
ndmk-code-block.
Por qué 4 backticks en el fence exterior
Un fence de Markdown de N backticks solo lo cierra OTRO fence de N backticks o más. Envolver con 4
backticks deja que el contenido traiga su propio fence de 3 backticks (el código real) sin que este
último cierre el exterior antes de tiempo -- el fence interior se lexea de nuevo
(ctx.lex(token.text), en code-block-extension.ts) buscando el primer token de tipo code
dentro, y de ahí salen code/lang; path sale del resto del info string exterior.
Cómo funciona por dentro
CodeBlockComponent usa highlight.js/lib/core -- el build SIN gramáticas incluidas, para que el
bundle final no cargue lenguajes que la app anfitriona nunca usa. Cada lenguaje se registra en
hljs de forma perezosa, la primera vez que se necesita, y el registro se dedupea por FUNCIÓN real
(registeredLanguages.get(lang) === register), no por nombre de lenguaje -- si dos
provideCodeBlockLanguages() distintos (ej. un módulo lazy con su propio override) declaran el
mismo lang con una función register DISTINTA, no queda descartada en silencio para siempre: se
vuelve a registrar (hljs.registerLanguage soporta sobreescribir, gana la última llamada). Solo se
saltea el registro cuando es EXACTAMENTE la misma función ya registrada -- el caso común, evita
trabajo repetido en cada render.
El componente usa ViewEncapsulation.None -- el HTML que produce hljs.highlight() (spans
hljs-keyword/hljs-string/etc.) llega vía [innerHTML], y los nodos insertados así no reciben el
atributo de scoping que Angular sí aplica al resto del template. Sin ViewEncapsulation.None, las
reglas de color del .scss del componente no alcanzarían ese HTML crudo.
El contenedor lleva la clase not-prose (convención de Tailwind Typography): si el sitio anfitrión
envuelve el markdown renderizado en .prose, sus selectores :where(pre)/:where(code) igual
alcanzarían este bloque por nombre de etiqueta -- pintando un segundo fondo encimado sobre el que ya
define .code-block, y agregando comillas invertidas literales antes/después del código.
not-prose es el escape hatch oficial de ese plugin para "este subárbol ya trae su propio estilo,
no lo toques"; es inofensiva si el sitio no usa Tailwind Typography (una clase sin ningún selector
que la use).
C#, un caso especial
hljs es un resaltador por expresiones regulares, no un compilador real -- su gramática de C# solo
cubre keywords/strings/comments/numbers/tipos primitivos. enhance-csharp.ts es una segunda pasada
que corre SOLO para lang="csharp", sobre el HTML ya resaltado, y solo toca texto que hljs dejó
sin envolver en ningún <span>: colorea PascalCase como tipo (convención de C# para
clases/interfaces/records/DTOs) e identificador( como llamada a método. Vive en su propio archivo
-- no dentro de CodeBlockComponent -- porque tiene una razón de cambio distinta (ajustar la
detección de tipos/métodos de C#) a la del componente (layout/copiado/tema del bloque).
Temas de resaltado por lenguaje
El data-theme del contenedor (modTheme(), resuelto vía CodeThemeService.themeFor(lang)) forma
parte del selector CSS del tema junto con data-lang:
.code-block[data-theme='<id>'][data-lang='<lang>']. El id del tema entra al selector -- no solo un
booleano genérico -- porque CodeThemeService nunca descarga el <link> de un tema anterior al
elegir uno nuevo (ver loadStylesheet()): pueden quedar 2 o más hojas de estilo de temas distintas
cargadas al mismo tiempo (ej. bash en "A11y Dark", csharp en "An Old Hope"). Sin el id de tema en
el selector, esas hojas competirían por pintar el mismo bloque. Esta librería trae la infraestructura
completa (CodeThemeService, CODE_THEME_CATALOG) pero el catálogo vive vacío por default -- lo
llena parser-md-code-block-themes, ver
Ecosistema.
Tarjeta de comando de parser-md-code-block
```card-code-block (4 backticks) con campos nombrados @campo, cada uno en su propia línea,
hasta el siguiente @campo o el cierre del fence:
@description
Corre las pruebas del proyecto con **npm**.
@command bash
npm test
@result<Resultado esperado> text
Test Files 1 passed (1)
Tests 3 passed (3)
@note<Nota>
Si falla, revisá que hayas corrido `npm install` primero.Campos soportados
Todos opcionales salvo @command:
| Campo | Contenido | Etiqueta visible |
| --- | --- | --- |
| @description | Markdown libre (párrafos hasta el próximo @campo) | Sin etiqueta, solo el texto |
| @command [lang] | Un fence de código (el comando real) | Sin etiqueta -- el lenguaje se muestra en la barra del bloque |
| @result[<Etiqueta>] [lang] | Un fence de código (la salida esperada) | <Etiqueta> si se da, si no "Resultado esperado" |
| @note[<Etiqueta>] | Markdown libre | <Etiqueta> si se da, si no ninguna (solo el ícono) |
<Etiqueta> es texto libre entre < y >, opcional en cualquier campo (aunque solo
@result/@note la muestran visualmente hoy). lang (para @command/@result) también es
opcional -- si se omite, se usa el lenguaje que ya trae el fence de código anidado de ese campo.
Produce un segmento { type: 'card-code-block', descriptionHtml, commandLang, command, resultLabel,
resultLang, resultText, noteLabel, noteHtml } (CardCodeBlockSegment), renderizado por
ndmk-card-code-block.
Cómo funciona por dentro
parseCardCodeBlock() (en code-block-extension.ts) NO usa ctx.lex() para encontrar los campos
-- parsea el cuerpo del fence LÍNEA POR LÍNEA, buscando el patrón @campo[<Etiqueta>] [lang] al
principio de cada línea. Es deliberado: si usara el lexer de Markdown, @command bash seguido en la
línea siguiente por npm install (sin línea en blanco entre ambas) sería UN solo párrafo para
marked -- nada en el árbol de tokens distingue ahí "esto es la etiqueta del campo" de "esto es su
contenido". Partiendo por líneas con una regex explícita, cada sección sabe de antemano a qué campo
pertenece antes de decidir qué hacer con su cuerpo.
Una vez separadas las secciones, cada campo elige su tratamiento: description/note se vuelven a
lexear como Markdown (ctx.render(ctx.lex(section.body))) -- soportan negrita, enlaces, etc.
command/result se guardan RAW, sin pasar por el lexer -- así una línea como npm install nunca
se interpreta como prosa ni se le escapan caracteres que un comando real podría necesitar.
CardCodeBlockComponent no reimplementa el resaltado ni el botón de copiar -- compone el layout de
la tarjeta reusando 2 instancias internas de ndmk-code-block (ver
Bloque de código): una para @command, otra para @result con
embedded="true", que suprime la etiqueta flotante y el tratamiento visual de "salida de ejemplo"
propios de ese componente (la tarjeta ya trae los suyos). El HTML de description/note llega
sanitizado con DomSanitizer.bypassSecurityTrustHtml() -- válido acá porque el HTML viene de
contenido Markdown ya procesado por parser-md, nunca de un usuario final directo, el mismo
criterio que aplicaría cualquier renderer de Markdown a HTML confiable.
Por qué existe además de code-block
card-code-block no es una variante visual de code-block -- es un formato distinto para un caso
de uso distinto: documentar un PASO ejecutable (qué hace, cómo se corre, qué esperar, qué hacer si
falla) como una unidad, en vez de un bloque de código suelto.
Ecosistema de parser-md-code-block
< Índice Docs de parser-md-code-block
Relación con el ecosistema de parser-md-code-block
< Ecosistema de parser-md-code-block
@ngx-docs-markdown-kit/parser-md-code-block es una EXTENSIÓN de
@ngx-docs-markdown-kit/parser-md -- lo trae como peer
dependency y se registra con .use(codeBlockExtension()), sin tocar ni forkear el núcleo. parser-md
no sabe nada de code-block/card-code-block; simplemente expone el punto de extensión
(buildSegment) que esta librería implementa.
Quién depende de esta librería
@ngx-docs-markdown-kit/parser-md-code-block-themesla trae como peer dependency real -- es un paquete de PALETAS DE COLOR (generadas desde los temas dehighlight.js) para el resaltado de sintaxis que ya vive acá. Sinparser-md-code-blockinstalado,parser-md-code-block-themesno tiene nada que llenar:CODE_THEME_CATALOG(vercode-theme-catalog.tsen esta librería) está vacío por default a propósito, y ese otro paquete existe únicamente para poblarlo. La dirección de la dependencia es esta, no al revés -- esta librería nunca importa nada deparser-md-code-block-themes.create-ngx-docs-site(el CLI generador) la trae siempre en la plantilla de cualquier sitio nuevo, junto conparser-md, para que los fences```code-blocky```card-code-blockfuncionen de entrada en cualquier sitio generado.
Qué NO depende de esta librería
El selector de tema por lenguaje ("Mod") que expone parser-md-code-block-themes no reimplementa
un <select> propio -- usa SearchableSelectComponent de
@ngx-docs-markdown-kit/ui. Ese componente vivió en algún momento
DENTRO de esta librería, pero no tenía nada que ver con parsear Markdown ni resaltar código, así que
se extrajo a ui (librería sin ninguna dependencia de parser-md) -- ver la sección "Por qué
existe como librería separada" de
su página de uso para el detalle completo.
parser-md-code-block en sí no depende de ui ni de ningún componente genérico: solo expone
CodeBlockComponent/CardCodeBlockComponent y la infraestructura de temas (CodeThemeService,
CODE_THEME_CATALOG, CODE_BLOCK_LANGUAGES) que otros paquetes consumen.
Por qué el catálogo de temas vive vacío acá
CODE_THEME_CATALOG (InjectionToken<readonly CodeThemeCatalogEntry[]>) y CodeThemeService
están completos y funcionales en esta librería sin instalar nada más -- el selector de "Mod" queda
disponible, simplemente sin ningún tema para elegir más allá de "Default". La razón de tenerlos acá
y no en parser-md-code-block-themes es que son INFRAESTRUCTURA (persistencia en localStorage,
carga de <link> de CSS, resolución de tema por lenguaje) genérica, reusable por cualquier catálogo
de temas futuro -- no algo específico de los ~80 temas concretos que trae ese paquete. Instalar
parser-md-code-block-themes es estrictamente opcional: sin él, todo sigue funcionando, solo que
sin paletas alternativas.
Por qué code-block-languages.ts es un archivo de datos puro
CODE_BLOCK_LANGUAGES (los 8 lenguajes registrados por default: csharp, bash, powershell,
json, typescript, javascript, css, scss) vive en un archivo sin ningún import relativo a
propósito -- scripts/generate-code-themes.mjs, en la raíz del monorepo, lo importa DIRECTO con el
soporte nativo de TypeScript de Node (sin bundler) para saber para qué lenguajes generar CSS al
armar el catálogo de parser-md-code-block-themes. Ese modo de carga no resuelve una cadena de
imports locales como sí lo hace el build real de Angular -- por eso el enhancer de C#
(enhance-csharp.ts, la segunda pasada que colorea PascalCase/llamadas a método que la gramática
regex de hljs no cubre) NO se referencia desde ese archivo de datos: vive wireado directo en
CodeBlockComponent, que ese script nunca carga.
Parser MD Code Block de parser-md-code-block
REGRESAR A APARTADOS DE parser-md-code-block
parser-md-code-block extiende parser-md con 2 fences enriquecidos --
```code-block y ```card-code-block -- y los componentes de Angular
(CodeBlockComponent, CardCodeBlockComponent) que los renderizan con resaltado de sintaxis real.
Por que un fence aparte del Markdown normal
Un fence simple (3 backticks) nunca se enriquece a proposito -- solo el fence de 4 backticks activa
el resaltado y las tarjetas de comando. card-code-block parsea sus campos (@description/
@command/@result/@note) linea por linea en vez de con el lexer de Markdown, porque un
@command seguido de codigo sin linea en blanco es indistinguible para marked.
Que trae de base
Resaltado de sintaxis real (no aproximado) para los lenguajes soportados, mas la infraestructura de
temas que consume parser-md-code-block-themes como extension opcional -- si no esta instalado, el
bloque de codigo sigue funcionando con su tema por default.
