diff --git a/helper/html/a.gohtml b/helper/html/a.gohtml new file mode 100644 index 0000000..55e533c --- /dev/null +++ b/helper/html/a.gohtml @@ -0,0 +1 @@ +{{.xx}} \ No newline at end of file diff --git a/helper/html/html.go b/helper/html/html.go index a95ad83..133b017 100644 --- a/helper/html/html.go +++ b/helper/html/html.go @@ -1,9 +1,11 @@ package html import ( + "bytes" "fmt" "github.com/dlclark/regexp2" "github.com/fthvgb1/wp-go/helper/slice" + "html/template" "regexp" "strings" ) @@ -164,3 +166,13 @@ func UnClosedTag(s []string) []string { i++ } } + +func RenderedHtml(t *template.Template, data map[string]any) (r string, err error) { + var buf bytes.Buffer + err = t.Execute(&buf, data) + if err != nil { + return + } + r = buf.String() + return +} diff --git a/helper/html/html_test.go b/helper/html/html_test.go index 8a3a192..4dc1ddc 100644 --- a/helper/html/html_test.go +++ b/helper/html/html_test.go @@ -1,6 +1,7 @@ package html import ( + "html/template" "reflect" "testing" ) @@ -228,3 +229,46 @@ func Test_clearTag(t *testing.T) { }) } } + +func TestRenderedHtml(t *testing.T) { + type args struct { + t *template.Template + data map[string]any + } + tests := []struct { + name string + args args + wantR string + wantErr bool + }{ + { + name: "t1", + args: args{ + t: func() *template.Template { + tt, err := template.ParseFiles("./a.gohtml") + if err != nil { + panic(err) + } + return tt + }(), + data: map[string]any{ + "xx": "oo", + }, + }, + wantR: "oo", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotR, err := RenderedHtml(tt.args.t, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("RenderedHtml() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotR != tt.wantR { + t.Errorf("RenderedHtml() gotR = %v, want %v", gotR, tt.wantR) + } + }) + } +} diff --git a/helper/slice/set.go b/helper/slice/set.go new file mode 100644 index 0000000..8963d80 --- /dev/null +++ b/helper/slice/set.go @@ -0,0 +1,114 @@ +package slice + +func IsContained[T comparable](a T, arr []T) bool { + for _, v := range arr { + if a == v { + return true + } + } + return false +} + +func IsContainedByFn[T any](a []T, e T, fn func(i, j T) bool) bool { + for _, t := range a { + if fn(e, t) { + return true + } + } + return false +} + +// Diff return elements which in a and not in b,... +func Diff[T comparable](a []T, b ...[]T) (r []T) { + for _, t := range a { + f := false + for _, ts := range b { + if IsContained(t, ts) { + f = true + break + } + } + if f { + continue + } + r = append(r, t) + } + return +} + +func DiffByFn[T any](a []T, fn func(i, j T) bool, b ...[]T) (r []T) { + for _, t := range a { + f := false + for _, ts := range b { + if IsContainedByFn(ts, t, fn) { + f = true + break + } + } + if f { + continue + } + r = append(r, t) + } + return +} + +func Intersect[T comparable](a []T, b ...[]T) (r []T) { + for _, t := range a { + f := false + for _, ts := range b { + if !IsContained(t, ts) { + f = true + break + } + } + if f { + continue + } + r = append(r, t) + } + return +} + +func IntersectByFn[T any](a []T, fn func(i, j T) bool, b ...[]T) (r []T) { + for _, t := range a { + f := false + for _, ts := range b { + if !IsContainedByFn(ts, t, fn) { + f = true + break + } + } + if f { + continue + } + r = append(r, t) + } + return +} + +func Unique[T comparable](a ...[]T) (r []T) { + m := map[T]struct{}{} + for _, ts := range a { + for _, t := range ts { + if _, ok := m[t]; !ok { + m[t] = struct{}{} + r = append(r, t) + } else { + continue + } + } + } + return +} + +func UniqueByFn[T any](fn func(T, T) bool, a ...[]T) (r []T) { + for _, ts := range a { + for _, t := range ts { + if !IsContainedByFn(r, t, fn) { + r = append(r, t) + } + } + } + return r +} diff --git a/helper/slice/set_test.go b/helper/slice/set_test.go new file mode 100644 index 0000000..a0ad576 --- /dev/null +++ b/helper/slice/set_test.go @@ -0,0 +1,211 @@ +package slice + +import ( + "github.com/fthvgb1/wp-go/helper/number" + "reflect" + "testing" +) + +type x struct { + int + y string +} + +func y(i []int) []x { + return Map(i, func(t int) x { + return x{t, ""} + }) +} + +func TestDiff(t *testing.T) { + type args[T int] struct { + a []T + b [][]T + } + type testCase[T int] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[int]{ + { + name: "t1", + args: args[int]{ + a: number.Range(1, 10, 1), + b: [][]int{number.Range(3, 7, 1), number.Range(6, 9, 1)}, + }, + wantR: []int{1, 2, 10}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := Diff(tt.args.a, tt.args.b...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("Diff() = %v, want %v", gotR, tt.wantR) + } + }) + } +} + +func TestDiffByFn(t *testing.T) { + + type args[T x] struct { + a []T + fn func(i, j T) bool + b [][]T + } + type testCase[T x] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[x]{ + { + name: "t1", + args: args[x]{ + a: y(number.Range(1, 10, 1)), + fn: func(i, j x) bool { + return i.int == j.int + }, + b: [][]x{ + y(number.Range(3, 7, 1)), + y(number.Range(6, 9, 1)), + }, + }, + wantR: []x{{1, ""}, {2, ""}, {10, ""}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := DiffByFn(tt.args.a, tt.args.fn, tt.args.b...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("DiffByFn() = %v, want %v", gotR, tt.wantR) + } + }) + } +} + +func TestIntersect(t *testing.T) { + type args[T int] struct { + a []T + b [][]T + } + type testCase[T int] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[int]{ + { + name: "t1", + args: args[int]{ + a: number.Range(1, 10, 1), + b: [][]int{number.Range(3, 7, 1), number.Range(6, 9, 1)}, + }, + wantR: []int{6, 7}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := Intersect(tt.args.a, tt.args.b...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("Intersect() = %v, want %v", gotR, tt.wantR) + } + }) + } +} + +func TestIntersectByFn(t *testing.T) { + type args[T x] struct { + a []T + fn func(i, j T) bool + b [][]T + } + type testCase[T x] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[x]{ + { + name: "t1", + args: args[x]{ + a: y(number.Range(1, 10, 1)), + fn: func(i, j x) bool { + return i.int == j.int + }, + b: [][]x{ + y(number.Range(3, 7, 1)), + y(number.Range(6, 9, 1)), + }, + }, + wantR: []x{{6, ""}, {7, ""}}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := IntersectByFn(tt.args.a, tt.args.fn, tt.args.b...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("IntersectByFn() = %v, want %v", gotR, tt.wantR) + } + }) + } +} + +func TestUnique(t *testing.T) { + type args[T int] struct { + a [][]T + } + type testCase[T int] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[int]{ + { + name: "t1", + args: args[int]{ + a: [][]int{ + number.Range(1, 5, 1), + number.Range(3, 6, 1), + number.Range(6, 15, 1), + }, + }, + wantR: number.Range(1, 15, 1), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := Unique(tt.args.a...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("Unique() = %v, want %v", gotR, tt.wantR) + } + }) + } +} + +func TestUniqueByFn(t *testing.T) { + type args[T x] struct { + fn func(T, T) bool + a [][]T + } + type testCase[T x] struct { + name string + args args[T] + wantR []T + } + tests := []testCase[x]{ + { + name: "t1", + args: args[x]{ + fn: func(i, j x) bool { + return i.int == j.int + }, + a: [][]x{y([]int{1, 1, 2, 2, 3, 3}), y([]int{2, 2, 4, 4})}, + }, + wantR: y([]int{1, 2, 3, 4}), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if gotR := UniqueByFn(tt.args.fn, tt.args.a...); !reflect.DeepEqual(gotR, tt.wantR) { + t.Errorf("UniqueByFn() = %v, want %v", gotR, tt.wantR) + } + }) + } +} diff --git a/helper/slice/slice.go b/helper/slice/slice.go index 7a96fb2..d45b4ce 100644 --- a/helper/slice/slice.go +++ b/helper/slice/slice.go @@ -1,5 +1,7 @@ package slice +import "github.com/fthvgb1/wp-go/helper" + func Map[T, R any](arr []T, fn func(T) R) []R { r := make([]R, 0, len(arr)) for _, t := range arr { @@ -146,11 +148,19 @@ func Comb[T any](arr []T, m int) (r [][]T) { return r } -func IsContained[T comparable](a T, arr []T) bool { - for _, v := range arr { - if a == v { - return true +func GroupBy[K comparable, T, V any](a []T, fn func(T) (K, V)) map[K][]V { + r := make(map[K][]V) + for _, t := range a { + k, v := fn(t) + if _, ok := r[k]; !ok { + r[k] = []V{v} + } else { + r[k] = append(r[k], v) } } - return false + return r +} + +func ToAnySlice[T any](a []T) []any { + return Map(a, helper.ToAny[T]) } diff --git a/helper/slice/slice_test.go b/helper/slice/slice_test.go index 46f1e0e..126e0f4 100644 --- a/helper/slice/slice_test.go +++ b/helper/slice/slice_test.go @@ -463,3 +463,67 @@ func TestFilterAndMap(t *testing.T) { }) } } + +func TestGroupBy(t *testing.T) { + type args[T x, K int, V string] struct { + a []T + fn func(T) (K, V) + } + type testCase[T x, K int, V string] struct { + name string + args args[T, K, V] + want map[K][]V + } + tests := []testCase[x, int, string]{ + { + name: "t1", + args: args[x, int, string]{ + a: Map([]int{1, 1, 2, 2, 3, 3, 4, 4, 5, 5}, func(t int) x { + return x{t, number.ToString(t)} + }), + fn: func(v x) (int, string) { + return v.int, v.y + }, + }, + want: map[int][]string{ + 1: {"1", "1"}, + 2: {"2", "2"}, + 3: {"3", "3"}, + 4: {"4", "4"}, + 5: {"5", "5"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GroupBy(tt.args.a, tt.args.fn); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GroupBy() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestToAnySlice(t *testing.T) { + type args[T int] struct { + a []T + } + type testCase[T int] struct { + name string + args args[T] + want []any + } + tests := []testCase[int]{ + { + name: "t1", + args: args[int]{number.Range(1, 5, 1)}, + want: []any{1, 2, 3, 4, 5}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ToAnySlice(tt.args.a); !reflect.DeepEqual(got, tt.want) { + t.Errorf("ToAnySlice() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/actions/detail.go b/internal/actions/detail.go index 5d29f48..dafa759 100644 --- a/internal/actions/detail.go +++ b/internal/actions/detail.go @@ -2,7 +2,6 @@ package actions import ( "fmt" - str "github.com/fthvgb1/wp-go/helper/strings" "github.com/fthvgb1/wp-go/internal/pkg/cache" "github.com/fthvgb1/wp-go/internal/pkg/logs" "github.com/fthvgb1/wp-go/internal/pkg/models" @@ -30,7 +29,7 @@ func Detail(c *gin.Context) { archive := cache.Archives(c) categoryItems := cache.Categories(c) recentComments := cache.RecentComments(c, 5) - var h = gin.H{ + var ginH = gin.H{ "title": wpconfig.Options.Value("blogname"), "options": wpconfig.Options, "recentPosts": recent, @@ -39,17 +38,21 @@ func Detail(c *gin.Context) { "recentComments": recentComments, } isApproveComment := false + status := plugins.Ok defer func() { - status := http.StatusOK + code := http.StatusOK if err != nil { - status = http.StatusInternalServerError + code = http.StatusNotFound c.Error(err) + status = plugins.Error return } if isApproveComment == true { return } - c.HTML(status, str.Join(theme.GetTemplateName(), "/posts/detail.gohtml"), h) + + t := theme.GetTemplateName() + theme.Hook(t, code, c, ginH, plugins.Detail, status) }() id := c.Param("id") Id := 0 @@ -92,19 +95,19 @@ func Detail(c *gin.Context) { commentss := treeComments(comments) prev, next, err := cache.GetContextPost(c, post.Id, post.PostDate) logs.ErrPrintln(err, "get pre and next post", post.Id, post.PostDate) - h["title"] = fmt.Sprintf("%s-%s", post.PostTitle, wpconfig.Options.Value("blogname")) - h["post"] = post - h["showComment"] = showComment - h["prev"] = prev + ginH["title"] = fmt.Sprintf("%s-%s", post.PostTitle, wpconfig.Options.Value("blogname")) + ginH["post"] = post + ginH["showComment"] = showComment + ginH["prev"] = prev depth := wpconfig.Options.Value("thread_comments_depth") d, err := strconv.Atoi(depth) if err != nil { - logs.ErrPrintln(err, "get comment depth") + logs.ErrPrintln(err, "get comment depth ", depth) d = 5 } - h["comments"] = hh.formatComment(commentss, 1, d) - h["next"] = next - h["user"] = user + ginH["comments"] = hh.formatComment(commentss, 1, d) + ginH["next"] = next + ginH["user"] = user } type Comment struct { diff --git a/internal/middleware/sendmail.go b/internal/middleware/sendmail.go index 2653a52..0870d1f 100644 --- a/internal/middleware/sendmail.go +++ b/internal/middleware/sendmail.go @@ -21,34 +21,37 @@ func RecoverAndSendMail(w io.Writer) func(ctx *gin.Context) { return gin.CustomRecoveryWithWriter(w, func(ctx *gin.Context, err any) { c := ctx.Copy() stack := stack(4) - go func() { - httpRequest, _ := httputil.DumpRequest(c.Request, true) - headers := strings.Split(string(httpRequest), "\r\n") - for idx, header := range headers { - current := strings.Split(header, ":") - if current[0] == "Authorization" { - headers[idx] = current[0] + ": *" + if gin.Mode() == gin.ReleaseMode { + go func() { + httpRequest, _ := httputil.DumpRequest(c.Request, true) + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } } - } - headersToStr := strings.Join(headers, "
") - content := `
err:
%v

+ headersToStr := strings.Join(headers, "
") + content := `
err:
%v

stack:
%v

headers:
%s
` - content = fmt.Sprintf(content, - err, - formatStack(string(stack)), - headersToStr, - ) + content = fmt.Sprintf(content, + err, + formatStack(string(stack)), + headersToStr, + ) - er := mail.SendMail( - []string{config.Conf.Load().Mail.User}, - fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content) + er := mail.SendMail( + []string{config.Conf.Load().Mail.User}, + fmt.Sprintf("%s%s %s 发生错误", fmt.Sprintf(wpconfig.Options.Value("siteurl")), c.FullPath(), time.Now().Format(time.RFC1123Z)), content) + + if er != nil { + logs.ErrPrintln(er, "recover send mail fail", fmt.Sprintf("%v", err)) + } + }() + } - if er != nil { - logs.ErrPrintln(er, "recover send mail fail", fmt.Sprintf("%v", err)) - } - }() ctx.AbortWithStatus(http.StatusInternalServerError) }) } diff --git a/internal/pkg/dao/comments.go b/internal/pkg/dao/comments.go index 7c253c2..91ac576 100644 --- a/internal/pkg/dao/comments.go +++ b/internal/pkg/dao/comments.go @@ -2,7 +2,6 @@ package dao import ( "context" - "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/model" @@ -48,7 +47,7 @@ func GetCommentByIds(args ...any) (map[uint64]models.Comments, error) { m := make(map[uint64]models.Comments) r, err := model.SimpleFind[models.Comments](ctx, model.SqlBuilder{ {"comment_ID", "in", ""}, {"comment_approved", "1"}, - }, "*", slice.Map(ids, helper.ToAny[uint64])) + }, "*", slice.ToAnySlice(ids)) if err != nil { return m, err } diff --git a/internal/pkg/dao/postmeta.go b/internal/pkg/dao/postmeta.go index f197d8a..a8758ff 100644 --- a/internal/pkg/dao/postmeta.go +++ b/internal/pkg/dao/postmeta.go @@ -3,7 +3,6 @@ package dao import ( "context" "fmt" - "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/maps" "github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/internal/pkg/logs" @@ -19,7 +18,7 @@ func GetPostMetaByPostIds(args ...any) (r map[uint64]map[string]any, err error) ids := args[1].([]uint64) rr, err := model.Find[models.Postmeta](ctx, model.SqlBuilder{ {"post_id", "in", ""}, - }, "*", "", nil, nil, nil, 0, slice.Map(ids, helper.ToAny[uint64])) + }, "*", "", nil, nil, nil, 0, slice.ToAnySlice(ids)) if err != nil { return } @@ -93,6 +92,7 @@ func thumbnail(metadata models.WpAttachmentMetadata, thumbType, host string) (r } else if r.Width >= 767 { r.Sizes = "(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px" } + r.OriginAttachmentData = metadata } return } diff --git a/internal/pkg/dao/posts.go b/internal/pkg/dao/posts.go index e58e5f9..089c586 100644 --- a/internal/pkg/dao/posts.go +++ b/internal/pkg/dao/posts.go @@ -4,7 +4,6 @@ import ( "context" "database/sql" "fmt" - "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/slice" "github.com/fthvgb1/wp-go/internal/pkg/models" "github.com/fthvgb1/wp-go/internal/wpconfig" @@ -18,7 +17,7 @@ func GetPostsByIds(ids ...any) (m map[uint64]models.Posts, err error) { ctx := ids[0].(context.Context) m = make(map[uint64]models.Posts) id := ids[1].([]uint64) - arg := slice.Map(id, helper.ToAny[uint64]) + arg := slice.ToAnySlice(id) rawPosts, err := model.Find[models.Posts](ctx, model.SqlBuilder{{ "Id", "in", "", }}, "a.*,ifnull(d.name,'') category_name,ifnull(taxonomy,'') `taxonomy`", "", nil, model.SqlBuilder{{ diff --git a/internal/pkg/models/wp_posts.go b/internal/pkg/models/wp_posts.go index f762a77..150ab59 100644 --- a/internal/pkg/models/wp_posts.go +++ b/internal/pkg/models/wp_posts.go @@ -38,12 +38,16 @@ type Posts struct { AttachmentMetadata WpAttachmentMetadata } +type Image struct { +} + type PostThumbnail struct { - Path string - Width int - Height int - Srcset string - Sizes string + Path string + Width int + Height int + Srcset string + Sizes string + OriginAttachmentData WpAttachmentMetadata } func (w Posts) PrimaryKey() string { diff --git a/internal/theme/twentyseventeen/twentyseventeen.go b/internal/theme/twentyseventeen/twentyseventeen.go index 291c1de..a284ca7 100644 --- a/internal/theme/twentyseventeen/twentyseventeen.go +++ b/internal/theme/twentyseventeen/twentyseventeen.go @@ -52,6 +52,7 @@ func Hook(status int, c *gin.Context, h gin.H, scene, stats int) { } } } else if scene == plugins.Detail { + h["HeaderImage"] = getHeaderImage(c) templ = "twentyseventeen/posts/detail.gohtml" } c.HTML(status, templ, h) diff --git a/model/query_test.go b/model/query_test.go index f742527..0674c42 100644 --- a/model/query_test.go +++ b/model/query_test.go @@ -3,7 +3,6 @@ package model import ( "context" "database/sql" - "github.com/fthvgb1/wp-go/helper" "github.com/fthvgb1/wp-go/helper/number" "github.com/fthvgb1/wp-go/helper/slice" _ "github.com/go-sql-driver/mysql" @@ -499,10 +498,10 @@ func TestSimplePagination(t *testing.T) { order: nil, join: nil, having: nil, - in: [][]any{slice.Map[int, any](number.Range(431, 440, 1), helper.ToAny[int])}, + in: [][]any{slice.ToAnySlice(number.Range(431, 440, 1))}, }, wantR: func() (r []post) { - r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?,?,?,?)", slice.Map[int, any](number.Range(431, 435, 1), helper.ToAny[int])...) + r, err := Select[post](ctx, "select * from "+post{}.Table()+" where ID in (?,?,?,?,?)", slice.ToAnySlice(number.Range(431, 435, 1))...) if err != nil && err != sql.ErrNoRows { panic(err) } else if err == sql.ErrNoRows { diff --git a/multipTemplate/multiptemplate.go b/multipTemplate/multiptemplate.go index 07dd545..4c886c9 100644 --- a/multipTemplate/multiptemplate.go +++ b/multipTemplate/multiptemplate.go @@ -17,6 +17,22 @@ type MultipleFsTemplate struct { Fs embed.FS } +func (t *MultipleFileTemplate) AppendTemplate(name string, templates ...string) *MultipleFileTemplate { + tmpl, ok := t.Template[name] + if ok { + t.Template[name] = template.Must(tmpl.ParseFiles(templates...)) + } + return t +} + +func (t *MultipleFsTemplate) AppendTemplate(name string, templates ...string) *MultipleFsTemplate { + tmpl, ok := t.Template[name] + if ok { + t.Template[name] = template.Must(tmpl.ParseFS(t.Fs, templates...)) + } + return t +} + func NewFileTemplate() *MultipleFileTemplate { return &MultipleFileTemplate{ Template: make(map[string]*template.Template), @@ -47,9 +63,6 @@ func (t *MultipleFileTemplate) AddTemplate(mainTemplatePattern string, fnMap tem panic(err) } for _, mainTemplate := range mainTemplates { - if _, ok := t.Template[mainTemplate]; ok { - panic("exists same Template " + mainTemplate) - } file := filepath.Base(mainTemplate) pattern := append([]string{mainTemplate}, layoutTemplatePattern...) t.Template[mainTemplate] = template.Must(template.New(file).Funcs(fnMap).ParseFiles(pattern...))