Skip to content

Commit 70d4422

Browse files
committed
Merge branch 'master' into 1.3-beta
2 parents 2a153c0 + 902817a commit 70d4422

File tree

5 files changed

+284
-1
lines changed

5 files changed

+284
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"jquery": "^2.2.1",
9696
"js-beautify": "^1.6.2",
9797
"lodash": "^4.5.1",
98+
"numeral": "^1.5.3",
9899
"react": "^0.14.7",
99100
"react-addons-perf": "^0.14.7",
100101
"react-dom": "^0.14.7"

src/MJMLElementsCollection.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import Column from './components/Column'
44
import Divider from './components/Divider'
55
import Html from './components/Html'
66
import Image from './components/Image'
7+
import Invoice from './components/Invoice'
8+
import InvoiceItem from './components/Invoice/Item'
79
import List from './components/List'
810
import Raw from './components/Raw'
911
import Section from './components/Section'
@@ -17,14 +19,16 @@ const MJMLStandardElements = {
1719
'divider': Divider,
1820
'html': Html,
1921
'image': Image,
22+
'invoice-item': InvoiceItem,
23+
'invoice': Invoice,
2024
'list': List,
2125
'raw': Raw,
2226
'section': Section,
2327
'social': Social,
2428
'text': Text
2529
}
2630

27-
export const endingTags = ['mj-text', 'mj-html', 'mj-button', 'mj-list', 'mj-raw']
31+
export const endingTags = ['mj-text', 'mj-html', 'mj-button', 'mj-list', 'mj-raw', 'mj-table', 'mj-invoice-item']
2832
export const unsafeTags = ['mj-raw']
2933

3034
export const registerElement = (tagName, element, options = {}) => {

src/components/Invoice/Item.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import MJMLElement from '../decorators/MJMLElement'
2+
import React, { Component } from 'react'
3+
import _ from 'lodash'
4+
5+
@MJMLElement({
6+
tagName: 'mj-invoice-item',
7+
content: ' ',
8+
9+
attributes: {
10+
'name': '',
11+
'price': 0,
12+
'quantity': 0,
13+
14+
'color': '#747474',
15+
'font-family': 'Roboto, Ubuntu, Helvetica, Arial, sans-serif',
16+
'padding': '10px 20px',
17+
'font-size': '14px',
18+
'text-align': 'left'
19+
}
20+
})
21+
class InvoiceItem extends Component {
22+
23+
static baseStyles = {
24+
td: {
25+
fontWeight: 500,
26+
lineHeight: 1
27+
},
28+
name: { wordBreak: 'break-all' },
29+
quantity: { textAlign: 'right' }
30+
}
31+
32+
getStyles() {
33+
const { mjAttribute } = this.props
34+
35+
const styles = _.merge({}, this.constructor.baseStyles, {
36+
td: {
37+
color: mjAttribute('color'),
38+
fontFamily: mjAttribute('font-family'),
39+
padding: mjAttribute('padding'),
40+
fontSize: mjAttribute('font-size'),
41+
textAlign: mjAttribute('text-align')
42+
}
43+
})
44+
45+
styles.name = _.merge({}, styles.td, styles.name)
46+
styles.quantity = _.merge({}, styles.td, styles.quantity)
47+
48+
return styles
49+
}
50+
51+
render() {
52+
this.styles = this.getStyles()
53+
54+
const { mjAttribute } = this.props
55+
56+
return (
57+
<tr>
58+
<td style={this.styles.name}>{mjAttribute('name')}</td>
59+
<td style={this.styles.td}>{mjAttribute('price')}</td>
60+
<td style={this.styles.quantity}>{mjAttribute('quantity')}</td>
61+
</tr>
62+
)
63+
}
64+
}
65+
66+
export default InvoiceItem

src/components/Invoice/index.js

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import MJMLElement from '../decorators/MJMLElement'
2+
import React, { Component } from 'react'
3+
import _ from 'lodash'
4+
import numeral from 'numeral'
5+
6+
import MJTable from '../Table'
7+
8+
@MJMLElement({
9+
tagName: 'mj-invoice',
10+
11+
attributes: {
12+
'color': '#b9b9b9',
13+
'font-family': 'Roboto, Ubuntu, Helvetica, Arial, sans-serif',
14+
'font-size': '13px',
15+
'line-height': '22px',
16+
'border': '1px solid #ecedee',
17+
18+
'intl': 'name:Name;price:Price;quantity:Quantity'
19+
}
20+
})
21+
class Invoice extends Component {
22+
23+
constructor(props) {
24+
super(props)
25+
26+
const format = props.mjAttribute('format')
27+
const currencies = format.match(/([^-\d.,])/g)
28+
29+
this.items = props.mjChildren().filter((child) => child.get('tagName') === 'mj-invoice-item')
30+
this.format = format.replace(/([^-\d.,])/g, '$')
31+
this.currency = (currencies) ? currencies[0] : null
32+
}
33+
34+
static baseStyles = {
35+
th: {
36+
padding: '10px 20px',
37+
fontWeight: 700,
38+
textTransform: 'uppercase',
39+
textAlign: 'left'
40+
}
41+
}
42+
43+
static intl = {
44+
name : 'Name',
45+
price : 'Price',
46+
quantity: 'Quantity',
47+
total : 'Total:'
48+
}
49+
50+
getStyles() {
51+
const { mjAttribute } = this.props
52+
53+
const styles = _.merge({}, this.constructor.baseStyles, {
54+
table: {
55+
color: mjAttribute('color'),
56+
fontFamily: mjAttribute('font-family'),
57+
fontSize: mjAttribute('font-size'),
58+
lineHeight: mjAttribute('line-height')
59+
},
60+
th: {
61+
fontFamily: mjAttribute('font-family'),
62+
fontSize: mjAttribute('font-size'),
63+
lineHeight: mjAttribute('line-height')
64+
},
65+
thead: {
66+
borderBottom: mjAttribute('border')
67+
},
68+
tfoot: {
69+
borderTop: mjAttribute('border')
70+
},
71+
total: {
72+
padding: '10px 20px',
73+
fontFamily: mjAttribute('font-family'),
74+
fontSize: mjAttribute('font-size'),
75+
fontWeight: 700,
76+
lineHeight: mjAttribute('line-height'),
77+
textAlign: 'right'
78+
}
79+
})
80+
81+
styles.thQuantity = _.merge({}, styles.th, { textAlign: 'right' })
82+
83+
return styles
84+
}
85+
86+
getAttributes() {
87+
const { mjAttribute } = this.props
88+
89+
return {
90+
table: {
91+
color: mjAttribute('color'),
92+
fontFamily: mjAttribute('font-family'),
93+
fontSize: mjAttribute('font-size'),
94+
lineHeight: mjAttribute('line-height')
95+
}
96+
}
97+
}
98+
99+
getIntl() {
100+
const { mjAttribute } = this.props
101+
102+
const intl = _.cloneDeep(this.constructor.intl)
103+
104+
mjAttribute('intl').split(';').forEach((t) => {
105+
if (t && t.indexOf(':') != -1) {
106+
t = t.split(':')
107+
intl[t[0].trim()] = t[1].trim()
108+
}
109+
})
110+
111+
return intl
112+
}
113+
114+
getTotal() {
115+
const format = this.format
116+
const currency = this.currency
117+
const total = this.items.reduce((prev, item) => {
118+
const unitPrice = parseFloat(numeral().unformat(item.getIn(['attributes', 'price'])))
119+
const quantity = parseInt(item.getIn(['attributes', 'quantity']))
120+
121+
return prev + unitPrice * quantity
122+
}, 0)
123+
124+
return numeral(total).format(format).replace(/([^-\d.,])/g, currency)
125+
}
126+
127+
render() {
128+
this.styles = this.getStyles()
129+
130+
const { renderChildren } = this.props
131+
132+
const intl = this.getIntl()
133+
const attrs = this.getAttributes()
134+
135+
const total = this.getTotal()
136+
137+
return (
138+
<MJTable {...attrs.table}>
139+
<thead>
140+
<tr style={this.styles.thead}>
141+
<th style={this.styles.th}>{intl['name']}</th>
142+
<th style={this.styles.th}>{intl['price']}</th>
143+
<th style={this.styles.thQuantity}>{intl['quantity']}</th>
144+
</tr>
145+
</thead>
146+
<tbody>
147+
{renderChildren()}
148+
</tbody>
149+
<tfoot>
150+
<tr style={this.styles.tfoot}>
151+
<th style={this.styles.th} colSpan="2">{intl['total']}</th>
152+
<td style={this.styles.total}>{total}</td>
153+
</tr>
154+
</tfoot>
155+
</MJTable>
156+
)
157+
}
158+
}
159+
160+
export default Invoice

src/components/Table.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import MJMLColumnElement from './decorators/MJMLColumnElement'
2+
import React, { Component } from 'react'
3+
import _ from 'lodash'
4+
5+
@MJMLColumnElement({
6+
tagName: 'mj-table',
7+
content: '',
8+
attributes: {
9+
'align': 'left',
10+
'color': '#000',
11+
'font-family': 'Ubuntu, Helvetica, Arial, sans-serif',
12+
'font-size': '13px',
13+
'line-height': '22px',
14+
'padding': '10px 25px',
15+
'width': '100%'
16+
}
17+
})
18+
class Table extends Component {
19+
20+
static baseStyles = {}
21+
22+
getStyles() {
23+
const { mjAttribute } = this.props
24+
25+
return _.merge({}, this.constructor.baseStyles, {
26+
table: {
27+
color: mjAttribute('color'),
28+
fontFamily: mjAttribute('font-family'),
29+
fontSize: mjAttribute('font-size'),
30+
lineHeight: mjAttribute('line-height')
31+
}
32+
})
33+
}
34+
35+
render() {
36+
this.styles = this.getStyles()
37+
38+
const { mjAttribute, mjContent } = this.props
39+
40+
return (
41+
<table
42+
border="0"
43+
cellPadding="0"
44+
cellSpacing="0"
45+
dangerouslySetInnerHTML={{__html: mjContent() }}
46+
style={this.styles.table}
47+
width={mjAttribute('width')} />
48+
)
49+
}
50+
}
51+
52+
export default Table

0 commit comments

Comments
 (0)