diff --git a/config.example.yaml b/config.example.yaml
index 170218f..a614e89 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -71,3 +71,7 @@ trustIps: []
trustServerNames: ["xy.test","blog.xy.test"]
# port
port: 8082
+# 主题 为空值为option template,没有就默认为twentyfifteen
+theme: "twentyfifteen"
+# 文档排序默认升序还是降序
+postOrder: "desc"
\ No newline at end of file
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/actions/index.go b/internal/actions/index.go
index 7d8af97..b7c82b6 100644
--- a/internal/actions/index.go
+++ b/internal/actions/index.go
@@ -7,6 +7,7 @@ import (
"github.com/fthvgb1/wp-go/helper/slice"
str "github.com/fthvgb1/wp-go/helper/strings"
"github.com/fthvgb1/wp-go/internal/pkg/cache"
+ "github.com/fthvgb1/wp-go/internal/pkg/config"
"github.com/fthvgb1/wp-go/internal/pkg/dao"
"github.com/fthvgb1/wp-go/internal/pkg/logs"
"github.com/fthvgb1/wp-go/internal/pkg/models"
@@ -92,10 +93,17 @@ func (h *indexHandle) getSearchKey() string {
return fmt.Sprintf("action:%s|%s|%s|%s|%s|%s|%d|%d", h.author, h.search, h.orderBy, h.order, h.category, h.categoryType, h.page, h.pageSize)
}
+var orders = []string{"asc", "desc"}
+
func (h *indexHandle) parseParams() (err error) {
h.order = h.c.Query("order")
- if !slice.IsContained(h.order, []string{"asc", "desc"}) {
+
+ if !slice.IsContained(h.order, orders) {
+ order := config.Conf.Load().PostOrder
h.order = "asc"
+ if order != "" && slice.IsContained(order, orders) {
+ h.order = order
+ }
}
year := h.c.Param("year")
if year != "" {
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 := `