Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
Inspired by the [juice](https://github.com/Automattic/juice) library.

## Features
- Uses [cheerio](https://github.com/cheeriojs/cheerio) instead of jsdom
- Works on Windows
- Preserves Doctype
- Modular
Expand Down Expand Up @@ -171,10 +170,6 @@ When a data-embed attribute is present on a <style></style> tag, inline-css will

This can be used to embed email client support hacks that rely on css selectors into your email templates.

### cheerio options

Options to passed to [cheerio](https://github.com/cheeriojs/cheerio).

## Contributing

See the [CONTRIBUTING Guidelines](https://github.com/jonkemp/inline-css/blob/master/CONTRIBUTING.md)
Expand Down
5 changes: 0 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@ module.exports = (html, options) => new Promise((resolve, reject) => {
HBS: { start: '{{', end: '}}' }
},
xmlMode: false,
decodeEntities: false,
lowerCaseTags: true,
lowerCaseAttributeNames: false,
recognizeCDATA: false,
recognizeSelfClosing: false
}, options);

inlineContent(String(html), opt)
Expand Down
10 changes: 6 additions & 4 deletions lib/handleRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,23 @@ const parseCSS = require('css-rules');
const styleSelector = cssSelector('<style attribute>', [ 1, 0, 0, 0 ]);
const addProps = require('./addProps');

module.exports = (rule, $) => {
module.exports = (rule, { window }) => {
const sel = rule[0];
const style = rule[1];
const selector = cssSelector(sel);
const editedElements = [];

$(sel).each((index, el) => {
const elements = window.document.querySelectorAll(sel);

Array.prototype.forEach.call(elements, el => {
let cssText;

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

// if the element has inline styles, fake selector with topmost specificity
if ($(el).attr('style')) {
cssText = `* { ${$(el).attr('style')} } `;
if (el.getAttribute('style')) {
cssText = `* { ${el.getAttribute('style')} } `;

addProps(el, parseCSS(cssText)[0][1], styleSelector);
}
Expand Down
37 changes: 18 additions & 19 deletions lib/inline-css.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
const parseCSS = require('css-rules');
const cheerio = require('cheerio');
const jsdom = require('jsdom');
const { JSDOM } = jsdom;
const pseudoCheck = require('./pseudoCheck');
const handleRule = require('./handleRule');
const flatten = require('flat-util');
const setStyleAttrs = require('./setStyleAttrs');
const setWidthAttrs = require('./setWidthAttrs');
const removeClassId = require('./removeClassId');
const setTableAttrs = require('./setTableAttrs');
const pick = require('pick-util');

function replaceCodeBlock(html, re, block) {
return html.replace(re, () => block);
}

function decodeHTMLEntities(str) {
return String(str).replace(/&amp;/g, '&');
}

module.exports = (html, css, options) => {
const opts = options || {};
let rules;
Expand Down Expand Up @@ -48,16 +52,10 @@ module.exports = (html, css, options) => {

const encodeEntities = _html => encodeCodeBlocks(_html);
const decodeEntities = _html => decodeCodeBlocks(_html);
let $;

$ = cheerio.load(encodeEntities(html), pick(opts, [
'xmlMode',
'decodeEntities',
'lowerCaseTags',
'lowerCaseAttributeNames',
'recognizeCDATA',
'recognizeSelfClosing'
]));
const dom = new JSDOM(encodeEntities(html), {
contentType: opts.xmlMode ? 'application/xhtml+xml' : 'text/html'
});

try {
rules = parseCSS(css);
Expand All @@ -76,36 +74,37 @@ module.exports = (html, css, options) => {
}

try {
el = handleRule(rule, $);
el = handleRule(rule, dom);

editedElements.push(el);
} catch (err) {
// skip invalid selector
return false;
}
return undefined;
});

// flatten array if nested
editedElements = flatten(editedElements);

editedElements.forEach(el => {
setStyleAttrs(el, $);
setStyleAttrs(el, dom);

if (opts.applyWidthAttributes) {
setWidthAttrs(el, $);
setWidthAttrs(el);
}

if (opts.removeHtmlSelectors) {
removeClassId(el, $);
removeClassId(el);
}
});

if (opts.applyTableAttributes) {
$('table').each((index, el) => {
setTableAttrs(el, $);
const tables = dom.window.document.querySelectorAll('table');

Array.prototype.forEach.call(tables, el => {
setTableAttrs(el, dom);
});
}

return decodeEntities($.html());
return decodeEntities(decodeHTMLEntities(dom.serialize()));
};
1 change: 0 additions & 1 deletion lib/pseudoCheck.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,4 @@ module.exports = rule => {
return false;
}
}
return undefined;
};
6 changes: 3 additions & 3 deletions lib/removeClassId.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
module.exports = (el, $) => {
module.exports = (el) => {
const selectors = [ 'class', 'id' ];

selectors.forEach(selector => {
const attribute = $(el).attr(selector);
const attribute = el.getAttribute(selector);

if (typeof attribute !== 'undefined') {
$(el).removeAttr(selector);
el.removeAttribute(selector);
}
});
};
4 changes: 2 additions & 2 deletions lib/setStyleAttrs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = (el, $) => {
module.exports = (el) => {
let i;
let style = [];

Expand All @@ -21,5 +21,5 @@ module.exports = (el, $) => {
return (aProp > bProp ? 1 : aProp < bProp ? -1 : 0);
});

$(el).attr('style', style.join(' '));
el.setAttribute('style', style.join(' '));
};
48 changes: 28 additions & 20 deletions lib/setTableAttrs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,51 +26,59 @@ const tableStyleAttrMap = {

const attributesToRemovePxFrom = [ 'height', 'width' ];

const applyStylesAsProps = ($el, styleToAttrMap) => {
let style, styleVal, attributeValue;
const applyStylesAsProps = (el, styleToAttrMap) => {
let style;
let styleVal;
let attributeValue;

for (style in styleToAttrMap) {
styleVal = $el.css(style);
styleVal = el.style[style];

if (styleVal !== undefined) {
if (attributesToRemovePxFrom.indexOf(style) > -1) {
if (attributesToRemovePxFrom.includes(style)) {
attributeValue = styleVal.replace(/px$/i, '');
} else {
attributeValue = styleVal;
}

$el.attr(styleToAttrMap[style], attributeValue);
$el.css(style, '');
if (attributeValue.length > 0) {
el.setAttribute(styleToAttrMap[style], attributeValue);
} else {
el.removeAttribute(styleToAttrMap[style]);
}
el.style[style] = '';
}
}
};

const batchApplyStylesAsProps = ($el, sel, $) => {
$el.find(sel).each((i, childEl) => {
applyStylesAsProps($(childEl), tableStyleAttrMap[sel]);
const batchApplyStylesAsProps = ($el, sel) => {
const elements = $el.querySelectorAll(sel);

Array.prototype.forEach.call(elements, el => {
applyStylesAsProps(el, tableStyleAttrMap[sel]);
});
};

function resetAttr(node, attribute) {
if (node.attr(attribute)) {
return;
const resetAttribute = (el, attribute) => {
if (!el.getAttribute(attribute)) {
el.setAttribute(attribute, 0);
}
node.attr(attribute, 0);
}
return el;
};

module.exports = (el, $) => {
module.exports = (el) => {
let selector;
let $el = $(el);
let $el = el;

resetAttr($el, 'border');
resetAttr($el, 'cellpadding');
resetAttr($el, 'cellspacing');
$el = resetAttribute($el, 'border');
$el = resetAttribute($el, 'cellpadding');
$el = resetAttribute($el, 'cellspacing');

for (selector in tableStyleAttrMap) {
if (selector === 'table') {
applyStylesAsProps($el, tableStyleAttrMap.table);
} else {
batchApplyStylesAsProps($el, selector, $);
batchApplyStylesAsProps($el, selector);
}
}
};
6 changes: 3 additions & 3 deletions lib/setWidthAttrs.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const widthElements = [ 'table', 'td', 'img' ];

module.exports = (el, $) => {
module.exports = (el) => {
let i;
let pxWidth;

if (widthElements.indexOf(el.name) > -1) {
if (widthElements.includes(el.tagName.toLowerCase())) {
for (i in el.styleProps) {
if (el.styleProps[i].prop === 'width' && el.styleProps[i].value.match(/px/)) {
pxWidth = el.styleProps[i].value.replace('px', '');

$(el).attr('width', pxWidth);
el.setAttribute('width', pxWidth);
return;
}
}
Expand Down
Loading