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

Can I add blocks or variables to markdown content to render into layout sections #685

Closed
KyleMit opened this issue Sep 6, 2019 · 10 comments

Comments

@KyleMit
Copy link

KyleMit commented Sep 6, 2019

My goal is to do something like this (similar to Vue's Single File Components (SFC)):

Template Sections

I've tried two different approaches for getting there so far (but both fall short)

A) Using set

Setting a styleSection variable inside my markdown file like this:

---
title
---

## My Content 

{% set styleSection %}
<style> body { background: red; } </style>
{% endset %}

And then outputting the variable in the layout like this:

<html lang="en">
<head>
    {{ styleSection | cssmin | safe }}
</head>
<body >
    {{ content | safe }}
</body>
</html>

However, the variable seems local to the markdown scope, so it's not available to the layout page

B) Using block

So the layout template looks like this:

<html lang="en">
<head>
    {% block styleSection %}
    <!-- placeholder -->
    {% endblock %}
</head>
<body >
    {{ content | safe }}
</body>
</html>

And the markdown file attempts to override that block placeholder

---
title
---

## My Content 

{% block styleSection %}
<style> body { background: red; } </style>
{% endblock %}

However, I believe this only works with template inheritance, which I'm not sure how to specify within a markdown file

Any ideas / suggested approaches to how to do this?

The easy workaround is too just keep the <style> and <script> tags inline. I have HTML enabled inside of markdown, and it doesn't really matter to the dom where those things show up, and . I'm just trying to prevent FOUC by rendering styles early and deferring optional JS to the bottom to make sure it loads last (any potentially has access to other scripts that have run if need be)

Other Issues

Looks like this has been brought up in these issues as well

@mirisuzanne
Copy link

As far as I know you can't do it in a markdown-parsed file, but you could have the file parse as nunjucks, and then add a "paired shortcode" for markdown rendering:

  eleventyConfig.addPairedShortcode("markdown", (content, inline = null) => {
    return inline
      ? markdownIt.renderInline(content)
      : markdownIt.render(content);
  });
{% markdown %}
# I can write *markdown* in this paired shortcode…
{% endmarkdown %}

@KyleMit
Copy link
Author

KyleMit commented Sep 10, 2019

@mirisuzanne , thanks, that probably looks like the best strategy.

For whatever reason, I have an easier time picturing a little bit of html in my markdown than a adding some markdown to my html, especially for content heavy pages. Plus, I really like the authoring support I get from having the .md extension in terms of syntax highlighting, native readability, shortcuts, and static previews.

Thank you so much for the suggestion - If I really have a strong perf use case for ordering markup, I'll probably need to go that route

@mirisuzanne
Copy link

Yeah, I’m with you. The other solution I’ve used is to put more things into my YAML - both html and markdown - so I can retain the MD highlighting on the page, and render those yaml blocks wherever I need in the template. Looking at your diagram above, that’s probably where I would put extra CSS and JS.

@KyleMit
Copy link
Author

KyleMit commented Sep 10, 2019

Hmm... yeah, in the scripts case, I'm actually authoring it externally and just pulling in the <script src="..."> for that page, so adding that to the yaml wouldn't be too bad:

---
title: How To Cat
stylePath: /_includes/scripts/posts/how-to-cat.css
scriptPath: /_includes/scripts/posts/how-to-cat.js
---

Then it would be pretty easy to maintain a nice authoring experience for JS/CSS and pull in into the rendered page wherever you want

@Ryuno-Ki
Copy link
Contributor

Ryuno-Ki commented Oct 8, 2019

Your last solution is actually quite close to what I use. But instead of a single value I have an Array there.
This way I can declare a list of dependencies.
In the template I check whether the key is set and if so iterate over its content.
You could define another key for inlined dependency if you want to modify the critical path.

@bennypowers
Copy link

I'm working on some API docs layouts based on the custom-elements-manifest spec, and it would be lovely to be able to hook into the layout phase. So the user would have a markdown page:

---
layout: api
title: Super Card Element
package: 'super-card' # npm name, 11ty can access a copy of the manifest at data/super-card/custom-elements.json is stored 
module: './super-card.js' # path key, also referenced in package.json exports and in custom-elements.json
---

Super cards are the best cards

Meanwhile, the nunjucks api layout gets the manifest from the data cascade, finds the relevant module, and outputs a whole DOM tree of API docs based on the content, e.g. what exports are available, what the custom element class shape is, etc. For a comprehensive manifest, you end up with a docs page with "exports", "properties", "methods", "slots", etc.

What I've accomplished so far is that 11ty renders the title and "Super cards are the best cars", then renders the content from api.njk

Since nunjucks is awesome, and 11ty is awesome, all of ☝️ is awesome, but our documentation author might want to have access to the layout from markdown:

  • to pick up page headers that nunjucks renders, in order to build the navigation sidebar without hacks
  • to insert content in between or modify sections (e.g. "put this block of md content in between the "exports" and "properties" sections of the layout output, change the "methods" heading to "functions")
  • etc.

I'm relying at the moment on some of those alluded-to hacks to build the nav from the njk layout's headings by directly hooking the manifest data into the nav filter, but I haven't found a satisfactory way to hook into the content. As a markdown author, I'd prefer not to author my markdown in yaml front matter, for the above-mentioned reasons.

Hope something like this lands soon :D

@danburzo
Copy link
Contributor

Supporting this with the Nunjucks API (more so as the project seems dormant) doesn't seem possible to me, but I would love to be wrong! To my mind, a solution will probably entail a degree of monkey-patching.

If anyone wants to explore this avenue, you can transplant blocks from a child Template (ie. the Markdown source parsed with Nunjucks) to a parent Template (ie. the layout template):

const njk = require('nunjucks');

const parent = `
{% block dummy %}
initial
{% endblock %}
`;

const child = `
{% block dummy %}
overwritten
{% endblock %}
`;

const parentTemplate = new njk.Template(parent, null, null, true);
const childTemplate = new njk.Template(child, null, null, true);
for (let name in childTemplate.blocks) {
	parentTemplate.blocks[name] = childTemplate.blocks[name];
}

console.log(parentTemplate.render());
// => "overwritten"

super() within the child block is not supported, since Nunjuck's Context object is not exported, so the way it loads blocks can't be patched.

Hope this inspires someone to look further into it, this would be a pretty impressive feature to land in Eleventy!

@danburzo
Copy link
Contributor

danburzo commented Feb 3, 2023

Okay, the newly released @11ty/eleventy-plugin-bundle made me realize slotted content can be achieved much simpler with a pair of shortcodes:

module.exports = function(config) {
	const slots = {};

	config.addPairedShortcode('slotted', function(content, name) {
		const key = this.page.inputPath;
		if (!slots[key]) {
			slots[key] = {};
		}
		slots[key][name] = content;
		return '';
	});

	config.addShortcode('slot', function(name) {
		return slots[this.page.inputPath]?.[name];
	});
}

Then, in your markdown:

---
title: 'My title'
---

My content.

{% slotted 'custom_style' %}
<style type='text/css'>
body {
  font-family: serif;
}
</style>
{% endslotted %}

And your Nunjucks layout:

<!doctype html>
<html lang='en'>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>{{ title }}</title>
	{% slot 'custom_style' %}
</head>
<body>
	<h1>{{ title }}</h1>
	{{ content | safe }}
</body>
</html>

This works for passing raw content to the layout template. If you need that interpreted as Markdown, you can pair it with the Render plugin and output it as:

{% renderTemplate 'njk,md'%}
	{% slot "custom_style" %}
{% endrenderTemplate %}

@danburzo
Copy link
Contributor

For anyone still following the subject, I've whittled down my solution to a single paired shortcode + a piece of computed data passed through renderTemplate: https://danburzo.ro/eleventy-slotted-content/

@zachleat
Copy link
Member

zachleat commented Jul 5, 2024

This is also possible with the bundle plugin, included with Eleventy v3.0.0-alpha.10 or newer: https://github.com/11ty/eleventy-plugin-bundle?tab=readme-ov-file#react-helmet-style-head-additions

This is an automated message to let you know that a helpful response was posted to your issue and for the health of the repository issue tracker the issue will be closed. This is to help alleviate issues hanging open waiting for a response from the original poster.

If the response works to solve your problem—great! But if you’re still having problems, do not let the issue’s closing deter you if you have additional questions! Post another comment and we will reopen the issue. Thanks!

@zachleat zachleat closed this as completed Jul 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants