Skip to content

Commit

Permalink
Merge 581b76f into 044e04a
Browse files Browse the repository at this point in the history
  • Loading branch information
jonkemp committed Jul 8, 2022
2 parents 044e04a + 581b76f commit f70982f
Show file tree
Hide file tree
Showing 99 changed files with 1,142 additions and 4,432 deletions.
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

0 comments on commit f70982f

Please sign in to comment.