emogrifier icon indicating copy to clipboard operation
emogrifier copied to clipboard

Does it support custom property?

Open kaaaaaaaaaaai opened this issue 4 years ago • 4 comments

in css

:root{
    --quote: #b1472d;
}
.quote{
background-color:var(--quote, #ececec)
}

i want to be get this text that after convert html

<div class="quote" style="background-color: #b1472d" >test</div>

kaaaaaaaaaaai avatar Jun 24 '21 06:06 kaaaaaaaaaaai

Hi @kaaaaaaaaaaai,

Good question. (I was just looking at this recently and wondering if it would be preferable to SASS, etc.)

Emogrifier currently has no special processing of custom properties.

I have not tested, but the expected output from Emogrifier 5.x for your case should be

<style>
  :root {
    --quote: #b1472d;
  }
</style>
<div class="quote" style="background-color: var(--quote, #ececec);">test</div>

So it would be up to the (email) client to apply the custom property through the inlined style attribute. I am not aware of any email clients that support this.

Thus I think this is something Emogrifier could (and perhaps should) be enhanced to support. It's not straightforward but I think do-able without any significant refactoring.

We would welcome any PRs working towards this 😉

JakeQZ avatar Jun 25 '21 01:06 JakeQZ

@oliverklee @JakeQZ Thanks for the response.

Thus I think this is something Emogrifier could (and perhaps should) be enhanced to support. ithink so, too.

It's not straightforward but I think do-able without any significant refactoring.

I'll take a look at the code. and put out a PR if I can :(.

Thanks for the good OSS.

kaaaaaaaaaaai avatar Jun 25 '21 02:06 kaaaaaaaaaaai

@JakeQZ I'm trying to read the code, but I can't think of any way to respond.

I'd like to know if you have any ideas on how to handle this.

kaaaaaaaaaaai avatar Jul 02 '21 02:07 kaaaaaaaaaaai

I'm trying to read the code, but I can't think of any way to respond.

Yeah, I can appreciate not being familiar with the code and/or how Emogrifier works, it won't be at all clear how to tackle this.

It may help if I outline the procedure of Emogrifier:

  1. Sort the CSS rules in order of selector precedence, lowest first.
  2. For each of the sorted rules
    • Identify the elements they apply to (by converting the CSS selector to an XPath expression);
    • For each element
      • Merge the property declarations from the CSS rule into the style attribute of the element, replacing any existing declarations for the same property, whilst respecting !important.
  3. Finally, remove !important from the declarations in style attributes.

I am thinking that to solve this there are two independent changes needed - one small; one bigger two-parter:

  1. Support :root;
    1. Possibly cascade the --var declarations through to all descendent elements at time of application;
    2. Modify step 3 above to support --var at the same time as removing !important - i.e. when finalizing the style attribute - possibly including traversing back up the tree looking for --var declarations (unless the cascading is done in 2i above).

If :root was supported, the given input should come out, with cascading (in 2i), as follows (instead of the what I posted previously above as current behaviour):

<div class="quote" style="--quote: #b1472d; background-color: var(--quote, #ececec);">test</div>

Without cascading (in 2i), we would have

<html style="--quote: #b1472d;">
  <head></head>
  <body>
    <div class="quote" style="background-color: var(--quote, #ececec);">test</div>
  </body>
</html>

The finalization 'step 3' I think already involves splitting the style attribute back into key-value pairs, and it should be clear from the above example(s) what additionally needs to be done here.

I am not sure whether traversing back up the tree looking for a --var declaration whenever var() is found is a better approach than applying the --var declaration to all descendents whenever it is found.

Anyway, step 3 is done by removeImportantAnnotationFromAllInlineStyles, so around that call in the procedure would be the place to insert a call to a new method to finalize the custom properties (i.e. assign the vars).

Regarding supporting :root, this may be very simple. We use symfony/css-selector to convert selectors to XPath expressions. This hopefully already supports :root. We only allow pseudo-classes known to be supported. If Symfony supports it, it should just require changing the regular expression PSEUDO_CLASS_MATCHER to allow it (and adding a few tests to confirm).

Hope this helps, and thanks for your continued interest. As I said, I think this would be an excellent enhancement to Emogrifier. Feel free to ask any more questions.

PS. I am assuming that --var and var() will be passed through as expected by the current code - this may of course not be the case (though I can't think of a reason why it wouldn't).

PPS. Regarding 'step 3', I think removeImportantAnnotationFromNodeInlineStyle should become a more generically-named method (like finalizeStyleAttribute) which does the splitting into key-value pairs, and re-assembling later, and in-between calls off to separate methods, one of which would be removeImportant..., and, with this proposal, a second would be to apply the vars. Though I am not sure if the order matters (or even if this is workable due to some nuance which means both steps can't be done together node-by-node but instead need to be done separately, one after the other each on the DOM tree as a whole), or whether !important can apply to a --var and if so what it means.

JakeQZ avatar Jul 05 '21 00:07 JakeQZ