Skip to content

Commit 25ad5eb

Browse files
authored
Merge pull request #5 from ahmadfaizk/feat/support-table
Add dynamic components
2 parents 9fb6e70 + adc0a5a commit 25ad5eb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2047
-2177
lines changed

.editorconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
root = true
2+
3+
[*.md]
4+
trim_trailing_whitespace = false
5+
max_line_length = 80
6+
7+
[*.txt]
8+
trim_trailing_whitespace = false
9+
max_line_length = 80
10+
11+
[*.{html,css}]
12+
indent_size = 2
13+
tab_width = 2
14+
max_line_length = 120

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,9 @@ coverage-check:
4444
exit 1; \
4545
else \
4646
echo "Coverage is sufficient: $$COVERAGE%"; \
47-
fi
47+
fi
48+
49+
.PHONY: update-examples
50+
update-examples:
51+
@echo "Updating example files..."
52+
go run ./examples/main.go

component.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package mailer
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
htmltemplate "html/template"
7+
"strings"
8+
)
9+
10+
type Component interface {
11+
HTML(tmpl *htmltemplate.Template) (string, error)
12+
PlainText() string
13+
}
14+
15+
var _ Component = &Action{}
16+
var _ Component = &Line{}
17+
var _ Component = &Table{}
18+
19+
// Action represents an action button in the email message.
20+
type Action struct {
21+
Text string
22+
URL string
23+
Color string
24+
}
25+
26+
type Line struct {
27+
Text string
28+
}
29+
30+
// Table represents a simple table structure for the email message.
31+
type Table struct {
32+
Headers []TableHeader
33+
Rows [][]string
34+
}
35+
36+
// TableHeader represents a header in the table.
37+
type TableHeader struct {
38+
Text string
39+
Width string
40+
Align string
41+
}
42+
43+
func (a Action) HTML(tmpl *htmltemplate.Template) (string, error) {
44+
var buf bytes.Buffer
45+
err := tmpl.ExecuteTemplate(&buf, "button", a)
46+
if err != nil {
47+
return "", err
48+
}
49+
return buf.String(), nil
50+
}
51+
52+
func (a Action) PlainText() string {
53+
return fmt.Sprintf("%s ( %s )", a.Text, a.URL)
54+
}
55+
56+
func (l Line) HTML(tmpl *htmltemplate.Template) (string, error) {
57+
var buf bytes.Buffer
58+
err := tmpl.ExecuteTemplate(&buf, "line", l)
59+
if err != nil {
60+
return "", err
61+
}
62+
return buf.String(), nil
63+
}
64+
65+
func (l Line) PlainText() string {
66+
return l.Text
67+
}
68+
69+
func (t Table) HTML(tmpl *htmltemplate.Template) (string, error) {
70+
var buf bytes.Buffer
71+
err := tmpl.ExecuteTemplate(&buf, "table", t)
72+
if err != nil {
73+
return "", err
74+
}
75+
return buf.String(), nil
76+
}
77+
78+
func (t Table) PlainText() string {
79+
var sb strings.Builder
80+
81+
// Add optional instruction
82+
83+
// Determine column widths
84+
colWidths := make([]int, len(t.Headers))
85+
for i, header := range t.Headers {
86+
colWidths[i] = len(header.Text)
87+
}
88+
for _, row := range t.Rows {
89+
for i, cell := range row {
90+
if len(cell) > colWidths[i] {
91+
colWidths[i] = len(cell)
92+
}
93+
}
94+
}
95+
96+
// Write headers
97+
for i, header := range t.Headers {
98+
sb.WriteString(t.padCell(header.Text, colWidths[i], header.Align))
99+
if i < len(t.Headers)-1 {
100+
sb.WriteString(" | ")
101+
}
102+
}
103+
sb.WriteString("\n")
104+
105+
// Write separator line
106+
for i, width := range colWidths {
107+
sb.WriteString(strings.Repeat("-", width))
108+
if i < len(colWidths)-1 {
109+
sb.WriteString("-+-")
110+
}
111+
}
112+
sb.WriteString("\n")
113+
114+
// Write rows
115+
for _, row := range t.Rows {
116+
for i, cell := range row {
117+
align := t.Headers[i].Align
118+
sb.WriteString(t.padCell(cell, colWidths[i], align))
119+
if i < len(row)-1 {
120+
sb.WriteString(" | ")
121+
}
122+
}
123+
sb.WriteString("\n")
124+
}
125+
126+
return sb.String()
127+
}
128+
129+
func (t Table) padCell(text string, width int, align string) string {
130+
switch strings.ToLower(align) {
131+
case "right":
132+
return fmt.Sprintf("%*s", width, text)
133+
case "center":
134+
padding := width - len(text)
135+
left := padding / 2
136+
right := padding - left
137+
return strings.Repeat(" ", left) + text + strings.Repeat(" ", right)
138+
default: // left
139+
return fmt.Sprintf("%-*s", width, text)
140+
}
141+
}

config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
package mailer
22

3+
import "fmt"
4+
5+
// Address represents an email address with an optional name.
6+
type Address struct {
7+
Name string
8+
Address string
9+
}
10+
11+
// String returns a string representation of the email message.
12+
func (a Address) String() string {
13+
if a.Name == "" {
14+
return a.Address
15+
}
16+
return fmt.Sprintf("%s <%s>", a.Name, a.Address)
17+
}
18+
319
type config struct {
420
theme string
521
from Address

0 commit comments

Comments
 (0)