Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(markdoc): allowIndentation integration option #8802

Merged
merged 2 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hip-rockets-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/markdoc': minor
---

Added ignoreIndentation as a markdoc integration option to enable better readability of source code.
34 changes: 34 additions & 0 deletions packages/integrations/markdoc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,40 @@ To achieve a more Markdown-like experience, where HTML elements can be included
> **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.

### `ignoreIndentation`

By default, any content that is indented by four spaces is treated as a code block. Unfortunately, this behavior makes it difficult to use arbitrary levels of indentation to improve the readability of documents with complex structure.

When using nested tags in Markdoc, it can be helpful to indent the content inside of tags so that the level of depth is clear. To support arbitrary indentation, we have to disable the indent-based code blocks and modify several other markdown-it parsing rules that account for indent-based code blocks. These changes can be applied by enabling the ignoreIndentation option.

```diff lang="js" "ignoreIndentation: true"
// astro.config.mjs
import { defineConfig } from 'astro/config';
import markdoc from '@astrojs/markdoc';

export default defineConfig({
// ...
+ integrations: [markdoc({ ignoreIndentation: true })],
// ^^^^^^^^^^^^^^^^^^^^^^^
});
```

```md
# Welcome to Markdoc with indented tags 👋
# Note: Can use either spaces or tabs for indentation

{% custom-tag %}
{% custom-tag %}
### Tags can be indented for better readability

{% another-custom-tag %}
This is easier to follow when there is a lot of nesting
{% /another-custom-tag %}

{% /custom-tag %}
{% /custom-tag %}
```

## 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.
Expand Down
1 change: 1 addition & 0 deletions packages/integrations/markdoc/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface MarkdocIntegrationOptions {
allowHTML?: boolean;
ignoreIndentation?: boolean;
}
6 changes: 5 additions & 1 deletion packages/integrations/markdoc/src/tokenizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ export function getMarkdocTokenizer(options: MarkdocIntegrationOptions | undefin
};

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;
// enable HTML token detection in markdown-it
tokenizerOptions.html = true;
}
if (options?.ignoreIndentation) {
// allow indentation so nested Markdoc tags can be formatted for better readability
tokenizerOptions.allowIndentation = true;
}

_cachedMarkdocTokenizers[key] = new Markdoc.Tokenizer(tokenizerOptions);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineConfig } from 'astro/config';
import markdoc from '@astrojs/markdoc';

// https://astro.build/config
export default defineConfig({
integrations: [markdoc({ ignoreIndentation: true })],
});
Original file line number Diff line number Diff line change
@@ -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',
},
},
},
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@test/markdoc-render-with-indented-components",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/markdoc": "workspace:*",
"astro": "workspace:*"
}
}
Original file line number Diff line number Diff line change
@@ -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} />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<marquee data-custom-marquee {...Astro.props}><slot /></marquee>
Original file line number Diff line number Diff line change
@@ -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;
```
AndyClifford marked this conversation as resolved.
Show resolved Hide resolved
{% /marquee-element %}

Original file line number Diff line number Diff line change
@@ -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>
42 changes: 42 additions & 0 deletions packages/integrations/markdoc/test/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ describe('Markdoc - render', () => {
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 () => {
const fixture = await getFixture('render-null');
const server = await fixture.startDevServer();
Expand Down Expand Up @@ -87,6 +100,15 @@ describe('Markdoc - render', () => {
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 () => {
const fixture = await getFixture('render-null');
await fixture.build();
Expand Down Expand Up @@ -125,6 +147,26 @@ function renderComponentsChecks(html) {
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 h3
const h3 = document.querySelector('h3');
expect(h3.textContent).to.equal('I am an h3!');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you know if this is indented? Should we check for spaces/tabs somehow?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think only the source code becomes indented

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this doesn't answer my question. The comment in the test says "we check if text is indented" , but still I can't understand when a text should not be indented. Sorry for those dumb questions , I have little context about markdoc

Copy link
Contributor Author

@AndyClifford AndyClifford Oct 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ematipico No worries ! I can see how these comments are a bit confusing - I've updated them.

The allowIndentation option is purely to enable better readability of source code and it will not affect the rendered HTML. Check out Markdoc's official documentation on indentation https://markdoc.dev/docs/faq#indentation

  • allowIndentation = false:

    {% foo %}
    {% bar %}
    This is content inside of nested tags
    {% /bar %}
    {% /foo %}
    
  • allowIndentation = true (easier to read, level of depth is clear):

    {% foo %}
      {% bar %}
        This is content inside of nested tags
      {% /bar %}
    {% /foo %}
    

The rendered HTML is exactly the same in both examples. The second example will fail to render if the allowIndentation option is not enabled.

I hope this helps!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think ignoreIndentation: true might be better here?

It makes sense for it to be called allowIndentation in markdoc's tokenizer options. It might be better to hint at its overall effect with the name of the top-level option.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah ignoreIndentation sounds good !


// Renders 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 */
function renderConfigChecks(html) {
const { document } = parseHTML(html);
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading