getzola / zola

A fast static site generator in a single binary with everything built-in. https://www.getzola.org
https://www.getzola.org
MIT License
13.44k stars 941 forks source link

Allow shortcodes in shortcode bodies #515

Open glfmn opened 5 years ago

glfmn commented 5 years ago

If a short-code with a body is used in the body of a short-code it's {% end %} is matched with the outer short-code. This prevents the use of nested short-codes with bodies.

Use Case

I'm trying to write reveal.js presentations in Gutenberg currently and I have defined simple shortcodes for vertical slides, fragments, and notes:

<aside class="notes">
{{ body | safe | trim | markdown }}
</aside>
<section{%- if id %} id="{{id}}" {%- endif -%}
        {%- if attributes -%}
          {%- for attr in attributes %}{#
            #} {{attr.attribute}}="{{attr.value}}"
          {%- endfor -%}
        {%- endif -%}>
{{ body | safe | markdown(inline=true) }}
</section>
<span class="fragment">
    {{ body | safe | trim | markdown }}
</span>

This makes it easier to write slides like so:

+++
title = "Installation instructions"
+++

{% vertical_slide() %}
Main content

{% notes() %}
- notes on main content
{% end %}
{% end %}

{% vertical_slide() %}
Basement slide with extra content

{% notes() %}
- notes on basement content
{% end %}
{% end %}

Workaround

Of course I can just throw in html to work around this, but I find short-codes are more pleasant to work with, especially when I have more complicated things going on like figures with complex markup that I've created with short-codes showing up in vertical slides.

For example:

{% vertical_slide() %}
**Linux** (Ubuntu/Debian)

`sudo apt-get install git`

<figure class="toggle-figure">
    <span class="toggle-figure__button"></span>
    <img class="toggle-figure__figure" alt="`sudo apt-get install git`" src="img/gif/linux-install-git.gif"/>
</figure>

<aside class="notes">
- Linux typically comes with `git` installed
- `git` was created specifically to manage the Linux project
</aside>
{% end %}

However, this isn't ideal since the list inside the aside is not rendered as markdown.

Keats commented 5 years ago

Having shortcodes inside shortcodes is very unlikely to happen imo, it doesn't feel great and would probably be a bit messy since there are already issues with the shortcodes <> markdown interactions in some cases (https://github.com/Keats/gutenberg/issues/479)

If you have time you could write the code and I will have a look ('m not promising to merge it just to be clear).

Keats commented 5 years ago

Is there anyone wanting to implement that? It looks like most of the work is actually parser-related, in components/rendering/shortcode.rs and the content.pest file in the same folder.

creikey commented 5 years ago

@Keats I will be more than willing to implement this once I am done with my current project.

FelixHenninger commented 5 years ago

Hi y'all,

Thanks for all of your efforts with Zola -- I really enjoy building sites with it! I just thought I'd chime in to say that I, too, would absolutely love to see nested shortcodes.

Obviously, I don't have the same perspective as you, @Keats, but for me personally, I think this feature would make the interaction with markdown much more natural -- right now, I'm writing a lot of repetitive HTML on my site, and I think that (a) nested shortcodes would actually simplify the page code greatly and (b) actually improve the interaction with markdown because inserting HTML breaks parsing, too.

I would also be thrilled to help with this (@creikey, would love to join forces or support you in any way!), but not being familiar with rust nor pest (humble web dev here 😉), I'm not sure how to get started. My very naive impression is that shortcode_with_body and text_in_body_sc in components/rendering/src/content.pest would need to be adapted to allow for nested shortcodes (so text_in_body_sc would be able to include shortcode_with_body as well as inline_shortcode), and render_shortcode in components/rendering/src/shortcode.rs would need to run recursively. Would that be a starting point?

Thanks a bunch, and again, please don't hesitate to let me know if I can help with this! (maybe I could try to make a test case #479?)

-Felix

Keats commented 5 years ago

Thanks for the kind words!

Regarding the work to be done, I am not 100% sure as I haven't a look at that in ages. I think you nailed the parser changes but it's likely to be more complex when rendering them than just running recursively. I think https://github.com/getzola/zola/blob/fa0cf05fe0ab911a7e59c27cc7ab80fbcc9eff2c/components/rendering/src/shortcode.rs#L173 will need to be extracted to another function and https://github.com/getzola/zola/blob/fa0cf05fe0ab911a7e59c27cc7ab80fbcc9eff2c/components/rendering/src/shortcode.rs#L185 will need to call it instead of taking everything as a string. It doesn't look too hard but you never know...

FelixHenninger commented 5 years ago

Hej, thanks a lot for your super-quick and kind response ... and no worries, this is the absolute least I can do!

I'm really enjoying working with Zola, and it's been an immense help with a new website I've been setting up -- I'd love to explore abstracting away some common elements that take up a lot of space in the code, and see whether that would make sense for Zola in general.

As noted above, I'm new to Pest and Rust, so I can't make any promises, but I'll play around with the pest online editor over the coming days to see whether I can get the parser running, and then maybe I'll have a go at the rendering code.

@creikey Please let me know if you'd like to take over, I wouldn't want to steal this from you if you've already made plans.

creikey commented 5 years ago

@FelixHenninger I am very busy with Naval Battle and Godot Engine as of now, unlikely I'll start working on this in the near future.

Th3Whit3Wolf commented 4 years ago

This would make pages with columns much simpler

adrian5 commented 4 years ago

This is something that I too have been looking for in static site generators without success (I'm currently using Hugo). My use case is documentation, e.g.:

{{% note %}}
Read more about {{ doc "nginx#anchor" }} and see these {{ file "snippet.conf" "custom settings" }}.
{{% end %}}

There are a lot of possibilities that allow one to write concise documentation. Having to use inline HTML or do manual validation is terrible for productivity, and any changes to recurring elements would have be reapplied in every document, instead of a single template file.

Riezebos commented 4 years ago

@Th3Whit3Wolf I have exactly the same use case! We currently have a workaround (shown below) by creating a .md file for each column and using the paginator, but it would be nice to be able to create rows and columns from within the same markdown file.

{% block content %}
  <!-- Main Content -->
  <div class="container">
    <div class="row">
      {% for page in paginator.pages %}
      {% if page.extra.col %}
      <div class="col-md-{{page.extra.col}} col-sm-12 mx-auto">
      {% else %}
      <div class="col-md-4 col-sm-12 mx-auto">
      {% endif %}
        <h2>{{ page.title }}</h2>
        {{ page.content | safe }}
      </div>
      {% endfor%}
    </div>
    {% endblock content %}
philpax commented 2 years ago

Hi there, has there been any movement on this? I noticed that #1564 was merged, but it looks like that may not have resolved this issue. This is not a blocker for me as my outer-shortcode is just a single HTML tag, but I'd love to have this working to keep HTML out of my Markdown if possible 🙏

Keats commented 2 years ago

No progress on that, it's been punted on as it's not a super important feature. Maybe after the 0.16 and more tests are added it should be possible for someone else to implement it

eugenesvk commented 1 year ago

Am I correct that this would allow resolving this issue https://github.com/getzola/zola/issues/375 as I could pass a colocated file name as an argument to a shortcode, and then let the shortcode do something like include "{{page.colocated_path}}{{name}}.html"

to include a pre-built html file in any part of the markdown page that I want (which is neat for organization, currently I'd have to move all co-located htmls to the templates/shortcode location to be able to import them within markdown files)?

Keats commented 1 year ago

You can use the load_data function to load the file but include with a variable is not going to happen

mbaltrusitis commented 1 year ago

Commenting that I would also be interested in nested shortcodes. My attempted use case below:

use case.

As a theme writer I want to be able to have two stages of shortcodes where the first stage renders a markdown body that can then be further embedded within custom HTML. In the example below the snippet.md Markdown Shortcode fetches plaintext data to be rendered within a code block (i.e., ```) which is then passed as a body to the _source_url.html HTML Shortcode.

example.

snipped.md

{% if filename %}
    <!-- if a specific file within a multi-file snippet is specified, use it -->
    {% set snippet_url = url ~ "/raw/main/" ~ filename %}
{% else %}
    {% set snippet_url = url ~ "/raw" %}
{% endif %}

{% set source = load_data(url=snippet_url) %}

<a class="text-xs" href=url>source</a>    

{% _source_link(source_url=url) %}
```{{ file_type }}
{{ source }}
```
{% end %}

_source_url.html

<div class="source-link">
    {{ body }}
    <small><a href={{ source_url }}>source.</a></small>
</div>

workaround.

I opted to put my link above the code vs. under it to avoid padding issues and call the two short codes separately.

{{ source_link(url="https://gitlab.com/-/snippets/2549420") }}
{{ gitlab_snippet(url="https://gitlab.com/-/snippets/2549420", file_type="makefile", filename="Makefile")  }}

This has the downside of less control but reasonably checks the box (for now) of what I was looking for.

An alternative workaround could be to pass the snippet as a body and then abuse the {{ body }} variable but that has some code smell to it and I don't trust myself to remember the hackery next time I expect to touch this code.

Jieiku commented 3 weeks ago

I thought of a use for this as well, shortcodes can take arguments.

My idea was to create another variant of a shortcode with preloaded arguments.

So in the second variant, it would call the main shortcode but with options already set.