Flutter-Bounty-Hunters / static_shock

A static site generator for Dart.
MIT License
58 stars 5 forks source link

Extending the markdown parser to allow individual image styling #120

Open suragch opened 1 month ago

suragch commented 1 month ago

A common problem I have when writing articles is correctly specifying the image width, horizontal placement, and caption.

Markdown doesn't support setting the image width so it has to be done with HTML/CSS. I can specify the style globally to make sure that my image isn't wider than the screen size like so:

img {
    max-width: 100%;
    height: auto;
}

However, when writing an article, I have screenshots of many varying widths. In Medium I could sometime hack it by adding lots of horizontal white space around the image. Or in a Markdown context I can use HTML/CSS like so:

<img src="./img/myapp.png" width="400px" alt="image description" />

Centering and adding a caption is another common task. I can do it in HTML/CSS like so:

<div style="text-align: center;">
    <img src="./img/myapp.png" width="400px" alt="image description" />
    <div style="font-style: italic; margin-top: 0.5rem;">This is the image label</div>
</div>

Here is a screenshot showing the original (![image description](./img/myapp.png)) image and the HTML version one above the other:

Screenshot 2024-05-17 at 15 00 39

If there were a way to specify the image width, alignment, and caption, that would be great. This feature request is outside of standard Markdown, though, so I understand if you don't want to implement it.

I'm not sure what the API would look like. Maybe something like this:

![image caption](./img/myapp.png "width=300, alignment=center")

Or this:

![image description](./img/myapp.png){width=300, alignment=center, caption="image caption"}

Hmm, I was just thinking, is this the kind of thing Jinja could do? I wonder if there were some sort of Jinja template that I could plug in the width, alignment and caption variables.

matthew-carroll commented 1 month ago

For the time being, you can define a component for this purpose. Off the top of my head, I think the steps would be like this:

Create a Jinja template at _includes/components/blogImage.jinja.

In that component, use the HTML that fits your use, e.g.:

<div style="text-align: center;">
    <img src="{{ image }}" width="{{ width }}" alt="{{ description }}" />
    {% if caption is defined %}
    <div style="font-style: italic; margin-top: 0.5rem;">{{ caption }}</div>
    {% endif %}
</div>

Then, in a given blog post, you'll need to do two things.

First, to use Jinja in the middle of Markdown, you need to add a Frontmatter property telling the pipeline to do that:

---
title: something
contentRenderers:
 - jinja
 - markdown
---
# My Article

If you want to configure all articles to (possibly) include Jinja, you can configure the contentRenderers in a _data.yaml file that gets inherited by all pages lower down, e.g.:

source/articles/_data.yaml

contentRenderers:
 - jinja
 - markdown

Then, once configured to run Jinja within Markdown, use the component wherever you'd like:

# My Article
Text above the image component.

{{ components.blogImage(image="./img1.png", width="400px", description="Image 1", caption="My caption") }}

Text below the image component.

Please let me know if you're able to get that to work.

The broader question is to what we might provide in something like a Blog Template. Perhaps there's something useful for images on that front. But we'll need to ensure that any such out-of-the-box tools make sense in general, and are worth maintaining.

suragch commented 1 month ago

I wasn't able to get your example to work. I got the following error:

Unhandled exception:
NoSuchMethodError: Closure call with mismatched arguments: function 'JinjaPageRenderer._renderJinjaTemplate.<anonymous closure>'
Receiver: Closure: ([Map<Object?, Object?>?]) => String
Tried calling: JinjaPageRenderer._renderJinjaTemplate.<anonymous closure>(caption: "My caption", description: "Image 1", image: "./image2.png", width: "400px")
Found: JinjaPageRenderer._renderJinjaTemplate.<anonymous closure>([Map<Object?, Object?>?]) => String

That may be because of some mistake I made.

Rather than getting a Jinja component to work in Markdown, though, a new idea I had is whether it is possible to write my own Plugin. Is it possible for me write a plugin that parses the markdown file and replaces one string with another?

I'm not a big fan of the Jinja syntax. What I would like to do is add a plugin to main like so:

..plugin(const MyPlugin())

That plugin does a regex match for:

![image caption](./img/myapp.png "width=300, alignment=center")

and replaces it with:

<div style="text-align: center;">
    <img src="./img/myapp.png" width="300px" alt="image caption" />
    <div style="font-style: italic; margin-top: 0.5rem;">image caption</div>
</div>

Browsing the source code of JinjaPlugin, that looks quite possible, though I haven't figured out how PageLoader, PageTransformer and PageRenderer work yet.