瀏覽代碼

修改渲染引擎

fancl 1 年之前
父節點
當前提交
7fbf57b27e
共有 6 個文件被更改,包括 281 次插入53 次删除
  1. 1 1
      go.mod
  2. 191 16
      pgenr.go
  3. 2 11
      render.go
  4. 15 4
      render_test.go
  5. 62 21
      theme/default.go
  6. 10 0
      unexported.go

+ 1 - 1
go.mod

@@ -5,6 +5,7 @@ go 1.17
 require (
 	github.com/Masterminds/sprig v2.22.0+incompatible
 	github.com/vanng822/go-premailer v1.20.1
+	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
 )
 
 require (
@@ -20,5 +21,4 @@ require (
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/vanng822/css v1.0.1 // indirect
 	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
-	golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
 )

+ 191 - 16
pgenr.go

@@ -1,5 +1,11 @@
 package pgenr
 
+import (
+	"golang.org/x/net/html/atom"
+	"html"
+	"strings"
+)
+
 const (
 	TextThemeSuccess = "success"
 	TextThemeDanger  = "danger"
@@ -11,35 +17,45 @@ type (
 	ButtonOption func(btn *Button)
 
 	Text struct {
+		Tag     atom.Atom
 		Content string
 		Theme   string
 		Color   string
+		Style   map[string]string
 	}
 
 	Button struct {
 		Url   string
 		Text  string
 		Color string
+		Style map[string]string
 	}
 
 	Entry struct {
-		Title string
+		Title *Text
 		Items map[string]*Text
 	}
 
 	Action struct {
-		Instructions string
+		Instructions *Text
 		Button       *Button
 		InviteCode   string
 	}
 
+	Table struct {
+		Title  *Text
+		Header []*Text
+		Body   [][]*Text
+	}
+
 	Page struct {
 		Title     string
 		Head      string
-		Intros    []string
+		Intros    []*Text
 		Entries   []*Entry
-		Actions   []Action
-		Outros    []string
+		Actions   []*Action
+		Tables    []*Table
+		Outros    []*Text
 		Copyright string
 	}
 )
@@ -62,8 +78,13 @@ func (page *Page) SetCopyright(s string) *Page {
 	return page
 }
 
-func (page *Page) AddIntro(s string) *Page {
-	page.Intros = append(page.Intros, s)
+func (page *Page) AddPlainIntro(s string) *Page {
+	page.Intros = append(page.Intros, NewText(s, WithTextTag(atom.P)))
+	return page
+}
+
+func (page *Page) AddIntro(t *Text) *Page {
+	page.Intros = append(page.Intros, t)
 	return page
 }
 
@@ -72,34 +93,50 @@ func (page *Page) AddEntry(e *Entry) *Page {
 	return page
 }
 
+func (page *Page) AddTable(t *Table) *Page {
+	page.Tables = append(page.Tables, t)
+	return page
+}
+
 func (page *Page) AddButtonAction(s string, btn *Button) *Page {
-	page.Actions = append(page.Actions, Action{Instructions: s, Button: btn})
+	page.Actions = append(page.Actions, &Action{Instructions: NewText(s, WithTextTag(atom.P)), Button: btn})
 	return page
 }
 
 func (page *Page) AddInviteCodeAction(s string, code string) *Page {
-	page.Actions = append(page.Actions, Action{Instructions: s, InviteCode: code})
+	page.Actions = append(page.Actions, &Action{Instructions: NewText(s, WithTextTag(atom.P)), InviteCode: code})
+	return page
+}
+
+func (page *Page) AddOutro(t *Text) *Page {
+	page.Outros = append(page.Outros, t)
 	return page
 }
 
-func (page *Page) AddOutro(s string) *Page {
-	page.Outros = append(page.Outros, s)
+func (page *Page) AddPlainOutro(s string) *Page {
+	page.Outros = append(page.Outros, NewText(s, WithTextTag(atom.P)))
 	return page
 }
 
+func (page *Page) Escape() {
+	page.Head = html.EscapeString(page.Head)
+	page.Copyright = html.EscapeString(page.Copyright)
+}
+
 func NewPage(title string) *Page {
 	return &Page{
 		Title:   title,
-		Intros:  make([]string, 0),
+		Intros:  make([]*Text, 0),
 		Entries: make([]*Entry, 0),
-		Actions: make([]Action, 0),
-		Outros:  make([]string, 0),
+		Actions: make([]*Action, 0),
+		Outros:  make([]*Text, 0),
+		Tables:  make([]*Table, 0),
 	}
 }
 
 func NewEntry(title string) *Entry {
 	return &Entry{
-		Title: title,
+		Title: NewText(title, WithTextTag(atom.P)),
 		Items: make(map[string]*Text),
 	}
 }
@@ -110,12 +147,141 @@ func WithTextTheme(s string) TextOption {
 	}
 }
 
+func WithTextStyle(ms map[string]string) TextOption {
+	return func(t *Text) {
+		t.Style = ms
+	}
+}
+
+func WithTextTag(tag atom.Atom) TextOption {
+	return func(t *Text) {
+		t.Tag = tag
+	}
+}
+
 func WithTextColor(color string) TextOption {
 	return func(t *Text) {
 		t.Color = color
 	}
 }
 
+func (button *Button) String() string {
+	var (
+		sb strings.Builder
+	)
+	if button.Style == nil {
+		button.Style = make(map[string]string)
+	}
+	if button.Color != "" {
+		button.Style["color"] = button.Color
+	}
+	sb.WriteString("<a class='button'")
+	if button.Url != "" {
+		sb.WriteString(" href='" + button.Url + "'")
+	}
+	if len(button.Style) > 0 {
+		sb.WriteString(" style='")
+		for k, v := range button.Style {
+			sb.WriteString(k)
+			sb.WriteString(":")
+			sb.WriteString(v)
+			sb.WriteString(";")
+		}
+		sb.WriteString("'")
+	}
+	sb.WriteString(">")
+	sb.WriteString(html.EscapeString(button.Text))
+	sb.WriteString("</a>")
+	return sb.String()
+}
+
+func (table *Table) SetHead(header ...*Text) {
+	table.Header = header
+}
+
+func (table *Table) AddCell(texts ...*Text) {
+	if table.Body == nil {
+		table.Body = make([][]*Text, 0)
+	}
+	table.Body = append(table.Body, texts)
+}
+
+func (table *Table) String() string {
+	var (
+		sb strings.Builder
+	)
+	sb.WriteString("<div class='table-wrapper'>")
+	if table.Title != nil {
+		sb.WriteString("<div class='table-title'>")
+		sb.WriteString(table.Title.String())
+		sb.WriteString("</div>")
+	}
+	sb.WriteString("<table class='table'>")
+
+	if len(table.Header) > 0 {
+		sb.WriteString("<thead><tr>")
+		for _, text := range table.Header {
+			sb.WriteString("<th>")
+			sb.WriteString(text.String())
+			sb.WriteString("</th>")
+		}
+		sb.WriteString("</tr></thead>")
+	}
+	if len(table.Body) > 0 {
+		sb.WriteString("<tbody>")
+		for _, cell := range table.Body {
+			sb.WriteString("<tr>")
+			for _, text := range cell {
+				sb.WriteString("<td>")
+				sb.WriteString(text.String())
+				sb.WriteString("</td>")
+			}
+			sb.WriteString("</tr>")
+		}
+		sb.WriteString("</tbody>")
+	}
+	sb.WriteString("</table></div>")
+	return sb.String()
+}
+
+func (text *Text) String() string {
+	var (
+		sb strings.Builder
+	)
+	if text.Style == nil {
+		text.Style = make(map[string]string)
+	}
+	if text.Color != "" {
+		text.Style["color"] = text.Color
+	}
+	if text.Tag == 0 {
+		text.Tag = atom.Span
+	}
+	sb.WriteString("<")
+	sb.WriteString(text.Tag.String())
+	if text.Theme != "" {
+		sb.WriteString(" class='text-")
+		sb.WriteString(text.Theme)
+		sb.WriteString("'")
+	}
+	if len(text.Style) > 0 {
+		sb.WriteString(" style='")
+		for k, v := range text.Style {
+			sb.WriteString(k)
+			sb.WriteString(":")
+			sb.WriteString(v)
+			sb.WriteString(";")
+		}
+		sb.WriteString("'")
+	}
+	sb.WriteString(">")
+	sb.WriteString(html.EscapeString(text.Content))
+	sb.WriteString("</")
+	sb.WriteString(text.Tag.String())
+	sb.WriteString(">")
+	return sb.String()
+}
+
 func NewButton(label, link string, opts ...ButtonOption) *Button {
 	btn := &Button{Text: label, Url: link}
 	for _, cb := range opts {
@@ -125,9 +291,18 @@ func NewButton(label, link string, opts ...ButtonOption) *Button {
 }
 
 func NewText(s string, opts ...TextOption) *Text {
-	txt := &Text{Content: s}
+	txt := &Text{Content: s, Tag: atom.Span}
 	for _, cb := range opts {
 		cb(txt)
 	}
 	return txt
 }
+
+func NewTable(title *Text) *Table {
+	table := &Table{
+		Title:  title,
+		Header: make([]*Text, 0),
+		Body:   make([][]*Text, 0),
+	}
+	return table
+}

+ 2 - 11
render.go

@@ -3,9 +3,8 @@ package pgenr
 import (
 	"bytes"
 	"git.nspix.com/golang/pgenr/theme"
-	"github.com/Masterminds/sprig"
 	"github.com/vanng822/go-premailer/premailer"
-	"html/template"
+	"text/template"
 )
 
 const (
@@ -25,12 +24,6 @@ type (
 	}
 )
 
-var templateFunc = template.FuncMap{
-	"url": func(s string) template.URL {
-		return template.URL(s)
-	},
-}
-
 func Render(page *Page, cbs ...RenderOption) (str string, err error) {
 	var (
 		tpl     *template.Template
@@ -44,9 +37,7 @@ func Render(page *Page, cbs ...RenderOption) (str string, err error) {
 	for _, cb := range cbs {
 		cb(opts)
 	}
-	if tpl, err = template.New(Name).Funcs(sprig.FuncMap()).Funcs(templateFunc).Funcs(template.FuncMap{
-		"safe": func(s string) template.HTML { return template.HTML(s) }, // Used for keeping comments in generated template
-	}).Parse(opts.Theme.Template()); err != nil {
+	if tpl, err = template.New(Name).Parse(opts.Theme.Template()); err != nil {
 		return
 	}
 	if err = tpl.Execute(&buffer, Template{page}); err != nil {

+ 15 - 4
render_test.go

@@ -1,19 +1,30 @@
 package pgenr
 
 import (
+	"fmt"
 	"io/ioutil"
 	"testing"
+	"time"
 )
 
+func TestAtom(t *testing.T) {
+	fmt.Println(time.Now().Add(-2 * time.Hour).Unix())
+}
+
 func TestRender(t *testing.T) {
 	page := NewPage("Hi Jon Snow,")
 	page.SetHead("Hi Jon Snow,").
 		SetCopyright("Copyright © 2017 Hermes. All rights reserved")
-	page.AddIntro("Welcome to Hermes! We're very excited to have you on board.")
+	page.AddPlainIntro("Welcome to Hermes! We're very excited to have you on board.")
 	//page.AddEntry(NewEntry("Welcome to Hermes").AddItem("Stock1", NewText("14.58", WithTextTheme(TextThemeSuccess))).AddItem("asddas", NewText("15.8", WithTextTheme(TextThemeDanger))))
-	page.AddOutro("Need help, or have questions? Just reply to this email, we'd love to help.")
-	page.AddOutro("Yours truly,")
-	page.AddOutro("Hermes - http://google.com")
+	page.AddPlainOutro("Need help, or have questions? Just reply to this email, we'd love to help.")
+	page.AddPlainOutro("Yours truly,")
+	page.AddPlainOutro("Hermes - https://google.com")
+	table := NewTable(NewText("This year sale table", WithTextStyle(map[string]string{"font-size": "1.06rem", "font-weight": "550"})))
+	table.SetHead(NewText("Name"), NewText("Age"), NewText("Price"))
+	table.AddCell(NewText("ZhanSan"), NewText("31"), NewText("185.6"))
+	table.AddCell(NewText("Lisi"), NewText("35"), NewText("102.6"))
+	page.AddTable(table)
 	//page.AddButtonAction("To get started with Hermes, please click here:", NewButton("Confirm your account", "https://example-hermes.com/"))
 	page.AddInviteCodeAction("To get started with Hermes, please click here:", "950038")
 	if str, err := Render(page); err == nil {

+ 62 - 21
theme/default.go

@@ -48,6 +48,10 @@ func (theme *Default) Template() string {
 		margin: 30px 0 20px 0;
 		text-align: center;
 	}
+	
+	.table-wrapper{
+		margin-bottom: 1.5rem;
+	}
 
     .text-success {
       color: #20c55c;
@@ -112,6 +116,48 @@ func (theme *Default) Template() string {
       justify-content: flex-start;
       line-height: 2;
     }
+	
+	.table {
+      border: 1px solid #ddd;
+      width: 100%;
+      max-width: 100%;
+      margin-bottom: 20px;
+      display: table;
+      border-collapse: separate;
+      box-sizing: border-box;
+      text-indent: initial;
+      border-spacing: 2px;
+	  border-collapse: collapse;
+      border-spacing: 0;
+	}
+
+	.table-title{
+	  box-sizing: border-box;
+	  border-left: 3px solid #727cf5;
+	  padding: .38rem 0 .38rem .5rem;
+	  margin-bottom: 1rem;
+	}
+   
+    .table>thead:first-child>tr:first-child>th {
+	  border-top: 0;
+	}
+	
+    .table>thead>tr>th{
+	  vertical-align: bottom;
+      padding: 8px;
+      line-height: 1.42857143;
+      border-bottom-width: 2px;
+	  border: 1px solid #ddd;
+	  text-align: left;
+	}
+
+    .table>tbody>tr>td{
+	  border: 1px solid #ddd;
+	  padding: 8px;
+      line-height: 1.42857143;
+      vertical-align: top;
+	  text-align: left;
+	}
 
     .button {
       padding: .8rem 2rem;
@@ -146,25 +192,21 @@ func (theme *Default) Template() string {
 		{{ if .Page.Head }}<h1>{{ .Page.Head }}</h1>{{ end }}
 		{{ with .Page.Intros }}
 			{{ if gt (len .) 0 }}
-			  {{ range $line := . }}
-				<p>{{ $line }}</p>
+			  {{ range $text := . }}
+				 {{ $text }}
 			  {{ end }}
 			{{ end }}
 		{{ end }}
 		{{ with .Page.Actions }}
 			{{ if gt (len .) 0 }}
 				{{ range $action := . }}
-					{{ if $action.Instructions }} <p> {{ $action.Instructions }} </p> {{ end }}
+					{{ if $action.Instructions }}
+						{{ $action.Instructions }}
+					{{ end }}
 					{{ if $action.InviteCode }}
 						<div class="action-block"><span class="invite-code">{{ $action.InviteCode }}</span></div>
 					{{ else if $action.Button }}
-						<div class="action-block">
-							{{ if $action.Button.Color }}
-							<a class="button" href="{{ $action.Button.Url }}" style="background-color:{{ $action.Button.Color }}">{{ $action.Button.Text }}</a>
-							{{ else }}
-							<a class="button" href="{{ $action.Button.Url }}" >{{ $action.Button.Text }}</a>
-							{{ end }}
-						</div>
+						<div class="action-block">{{ $action.Button }}</div>
 					{{ end }}
 				{{ end }}
 			{{ end }}
@@ -179,15 +221,7 @@ func (theme *Default) Template() string {
 						  {{ range $label,$value := . }}
 							<div class="row">
 							  <div class="preview-label">{{ $label }}</div>
-							  <div class="preview-value">
-							  {{ if $value.Theme }}
-							    <span class="text-{{ $value.Theme }}">{{ $value.Content }}</span>
-							  {{ else if $value.Color }}
-								<span style="color:{{ $value.Theme }}">{{ $value.Content }}</span>
-							  {{ else }}
-								{{ $value.Content }}
-							  {{ end }}
-							  </div>
+							  <div class="preview-value">{{ $value }}</div>
 						    </div>
 						  {{ end }}
 						{{ end }}
@@ -196,10 +230,17 @@ func (theme *Default) Template() string {
 				{{ end }}
 			{{ end }}
 		{{ end }}
+		{{ with .Page.Tables }}
+			{{ if gt (len .) 0 }}
+				{{ range $table := . }}
+					{{ $table }}
+				{{ end }}
+			{{ end }}
+		{{ end }}
 		{{ with .Page.Outros }}
 			{{ if gt (len .) 0 }}
-			  {{ range $line := . }}
-				<p>{{ $line }}</p>
+			  {{ range $text := . }}
+				{{ $text }}
 			  {{ end }}
 			{{ end }}
 		{{ end }}

+ 10 - 0
unexported.go

@@ -0,0 +1,10 @@
+package pgenr
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+func GetUnexportedField(field reflect.Value) interface{} {
+	return reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr())).Elem().Interface()
+}