Skip to content

fix(inline-props): apply end-of-line attributes to block tokens when inline Markdown present#16

Open
kentrpg wants to merge 5 commits intocomarkdown:mainfrom
kentrpg:fix/15
Open

fix(inline-props): apply end-of-line attributes to block tokens when inline Markdown present#16
kentrpg wants to merge 5 commits intocomarkdown:mainfrom
kentrpg:fix/15

Conversation

@kentrpg
Copy link

@kentrpg kentrpg commented Jun 29, 2025

Description

This PR fixes that end-of-line attributes are correctly applied to the parent block-level token, even when the line contains other inline Markdown syntax.

Before

Previously, attributes were incorrectly applied to the last inline element. For example, this Markdown:

**bold**{.text-red}

would render as:

<p><strong class="text-red">bold</strong></p>

After

With this change, the same Markdown now produces the correct HTML, with the attributes applied to the parent <p> tag:

<p class="text-red"><strong>bold</strong></p>

Linked Issues

fix #15

Additional context

<p><code style="color: red">code</code></p>
<p><em style="color: blue">italic</em></p>
<p style="color: red"><code>code</code></p>
<p style="color: blue"><em>italic</em></p>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not so sure about this behaviour change, shouldn't the style applied to the code and em tag?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about that! I was testing another (not yet submitted) issue and PR related to inline-props, and accidentally included those output changes in this PR’s tests.
I noticed the mistake and have already reverted the unintended output changes in this commit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you revert that and update the snapshot? Thanks

Copy link
Author

@kentrpg kentrpg Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not so sure about this behaviour change, shouldn't the style applied to the code and em tag?

Sorry, I was a bit nervous sending my first PR to a technical guru!

I've reviewed the logic again:
Since the attribute syntax is placed at the end of the line, the style is now applied to the block-level <p> tag, not the code or em tag.

`code`{style="color: red"}
_italic_{style="color: blue"}

If the attribute syntax needs to apply to the inline-level token instead, I found that adding an empty {} at the end works without changing any other logic. What do you think about this approach?

For example:

`code`{style="color: red"}{}
_italic_{style="color: blue"}{}
[Link](https://nuxt.com){class="nuxt"}{}
![Nuxt Logo](https://nuxt.com/assets/design-kit/logo/icon-green.svg){#nuxt-logo}{}

Copy link
Author

@kentrpg kentrpg Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just to double-check, should I only revert test(fixtures): fix unintended changes in inline-props output this commit, or do I also need to update the snapshot after reverting? Thanks

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think {class="nuxt"}{} is a bit counter intutive, as I would expect

foo **bar**{style="color: red"} baz
**bar**{style="color: red"}

to behave the same, that applying the style to the b element

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d like to ask for a bit more detail about your expectation.
Are you expecting this behavior only when the attribute syntax sets a style property?

If so, I think I can adjust the condition like this:

if (
  token.type === 'inline' &&
  token.children?.[token.children.length - 1].type === 'mdc_inline_props' &&
  token.children[token.children.length - 1].attrs.length === 1 &&
  token.children[token.children.length - 1].attrs[0][0] === 'style'
) {
 // ...
}

The logic here is to check whether the last mdc_inline_props token only has a style attribute.
Other attributes can still be applied to the block-level token.for example, **bar**{.text-red} would apply the class to the p element.

Copy link
Author

@kentrpg kentrpg Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply attributes to the parent token only when the content is not purely inline elements.
Below is my attempt at designing the filtering logic with a reverse for loop.

if (token.type === "inline" && token.children?.[token.children.length - 1].type === "mdc_inline_props") {
  const contentTokens = token.children.slice(0, -1)
  let inlineCount = 0
  let onlyInline = true
  for (let index = contentTokens.length - 1; index >= 0; index--) {
    if (contentTokens[index].type === 'text') {
      if (!contentTokens[index].content.trim()) 
        continue

      onlyInline = false
      break
    }

    // handle self-closing token
    if (contentTokens[index].nesting === 0) {
      inlineCount++
      continue
    }

    let searchIndex = index - 1
    while (searchIndex >= 0) {
      const searchToken = contentTokens[searchIndex]
      if (searchToken.nesting === 1 
        && searchToken.tag === contentTokens[index].tag 
        && searchToken.level === contentTokens[index].level
      ) {
        break
      }
      searchIndex--
    }

    if (searchIndex < 0)
      throw new Error(`No matching opening tag found for ${JSON.stringify(contentTokens[index])}`)

    index = searchIndex
    inlineCount++
  }

  if (!onlyInline || inlineCount !== 1) {
    const props = token.children.pop()?.attrs
    props?.forEach(([key, value]) => {
      if (key === "class")
        prev.attrJoin("class", value)
      else
        prev.attrSet(key, value)
    })
  } 
}

Not sure if this meets your expectation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

End-of-line attributes fail to apply to block tokens in the presence of inline Markdown

2 participants