Skip to content

Commit

Permalink
v0.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas committed Aug 9, 2024
1 parent 574af48 commit a399659
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 188 deletions.
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> DEMO: https://w99910.github.io/html-to-table/
### It's zero dependency and light-weight with **18.2kb** unpacked size.
### It's zero dependency and light-weight.

I have been trying to send emails using html layout but there are lots of html elements and styles that email clients
don't support.
Expand Down Expand Up @@ -40,10 +40,26 @@ html2table.excludeElementByPattern('toolbar')
html2table.convert(document.querySelector('your-element-to-convert'));
```

## Tips

- `Base64` image data does not work. So use `absolute` image url.
- Don't use `linear-gradient` css function in inline css. Instead, create the gradient as an image and load it using `url`.
```css
{
background: url("link-to-your-image")
}
```
- Use `background-color` as a fall-back background color if image would not be working in some email clients.


## MIT LICENSE

## CHANGELOG

- **0.1.2**
- Add `bgcolor` attribute for fall-back `background` css style.
- `align` and `valign` of table now consider the css properties of parent element. if there is no parent element, default value will be used.
- Deprecated feature that convert SVG to Base64 PNG.
- **0.1.1**
- Add `alt` and `title` to allowed attribute when it is cloned.
- Fix `width` being `0` in some cases.
Expand Down
2 changes: 1 addition & 1 deletion dist/html-to-table.cjs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
"use strict";const h={supportedCssProperties:["backgroundColor","background","backgroundImage","backgroundPosition","border(.)+","margin(.)+","padding(.)+","width","height","maxWidth","maxHeight","color","fontSize","font","fontWeight","textAlign","textDecoration","-webkitTextDecorationColor","textIndent","textTransform","letterSpacing","listStyleType","listStylePosition"],supportedHTMLTags:["p","span","h1","h2","h3","h4","h5","h6","br","strong","em","blockquote","ul","li","ol","pre","a","image","img","svg","table","td","tr","tbody","code"]};function b(u,n){let t=u.split(" ")[0];const e=parseFloat(t);if(isNaN(e))return!1;if(t.endsWith("px"))return e;if(t.endsWith("%"))return!1;if(t.endsWith("em")||t.endsWith("rem")){const l=parseFloat(getComputedStyle(n).fontSize);return e*l}else if(t.endsWith("vw")){const l=window.innerWidth;return e/100*l}else if(t.endsWith("vh")){const l=window.innerHeight;return e/100*l}else if(t.endsWith("vmin")){const l=Math.min(window.innerWidth,window.innerHeight);return e/100*l}else if(t.endsWith("vmax")){const l=Math.max(window.innerWidth,window.innerHeight);return e/100*l}else return e}class m{constructor(){this.supportedCSSProperties=h.supportedCssProperties}parse(n,s=!1){if(!n)return{};let t=window.getComputedStyle(n),e={};switch(e.tableAlign="left",t.textAlign!=="start"&&(e.tableAlign=t.textAlign),e.tableVAlign="top",t.justifyContent){case"start":t.flexDirection==="column"?e.tableVAlign="top":e.tableAlign="left";break;case"center":t.flexDirection==="column"?e.tableVAlign="center":e.tableAlign="center";break;case"end":t.flexDirection==="column"?e.tableVAlign="bottom":e.tableAlign="right";break}switch(t.alignItems){case"start":t.flexDirection==="column"?e.tableAlign="top":e.tableVAlign="left";break;case"center":t.flexDirection==="column"?e.tableAlign="center":e.tableVAlign="center";break;case"end":t.flexDirection==="column"?e.tableAlign="bottom":e.tableVAlign="right";break}["left","center","right"].includes(t.alignSelf)&&(e.margin="auto auto");let l=t.margin.split(" ");l[0]&&l[0]==="auto"&&(e.tableAlign="center"),l[1]&&l[1]==="auto"&&(e.tableVAlign="center");let g=i=>{for(const r of this.supportedCSSProperties)if(new RegExp(r+"$").test(i))return!0;return!1},c=n.parentElement;return Object.keys(t).forEach(i=>{if(g(i))try{if(i.startsWith("font")){e[i]=t[i];return}let r=b(t[i],c);if(r&&c&&s&&((i.includes("width")||i.includes("left")||i.includes("right"))&&(r=r/c.getBoundingClientRect().width*100+"%"),(i.includes("height")||i.includes("top")||i.includes("bottom"))&&(r=r/c.getBoundingClientRect().height*100+"%")),i.includes("width"),i==="display"&&!t[i].includes("block"))return;typeof r=="number"&&(r+="px",r==="0px"&&(r="auto")),e[i]=r||t[i]}catch{console.log(i,t[i])}}),e}}class w{constructor(){this._excludeElementPattern=null,this.settings=h,this.cssParser=new m,this.img=new Image}excludeElementByPattern(n){return this._excludeElementPattern=new RegExp(n),this}applyCss(n,s={},t=[],e=[]){Object.keys(s).forEach(l=>{e.length>0&&!e.includes(l)||t.includes(l)||(n.style[l]=s[l])})}convert(n,s){if(this._excludeElementPattern&&(this._excludeElementPattern.test(n.className)||this._excludeElementPattern.test(n.id)))return null;let t={width:"",rows:{}},e=this.cssParser.parse(n);if(n.nodeType!==Element.TEXT_NODE){let g=Array.from(n.childNodes),c=Array.from(g).filter(r=>r.nodeType===Node.ELEMENT_NODE&&["DIV","SECTION","ARTICLE","MAIN","ASIDE","IMG"].includes(r.tagName)).length===0,i=0;g.forEach((r,a)=>{if(t.rows.hasOwnProperty(i)||(t.rows[i]={children:[],top:0,bottom:0}),r.nodeType===Element.TEXT_NODE||c){if(r.nodeType===Element.TEXT_NODE&&r.textContent.trim().length===0)return;let o=t.rows[i].children.length-1;if(o<0){let d=document.createElement("div");d.setAttribute("temp","true"),d.appendChild(this.getCloneNode(r)),t.rows[i].children.push(d)}else{let d=t.rows[i].children[o],f=this.getCloneNode(r);d.appendChild(f),t.rows[i].children[o]=d}return}if(r.nodeType===Element.ELEMENT_NODE){let o=r.getBoundingClientRect();t.rows[i].top===0&&(t.rows[i].top=o.top),t.rows[i].bottom===0&&(t.rows[i].bottom=o.bottom),o.top>=t.rows[i].bottom&&(i++,t.rows[i]={children:[],top:o.top,bottom:o.bottom}),t.rows[i].top>o.top&&(t.rows[i].top=o.top),o.bottom>t.rows[i].bottom&&(t.rows[i].bottom=o.bottom);let d=this.settings.supportedHTMLTags.includes(r.tagName.toLowerCase())?this.getCloneNode(r):this.convert(r,n);if(!d)return;t.rows[i].children.push(d)}})}else n.textContent.trim().length>0&&t.rows[0].push(n.textContent);let l=this.createTable();return l.setAttribute("align",e.tableAlign??"left"),l.setAttribute("valign",e.tableVAlign??"top"),this.applyCss(l,e,["width"]),s||(l.style.width="100%",l.style.height="100%",l.style.margin="0",l.style.padding="0"),Object.keys(t.rows).forEach(g=>{let c=document.createElement("tr");t.rows[g].children.forEach((i,r)=>{let a=document.createElement("td");if(a.setAttribute("align",e.tableAlign??"left"),a.setAttribute("valign",e.tableVAlign??"top"),s&&(a.style.width=e.width,i.tagName!=="TABLE"&&i.getBoundingClientRect)){let o=i.getBoundingClientRect().width;o>0&&(a.style.width=o+"px")}typeof i=="string"?a.innerHTML=i:i.getAttribute("temp")?(a.style.display="inline-block",Array.from(i.childNodes).forEach(o=>{a.appendChild(o)})):a.appendChild(i),c.appendChild(a)}),l.querySelector("tbody").appendChild(c)}),l}createTable(){let n=document.createElement("table");n.setAttribute("border",0),n.setAttribute("cellpadding",0),n.setAttribute("cellspacing",0);let s=document.createElement("tbody");return n.appendChild(s),n}convertSvgToImage(n){if(!n instanceof SVGElement)return n;let s=document.createElement("img");s.style.verticalAlign="middle",n.setAttribute("stroke",window.getComputedStyle(n).color);let t=new Image;const e=document.createElement("canvas"),l=new XMLSerializer().serializeToString(n);return t.onload=function(){e.width=n.getBoundingClientRect().width>0?n.getBoundingClientRect().width:n.getAttribute("width"),e.height=n.getBoundingClientRect().height?n.getBoundingClientRect().height:n.getAttribute("height"),e.getContext("2d").drawImage(t,0,0),s.src=e.toDataURL("image/png")},t.src="data:image/svg+xml;base64,"+btoa(l),s}getCloneNode(n){if(n instanceof SVGElement)return this.convertSvgToImage(n);let s=n.cloneNode(!1);if(n.nodeType===Node.ELEMENT_NODE){let e=this.cssParser.parse(n);Array.from(n.attributes).forEach(l=>{["href","src","title","alt"].includes(l.name)||s.removeAttribute(l.name)}),Object.keys(e).forEach(l=>{s.style[l]=e[l]}),s.style.display="inline-block"}let t=n.childNodes;return Array.from(t).forEach(e=>{s.appendChild(this.getCloneNode(e))}),s}}module.exports=w;
"use strict";const f={supportedCssProperties:["backgroundColor","background","backgroundImage","backgroundPosition","border(.)+","margin(.)+","padding(.)+","width","height","maxWidth","maxHeight","color","fontSize","font","fontWeight","textAlign","textDecoration","-webkitTextDecorationColor","textIndent","textTransform","letterSpacing","listStyleType","listStylePosition"],supportedHTMLTags:["p","span","h1","h2","h3","h4","h5","h6","br","strong","em","blockquote","ul","li","ol","pre","a","image","img","svg","table","td","tr","tbody","code"]};function m(g,l){let t=g.split(" ")[0];const i=parseFloat(t);if(isNaN(i))return!1;if(t.endsWith("px"))return i;if(t.endsWith("%"))return!1;if(t.endsWith("em")||t.endsWith("rem")){const n=parseFloat(getComputedStyle(l).fontSize);return i*n}else if(t.endsWith("vw")){const n=window.innerWidth;return i/100*n}else if(t.endsWith("vh")){const n=window.innerHeight;return i/100*n}else if(t.endsWith("vmin")){const n=Math.min(window.innerWidth,window.innerHeight);return i/100*n}else if(t.endsWith("vmax")){const n=Math.max(window.innerWidth,window.innerHeight);return i/100*n}else return i}class p{constructor(){this.supportedCSSProperties=f.supportedCssProperties}parse(l,o=!1){if(!l)return{};let t=window.getComputedStyle(l),i={};switch(i.tableAlign="left",t.textAlign!=="start"&&(i.tableAlign=t.textAlign),i.tableVAlign="top",t.justifyContent){case"start":t.flexDirection==="column"?i.tableVAlign="top":i.tableAlign="left";break;case"center":t.flexDirection==="column"?i.tableVAlign="center":i.tableAlign="center";break;case"end":t.flexDirection==="column"?i.tableVAlign="bottom":i.tableAlign="right";break}switch(t.alignItems){case"start":t.flexDirection==="column"?i.tableAlign="top":i.tableVAlign="left";break;case"center":t.flexDirection==="column"?i.tableAlign="center":i.tableVAlign="center";break;case"end":t.flexDirection==="column"?i.tableAlign="bottom":i.tableVAlign="right";break}["left","center","right"].includes(t.alignSelf)&&(i.margin="auto auto");let n=t.margin.split(" ");n[0]&&n[0]==="auto"&&(i.tableAlign="center"),n[1]&&n[1]==="auto"&&(i.tableVAlign="center");let c=r=>{for(const e of this.supportedCSSProperties)if(new RegExp(e+"$").test(r))return!0;return!1},u=l.parentElement;return Object.keys(t).forEach(r=>{if(c(r))try{if(r.startsWith("font")){i[r]=t[r];return}let e=m(t[r],u);if(e&&u&&o&&((r.includes("width")||r.includes("left")||r.includes("right"))&&(e=e/u.getBoundingClientRect().width*100+"%"),(r.includes("height")||r.includes("top")||r.includes("bottom"))&&(e=e/u.getBoundingClientRect().height*100+"%")),r.includes("width"),r==="display"&&!t[r].includes("block"))return;typeof e=="number"&&(e+="px",e==="0px"&&(e="auto")),i[r]=e||t[r]}catch{console.log(r,t[r])}}),i}}class w{constructor(){this._excludeElementPattern=null,this.settings=f,this.cssParser=new p,this.img=new Image}excludeElementByPattern(l){return this._excludeElementPattern=new RegExp(l),this}applyCss(l,o={},t=[],i=[]){Object.keys(o).forEach(n=>{i.length>0&&!i.includes(n)||t.includes(n)||(l.style[n]=o[n])})}convert(l,o){if(this._excludeElementPattern&&(this._excludeElementPattern.test(l.className)||this._excludeElementPattern.test(l.id)))return null;let t={width:"",rows:{}};if(l.nodeType!==Element.TEXT_NODE){let u=Array.from(l.childNodes),r=Array.from(u).filter(a=>a.nodeType===Node.ELEMENT_NODE&&["DIV","SECTION","ARTICLE","MAIN","ASIDE","IMG"].includes(a.tagName)).length===0,e=0;u.forEach((a,d)=>{if(t.rows.hasOwnProperty(e)||(t.rows[e]={children:[],top:0,bottom:0}),a.nodeType===Element.TEXT_NODE||r){if(a.nodeType===Element.TEXT_NODE&&a.textContent.trim().length===0)return;let s=t.rows[e].children.length-1;if(s<0){let h=document.createElement("div");h.setAttribute("temp","true"),h.appendChild(this.getCloneNode(a)),t.rows[e].children.push(h)}else{let h=t.rows[e].children[s],b=this.getCloneNode(a);h.appendChild(b),t.rows[e].children[s]=h}return}if(a.nodeType===Element.ELEMENT_NODE){let s=a.getBoundingClientRect();t.rows[e].top===0&&(t.rows[e].top=s.top),t.rows[e].bottom===0&&(t.rows[e].bottom=s.bottom),s.top>=t.rows[e].bottom&&(e++,t.rows[e]={children:[],top:s.top,bottom:s.bottom}),t.rows[e].top>s.top&&(t.rows[e].top=s.top),s.bottom>t.rows[e].bottom&&(t.rows[e].bottom=s.bottom);let h=this.settings.supportedHTMLTags.includes(a.tagName.toLowerCase())?this.getCloneNode(a):this.convert(a,l);if(!h)return;t.rows[e].children.push(h)}})}else l.textContent.trim().length>0&&t.rows[0].push(l.textContent);let i=this.cssParser.parse(o),n=this.cssParser.parse(l),c=this.createTable();return c.setAttribute("align",i.tableAlign??"left"),c.setAttribute("valign",i.tableVAlign??"top"),c.setAttribute("bgcolor",n.backgroundColor??n.background),this.applyCss(c,n,["width"]),o||(c.style.width="100%",c.style.height="100%",c.style.margin="0",c.style.padding="0"),Object.keys(t.rows).forEach(u=>{let r=document.createElement("tr");t.rows[u].children.forEach((e,a)=>{let d=document.createElement("td");if(d.setAttribute("align",n.tableAlign??"left"),d.setAttribute("valign",n.tableVAlign??"top"),o&&(d.style.width=n.width,e.tagName!=="TABLE"&&e.getBoundingClientRect)){let s=e.getBoundingClientRect().width;s>0&&(d.style.width=s+"px")}typeof e=="string"?d.innerHTML=e:e.getAttribute("temp")?(d.style.display="inline-block",Array.from(e.childNodes).forEach(s=>{d.appendChild(s)})):d.appendChild(e),r.appendChild(d)}),c.querySelector("tbody").appendChild(r)}),c}createTable(){let l=document.createElement("table");l.setAttribute("border",0),l.setAttribute("cellpadding",0),l.setAttribute("cellspacing",0);let o=document.createElement("tbody");return l.appendChild(o),l}getCloneNode(l){let o=l.cloneNode(!1);if(l.nodeType===Node.ELEMENT_NODE){let i=this.cssParser.parse(l);Array.from(l.attributes).forEach(n=>{["href","src","title","alt"].includes(n.name)||o.removeAttribute(n.name)}),Object.keys(i).forEach(n=>{o.style[n]=i[n]}),o.style.display="inline-block"}let t=l.childNodes;return Array.from(t).forEach(i=>{o.appendChild(this.getCloneNode(i))}),o}}module.exports=w;
Loading

0 comments on commit a399659

Please sign in to comment.