@@ -4,12 +4,13 @@ import (
44 "bytes"
55 "fmt"
66 htmltemplate "html/template"
7+ "maps"
78 "regexp"
89 "strings"
910 "sync/atomic"
10- texttemplate "text/template"
1111 "time"
1212
13+ "github.com/ahmadfaizk/go-mailgen/component"
1314 "github.com/ahmadfaizk/go-mailgen/templates"
1415 "github.com/vanng822/go-premailer/premailer"
1516)
@@ -22,6 +23,21 @@ type Product struct {
2223 Copyright string
2324}
2425
26+ type Table struct {
27+ Data [][]Entry
28+ Columns Columns
29+ }
30+
31+ type Entry struct {
32+ Key string
33+ Value string
34+ }
35+
36+ type Columns struct {
37+ CustomWidth map [string ]string
38+ CustomAlign map [string ]string
39+ }
40+
2541// Builder represents an email message with various fields such as subject, recipients, and content.
2642// It provides methods to set these fields and generate the HTML content for the email.
2743type Builder struct {
@@ -35,26 +51,28 @@ type Builder struct {
3551 preheader string
3652 greeting string
3753 salutation string
38- actions []Action
39- components []Component
54+ actions []* component. Action
55+ components []component. Component
4056 product Product
4157}
4258
4359var defaultBuilder atomic.Pointer [Builder ]
4460
4561func init () {
46- defaultBuilder .Store (newdefaultBuilder ())
62+ defaultBuilder .Store (newDefaultBuilder ())
4763}
4864
49- func newdefaultBuilder () * Builder {
50- builder := & Builder {}
51- return builder .Theme ("default" ).
52- Greeting ("Hello" ).
53- Salutation ("Best regards" ).
54- Product (Product {
55- Name : "GoMailgen" ,
56- URL : "https://github.com/ahmadfaizk/go-mailgen" ,
57- })
65+ func newDefaultBuilder () * Builder {
66+ return & Builder {
67+ theme : "default" ,
68+ greeting : "Hello" ,
69+ salutation : "Best regards" ,
70+ product : Product {
71+ Name : "Go-Mailgen" ,
72+ URL : "https://github.com/ahmadfaizk/go-mailgen" ,
73+ Copyright : fmt .Sprintf ("© %d Go-Mailgen. All rights reserved." , time .Now ().Year ()),
74+ },
75+ }
5876}
5977
6078func (b * Builder ) clone () * Builder {
@@ -68,8 +86,8 @@ func (b *Builder) clone() *Builder {
6886 preheader : b .preheader ,
6987 greeting : b .greeting ,
7088 salutation : b .salutation ,
71- actions : append ([]Action {}, b .actions ... ),
72- components : append ([]Component {}, b .components ... ),
89+ actions : append ([]* component. Action {}, b .actions ... ),
90+ components : append ([]component. Component {}, b .components ... ),
7391 product : b .product ,
7492 }
7593}
@@ -84,11 +102,11 @@ func SetDefault(b *Builder) {
84102 defaultBuilder .Store (b )
85103}
86104
87- // NewMessage creates a new Message instance with default values for greeting, salutation, and product.
105+ // New creates a new Message instance with default values for greeting, salutation, and product.
88106//
89107// Example usage:
90108//
91- // messsage := mailer.NewMessage().
109+ // message := mailer.NewMessage().
92110// Subject("Reset Password").
93111// To("recipient@example.com").
94112// Line("Click the button below to reset your password").
@@ -117,7 +135,7 @@ func (b *Builder) From(address string, name ...string) *Builder {
117135 return b
118136}
119137
120- // To adds a recipient's email address to the email message.
138+ // To add a recipient's email address to the email message.
121139func (b * Builder ) To (to string , others ... string ) * Builder {
122140 values := b .filterRecipients (to , others ... )
123141 if len (values ) == 0 {
@@ -143,7 +161,8 @@ func (b *Builder) filterRecipients(first string, others ...string) []string {
143161 return filtered
144162}
145163
146- func (b * Builder ) CC (cc string , others ... string ) * Builder {
164+ // Cc adds a carbon copy (CC) recipient's email address to the email message.
165+ func (b * Builder ) Cc (cc string , others ... string ) * Builder {
147166 values := b .filterRecipients (cc , others ... )
148167 if len (values ) == 0 {
149168 return b
@@ -152,7 +171,8 @@ func (b *Builder) CC(cc string, others ...string) *Builder {
152171 return b
153172}
154173
155- func (b * Builder ) BCC (bcc string , others ... string ) * Builder {
174+ // Bcc adds a blind carbon copy (BCC) recipient's email address to the email message.
175+ func (b * Builder ) Bcc (bcc string , others ... string ) * Builder {
156176 values := b .filterRecipients (bcc , others ... )
157177 if len (values ) == 0 {
158178 return b
@@ -179,7 +199,7 @@ func (b *Builder) Preheader(preheader string) *Builder {
179199}
180200
181201// Greeting sets the greeting line of the email message.
182- // Default is "Hello".
202+ // The default is "Hello".
183203func (b * Builder ) Greeting (greeting string ) * Builder {
184204 b .greeting = greeting
185205 return b
@@ -195,7 +215,7 @@ func (b *Builder) Salutation(salutation string) *Builder {
195215// Line adds a line of text to the email message.
196216// If an action is set, it will be added to the outro lines; otherwise, it will be added to the intro lines.
197217func (b * Builder ) Line (text string ) * Builder {
198- b .components = append (b .components , Line {Text : text })
218+ b .components = append (b .components , component. Line {Text : text })
199219 return b
200220}
201221
@@ -210,7 +230,7 @@ func (b *Builder) Linef(format string, args ...interface{}) *Builder {
210230// It creates a button in the email that links to the specified URL.
211231// The action can also include an optional instruction and color for the button.
212232func (b * Builder ) Action (text , url string , color ... string ) * Builder {
213- action := Action {
233+ action := & component. Action {
214234 Text : text ,
215235 URL : url ,
216236 Color : "#3869D4" ,
@@ -225,9 +245,11 @@ func (b *Builder) Action(text, url string, color ...string) *Builder {
225245
226246// Product sets the product information for the email message.
227247func (b * Builder ) Product (product Product ) * Builder {
248+ defaultProduct := defaultBuilder .Load ().product
249+
228250 b .product = product
229251 if b .product .Name == "" {
230- b .product .Name = "GoMailgen"
252+ b .product .Name = defaultProduct . Name
231253 }
232254 if b .product .URL == "" {
233255 b .product .URL = "#"
@@ -253,23 +275,27 @@ func (b *Builder) Product(product Product) *Builder {
253275// },
254276// })
255277func (b * Builder ) Table (table Table ) * Builder {
256- // Ensure headers have default values for width and alignment
257- for i , header := range table .Headers {
258- if header .Width == "" {
259- table .Headers [i ].Width = "auto"
260- }
261- if header .Align == "" {
262- table .Headers [i ].Align = "left"
263- }
264- }
265- if table .Rows == nil {
266- table .Rows = [][]string {}
267- }
268- if len (table .Rows ) == 0 && len (table .Headers ) == 0 {
278+ if len (table .Data ) == 0 {
269279 return b // No table to add
270280 }
271-
272- b .components = append (b .components , table )
281+ // headers := make([]string, 0, len(table.Data[0]))
282+ // for _, entry := range table.Data[0] {
283+ // headers = append(headers, entry.Key)
284+
285+ // width, ok := table.Columns.CustomWidth[entry.Key]
286+ // if !ok || width == "" {
287+ // width = "auto"
288+ // }
289+ // table.Columns.CustomWidth[entry.Key] = width
290+
291+ // align, ok := table.Columns.CustomAlign[entry.Key]
292+ // if !ok || align == "" {
293+ // align = "left"
294+ // }
295+ // table.Columns.CustomAlign[entry.Key] = align
296+ // }
297+
298+ b .components = append (b .components , table .component ())
273299 return b
274300}
275301
@@ -303,7 +329,7 @@ type templateData struct {
303329 Salutation string
304330 ComponentsHTML []htmltemplate.HTML
305331 ComponentsText []string
306- Actions []Action // Used for sub-copy in HTML
332+ Actions []* component. Action // Used for sub-copy in HTML
307333 Product Product
308334}
309335
@@ -314,13 +340,6 @@ func (b *Builder) htmlTemplate() *htmltemplate.Template {
314340 return templates .DefaultHtmlTmpl
315341}
316342
317- func (b * Builder ) plainTextTemplate () * texttemplate.Template {
318- if b .theme == "plain" {
319- return templates .PlainPlainTextTmpl
320- }
321- return templates .DefaultPlainTextTmpl
322- }
323-
324343func (b * Builder ) generateHTML () (string , error ) {
325344 tmpl := b .htmlTemplate ()
326345
@@ -359,15 +378,18 @@ func (b *Builder) generateHTML() (string, error) {
359378}
360379
361380func (b * Builder ) generatePlaintext () (string , error ) {
362- tmpl := b . plainTextTemplate ()
381+ tmpl := templates . PlainTextTmpl
363382 var componentsText []string
364383 for _ , comp := range b .components {
365- text := comp .PlainText ()
384+ text , err := comp .PlainText (tmpl )
385+ if err != nil {
386+ return "" , err
387+ }
366388 componentsText = append (componentsText , text )
367389 }
368390
369391 data := templateData {
370- Greeting : boxString ( fmt . Sprintf ( "%s," , b .greeting )) ,
392+ Greeting : b .greeting ,
371393 Preheader : b .preheader ,
372394 Salutation : b .salutation ,
373395 Product : b .product ,
@@ -386,24 +408,28 @@ func (b *Builder) generatePlaintext() (string, error) {
386408 return text , nil
387409}
388410
389- func boxString (s string ) string {
390- // Find the max line length (in case of multi-line input)
391- lines := strings .Split (s , "\n " )
392- maxLen := 0
393- for _ , line := range lines {
394- if len (line ) > maxLen {
395- maxLen = len (line )
411+ func (t Table ) component () component.Component {
412+ if len (t .Data ) == 0 {
413+ return nil
414+ }
415+ tableComponent := component.Table {
416+ Data : make ([][]component.Entry , len (t .Data )),
417+ Columns : component.Columns {
418+ CustomWidth : make (map [string ]string ),
419+ CustomAlign : make (map [string ]string ),
420+ },
421+ }
422+ for i , row := range t .Data {
423+ tableComponent .Data [i ] = make ([]component.Entry , len (row ))
424+ for j , entry := range row {
425+ tableComponent.Data [i ][j ] = component.Entry {
426+ Key : entry .Key ,
427+ Value : entry .Value ,
428+ }
396429 }
397430 }
431+ maps .Copy (tableComponent .Columns .CustomWidth , t .Columns .CustomWidth )
432+ maps .Copy (tableComponent .Columns .CustomAlign , t .Columns .CustomAlign )
398433
399- // The border should match the longest line length
400- border := strings .Repeat ("*" , maxLen )
401-
402- // Combine
403- var b strings.Builder
404- b .WriteString (border + "\n " )
405- b .WriteString (s + "\n " )
406- b .WriteString (border )
407-
408- return b .String ()
434+ return & tableComponent
409435}
0 commit comments