Skip to content

Commit

Permalink
Merge pull request #91 from parshap/pseudo-elements
Browse files Browse the repository at this point in the history
Inline pseudo elements
  • Loading branch information
jrit committed May 4, 2015
2 parents 61c1ac3 + 19947b4 commit c59601d
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 6 deletions.
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Next Release
* Fix order of inlined style properties. Now sorted by selector
specificity, resulting in the same computed styles that the original
CSS would have had.
* Add option to inline pseudo elements as <span> elements

1.0.2 / 2015-04-27
==================
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ All juice methods take an options object that can contain any of these propertie
* `applyWidthAttributes` - whether to use any CSS pixel widths to create `width` attributes on elements set in `juice.widthElements`. Defaults to `false`.
* `applyAttributesTableElements` - whether to create attributes for styles in `juice.styleToAttribute` on elements set in `juice.tableElements`. Defaults to `false`.
* `webResources` - An options object that will be passed through to web-resource-inliner for juice functions that will get remote resources (`juiceResources` and `juiceFile`). Defaults to `{}`.
* `inlinePseudoElements` - Whether to insert pseudo elements (`::before` and `::after`) as `<span>` into the dom. *Note*: Inserting pseudo elements will modify the dom and may conflict with css selectors elsewhere on the page (e.g., `:last-child`).

### Methods

Expand Down
102 changes: 96 additions & 6 deletions lib/juice.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ function inlineDocument($, css, options) {
rules.forEach(handleRule);
editedElements.forEach(setStyleAttrs);

if (options && options.inlinePseudoElements) {
editedElements.forEach(inlinePseudoElements);
}

if (options && options.applyWidthAttributes) {
editedElements.forEach(setWidthAttrs);
}
Expand All @@ -86,10 +90,11 @@ function inlineDocument($, css, options) {
function handleRule(rule) {
var sel = rule[0]
, style = rule[1]
, selector = new Selector(sel);
, selector = new Selector(sel)
, parsedSelector = selector.parsed()
, pseudoElementType = getPseudoElementType(parsedSelector);

// skip rule if the selector has any pseudos which are ignored
var parsedSelector = selector.parsed();
for (var i = 0; i < parsedSelector.length; ++i) {
var subSel = parsedSelector[i];
if (subSel.pseudos) {
Expand All @@ -100,15 +105,37 @@ function inlineDocument($, css, options) {
}
}

if (pseudoElementType) {
var last = parsedSelector[parsedSelector.length - 1];
var pseudos = last.pseudos;
last.pseudos = filterElementPseudos(last.pseudos),
sel = parsedSelector.toString();
last.pseudos = pseudos;
}

var els;
try {
els = $(sel);
} catch (err) {
// skip invalid selector
return;
}

els.each(function () {
var el = this;

if (pseudoElementType) {
var pseudoElPropName = "pseudo" + pseudoElementType;
var pseudoEl = el[pseudoElPropName];
if (!pseudoEl) {
pseudoEl = el[pseudoElPropName] = $("<span />");
pseudoEl.pseudoElementType = pseudoElementType;
pseudoEl.pseudoElementParent = el;
el[pseudoElPropName] = pseudoEl;
}
el = pseudoEl;
}

if (!el.styleProps) {
el.styleProps = {}

Expand Down Expand Up @@ -156,10 +183,32 @@ function inlineDocument($, css, options) {
return a.selector.specificity().join("").localeCompare(
b.selector.specificity().join(""));
});
var string = props.map(function(prop) {
return prop.prop + ": " + prop.value.replace(/["]/g, "'") + ";";
}).join(" ");
$(el).attr('style', string);
var string = props
.filter(function(prop) {
// Content becomes the innerHTML of pseudo elements, not used as a
// style property
return prop.prop !== "content";
})
.map(function(prop) {
return prop.prop + ": " + prop.value.replace(/["]/g, "'") + ";";
})
.join(" ");
if (string) {
$(el).attr('style', string);
}
}

function inlinePseudoElements(el) {
if (el.pseudoElementType && el.styleProps.content) {
el.html(parseContent(el.styleProps.content.value));
var parent = el.pseudoElementParent;
if (el.pseudoElementType === "before") {
$(parent).prepend(el);
}
else {
$(parent).append(el);
}
}
}

function setWidthAttrs(el) {
Expand Down Expand Up @@ -189,6 +238,47 @@ function inlineDocument($, css, options) {
}
}

function parseContent(content) {
if (content === "none" || content === "normal") {
return "";
}

// Naive parsing, assume well-formed value
content = content.slice(1, content.length - 1);
// Naive unescape, assume no unicode char codes
content = content.replace(/\\/g, "");
return content;
}

// Return "before" or "after" if the given selector is a pseudo element (e.g.,
// a::after).
function getPseudoElementType(selector) {
if (selector.length === 0) {
return;
}

var pseudos = selector[selector.length - 1].pseudos;
if (!pseudos) {
return;
}

for (var i = 0; i < pseudos.length; i++) {
if (isPseudoElementName(pseudos[i])) {
return pseudos[i].name;
}
}
}

function isPseudoElementName(pseudo) {
return pseudo.name === "before" || pseudo.name === "after";
}

function filterElementPseudos(pseudos) {
return pseudos.filter(function(pseudo) {
return !isPseudoElementName(pseudo);
});
}

function juiceDocument($, options) {
options = getDefaultOptions(options);
var css = extractCssFromDocument($, options);
Expand Down
Empty file.
41 changes: 41 additions & 0 deletions test/cases/juice-content/pseudo-elements.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<html><body>
<style>
a {
text-decoration: underline;
}

a:before,
a:after {
content: "a";
}

* a:before {
content: '®\+';
}

a:after,
a::after {
font-weight: bold;
}

a:first-child {
color: blue;
}

b:after {
content: " ";
}

b:after {
content: "b";
color: green;
}

b :last-child {
color: red;
}
</style>
<a href="#">Test</a>
<b></b>
<b><span></span></b>
</body></html>
5 changes: 5 additions & 0 deletions test/cases/juice-content/pseudo-elements.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"url": "./",
"removeStyleTags": true,
"inlinePseudoElements": true
}
6 changes: 6 additions & 0 deletions test/cases/juice-content/pseudo-elements.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<html><body>

<a href="#" style="text-decoration: underline; color: blue;"><span>®+</span>Test<span style="font-weight: bold;">a</span></a>
<b><span style="color: green;">b</span></b>
<b><span style="color: red;"></span><span style="color: green;">b</span></b>
</body></html>

0 comments on commit c59601d

Please sign in to comment.