Markdoc Integration allowIndentation option
Added allowIndentation to the markdoc integration options. This allows indentation of markdoc tags for better readability without allowing raw HTML in Markdoc files.
This commit is contained in:
parent
a8b979ef40
commit
c5f5bf4dc0
13 changed files with 200 additions and 1 deletions
5
.changeset/hip-rockets-glow.md
Normal file
5
.changeset/hip-rockets-glow.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'@astrojs/markdoc': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added allowIndentation as a markdoc integration option to enable better readability
|
|
@ -458,6 +458,39 @@ To achieve a more Markdown-like experience, where HTML elements can be included
|
||||||
> **Warning**
|
> **Warning**
|
||||||
> When `allowHTML` is enabled, HTML markup inside Markdoc documents will be rendered as actual HTML elements (including `<script>`), making attack vectors like XSS possible. Ensure that any HTML markup comes from trusted sources.
|
> When `allowHTML` is enabled, HTML markup inside Markdoc documents will be rendered as actual HTML elements (including `<script>`), making attack vectors like XSS possible. Ensure that any HTML markup comes from trusted sources.
|
||||||
|
|
||||||
|
### `allowIndentation`
|
||||||
|
|
||||||
|
Enables indentation of Markdoc tags for better readability.
|
||||||
|
|
||||||
|
The allowIndentation option is experimental and is not enabled by default. When the option is enabled, it is not possible to use indent-based code blocks.
|
||||||
|
|
||||||
|
```diff lang="js" "allowIndentation: true"
|
||||||
|
// astro.config.mjs
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import markdoc from '@astrojs/markdoc';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// ...
|
||||||
|
+ integrations: [markdoc({ allowIndentation: true })],
|
||||||
|
// ^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```md
|
||||||
|
# Welcome to Markdoc with indented tags 👋
|
||||||
|
|
||||||
|
{% custom-tag %}
|
||||||
|
{% custom-tag %}
|
||||||
|
### Tags can be indented for better readability
|
||||||
|
|
||||||
|
{% another-custom-tag %}
|
||||||
|
This is easier to read when there is a lot of nesting
|
||||||
|
{% /another-custom-tag %}
|
||||||
|
|
||||||
|
{% /custom-tag %}
|
||||||
|
{% /custom-tag %}
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
- The [Astro Markdoc starter template](https://github.com/withastro/astro/tree/latest/examples/with-markdoc) shows how to use Markdoc files in your Astro project.
|
- The [Astro Markdoc starter template](https://github.com/withastro/astro/tree/latest/examples/with-markdoc) shows how to use Markdoc files in your Astro project.
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
export interface MarkdocIntegrationOptions {
|
export interface MarkdocIntegrationOptions {
|
||||||
allowHTML?: boolean;
|
allowHTML?: boolean;
|
||||||
|
allowIndentation?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,15 @@ export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefin
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options?.allowHTML) {
|
if (options?.allowHTML) {
|
||||||
// we want to allow indentation for Markdoc tags that are interleaved inside HTML block elements
|
// allow indentation for Markdoc tags that are interleaved inside HTML block elements
|
||||||
tokenizerOptions.allowIndentation = true;
|
tokenizerOptions.allowIndentation = true;
|
||||||
// enable HTML token detection in markdown-it
|
// enable HTML token detection in markdown-it
|
||||||
tokenizerOptions.html = true;
|
tokenizerOptions.html = true;
|
||||||
}
|
}
|
||||||
|
if (options?.allowIndentation) {
|
||||||
|
// allow indentation so nested Markdoc tags can be formatted for better readability
|
||||||
|
tokenizerOptions.allowIndentation = true;
|
||||||
|
}
|
||||||
|
|
||||||
_cachedMarkdocTokenizers[key] = new Markdoc.Tokenizer(tokenizerOptions);
|
_cachedMarkdocTokenizers[key] = new Markdoc.Tokenizer(tokenizerOptions);
|
||||||
}
|
}
|
||||||
|
|
7
packages/integrations/markdoc/test/fixtures/render-with-indented-components/astro.config.mjs
vendored
Normal file
7
packages/integrations/markdoc/test/fixtures/render-with-indented-components/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
import markdoc from '@astrojs/markdoc';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({
|
||||||
|
integrations: [markdoc({ allowIndentation: true })],
|
||||||
|
});
|
26
packages/integrations/markdoc/test/fixtures/render-with-indented-components/markdoc.config.ts
vendored
Normal file
26
packages/integrations/markdoc/test/fixtures/render-with-indented-components/markdoc.config.ts
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { defineMarkdocConfig, component } from '@astrojs/markdoc/config';
|
||||||
|
|
||||||
|
export default defineMarkdocConfig({
|
||||||
|
nodes: {
|
||||||
|
fence: {
|
||||||
|
render: component('./src/components/Code.astro'),
|
||||||
|
attributes: {
|
||||||
|
language: { type: String },
|
||||||
|
content: { type: String },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
'marquee-element': {
|
||||||
|
render: component('./src/components/CustomMarquee.astro'),
|
||||||
|
attributes: {
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'left',
|
||||||
|
matches: ['left', 'right', 'up', 'down'],
|
||||||
|
errorLevel: 'critical',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
12
packages/integrations/markdoc/test/fixtures/render-with-indented-components/package.json
vendored
Normal file
12
packages/integrations/markdoc/test/fixtures/render-with-indented-components/package.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "@test/markdoc-render-with-indented-components",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/markdoc": "workspace:*",
|
||||||
|
"astro": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shiki": "^0.14.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
import { Code } from 'astro/components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
content: string;
|
||||||
|
language: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { content, language } = Astro.props as Props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Code lang={language} code={content} />
|
|
@ -0,0 +1 @@
|
||||||
|
<marquee data-custom-marquee {...Astro.props}><slot /></marquee>
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
title: Post with indented components
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post with indented components
|
||||||
|
|
||||||
|
This uses a custom marquee component with a shortcode:
|
||||||
|
|
||||||
|
{% marquee-element direction="right" %}
|
||||||
|
I'm a marquee too!
|
||||||
|
|
||||||
|
{% marquee-element direction="right" %}
|
||||||
|
I'm an indented marquee!
|
||||||
|
|
||||||
|
### I am an h3!
|
||||||
|
{% /marquee-element %}
|
||||||
|
|
||||||
|
And a nested code block:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const isRenderedWithShiki = true;
|
||||||
|
```
|
||||||
|
{% /marquee-element %}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
import { getEntryBySlug } from "astro:content";
|
||||||
|
|
||||||
|
const post = await getEntryBySlug('blog', 'with-indented-components');
|
||||||
|
const { Content } = await post.render();
|
||||||
|
---
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Content</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Content />
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -46,6 +46,19 @@ describe('Markdoc - render', () => {
|
||||||
await server.stop();
|
await server.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders content - with indented components', async () => {
|
||||||
|
const fixture = await getFixture('render-with-indented-components');
|
||||||
|
const server = await fixture.startDevServer();
|
||||||
|
|
||||||
|
const res = await fixture.fetch('/');
|
||||||
|
const html = await res.text();
|
||||||
|
|
||||||
|
renderIndentedComponentsChecks(html);
|
||||||
|
|
||||||
|
await server.stop();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it('renders content - with `render: null` in document', async () => {
|
it('renders content - with `render: null` in document', async () => {
|
||||||
const fixture = await getFixture('render-null');
|
const fixture = await getFixture('render-null');
|
||||||
const server = await fixture.startDevServer();
|
const server = await fixture.startDevServer();
|
||||||
|
@ -87,6 +100,15 @@ describe('Markdoc - render', () => {
|
||||||
renderComponentsChecks(html);
|
renderComponentsChecks(html);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders content - with indented components', async () => {
|
||||||
|
const fixture = await getFixture('render-with-indented-components');
|
||||||
|
await fixture.build();
|
||||||
|
|
||||||
|
const html = await fixture.readFile('/index.html');
|
||||||
|
|
||||||
|
renderIndentedComponentsChecks(html);
|
||||||
|
});
|
||||||
|
|
||||||
it('renders content - with `render: null` in document', async () => {
|
it('renders content - with `render: null` in document', async () => {
|
||||||
const fixture = await getFixture('render-null');
|
const fixture = await getFixture('render-null');
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
|
@ -125,6 +147,26 @@ function renderComponentsChecks(html) {
|
||||||
expect(pre.className).to.equal('astro-code github-dark');
|
expect(pre.className).to.equal('astro-code github-dark');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string} html */
|
||||||
|
function renderIndentedComponentsChecks(html) {
|
||||||
|
const { document } = parseHTML(html);
|
||||||
|
const h2 = document.querySelector('h2');
|
||||||
|
expect(h2.textContent).to.equal('Post with indented components');
|
||||||
|
|
||||||
|
// Renders custom shortcode components
|
||||||
|
const marquees = document.querySelectorAll('marquee');
|
||||||
|
expect(marquees.length).to.equal(2);
|
||||||
|
|
||||||
|
// Renders indented h3
|
||||||
|
const h3 = document.querySelector('h3');
|
||||||
|
expect(h3.textContent).to.equal('I am an h3!');
|
||||||
|
|
||||||
|
// Renders indented Astro Code component
|
||||||
|
const pre = document.querySelector('pre');
|
||||||
|
expect(pre).to.not.be.null;
|
||||||
|
expect(pre.className).to.equal('astro-code github-dark');
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {string} html */
|
/** @param {string} html */
|
||||||
function renderConfigChecks(html) {
|
function renderConfigChecks(html) {
|
||||||
const { document } = parseHTML(html);
|
const { document } = parseHTML(html);
|
||||||
|
|
|
@ -4019,6 +4019,19 @@ importers:
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../../../astro
|
version: link:../../../../../astro
|
||||||
|
|
||||||
|
packages/integrations/markdoc/test/fixtures/render-with-indented-components:
|
||||||
|
dependencies:
|
||||||
|
'@astrojs/markdoc':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../..
|
||||||
|
astro:
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../../../../astro
|
||||||
|
devDependencies:
|
||||||
|
shiki:
|
||||||
|
specifier: ^0.14.3
|
||||||
|
version: 0.14.3
|
||||||
|
|
||||||
packages/integrations/markdoc/test/fixtures/variables:
|
packages/integrations/markdoc/test/fixtures/variables:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/markdoc':
|
'@astrojs/markdoc':
|
||||||
|
|
Loading…
Reference in a new issue