verbb / vizy

A flexible visual editor for Craft CMS
Other
44 stars 8 forks source link

Wrapping Groups of Nodes in Tailwind CSS Prose Class #61

Closed danleecpi closed 3 years ago

danleecpi commented 3 years ago

Description I'm trying to wrap groups of nodes rendered using node.renderHtml() in a Tailwind CSS prose class. This allows me to auto-format rich text.

Looking through the Vizy documentation it isn't so clear how I could do that. I have blocks that I don't want to get wrapped in the class such as pull quotes or image. Anything with text formatting however should be wrapped in the class.

My first attempt below counts the loop to add in the class but I quickly realised this does not work as the blocks I don't want to be wrapped are contained within the class.

Is there a way I can do detection on groups of blocks within the loop?

Steps to reproduce current code

{% for node in entry.nt_article.all() %}
    {% if node.type == 'vizyBlock' %}
        {% if node.handle == 'pullQuote' %}
            {% include "_components/01-atoms/quote/pull_quote.twig" with {pull_quote: pull_quote} %}
        {% endif %}
    {% else %}
        {% if loop.first %}
            <div class="tw-text-xl tw-prose tw-text-gray-500">
        {% endif %}
            {{ node.renderHtml() }}
        {% if loop.last %}
            </div>
        {% endif %}
    {% endif %}
{% endfor %}
engram-design commented 3 years ago

Yep, so this is a tricky case, in that if you were using Vizy as a WYSIWYG field, you'd wrap all the output in a prose class, which is simply enough. As soon as you add complexity with Vizy Blocks, with complex fields, this doesn't really hold up as a good solution. All the nested fields in each Vizy Block would be inside a prose class, which you wouldn't want. In some ways, you want to be about to "break out" of a prose class, but that's complex/not possible.

So the way you've done it above where you're checking if a Vizy Block, include your partial, and otherwise if it's any other sort of node, wrap it in a prose class is indeed a good start. However, this will wrap every node in that class. So you might have 3 paragraph nodes in your editor and end up with:

<div class="tw-text-xl tw-prose tw-text-gray-500">
    <p>Text...</p>
</div>

<div class="tw-text-xl tw-prose tw-text-gray-500">
    <p>Text...</p>
</div>

<div class="tw-text-xl tw-prose tw-text-gray-500">
    <p>Text...</p>
</div>

Which kind of defeats the purpose of the class, because you'd want it to add mb-4 (or similar) to each paragraph, unless it's the last one, of which it always will be. Your check on loop.first/last won't work either because you want to check for some nodes to exclude them.

I'll say there's not really a good way to handle this right now, sorry. You're essentially describing a way to group sequential nodes of a given type, and if not in those types, don't include the class.

In saying that, maybe this is an approach to consider, although it's pretty involved!

{% set nodes = entry.nt_article.all() %}

{# A variable to keep track of whether `.tw-prose` has been output to wrap nodes in #}
{% set wrappedNode = false %}

{# An array of node type we want excluded from `.tw-prose` #}
{% set excludedNodes = ['image'] %}

{% for node in nodes %}
    {% if node.type == 'vizyBlock' %}
        {# ... #}
    {% else %}
        {# If we haven't output a `.tw-prose` class, and this is a node we want to wrap, do it #}
        {% if not wrappedNode and node.type not in excludedNodes %}
            <div class="tw-text-xl tw-prose tw-text-gray-500">

            {# Prevent this from outputting again until we want it to (we've stopped wrapping) #}
            {% set wrappedNode = true %}
        {% endif %}

        {# Output the node's HTML #}
        {{ node.renderHtml() }}

        {# Find the next node. If it's a type we want not in prose, end here #}
        {% set nextNode = nodes[loop.index] ?? null %}

        {# Is there a next node, and in our excluded types #}
        {# Also check if there is no next node, and we've started wrapping. We don't want any loose ends. #}
        {% if (not nextNode and wrappedNode) or (nextNode and nextNode.type in excludedNodes) %}
            </div>

            {% set wrappedNode = false %}
        {% endif %}
    {% endif %}
{% endfor %}

Here we loop through your nodes, but maintain a variable to detect when we've started wrapping. In addition, we look at the next node (if there is one), and if that needs to break out of your prose class, then stop there. We also need to handle the case of single nodes in a field where there isn't a next node, but we've started wrapping.

I've commented it as best I can, but maybe let me know if anything doesn't make sense?

It might be tricky to implement this behaviour natively to Vizy, but I'll consider if it's possible.

danleecpi commented 3 years ago

Thanks @engram-design for this really clear and detailed answer.

Trying out your code I found that my blocks were not getting excluded from being wrapped.

Defining the excludedNodes array like the following seems to fix the issue:

{% set excludedNodes = ['vizyBlock'] %}

Would that be the correct way to do this?

engram-design commented 3 years ago

Ah, yes that's a good point. You'll want to include that in your excluded nodes as well. I was actually only testing on the WYSIWYG nodes, but a Vizy Block will also typically be excluded from this behaviour as well.