statamic / cms

The core Laravel CMS Composer package
https://statamic.com
Other
4.13k stars 542 forks source link

Modifiers bard_items / raw | bard_items behavior with Antlers #10862

Closed AtmoFX closed 1 month ago

AtmoFX commented 2 months ago

Bug description

I was trying to customize the rendering of a bard field by processing the document's nodes 1 by 1. As per the documentation, the bard_items modifier "converts any Bard data to a flat array of ProseMirror nodes and marks". My idea was to browse through the nodes inside document structure as per that other documentation. The ultimate goal is to change the DOM compared to the default behavior of Statamic e.g. render images inside <figure> tags.

It is very possible this exercise is doomed from the start, as bard fields just work fine with native rendering; however, the only purpose I can fathom for the bard_items modifier is exactly what I am trying to do.

Hopefully, if nodes can be traversed in a sensible way, with recursivity, the feasibility of such approach would be demonstrated. I have inferred from the documentation that recursivity, the same way it is done for NAVs (i.e. `{{ recursive children }}, may not work (that is why nodes are returned in a flat array) but TBH, I could not reach a point where I could test that very part.

The value I try to render is, for illustration, the below value from a bard field configured without sets (I will include what happens with sets below):

content:
  -
    type: heading
    attrs:
      level: 2
    content:
      -
        type: text
        text: Title
  -
    type: paragraph
    content:
      -
        type: text
        text: 'Paragraph1 Line1'
      -
        type: hardBreak
      -
        type: text
        text: 'Paragraph1 Line 2'
  -
    type: paragraph
    content:
      -
        type: text
        text: 'Paragraph2 Line1'
      -
        type: hardBreak
      -
        type: text
        text: 'Paragraph2 Line 2'
---

I fail to get "a flat array of ProseMirror nodes" using the bard_items / raw | bard_items modifiers. Also, I am getting an error in 1 situation.

How to reproduce

Render the provided document with each of the following code snippets:

It can be argued that raw | bard_items returns a flat array but it seems that bard_items alone does not do that, contrary to the documentation. As per my tests, I do not think what is returned is ProseMirror nodes. A lot of the documented properties ({{ type }}, {{ text }}, {{ attrs }}, ...) are accessible but not all of them (properties related to nodes children: {{ content }}, {{ childCount }}, ... + a few more such as nodeSize).

Logs

No response

Environment

Environment
Application Name: Gaia ESG fintech platform
Laravel Version: 11.23.5
PHP Version: 8.3.12
Composer Version: 2.7.7
Environment: local
Debug Mode: ENABLED
URL: gaia-hub.test
Maintenance Mode: OFF
Timezone: UTC
Locale: en

Cache
Config: NOT CACHED
Events: NOT CACHED
Routes: NOT CACHED
Views: CACHED

Drivers
Broadcasting: log
Cache: file
Database: sqlite
Logs: stack / single
Mail: log
Queue: sync
Session: file

Statamic
Addons: 1
Sites: 1
Stache Watcher: Enabled (auto)
Static Caching: Disabled
Version: 5.27.0 Solo

Statamic Addons
alt-design/alt-seo: 1.2.1

Installation

Fresh statamic/statamic site via CLI

Additional details

No response

duncanmcclean commented 1 month ago

{{ content | bard_items }} fails with a type error:

The bard_items modifier expects an array, which is what the {{ content }} field will give you if you create any sets. If you don't have any sets, it just outputs HTML.

I don't think the bard_items modifier is intended to work without sets.

The ultimate goal is to change the DOM compared to the default behavior of Statamic e.g. render images inside

tags.

If you're wanting control over the HTML outputted by Bard, I'd probably create a Bard set for displaying images, instead of using the built-in images feature and trying to override the way it outputs images.

That's what I do on one of my sites:

CleanShot 2024-10-11 at 14 25 50

{{ images }}
    <figure>
        <img src="{{ url }}" alt="{{ alt }}">
    </figure>
{{ /images }}
AtmoFX commented 1 month ago

@duncanmcclean The help page does explicitly say {{ content | raw | bard_items }} is valid for bard fields without sets, quote:

The raw value from a Bard field (a ProseMirror document), with or without sets

.... but the returned array is unusable like I said: it turns the 3 level-1 nodes of the above sample into 10 nodes (all the subnodes are included) and with some ProseMirror node properties missing (e.g. {{ childCount }}).

Had the array reproduced the original structure of the document, your solution would be unnecessary.

jacksleight commented 1 month ago

it turns the 3 level-1 nodes of the above sample into 10 nodes (all the subnodes are included)

This is intentional, from the docs: "Converts any Bard data to a flat array of ProseMirror nodes"

and with some ProseMirror node properties missing

It only returns the raw node data. It doesn't return objects with all the additional properties that the ProseMirrror JS libraries provide, that wouldn’t really be feasible since the PHP library does not provide those properties.

Had the array reproduced the original structure of the document, your solution would be unnecessary.

If you want the original structure you can just use the raw data directly:

{{ nodes = bard_field | raw }}
{{ nodes }}
    {{ type }} - {{ content | to_json }}
{{ /nodes }}
AtmoFX commented 1 month ago

I could not find a way to do anything with the JSON. I indeed get it as an array of JSON string and AFAICT, their content is correct but I did not find how to extract the properties from them.

Anyway, I could not help but notice that what I described in the question seemed to look OK to you two whereas I am struggling to even find a purpose to bard_items. When it does not generate an error (i.e. when used with raw), it transforms the ProseMirror document so much I do no see how I can possibly process the array. Surely, there is something I am missing...

jacksleight commented 1 month ago

I could not find a way to do anything with the JSON. I indeed get it as an array of JSON string and AFAICT, their content is correct but I did not find how to extract the properties from them.

What are you trying to do? If you loop over the raw data as shown above you can access each node's values with {{ type }}, {{ content }}, {{ attrs:level }} etc. However it's pretty unusual to that at all to be honest, the normal way to output a Bard field is to do {{ bard_field }} and let the augmentation process/Tiptap package convert the nodes to an HTML string for you.

I am struggling to even find a purpose to bard_items.

The bard_items modifier is intended to be used with other modifiers that expect a flat array of data, eg. if you wanted extract a list of all the headings for a table of contents you could use bard_items to convert the tree structure to a flat array, and then the where modifier to filter just the heading nodes. There are some other examples in the docs.

AtmoFX commented 1 month ago

Just wanted to render a field in a different way than the default and only in 1 template

jacksleight commented 1 month ago

OK. I wouldn't recommend trying to manually loop over nodes to render out each HTML tag, you'd basically be reinventing everything the Tiptap package is already doing. You can customise how Tiptap renders the HTML by implementing your own extensions, or you might want to check out Bard Mutator.